Upgrade bugs.webkit.org to Bugzilla 4.2
authorddkilzer@apple.com <ddkilzer@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 16 Oct 2014 16:00:58 +0000 (16:00 +0000)
committerddkilzer@apple.com <ddkilzer@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 16 Oct 2014 16:00:58 +0000 (16:00 +0000)
<http://webkit.org/b/55882>

Upgrade to Bugzilla 4.2.1.

Conflicts:
    .htaccess
    Bugzilla.pm
    Bugzilla/Auth.pm
    Bugzilla/Auth/Login/CGI.pm
    Bugzilla/Auth/Persist/Cookie.pm
    Bugzilla/Bug.pm
    Bugzilla/BugMail.pm
    Bugzilla/CGI.pm
    Bugzilla/Config/Attachment.pm
    Bugzilla/Config/Common.pm
    Bugzilla/Config/General.pm
    Bugzilla/Constants.pm
    Bugzilla/DB/Mysql.pm
    Bugzilla/DB/Oracle.pm
    Bugzilla/DB/Schema.pm
    Bugzilla/DB/Schema/Oracle.pm
    Bugzilla/Error.pm
    Bugzilla/Flag.pm
    Bugzilla/FlagType.pm
    Bugzilla/Hook.pm
    Bugzilla/Install/DB.pm
    Bugzilla/Install/Filesystem.pm
    Bugzilla/Install/Localconfig.pm
    Bugzilla/Install/Requirements.pm
    Bugzilla/Install/Util.pm
    Bugzilla/Mailer.pm
    Bugzilla/Product.pm
    Bugzilla/Search.pm
    Bugzilla/Search/Quicksearch.pm
    Bugzilla/Search/Saved.pm
    Bugzilla/Series.pm
    Bugzilla/Template.pm
    Bugzilla/Template/Plugin/Hook.pm
    Bugzilla/Token.pm
    Bugzilla/User.pm
    Bugzilla/Util.pm
    Bugzilla/WebService.pm
    Bugzilla/WebService/Bug.pm
    Bugzilla/WebService/Bugzilla.pm
    Bugzilla/WebService/Constants.pm
    Bugzilla/WebService/Product.pm
    Bugzilla/WebService/User.pm
    attachment.cgi
    buglist.cgi
    checksetup.pl
    colchange.cgi
    collectstats.pl
    contrib/bugzilla_ldapsync.rb
    contrib/bzdbcopy.pl
    contrib/gnats2bz.pl
    contrib/recode.pl
    contrib/sendbugmail.pl
    contrib/yp_nomail.sh
    docs/en/xml/Bugzilla-Guide.xml
    docs/en/xml/about.xml
    docs/en/xml/installation.xml
    docs/en/xml/security.xml
    docs/en/xml/troubleshooting.xml
    editflagtypes.cgi
    editparams.cgi
    editproducts.cgi
    editvalues.cgi
    editwhines.cgi
    email_in.pl
    enter_bug.cgi
    extensions/BmpConvert/Config.pm
    extensions/OldBugMove/template/en/default/admin/params/oldbugmove.html.tmpl
    extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl
    extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl
    extensions/example/code/webservice-error_codes.pl
    extensions/example/version.pl
    images/favicon.ico
    importxml.pl
    index.cgi
    install-module.pl
    js/field.js
    js/util.js
    long_list.cgi
    mod_perl.pl
    post_bug.cgi
    process_bug.cgi
    quips.cgi
    sanitycheck.cgi
    show_bug.cgi
    showattachment.cgi
    sidebar.cgi
    skins/contrib/Dusk/global.css
    skins/contrib/Dusk/index.css
    skins/standard/global.css
    skins/standard/show_bug.css
    t/008filter.t
    template/en/custom/attachment/review.html.tmpl
    template/en/default/account/prefs/saved-searches.html.tmpl
    template/en/default/admin/classifications/delete.html.tmpl
    template/en/default/admin/classifications/edit-common.html.tmpl
    template/en/default/admin/classifications/footer.html.tmpl
    template/en/default/admin/components/create.html.tmpl
    template/en/default/admin/components/edit.html.tmpl
    template/en/default/admin/params/attachment.html.tmpl
    template/en/default/admin/sanitycheck/messages.html.tmpl
    template/en/default/admin/users/confirm-delete.html.tmpl
    template/en/default/admin/workflow/edit.html.tmpl
    template/en/default/attachment/created.html.tmpl
    template/en/default/attachment/diff-header.html.tmpl
    template/en/default/attachment/edit.html.tmpl
    template/en/default/attachment/list.html.tmpl
    template/en/default/attachment/updated.html.tmpl
    template/en/default/bug/comments.html.tmpl
    template/en/default/bug/create/create-guided.html.tmpl
    template/en/default/bug/create/create.html.tmpl
    template/en/default/bug/create/created.html.tmpl
    template/en/default/bug/edit.html.tmpl
    template/en/default/bug/field.html.tmpl
    template/en/default/bug/process/header.html.tmpl
    template/en/default/bug/show.html.tmpl
    template/en/default/bug/show.xml.tmpl
    template/en/default/config.rdf.tmpl
    template/en/default/email/whine.txt.tmpl
    template/en/default/filterexceptions.pl
    template/en/default/flag/list.html.tmpl
    template/en/default/global/common-links.html.tmpl
    template/en/default/global/confirm-action.html.tmpl
    template/en/default/global/field-descs.none.tmpl
    template/en/default/global/footer.html.tmpl
    template/en/default/global/header.html.tmpl
    template/en/default/global/user-error.html.tmpl
    template/en/default/global/userselect.html.tmpl
    template/en/default/list/edit-multiple.html.tmpl
    template/en/default/list/list.html.tmpl
    template/en/default/pages/fields.html.tmpl
    template/en/default/pages/release-notes.html.tmpl
    template/en/default/search/boolean-charts.html.tmpl
    template/en/default/search/form.html.tmpl
    template/en/default/search/search-report-graph.html.tmpl
    template/en/default/search/search-report-table.html.tmpl
    template/en/default/setup/strings.txt.pl
    token.cgi
    userprefs.cgi
    xml.cgi
    xmlrpc.cgi

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@174764 268f45cc-cd09-0410-ab3c-d52691b4dbfc

784 files changed:
Websites/bugs.webkit.org/.bzrignore [new file with mode: 0644]
Websites/bugs.webkit.org/.cvsignore [deleted file]
Websites/bugs.webkit.org/.htaccess
Websites/bugs.webkit.org/Bugzilla.pm
Websites/bugs.webkit.org/Bugzilla/.cvsignore [deleted file]
Websites/bugs.webkit.org/Bugzilla/Attachment.pm
Websites/bugs.webkit.org/Bugzilla/Attachment/PatchReader.pm
Websites/bugs.webkit.org/Bugzilla/Auth.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Login.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Login/CGI.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Login/Cookie.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Login/Env.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Login/Stack.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Persist/Cookie.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Verify.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Verify/DB.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Verify/LDAP.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Verify/Stack.pm
Websites/bugs.webkit.org/Bugzilla/Bug.pm
Websites/bugs.webkit.org/Bugzilla/BugMail.pm
Websites/bugs.webkit.org/Bugzilla/BugUrl.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/BugUrl/Bugzilla.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/BugUrl/Bugzilla/Local.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/BugUrl/Debian.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/BugUrl/Google.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/BugUrl/JIRA.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/BugUrl/Launchpad.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/BugUrl/MantisBT.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/BugUrl/SourceForge.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/BugUrl/Trac.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/CGI.pm
Websites/bugs.webkit.org/Bugzilla/Chart.pm
Websites/bugs.webkit.org/Bugzilla/Classification.pm
Websites/bugs.webkit.org/Bugzilla/Comment.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Component.pm
Websites/bugs.webkit.org/Bugzilla/Config.pm
Websites/bugs.webkit.org/Bugzilla/Config/Admin.pm
Websites/bugs.webkit.org/Bugzilla/Config/Advanced.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Config/Attachment.pm
Websites/bugs.webkit.org/Bugzilla/Config/Auth.pm
Websites/bugs.webkit.org/Bugzilla/Config/BugChange.pm
Websites/bugs.webkit.org/Bugzilla/Config/BugFields.pm
Websites/bugs.webkit.org/Bugzilla/Config/Common.pm
Websites/bugs.webkit.org/Bugzilla/Config/Core.pm
Websites/bugs.webkit.org/Bugzilla/Config/DependencyGraph.pm
Websites/bugs.webkit.org/Bugzilla/Config/General.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Config/GroupSecurity.pm
Websites/bugs.webkit.org/Bugzilla/Config/LDAP.pm
Websites/bugs.webkit.org/Bugzilla/Config/MTA.pm
Websites/bugs.webkit.org/Bugzilla/Config/PatchViewer.pm
Websites/bugs.webkit.org/Bugzilla/Config/Query.pm
Websites/bugs.webkit.org/Bugzilla/Config/RADIUS.pm
Websites/bugs.webkit.org/Bugzilla/Config/ShadowDB.pm
Websites/bugs.webkit.org/Bugzilla/Config/UserMatch.pm
Websites/bugs.webkit.org/Bugzilla/Constants.pm
Websites/bugs.webkit.org/Bugzilla/DB.pm
Websites/bugs.webkit.org/Bugzilla/DB/Mysql.pm
Websites/bugs.webkit.org/Bugzilla/DB/Oracle.pm
Websites/bugs.webkit.org/Bugzilla/DB/Pg.pm
Websites/bugs.webkit.org/Bugzilla/DB/Schema.pm
Websites/bugs.webkit.org/Bugzilla/DB/Schema/Mysql.pm
Websites/bugs.webkit.org/Bugzilla/DB/Schema/Oracle.pm
Websites/bugs.webkit.org/Bugzilla/DB/Schema/Pg.pm
Websites/bugs.webkit.org/Bugzilla/DB/Schema/Sqlite.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/DB/Sqlite.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Error.pm
Websites/bugs.webkit.org/Bugzilla/Extension.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Field.pm
Websites/bugs.webkit.org/Bugzilla/Field/Choice.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Field/ChoiceInterface.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Flag.pm
Websites/bugs.webkit.org/Bugzilla/FlagType.pm
Websites/bugs.webkit.org/Bugzilla/Group.pm
Websites/bugs.webkit.org/Bugzilla/Hook.pm
Websites/bugs.webkit.org/Bugzilla/Install.pm
Websites/bugs.webkit.org/Bugzilla/Install/CPAN.pm
Websites/bugs.webkit.org/Bugzilla/Install/DB.pm
Websites/bugs.webkit.org/Bugzilla/Install/Filesystem.pm
Websites/bugs.webkit.org/Bugzilla/Install/Localconfig.pm
Websites/bugs.webkit.org/Bugzilla/Install/Requirements.pm
Websites/bugs.webkit.org/Bugzilla/Install/Util.pm
Websites/bugs.webkit.org/Bugzilla/Job/Mailer.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/JobQueue.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/JobQueue/Runner.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Keyword.pm
Websites/bugs.webkit.org/Bugzilla/Mailer.pm
Websites/bugs.webkit.org/Bugzilla/Migrate.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Migrate/Gnats.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Milestone.pm
Websites/bugs.webkit.org/Bugzilla/Object.pm
Websites/bugs.webkit.org/Bugzilla/Product.pm
Websites/bugs.webkit.org/Bugzilla/RNG.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Search.pm
Websites/bugs.webkit.org/Bugzilla/Search/Clause.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Search/Condition.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Search/Quicksearch.pm
Websites/bugs.webkit.org/Bugzilla/Search/Recent.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Search/Saved.pm
Websites/bugs.webkit.org/Bugzilla/Series.pm
Websites/bugs.webkit.org/Bugzilla/Status.pm
Websites/bugs.webkit.org/Bugzilla/Template.pm
Websites/bugs.webkit.org/Bugzilla/Template/Context.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Template/Parser.pm [deleted file]
Websites/bugs.webkit.org/Bugzilla/Template/Plugin/Hook.pm
Websites/bugs.webkit.org/Bugzilla/Token.pm
Websites/bugs.webkit.org/Bugzilla/Update.pm
Websites/bugs.webkit.org/Bugzilla/User.pm
Websites/bugs.webkit.org/Bugzilla/User/Setting.pm
Websites/bugs.webkit.org/Bugzilla/User/Setting/Timezone.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Util.pm
Websites/bugs.webkit.org/Bugzilla/Version.pm
Websites/bugs.webkit.org/Bugzilla/WebService.pm
Websites/bugs.webkit.org/Bugzilla/WebService/Bug.pm
Websites/bugs.webkit.org/Bugzilla/WebService/Bugzilla.pm
Websites/bugs.webkit.org/Bugzilla/WebService/Constants.pm
Websites/bugs.webkit.org/Bugzilla/WebService/Group.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/WebService/Product.pm
Websites/bugs.webkit.org/Bugzilla/WebService/README [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/WebService/Server.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/WebService/Server/JSONRPC.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/WebService/Server/XMLRPC.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/WebService/User.pm
Websites/bugs.webkit.org/Bugzilla/WebService/Util.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Whine.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Whine/Query.pm [new file with mode: 0644]
Websites/bugs.webkit.org/Bugzilla/Whine/Schedule.pm [new file with mode: 0644]
Websites/bugs.webkit.org/QUICKSTART [deleted file]
Websites/bugs.webkit.org/README
Websites/bugs.webkit.org/UPGRADING [deleted file]
Websites/bugs.webkit.org/UPGRADING-pre-2.8 [deleted file]
Websites/bugs.webkit.org/attachment.cgi
Websites/bugs.webkit.org/buglist.cgi
Websites/bugs.webkit.org/bugzilla.dtd
Websites/bugs.webkit.org/chart.cgi
Websites/bugs.webkit.org/checksetup.pl
Websites/bugs.webkit.org/colchange.cgi
Websites/bugs.webkit.org/collectstats.pl
Websites/bugs.webkit.org/config.cgi
Websites/bugs.webkit.org/contrib/README
Websites/bugs.webkit.org/contrib/bugzilla-queue.rhel [new file with mode: 0755]
Websites/bugs.webkit.org/contrib/bugzilla-queue.suse [new file with mode: 0755]
Websites/bugs.webkit.org/contrib/bugzilla-submit/bugdata.txt [changed mode: 0644->0755]
Websites/bugs.webkit.org/contrib/bugzilla-submit/bugzilla-submit
Websites/bugs.webkit.org/contrib/bugzilla-submit/bugzilla-submit.xml [changed mode: 0644->0755]
Websites/bugs.webkit.org/contrib/bugzilla_ldapsync.rb [deleted file]
Websites/bugs.webkit.org/contrib/bz_webservice_demo.pl
Websites/bugs.webkit.org/contrib/bzdbcopy.pl
Websites/bugs.webkit.org/contrib/cmdline/query.conf [changed mode: 0644->0755]
Websites/bugs.webkit.org/contrib/console.pl [new file with mode: 0755]
Websites/bugs.webkit.org/contrib/convert-workflow.pl [new file with mode: 0755]
Websites/bugs.webkit.org/contrib/cvs-update.pl [changed mode: 0644->0755]
Websites/bugs.webkit.org/contrib/extension-convert.pl [new file with mode: 0755]
Websites/bugs.webkit.org/contrib/fixperms.pl [moved from Websites/bugs.webkit.org/extensions/example/code/webservice-error_codes.pl with 67% similarity, mode: 0755]
Websites/bugs.webkit.org/contrib/gnats2bz.pl [deleted file]
Websites/bugs.webkit.org/contrib/gnatsparse/README [deleted file]
Websites/bugs.webkit.org/contrib/gnatsparse/gnatsparse.py [deleted file]
Websites/bugs.webkit.org/contrib/gnatsparse/magic.py [deleted file]
Websites/bugs.webkit.org/contrib/gnatsparse/specialuu.py [deleted file]
Websites/bugs.webkit.org/contrib/jb2bz.py [changed mode: 0644->0755]
Websites/bugs.webkit.org/contrib/merge-users.pl [changed mode: 0644->0755]
Websites/bugs.webkit.org/contrib/new-yui.sh [new file with mode: 0755]
Websites/bugs.webkit.org/contrib/recode.pl
Websites/bugs.webkit.org/contrib/sendbugmail.pl [changed mode: 0644->0755]
Websites/bugs.webkit.org/contrib/sendunsentbugmail.pl [changed mode: 0644->0755]
Websites/bugs.webkit.org/contrib/yp_nomail.sh [deleted file]
Websites/bugs.webkit.org/createaccount.cgi
Websites/bugs.webkit.org/describecomponents.cgi
Websites/bugs.webkit.org/describekeywords.cgi
Websites/bugs.webkit.org/docs/en/.cvsignore [deleted file]
Websites/bugs.webkit.org/docs/en/README.docs
Websites/bugs.webkit.org/docs/en/images/bzLifecycle.png
Websites/bugs.webkit.org/docs/en/images/bzLifecycle.xml
Websites/bugs.webkit.org/docs/en/images/caution.gif
Websites/bugs.webkit.org/docs/en/images/tip.gif
Websites/bugs.webkit.org/docs/en/images/warning.gif
Websites/bugs.webkit.org/docs/en/rel_notes.txt
Websites/bugs.webkit.org/docs/en/xml/.cvsignore [deleted file]
Websites/bugs.webkit.org/docs/en/xml/Bugzilla-Guide.xml
Websites/bugs.webkit.org/docs/en/xml/about.xml
Websites/bugs.webkit.org/docs/en/xml/administration.xml
Websites/bugs.webkit.org/docs/en/xml/customization.xml
Websites/bugs.webkit.org/docs/en/xml/gfdl.xml
Websites/bugs.webkit.org/docs/en/xml/glossary.xml
Websites/bugs.webkit.org/docs/en/xml/installation.xml
Websites/bugs.webkit.org/docs/en/xml/integration.xml [deleted file]
Websites/bugs.webkit.org/docs/en/xml/introduction.xml [deleted file]
Websites/bugs.webkit.org/docs/en/xml/modules.xml
Websites/bugs.webkit.org/docs/en/xml/patches.xml
Websites/bugs.webkit.org/docs/en/xml/requiredsoftware.xml [deleted file]
Websites/bugs.webkit.org/docs/en/xml/security.xml
Websites/bugs.webkit.org/docs/en/xml/troubleshooting.xml
Websites/bugs.webkit.org/docs/en/xml/using.xml
Websites/bugs.webkit.org/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
Websites/bugs.webkit.org/docs/makedocs.pl
Websites/bugs.webkit.org/duplicates.cgi
Websites/bugs.webkit.org/editclassifications.cgi
Websites/bugs.webkit.org/editcomponents.cgi
Websites/bugs.webkit.org/editfields.cgi
Websites/bugs.webkit.org/editflagtypes.cgi
Websites/bugs.webkit.org/editgroups.cgi
Websites/bugs.webkit.org/editkeywords.cgi
Websites/bugs.webkit.org/editmilestones.cgi
Websites/bugs.webkit.org/editparams.cgi
Websites/bugs.webkit.org/editproducts.cgi
Websites/bugs.webkit.org/editusers.cgi
Websites/bugs.webkit.org/editvalues.cgi
Websites/bugs.webkit.org/editversions.cgi
Websites/bugs.webkit.org/editwhines.cgi
Websites/bugs.webkit.org/editworkflow.cgi
Websites/bugs.webkit.org/email_in.pl
Websites/bugs.webkit.org/enter_bug.cgi
Websites/bugs.webkit.org/extensions/BmpConvert/Config.pm [moved from Websites/bugs.webkit.org/extensions/example/code/install-before_final_checks.pl with 50% similarity]
Websites/bugs.webkit.org/extensions/BmpConvert/Extension.pm [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/BmpConvert/disabled [moved from Websites/bugs.webkit.org/extensions/example/disabled with 100% similarity]
Websites/bugs.webkit.org/extensions/Example/Config.pm [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Example/Extension.pm [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Example/disabled [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Example/lib/Auth/Login.pm [moved from Websites/bugs.webkit.org/extensions/example/code/colchange-columns.pl with 62% similarity]
Websites/bugs.webkit.org/extensions/Example/lib/Auth/Verify.pm [moved from Websites/bugs.webkit.org/extensions/example/code/config.pl with 69% similarity]
Websites/bugs.webkit.org/extensions/Example/lib/Config.pm [moved from Websites/bugs.webkit.org/extensions/example/lib/ConfigExample.pm with 94% similarity]
Websites/bugs.webkit.org/extensions/Example/lib/Util.pm [moved from Websites/bugs.webkit.org/extensions/example/code/webservice.pl with 85% similarity]
Websites/bugs.webkit.org/extensions/Example/lib/WebService.pm [moved from Websites/bugs.webkit.org/extensions/example/lib/WSExample.pm with 90% similarity]
Websites/bugs.webkit.org/extensions/Example/template/en/default/account/prefs/my_tab.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Example/template/en/default/admin/params/example.html.tmpl [moved from Websites/bugs.webkit.org/extensions/example/template/en/default/admin/params/example.html.tmpl with 100% similarity]
Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/global/setting-descs-settings.none.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/global/user-error-errors.html.tmpl [moved from Websites/bugs.webkit.org/extensions/example/template/en/global/user-error-errors.html.tmpl with 81% similarity]
Websites/bugs.webkit.org/extensions/Example/template/en/default/pages/example.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Example/template/en/default/setup/strings.txt.pl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/OldBugMove/Config.pm [moved from Websites/bugs.webkit.org/extensions/example/code/product-confirm_delete.pl with 57% similarity]
Websites/bugs.webkit.org/extensions/OldBugMove/Extension.pm [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/OldBugMove/disabled [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/OldBugMove/lib/Params.pm [moved from Websites/bugs.webkit.org/Bugzilla/Config/BugMove.pm with 73% similarity]
Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/admin/params/oldbugmove.html.tmpl [moved from Websites/bugs.webkit.org/template/en/default/attachment/content-types.html.tmpl with 54% similarity]
Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/bug/format_comment-type.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/global/user-error-auth_failure_action.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/global/user-error-errors.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/list/edit-multiple-after_groups.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/Extension.pm [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/disabled [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/show-header-end.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/code-error-errors.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/search/form-after_freetext_fields.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting.html.tmpl [moved from Websites/bugs.webkit.org/template/en/default/pages/voting.html.tmpl with 97% similarity]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl [moved from Websites/bugs.webkit.org/template/en/default/bug/votes/list-for-bug.html.tmpl with 73% similarity]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting/user.html.tmpl [moved from Websites/bugs.webkit.org/template/en/default/bug/votes/list-for-user.html.tmpl with 74% similarity]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/voting/delete-all.html.tmpl [moved from Websites/bugs.webkit.org/template/en/default/bug/votes/delete-all.html.tmpl with 91% similarity]
Websites/bugs.webkit.org/extensions/Voting/template/en/default/voting/votes-removed.txt.tmpl [moved from Websites/bugs.webkit.org/template/en/default/email/votes-removed.txt.tmpl with 100% similarity]
Websites/bugs.webkit.org/extensions/Voting/web/style.css [moved from Websites/bugs.webkit.org/skins/standard/voting.css with 94% similarity]
Websites/bugs.webkit.org/extensions/create.pl [new file with mode: 0755]
Websites/bugs.webkit.org/extensions/example/code/bug-end_of_update.pl [deleted file]
Websites/bugs.webkit.org/extensions/example/code/buglist-columns.pl [deleted file]
Websites/bugs.webkit.org/extensions/example/code/flag-end_of_update.pl [deleted file]
Websites/bugs.webkit.org/extensions/example/info.pl [deleted file]
Websites/bugs.webkit.org/importxml.pl
Websites/bugs.webkit.org/index.cgi
Websites/bugs.webkit.org/install-module.pl
Websites/bugs.webkit.org/jobqueue.pl [new file with mode: 0755]
Websites/bugs.webkit.org/js/TUI.js
Websites/bugs.webkit.org/js/attachment.js
Websites/bugs.webkit.org/js/bug.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/change-columns.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/comments.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/custom-search.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/field.js
Websites/bugs.webkit.org/js/flag.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/global.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/help.js [deleted file]
Websites/bugs.webkit.org/js/history.js/license.txt [new file with mode: 0644]
Websites/bugs.webkit.org/js/history.js/native.history.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/history.js/readme.txt [new file with mode: 0644]
Websites/bugs.webkit.org/js/util.js
Websites/bugs.webkit.org/js/yui/animation/animation-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/ajax-loader.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/asc.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/autocomplete.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/back-h.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/back-v.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/bar-h.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/bar-v.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/bg-h.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/bg-v.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/blankimage.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/button.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/calendar.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/carousel.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/check0.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/check1.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/check2.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/colorpicker.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/container.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/datatable.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/desc.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/dt-arrow-dn.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/dt-arrow-up.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-knob.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-sprite-active.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-sprite.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/header_background.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/hue_bg.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/imagecropper.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/layout.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/layout_sprite.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/loading.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/logger.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu-button-arrow-disabled.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu-button-arrow.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/menubaritem_submenuindicator.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/menubaritem_submenuindicator_disabled.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_checkbox.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_checkbox_disabled.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_submenuindicator.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_submenuindicator_disabled.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/paginator.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/picker_mask.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/profilerviewer.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/progressbar.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/resize.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/simpleeditor.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/slider.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-active.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-disabled.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-focus.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-hover.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/sprite.png [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/tabview.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview-loading.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview-sprite.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/wait.gif [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/assets/skins/sam/yuitest.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/autocomplete/autocomplete-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/base/base-min.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/base/base.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/button/button-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/calendar.js [deleted file]
Websites/bugs.webkit.org/js/yui/calendar/calendar-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/carousel/carousel-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/charts/charts-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/colorpicker/colorpicker-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/connection/connection-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/connection/connection.swf [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/connection/connection_core-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/container/container-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/container/container_core-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/cookie/cookie-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/datasource/datasource-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/datatable/datatable-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/datemath/datemath-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/dom/dom-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/dragdrop/dragdrop-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/element-delegate/element-delegate-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/element/element-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/event-delegate/event-delegate-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/event-mouseenter/event-mouseenter-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/event-simulate/event-simulate-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/event/event-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/fonts/fonts-min.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/fonts/fonts.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/get/get-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/grids/grids-min.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/grids/grids.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/history/history-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/imagecropper/imagecropper-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/imageloader/imageloader-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/json/json-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/layout/layout-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/logger/logger-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/menu/menu-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/paginator/paginator-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/profiler/profiler-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/profilerviewer/profilerviewer-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/progressbar/progressbar-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/reset-fonts-grids/reset-fonts-grids.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/reset-fonts/reset-fonts.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/reset/reset-min.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/reset/reset.css [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/resize/resize-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/selector/selector-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/slider/slider-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/storage/storage-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/stylesheet/stylesheet-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/swf/swf-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/swfdetect/swfdetect-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/swfstore/swfstore-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/swfstore/swfstore.swf [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/tabview/tabview-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/treeview/treeview-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/uploader/uploader-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/yahoo-dom-event.js [deleted file]
Websites/bugs.webkit.org/js/yui/yahoo-dom-event/yahoo-dom-event.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/yahoo/yahoo-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/yuiloader/yuiloader-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/yuitest/yuitest-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/js/yui/yuitest/yuitest_core-min.js [new file with mode: 0644]
Websites/bugs.webkit.org/jsonrpc.cgi [new file with mode: 0755]
Websites/bugs.webkit.org/long_list.cgi [deleted file]
Websites/bugs.webkit.org/migrate.pl [new file with mode: 0755]
Websites/bugs.webkit.org/mod_perl.pl
Websites/bugs.webkit.org/page.cgi
Websites/bugs.webkit.org/post_bug.cgi
Websites/bugs.webkit.org/process_bug.cgi
Websites/bugs.webkit.org/query.cgi
Websites/bugs.webkit.org/quips.cgi
Websites/bugs.webkit.org/relogin.cgi
Websites/bugs.webkit.org/report.cgi
Websites/bugs.webkit.org/reports.cgi
Websites/bugs.webkit.org/request.cgi
Websites/bugs.webkit.org/sanitycheck.cgi
Websites/bugs.webkit.org/sanitycheck.pl
Websites/bugs.webkit.org/search_plugin.cgi
Websites/bugs.webkit.org/show_activity.cgi
Websites/bugs.webkit.org/show_bug.cgi
Websites/bugs.webkit.org/showattachment.cgi [deleted file]
Websites/bugs.webkit.org/showdependencygraph.cgi
Websites/bugs.webkit.org/showdependencytree.cgi
Websites/bugs.webkit.org/sidebar.cgi [deleted file]
Websites/bugs.webkit.org/skins/.cvsignore [deleted file]
Websites/bugs.webkit.org/skins/README [new file with mode: 0644]
Websites/bugs.webkit.org/skins/contrib/Dusk/.cvsignore [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/IE-fixes.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/admin.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/create_attachment.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/dependency-tree.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/duplicates.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/editusers.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/global.css
Websites/bugs.webkit.org/skins/contrib/Dusk/help.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/index.css
Websites/bugs.webkit.org/skins/contrib/Dusk/panel.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/params.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/release-notes.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/show_bug.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/show_multiple.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/summarize-time.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/voting.css [deleted file]
Websites/bugs.webkit.org/skins/contrib/Dusk/yui/calendar.css [deleted file]
Websites/bugs.webkit.org/skins/custom/IE-fixes.css [deleted file]
Websites/bugs.webkit.org/skins/custom/admin.css [deleted file]
Websites/bugs.webkit.org/skins/custom/buglist.css [deleted file]
Websites/bugs.webkit.org/skins/custom/create_attachment.css [deleted file]
Websites/bugs.webkit.org/skins/custom/dependency-tree.css [deleted file]
Websites/bugs.webkit.org/skins/custom/duplicates.css [deleted file]
Websites/bugs.webkit.org/skins/custom/editusers.css [deleted file]
Websites/bugs.webkit.org/skins/custom/help.css [deleted file]
Websites/bugs.webkit.org/skins/custom/opendarwin.gif [deleted file]
Websites/bugs.webkit.org/skins/custom/panel.css [deleted file]
Websites/bugs.webkit.org/skins/custom/params.css [deleted file]
Websites/bugs.webkit.org/skins/custom/release-notes.css [deleted file]
Websites/bugs.webkit.org/skins/custom/show_bug.css [deleted file]
Websites/bugs.webkit.org/skins/custom/show_multiple.css [deleted file]
Websites/bugs.webkit.org/skins/custom/summarize-time.css [deleted file]
Websites/bugs.webkit.org/skins/custom/voting.css [deleted file]
Websites/bugs.webkit.org/skins/custom/yui/calendar.css [deleted file]
Websites/bugs.webkit.org/skins/standard/IE-fixes.css
Websites/bugs.webkit.org/skins/standard/admin.css
Websites/bugs.webkit.org/skins/standard/attachment.css [new file with mode: 0644]
Websites/bugs.webkit.org/skins/standard/buglist.css
Websites/bugs.webkit.org/skins/standard/create_attachment.css [deleted file]
Websites/bugs.webkit.org/skins/standard/duplicates.css
Websites/bugs.webkit.org/skins/standard/enter_bug.css [new file with mode: 0644]
Websites/bugs.webkit.org/skins/standard/global.css
Websites/bugs.webkit.org/skins/standard/help.css [deleted file]
Websites/bugs.webkit.org/skins/standard/index.css
Websites/bugs.webkit.org/skins/standard/index/file-a-bug.png [new file with mode: 0644]
Websites/bugs.webkit.org/skins/standard/index/front.png [deleted file]
Websites/bugs.webkit.org/skins/standard/index/help.png [new file with mode: 0644]
Websites/bugs.webkit.org/skins/standard/index/new-account.png [new file with mode: 0644]
Websites/bugs.webkit.org/skins/standard/index/search.png [new file with mode: 0644]
Websites/bugs.webkit.org/skins/standard/page.css [new file with mode: 0644]
Websites/bugs.webkit.org/skins/standard/panel.css [deleted file]
Websites/bugs.webkit.org/skins/standard/release-notes.css [deleted file]
Websites/bugs.webkit.org/skins/standard/reports.css [new file with mode: 0644]
Websites/bugs.webkit.org/skins/standard/search_form.css [new file with mode: 0644]
Websites/bugs.webkit.org/skins/standard/show_bug.css
Websites/bugs.webkit.org/skins/standard/yui/calendar.css [deleted file]
Websites/bugs.webkit.org/skins/standard/yui/sprite.png [deleted file]
Websites/bugs.webkit.org/summarize_time.cgi
Websites/bugs.webkit.org/t/001compile.t
Websites/bugs.webkit.org/t/002goodperl.t
Websites/bugs.webkit.org/t/004template.t
Websites/bugs.webkit.org/t/005whitespace.t [moved from Websites/bugs.webkit.org/t/005no_tabs.t with 66% similarity]
Websites/bugs.webkit.org/t/007util.t
Websites/bugs.webkit.org/t/008filter.t
Websites/bugs.webkit.org/t/009bugwords.t
Websites/bugs.webkit.org/t/010dependencies.t
Websites/bugs.webkit.org/t/012throwables.t
Websites/bugs.webkit.org/t/Support/Files.pm
Websites/bugs.webkit.org/t/Support/Templates.pm
Websites/bugs.webkit.org/template/.cvsignore [deleted file]
Websites/bugs.webkit.org/template/en/.cvsignore [deleted file]
Websites/bugs.webkit.org/template/en/custom/attachment/content-types.html.tmpl [deleted file]
Websites/bugs.webkit.org/template/en/custom/attachment/create.html.tmpl
Websites/bugs.webkit.org/template/en/custom/attachment/created.html.tmpl
Websites/bugs.webkit.org/template/en/custom/attachment/createformcontents.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/custom/attachment/edit.html.tmpl
Websites/bugs.webkit.org/template/en/custom/attachment/list.html.tmpl
Websites/bugs.webkit.org/template/en/custom/bug/edit.html.tmpl
Websites/bugs.webkit.org/template/en/custom/bug/field.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/custom/bug/format_comment.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/custom/bug/navigate.html.tmpl
Websites/bugs.webkit.org/template/en/custom/flag/list.html.tmpl
Websites/bugs.webkit.org/template/en/custom/global/choose-product.html.tmpl
Websites/bugs.webkit.org/template/en/custom/global/header.html.tmpl
Websites/bugs.webkit.org/template/en/custom/list/list.html.tmpl
Websites/bugs.webkit.org/template/en/custom/request/email.txt.tmpl
Websites/bugs.webkit.org/template/en/custom/request/queue.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/auth/login-small.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/auth/login.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/cancel-token.txt.tmpl
Websites/bugs.webkit.org/template/en/default/account/create.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/email/change-new.txt.tmpl
Websites/bugs.webkit.org/template/en/default/account/email/change-old.txt.tmpl
Websites/bugs.webkit.org/template/en/default/account/email/confirm-new.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/email/request-new.txt.tmpl
Websites/bugs.webkit.org/template/en/default/account/password/forgotten-password.txt.tmpl
Websites/bugs.webkit.org/template/en/default/account/password/set-forgotten-password.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/prefs/account.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/prefs/email.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/prefs/permissions.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/prefs/prefs.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/prefs/saved-searches.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/profile-activity.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/admin.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/classifications/add.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/classifications/del.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/classifications/edit-common.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/admin/classifications/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/classifications/footer.html.tmpl [moved from Websites/bugs.webkit.org/template/en/default/list/list.js.tmpl with 60% similarity]
Websites/bugs.webkit.org/template/en/default/admin/classifications/reclassify.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/classifications/select.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/components/confirm-delete.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/components/create.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/components/edit-common.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/admin/components/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/components/footer.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/components/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/custom_fields/cf-js.js.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/admin/custom_fields/create.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/custom_fields/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/custom_fields/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/create.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/footer.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/flag-type/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/flag-type/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/groups/confirm-remove.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/groups/delete.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/groups/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/groups/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/keywords/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/milestones/confirm-delete.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/milestones/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/milestones/footer.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/milestones/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/admin.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/advanced.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/admin/params/attachment.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/auth.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/bugchange.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/bugfields.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/bugmove.html.tmpl [deleted file]
Websites/bugs.webkit.org/template/en/default/admin/params/common.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/core.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/dependencygraph.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/editparams.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/general.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/admin/params/groupsecurity.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/index.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/mta.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/query.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/radius.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/usermatch.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/products/confirm-delete.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/products/create.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/products/edit-common.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/products/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/products/footer.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/products/groupcontrol/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/products/groupcontrol/updated.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/products/list-classifications.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/products/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/products/updated.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/sanitycheck/messages.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/sudo.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/table.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/users/confirm-delete.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/users/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/users/listselectvars.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/users/responsibilities.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/users/search.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/users/userdata.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/versions/confirm-delete.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/versions/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/versions/footer.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/versions/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/workflow/comment.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/workflow/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/cancel-create-dupe.html.tmpl [deleted file]
Websites/bugs.webkit.org/template/en/default/attachment/create.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/created.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/createformcontents.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/diff-file.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/diff-footer.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/diff-header.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/midair.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/show-multiple.html.tmpl
Websites/bugs.webkit.org/template/en/default/attachment/updated.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/activity/show.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/activity/table.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/comments.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/create/confirm-create-dupe.html.tmpl [deleted file]
Websites/bugs.webkit.org/template/en/default/bug/create/create-guided.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/create/create.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/create/created.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/dependency-graph.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/dependency-tree.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/field-events.js.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/bug/field-help.none.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/bug/field-label.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/bug/field.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/format_comment.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/bug/knob.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/link.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/bug/navigate.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/process/bugmail.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/process/header.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/process/midair.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/process/results.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/process/verify-new-product.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/show-header.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/bug/show-multiple.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/show.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/show.xml.tmpl
Websites/bugs.webkit.org/template/en/default/bug/summarize-time.html.tmpl
Websites/bugs.webkit.org/template/en/default/config.js.tmpl
Websites/bugs.webkit.org/template/en/default/config.rdf.tmpl
Websites/bugs.webkit.org/template/en/default/email/bugmail-common.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/email/bugmail-header.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/email/bugmail.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/email/bugmail.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/email/lockout.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/email/newchangedmail.txt.tmpl [deleted file]
Websites/bugs.webkit.org/template/en/default/email/sanitycheck.txt.tmpl
Websites/bugs.webkit.org/template/en/default/email/whine.txt.tmpl
Websites/bugs.webkit.org/template/en/default/extensions/config.pm.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/extensions/extension.pm.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/extensions/hook-readme.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/extensions/license.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/extensions/name-readme.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/extensions/util.pm.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/extensions/web-readme.txt.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/filterexceptions.pl
Websites/bugs.webkit.org/template/en/default/flag/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/choose-classification.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/choose-product.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/code-error.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/common-links.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/confirm-action.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/confirm-user-match.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/field-descs.none.tmpl
Websites/bugs.webkit.org/template/en/default/global/footer.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/header.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/help.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/hidden-fields.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/messages.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/per-bug-queries.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/reason-descs.none.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/global/setting-descs.none.tmpl
Websites/bugs.webkit.org/template/en/default/global/site-navigation.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/tabs.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/textarea.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/useful-links.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/user-error.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/user.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/global/userselect.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/value-descs.js.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/global/value-descs.none.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/global/variables.none.tmpl
Websites/bugs.webkit.org/template/en/default/index.html.tmpl
Websites/bugs.webkit.org/template/en/default/list/change-columns.html.tmpl
Websites/bugs.webkit.org/template/en/default/list/edit-multiple.html.tmpl
Websites/bugs.webkit.org/template/en/default/list/list-simple.html.tmpl
Websites/bugs.webkit.org/template/en/default/list/list.atom.tmpl
Websites/bugs.webkit.org/template/en/default/list/list.csv.tmpl
Websites/bugs.webkit.org/template/en/default/list/list.html.tmpl
Websites/bugs.webkit.org/template/en/default/list/list.ics.tmpl
Websites/bugs.webkit.org/template/en/default/list/list.rdf.tmpl
Websites/bugs.webkit.org/template/en/default/list/quips.html.tmpl
Websites/bugs.webkit.org/template/en/default/list/table.html.tmpl
Websites/bugs.webkit.org/template/en/default/pages/bug-writing.html.tmpl
Websites/bugs.webkit.org/template/en/default/pages/fields.html.tmpl
Websites/bugs.webkit.org/template/en/default/pages/linked.html.tmpl
Websites/bugs.webkit.org/template/en/default/pages/quicksearch.html.tmpl
Websites/bugs.webkit.org/template/en/default/pages/quicksearchhack.html.tmpl [deleted file]
Websites/bugs.webkit.org/template/en/default/pages/release-notes.html.tmpl
Websites/bugs.webkit.org/template/en/default/pages/release-notes3.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/pages/sudo.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/chart.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/components.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/create-chart.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/delete-series.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/reports/duplicates-simple.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/duplicates-table.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/duplicates.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/edit-series.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/keywords.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/menu.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/old-charts.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/report-bar.png.tmpl
Websites/bugs.webkit.org/template/en/default/reports/report-line.png.tmpl
Websites/bugs.webkit.org/template/en/default/reports/report-pie.png.tmpl
Websites/bugs.webkit.org/template/en/default/reports/report-table.csv.tmpl
Websites/bugs.webkit.org/template/en/default/reports/report-table.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/report.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/series-common.html.tmpl
Websites/bugs.webkit.org/template/en/default/request/email.txt.tmpl
Websites/bugs.webkit.org/template/en/default/request/queue.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/boolean-charts.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/field.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/search/form.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/knob.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/search-advanced.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/search-create-series.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/search-help.html.tmpl [deleted file]
Websites/bugs.webkit.org/template/en/default/search/search-plugin.xml.tmpl
Websites/bugs.webkit.org/template/en/default/search/search-report-graph.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/search-report-select.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/search-report-table.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/search-specific.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/tabs.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/type-select.html.tmpl [new file with mode: 0644]
Websites/bugs.webkit.org/template/en/default/setup/strings.txt.pl
Websites/bugs.webkit.org/template/en/default/sidebar.xul.tmpl [deleted file]
Websites/bugs.webkit.org/template/en/default/welcome-admin.html.tmpl
Websites/bugs.webkit.org/template/en/default/whine/mail.html.tmpl
Websites/bugs.webkit.org/template/en/default/whine/mail.txt.tmpl
Websites/bugs.webkit.org/template/en/default/whine/multipart-mime.txt.tmpl
Websites/bugs.webkit.org/template/en/default/whine/schedule.html.tmpl
Websites/bugs.webkit.org/template/en/extension/filterexceptions.pl [deleted file]
Websites/bugs.webkit.org/testserver.pl
Websites/bugs.webkit.org/token.cgi
Websites/bugs.webkit.org/userprefs.cgi
Websites/bugs.webkit.org/votes.cgi
Websites/bugs.webkit.org/whine.pl
Websites/bugs.webkit.org/whineatnews.pl
Websites/bugs.webkit.org/xml.cgi [deleted file]
Websites/bugs.webkit.org/xmlrpc.cgi
Websites/bugs.webkit.org/xt/README [new file with mode: 0644]
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search.pm [new file with mode: 0644]
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/AndTest.pm [new file with mode: 0644]
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/Constants.pm [new file with mode: 0644]
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/CustomTest.pm [new file with mode: 0644]
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/FieldTest.pm [new file with mode: 0644]
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm [new file with mode: 0644]
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/InjectionTest.pm [new file with mode: 0644]
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/NotTest.pm [new file with mode: 0644]
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/OperatorTest.pm [new file with mode: 0644]
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/OrTest.pm [new file with mode: 0644]
Websites/bugs.webkit.org/xt/search.t [new file with mode: 0644]

diff --git a/Websites/bugs.webkit.org/.bzrignore b/Websites/bugs.webkit.org/.bzrignore
new file mode 100644 (file)
index 0000000..7ab83e7
--- /dev/null
@@ -0,0 +1,32 @@
+.htaccess
+/lib/*
+/template/en/custom
+/docs/bugzilla.ent
+/docs/en/xml/bugzilla.ent
+/docs/en/txt
+/docs/en/html
+/docs/en/pdf
+/skins/custom
+/graphs
+/data
+/localconfig
+/index.html
+
+/skins/contrib/Dusk/IE-fixes.css
+/skins/contrib/Dusk/admin.css
+/skins/contrib/Dusk/attachment.css
+/skins/contrib/Dusk/create_attachment.css
+/skins/contrib/Dusk/dependency-tree.css
+/skins/contrib/Dusk/duplicates.css
+/skins/contrib/Dusk/editusers.css
+/skins/contrib/Dusk/enter_bug.css
+/skins/contrib/Dusk/help.css
+/skins/contrib/Dusk/panel.css
+/skins/contrib/Dusk/page.css
+/skins/contrib/Dusk/params.css
+/skins/contrib/Dusk/reports.css
+/skins/contrib/Dusk/show_bug.css
+/skins/contrib/Dusk/search_form.css
+/skins/contrib/Dusk/show_multiple.css
+/skins/contrib/Dusk/summarize-time.css
+.DS_Store
diff --git a/Websites/bugs.webkit.org/.cvsignore b/Websites/bugs.webkit.org/.cvsignore
deleted file mode 100644 (file)
index cba381b..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-.htaccess
-graphs
-data
-localconfig
-index.html
-old-params.txt
index 64e9809..affa4db 100644 (file)
@@ -1,10 +1,33 @@
-# don't allow people to retrieve non-cgi executable files or our private data
+# Don't allow people to retrieve non-cgi executable files or our private data
 <FilesMatch ^(.*\.pm|.*\.pl|.*localconfig.*)$>
   deny from all
 </FilesMatch>
 <FilesMatch ^(localconfig.js|localconfig.rdf)$>
   allow 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>
 
 # Force all connections to HTTPS for 90 days at a time.
-Header set Strict-Transport-Security "max-age=7776000"
+<IfModule mod_headers.c>
+  Header set Strict-Transport-Security "max-age=7776000"
+</IfModule>
index d54f974..65ddcc2 100644 (file)
@@ -40,37 +40,45 @@ 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;
 
-# This creates the request cache for non-mod_perl installations.
-our $_request_cache = {};
-
 #####################################################################
 # Constants
 #####################################################################
 
 # Scripts that are not stopped by shutdownhtml being in effect.
-use constant SHUTDOWNHTML_EXEMPT => [
-    'editparams.cgi',
-    'checksetup.pl',
-    'recode.pl',
-];
+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 => [
-    'whine.pl'
-];
+use constant SHUTDOWNHTML_EXIT_SILENTLY => qw(
+    whine.pl
+);
+
+# shutdownhtml pages are sent as an HTTP 503. After how many seconds
+# should search engines attempt to index the page again?
+use constant SHUTDOWNHTML_RETRY_AFTER => 3600;
 
 #####################################################################
 # Global Code
@@ -80,13 +88,26 @@ use constant SHUTDOWNHTML_EXIT_SILENTLY => [
 
 # Note that this is a raw subroutine, not a method, so $class isn't available.
 sub init_page {
-    (binmode STDOUT, ':utf8') if Bugzilla->params->{'utf8'};
+    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'} = '';
+    }
 
-    # 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.
@@ -102,25 +123,27 @@ sub init_page {
         };
     }
 
+    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.
     #
-    # Because this is code which is run live from perl "use" commands of other
-    # scripts, we're skipping this part if we get here during a perl syntax 
-    # check -- runtests.pl compiles scripts without running them, so we 
-    # need to make sure that this check doesn't apply to 'perl -c' calls.
-    #
     # This code must go here. It cannot go anywhere in Bugzilla::CGI, because
     # it uses Template, and that causes various dependency loops.
-    if (!$^C && Bugzilla->params->{"shutdownhtml"} 
-        && lsearch(SHUTDOWNHTML_EXEMPT, basename($0)) == -1)
+    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 (lsearch(SHUTDOWNHTML_EXIT_SILENTLY, basename($0)) > -1
-            && !i_am_cgi())
+        if (!i_am_cgi()
+            && grep { $_ eq $script } SHUTDOWNHTML_EXIT_SILENTLY)
         {
             exit;
         }
@@ -151,7 +174,12 @@ sub init_page {
         else {
             $extension = 'txt';
         }
-        print Bugzilla->cgi->header() if i_am_cgi();
+        if (i_am_cgi()) {
+            # Set the HTTP status to 503 when Bugzilla is down to avoid pages
+            # being indexed by search engines.
+            print Bugzilla->cgi->header(-status => 503, 
+                -retry_after => SHUTDOWNHTML_RETRY_AFTER);
+        }
         my $t_output;
         $template->process("global/message.$extension.tmpl", $vars, \$t_output)
             || ThrowTemplateError($template->error);
@@ -160,34 +188,103 @@ sub init_page {
     }
 }
 
-init_page() if !$ENV{MOD_PERL};
-
 #####################################################################
 # Subroutines and Methods
 #####################################################################
 
 sub template {
     my $class = shift;
-    $class->request_cache->{language} = "";
     $class->request_cache->{template} ||= Bugzilla::Template->create();
     return $class->request_cache->{template};
 }
 
 sub template_inner {
     my ($class, $lang) = @_;
-    $lang = defined($lang) ? $lang : ($class->request_cache->{language} || "");
-    $class->request_cache->{language} = $lang;
+    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();
+        ||= 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();
@@ -223,6 +320,10 @@ sub sudo_request {
     # 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) = @_;
 
@@ -230,9 +331,17 @@ sub login {
 
     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.
@@ -243,37 +352,42 @@ sub login {
     # 3: There must be a valid value in the 'sudo' cookie
     # 4: A Bugzilla::User object must exist for the given cookie value
     # 5: That user must NOT be in the 'bz_sudo_protect' group
-    my $sudo_cookie = $class->cgi->cookie('sudo');
-    detaint_natural($sudo_cookie) if defined($sudo_cookie);
-    my $sudo_target;
-    $sudo_target = new Bugzilla::User($sudo_cookie) if defined($sudo_cookie);
-    if (defined($authenticated_user)                 &&
-        $authenticated_user->in_group('bz_sudoers')  &&
-        defined($sudo_cookie)                        &&
-        defined($sudo_target)                        &&
-        !($sudo_target->in_group('bz_sudo_protect'))
-       )
-    {
-        $class->set_user($sudo_target);
-        $class->request_cache->{sudoer} = $authenticated_user;
-        # And make sure that both users have the same Auth object,
-        # since we never call Auth::login for the sudo target.
-        $sudo_target->set_authorizer($authenticated_user->authorizer);
+    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.
+            # 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);
     }
 
-    # We run after the login has completed since
-    # some of the checks in ssl_require_redirect
-    # look for Bugzilla->user->id to determine 
-    # if redirection is required.
-    if (i_am_cgi() && ssl_require_redirect()) {
-        $class->cgi->require_https($class->params->{'sslbase'});
-    }
-    
     return $class->user;
 }
 
@@ -311,33 +425,30 @@ sub logout_request {
     # 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->request_cache->{dbh_main} 
-        = Bugzilla::DB::connect_main();
+    $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 $class->request_cache->{languages}
-        if $class->request_cache->{languages};
-
-    my @files = glob(catdir(bz_locations->{'templatedir'}, '*'));
-    my @languages;
-    foreach my $dir_entry (@files) {
-        # It's a language directory only if it contains "default" or
-        # "custom". This auto-excludes CVS directories as well.
-        next unless (-d catdir($dir_entry, 'default')
-                  || -d catdir($dir_entry, 'custom'));
-        $dir_entry = basename($dir_entry);
-        # Check for language tag format conforming to RFC 1766.
-        next unless $dir_entry =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/;
-        push(@languages, $dir_entry);
-    }
-    return $class->request_cache->{languages} = \@languages;
+    return Bugzilla::Install::Util::supported_languages();
 }
 
 sub error_mode {
@@ -346,7 +457,16 @@ sub error_mode {
         $class->request_cache->{error_mode} = $newval;
     }
     return $class->request_cache->{error_mode}
-        || Bugzilla::Constants::ERROR_MODE_WEBPAGE;
+        || (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 {
@@ -358,12 +478,18 @@ sub usage_mode {
         elsif ($newval == USAGE_MODE_CMDLINE) {
             $class->error_mode(ERROR_MODE_DIE);
         }
-        elsif ($newval == USAGE_MODE_WEBSERVICE) {
+        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});
@@ -371,7 +497,7 @@ sub usage_mode {
         $class->request_cache->{usage_mode} = $newval;
     }
     return $class->request_cache->{usage_mode}
-        || Bugzilla::Constants::USAGE_MODE_BROWSER;
+        || (i_am_cgi()? USAGE_MODE_BROWSER : USAGE_MODE_CMDLINE);
 }
 
 sub installation_mode {
@@ -403,7 +529,7 @@ sub switch_to_shadow_db {
         if ($class->params->{'shadowdb'}) {
             $class->request_cache->{dbh_shadow} = Bugzilla::DB::connect_shadow();
         } else {
-            $class->request_cache->{dbh_shadow} = request_cache()->{dbh_main};
+            $class->request_cache->{dbh_shadow} = $class->dbh_main;
         }
     }
 
@@ -417,21 +543,56 @@ sub switch_to_shadow_db {
 sub switch_to_main_db {
     my $class = shift;
 
-    $class->request_cache->{dbh} = $class->request_cache->{dbh_main};
-    # We have to return $class->dbh instead of {dbh} as
-    # {dbh_main} may be undefined if no connection to the main DB
-    # has been established yet.
-    return $class->dbh;
+    $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 fields {
+    my ($class, $criteria) = @_;
+    $criteria ||= {};
+    my $cache = $class->request_cache;
+
+    # We create an advanced cache for fields by type, so that we
+    # can avoid going back to the database for every fields() call.
+    # (And most of our fields() calls are for getting fields by type.)
+    #
+    # We also cache fields by name, because calling $field->name a few
+    # million times can be slow in calling code, but if we just do it
+    # once here, that makes things a lot faster for callers.
+    if (!defined $cache->{fields}) {
+        my @all_fields = Bugzilla::Field->get_all;
+        my (%by_name, %by_type);
+        foreach my $field (@all_fields) {
+            my $name = $field->name;
+            $by_type{$field->type}->{$name} = $field;
+            $by_name{$name} = $field;
+        }
+        $cache->{fields} = { by_type => \%by_type, by_name => \%by_name };
+    }
+
+    my $fields = $cache->{fields};
+    my %requested;
+    if (my $types = delete $criteria->{type}) {
+        $types = ref($types) ? $types : [$types];
+        %requested = map { %{ $fields->{by_type}->{$_} || {} } } @$types;
+    }
+    else {
+        %requested = %{ $fields->{by_name} };
+    }
+
+    my $do_by_name = delete $criteria->{by_name};
+
+    # Filtering before returning the fields based on
+    # the criterias.
+    foreach my $filter (keys %$criteria) {
+        foreach my $field (keys %requested) {
+            if ($requested{$field}->$filter != $criteria->{$filter}) {
+                delete $requested{$field};
+            }
+        }
+    }
+
+    return $do_by_name ? \%requested : [values %requested];
 }
 
 sub active_custom_fields {
@@ -447,21 +608,35 @@ sub has_flags {
     my $class = shift;
 
     if (!defined $class->request_cache->{has_flags}) {
-        $class->request_cache->{has_flags} = Bugzilla::Flag::has_flags();
+        $class->request_cache->{has_flags} = Bugzilla::Flag->any_exist;
     }
     return $class->request_cache->{has_flags};
 }
 
-sub hook_args {
-    my ($class, $args) = @_;
-    $class->request_cache->{hook_args} = $args if $args;
-    return $class->request_cache->{hook_args};
+sub 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;
-        return Apache2::RequestUtil->request->pnotes();
+        # 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;
 }
@@ -479,6 +654,12 @@ sub _cleanup {
         $dbh->disconnect;
     }
     undef $_request_cache;
+
+    # These are both set by CGI.pm but need to be undone so that
+    # Apache can actually shut down its children if it needs to.
+    foreach my $signal (qw(TERM PIPE)) {
+        $SIG{$signal} = 'DEFAULT' if $SIG{$signal} && $SIG{$signal} eq 'IGNORE';
+    }
 }
 
 sub END {
@@ -486,6 +667,8 @@ sub END {
     _cleanup() unless $ENV{MOD_PERL};
 }
 
+init_page() if !$ENV{MOD_PERL};
+
 1;
 
 __END__
@@ -569,6 +752,26 @@ 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
@@ -602,6 +805,13 @@ 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
@@ -626,6 +836,30 @@ Essentially, causes calls to C<Bugzilla-E<gt>user> to return C<undef>. This has
 effect of logging out a user for the current request only; cookies and
 database sessions are left intact.
 
+=item C<fields>
+
+This is the standard way to get arrays or hashes of L<Bugzilla::Field>
+objects when you need them. It takes the following named arguments
+in a hashref:
+
+=over
+
+=item C<by_name>
+
+If false (or not specified), this method will return an arrayref of
+the requested fields. The order of the returned fields is random.
+
+If true, this method will return a hashref of fields, where the keys
+are field names and the valules are L<Bugzilla::Field> objects.
+
+=item C<type>
+
+Either a single C<FIELD_TYPE_*> constant or an arrayref of them. If specified,
+the returned fields will be limited to the types in the list. If you don't
+specify this argument, all fields will be returned.
+
+=back
+
 =item C<error_mode>
 
 Call either C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE)>
@@ -646,10 +880,11 @@ usage mode changes.
 =item C<usage_mode>
 
 Call either C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE)>
-or C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_WEBSERVICE)> near the
+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.
@@ -670,6 +905,10 @@ used to automatically answer or skip prompts.
 
 The current database handle. See L<DBI>.
 
+=item C<dbh_main>
+
+The main database handle. See L<DBI>.
+
 =item C<languages>
 
 Currently installed languages.
@@ -689,9 +928,21 @@ The current Parameters of Bugzilla, as a hashref. If C<data/params>
 does not exist, then we return an empty hashref. If C<data/params>
 is unreadable or is not valid perl, we C<die>.
 
-=item C<hook_args>
+=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>
 
-If you are running inside a code hook (see L<Bugzilla::Hook>) this
-is how you get the arguments passed to the hook.
+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/Websites/bugs.webkit.org/Bugzilla/.cvsignore b/Websites/bugs.webkit.org/Bugzilla/.cvsignore
deleted file mode 100644 (file)
index 03c88fd..0000000
+++ /dev/null
@@ -1 +0,0 @@
-.htaccess
index 314227c..b1f47d0 100644 (file)
@@ -28,23 +28,26 @@ package Bugzilla::Attachment;
 
 =head1 NAME
 
-Bugzilla::Attachment - a file related to a bug that a user has uploaded
-                       to the Bugzilla server
+Bugzilla::Attachment - Bugzilla attachment class.
 
 =head1 SYNOPSIS
 
   use Bugzilla::Attachment;
 
   # Get the attachment with the given ID.
-  my $attachment = Bugzilla::Attachment->get($attach_id);
+  my $attachment = new Bugzilla::Attachment($attach_id);
 
   # Get the attachments with the given IDs.
-  my $attachments = Bugzilla::Attachment->get_list($attach_ids);
+  my $attachments = Bugzilla::Attachment->new_from_list($attach_ids);
 
 =head1 DESCRIPTION
 
-This module defines attachment objects, which represent files related to bugs
-that users upload to the Bugzilla server.
+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
 
@@ -54,61 +57,75 @@ use Bugzilla::Flag;
 use Bugzilla::User;
 use Bugzilla::Util;
 use Bugzilla::Field;
+use Bugzilla::Hook;
 
-sub get {
-    my $invocant = shift;
-    my $id = shift;
+use File::Copy;
+use List::Util qw(max);
 
-    my $attachments = _retrieve([$id]);
-    my $self = $attachments->[0];
-    bless($self, ref($invocant) || $invocant) if $self;
+use base qw(Bugzilla::Object);
 
-    return $self;
-}
+###############################
+####    Initialization     ####
+###############################
 
-sub get_list {
-    my $invocant = shift;
-    my $ids = shift;
+use constant DB_TABLE   => 'attachments';
+use constant ID_FIELD   => 'attach_id';
+use constant LIST_ORDER => ID_FIELD;
+# Attachments are tracked in bugs_activity.
+use constant AUDIT_CREATES => 0;
+use constant AUDIT_UPDATES => 0;
 
-    my $attachments = _retrieve($ids);
-    foreach my $attachment (@$attachments) {
-        bless($attachment, ref($invocant) || $invocant);
-    }
+sub DB_COLUMNS {
+    my $dbh = Bugzilla->dbh;
 
-    return $attachments;
+    return qw(
+        attach_id
+        bug_id
+        description
+        filename
+        isobsolete
+        ispatch
+        isprivate
+        mimetype
+        modification_time
+        submitter_id),
+        $dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i') . ' AS creation_ts';
 }
 
-sub _retrieve {
-    my ($ids) = @_;
-
-    return [] if scalar(@$ids) == 0;
-
-    my @columns = (
-        'attachments.attach_id AS id',
-        'attachments.bug_id AS bug_id',
-        'attachments.description AS description',
-        'attachments.mimetype AS contenttype',
-        'attachments.submitter_id AS attacher_id',
-        Bugzilla->dbh->sql_date_format('attachments.creation_ts',
-                                       '%Y.%m.%d %H:%i') . " AS attached",
-        'attachments.modification_time',
-        'attachments.filename AS filename',
-        'attachments.ispatch AS ispatch',
-        'attachments.isurl AS isurl',
-        'attachments.isobsolete AS isobsolete',
-        'attachments.isprivate AS isprivate'
-    );
-    my $columns = join(", ", @columns);
-    my $dbh = Bugzilla->dbh;
-    my $records = $dbh->selectall_arrayref(
-                      "SELECT $columns
-                         FROM attachments
-                        WHERE " 
-                       . Bugzilla->dbh->sql_in('attach_id', $ids) 
-                 . " ORDER BY attach_id",
-                       { Slice => {} });
-    return $records;
-}
+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,
+    filename      => \&_check_filename,
+    ispatch       => \&Bugzilla::Object::check_boolean,
+    isprivate     => \&_check_is_private,
+    mimetype      => \&_check_content_type,
+};
+
+use constant VALIDATOR_DEPENDENCIES => {
+    mimetype => ['ispatch'],
+};
+
+use constant UPDATE_VALIDATORS => {
+    isobsolete => \&Bugzilla::Object::check_boolean,
+};
+
+###############################
+####      Accessors      ######
+###############################
 
 =pod
 
@@ -116,34 +133,35 @@ sub _retrieve {
 
 =over
 
-=item C<id>
+=item C<bug_id>
 
-the unique identifier for the attachment
+the ID of the bug to which the attachment is attached
 
 =back
 
 =cut
 
-sub id {
+sub bug_id {
     my $self = shift;
-    return $self->{id};
+    return $self->{bug_id};
 }
 
 =over
 
-=item C<bug_id>
+=item C<bug>
 
-the ID of the bug to which the attachment is attached
+the bug object to which the attachment is attached
 
 =back
 
 =cut
 
-# XXX Once Bug.pm slims down sufficiently this should become a reference
-# to a bug object.
-sub bug_id {
+sub bug {
     my $self = shift;
-    return $self->{bug_id};
+
+    require Bugzilla::Bug;
+    $self->{bug} ||= Bugzilla::Bug->new($self->bug_id);
+    return $self->{bug};
 }
 
 =over
@@ -173,7 +191,7 @@ the attachment's MIME media type
 
 sub contenttype {
     my $self = shift;
-    return $self->{contenttype};
+    return $self->{mimetype};
 }
 
 =over
@@ -189,7 +207,7 @@ the user who attached the attachment
 sub attacher {
     my $self = shift;
     return $self->{attacher} if exists $self->{attacher};
-    $self->{attacher} = new Bugzilla::User($self->{attacher_id});
+    $self->{attacher} = new Bugzilla::User($self->{submitter_id});
     return $self->{attacher};
 }
 
@@ -205,7 +223,7 @@ the date and time on which the attacher attached the attachment
 
 sub attached {
     my $self = shift;
-    return $self->{attached};
+    return $self->{creation_ts};
 }
 
 =over
@@ -255,21 +273,6 @@ sub 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
@@ -351,7 +354,7 @@ sub data {
                                                       FROM attach_data
                                                       WHERE id = ?",
                                                      undef,
-                                                     $self->{id});
+                                                     $self->id);
 
     # If there's no attachment data in the database, the attachment is stored
     # in a local file, so retrieve it from there.
@@ -396,7 +399,7 @@ sub datasize {
         Bugzilla->dbh->selectrow_array("SELECT LENGTH(thedata)
                                         FROM attach_data
                                         WHERE id = ?",
-                                       undef, $self->{id}) || 0;
+                                       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,
@@ -412,6 +415,13 @@ sub datasize {
     return $self->{datasize};
 }
 
+sub _get_local_filename {
+    my $self = shift;
+    my $hash = ($self->id % 100) + 100;
+    $hash =~ s/.*(\d\d)$/group.$1/;
+    return bz_locations()->{'attachdir'} . "/$hash/attachment." . $self->id;
+}
+
 =over
 
 =item C<flags>
@@ -424,29 +434,146 @@ flags that have been set on the attachment
 
 sub flags {
     my $self = shift;
-    return $self->{flags} if exists $self->{flags};
 
-    $self->{flags} = Bugzilla::Flag->match({ 'attach_id' => $self->id });
+    # Don't cache it as it must be in sync with ->flag_types.
+    $self->{flags} = [map { @{$_->{flags}} } @{$self->flag_types}];
     return $self->{flags};
 }
 
-# Instance methods; no POD documentation here yet because the only ones so far
-# are private.
+=over
+
+=item C<flag_types>
 
-sub _get_local_filename {
+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;
-    my $hash = ($self->id % 100) + 100;
-    $hash =~ s/.*(\d\d)$/group.$1/;
-    return bz_locations()->{'attachdir'} . "/$hash/attachment." . $self->id;
+    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};
 }
 
-sub _validate_filename {
-    my ($throw_error) = @_;
-    my $cgi = Bugzilla->cgi;
-    defined $cgi->upload('data')
-        || ($throw_error ? ThrowUserError("file_not_specified") : return 0);
+###############################
+####      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}};
+        }
+    }
+}
 
-    my $filename = $cgi->upload('data');
+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_patch = ref($invocant) ? $invocant->ispatch : $params->{ispatch};
+    $content_type = 'text/plain' if $is_patch;
+    $content_type = clean_text($content_type);
+    # The subsets below cover all existing MIME types and charsets registered by IANA.
+    # (MIME type: RFC 2045 section 5.1; charset: RFC 2278 section 3.3)
+    my $legal_types = join('|', LEGAL_CONTENT_TYPES);
+    if (!$content_type
+        || $content_type !~ /^($legal_types)\/[a-z0-9_\-\+\.]+(;\s*charset=[a-z0-9_\-\+]+)?$/i)
+    {
+        ThrowUserError("invalid_content_type", { contenttype => $content_type });
+    }
+    trick_taint($content_type);
+
+    return $content_type;
+}
+
+sub _check_data {
+    my ($invocant, $params) = @_;
+
+    my $data = $params->{data};
+    $params->{filesize} = ref $data ? -s $data : length($data);
+
+    Bugzilla::Hook::process('attachment_process_data', { data       => \$data,
+                                                         attributes => $params });
+
+    $params->{filesize} || ThrowUserError('zero_length_file');
+    # Make sure the attachment does not exceed the maximum permitted size.
+    my $max_size = max(Bugzilla->params->{'maxlocalattachment'} * 1048576,
+                       Bugzilla->params->{'maxattachmentsize'} * 1024);
+
+    if ($params->{filesize} > $max_size) {
+        my $vars = { filesize => sprintf("%.0f", $params->{filesize}/1024) };
+        ThrowUserError('file_too_large', $vars);
+    }
+    return $data;
+}
+
+sub _check_description {
+    my ($invocant, $description) = @_;
+
+    $description = trim($description);
+    $description || ThrowUserError('missing_attachment_description');
+    return $description;
+}
+
+sub _check_filename {
+    my ($invocant, $filename) = @_;
+
+    $filename = clean_text($filename);
+    if (!$filename) {
+        if (ref $invocant) {
+            ThrowUserError('filename_not_specified');
+        }
+        else {
+            ThrowUserError('file_not_specified');
+        }
+    }
 
     # 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
@@ -458,65 +585,21 @@ sub _validate_filename {
     # 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 _validate_data {
-    my ($throw_error, $hr_vars) = @_;
-    my $cgi = Bugzilla->cgi;
-    my $maxsize = $cgi->param('ispatch') ? Bugzilla->params->{'maxpatchsize'} 
-                  : Bugzilla->params->{'maxattachmentsize'};
-    $maxsize *= 1024; # Convert from K
-    my $fh;
-    # Skip uploading into a local variable if the user wants to upload huge
-    # attachments into local files.
-    if (!$cgi->param('bigfile')) {
-        $fh = $cgi->upload('data');
-    }
-    my $data;
-
-    # We could get away with reading only as much as required, except that then
-    # we wouldn't have a size to print to the error handler below.
-    if (!$cgi->param('bigfile')) {
-        # enable 'slurp' mode
-        local $/;
-        $data = <$fh>;
-    }
-
-    $data
-        || ($cgi->param('bigfile'))
-        || ($throw_error ? ThrowUserError("zero_length_file") : return 0);
-
-    # Windows screenshots are usually uncompressed BMP files which
-    # makes for a quick way to eat up disk space. Let's compress them.
-    # We do this before we check the size since the uncompressed version
-    # could easily be greater than maxattachmentsize.
-    if (Bugzilla->params->{'convert_uncompressed_images'}
-        && $cgi->param('contenttype') eq 'image/bmp') {
-        require Image::Magick;
-        my $img = Image::Magick->new(magick=>'bmp');
-        $img->BlobToImage($data);
-        $img->set(magick=>'png');
-        my $imgdata = $img->ImageToBlob();
-        $data = $imgdata;
-        $cgi->param('contenttype', 'image/png');
-        $hr_vars->{'convertedbmp'} = 1;
-    }
+sub _check_is_private {
+    my ($invocant, $is_private) = @_;
 
-    # Make sure the attachment does not exceed the maximum permitted size
-    my $len = $data ? length($data) : 0;
-    if ($maxsize && $len > $maxsize) {
-        my $vars = { filesize => sprintf("%.0f", $len/1024) };
-        if ($cgi->param('ispatch')) {
-            $throw_error ? ThrowUserError("patch_too_large", $vars) : return 0;
-        }
-        else {
-            $throw_error ? ThrowUserError("file_too_large", $vars) : return 0;
-        }
+    $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 $data || '';
+    return $is_private;
 }
 
 =pod
@@ -538,7 +621,7 @@ Returns:    a reference to an array of attachment objects.
 =cut
 
 sub get_attachments_by_bug {
-    my ($class, $bug_id) = @_;
+    my ($class, $bug_id, $vars) = @_;
     my $user = Bugzilla->user;
     my $dbh = Bugzilla->dbh;
 
@@ -555,107 +638,26 @@ sub get_attachments_by_bug {
     my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
                                                WHERE bug_id = ? $and_restriction",
                                                undef, @values);
-    my $attachments = Bugzilla::Attachment->get_list($attach_ids);
-    return $attachments;
-}
-
-=pod
-
-=item C<validate_is_patch()>
-
-Description: validates the "patch" flag passed in by CGI.
-
-Returns:    1 on success.
-
-=cut
-
-sub validate_is_patch {
-    my ($class, $throw_error) = @_;
-    my $cgi = Bugzilla->cgi;
-
-    # Set the ispatch flag to zero if it is undefined, since the UI uses
-    # an HTML checkbox to represent this flag, and unchecked HTML checkboxes
-    # do not get sent in HTML requests.
-    $cgi->param('ispatch', $cgi->param('ispatch') ? 1 : 0);
-
-    # Set the content type to text/plain if the attachment is a patch.
-    $cgi->param('contenttype', 'text/plain') if $cgi->param('ispatch');
-
-    return 1;
-}
-
-=pod
-
-=item C<validate_description()>
-
-Description: validates the description passed in by CGI.
-
-Returns:    1 on success.
-
-=cut
-
-sub validate_description {
-    my ($class, $throw_error) = @_;
-    my $cgi = Bugzilla->cgi;
-
-    $cgi->param('description')
-        || ($throw_error ? ThrowUserError("missing_attachment_description") : return 0);
-
-    return 1;
-}
-
-=pod
-
-=item C<validate_content_type()>
 
-Description: validates the content type passed in by CGI.
+    my $attachments = Bugzilla::Attachment->new_from_list($attach_ids);
 
-Returns:    1 on success.
+    # 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;
 
-=cut
+        my $flags = Bugzilla::Flag->match({ bug_id      => $bug_id,
+                                            target_type => 'attachment' });
 
-sub validate_content_type {
-    my ($class, $throw_error) = @_;
-    my $cgi = Bugzilla->cgi;
-
-    if (!defined $cgi->param('contenttypemethod')) {
-        $throw_error ? ThrowUserError("missing_content_type_method") : return 0;
-    }
-    elsif ($cgi->param('contenttypemethod') eq 'autodetect') {
-        my $contenttype =
-            $cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
-        # The user asked us to auto-detect the content type, so use the type
-        # specified in the HTTP request headers.
-        if ( !$contenttype ) {
-            $throw_error ? ThrowUserError("missing_content_type") : return 0;
-        }
-        $cgi->param('contenttype', $contenttype);
-    }
-    elsif ($cgi->param('contenttypemethod') eq 'list') {
-        # The user selected a content type from the list, so use their
-        # selection.
-        $cgi->param('contenttype', $cgi->param('contenttypeselection'));
-    }
-    elsif ($cgi->param('contenttypemethod') eq 'manual') {
-        # The user entered a content type manually, so use their entry.
-        $cgi->param('contenttype', $cgi->param('contenttypeentry'));
-    }
-    else {
-        $throw_error ?
-            ThrowCodeError("illegal_content_type_method",
-                           { contenttypemethod => $cgi->param('contenttypemethod') }) :
-            return 0;
-    }
+        # Exclude flags for private attachments you cannot see.
+        @$flags = grep {exists $att{$_->attach_id}} @$flags;
 
-    if ( $cgi->param('contenttype') !~
-           /^(application|audio|image|message|model|multipart|text|video)\/.+$/ ) {
-        $throw_error ?
-            ThrowUserError("invalid_content_type",
-                           { contenttype => $cgi->param('contenttype') }) :
-            return 0;
+        push(@{$att{$_->attach_id}->{flags}}, $_) foreach @$flags;
+        $attachments = [sort {$a->id <=> $b->id} values %att];
     }
-
-    return 1;
+    return $attachments;
 }
 
 =pod
@@ -670,7 +672,7 @@ Description: validates if the user is allowed to view and edit the attachment.
 Params:      $attachment - the attachment object being edited.
              $product_id - the product ID the attachment belongs to.
 
-Returns:     1 on success. Else an error is thrown.
+Returns:     1 on success, 0 otherwise.
 
 =cut
 
@@ -679,15 +681,12 @@ sub validate_can_edit {
     my $user = Bugzilla->user;
 
     # The submitter can edit their attachments.
-    return 1 if ($attachment->attacher->id == $user->id
-                 || ((!$attachment->isprivate || $user->is_insider)
-                      && $user->in_group('editbugs', $product_id)));
-
-    # If we come here, then this attachment cannot be seen by the user.
-    ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
+    return ($attachment->attacher->id == $user->id
+            || ((!$attachment->isprivate || $user->is_insider)
+                 && $user->in_group('editbugs', $product_id))) ? 1 : 0;
 }
 
-=item C<validate_obsolete($bug)>
+=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.
@@ -695,33 +694,34 @@ Description: validates if attachments the user wants to mark as obsolete
              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:     1 on success. Else an error is thrown.
+Returns:     The list of attachment objects to mark as obsolete.
+             Else an error is thrown.
 
 =cut
 
 sub validate_obsolete {
-    my ($class, $bug) = @_;
-    my $cgi = Bugzilla->cgi;
+    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 ($cgi->param('obsolete')) {
+    foreach my $attachid (@$list) {
         my $vars = {};
         $vars->{'attach_id'} = $attachid;
 
         detaint_natural($attachid)
           || ThrowCodeError('invalid_attach_id_to_obsolete', $vars);
 
-        my $attachment = Bugzilla::Attachment->get($attachid);
-
         # Make sure the attachment exists in the database.
-        ThrowUserError('invalid_attach_id', $vars) unless $attachment;
+        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);
+        $attachment->validate_can_edit($bug->product_id)
+          || ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
 
         $vars->{'description'} = $attachment->description;
 
@@ -731,203 +731,150 @@ sub validate_obsolete {
             ThrowCodeError('mismatched_bug_ids_on_obsolete', $vars);
         }
 
-        if ($attachment->isobsolete) {
-          ThrowCodeError('attachment_already_obsolete', $vars);
-        }
+        next if $attachment->isobsolete;
 
         push(@obsolete_attachments, $attachment);
     }
     return @obsolete_attachments;
 }
 
+###############################
+####     Constructors     #####
+###############################
 
 =pod
 
-=item C<insert_attachment_for_bug($throw_error, $bug, $user, $timestamp, $hr_vars)>
+=item C<create>
 
-Description: inserts an attachment from CGI input for the given bug.
+Description: inserts an attachment into the given bug.
 
-Params:     C<$bug> - Bugzilla::Bug object - the bug for which to insert
+Params:     takes a hashref with the following keys:
+            C<bug> - Bugzilla::Bug object - the bug for which to insert
             the attachment.
-            C<$user> - Bugzilla::User object - the user we're inserting an
-            attachment for.
-            C<$timestamp> - scalar - timestamp of the insert as returned
-            by SELECT NOW().
-            C<$hr_vars> - hash reference - reference to a hash of template
-            variables.
-
-Returns:    the ID of the new attachment.
+            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.
+
+Returns:    The new attachment object.
 
 =cut
 
-sub insert_attachment_for_bug {
-    my ($class, $throw_error, $bug, $user, $timestamp, $hr_vars) = @_;
-
-    my $cgi = Bugzilla->cgi;
+sub create {
+    my $class = shift;
     my $dbh = Bugzilla->dbh;
-    my $attachurl = $cgi->param('attachurl') || '';
-    my $data;
-    my $filename;
-    my $contenttype;
-    my $isurl;
-    $class->validate_is_patch($throw_error) || return;
-    $class->validate_description($throw_error) || return;
-
-    if (Bugzilla->params->{'allow_attach_url'}
-        && ($attachurl =~ /^(http|https|ftp):\/\/\S+/)
-        && !defined $cgi->upload('data'))
-    {
-        $filename = '';
-        $data = $attachurl;
-        $isurl = 1;
-        $contenttype = 'text/plain';
-        $cgi->param('ispatch', 0);
-        $cgi->delete('bigfile');
-    }
-    else {
-        $filename = _validate_filename($throw_error) || return;
-        # need to validate content type before data as
-        # we now check the content type for image/bmp in _validate_data()
-        unless ($cgi->param('ispatch')) {
-            $class->validate_content_type($throw_error) || return;
-
-            # Set the ispatch flag to 1 if we're set to autodetect
-            # and the content type is text/x-diff or text/x-patch
-            if ($cgi->param('contenttypemethod') eq 'autodetect'
-                && $cgi->param('contenttype') =~ m{text/x-(?:diff|patch)})
-            {
-                $cgi->param('ispatch', 1);
-                $cgi->param('contenttype', 'text/plain');
-            }
-        }
-        $data = _validate_data($throw_error, $hr_vars);
-        # If the attachment is stored locally, $data eq ''.
-        # If an error is thrown, $data eq '0'.
-        ($data ne '0') || return;
-        $contenttype = $cgi->param('contenttype');
-
-        # These are inserted using placeholders so no need to panic
-        trick_taint($filename);
-        trick_taint($contenttype);
-        $isurl = 0;
-    }
 
-    # Check attachments the user tries to mark as obsolete.
-    my @obsolete_attachments;
-    if ($cgi->param('obsolete')) {
-        @obsolete_attachments = $class->validate_obsolete($bug);
-    }
+    $class->check_required_create_fields(@_);
+    my $params = $class->run_create_validators(@_);
 
-    # The order of these function calls is important, as Flag::validate
-    # assumes User::match_field has ensured that the
-    # values in the requestee fields are legitimate user email addresses.
-    my $match_status = Bugzilla::User::match_field($cgi, {
-        '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
-    }, MATCH_SKIP_CONFIRM);
+    # Extract everything which is not a valid column name.
+    my $bug = delete $params->{bug};
+    $params->{bug_id} = $bug->id;
+    my $data = delete $params->{data};
+    my $size = delete $params->{filesize};
 
-    $hr_vars->{'match_field'} = 'requestee';
-    if ($match_status == USER_MATCH_FAILED) {
-        $hr_vars->{'message'} = 'user_match_failed';
-    }
-    elsif ($match_status == USER_MATCH_MULTIPLE) {
-        $hr_vars->{'message'} = 'user_match_multiple';
-    }
+    my $attachment = $class->insert_create_data($params);
+    my $attachid = $attachment->id;
 
-    # Escape characters in strings that will be used in SQL statements.
-    my $description = $cgi->param('description');
-    trick_taint($description);
-    my $isprivate = $cgi->param('isprivate') ? 1 : 0;
-
-    # Insert the attachment into the database.
-    my $sth = $dbh->do(
-        "INSERT INTO attachments
-            (bug_id, creation_ts, modification_time, filename, description,
-             mimetype, ispatch, isurl, isprivate, submitter_id)
-         VALUES (?,?,?,?,?,?,?,?,?,?)", undef, ($bug->bug_id, $timestamp, $timestamp,
-              $filename, $description, $contenttype, $cgi->param('ispatch'),
-              $isurl, $isprivate, $user->id));
-    # Retrieve the ID of the newly created attachment record.
-    my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
-
-    # We only use $data here in this INSERT with a placeholder,
-    # so it's safe.
-    $sth = $dbh->prepare("INSERT INTO attach_data
-                         (id, thedata) VALUES ($attachid, ?)");
-    trick_taint($data);
-    $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
-    $sth->execute();
-
-    # If the file is to be stored locally, stream the file from the web server
-    # to the local file without reading it into a local variable.
-    if ($cgi->param('bigfile')) {
+    # The file is too large to be stored in the DB, so we store it locally.
+    if ($size > Bugzilla->params->{'maxattachmentsize'} * 1024) {
         my $attachdir = bz_locations()->{'attachdir'};
-        my $fh = $cgi->upload('data');
         my $hash = ($attachid % 100) + 100;
         $hash =~ s/.*(\d\d)$/group.$1/;
         mkdir "$attachdir/$hash", 0770;
         chmod 0770, "$attachdir/$hash";
-        open(AH, ">$attachdir/$hash/attachment.$attachid");
-        binmode AH;
-        my $sizecount = 0;
-        my $limit = (Bugzilla->params->{"maxlocalattachment"} * 1048576);
-        while (<$fh>) {
-            print AH $_;
-            $sizecount += length($_);
-            if ($sizecount > $limit) {
-                close AH;
-                close $fh;
-                unlink "$attachdir/$hash/attachment.$attachid";
-                $throw_error ? ThrowUserError("local_file_too_large") : return;
-            }
+        if (ref $data) {
+            copy($data, "$attachdir/$hash/attachment.$attachid");
+            close $data;
         }
-        close AH;
-        close $fh;
+        else {
+            open(AH, '>', "$attachdir/$hash/attachment.$attachid");
+            binmode AH;
+            print AH $data;
+            close AH;
+        }
+        $data = ''; # Will be stored in the DB.
+    }
+    # If we have a filehandle, we need its content to store it in the DB.
+    elsif (ref $data) {
+        local $/;
+        # Store the content in a temp variable while we close the FH.
+        my $tmp = <$data>;
+        close $data;
+        $data = $tmp;
     }
 
-    # Make existing attachments obsolete.
-    my $fieldid = get_field_id('attachments.isobsolete');
+    my $sth = $dbh->prepare("INSERT INTO attach_data
+                             (id, thedata) VALUES ($attachid, ?)");
+
+    trick_taint($data);
+    $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
+    $sth->execute();
+
+    $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->{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)');
 
-    foreach my $obsolete_attachment (@obsolete_attachments) {
-        # If the obsolete attachment has request flags, cancel them.
-        # This call must be done before updating the 'attachments' table.
-        Bugzilla::Flag->CancelRequests($bug, $obsolete_attachment, $timestamp);
+    my ($changes, $old_self) = $self->SUPER::update(@_);
 
-        $dbh->do('UPDATE attachments SET isobsolete = 1, modification_time = ?
-                  WHERE attach_id = ?',
-                 undef, ($timestamp, $obsolete_attachment->id));
+    my ($removed, $added) = Bugzilla::Flag->update_flags($self, $old_self, $timestamp);
+    if ($removed || $added) {
+        $changes->{'flagtypes.name'} = [$removed, $added];
+    }
 
-        $dbh->do('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
-                                             fieldid, removed, added)
-                       VALUES (?,?,?,?,?,?,?)',
-                  undef, ($bug->bug_id, $obsolete_attachment->id, $user->id,
-                          $timestamp, $fieldid, 0, 1));
+    # 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]);
     }
 
-    my $attachment = Bugzilla::Attachment->get($attachid);
-
-    # 1. Add flags, if any. To avoid dying if something goes wrong
-    # while processing flags, we will eval() flag validation.
-    # This requires errors to die().
-    # XXX: this can go away as soon as flag validation is able to
-    #      fail without dying.
-    #
-    # 2. Flag::validate() should not detect any reference to existing flags
-    # when creating a new attachment. Setting the third param to -1 will
-    # force this function to check this point.
-    my $error_mode_cache = Bugzilla->error_mode;
-    Bugzilla->error_mode(ERROR_MODE_DIE);
-    eval {
-        Bugzilla::Flag::validate($bug->bug_id, -1, SKIP_REQUESTEE_ON_ERROR);
-        Bugzilla::Flag->process($bug, $attachment, $timestamp, $hr_vars);
-    };
-    Bugzilla->error_mode($error_mode_cache);
-    if ($@) {
-        $hr_vars->{'message'} = 'flag_creation_failed';
-        $hr_vars->{'flag_creation_error'} = $@;
+    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 the new attachment object.
-    return $attachment;
+    return $changes;
 }
 
 =pod
@@ -951,9 +898,61 @@ sub remove_from_db {
     $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->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isobsolete = ?
+              WHERE attach_id = ?', undef, ('text/plain', 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('attach_text'));
+
+    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;
index cfc7610..01a624a 100644 (file)
@@ -37,6 +37,7 @@ sub process_diff {
         $last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
         # Actually print out the patch.
         print $cgi->header(-type => 'text/plain',
+                           -x_content_type_options => "nosniff",
                            -expires => '+3M');
         disable_utf8();
         $reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
@@ -118,6 +119,7 @@ sub process_interdiff {
         $last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
         # Actually print out the patch.
         print $cgi->header(-type => 'text/plain',
+                           -x_content_type_options => "nosniff",
                            -expires => '+3M');
         disable_utf8();
     }
index 8e18f86..45034e1 100644 (file)
@@ -32,6 +32,9 @@ use fields qw(
 
 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;
@@ -83,7 +86,7 @@ sub login {
 
     # Make sure the user isn't disabled.
     my $user = $login_info->{user};
-    if ($user->disabledtext) {
+    if (!$user->is_enabled) {
         return $self->_handle_login_result({ failure => AUTH_DISABLED,
                                               user    => $user }, $type);
     }
@@ -131,6 +134,12 @@ sub user_can_create_account {
            && $getter->user_can_create_account;
 }
 
+sub extern_id_used {
+    my ($self) = @_;
+    return $self->{_info_getter}->extern_id_used
+           ||  $self->{_verifier}->extern_id_used;
+}
+
 sub can_change_email {
     return $_[0]->user_can_create_account;
 }
@@ -143,12 +152,22 @@ sub _handle_login_result {
     my $fail_code = $result->{failure};
 
     if (!$fail_code) {
-        if ($self->{_info_getter}->{successful}->requires_persistence) {
+        # 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) {
-        ThrowCodeError($result->{error}, $result->{details});
+        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) 
@@ -162,7 +181,10 @@ sub _handle_login_result {
     # 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) {
-        ThrowUserError("invalid_username_or_password");
+        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) {
@@ -173,6 +195,41 @@ sub _handle_login_result {
         ThrowUserError("account_disabled",
             {'disabled_reason' => $result->{user}->disabledtext});
     }
+    elsif ($fail_code == AUTH_LOCKOUT) {
+        my $attempts = $user->account_ip_login_failures;
+
+        # We want to know when the account will be unlocked. This is 
+        # determined by the 5th-from-last login failure (or more/less than
+        # 5th, if MAX_LOGIN_ATTEMPTS is not 5).
+        my $determiner = $attempts->[scalar(@$attempts) - MAX_LOGIN_ATTEMPTS];
+        my $unlock_at = datetime_from($determiner->{login_time}, 
+                                      Bugzilla->local_timezone);
+        $unlock_at->add(minutes => LOGIN_LOCKOUT_INTERVAL);
+
+        # If we were *just* locked out, notify the maintainer about the
+        # lockout.
+        if ($result->{just_locked_out}) {
+            # We're sending to the maintainer, who may be not a Bugzilla 
+            # account, but just an email address. So we use the
+            # installation's default language for sending the email.
+            my $default_settings = Bugzilla::User::Setting::get_defaults();
+            my $template = Bugzilla->template_inner(
+                               $default_settings->{lang}->{default_value});
+            my $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 });
@@ -234,6 +291,11 @@ various fields to be used in the error message.
 
 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>.
@@ -251,6 +313,15 @@ should never be communicated to the user, for security reasons.
 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
@@ -333,6 +404,10 @@ Params:      None
 Returns:     C<true> if users are allowed to create new Bugzilla accounts,
              C<false> otherwise.
 
+=item C<extern_id_used>
+
+Description: Whether or not current login system uses extern_id.
+
 =item C<can_change_email>
 
 Description: Whether or not the current login system allows users to
index 4a4c5f2..42ce51c 100644 (file)
@@ -27,6 +27,8 @@ use constant can_login  => 1;
 use constant requires_persistence  => 1;
 use constant requires_verification => 1;
 use constant user_can_create_account => 0;
+use constant is_automatic => 0;
+use constant extern_id_used => 0; 
 
 sub new {
     my ($class) = @_;
@@ -122,4 +124,20 @@ got from this login method. Defaults to C<true>.
 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>.
+
+=item C<extern_id_used>
+
+Whether or not this login method uses the extern_id field. If
+used, users with editusers permission will be be allowed to
+edit the extern_id for all users.
+
+The default value is C<0>.
+
 =back
index 5be98aa..8e877b9 100644 (file)
@@ -40,12 +40,10 @@ use Bugzilla::Error;
 
 sub get_login_info {
     my ($self) = @_;
-    my $cgi = Bugzilla->cgi;
-
-    my $username = trim($cgi->param("Bugzilla_login"));
-    my $password = $cgi->param("Bugzilla_password");
+    my $params = Bugzilla->input_params;
 
-    $cgi->delete('Bugzilla_login', 'Bugzilla_password');
+    my $username = trim(delete $params->{"Bugzilla_login"});
+    my $password = delete $params->{"Bugzilla_password"};
 
     if (!defined $username || !defined $password) {
         return { failure => AUTH_NODATA };
@@ -59,21 +57,8 @@ sub fail_nodata {
     my $cgi = Bugzilla->cgi;
     my $template = Bugzilla->template;
 
-    if (Bugzilla->error_mode == Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT) {
-        die SOAP::Fault
-            ->faultcode(ERROR_AUTH_NODATA)
-            ->faultstring('Login Required');
-    }
-
-    # If system is not configured to never require SSL connections
-    # we want to always redirect to SSL since passing usernames and
-    # passwords over an unprotected connection is a bad idea. If we 
-    # get here then a login form will be provided to the user so we
-    # want this to be protected if possible.
-    if ($cgi->protocol ne 'https' && Bugzilla->params->{'sslbase'} ne ''
-        && Bugzilla->params->{'ssl'} ne 'never')
-    {
-        $cgi->require_https(Bugzilla->params->{'sslbase'});
+    if (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
+        ThrowUserError('login_required');
     }
 
     print $cgi->header();
index e2cd8f5..91fb820 100644 (file)
@@ -27,6 +27,7 @@ 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.
@@ -35,8 +36,7 @@ sub get_login_info {
     my $cgi = Bugzilla->cgi;
     my $dbh = Bugzilla->dbh;
 
-    my $ip_addr      = $cgi->remote_addr();
-    my $net_addr     = get_netaddr($ip_addr);
+    my $ip_addr      = remote_ip();
     my $login_cookie = $cgi->cookie("Bugzilla_logincookie");
     my $user_id      = $cgi->cookie("Bugzilla_login");
 
@@ -60,24 +60,16 @@ sub get_login_info {
         trick_taint($login_cookie);
         detaint_natural($user_id);
 
-        my $query = "SELECT userid
-                       FROM logincookies
-                      WHERE logincookies.cookie = ?
-                            AND logincookies.userid = ?
-                            AND (logincookies.ipaddr = ?";
-
-        # If we have a network block that's allowed to use this cookie,
-        # as opposed to just a single IP.
-        my @params = ($login_cookie, $user_id, $ip_addr);
-        if (defined $net_addr) {
-            trick_taint($net_addr);
-            $query .= " OR logincookies.ipaddr = ?";
-            push(@params, $net_addr);
-        }
-        $query .= ")";
+        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 ($dbh->selectrow_array($query, undef, @params)) {
+        if ($is_valid) {
             # If we logged in successfully, then update the lastused 
             # time on the login cookie
             $dbh->do("UPDATE logincookies SET lastused = NOW() 
index 180e79b..76227f1 100644 (file)
@@ -29,7 +29,10 @@ 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;
+use constant extern_id_used => 1;
 
 sub get_login_info {
     my ($self) = @_;
index d510038..e8d9c46 100644 (file)
@@ -26,16 +26,26 @@ use fields qw(
     _stack
     successful
 );
+use Hash::Util qw(lock_keys);
+use Bugzilla::Hook;
+use Bugzilla::Constants;
+use List::MoreUtils qw(any);
 
 sub new {
     my $class = shift;
     my $self = $class->SUPER::new(@_);
     my $list = shift;
+    my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list);
+    lock_keys(%methods);
+    Bugzilla::Hook::process('auth_login_methods', { modules => \%methods });
+
     $self->{_stack} = [];
     foreach my $login_method (split(',', $list)) {
-        require "Bugzilla/Auth/Login/${login_method}.pm";
-        push(@{$self->{_stack}}, 
-             "Bugzilla::Auth::Login::$login_method"->new(@_));
+        my $module = $methods{$login_method};
+        require $module;
+        $module =~ s|/|::|g;
+        $module =~ s/.pm$//;
+        push(@{$self->{_stack}}, $module->new(@_));
     }
     return $self;
 }
@@ -44,10 +54,20 @@ 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.
+        
+        # We only carry on down the stack if this method denied all knowledge.
+        last unless ($result->{failure}
+                    && ($result->{failure} eq AUTH_NODATA 
+                       || $result->{failure} eq AUTH_NO_SUCH_USER));
+        
+        # If none of the methods succeed, it's undef.
         $self->{successful} = undef;
     }
     return $result;
@@ -84,4 +104,9 @@ sub user_can_create_account {
     return 0;
 }
 
+sub extern_id_used {
+    my ($self) = @_;
+    return any { $_->extern_id_used } @{ $self->{_stack} };
+}
+
 1;
index 420bad1..57fa962 100644 (file)
@@ -48,18 +48,16 @@ sub persist_login {
     my ($self, $user) = @_;
     my $dbh = Bugzilla->dbh;
     my $cgi = Bugzilla->cgi;
-
-    my $ip_addr = $cgi->remote_addr;
-    unless ($cgi->param('Bugzilla_restrictlogin') ||
-            Bugzilla->params->{'loginnetmask'} == 32) 
-    {
-        $ip_addr = get_netaddr($ip_addr);
+    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);
     }
 
-    # 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 = 
@@ -71,8 +69,9 @@ sub persist_login {
 
     # 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->do("DELETE FROM logincookies WHERE lastused < "
+             . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-',
+                                   MAX_LOGINCOOKIE_AGE, 'DAY'));
 
     $dbh->bz_commit_transaction();
 
@@ -83,17 +82,15 @@ sub persist_login {
     # or admin didn't forbid it and user told to remember.
     if ( Bugzilla->params->{'rememberlogin'} eq 'on' ||
          (Bugzilla->params->{'rememberlogin'} ne 'off' &&
-          $cgi->param('Bugzilla_remember') &&
-          $cgi->param('Bugzilla_remember') eq 'on') ) 
+          $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'} ne 'never'
-        && Bugzilla->params->{'sslbase'} ne '')
-    {
-        # Bugzilla->login will automatically redirect to https://,
-        # so it's safe to turn on the 'secure' bit.
+    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;
     }
 
@@ -161,6 +158,7 @@ sub clear_browser_cookies {
     my $cgi = Bugzilla->cgi;
     $cgi->remove_cookie('Bugzilla_login');
     $cgi->remove_cookie('Bugzilla_logincookie');
+    $cgi->remove_cookie('sudo');
 }
 
 1;
index b293e25..a8cd0af 100644 (file)
@@ -25,6 +25,7 @@ use Bugzilla::User;
 use Bugzilla::Util;
 
 use constant user_can_create_account => 1;
+use constant extern_id_used => 0;
 
 sub new {
     my ($class, $login_type) = @_;
@@ -232,4 +233,12 @@ C<false>.
 Whether or not users can manually create accounts in this type of
 account source. Defaults to C<true>.
 
+=item C<extern_id_used>
+
+Whether or not this verifier method uses the extern_id field. If
+used, users with editusers permission will be be allowed to
+edit the extern_id for all users.
+
+The default value is C<false>.
+
 =back
index f2c008d..2fcfd40 100644 (file)
@@ -41,33 +41,58 @@ sub check_credentials {
     my $dbh = Bugzilla->dbh;
 
     my $username = $login_data->{username};
-    my $user_id  = login_to_id($username);
+    my $user = new Bugzilla::User({ name => $username });
 
-    return { failure => AUTH_NO_SUCH_USER } unless $user_id;
+    return { failure => AUTH_NO_SUCH_USER } unless $user;
 
-    $login_data->{bz_username} = $username;
-    my $password = $login_data->{password};
-
-    trick_taint($username);
-    my ($real_password_crypted) = $dbh->selectrow_array(
-        "SELECT cryptpassword FROM profiles WHERE userid = ?",
-        undef, $user_id);
+    $login_data->{user} = $user;
+    $login_data->{bz_username} = $user->login;
 
-    # Wide characters cause crypt to die
-    if (Bugzilla->params->{'utf8'}) {
-        utf8::encode($password) if utf8::is_utf8($password);
+    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 = crypt($password, $real_password_crypted);
-    return { failure => AUTH_LOGINFAILED }
-        if $entered_password_crypted ne $real_password_crypted;
+    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 they may have generated.
-    Bugzilla::Token::DeletePasswordTokens($user_id, "user_logged_in");
+    # 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;
 }
index b590430..cdc802c 100644 (file)
@@ -56,7 +56,7 @@ sub check_credentials {
     # just appending the Base DN to the uid isn't sufficient to get the
     # user's DN.  For servers which don't work this way, there will still
     # be no harm done.
-    $self->_bind_ldap_anonymously();
+    $self->_bind_ldap_for_search();
 
     # Now, we verify that the user exists, and get a LDAP Distinguished
     # Name for the user.
@@ -76,12 +76,35 @@ sub check_credentials {
     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 => $detail_result->error, username => $username}
-    } if $detail_result->code;
+             details => {errstr => $error_string, username => $username}
+    } if !$user_entry;
 
-    my $user_entry = $detail_result->shift_entry;
 
     my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
     if ($mail_attr) {
@@ -128,7 +151,7 @@ sub _bz_search_params {
                       . Bugzilla->params->{"LDAPfilter"} . ')');
 }
 
-sub _bind_ldap_anonymously {
+sub _bind_ldap_for_search {
     my ($self) = @_;
     my $bind_result;
     if (Bugzilla->params->{"LDAPbinddn"}) {
index 577b5a2..e1a0119 100644 (file)
@@ -22,15 +22,26 @@ use fields qw(
     successful
 );
 
+use Bugzilla::Hook;
+
+use Hash::Util qw(lock_keys);
+use List::MoreUtils qw(any);
+
 sub new {
     my $class = shift;
     my $list = shift;
     my $self = $class->SUPER::new(@_);
+    my %methods = map { $_ => "Bugzilla/Auth/Verify/$_.pm" } split(',', $list);
+    lock_keys(%methods);
+    Bugzilla::Hook::process('auth_verify_methods', { modules => \%methods });
+
     $self->{_stack} = [];
     foreach my $verify_method (split(',', $list)) {
-        require "Bugzilla/Auth/Verify/${verify_method}.pm";
-        push(@{$self->{_stack}}, 
-             "Bugzilla::Auth::Verify::$verify_method"->new(@_));
+        my $module = $methods{$verify_method};
+        require $module;
+        $module =~ s|/|::|g;
+        $module =~ s/.pm$//;
+        push(@{$self->{_stack}}, $module->new(@_));
     }
     return $self;
 }
@@ -78,4 +89,9 @@ sub user_can_create_account {
     return 0;
 }
 
+sub extern_id_used {
+    my ($self) = @_;
+    return any { $_->extern_id_used } @{ $self->{_stack} };
+}
+
 1;
index 7f2b8d8..a848c86 100644 (file)
@@ -25,6 +25,8 @@
 #                 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>
+#                 Christian Legnitto <clegnitto@mozilla.com>
 
 package Bugzilla::Bug;
 
@@ -37,24 +39,30 @@ 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 Bugzilla::BugUrl;
 
-use List::Util qw(min);
+use List::MoreUtils qw(firstidx uniq part);
+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 ValidateBugID
-    RemoveVotes CheckIfVotedConfirmed
+    bug_alias_to_id
     LogActivityEntry
     editable_bug_fields
-    SPECIAL_STATUS_WORKFLOW_ACTIONS
 );
 
 #####################################################################
@@ -65,6 +73,9 @@ use constant DB_TABLE   => 'bugs';
 use constant ID_FIELD   => 'bug_id';
 use constant NAME_FIELD => 'alias';
 use constant LIST_ORDER => ID_FIELD;
+# Bugs have their own auditing table, bugs_activity.
+use constant AUDIT_CREATES => 0;
+use constant AUDIT_UPDATES => 0;
 
 # This is a sub because it needs to call other subroutines.
 sub DB_COLUMNS {
@@ -72,7 +83,8 @@ sub DB_COLUMNS {
     my @custom = grep {$_->type != FIELD_TYPE_MULTI_SELECT}
                       Bugzilla->active_custom_fields;
     my @custom_names = map {$_->name} @custom;
-    return qw(
+
+    my @columns = (qw(
         alias
         assigned_to
         bug_file_loc
@@ -84,6 +96,7 @@ sub DB_COLUMNS {
         delta_ts
         estimated_time
         everconfirmed
+        lastdiffed
         op_sys
         priority
         product_id
@@ -100,34 +113,45 @@ sub DB_COLUMNS {
     '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;
+    @custom_names);
+    
+    Bugzilla::Hook::process("bug_columns", { columns => \@columns });
+    
+    return @columns;
 }
 
-use constant REQUIRED_CREATE_FIELDS => qw(
-    component
-    product
-    short_desc
-    version
-);
-
-# There are also other, more complex validators that are called
-# from run_create_validators.
 sub VALIDATORS {
+
     my $validators = {
         alias          => \&_check_alias,
+        assigned_to    => \&_check_assigned_to,
         bug_file_loc   => \&_check_bug_file_loc,
-        bug_severity   => \&_check_bug_severity,
+        bug_severity   => \&_check_select_field,
+        bug_status     => \&_check_bug_status,
+        cc             => \&_check_cc,
         comment        => \&_check_comment,
-        commentprivacy => \&_check_commentprivacy,
+        component      => \&_check_component,
+        creation_ts    => \&_check_creation_ts,
         deadline       => \&_check_deadline,
-        estimated_time => \&_check_estimated_time,
-        op_sys         => \&_check_op_sys,
+        dup_id         => \&_check_dup_id,
+        estimated_time => \&_check_time_field,
+        everconfirmed  => \&Bugzilla::Object::check_boolean,
+        groups         => \&_check_groups,
+        keywords       => \&_check_keywords,
+        op_sys         => \&_check_select_field,
         priority       => \&_check_priority,
         product        => \&_check_product,
-        remaining_time => \&_check_remaining_time,
-        rep_platform   => \&_check_rep_platform,
+        qa_contact     => \&_check_qa_contact,
+        remaining_time => \&_check_time_field,
+        rep_platform   => \&_check_select_field,
+        resolution     => \&_check_resolution,
         short_desc     => \&_check_short_desc,
         status_whiteboard => \&_check_status_whiteboard,
+        target_milestone  => \&_check_target_milestone,
+        version           => \&_check_version,
+
+        cclist_accessible   => \&Bugzilla::Object::check_boolean,
+        reporter_accessible => \&Bugzilla::Object::check_boolean,
     };
 
     # Set up validators for custom fields.    
@@ -145,6 +169,9 @@ sub VALIDATORS {
         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;
         }
@@ -154,16 +181,33 @@ sub VALIDATORS {
     return $validators;
 };
 
-use constant UPDATE_VALIDATORS => {
-    assigned_to         => \&_check_assigned_to,
-    bug_status          => \&_check_bug_status,
-    cclist_accessible   => \&Bugzilla::Object::check_boolean,
-    dup_id              => \&_check_dup_id,
-    qa_contact          => \&_check_qa_contact,
-    reporter_accessible => \&Bugzilla::Object::check_boolean,
-    resolution          => \&_check_resolution,
-    target_milestone    => \&_check_target_milestone,
-    version             => \&_check_version,
+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'],
+        comment          => ['creation_ts'],
+        component        => ['product'],
+        dup_id           => ['bug_status', 'resolution'],
+        groups           => ['product'],
+        keywords         => ['product'],
+        resolution       => ['bug_status'],
+        qa_contact       => ['component'],
+        target_milestone => ['product'],
+        version          => ['product'],
+    );
+
+    foreach my $field (@{ Bugzilla->fields }) {
+        $deps{$field->name} = [ $field->visibility_field->name ]
+            if $field->{visibility_field_id};
+    }
+
+    $cache->{bug_validator_dependencies} = \%deps;
+    return \%deps;
 };
 
 sub UPDATE_COLUMNS {
@@ -204,39 +248,89 @@ use constant NUMERIC_COLUMNS => qw(
 );
 
 sub DATE_COLUMNS {
-    my @fields = Bugzilla->get_fields(
-        { custom => 1, type => FIELD_TYPE_DATETIME });
+    my @fields = @{ Bugzilla->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;
 
-use constant SPECIAL_STATUS_WORKFLOW_ACTIONS => qw(
-    none
-    duplicate
-    change_resolution
-    clearresolution
-);
+# 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',
+};
+
+# Creation timestamp is here because it needs to be validated
+# but it can be NULL in the database (see comments in create above)
+#
+# 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(creation_ts target_milestone cc qa_contact groups);
 
 #####################################################################
 
+# This and "new" catch every single way of creating a bug, so that we
+# can call _create_cf_accessors.
+sub _do_list_select {
+    my $invocant = shift;
+    $invocant->_create_cf_accessors();
+    return $invocant->SUPER::_do_list_select(@_);
+}
+
 sub new {
     my $invocant = shift;
     my $class = ref($invocant) || $invocant;
     my $param = shift;
 
+    $class->_create_cf_accessors();
+
+    # 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+$/)) {
@@ -261,15 +355,227 @@ sub new {
     # 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;
-        $error_self->{'bug_id'} = ref($param) ? $param->{name} : $param;
-        $error_self->{'error'}  = 'NotFound';
         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(@_);
+}
+
+# Helps load up information for bugs for show_bug.cgi and other situations
+# that will need to access info on lots of bugs.
+sub preload {
+    my ($class, $bugs) = @_;
+    my $user = Bugzilla->user;
+
+    # It would be faster but MUCH more complicated to select all the
+    # deps for the entire list in one SQL statement. If we ever have
+    # a profile that proves that that's necessary, we can switch over
+    # to the more complex method.
+    my @all_dep_ids;
+    foreach my $bug (@$bugs) {
+        push(@all_dep_ids, @{ $bug->blocked }, @{ $bug->dependson });
+    }
+    @all_dep_ids = uniq @all_dep_ids;
+    # If we don't do this, can_see_bug will do one call per bug in
+    # the dependency lists, during get_bug_link in Bugzilla::Template.
+    $user->visible_bugs(\@all_dep_ids);
+}
+
+sub possible_duplicates {
+    my ($class, $params) = @_;
+    my $short_desc = $params->{summary};
+    my $products = $params->{products} || [];
+    my $limit = $params->{limit} || MAX_POSSIBLE_DUPLICATES;
+    $limit = MAX_POSSIBLE_DUPLICATES if $limit > MAX_POSSIBLE_DUPLICATES;
+    $products = [$products] if !ref($products) eq 'ARRAY';
+
+    my $orig_limit = $limit;
+    detaint_natural($limit) 
+        || ThrowCodeError('param_must_be_numeric', 
+                          { function => 'possible_duplicates',
+                            param    => $orig_limit });
+
+    my $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 " .
+          $dbh->sql_limit($sql_limit), {Slice=>{}});
+
+    my @actual_dupe_ids;
+    # Resolve duplicates into their ultimate target duplicates.
+    foreach my $bug (@$possible_dupes) {
+        my $push_id = $bug->{bug_id};
+        if ($bug->{resolution} && $bug->{resolution} eq 'DUPLICATE') {
+            $push_id = _resolve_ultimate_dup_id($bug->{bug_id});
+        }
+        push(@actual_dupe_ids, $push_id);
+    }
+    @actual_dupe_ids = uniq @actual_dupe_ids;
+    if (scalar @actual_dupe_ids > $limit) {
+        @actual_dupe_ids = @actual_dupe_ids[0..($limit-1)];
+    }
+
+    my $visible = $user->visible_bugs(\@actual_dupe_ids);
+    return $class->new_from_list($visible);
+}
+
 # Docs for create() (there's no POD in this file yet, but we very
 # much need this documented right now):
 #
@@ -331,18 +637,12 @@ sub create {
 
     # 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 ($comment, $privacy) = ($params->{comment}, $params->{commentprivacy});
-    delete $params->{comment};
-    delete $params->{commentprivacy};
-
-    # Set up the keyword cache for bug creation.
-    my $keywords = $params->{keywords};
-    $params->{keywords} = join(', ', sort {lc($a) cmp lc($b)} 
-                                          map($_->name, @$keywords));
+    my $cc_ids           = delete $params->{cc};
+    my $groups           = delete $params->{groups};
+    my $depends_on       = delete $params->{dependson};
+    my $blocked          = delete $params->{blocked};
+    my $keywords         = delete $params->{keywords};
+    my $creation_comment = delete $params->{comment};
 
     # We don't want the bug to appear in the system until it's correctly
     # protected by groups.
@@ -354,8 +654,8 @@ sub create {
     # Add the group restrictions
     my $sth_group = $dbh->prepare(
         'INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
-    foreach my $group_id (@$groups) {
-        $sth_group->execute($bug->bug_id, $group_id);
+    foreach my $group (@$groups) {
+        $sth_group->execute($bug->bug_id, $group->id);
     }
 
     $dbh->do('UPDATE bugs SET creation_ts = ? WHERE bug_id = ?', undef,
@@ -406,20 +706,18 @@ sub create {
         }
     }
 
-    # And insert the comment. We always insert a comment on bug creation,
+    # Comment #0 handling...
+
+    # We now have a bug id so we can fill this out
+    $creation_comment->{'bug_id'} = $bug->id;
+
+    # Insert the comment. We always insert a comment on bug creation,
     # but sometimes it's blank.
-    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::Comment->insert_create_data($creation_comment);
+
+    Bugzilla::Hook::process('bug_end_of_create', { bug => $bug,
+                                                   timestamp => $timestamp,
+                                                 });
 
     $dbh->bz_commit_transaction();
 
@@ -431,43 +729,17 @@ sub create {
     return $bug;
 }
 
-
 sub run_create_validators {
     my $class  = shift;
     my $params = $class->SUPER::run_create_validators(@_);
 
-    my $product = $params->{product};
+    my $product = delete $params->{product};
     $params->{product_id} = $product->id;
-    delete $params->{product};
-
-    ($params->{bug_status}, $params->{everconfirmed})
-        = $class->_check_bug_status($params->{bug_status}, $product,
-                                    $params->{comment});
-
-    $params->{target_milestone} = $class->_check_target_milestone(
-        $params->{target_milestone}, $product);
-
-    $params->{version} = $class->_check_version($params->{version}, $product);
-
-    $params->{keywords} = $class->_check_keywords($params->{keywords}, $product);
-
-    $params->{groups} = $class->_check_groups($product,
-        $params->{groups});
-
-    my $component = $class->_check_component($params->{component}, $product);
+    my $component = delete $params->{component};
     $params->{component_id} = $component->id;
-    delete $params->{component};
 
-    $params->{assigned_to} = 
-        $class->_check_assigned_to($params->{assigned_to}, $component);
-    $params->{qa_contact} =
-        $class->_check_qa_contact($params->{qa_contact}, $component);
-    $params->{cc} = $class->_check_cc($component, $params->{cc});
-
-    # Callers cannot set Reporter, currently.
+    # Callers cannot set reporter, creation_ts, or delta_ts.
     $params->{reporter} = $class->_check_reporter();
-
-    $params->{creation_ts} ||= Bugzilla->dbh->selectrow_array('SELECT NOW()');
     $params->{delta_ts} = $params->{creation_ts};
 
     if ($params->{estimated_time}) {
@@ -483,10 +755,20 @@ sub run_create_validators {
 
     # You can't set these fields on bug creation (or sometimes ever).
     delete $params->{resolution};
-    delete $params->{votes};
     delete $params->{lastdiffed};
     delete $params->{bug_id};
 
+    Bugzilla::Hook::process('bug_end_of_create_validators',
+                            { params => $params });
+
+    my @mandatory_fields = @{ Bugzilla->fields({ is_mandatory => 1,
+                                                 enter_bug    => 1,
+                                                 obsolete     => 0 }) };
+    foreach my $field (@mandatory_fields) {
+        $class->_check_field_is_mandatory($params->{$field->name}, $field,
+                                          $params);
+    }
+
     return $params;
 }
 
@@ -496,10 +778,11 @@ sub update {
     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 NOW()");
+    my $delta_ts = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+    $dbh->bz_start_transaction();
 
-    my $old_bug = $self->new($self->id);
-    my $changes = $self->SUPER::update(@_);
+    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.
@@ -557,8 +840,6 @@ sub update {
         $dbh->do('INSERT INTO keywords (bug_id, keywordid) VALUES (?,?)',
                  undef, $self->id, $keyword_id);
     }
-    $dbh->do('UPDATE bugs SET keywords = ? WHERE bug_id = ?', undef,
-             $self->keywords, $self->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);
@@ -625,25 +906,33 @@ sub update {
         $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},
+        # Override the Comment's timestamp to be identical to the update
+        # timestamp.
+        $comment->{bug_when} = $delta_ts;
+        $comment = Bugzilla::Comment->insert_create_data($comment);
+        if ($comment->work_time) {
+            LogActivityEntry($self->id, "work_time", "", $comment->work_time,
                 Bugzilla->user->id, $delta_ts);
         }
     }
-    
-    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);
-        # XXX It'd be nice to track this in the bug activity.
+
+    # Comment Privacy 
+    foreach my $comment (@{$self->{comment_isprivate} || []}) {
+        $comment->update();
+        
+        my ($from, $to) 
+            = $comment->is_private ? (0, 1) : (1, 0);
+        LogActivityEntry($self->id, "longdescs.isprivate", $from, $to, 
+                         Bugzilla->user->id, $delta_ts, $comment->id);
     }
 
     # Insert the values into the multiselect value tables
@@ -664,6 +953,23 @@ sub update {
         }
     }
 
+    # See Also
+
+    my ($removed_see, $added_see) =
+        diff_arrays($old_bug->see_also, $self->see_also, 'name');
+
+    $_->remove_from_db foreach @$removed_see;
+    $_->insert_create_data($_) foreach @$added_see;
+
+    # If any changes were found, record it in the activity log
+    if (scalar @$removed_see || scalar @$added_see) {
+        $changes->{see_also} = [join(', ', map { $_->name } @$removed_see),
+                                join(', ', map { $_->name } @$added_see)];
+    }
+
+    $_->update foreach @{ $self->{_update_ref_bugs} || [] };
+    delete $self->{_update_ref_bugs};
+
     # Log bugs_activity items
     # XXX Eventually, when bugs_activity is able to track the dupe_id,
     # this code should go below the duplicates-table-updating code below.
@@ -690,29 +996,37 @@ sub update {
         $changes->{'dup_id'} = [$old_dup || undef, $cur_dup || undef];
     }
 
-    Bugzilla::Hook::process('bug-end_of_update', { bug       => $self,
-                                                   timestamp => $delta_ts,
-                                                   changes   => $changes,
-                                                 });
+    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}) {
+    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};
+        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;
 }
 
@@ -784,8 +1098,6 @@ sub remove_from_db {
     # - flags
     # - keywords
     # - longdescs
-    # - votes
-    # Also included are custom multi-select fields.
 
     # Also, the attach_data table uses attachments.attach_id as a foreign
     # key, and so indirectly depends on a bug deletion too.
@@ -801,7 +1113,6 @@ sub remove_from_db {
              undef, ($bug_id, $bug_id));