Upgrade bugs.webkit.org to Bugzilla 4.2
<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: http://svn.webkit.org/repository/webkit/trunk@174764 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Websites/bugs.webkit.org/.bzrignore b/Websites/bugs.webkit.org/.bzrignore
new file mode 100644
index 0000000..7ab83e7
--- /dev/null
+++ b/Websites/bugs.webkit.org/.bzrignore
@@ -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
index cba381b..0000000
--- a/Websites/bugs.webkit.org/.cvsignore
+++ /dev/null
@@ -1,6 +0,0 @@
-.htaccess
-graphs
-data
-localconfig
-index.html
-old-params.txt
diff --git a/Websites/bugs.webkit.org/.htaccess b/Websites/bugs.webkit.org/.htaccess
index 64e9809..affa4db 100644
--- a/Websites/bugs.webkit.org/.htaccess
+++ b/Websites/bugs.webkit.org/.htaccess
@@ -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>
diff --git a/Websites/bugs.webkit.org/Bugzilla.pm b/Websites/bugs.webkit.org/Bugzilla.pm
index d54f974..65ddcc2 100644
--- a/Websites/bugs.webkit.org/Bugzilla.pm
+++ b/Websites/bugs.webkit.org/Bugzilla.pm
@@ -40,37 +40,45 @@
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 @@
# 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';
+ }
- # 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'} = '';
+ if (${^TAINT}) {
+ # Some environment variables are not taint safe
+ delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+ # Some modules throw undefined errors (notably File::Spec::Win32) if
+ # PATH is undefined.
+ $ENV{'PATH'} = '';
+ }
+
+ # Because this function is run live from perl "use" commands of
+ # other scripts, we're skipping the rest of this function if we get here
+ # during a perl syntax check (perl -c, like we do during the
+ # 001compile.t test).
+ return if $^C;
# IIS prints out warnings to the webpage, so ignore them, or log them
# to a file if the file exists.
@@ -102,25 +123,27 @@
};
}
+ 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 @@
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 @@
}
}
-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 @@
# 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 @@
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 @@
# 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');
+ }
- # NOTE: If you want to do any special logging, do it here.
+ my $sudo_target = new Bugzilla::User($sudo_target_id);
+ if ($authenticated_user->in_group('bz_sudoers')
+ && defined $sudo_target
+ && !$sudo_target->in_group('bz_sudo_protect'))
+ {
+ $class->set_user($sudo_target);
+ $class->request_cache->{sudoer} = $authenticated_user;
+ # And make sure that both users have the same Auth object,
+ # since we never call Auth::login for the sudo target.
+ $sudo_target->set_authorizer($authenticated_user->authorizer);
+
+ # NOTE: If you want to do any special logging, do it here.
+ }
+ else {
+ delete_token($token);
+ $class->cgi->remove_cookie('sudo');
+ ThrowUserError('sudo_illegal_action', { sudoer => $authenticated_user,
+ target_user => $sudo_target });
+ }
}
else {
$class->set_user($authenticated_user);
}
- # 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 @@
# 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 @@
$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 @@
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 @@
$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 @@
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_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 @@
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 @@
$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 @@
_cleanup() unless $ENV{MOD_PERL};
}
+init_page() if !$ENV{MOD_PERL};
+
1;
__END__
@@ -569,6 +752,26 @@
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 @@
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 @@
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 @@
=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 @@
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 @@
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>
-If you are running inside a code hook (see L<Bugzilla::Hook>) this
-is how you get the arguments passed to the hook.
+Returns the local timezone of the Bugzilla installation,
+as a DateTime::TimeZone object. This detection is very time
+consuming, so we cache this information for future references.
+
+=item C<job_queue>
+
+Returns a L<Bugzilla::JobQueue> that you can use for queueing jobs.
+Will throw an error if job queueing is not correctly configured on
+this Bugzilla installation.
+
+=item C<feature>
+
+Tells you whether or not a specific feature is enabled. For names
+of features, see C<OPTIONAL_MODULES> in C<Bugzilla::Install::Requirements>.
=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/.cvsignore b/Websites/bugs.webkit.org/Bugzilla/.cvsignore
deleted file mode 100644
index 03c88fd..0000000
--- a/Websites/bugs.webkit.org/Bugzilla/.cvsignore
+++ /dev/null
@@ -1 +0,0 @@
-.htaccess
diff --git a/Websites/bugs.webkit.org/Bugzilla/Attachment.pm b/Websites/bugs.webkit.org/Bugzilla/Attachment.pm
index 314227c..b1f47d0 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Attachment.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Attachment.pm
@@ -28,23 +28,26 @@
=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,83 +57,82 @@
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);
- }
-
- return $attachments;
-}
-
-sub _retrieve {
- my ($ids) = @_;
-
- return [] if scalar(@$ids) == 0;
-
- my @columns = (
- 'attachments.attach_id AS id',
- 'attachments.bug_id AS bug_id',
- 'attachments.description AS description',
- 'attachments.mimetype AS contenttype',
- 'attachments.submitter_id AS attacher_id',
- Bugzilla->dbh->sql_date_format('attachments.creation_ts',
- '%Y.%m.%d %H:%i') . " AS attached",
- 'attachments.modification_time',
- 'attachments.filename AS filename',
- 'attachments.ispatch AS ispatch',
- 'attachments.isurl AS isurl',
- 'attachments.isobsolete AS isobsolete',
- 'attachments.isprivate AS isprivate'
- );
- my $columns = join(", ", @columns);
+sub DB_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;
+
+ 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';
}
+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
=head2 Instance Properties
=over
-=item C<id>
-
-the unique identifier for the attachment
-
-=back
-
-=cut
-
-sub id {
- my $self = shift;
- return $self->{id};
-}
-
-=over
-
=item C<bug_id>
the ID of the bug to which the attachment is attached
@@ -139,8 +141,6 @@
=cut
-# XXX Once Bug.pm slims down sufficiently this should become a reference
-# to a bug object.
sub bug_id {
my $self = shift;
return $self->{bug_id};
@@ -148,6 +148,24 @@
=over
+=item C<bug>
+
+the bug object to which the attachment is attached
+
+=back
+
+=cut
+
+sub bug {
+ my $self = shift;
+
+ require Bugzilla::Bug;
+ $self->{bug} ||= Bugzilla::Bug->new($self->bug_id);
+ return $self->{bug};
+}
+
+=over
+
=item C<description>
user-provided text describing the attachment
@@ -173,7 +191,7 @@
sub contenttype {
my $self = shift;
- return $self->{contenttype};
+ return $self->{mimetype};
}
=over
@@ -189,7 +207,7 @@
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 @@
sub attached {
my $self = shift;
- return $self->{attached};
+ return $self->{creation_ts};
}
=over
@@ -255,21 +273,6 @@
=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 @@
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 @@
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 @@
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 @@
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
-sub _get_local_filename {
+=item C<flag_types>
+
+Return all flag types available for this attachment as well as flags
+already set, grouped by flag type.
+
+=back
+
+=cut
+
+sub flag_types {
my $self = shift;
- 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 ######
+###############################
- my $filename = $cgi->upload('data');
+sub set_content_type { $_[0]->set('mimetype', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_filename { $_[0]->set('filename', $_[1]); }
+sub set_is_patch { $_[0]->set('ispatch', $_[1]); }
+sub set_is_private { $_[0]->set('isprivate', $_[1]); }
+
+sub set_is_obsolete {
+ my ($self, $obsolete) = @_;
+
+ my $old = $self->isobsolete;
+ $self->set('isobsolete', $obsolete);
+ my $new = $self->isobsolete;
+
+ # If the attachment is being marked as obsolete, cancel pending requests.
+ if ($new && $old != $new) {
+ my @requests = grep { $_->status eq '?' } @{$self->flags};
+ return unless scalar @requests;
+
+ my %flag_ids = map { $_->id => 1 } @requests;
+ foreach my $flagtype (@{$self->flag_types}) {
+ @{$flagtype->{flags}} = grep { !$flag_ids{$_->id} } @{$flagtype->{flags}};
+ }
+ }
+}
+
+sub set_flags {
+ my ($self, $flags, $new_flags) = @_;
+
+ Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags);
+}
+
+sub _check_bug {
+ my ($invocant, $bug) = @_;
+ my $user = Bugzilla->user;
+
+ $bug = ref $invocant ? $invocant->bug : $bug;
+
+ $bug || ThrowCodeError('param_required',
+ { function => "$invocant->create", param => 'bug' });
+
+ ($user->can_see_bug($bug->id) && $user->can_edit_product($bug->product_id))
+ || ThrowUserError("illegal_attachment_edit_bug", { bug_id => $bug->id });
+
+ return $bug;
+}
+
+sub _check_content_type {
+ my ($invocant, $content_type, undef, $params) = @_;
+
+ my $is_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 @@
# 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');
+sub _check_is_private {
+ my ($invocant, $is_private) = @_;
+
+ $is_private = $is_private ? 1 : 0;
+ if (((!ref $invocant && $is_private)
+ || (ref $invocant && $invocant->isprivate != $is_private))
+ && !Bugzilla->user->is_insider) {
+ ThrowUserError('user_not_insider');
}
- my $data;
-
- # We could get away with reading only as much as required, except that then
- # we wouldn't have a size to print to the error handler below.
- if (!$cgi->param('bigfile')) {
- # enable 'slurp' mode
- local $/;
- $data = <$fh>;
- }
-
- $data
- || ($cgi->param('bigfile'))
- || ($throw_error ? ThrowUserError("zero_length_file") : return 0);
-
- # Windows screenshots are usually uncompressed BMP files which
- # makes for a quick way to eat up disk space. Let's compress them.
- # We do this before we check the size since the uncompressed version
- # could easily be greater than maxattachmentsize.
- if (Bugzilla->params->{'convert_uncompressed_images'}
- && $cgi->param('contenttype') eq 'image/bmp') {
- require Image::Magick;
- my $img = Image::Magick->new(magick=>'bmp');
- $img->BlobToImage($data);
- $img->set(magick=>'png');
- my $imgdata = $img->ImageToBlob();
- $data = $imgdata;
- $cgi->param('contenttype', 'image/png');
- $hr_vars->{'convertedbmp'} = 1;
- }
-
- # Make sure the attachment does not exceed the maximum permitted size
- my $len = $data ? length($data) : 0;
- if ($maxsize && $len > $maxsize) {
- my $vars = { filesize => sprintf("%.0f", $len/1024) };
- if ($cgi->param('ispatch')) {
- $throw_error ? ThrowUserError("patch_too_large", $vars) : return 0;
- }
- else {
- $throw_error ? ThrowUserError("file_too_large", $vars) : return 0;
- }
- }
-
- return $data || '';
+ return $is_private;
}
=pod
@@ -538,7 +621,7 @@
=cut
sub get_attachments_by_bug {
- my ($class, $bug_id) = @_;
+ my ($class, $bug_id, $vars) = @_;
my $user = Bugzilla->user;
my $dbh = Bugzilla->dbh;
@@ -555,111 +638,30 @@
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);
+
+ my $attachments = Bugzilla::Attachment->new_from_list($attach_ids);
+
+ # To avoid $attachment->flags to run SQL queries itself for each
+ # attachment listed here, we collect all the data at once and
+ # populate $attachment->{flags} ourselves.
+ if ($vars->{preload}) {
+ $_->{flags} = [] foreach @$attachments;
+ my %att = map { $_->id => $_ } @$attachments;
+
+ my $flags = Bugzilla::Flag->match({ bug_id => $bug_id,
+ target_type => 'attachment' });
+
+ # Exclude flags for private attachments you cannot see.
+ @$flags = grep {exists $att{$_->attach_id}} @$flags;
+
+ push(@{$att{$_->attach_id}->{flags}}, $_) foreach @$flags;
+ $attachments = [sort {$a->id <=> $b->id} values %att];
+ }
return $attachments;
}
=pod
-=item C<validate_is_patch()>
-
-Description: validates the "patch" flag passed in by CGI.
-
-Returns: 1 on success.
-
-=cut
-
-sub validate_is_patch {
- my ($class, $throw_error) = @_;
- my $cgi = Bugzilla->cgi;
-
- # Set the ispatch flag to zero if it is undefined, since the UI uses
- # an HTML checkbox to represent this flag, and unchecked HTML checkboxes
- # do not get sent in HTML requests.
- $cgi->param('ispatch', $cgi->param('ispatch') ? 1 : 0);
-
- # Set the content type to text/plain if the attachment is a patch.
- $cgi->param('contenttype', 'text/plain') if $cgi->param('ispatch');
-
- return 1;
-}
-
-=pod
-
-=item C<validate_description()>
-
-Description: validates the description passed in by CGI.
-
-Returns: 1 on success.
-
-=cut
-
-sub validate_description {
- my ($class, $throw_error) = @_;
- my $cgi = Bugzilla->cgi;
-
- $cgi->param('description')
- || ($throw_error ? ThrowUserError("missing_attachment_description") : return 0);
-
- return 1;
-}
-
-=pod
-
-=item C<validate_content_type()>
-
-Description: validates the content type passed in by CGI.
-
-Returns: 1 on success.
-
-=cut
-
-sub validate_content_type {
- my ($class, $throw_error) = @_;
- my $cgi = Bugzilla->cgi;
-
- if (!defined $cgi->param('contenttypemethod')) {
- $throw_error ? ThrowUserError("missing_content_type_method") : return 0;
- }
- elsif ($cgi->param('contenttypemethod') eq 'autodetect') {
- my $contenttype =
- $cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
- # The user asked us to auto-detect the content type, so use the type
- # specified in the HTTP request headers.
- if ( !$contenttype ) {
- $throw_error ? ThrowUserError("missing_content_type") : return 0;
- }
- $cgi->param('contenttype', $contenttype);
- }
- elsif ($cgi->param('contenttypemethod') eq 'list') {
- # The user selected a content type from the list, so use their
- # selection.
- $cgi->param('contenttype', $cgi->param('contenttypeselection'));
- }
- elsif ($cgi->param('contenttypemethod') eq 'manual') {
- # The user entered a content type manually, so use their entry.
- $cgi->param('contenttype', $cgi->param('contenttypeentry'));
- }
- else {
- $throw_error ?
- ThrowCodeError("illegal_content_type_method",
- { contenttypemethod => $cgi->param('contenttypemethod') }) :
- return 0;
- }
-
- if ( $cgi->param('contenttype') !~
- /^(application|audio|image|message|model|multipart|text|video)\/.+$/ ) {
- $throw_error ?
- ThrowUserError("invalid_content_type",
- { contenttype => $cgi->param('contenttype') }) :
- return 0;
- }
-
- return 1;
-}
-
-=pod
-
=item C<validate_can_edit($attachment, $product_id)>
Description: validates if the user is allowed to view and edit the attachment.
@@ -670,7 +672,7 @@
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 @@
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 @@
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,205 +731,152 @@
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.
+ 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 ID of the new attachment.
+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;
+ $class->check_required_create_fields(@_);
+ my $params = $class->run_create_validators(@_);
- # 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');
+ # 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};
- # These are inserted using placeholders so no need to panic
- trick_taint($filename);
- trick_taint($contenttype);
- $isurl = 0;
- }
+ my $attachment = $class->insert_create_data($params);
+ my $attachid = $attachment->id;
- # Check attachments the user tries to mark as obsolete.
- my @obsolete_attachments;
- if ($cgi->param('obsolete')) {
- @obsolete_attachments = $class->validate_obsolete($bug);
- }
-
- # The order of these function calls is important, as Flag::validate
- # assumes User::match_field has ensured that the
- # values in the requestee fields are legitimate user email addresses.
- my $match_status = Bugzilla::User::match_field($cgi, {
- '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
- }, MATCH_SKIP_CONFIRM);
-
- $hr_vars->{'match_field'} = 'requestee';
- if ($match_status == USER_MATCH_FAILED) {
- $hr_vars->{'message'} = 'user_match_failed';
- }
- elsif ($match_status == USER_MATCH_MULTIPLE) {
- $hr_vars->{'message'} = 'user_match_multiple';
- }
-
- # Escape characters in strings that will be used in SQL statements.
- my $description = $cgi->param('description');
- trick_taint($description);
- my $isprivate = $cgi->param('isprivate') ? 1 : 0;
-
- # Insert the attachment into the database.
- my $sth = $dbh->do(
- "INSERT INTO attachments
- (bug_id, creation_ts, modification_time, filename, description,
- mimetype, ispatch, isurl, isprivate, submitter_id)
- VALUES (?,?,?,?,?,?,?,?,?,?)", undef, ($bug->bug_id, $timestamp, $timestamp,
- $filename, $description, $contenttype, $cgi->param('ispatch'),
- $isurl, $isprivate, $user->id));
- # Retrieve the ID of the newly created attachment record.
- my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
-
- # We only use $data here in this INSERT with a placeholder,
- # so it's safe.
- $sth = $dbh->prepare("INSERT INTO attach_data
- (id, thedata) VALUES ($attachid, ?)");
- trick_taint($data);
- $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
- $sth->execute();
-
- # If the file is to be stored locally, stream the file from the web server
- # to the local file without reading it into a local variable.
- if ($cgi->param('bigfile')) {
+ # 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, ?)");
- 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);
+ trick_taint($data);
+ $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
+ $sth->execute();
- $dbh->do('UPDATE attachments SET isobsolete = 1, modification_time = ?
- WHERE attach_id = ?',
- undef, ($timestamp, $obsolete_attachment->id));
-
- $dbh->do('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
- fieldid, removed, added)
- VALUES (?,?,?,?,?,?,?)',
- undef, ($bug->bug_id, $obsolete_attachment->id, $user->id,
- $timestamp, $fieldid, 0, 1));
- }
-
- my $attachment = Bugzilla::Attachment->get($attachid);
-
- # 1. Add flags, if any. To avoid dying if something goes wrong
- # while processing flags, we will eval() flag validation.
- # This requires errors to die().
- # XXX: this can go away as soon as flag validation is able to
- # fail without dying.
- #
- # 2. Flag::validate() should not detect any reference to existing flags
- # when creating a new attachment. Setting the third param to -1 will
- # force this function to check this point.
- my $error_mode_cache = Bugzilla->error_mode;
- Bugzilla->error_mode(ERROR_MODE_DIE);
- eval {
- Bugzilla::Flag::validate($bug->bug_id, -1, SKIP_REQUESTEE_ON_ERROR);
- Bugzilla::Flag->process($bug, $attachment, $timestamp, $hr_vars);
- };
- Bugzilla->error_mode($error_mode_cache);
- if ($@) {
- $hr_vars->{'message'} = 'flag_creation_failed';
- $hr_vars->{'flag_creation_error'} = $@;
- }
+ $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)');
+
+ my ($changes, $old_self) = $self->SUPER::update(@_);
+
+ my ($removed, $added) = Bugzilla::Flag->update_flags($self, $old_self, $timestamp);
+ if ($removed || $added) {
+ $changes->{'flagtypes.name'} = [$removed, $added];
+ }
+
+ # Record changes in the activity table.
+ my $sth = $dbh->prepare('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
+ fieldid, removed, added)
+ VALUES (?, ?, ?, ?, ?, ?, ?)');
+
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
+ $field = "attachments.$field" unless $field eq "flagtypes.name";
+ my $fieldid = get_field_id($field);
+ $sth->execute($self->bug_id, $self->id, $user->id, $timestamp,
+ $fieldid, $change->[0], $change->[1]);
+ }
+
+ if (scalar(keys %$changes)) {
+ $dbh->do('UPDATE attachments SET modification_time = ? WHERE attach_id = ?',
+ undef, ($timestamp, $self->id));
+ $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+ undef, ($timestamp, $self->bug_id));
+ }
+
+ return $changes;
+}
+
=pod
=item C<remove_from_db()>
@@ -951,9 +898,61 @@
$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;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Attachment/PatchReader.pm b/Websites/bugs.webkit.org/Bugzilla/Attachment/PatchReader.pm
index cfc7610..01a624a 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Attachment/PatchReader.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Attachment/PatchReader.pm
@@ -37,6 +37,7 @@
$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 @@
$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();
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth.pm b/Websites/bugs.webkit.org/Bugzilla/Auth.pm
index 8e18f86..45034e1 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth.pm
@@ -32,6 +32,9 @@
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 @@
# 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 @@
&& $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 @@
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 @@
# 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 @@
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 @@
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 @@
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 @@
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
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth/Login.pm b/Websites/bugs.webkit.org/Bugzilla/Auth/Login.pm
index 4a4c5f2..42ce51c 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth/Login.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth/Login.pm
@@ -27,6 +27,8 @@
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 @@
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
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth/Login/CGI.pm b/Websites/bugs.webkit.org/Bugzilla/Auth/Login/CGI.pm
index 5be98aa..8e877b9 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth/Login/CGI.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth/Login/CGI.pm
@@ -40,12 +40,10 @@
sub get_login_info {
my ($self) = @_;
- my $cgi = Bugzilla->cgi;
+ my $params = Bugzilla->input_params;
- my $username = trim($cgi->param("Bugzilla_login"));
- my $password = $cgi->param("Bugzilla_password");
-
- $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 @@
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();
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Cookie.pm b/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Cookie.pm
index e2cd8f5..91fb820 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Cookie.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Cookie.pm
@@ -27,6 +27,7 @@
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 @@
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 @@
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()
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Env.pm b/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Env.pm
index 180e79b..76227f1 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Env.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Env.pm
@@ -29,7 +29,10 @@
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) = @_;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Stack.pm b/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Stack.pm
index d510038..e8d9c46 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Stack.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth/Login/Stack.pm
@@ -26,16 +26,26 @@
_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 @@
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 @@
return 0;
}
+sub extern_id_used {
+ my ($self) = @_;
+ return any { $_->extern_id_used } @{ $self->{_stack} };
+}
+
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth/Persist/Cookie.pm b/Websites/bugs.webkit.org/Bugzilla/Auth/Persist/Cookie.pm
index 420bad1..57fa962 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth/Persist/Cookie.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth/Persist/Cookie.pm
@@ -48,18 +48,16 @@
my ($self, $user) = @_;
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
+ my $input_params = Bugzilla->input_params;
- my $ip_addr = $cgi->remote_addr;
- unless ($cgi->param('Bugzilla_restrictlogin') ||
- Bugzilla->params->{'loginnetmask'} == 32)
- {
- $ip_addr = get_netaddr($ip_addr);
+ 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 @@
# 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 @@
# 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 @@
my $cgi = Bugzilla->cgi;
$cgi->remove_cookie('Bugzilla_login');
$cgi->remove_cookie('Bugzilla_logincookie');
+ $cgi->remove_cookie('sudo');
}
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth/Verify.pm b/Websites/bugs.webkit.org/Bugzilla/Auth/Verify.pm
index b293e25..a8cd0af 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth/Verify.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth/Verify.pm
@@ -25,6 +25,7 @@
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 @@
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
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/DB.pm b/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/DB.pm
index f2c008d..2fcfd40 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/DB.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/DB.pm
@@ -41,33 +41,58 @@
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};
+ $login_data->{user} = $user;
+ $login_data->{bz_username} = $user->login;
- trick_taint($username);
- my ($real_password_crypted) = $dbh->selectrow_array(
- "SELECT cryptpassword FROM profiles WHERE userid = ?",
- undef, $user_id);
-
- # Wide characters cause crypt to die
- if (Bugzilla->params->{'utf8'}) {
- utf8::encode($password) if utf8::is_utf8($password);
+ 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;
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/LDAP.pm b/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/LDAP.pm
index b590430..cdc802c 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/LDAP.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/LDAP.pm
@@ -56,7 +56,7 @@
# 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 @@
return { failure => AUTH_LOGINFAILED } if $pw_result->code;
# And now we fill in the user's details.
- my $detail_result = $self->ldap->search(_bz_search_params($username));
- return { failure => AUTH_ERROR, error => "ldap_search_error",
- details => {errstr => $detail_result->error, username => $username}
- } if $detail_result->code;
- my $user_entry = $detail_result->shift_entry;
+ # First try the search as the (already bound) user in question.
+ my $user_entry;
+ my $error_string;
+ my $detail_result = $self->ldap->search(_bz_search_params($username));
+ if ($detail_result->code) {
+ # Stash away the original error, just in case
+ $error_string = $detail_result->error;
+ } else {
+ $user_entry = $detail_result->shift_entry;
+ }
+
+ # If that failed (either because the search failed, or returned no
+ # results) then try re-binding as the initial search user, but only
+ # if the LDAPbinddn parameter is set.
+ if (!$user_entry && Bugzilla->params->{"LDAPbinddn"}) {
+ $self->_bind_ldap_for_search();
+
+ $detail_result = $self->ldap->search(_bz_search_params($username));
+ if (!$detail_result->code) {
+ $user_entry = $detail_result->shift_entry;
+ }
+ }
+
+ # If we *still* don't have anything in $user_entry then give up.
+ return { failure => AUTH_ERROR, error => "ldap_search_error",
+ details => {errstr => $error_string, username => $username}
+ } if !$user_entry;
+
my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
if ($mail_attr) {
@@ -128,7 +151,7 @@
. Bugzilla->params->{"LDAPfilter"} . ')');
}
-sub _bind_ldap_anonymously {
+sub _bind_ldap_for_search {
my ($self) = @_;
my $bind_result;
if (Bugzilla->params->{"LDAPbinddn"}) {
diff --git a/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/Stack.pm b/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/Stack.pm
index 577b5a2..e1a0119 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/Stack.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Auth/Verify/Stack.pm
@@ -22,15 +22,26 @@
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 @@
return 0;
}
+sub extern_id_used {
+ my ($self) = @_;
+ return any { $_->extern_id_used } @{ $self->{_stack} };
+}
+
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Bug.pm b/Websites/bugs.webkit.org/Bugzilla/Bug.pm
index 7f2b8d8..a848c86 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Bug.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Bug.pm
@@ -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::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 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 @@
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 @@
delta_ts
estimated_time
everconfirmed
+ lastdiffed
op_sys
priority
product_id
@@ -100,34 +113,45 @@
'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 @@
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 @@
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 @@
);
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 @@
# 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 @@
# 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 @@
# 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 @@
}
}
- # 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 @@
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 @@
# 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 @@
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)');
- my $old_bug = $self->new($self->id);
- my $changes = $self->SUPER::update(@_);
+ $dbh->bz_start_transaction();
+
+ my ($changes, $old_bug) = $self->SUPER::update(@_);
# Certain items in $changes have to be fixed so that they hold
# a name instead of an ID.
@@ -557,8 +840,6 @@
$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 @@
$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 @@
}
}
+ # 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 @@
$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 @@
# - 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 @@
undef, ($bug_id, $bug_id));
$dbh->do("DELETE FROM flags WHERE bug_id = ?", undef, $bug_id);
$dbh->do("DELETE FROM keywords WHERE bug_id = ?", undef, $bug_id);
- $dbh->do("DELETE FROM votes WHERE bug_id = ?", undef, $bug_id);
# The attach_data table doesn't depend on bugs.bug_id directly.
my $attach_ids =
@@ -818,21 +1129,109 @@
$dbh->do("DELETE FROM bugs WHERE bug_id = ?", undef, $bug_id);
$dbh->do("DELETE FROM longdescs WHERE bug_id = ?", undef, $bug_id);
- # Delete entries from custom multi-select fields.
- my @multi_selects = Bugzilla->get_fields({custom => 1, type => FIELD_TYPE_MULTI_SELECT});
-
- foreach my $field (@multi_selects) {
- $dbh->do("DELETE FROM bug_" . $field->name . " WHERE bug_id = ?", undef, $bug_id);
- }
-
$dbh->bz_commit_transaction();
# The bugs_fulltext table doesn't support transactions.
$dbh->do("DELETE FROM bugs_fulltext WHERE bug_id = ?", undef, $bug_id);
- # Now this bug no longer exists
- $self->DESTROY;
- return $self;
+ undef $self;
+}
+
+#####################################################################
+# Sending Email After Bug Update
+#####################################################################
+
+sub send_changes {
+ my ($self, $changes, $vars) = @_;
+
+ my $user = Bugzilla->user;
+
+ my $old_qa = $changes->{'qa_contact'}
+ ? $changes->{'qa_contact'}->[0] : '';
+ my $old_own = $changes->{'assigned_to'}
+ ? $changes->{'assigned_to'}->[0] : '';
+ my $old_cc = $changes->{cc}
+ ? $changes->{cc}->[0] : '';
+
+ my %forced = (
+ cc => [split(/[,;]+/, $old_cc)],
+ owner => $old_own,
+ qacontact => $old_qa,
+ changer => $user,
+ );
+
+ _send_bugmail({ id => $self->id, type => 'bug', forced => \%forced },
+ $vars);
+
+ # If the bug was marked as a duplicate, we need to notify users on the
+ # other bug of any changes to that bug.
+ my $new_dup_id = $changes->{'dup_id'} ? $changes->{'dup_id'}->[1] : undef;
+ if ($new_dup_id) {
+ _send_bugmail({ forced => { changer => $user }, type => "dupe",
+ id => $new_dup_id }, $vars);
+ }
+
+ # If there were changes in dependencies, we need to notify those
+ # dependencies.
+ if ($changes->{'bug_status'}) {
+ my ($old_status, $new_status) = @{ $changes->{'bug_status'} };
+
+ # If this bug has changed from opened to closed or vice-versa,
+ # then all of the bugs we block need to be notified.
+ if (is_open_state($old_status) ne is_open_state($new_status)) {
+ my $params = { forced => { changer => $user },
+ type => 'dep',
+ dep_only => 1,
+ blocker => $self,
+ changes => $changes };
+
+ foreach my $id (@{ $self->blocked }) {
+ $params->{id} = $id;
+ _send_bugmail($params, $vars);
+ }
+ }
+ }
+
+ # To get a list of all changed dependencies, convert the "changes" arrays
+ # into a long string, then collapse that string into unique numbers in
+ # a hash.
+ my $all_changed_deps = join(', ', @{ $changes->{'dependson'} || [] });
+ $all_changed_deps = join(', ', @{ $changes->{'blocked'} || [] },
+ $all_changed_deps);
+ my %changed_deps = map { $_ => 1 } split(', ', $all_changed_deps);
+ # When clearning one field (say, blocks) and filling in the other
+ # (say, dependson), an empty string can get into the hash and cause
+ # an error later.
+ delete $changed_deps{''};
+
+ foreach my $id (sort { $a <=> $b } (keys %changed_deps)) {
+ _send_bugmail({ forced => { changer => $user }, type => "dep",
+ id => $id }, $vars);
+ }
+
+ # Sending emails for the referenced bugs.
+ foreach my $ref_bug_id (uniq @{ $self->{see_also_changes} || [] }) {
+ _send_bugmail({ forced => { changer => $user },
+ id => $ref_bug_id }, $vars);
+ }
+}
+
+sub _send_bugmail {
+ my ($params, $vars) = @_;
+
+ require Bugzilla::BugMail;
+
+ my $results =
+ Bugzilla::BugMail::Send($params->{'id'}, $params->{'forced'}, $params);
+
+ if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
+ my $template = Bugzilla->template;
+ $vars->{$_} = $params->{$_} foreach keys %$params;
+ $vars->{'sent_bugmail'} = $results;
+ $template->process("bug/process/results.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ $vars->{'header_done'} = 1;
+ }
}
#####################################################################
@@ -869,8 +1268,10 @@
}
sub _check_assigned_to {
- my ($invocant, $assignee, $component) = @_;
+ my ($invocant, $assignee, undef, $params) = @_;
my $user = Bugzilla->user;
+ my $component = blessed($invocant) ? $invocant->component_obj
+ : $params->{component};
# Default assignee is the component owner.
my $id;
@@ -907,34 +1308,27 @@
return $url;
}
-sub _check_bug_severity {
- my ($invocant, $severity) = @_;
- $severity = trim($severity);
- check_field('bug_severity', $severity);
- return $severity;
-}
-
sub _check_bug_status {
- my ($invocant, $new_status, $product, $comment) = @_;
+ my ($invocant, $new_status, undef, $params) = @_;
my $user = Bugzilla->user;
my @valid_statuses;
my $old_status; # Note that this is undef for new bugs.
+ my ($product, $comment);
if (ref $invocant) {
- @valid_statuses = @{$invocant->status->can_change_to};
+ @valid_statuses = @{$invocant->statuses_available};
$product = $invocant->product_obj;
$old_status = $invocant->status;
my $comments = $invocant->{added_comments} || [];
$comment = $comments->[-1];
}
else {
+ $product = $params->{product};
+ $comment = $params->{comment};
@valid_statuses = @{Bugzilla::Status->can_change_to()};
- }
-
- if (!$product->votes_to_confirm) {
- # UNCONFIRMED becomes an invalid status if votes_to_confirm is 0,
- # even if you are in editbugs.
- @valid_statuses = grep {$_->name ne 'UNCONFIRMED'} @valid_statuses;
+ if (!$product->allows_unconfirmed) {
+ @valid_statuses = grep {$_->name ne 'UNCONFIRMED'} @valid_statuses;
+ }
}
# Check permissions for users filing new bugs.
@@ -965,9 +1359,13 @@
}
}
}
+
# Time to validate the bug status.
$new_status = Bugzilla::Status->check($new_status) unless ref($new_status);
- if (!grep {$_->name eq $new_status->name} @valid_statuses) {
+ # We skip this check if we are changing from a status to itself.
+ if ( (!$old_status || $old_status->id != $new_status->id)
+ && !grep {$_->name eq $new_status->name} @valid_statuses)
+ {
ThrowUserError('illegal_bug_status_transition',
{ old => $old_status, new => $new_status });
}
@@ -980,7 +1378,10 @@
}
- if (ref $invocant && $new_status->name eq 'ASSIGNED'
+ if (ref $invocant
+ && ($new_status->name eq 'IN_PROGRESS'
+ # Backwards-compat for the old default workflow.
+ or $new_status->name eq 'ASSIGNED')
&& Bugzilla->params->{"usetargetmilestone"}
&& Bugzilla->params->{"musthavemilestoneonaccept"}
# musthavemilestoneonaccept applies only if at least two
@@ -991,16 +1392,25 @@
ThrowUserError("milestone_required", { bug => $invocant });
}
- return $new_status->name if ref $invocant;
- return ($new_status->name, $new_status->name eq 'UNCONFIRMED' ? 0 : 1);
+ if (!blessed $invocant) {
+ $params->{everconfirmed} = $new_status->name eq 'UNCONFIRMED' ? 0 : 1;
+ }
+
+ return $new_status->name;
}
sub _check_cc {
- my ($invocant, $component, $ccs) = @_;
+ my ($invocant, $ccs, undef, $params) = @_;
+ my $component = blessed($invocant) ? $invocant->component_obj
+ : $params->{component};
return [map {$_->id} @{$component->initial_cc}] unless $ccs;
+ # Allow comma-separated input as well as arrayrefs.
+ $ccs = [split(/[,;]+/, $ccs)] if !ref $ccs;
+
my %cc_ids;
foreach my $person (@$ccs) {
+ $person = trim($person);
next unless $person;
my $id = login_to_id($person, THROW_ERROR);
$cc_ids{$id} = 1;
@@ -1013,52 +1423,66 @@
}
sub _check_comment {
- my ($invocant, $comment) = @_;
+ my ($invocant, $comment_txt, undef, $params) = @_;
- $comment = '' unless defined $comment;
+ # Comment can be empty. We should force it to be empty if the text is undef
+ if (!defined $comment_txt) {
+ $comment_txt = '';
+ }
- # Remove any trailing whitespace. Leading whitespace could be
- # a valid part of the comment.
- $comment =~ s/\s*$//s;
- $comment =~ s/\r\n?/\n/g; # Get rid of \r.
+ # Load up some data
+ my $isprivate = delete $params->{comment_is_private};
+ my $timestamp = $params->{creation_ts};
- ThrowUserError('comment_too_long') if length($comment) > MAX_COMMENT_LENGTH;
- return $comment;
-}
+ # Create the new comment so we can check it
+ my $comment = {
+ thetext => $comment_txt,
+ bug_when => $timestamp,
+ };
-sub _check_commentprivacy {
- my ($invocant, $comment_privacy) = @_;
- my $insider_group = Bugzilla->params->{"insidergroup"};
- return ($insider_group && Bugzilla->user->in_group($insider_group)
- && $comment_privacy) ? 1 : 0;
-}
+ # We don't include the "isprivate" column unless it was specified.
+ # This allows it to fall back to its database default.
+ if (defined $isprivate) {
+ $comment->{isprivate} = $isprivate;
+ }
-sub _check_comment_type {
- my ($invocant, $type) = @_;
- detaint_natural($type)
- || ThrowCodeError('bad_arg', { argument => 'type',
- function => caller });
- return $type;
+ # Validate comment. We have to do this special as a comment normally
+ # requires a bug to be already created. For a new bug, the first comment
+ # obviously can't get the bug if the bug is created after this
+ # (see bug 590334)
+ Bugzilla::Comment->check_required_create_fields($comment);
+ $comment = Bugzilla::Comment->run_create_validators($comment,
+ { skip => ['bug_id'] }
+ );
+
+ return $comment;
+
}
sub _check_component {
- my ($invocant, $name, $product) = @_;
+ my ($invocant, $name, undef, $params) = @_;
$name = trim($name);
$name || ThrowUserError("require_component");
- ($product = $invocant->product_obj) if ref $invocant;
+ my $product = blessed($invocant) ? $invocant->product_obj
+ : $params->{product};
my $obj = Bugzilla::Component->check({ product => $product, name => $name });
return $obj;
}
+sub _check_creation_ts {
+ return Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+}
+
sub _check_deadline {
my ($invocant, $date) = @_;
-
- # Check time-tracking permissions.
- my $tt_group = Bugzilla->params->{"timetrackinggroup"};
- # deadline() returns '' instead of undef if no deadline is set.
- my $current = ref $invocant ? ($invocant->deadline || undef) : undef;
- return $current unless $tt_group && Bugzilla->user->in_group($tt_group);
-
+
+ # When filing bugs, we're forgiving and just return undef if
+ # the user isn't a timetracker. When updating bugs, check_can_change_field
+ # controls permissions, so we don't want to check them here.
+ if (!ref $invocant and !Bugzilla->user->is_timetracker) {
+ return undef;
+ }
+
# Validate entered deadline
$date = trim($date);
return undef if !$date;
@@ -1081,12 +1505,14 @@
my %deps_in = (dependson => $depends_on || '', blocked => $blocks || '');
- foreach my $type qw(dependson blocked) {
- my @bug_ids = split(/[\s,]+/, $deps_in{$type});
+ foreach my $type (qw(dependson blocked)) {
+ my @bug_ids = ref($deps_in{$type})
+ ? @{$deps_in{$type}}
+ : split(/[\s,]+/, $deps_in{$type});
# Eliminate nulls.
@bug_ids = grep {$_} @bug_ids;
- # We do Validate up here to make sure all aliases are converted to IDs.
- ValidateBugID($_, $type) foreach @bug_ids;
+ # We do this up here to make sure all aliases are converted to IDs.
+ @bug_ids = map { $invocant->check($_, $type)->id } @bug_ids;
my @check_access = @bug_ids;
# When we're updating a bug, only added or removed bug_ids are
@@ -1108,11 +1534,10 @@
my $user = Bugzilla->user;
foreach my $modified_id (@check_access) {
- ValidateBugID($modified_id);
+ my $delta_bug = $invocant->check($modified_id);
# Under strict isolation, you can't modify a bug if you can't
# edit it, even if you can see it.
if (Bugzilla->params->{"strict_isolation"}) {
- my $delta_bug = new Bugzilla::Bug($modified_id);
if (!$user->can_edit_product($delta_bug->{'product_id'})) {
ThrowUserError("illegal_change_deps", {field => $type});
}
@@ -1135,37 +1560,22 @@
$dupe_of = trim($dupe_of);
$dupe_of || ThrowCodeError('undefined_field', { field => 'dup_id' });
- # Validate the bug ID. The second argument will force ValidateBugID() to
- # only make sure that the bug exists, and convert the alias to the bug ID
+ # Validate the bug ID. The second argument will force check() to only
+ # make sure that the bug exists, and convert the alias to the bug ID
# if a string is passed. Group restrictions are checked below.
- ValidateBugID($dupe_of, 'dup_id');
+ my $dupe_of_bug = $self->check($dupe_of, 'dup_id');
+ $dupe_of = $dupe_of_bug->id;
# If the dupe is unchanged, we have nothing more to check.
return $dupe_of if ($self->dup_id && $self->dup_id == $dupe_of);
# If we come here, then the duplicate is new. We have to make sure
# that we can view/change it (issue A on bug 96085).
- check_is_visible($dupe_of);
+ $dupe_of_bug->check_is_visible;
# Make sure a loop isn't created when marking this bug
# as duplicate.
- my %dupes;
- my $this_dup = $dupe_of;
- my $sth = $dbh->prepare('SELECT dupe_of FROM duplicates WHERE dupe = ?');
-
- while ($this_dup) {
- if ($this_dup == $self->id) {
- ThrowUserError('dupe_loop_detected', { bug_id => $self->id,
- dupe_of => $dupe_of });
- }
- # If $dupes{$this_dup} is already set to 1, then a loop
- # already exists which does not involve this bug.
- # As the user is not responsible for this loop, do not
- # prevent him from marking this bug as a duplicate.
- last if exists $dupes{$this_dup};
- $dupes{$this_dup} = 1;
- $this_dup = $dbh->selectrow_array($sth, undef, $this_dup);
- }
+ _resolve_ultimate_dup_id($self->id, $dupe_of, 1);
my $cur_dup = $self->dup_id || 0;
if ($cur_dup != $dupe_of && Bugzilla->params->{'commentonduplicate'}
@@ -1177,7 +1587,6 @@
# Should we add the reporter to the CC list of the new bug?
# If he can see the bug...
if ($self->reporter->can_see_bug($dupe_of)) {
- my $dupe_of_bug = new Bugzilla::Bug($dupe_of);
# We only add him if he's not the reporter of the other bug.
$self->{_add_dup_cc} = 1
if $dupe_of_bug->reporter->id != $self->reporter->id;
@@ -1202,9 +1611,7 @@
my $vars = {};
my $template = Bugzilla->template;
# Ask the user what they want to do about the reporter.
- $vars->{'cclist_accessible'} = $dbh->selectrow_array(
- q{SELECT cclist_accessible FROM bugs WHERE bug_id = ?},
- undef, $dupe_of);
+ $vars->{'cclist_accessible'} = $dupe_of_bug->cclist_accessible;
$vars->{'original_bug_id'} = $dupe_of;
$vars->{'duplicate_bug_id'} = $self->id;
print $cgi->header();
@@ -1217,71 +1624,67 @@
return $dupe_of;
}
-sub _check_estimated_time {
- return $_[0]->_check_time($_[1], 'estimated_time');
-}
-
sub _check_groups {
- my ($invocant, $product, $group_ids) = @_;
+ my ($invocant, $group_names, undef, $params) = @_;
- my $user = Bugzilla->user;
-
+ my $bug_id = blessed($invocant) ? $invocant->id : undef;
+ my $product = blessed($invocant) ? $invocant->product_obj
+ : $params->{product};
my %add_groups;
- my $controls = $product->group_controls;
- foreach my $id (@$group_ids) {
- my $group = new Bugzilla::Group($id)
- || ThrowUserError("invalid_group_ID");
-
- # This can only happen if somebody hacked the enter_bug form.
- ThrowCodeError("inactive_group", { name => $group->name })
- unless $group->is_active;
-
- my $membercontrol = $controls->{$id}
- && $controls->{$id}->{membercontrol};
- my $othercontrol = $controls->{$id}
- && $controls->{$id}->{othercontrol};
-
- my $permit = ($membercontrol && $user->in_group($group->name))
- || $othercontrol;
-
- $add_groups{$id} = 1 if $permit;
+ # In email or WebServices, when the "groups" item actually
+ # isn't specified, then just add the default groups.
+ if (!defined $group_names) {
+ my $available = $product->groups_available;
+ foreach my $group (@$available) {
+ $add_groups{$group->id} = $group if $group->{is_default};
+ }
}
+ else {
+ # Allow a comma-separated list, for email_in.pl.
+ $group_names = [map { trim($_) } split(',', $group_names)]
+ if !ref $group_names;
- foreach my $id (keys %$controls) {
- next unless $controls->{$id}->{'group'}->is_active;
- my $membercontrol = $controls->{$id}->{membercontrol} || 0;
- my $othercontrol = $controls->{$id}->{othercontrol} || 0;
+ # First check all the groups they chose to set.
+ my %args = ( product => $product->name, bug_id => $bug_id, action => 'add' );
+ foreach my $name (@$group_names) {
+ my $group = Bugzilla::Group->check_no_disclose({ %args, name => $name });
- # Add groups required
- if ($membercontrol == CONTROLMAPMANDATORY
- || ($othercontrol == CONTROLMAPMANDATORY
- && !$user->in_group_id($id)))
- {
- # User had no option, bug needs to be in this group.
- $add_groups{$id} = 1;
+ if (!$product->group_is_settable($group)) {
+ ThrowUserError('group_restriction_not_allowed', { %args, name => $name });
+ }
+ $add_groups{$group->id} = $group;
}
}
- my @add_groups = keys %add_groups;
+ # Now enforce mandatory groups.
+ $add_groups{$_->id} = $_ foreach @{ $product->groups_mandatory };
+
+ my @add_groups = values %add_groups;
return \@add_groups;
}
sub _check_keywords {
- my ($invocant, $keyword_string, $product) = @_;
- $keyword_string = trim($keyword_string);
- return [] if !$keyword_string;
+ my ($invocant, $keywords_in, undef, $params) = @_;
+
+ return [] if !defined $keywords_in;
+
+ my $keyword_array = $keywords_in;
+ if (!ref $keyword_array) {
+ $keywords_in = trim($keywords_in);
+ $keyword_array = [split(/[\s,]+/, $keywords_in)];
+ }
# On creation, only editbugs users can set keywords.
if (!ref $invocant) {
+ my $product = $params->{product};
return [] if !Bugzilla->user->in_group('editbugs', $product->id);
}
my %keywords;
- foreach my $keyword (split(/[\s,]+/, $keyword_string)) {
+ foreach my $keyword (@$keyword_array) {
next unless $keyword;
- my $obj = new Bugzilla::Keyword({ name => $keyword });
- ThrowUserError("unknown_keyword", { keyword => $keyword }) if !$obj;
+ my $obj = Bugzilla::Keyword->check($keyword);
$keywords{$obj->id} = $obj;
}
return [values %keywords];
@@ -1297,17 +1700,8 @@
}
# Check that the product exists and that the user
# is allowed to enter bugs into this product.
- Bugzilla->user->can_enter_product($name, THROW_ERROR);
- # can_enter_product already does everything that check_product
- # would do for us, so we don't need to use it.
- return new Bugzilla::Product({ name => $name });
-}
-
-sub _check_op_sys {
- my ($invocant, $op_sys) = @_;
- $op_sys = trim($op_sys);
- check_field('op_sys', $op_sys);
- return $op_sys;
+ my $product = Bugzilla->user->can_enter_product($name, THROW_ERROR);
+ return $product;
}
sub _check_priority {
@@ -1315,16 +1709,14 @@
if (!ref $invocant && !Bugzilla->params->{'letsubmitterchoosepriority'}) {
$priority = Bugzilla->params->{'defaultpriority'};
}
- $priority = trim($priority);
- check_field('priority', $priority);
-
- return $priority;
+ return $invocant->_check_select_field($priority, 'priority');
}
sub _check_qa_contact {
- my ($invocant, $qa_contact, $component) = @_;
+ my ($invocant, $qa_contact, undef, $params) = @_;
$qa_contact = trim($qa_contact) if !ref $qa_contact;
-
+ my $component = blessed($invocant) ? $invocant->component_obj
+ : $params->{component};
my $id;
if (!ref $invocant) {
# Bugs get no QA Contact on creation if useqacontact is off.
@@ -1354,17 +1746,6 @@
return $id || undef;
}
-sub _check_remaining_time {
- return $_[0]->_check_time($_[1], 'remaining_time');
-}
-
-sub _check_rep_platform {
- my ($invocant, $platform) = @_;
- $platform = trim($platform);
- check_field('rep_platform', $platform);
- return $platform;
-}
-
sub _check_reporter {
my $invocant = shift;
my $reporter;
@@ -1375,8 +1756,8 @@
else {
# On bug creation, the reporter is the logged in user
# (meaning that he must be logged in first!).
+ Bugzilla->login(LOGIN_REQUIRED);
$reporter = Bugzilla->user->id;
- $reporter || ThrowCodeError('invalid_user');
}
return $reporter;
}
@@ -1392,19 +1773,22 @@
if !$resolution && !$self->status->is_open;
# Make sure this is a valid resolution.
- check_field('resolution', $resolution);
+ $resolution = $self->_check_select_field($resolution, 'resolution');
# Don't allow open bugs to have resolutions.
ThrowUserError('resolution_not_allowed') if $self->status->is_open;
# Check noresolveonopenblockers.
- if (Bugzilla->params->{"noresolveonopenblockers"} && $resolution eq 'FIXED')
+ if (Bugzilla->params->{"noresolveonopenblockers"}
+ && $resolution eq 'FIXED'
+ && (!$self->resolution || $resolution ne $self->resolution)
+ && scalar @{$self->dependson})
{
- my @dependencies = CountOpenDependencies($self->id);
- if (@dependencies) {
+ my $dep_bugs = Bugzilla::Bug->new_from_list($self->dependson);
+ my $count_open = grep { $_->isopened } @$dep_bugs;
+ if ($count_open) {
ThrowUserError("still_unresolved_bugs",
- { dependencies => \@dependencies,
- dependency_count => scalar @dependencies });
+ { bug_id => $self->id, dep_count => $count_open });
}
}
@@ -1427,6 +1811,10 @@
if (!defined $short_desc || $short_desc eq '') {
ThrowUserError("require_summary");
}
+ if (length($short_desc) > MAX_FREETEXT_LENGTH) {
+ ThrowUserError('freetext_too_long',
+ { field => 'short_desc', text => $short_desc });
+ }
return $short_desc;
}
@@ -1505,46 +1893,81 @@
}
}
-sub _check_target_milestone {
- my ($invocant, $target, $product) = @_;
- $product = $invocant->product_obj if ref $invocant;
+sub _check_tag_name {
+ my ($invocant, $tag) = @_;
- $target = trim($target);
- $target = $product->default_milestone if !defined $target;
- check_field('target_milestone', $target,
- [map($_->name, @{$product->milestones})]);
- return $target;
+ $tag = clean_text($tag);
+ $tag || ThrowUserError('no_tag_to_edit');
+ ThrowUserError('tag_name_too_long') if length($tag) > MAX_LEN_QUERY_NAME;
+ trick_taint($tag);
+ # Tags are all lowercase.
+ return lc($tag);
}
-sub _check_time {
- my ($invocant, $time, $field) = @_;
+sub _check_target_milestone {
+ my ($invocant, $target, undef, $params) = @_;
+ my $product = blessed($invocant) ? $invocant->product_obj
+ : $params->{product};
+ $target = trim($target);
+ $target = $product->default_milestone if !defined $target;
+ my $object = Bugzilla::Milestone->check(
+ { product => $product, name => $target });
+ return $object->name;
+}
- my $current = 0;
- if (ref $invocant && $field ne 'work_time') {
- $current = $invocant->$field;
+sub _check_time_field {
+ my ($invocant, $value, $field, $params) = @_;
+
+ # When filing bugs, we're forgiving and just return 0 if
+ # the user isn't a timetracker. When updating bugs, check_can_change_field
+ # controls permissions, so we don't want to check them here.
+ if (!ref $invocant and !Bugzilla->user->is_timetracker) {
+ return 0;
}
- my $tt_group = Bugzilla->params->{"timetrackinggroup"};
- return $current unless $tt_group && Bugzilla->user->in_group($tt_group);
-
- $time = trim($time) || 0;
- ValidateTime($time, $field);
- return $time;
+
+ # check_time is in Bugzilla::Object.
+ return $invocant->check_time($value, $field, $params);
}
sub _check_version {
- my ($invocant, $version, $product) = @_;
+ my ($invocant, $version, undef, $params) = @_;
$version = trim($version);
- ($product = $invocant->product_obj) if ref $invocant;
- check_field('version', $version, [map($_->name, @{$product->versions})]);
- return $version;
-}
-
-sub _check_work_time {
- return $_[0]->_check_time($_[1], 'work_time');
+ my $product = blessed($invocant) ? $invocant->product_obj
+ : $params->{product};
+ my $object =
+ Bugzilla::Version->check({ product => $product, name => $version });
+ return $object->name;
}
# Custom Field Validators
+sub _check_field_is_mandatory {
+ my ($invocant, $value, $field, $params) = @_;
+
+ if (!blessed($field)) {
+ $field = Bugzilla::Field->new({ name => $field });
+ return if !$field;
+ }
+
+ return if !$field->is_mandatory;
+
+ return if !$field->is_visible_on_bug($params || $invocant);
+
+ if (ref($value) eq 'ARRAY') {
+ $value = join('', @$value);
+ }
+
+ $value = trim($value);
+ if (!defined($value)
+ or $value eq ""
+ or ($value eq '---' and $field->type == FIELD_TYPE_SINGLE_SELECT)
+ or ($value =~ EMPTY_DATETIME_REGEX
+ and $field->type == FIELD_TYPE_DATETIME))
+ {
+ ThrowUserError('required_field', { field => $field });
+ }
+}
+
sub _check_datetime_field {
my ($invocant, $date_time) = @_;
@@ -1570,31 +1993,82 @@
sub _check_default_field { return defined $_[1] ? trim($_[1]) : ''; }
sub _check_freetext_field {
- my ($invocant, $text) = @_;
+ my ($invocant, $text, $field) = @_;
$text = (defined $text) ? trim($text) : '';
if (length($text) > MAX_FREETEXT_LENGTH) {
- ThrowUserError('freetext_too_long', { text => $text });
+ ThrowUserError('freetext_too_long',
+ { field => $field, text => $text });
}
return $text;
}
sub _check_multi_select_field {
my ($invocant, $values, $field) = @_;
- return [] if !$values;
- foreach my $value (@$values) {
- $value = trim($value);
- check_field($field, $value);
- trick_taint($value);
+
+ # Allow users (mostly email_in.pl) to specify multi-selects as
+ # comma-separated values.
+ if (defined $values and !ref $values) {
+ # We don't split on spaces because multi-select values can and often
+ # do have spaces in them. (Theoretically they can have commas in them
+ # too, but that's much less common and people should be able to work
+ # around it pretty cleanly, if they want to use email_in.pl.)
+ $values = [split(',', $values)];
}
- return $values;
+
+ return [] if !$values;
+ my @checked_values;
+ foreach my $value (@$values) {
+ push(@checked_values, $invocant->_check_select_field($value, $field));
+ }
+ return \@checked_values;
}
sub _check_select_field {
my ($invocant, $value, $field) = @_;
- $value = trim($value);
- check_field($field, $value);
- return $value;
+ my $object = Bugzilla::Field::Choice->type($field)->check($value);
+ return $object->name;
+}
+
+sub _check_bugid_field {
+ my ($invocant, $value, $field) = @_;
+ return undef if !$value;
+
+ # check that the value is a valid, visible bug id
+ my $checked_id = $invocant->check($value, $field)->id;
+
+ # check for loop (can't have a loop if this is a new bug)
+ if (ref $invocant) {
+ _check_relationship_loop($field, $invocant->bug_id, $checked_id);
+ }
+
+ return $checked_id;
+}
+
+sub _check_relationship_loop {
+ # Generates a dependency tree for a given bug. Calls itself recursively
+ # to generate sub-trees for the bug's dependencies.
+ my ($field, $bug_id, $dep_id, $ids) = @_;
+
+ # Don't do anything if this bug doesn't have any dependencies.
+ return unless defined($dep_id);
+
+ # Check whether we have seen this bug yet
+ $ids = {} unless defined $ids;
+ $ids->{$bug_id} = 1;
+ if ($ids->{$dep_id}) {
+ ThrowUserError("relationship_loop_single", {
+ 'bug_id' => $bug_id,
+ 'dep_id' => $dep_id,
+ 'field_name' => $field});
+ }
+
+ # Get this dependency's record from the database
+ my $dbh = Bugzilla->dbh;
+ my $next_dep_id = $dbh->selectrow_array(
+ "SELECT $field FROM bugs WHERE bug_id = ?", undef, $dep_id);
+
+ _check_relationship_loop($field, $dep_id, $next_dep_id, $ids);
}
#####################################################################
@@ -1604,17 +2078,18 @@
sub fields {
my $class = shift;
- return (
+ my @fields =
+ (
# Standard Fields
# Keep this ordering in sync with bugzilla.dtd.
qw(bug_id alias creation_ts short_desc delta_ts
reporter_accessible cclist_accessible
classification_id classification
product component version rep_platform op_sys
- bug_status resolution dup_id
+ bug_status resolution dup_id see_also
bug_file_loc status_whiteboard keywords
priority bug_severity target_milestone
- dependson blocked votes everconfirmed
+ dependson blocked everconfirmed
reporter assigned_to cc estimated_time
remaining_time actual_time deadline),
@@ -1623,6 +2098,9 @@
# Custom Fields
map { $_->name } Bugzilla->active_custom_fields
);
+ Bugzilla::Hook::process('bug_fields', {'fields' => \@fields} );
+
+ return @fields;
}
#####################################################################
@@ -1654,6 +2132,7 @@
newvalue => $value,
privs => $privs });
}
+ $self->_check_field_is_mandatory($value, $field);
}
@@ -1661,6 +2140,131 @@
# "Set" Methods #
#################
+# Note that if you are changing multiple bugs at once, you must pass
+# other_bugs to set_all in order for it to behave properly.
+sub set_all {
+ my $self = shift;
+ my ($input_params) = @_;
+
+ # Clone the data as we are going to alter it, and this would affect
+ # subsequent bugs when calling set_all() again, as some fields would
+ # be modified or no longer defined.
+ my $params = {};
+ %$params = %$input_params;
+
+ # You cannot mark bugs as duplicate when changing several bugs at once
+ # (because currently there is no way to check for duplicate loops in that
+ # situation). You also cannot set the alias of several bugs at once.
+ if ($params->{other_bugs} and scalar @{ $params->{other_bugs} } > 1) {
+ ThrowUserError('dupe_not_allowed') if exists $params->{dup_id};
+ ThrowUserError('multiple_alias_not_allowed')
+ if defined $params->{alias};
+ }
+
+ # For security purposes, and because lots of other checks depend on it,
+ # we set the product first before anything else.
+ my $product_changed; # Used only for strict_isolation checks.
+ if (exists $params->{'product'}) {
+ $product_changed = $self->_set_product($params->{'product'}, $params);
+ }
+
+ # strict_isolation checks mean that we should set the groups
+ # immediately after changing the product.
+ $self->_add_remove($params, 'groups');
+
+ if (exists $params->{'dependson'} or exists $params->{'blocked'}) {
+ my %set_deps;
+ foreach my $name (qw(dependson blocked)) {
+ my @dep_ids = @{ $self->$name };
+ # If only one of the two fields was passed in, then we need to
+ # retain the current value for the other one.
+ if (!exists $params->{$name}) {
+ $set_deps{$name} = \@dep_ids;
+ next;
+ }
+
+ # Explicitly setting them to a particular value overrides
+ # add/remove.
+ if (exists $params->{$name}->{set}) {
+ $set_deps{$name} = $params->{$name}->{set};
+ next;
+ }
+
+ foreach my $add (@{ $params->{$name}->{add} || [] }) {
+ push(@dep_ids, $add) if !grep($_ == $add, @dep_ids);
+ }
+ foreach my $remove (@{ $params->{$name}->{remove} || [] }) {
+ @dep_ids = grep($_ != $remove, @dep_ids);
+ }
+ $set_deps{$name} = \@dep_ids;
+ }
+
+ $self->set_dependencies($set_deps{'dependson'}, $set_deps{'blocked'});
+ }
+
+ if (exists $params->{'keywords'}) {
+ # Sorting makes the order "add, remove, set", just like for other
+ # fields.
+ foreach my $action (sort keys %{ $params->{'keywords'} }) {
+ $self->modify_keywords($params->{'keywords'}->{$action}, $action);
+ }
+ }
+
+ if (exists $params->{'comment'} or exists $params->{'work_time'}) {
+ # Add a comment as needed to each bug. This is done early because
+ # there are lots of things that want to check if we added a comment.
+ $self->add_comment($params->{'comment'}->{'body'},
+ { isprivate => $params->{'comment'}->{'is_private'},
+ work_time => $params->{'work_time'} });
+ }
+
+ my %normal_set_all;
+ foreach my $name (keys %$params) {
+ # These are handled separately below.
+ if ($self->can("set_$name")) {
+ $normal_set_all{$name} = $params->{$name};
+ }
+ }
+ $self->SUPER::set_all(\%normal_set_all);
+
+ $self->reset_assigned_to if $params->{'reset_assigned_to'};
+ $self->reset_qa_contact if $params->{'reset_qa_contact'};
+
+ $self->_add_remove($params, 'see_also');
+
+ # And set custom fields.
+ my @custom_fields = Bugzilla->active_custom_fields;
+ foreach my $field (@custom_fields) {
+ my $fname = $field->name;
+ if (exists $params->{$fname}) {
+ $self->set_custom_field($field, $params->{$fname});
+ }
+ }
+
+ $self->_add_remove($params, 'cc');
+
+ # Theoretically you could move a product without ever specifying
+ # a new assignee or qa_contact, or adding/removing any CCs. So,
+ # we have to check that the current assignee, qa, and CCs are still
+ # valid if we've switched products, under strict_isolation. We can only
+ # do that here, because if they *did* change the assignee, qa, or CC,
+ # then we don't want to check the original ones, only the new ones.
+ $self->_check_strict_isolation() if $product_changed;
+}
+
+# Helper for set_all that helps with fields that have an "add/remove"
+# pattern instead of a "set_" pattern.
+sub _add_remove {
+ my ($self, $params, $name) = @_;
+ my @add = @{ $params->{$name}->{add} || [] };
+ my @remove = @{ $params->{$name}->{remove} || [] };
+ $name =~ s/s$//;
+ my $add_method = "add_$name";
+ my $remove_method = "remove_$name";
+ $self->$add_method($_) foreach @add;
+ $self->$remove_method($_) foreach @remove;
+}
+
sub set_alias { $_[0]->set('alias', $_[1]); }
sub set_assigned_to {
my ($self, $value) = @_;
@@ -1671,26 +2275,31 @@
}
sub reset_assigned_to {
my $self = shift;
- if (Bugzilla->params->{'commentonreassignbycomponent'}
- && !$self->{added_comments})
- {
- ThrowUserError('comment_required');
- }
my $comp = $self->component_obj;
$self->set_assigned_to($comp->default_assignee);
}
sub set_cclist_accessible { $_[0]->set('cclist_accessible', $_[1]); }
sub set_comment_is_private {
my ($self, $comment_id, $isprivate) = @_;
- return unless Bugzilla->user->is_insider;
- my ($comment) = grep($comment_id eq $_->{id}, @{$self->longdescs});
+
+ # We also allow people to pass in a hash of comment ids to update.
+ if (ref $comment_id) {
+ while (my ($id, $is) = each %$comment_id) {
+ $self->set_comment_is_private($id, $is);
+ }
+ return;
+ }
+
+ my ($comment) = grep($comment_id == $_->id, @{ $self->comments });
ThrowUserError('comment_invalid_isprivate', { id => $comment_id })
if !$comment;
$isprivate = $isprivate ? 1 : 0;
- if ($isprivate != $comment->{isprivate}) {
- $self->{comment_isprivate} ||= {};
- $self->{comment_isprivate}->{$comment_id} = $isprivate;
+ if ($isprivate != $comment->is_private) {
+ ThrowUserError('user_not_insider') if !Bugzilla->user->is_insider;
+ $self->{comment_isprivate} ||= [];
+ $comment->set_is_private($isprivate);
+ push @{$self->{comment_isprivate}}, $comment;
}
}
sub set_component {
@@ -1711,6 +2320,7 @@
}
sub set_custom_field {
my ($self, $field, $value) = @_;
+
if (ref $value eq 'ARRAY' && $field->type != FIELD_TYPE_MULTI_SELECT) {
$value = $value->[0];
}
@@ -1727,6 +2337,8 @@
detaint_natural($_) foreach (@$dependson, @$blocked);
$self->{'dependson'} = $dependson;
$self->{'blocked'} = $blocked;
+ delete $self->{depends_on_obj};
+ delete $self->{blocks_obj};
}
sub _clear_dup_id { $_[0]->{dup_id} = undef; }
sub set_dup_id {
@@ -1735,6 +2347,21 @@
$self->set('dup_id', $dup_id);
my $new = $self->dup_id;
return if $old == $new;
+
+ # Make sure that we have the DUPLICATE resolution. This is needed
+ # if somebody calls set_dup_id without calling set_bug_status or
+ # set_resolution.
+ if ($self->resolution ne 'DUPLICATE') {
+ # Even if the current status is VERIFIED, we change it back to
+ # RESOLVED (or whatever the duplicate_or_move_bug_status is) here,
+ # because that's the same thing the UI does when you click on the
+ # "Mark as Duplicate" link. If people really want to retain their
+ # current status, they can use set_bug_status and set the DUPLICATE
+ # resolution before getting here.
+ $self->set_bug_status(
+ Bugzilla->params->{'duplicate_or_move_bug_status'},
+ { resolution => 'DUPLICATE' });
+ }
# Update the other bug.
my $dupe_of = new Bugzilla::Bug($self->dup_id);
@@ -1762,10 +2389,17 @@
}
sub set_estimated_time { $_[0]->set('estimated_time', $_[1]); }
sub _set_everconfirmed { $_[0]->set('everconfirmed', $_[1]); }
+sub set_flags {
+ my ($self, $flags, $new_flags) = @_;
+
+ Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags);
+}
sub set_op_sys { $_[0]->set('op_sys', $_[1]); }
sub set_platform { $_[0]->set('rep_platform', $_[1]); }
sub set_priority { $_[0]->set('priority', $_[1]); }
-sub set_product {
+# For security reasons, you have to use set_all to change the product.
+# See the strict_isolation check in set_all for an explanation.
+sub _set_product {
my ($self, $name, $params) = @_;
my $old_product = $self->product_obj;
my $product = $self->_check_product($name);
@@ -1779,14 +2413,14 @@
$self->{_old_product_name} = $old_product->name;
# Delete fields that depend upon the old Product value.
delete $self->{choices};
- delete $self->{milestoneurl};
$product_changed = 1;
}
$params ||= {};
- my $comp_name = $params->{component} || $self->component;
- my $vers_name = $params->{version} || $self->version;
- my $tm_name = $params->{target_milestone};
+ # We delete these so that they're not set again later in set_all.
+ my $comp_name = delete $params->{component} || $self->component;
+ my $vers_name = delete $params->{version} || $self->version;
+ my $tm_name = delete $params->{target_milestone};
# This way, if usetargetmilestone is off and we've changed products,
# set_target_milestone will reset our target_milestone to
# $product->default_milestone. But if we haven't changed products,
@@ -1818,7 +2452,7 @@
undef $@;
Bugzilla->error_mode($old_error_mode);
- my $verified = $params->{change_confirmed};
+ my $verified = $params->{product_change_confirmed};
my %vars;
if (!$verified || !$component_ok || !$version_ok || !$milestone_ok) {
$vars{defaults} = {
@@ -1872,7 +2506,9 @@
# just die if any of these are invalid.
$self->set_component($comp_name);
$self->set_version($vers_name);
- if ($product_changed && !$self->check_can_change_field('target_milestone', 0, 1)) {
+ if ($product_changed
+ and !$self->check_can_change_field('target_milestone', 0, 1))
+ {
# Have to set this directly to bypass the validators.
$self->{target_milestone} = $product->default_milestone;
}
@@ -1880,28 +2516,24 @@
$self->set_target_milestone($tm_name);
}
}
-
+
if ($product_changed) {
- # Remove groups that aren't valid in the new product. This will also
- # have the side effect of removing the bug from groups that aren't
- # active anymore.
- #
+ # Remove groups that can't be set in the new product.
# We copy this array because the original array is modified while we're
# working, and that confuses "foreach".
my @current_groups = @{$self->groups_in};
foreach my $group (@current_groups) {
- if (!grep($group->id == $_->id, @{$product->groups_valid})) {
+ if (!$product->group_is_valid($group)) {
$self->remove_group($group);
}
}
-
+
# Make sure the bug is in all the mandatory groups for the new product.
- foreach my $group (@{$product->groups_mandatory_for(Bugzilla->user)}) {
+ foreach my $group (@{$product->groups_mandatory}) {
$self->add_group($group);
}
}
- # XXX This is temporary until all of process_bug uses update();
return $product_changed;
}
@@ -1916,11 +2548,6 @@
}
sub reset_qa_contact {
my $self = shift;
- if (Bugzilla->params->{'commentonreassignbycomponent'}
- && !$self->{added_comments})
- {
- ThrowUserError('comment_required');
- }
my $comp = $self->component_obj;
$self->set_qa_contact($comp->default_qa_contact);
}
@@ -1933,13 +2560,10 @@
my $old_res = $self->resolution;
$self->set('resolution', $value);
+ delete $self->{choices};
my $new_res = $self->resolution;
if ($new_res ne $old_res) {
- # MOVED has a special meaning and can only be used when
- # really moving bugs to another installation.
- ThrowCodeError('no_manual_moved') if ($new_res eq 'MOVED' && !$params->{moving});
-
# Clear the dup_id if we're leaving the dup resolution.
if ($old_res eq 'DUPLICATE') {
$self->_clear_dup_id();
@@ -1955,44 +2579,55 @@
# of another, theoretically. Note that this code block will also run
# when going between different closed states.
if ($self->resolution eq 'DUPLICATE') {
- if ($params->{dupe_of}) {
- $self->set_dup_id($params->{dupe_of});
+ if (my $dup_id = $params->{dup_id}) {
+ $self->set_dup_id($dup_id);
}
elsif (!$self->dup_id) {
ThrowUserError('dupe_id_required');
}
}
+
+ # This method has handled dup_id, so set_all doesn't have to worry
+ # about it now.
+ delete $params->{dup_id};
}
sub clear_resolution {
my $self = shift;
if (!$self->status->is_open) {
ThrowUserError('resolution_cant_clear', { bug_id => $self->id });
}
- if (Bugzilla->params->{'commentonclearresolution'}
- && $self->resolution && !$self->{added_comments})
- {
- ThrowUserError('comment_required');
- }
$self->{'resolution'} = '';
$self->_clear_dup_id;
}
sub set_severity { $_[0]->set('bug_severity', $_[1]); }
-sub set_status {
+sub set_bug_status {
my ($self, $status, $params) = @_;
my $old_status = $self->status;
$self->set('bug_status', $status);
delete $self->{'status'};
+ delete $self->{'statuses_available'};
+ delete $self->{'choices'};
my $new_status = $self->status;
-
+
if ($new_status->is_open) {
# Check for the everconfirmed transition
- $self->_set_everconfirmed(1) if $new_status->name ne 'UNCONFIRMED';
+ $self->_set_everconfirmed($new_status->name eq 'UNCONFIRMED' ? 0 : 1);
$self->clear_resolution();
+ # Calling clear_resolution handled the "resolution" and "dup_id"
+ # setting, so set_all doesn't have to worry about them.
+ delete $params->{resolution};
+ delete $params->{dup_id};
}
else {
# We do this here so that we can make sure closed statuses have
# resolutions.
- my $resolution = delete $params->{resolution} || $self->resolution;
+ my $resolution = $self->resolution;
+ # We need to check "defined" to prevent people from passing
+ # a blank resolution in the WebService, which would otherwise fail
+ # silently.
+ if (defined $params->{resolution}) {
+ $resolution = delete $params->{resolution};
+ }
$self->set_resolution($resolution, $params);
# Changing between closed statuses zeros the remaining time.
@@ -2031,6 +2666,10 @@
my ($self, $user_or_name) = @_;
my $user = ref $user_or_name ? $user_or_name
: Bugzilla::User->check($user_or_name);
+ my $currentUser = Bugzilla->user;
+ if (!$self->user->{'canedit'} && $user->id != $currentUser->id) {
+ ThrowUserError('cc_remove_denied');
+ }
my $cc_users = $self->cc_users;
@$cc_users = grep { $_->id != $user->id } @$cc_users;
}
@@ -2040,44 +2679,34 @@
sub add_comment {
my ($self, $comment, $params) = @_;
- $comment = $self->_check_comment($comment);
-
$params ||= {};
- if (exists $params->{work_time}) {
- $params->{work_time} = $self->_check_work_time($params->{work_time});
- ThrowUserError('comment_required')
- if $comment eq '' && $params->{work_time} != 0;
- }
- if (exists $params->{type}) {
- $params->{type} = $self->_check_comment_type($params->{type});
- }
- if (exists $params->{isprivate}) {
- $params->{isprivate} =
- $self->_check_commentprivacy($params->{isprivate});
- }
- # XXX We really should check extra_data, too.
- if ($comment eq '' && !($params->{type} || $params->{work_time})) {
+ # Fill out info that doesn't change and callers may not pass in
+ $params->{'bug_id'} = $self;
+ $params->{'thetext'} = defined($comment) ? $comment : '';
+
+ # Validate all the entered data
+ Bugzilla::Comment->check_required_create_fields($params);
+ $params = Bugzilla::Comment->run_create_validators($params);
+
+ # This makes it so we won't create new comments when there is nothing
+ # to add
+ if ($params->{'thetext'} eq ''
+ && !($params->{type} || abs($params->{work_time} || 0)))
+ {
return;
}
- # So we really want to comment. Make sure we are allowed to do so.
- my $privs;
- $self->check_can_change_field('longdesc', 0, 1, \$privs)
- || ThrowUserError('illegal_change', { field => 'longdesc', privs => $privs });
-
- $self->{added_comments} ||= [];
- my $add_comment = dclone($params);
- $add_comment->{thetext} = $comment;
-
- # We only want to trick_taint fields that we know about--we don't
- # want to accidentally let somebody set some field that's not OK
- # to set!
- foreach my $field (UPDATE_COMMENT_COLUMNS) {
- trick_taint($add_comment->{$field}) if defined $add_comment->{$field};
+ # If the user has explicitly set remaining_time, this will be overridden
+ # later in set_all. But if they haven't, this keeps remaining_time
+ # up-to-date.
+ if ($params->{work_time}) {
+ $self->set_remaining_time(max($self->remaining_time - $params->{work_time}, 0));
}
- push(@{$self->{added_comments}}, $add_comment);
+ $self->{added_comments} ||= [];
+
+ push(@{$self->{added_comments}}, $params);
}
# There was a lot of duplicate code when I wrote this as three separate
@@ -2086,15 +2715,15 @@
sub modify_keywords {
my ($self, $keywords, $action) = @_;
- $action ||= "makeexact";
- if (!grep($action eq $_, qw(add delete makeexact))) {
- $action = "makeexact";
+ $action ||= 'set';
+ if (!grep($action eq $_, qw(add remove set))) {
+ $action = 'set';
}
$keywords = $self->_check_keywords($keywords);
my (@result, $any_changes);
- if ($action eq 'makeexact') {
+ if ($action eq 'set') {
@result = @$keywords;
# Check if anything was added or removed.
my @old_ids = map { $_->id } @{$self->keyword_objects};
@@ -2128,21 +2757,29 @@
}
$self->{'keyword_objects'} = \@result;
- return $any_changes;
}
sub add_group {
my ($self, $group) = @_;
- # Invalid ids are silently ignored. (We can't tell people whether
- # or not a group exists.)
- $group = new Bugzilla::Group($group) unless ref $group;
- return unless $group;
+
+ # If the user enters "FoO" but the DB has "Foo", $group->name would
+ # return "Foo" and thus revealing the existence of the group name.
+ # So we have to store and pass the name as entered by the user to
+ # the error message, if we have it.
+ my $group_name = blessed($group) ? $group->name : $group;
+ my $args = { name => $group_name, product => $self->product,
+ bug_id => $self->id, action => 'add' };
+
+ $group = Bugzilla::Group->check_no_disclose($args) if !blessed $group;
+
+ # If the bug is already in this group, then there is nothing to do.
+ return if $self->in_group($group);
+
# Make sure that bugs in this product can actually be restricted
- # to this group.
- grep($group->id == $_->id, @{$self->product_obj->groups_valid})
- || ThrowUserError('group_invalid_restriction',
- { product => $self->product, group_id => $group->id });
+ # to this group by the current user.
+ $self->product_obj->group_is_settable($group)
+ || ThrowUserError('group_restriction_not_allowed', $args);
# OtherControl people can add groups only during a product change,
# and only when the group is not NA for them.
@@ -2151,36 +2788,38 @@
if (!$self->{_old_product_name}
|| $controls->{othercontrol} == CONTROLMAPNA)
{
- ThrowUserError('group_change_denied',
- { bug => $self, group_id => $group->id });
+ ThrowUserError('group_restriction_not_allowed', $args);
}
}
my $current_groups = $self->groups_in;
- if (!grep($group->id == $_->id, @$current_groups)) {
- push(@$current_groups, $group);
- }
+ push(@$current_groups, $group);
}
sub remove_group {
my ($self, $group) = @_;
- $group = new Bugzilla::Group($group) unless ref $group;
- return unless $group;
-
- # First, check if this is a valid group for this product.
- # You can *always* remove a group that is not valid for this product, so
- # we don't do any other checks if that's the case. (set_product does this.)
- #
- # This particularly happens when isbuggroup is no longer 1, and we're
- # moving a bug to a new product.
- if (grep($_->id == $group->id, @{$self->product_obj->groups_valid})) {
+
+ # See add_group() for the reason why we store the user input.
+ my $group_name = blessed($group) ? $group->name : $group;
+ my $args = { name => $group_name, product => $self->product,
+ bug_id => $self->id, action => 'remove' };
+
+ $group = Bugzilla::Group->check_no_disclose($args) if !blessed $group;
+
+ # If the bug isn't in this group, then either the name is misspelled,
+ # or the group really doesn't exist. Let the user know about this problem.
+ $self->in_group($group) || ThrowUserError('group_invalid_removal', $args);
+
+ # Check if this is a valid group for this product. You can *always*
+ # remove a group that is not valid for this product (set_product does this).
+ # This particularly happens when we're moving a bug to a new product.
+ # You still have to be a member of an inactive group to remove it.
+ if ($self->product_obj->group_is_valid($group)) {
my $controls = $self->product_obj->group_controls->{$group->id};
- # Nobody can ever remove a Mandatory group.
- if ($controls->{membercontrol} == CONTROLMAPMANDATORY) {
- ThrowUserError('group_invalid_removal',
- { product => $self->product, group_id => $group->id,
- bug => $self });
+ # Nobody can ever remove a Mandatory group, unless it became inactive.
+ if ($controls->{membercontrol} == CONTROLMAPMANDATORY && $group->is_active) {
+ ThrowUserError('group_invalid_removal', $args);
}
# OtherControl people can remove groups only during a product change,
@@ -2190,20 +2829,209 @@
|| $controls->{othercontrol} == CONTROLMAPMANDATORY
|| $controls->{othercontrol} == CONTROLMAPNA)
{
- ThrowUserError('group_change_denied',
- { bug => $self, group_id => $group->id });
+ ThrowUserError('group_invalid_removal', $args);
}
}
}
-
+
my $current_groups = $self->groups_in;
@$current_groups = grep { $_->id != $group->id } @$current_groups;
}
+sub add_see_also {
+ my ($self, $input, $skip_recursion) = @_;
+
+ # This is needed by xt/search.t.
+ $input = $input->name if blessed($input);
+
+ $input = trim($input);
+ return if !$input;
+
+ my ($class, $uri) = Bugzilla::BugUrl->class_for($input);
+
+ my $params = { value => $uri, bug_id => $self, class => $class };
+ $class->check_required_create_fields($params);
+
+ my $field_values = $class->run_create_validators($params);
+ my $value = $field_values->{value}->as_string;
+ trick_taint($value);
+ $field_values->{value} = $value;
+
+ # We only add the new URI if it hasn't been added yet. URIs are
+ # case-sensitive, but most of our DBs are case-insensitive, so we do
+ # this check case-insensitively.
+ if (!grep { lc($_->name) eq lc($value) } @{ $self->see_also }) {
+ my $privs;
+ my $can = $self->check_can_change_field('see_also', '', $value, \$privs);
+ if (!$can) {
+ ThrowUserError('illegal_change', { field => 'see_also',
+ newvalue => $value,
+ privs => $privs });
+ }
+ # If this is a link to a local bug then save the
+ # ref bug id for sending changes email.
+ my $ref_bug = delete $field_values->{ref_bug};
+ if ($class->isa('Bugzilla::BugUrl::Bugzilla::Local')
+ and !$skip_recursion)
+ {
+ $ref_bug->add_see_also($self->id, 'skip_recursion');
+ push @{ $self->{_update_ref_bugs} }, $ref_bug;
+ push @{ $self->{see_also_changes} }, $ref_bug->id;
+ }
+ push @{ $self->{see_also} }, bless ($field_values, $class);
+ }
+}
+
+sub remove_see_also {
+ my ($self, $url, $skip_recursion) = @_;
+ my $see_also = $self->see_also;
+
+ # This is needed by xt/search.t.
+ $url = $url->name if blessed($url);
+
+ my ($removed_bug_url, $new_see_also) =
+ part { lc($_->name) ne lc($url) } @$see_also;
+
+ my $privs;
+ my $can = $self->check_can_change_field('see_also', $see_also, $new_see_also, \$privs);
+ if (!$can) {
+ ThrowUserError('illegal_change', { field => 'see_also',
+ oldvalue => $url,
+ privs => $privs });
+ }
+
+ # Since we remove also the url from the referenced bug,
+ # we need to notify changes for that bug too.
+ $removed_bug_url = $removed_bug_url->[0];
+ if (!$skip_recursion and $removed_bug_url
+ and $removed_bug_url->isa('Bugzilla::BugUrl::Bugzilla::Local'))
+ {
+ my $ref_bug
+ = Bugzilla::Bug->check($removed_bug_url->ref_bug_url->bug_id);
+
+ if (Bugzilla->user->can_edit_product($ref_bug->product_id)) {
+ my $self_url = $removed_bug_url->local_uri($self->id);
+ $ref_bug->remove_see_also($self_url, 'skip_recursion');
+ push @{ $self->{_update_ref_bugs} }, $ref_bug;
+ push @{ $self->{see_also_changes} }, $ref_bug->id;
+ }
+ }
+
+ $self->{see_also} = $new_see_also || [];
+}
+
+sub add_tag {
+ my ($self, $tag) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ $tag = $self->_check_tag_name($tag);
+
+ my $tag_id = $user->tags->{$tag}->{id};
+ # If this tag doesn't exist for this user yet, create it.
+ if (!$tag_id) {
+ $dbh->do('INSERT INTO tag (user_id, name) VALUES (?, ?)',
+ undef, ($user->id, $tag));
+
+ $tag_id = $dbh->selectrow_array('SELECT id FROM tag
+ WHERE name = ? AND user_id = ?',
+ undef, ($tag, $user->id));
+ # The list has changed.
+ delete $user->{tags};
+ }
+ # Do nothing if this tag is already set for this bug.
+ return if grep { $_ eq $tag } @{$self->tags};
+
+ # Increment the counter. Do it before the SQL call below,
+ # to not count the tag twice.
+ $user->tags->{$tag}->{bug_count}++;
+
+ $dbh->do('INSERT INTO bug_tag (bug_id, tag_id) VALUES (?, ?)',
+ undef, ($self->id, $tag_id));
+
+ push(@{$self->{tags}}, $tag);
+}
+
+sub remove_tag {
+ my ($self, $tag) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ $tag = $self->_check_tag_name($tag);
+
+ my $tag_id = exists $user->tags->{$tag} ? $user->tags->{$tag}->{id} : undef;
+ # Do nothing if the user doesn't use this tag, or didn't set it for this bug.
+ return unless ($tag_id && grep { $_ eq $tag } @{$self->tags});
+
+ $dbh->do('DELETE FROM bug_tag WHERE bug_id = ? AND tag_id = ?',
+ undef, ($self->id, $tag_id));
+
+ $self->{tags} = [grep { $_ ne $tag } @{$self->tags}];
+
+ # Decrement the counter, and delete the tag if no bugs are using it anymore.
+ if (!--$user->tags->{$tag}->{bug_count}) {
+ $dbh->do('DELETE FROM tag WHERE name = ? AND user_id = ?',
+ undef, ($tag, $user->id));
+
+ # The list has changed.
+ delete $user->{tags};
+ }
+}
+
+sub tags {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ # This method doesn't support several users using the same bug object.
+ if (!exists $self->{tags}) {
+ $self->{tags} = $dbh->selectcol_arrayref(
+ 'SELECT name FROM bug_tag
+ INNER JOIN tag ON tag.id = bug_tag.tag_id
+ WHERE bug_id = ? AND user_id = ?',
+ undef, ($self->id, $user->id));
+ }
+ return $self->{tags};
+}
+
#####################################################################
-# Instance Accessors
+# Simple Accessors
#####################################################################
+# These are accessors that don't need to access the database.
+# Keep them in alphabetical order.
+
+sub alias { return $_[0]->{alias} }
+sub bug_file_loc { return $_[0]->{bug_file_loc} }
+sub bug_id { return $_[0]->{bug_id} }
+sub bug_severity { return $_[0]->{bug_severity} }
+sub bug_status { return $_[0]->{bug_status} }
+sub cclist_accessible { return $_[0]->{cclist_accessible} }
+sub component_id { return $_[0]->{component_id} }
+sub creation_ts { return $_[0]->{creation_ts} }
+sub estimated_time { return $_[0]->{estimated_time} }
+sub deadline { return $_[0]->{deadline} }
+sub delta_ts { return $_[0]->{delta_ts} }
+sub error { return $_[0]->{error} }
+sub everconfirmed { return $_[0]->{everconfirmed} }
+sub lastdiffed { return $_[0]->{lastdiffed} }
+sub op_sys { return $_[0]->{op_sys} }
+sub priority { return $_[0]->{priority} }
+sub product_id { return $_[0]->{product_id} }
+sub remaining_time { return $_[0]->{remaining_time} }
+sub reporter_accessible { return $_[0]->{reporter_accessible} }
+sub rep_platform { return $_[0]->{rep_platform} }
+sub resolution { return $_[0]->{resolution} }
+sub short_desc { return $_[0]->{short_desc} }
+sub status_whiteboard { return $_[0]->{status_whiteboard} }
+sub target_milestone { return $_[0]->{target_milestone} }
+sub version { return $_[0]->{version} }
+
+#####################################################################
+# Complex Accessors
+#####################################################################
+
+# These are accessors that have to access the database for additional
+# information about a bug.
+
# These subs are in alphabetical order, as much as possible.
# If you add a new sub, please try to keep it in alphabetical order
# with the other ones.
@@ -2232,12 +3060,43 @@
return $self->{'dup_id'};
}
+sub _resolve_ultimate_dup_id {
+ my ($bug_id, $dupe_of, $loops_are_an_error) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare('SELECT dupe_of FROM duplicates WHERE dupe = ?');
+
+ my $this_dup = $dupe_of || $dbh->selectrow_array($sth, undef, $bug_id);
+ my $last_dup = $bug_id;
+
+ my %dupes;
+ while ($this_dup) {
+ if ($this_dup == $bug_id) {
+ if ($loops_are_an_error) {
+ ThrowUserError('dupe_loop_detected', { bug_id => $bug_id,
+ dupe_of => $dupe_of });
+ }
+ else {
+ return $last_dup;
+ }
+ }
+ # If $dupes{$this_dup} is already set to 1, then a loop
+ # already exists which does not involve this bug.
+ # As the user is not responsible for this loop, do not
+ # prevent him from marking this bug as a duplicate.
+ return $last_dup if exists $dupes{$this_dup};
+ $dupes{$this_dup} = 1;
+ $last_dup = $this_dup;
+ $this_dup = $dbh->selectrow_array($sth, undef, $this_dup);
+ }
+
+ return $last_dup;
+}
+
sub actual_time {
my ($self) = @_;
return $self->{'actual_time'} if exists $self->{'actual_time'};
- if ( $self->{'error'} ||
- !Bugzilla->user->in_group(Bugzilla->params->{"timetrackinggroup"}) ) {
+ if ( $self->{'error'} || !Bugzilla->user->is_timetracker ) {
$self->{'actual_time'} = undef;
return $self->{'actual_time'};
}
@@ -2256,8 +3115,12 @@
if exists $self->{'any_flags_requesteeble'};
return 0 if $self->{'error'};
- $self->{'any_flags_requesteeble'} =
- grep($_->{'is_requesteeble'}, @{$self->flag_types});
+ my $any_flags_requesteeble =
+ grep { $_->is_requestable && $_->is_requesteeble } @{$self->flag_types};
+ # Useful in case a flagtype is no longer requestable but a requestee
+ # has been set before we turned off that bit.
+ $any_flags_requesteeble ||= grep { $_->requestee_id } @{$self->flags};
+ $self->{'any_flags_requesteeble'} = $any_flags_requesteeble;
return $self->{'any_flags_requesteeble'};
}
@@ -2267,22 +3130,8 @@
return $self->{'attachments'} if exists $self->{'attachments'};
return [] if $self->{'error'};
- my $attachments = Bugzilla::Attachment->get_attachments_by_bug($self->bug_id);
- $_->{'flags'} = [] foreach @$attachments;
- my %att = map { $_->id => $_ } @$attachments;
-
- # Retrieve all attachment flags at once for this bug, and group them
- # by attachment. We populate attachment flags here to avoid querying
- # the DB for each attachment individually later.
- my $flags = Bugzilla::Flag->match({ 'bug_id' => $self->bug_id,
- 'target_type' => 'attachment' });
-
- # Exclude flags for private attachments you cannot see.
- @$flags = grep {exists $att{$_->attach_id}} @$flags;
-
- push(@{$att{$_->attach_id}->{'flags'}}, $_) foreach @$flags;
-
- $self->{'attachments'} = [sort {$a->id <=> $b->id} values %att];
+ $self->{'attachments'} =
+ Bugzilla::Attachment->get_attachments_by_bug($self->bug_id, {preload => 1});
return $self->{'attachments'};
}
@@ -2302,8 +3151,25 @@
return $self->{'blocked'};
}
-# Even bugs in an error state always have a bug_id.
-sub bug_id { $_[0]->{'bug_id'}; }
+sub blocks_obj {
+ my ($self) = @_;
+ $self->{blocks_obj} ||= $self->_bugs_in_order($self->blocked);
+ return $self->{blocks_obj};
+}
+
+sub bug_group {
+ my ($self) = @_;
+ return join(', ', (map { $_->name } @{$self->groups_in}));
+}
+
+sub related_bugs {
+ my ($self, $relationship) = @_;
+ return [] if $self->{'error'};
+
+ my $field_name = $relationship->name;
+ $self->{'related_bugs'}->{$field_name} ||= $self->match({$field_name => $self->id});
+ return $self->{'related_bugs'}->{$field_name};
+}
sub cc {
my ($self) = @_;
@@ -2384,46 +3250,39 @@
return $self->{'dependson'};
}
+sub depends_on_obj {
+ my ($self) = @_;
+ $self->{depends_on_obj} ||= $self->_bugs_in_order($self->dependson);
+ return $self->{depends_on_obj};
+}
+
sub flag_types {
my ($self) = @_;
return $self->{'flag_types'} if exists $self->{'flag_types'};
return [] if $self->{'error'};
- # The types of flags that can be set on this bug.
- # If none, no UI for setting flags will be displayed.
- my $flag_types = Bugzilla::FlagType::match(
- {'target_type' => 'bug',
- 'product_id' => $self->{'product_id'},
- 'component_id' => $self->{'component_id'} });
+ my $vars = { target_type => 'bug',
+ product_id => $self->{product_id},
+ component_id => $self->{component_id},
+ bug_id => $self->bug_id };
- $_->{'flags'} = [] foreach @$flag_types;
- my %flagtypes = map { $_->id => $_ } @$flag_types;
-
- # Retrieve all bug flags at once for this bug and group them
- # by flag types.
- my $flags = Bugzilla::Flag->match({ 'bug_id' => $self->bug_id,
- 'target_type' => 'bug' });
-
- # Call the internal 'type_id' variable instead of the method
- # to not create a flagtype object.
- push(@{$flagtypes{$_->{'type_id'}}->{'flags'}}, $_) foreach @$flags;
-
- $self->{'flag_types'} =
- [sort {$a->sortkey <=> $b->sortkey || $a->name cmp $b->name} values %flagtypes];
-
+ $self->{'flag_types'} = Bugzilla::Flag->_flag_types($vars);
return $self->{'flag_types'};
}
+sub flags {
+ my $self = shift;
+
+ # Don't cache it as it must be in sync with ->flag_types.
+ $self->{flags} = [map { @{$_->{flags}} } @{$self->flag_types}];
+ return $self->{flags};
+}
+
sub isopened {
my $self = shift;
return is_open_state($self->{bug_status}) ? 1 : 0;
}
-sub isunconfirmed {
- my $self = shift;
- return ($self->bug_status eq 'UNCONFIRMED') ? 1 : 0;
-}
-
sub keywords {
my ($self) = @_;
return join(', ', (map { $_->name } @{$self->keyword_objects}));
@@ -2442,21 +3301,54 @@
return $self->{'keyword_objects'};
}
-sub longdescs {
- my ($self) = @_;
- return $self->{'longdescs'} if exists $self->{'longdescs'};
+sub comments {
+ my ($self, $params) = @_;
return [] if $self->{'error'};
- $self->{'longdescs'} = GetComments($self->{bug_id});
- return $self->{'longdescs'};
+ $params ||= {};
+
+ if (!defined $self->{'comments'}) {
+ $self->{'comments'} = Bugzilla::Comment->match({ bug_id => $self->id });
+ my $count = 0;
+ foreach my $comment (@{ $self->{'comments'} }) {
+ $comment->{count} = $count++;
+ $comment->{bug} = $self;
+ }
+ Bugzilla::Comment->preload($self->{'comments'});
+ }
+ my @comments = @{ $self->{'comments'} };
+
+ my $order = $params->{order}
+ || Bugzilla->user->setting('comment_sort_order');
+ if ($order ne 'oldest_to_newest') {
+ @comments = reverse @comments;
+ if ($order eq 'newest_to_oldest_desc_first') {
+ unshift(@comments, pop @comments);
+ }
+ }
+
+ if ($params->{after}) {
+ my $from = datetime_from($params->{after});
+ @comments = grep { datetime_from($_->creation_ts) > $from } @comments;
+ }
+ if ($params->{to}) {
+ my $to = datetime_from($params->{to});
+ @comments = grep { datetime_from($_->creation_ts) <= $to } @comments;
+ }
+ return \@comments;
}
-sub milestoneurl {
- my ($self) = @_;
- return $self->{'milestoneurl'} if exists $self->{'milestoneurl'};
- return '' if $self->{'error'};
-
- $self->{'milestoneurl'} = $self->product_obj->milestone_url;
- return $self->{'milestoneurl'};
+# This is needed by xt/search.t.
+sub percentage_complete {
+ my $self = shift;
+ return undef if $self->{'error'} || !Bugzilla->user->is_timetracker;
+ my $remaining = $self->remaining_time;
+ my $actual = $self->actual_time;
+ my $total = $remaining + $actual;
+ return undef if $total == 0;
+ # Search.pm truncates this value to an integer, so we want to as well,
+ # since this is mostly used in a test where its value needs to be
+ # identical to what the database will return.
+ return int(100 * ($actual / $total));
}
sub product {
@@ -2501,6 +3393,21 @@
return $self->{'reporter'};
}
+sub see_also {
+ my ($self) = @_;
+ return [] if $self->{'error'};
+ if (!exists $self->{see_also}) {
+ my $ids = Bugzilla->dbh->selectcol_arrayref(
+ 'SELECT id FROM bug_see_also WHERE bug_id = ?',
+ undef, $self->id);
+
+ my $bug_urls = Bugzilla::BugUrl->new_from_list($ids);
+
+ $self->{see_also} = $bug_urls;
+ }
+ return $self->{see_also};
+}
+
sub status {
my $self = shift;
return undef if $self->{'error'};
@@ -2509,6 +3416,36 @@
return $self->{'status'};
}
+sub statuses_available {
+ my $self = shift;
+ return [] if $self->{'error'};
+ return $self->{'statuses_available'}
+ if defined $self->{'statuses_available'};
+
+ my @statuses = @{ $self->status->can_change_to };
+
+ # UNCONFIRMED is only a valid status if it is enabled in this product.
+ if (!$self->product_obj->allows_unconfirmed) {
+ @statuses = grep { $_->name ne 'UNCONFIRMED' } @statuses;
+ }
+
+ my @available;
+ foreach my $status (@statuses) {
+ # Make sure this is a legal status transition
+ next if !$self->check_can_change_field(
+ 'bug_status', $self->status->name, $status->name);
+ push(@available, $status);
+ }
+
+ # If this bug has an inactive status set, it should still be in the list.
+ if (!grep($_->name eq $self->status->name, @available)) {
+ unshift(@available, $self->status);
+ }
+
+ $self->{'statuses_available'} = \@available;
+ return $self->{'statuses_available'};
+}
+
sub show_attachment_flags {
my ($self) = @_;
return $self->{'show_attachment_flags'}
@@ -2533,14 +3470,6 @@
return $self->{'show_attachment_flags'};
}
-sub use_votes {
- my ($self) = @_;
- return 0 if $self->{'error'};
-
- return Bugzilla->params->{'usevotes'}
- && $self->product_obj->votes_per_user > 0;
-}
-
sub groups {
my $self = shift;
return $self->{'groups'} if exists $self->{'groups'};
@@ -2618,13 +3547,17 @@
return $self->{'groups_in'};
}
+sub in_group {
+ my ($self, $group) = @_;
+ return grep($_->id == $group->id, @{$self->groups_in}) ? 1 : 0;
+}
+
sub user {
my $self = shift;
return $self->{'user'} if exists $self->{'user'};
return {} if $self->{'error'};
my $user = Bugzilla->user;
- my $canmove = Bugzilla->params->{'move-enabled'} && $user->is_mover;
my $prod_id = $self->{'product_id'};
@@ -2639,62 +3572,48 @@
my $isreporter = $user->id
&& $user->id == $self->{reporter_id};
- $self->{'user'} = {canmove => $canmove,
- canconfirm => $canconfirm,
+ $self->{'user'} = {canconfirm => $canconfirm,
canedit => $canedit,
isreporter => $isreporter};
return $self->{'user'};
}
+# This is intended to get values that can be selected by the user in the
+# UI. It should not be used for security or validation purposes.
sub choices {
my $self = shift;
return $self->{'choices'} if exists $self->{'choices'};
return {} if $self->{'error'};
+ my $user = Bugzilla->user;
- $self->{'choices'} = {};
-
- my @prodlist = map {$_->name} @{Bugzilla->user->get_enterable_products};
+ my @products = @{ $user->get_enterable_products };
# The current product is part of the popup, even if new bugs are no longer
# allowed for that product
- if (lsearch(\@prodlist, $self->product) < 0) {
- push(@prodlist, $self->product);
- @prodlist = sort @prodlist;
+ if (!grep($_->name eq $self->product_obj->name, @products)) {
+ unshift(@products, $self->product_obj);
}
+ my %class_ids = map { $_->classification_id => 1 } @products;
+ my $classifications =
+ Bugzilla::Classification->new_from_list([keys %class_ids]);
- # Hack - this array contains "". See bug 106589.
- my @res = grep ($_, @{get_legal_field_values('resolution')});
+ my %choices = (
+ bug_status => $self->statuses_available,
+ classification => $classifications,
+ product => \@products,
+ component => $self->product_obj->components,
+ version => $self->product_obj->versions,
+ target_milestone => $self->product_obj->milestones,
+ );
- $self->{'choices'} =
- {
- 'product' => \@prodlist,
- 'rep_platform' => get_legal_field_values('rep_platform'),
- 'priority' => get_legal_field_values('priority'),
- 'bug_severity' => get_legal_field_values('bug_severity'),
- 'op_sys' => get_legal_field_values('op_sys'),
- 'bug_status' => get_legal_field_values('bug_status'),
- 'resolution' => \@res,
- 'component' => [map($_->name, @{$self->product_obj->components})],
- 'version' => [map($_->name, @{$self->product_obj->versions})],
- 'target_milestone' => [map($_->name, @{$self->product_obj->milestones})],
- };
+ my $resolution_field = new Bugzilla::Field({ name => 'resolution' });
+ # Don't include the empty resolution in drop-downs.
+ my @resolutions = grep($_->name, @{ $resolution_field->legal_values });
+ $choices{'resolution'} = \@resolutions;
+ $self->{'choices'} = \%choices;
return $self->{'choices'};
}
-sub votes {
- my ($self) = @_;
- return 0 if $self->{error};
- return $self->{votes} if defined $self->{votes};
-
- my $dbh = Bugzilla->dbh;
- $self->{votes} = $dbh->selectrow_array(
- 'SELECT SUM(vote_count) FROM votes
- WHERE bug_id = ? ' . $dbh->sql_group_by('bug_id'),
- undef, $self->bug_id);
- $self->{votes} ||= 0;
- return $self->{votes};
-}
-
# Convenience Function. If you need speed, use this. If you need
# other Bug fields in addition to this, just create a new Bug with
# the alias.
@@ -2713,45 +3632,16 @@
# Subroutines
#####################################################################
-sub update_comment {
- my ($self, $comment_id, $new_comment) = @_;
-
- # Some validation checks.
- if ($self->{'error'}) {
- ThrowCodeError("bug_error", { bug => $self });
- }
- detaint_natural($comment_id)
- || ThrowCodeError('bad_arg', {argument => 'comment_id', function => 'update_comment'});
-
- # The comment ID must belong to this bug.
- my @current_comment_obj = grep {$_->{'id'} == $comment_id} @{$self->longdescs};
- scalar(@current_comment_obj)
- || ThrowCodeError('bad_arg', {argument => 'comment_id', function => 'update_comment'});
-
- # If the new comment is undefined, then there is nothing to update.
- # To delete a comment, an empty string should be passed.
- return unless defined $new_comment;
- $new_comment =~ s/\s*$//s; # Remove trailing whitespaces.
- $new_comment =~ s/\r\n?/\n/g; # Handle Windows and Mac-style line endings.
- trick_taint($new_comment);
-
- # We assume _check_comment() has already been called earlier.
- Bugzilla->dbh->do('UPDATE longdescs SET thetext = ? WHERE comment_id = ?',
- undef, ($new_comment, $comment_id));
- $self->_sync_fulltext();
-
- # Update the comment object with this new text.
- $current_comment_obj[0]->{'body'} = $new_comment;
-}
-
# Represents which fields from the bugs table are handled by process_bug.cgi.
sub editable_bug_fields {
my @fields = Bugzilla->dbh->bz_table_columns('bugs');
# Obsolete custom fields are not editable.
- my @obsolete_fields = Bugzilla->get_fields({obsolete => 1, custom => 1});
+ my @obsolete_fields = @{ Bugzilla->fields({obsolete => 1, custom => 1}) };
@obsolete_fields = map { $_->name } @obsolete_fields;
- foreach my $remove ("bug_id", "reporter", "creation_ts", "delta_ts", "lastdiffed", @obsolete_fields) {
- my $location = lsearch(\@fields, $remove);
+ foreach my $remove ("bug_id", "reporter", "creation_ts", "delta_ts",
+ "lastdiffed", @obsolete_fields)
+ {
+ my $location = firstidx { $_ eq $remove } @fields;
# Custom multi-select fields are not stored in the bugs table.
splice(@fields, $location, 1) if ($location > -1);
}
@@ -2762,113 +3652,33 @@
# XXX - When Bug::update() will be implemented, we should make this routine
# a private method.
+# Join with bug_status and bugs tables to show bugs with open statuses first,
+# and then the others
sub EmitDependList {
my ($myfield, $targetfield, $bug_id) = (@_);
my $dbh = Bugzilla->dbh;
my $list_ref = $dbh->selectcol_arrayref(
- "SELECT $targetfield FROM dependencies
- WHERE $myfield = ? ORDER BY $targetfield",
+ "SELECT $targetfield
+ FROM dependencies
+ INNER JOIN bugs ON dependencies.$targetfield = bugs.bug_id
+ INNER JOIN bug_status ON bugs.bug_status = bug_status.value
+ WHERE $myfield = ?
+ ORDER BY is_open DESC, $targetfield",
undef, $bug_id);
return $list_ref;
}
-sub ValidateTime {
- my ($time, $field) = @_;
-
- # regexp verifies one or more digits, optionally followed by a period and
- # zero or more digits, OR we have a period followed by one or more digits
- # (allow negatives, though, so people can back out errors in time reporting)
- if ($time !~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) {
- ThrowUserError("number_not_numeric",
- {field => "$field", num => "$time"});
- }
-
- # Only the "work_time" field is allowed to contain a negative value.
- if ( ($time < 0) && ($field ne "work_time") ) {
- ThrowUserError("number_too_small",
- {field => "$field", num => "$time", min_num => "0"});
- }
-
- if ($time > 99999.99) {
- ThrowUserError("number_too_large",
- {field => "$field", num => "$time", max_num => "99999.99"});
- }
-}
-
-sub GetComments {
- my ($id, $comment_sort_order, $start, $end, $raw) = @_;
- my $dbh = Bugzilla->dbh;
-
- $comment_sort_order = $comment_sort_order ||
- Bugzilla->user->settings->{'comment_sort_order'}->{'value'};
-
- my $sort_order = ($comment_sort_order eq "oldest_to_newest") ? 'asc' : 'desc';
-
- my @comments;
- my @args = ($id);
-
- my $query = 'SELECT longdescs.comment_id AS id, profiles.userid, ' .
- $dbh->sql_date_format('longdescs.bug_when', '%Y.%m.%d %H:%i:%s') .
- ' AS time, longdescs.thetext AS body, longdescs.work_time,
- isprivate, already_wrapped, type, extra_data
- FROM longdescs
- INNER JOIN profiles
- ON profiles.userid = longdescs.who
- WHERE longdescs.bug_id = ?';
- if ($start) {
- $query .= ' AND longdescs.bug_when > ?
- AND longdescs.bug_when <= ?';
- push(@args, ($start, $end));
- }
- $query .= " ORDER BY longdescs.bug_when $sort_order";
- my $sth = $dbh->prepare($query);
- $sth->execute(@args);
-
- while (my $comment_ref = $sth->fetchrow_hashref()) {
- my %comment = %$comment_ref;
- $comment{'author'} = new Bugzilla::User($comment{'userid'});
-
- # If raw data is requested, do not format 'special' comments.
- $comment{'body'} = format_comment(\%comment) unless $raw;
-
- push (@comments, \%comment);
- }
-
- if ($comment_sort_order eq "newest_to_oldest_desc_first") {
- unshift(@comments, pop @comments);
- }
-
- return \@comments;
-}
-
-# Format language specific comments. This routine must not update
-# $comment{'body'} itself, see BugMail::prepare_comments().
-sub format_comment {
- my $comment = shift;
- my $body;
-
- if ($comment->{'type'} == CMT_DUPE_OF) {
- $body = $comment->{'body'} . "\n\n" .
- get_text('bug_duplicate_of', { dupe_of => $comment->{'extra_data'} });
- }
- elsif ($comment->{'type'} == CMT_HAS_DUPE) {
- $body = get_text('bug_has_duplicate', { dupe => $comment->{'extra_data'} });
- }
- elsif ($comment->{'type'} == CMT_POPULAR_VOTES) {
- $body = get_text('bug_confirmed_by_votes');
- }
- elsif ($comment->{'type'} == CMT_MOVED_TO) {
- $body = $comment->{'body'} . "\n\n" .
- get_text('bug_moved_to', { login => $comment->{'extra_data'} });
- }
- else {
- $body = $comment->{'body'};
- }
- return $body;
+# Creates a lot of bug objects in the same order as the input array.
+sub _bugs_in_order {
+ my ($self, $bug_ids) = @_;
+ my $bugs = $self->new_from_list($bug_ids);
+ my %bug_map = map { $_->id => $_ } @$bugs;
+ my @result = map { $bug_map{$_} } @$bug_ids;
+ return \@result;
}
# Get the activity of a bug, starting from $starttime (if given).
-# This routine assumes ValidateBugID has been previously called.
+# This routine assumes Bugzilla::Bug->check has been previously called.
sub GetBugActivity {
my ($bug_id, $attach_id, $starttime) = @_;
my $dbh = Bugzilla->dbh;
@@ -2893,24 +3703,17 @@
# Only includes attachments the user is allowed to see.
my $suppjoins = "";
my $suppwhere = "";
- if (Bugzilla->params->{"insidergroup"}
- && !Bugzilla->user->in_group(Bugzilla->params->{'insidergroup'}))
+ if (!Bugzilla->user->is_insider)
{
$suppjoins = "LEFT JOIN attachments
ON attachments.attach_id = bugs_activity.attach_id";
$suppwhere = "AND COALESCE(attachments.isprivate, 0) = 0";
}
- my $query = "
- SELECT COALESCE(fielddefs.description, "
- # This is a hack - PostgreSQL requires both COALESCE
- # arguments to be of the same type, and this is the only
- # way supported by both MySQL 3 and PostgreSQL to convert
- # an integer to a string. MySQL 4 supports CAST.
- . $dbh->sql_string_concat('bugs_activity.fieldid', q{''}) .
- "), fielddefs.name, bugs_activity.attach_id, " .
+ my $query = "SELECT fielddefs.name, bugs_activity.attach_id, " .
$dbh->sql_date_format('bugs_activity.bug_when', '%Y.%m.%d %H:%i:%s') .
- ", bugs_activity.removed, bugs_activity.added, profiles.login_name
+ ", bugs_activity.removed, bugs_activity.added, profiles.login_name,
+ bugs_activity.comment_id
FROM bugs_activity
$suppjoins
LEFT JOIN fielddefs
@@ -2931,7 +3734,7 @@
my $incomplete_data = 0;
foreach my $entry (@$list) {
- my ($field, $fieldname, $attachid, $when, $removed, $added, $who) = @$entry;
+ my ($fieldname, $attachid, $when, $removed, $added, $who, $comment_id) = @$entry;
my %change;
my $activity_visible = 1;
@@ -2941,18 +3744,24 @@
|| $fieldname eq 'work_time'
|| $fieldname eq 'deadline')
{
- $activity_visible =
- Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'}) ? 1 : 0;
- } else {
+ $activity_visible = Bugzilla->user->is_timetracker;
+ }
+ elsif ($fieldname eq 'longdescs.isprivate'
+ && !Bugzilla->user->is_insider
+ && $added)
+ {
+ $activity_visible = 0;
+ }
+ else {
$activity_visible = 1;
}
if ($activity_visible) {
- # This gets replaced with a hyperlink in the template.
- $field =~ s/^Attachment\s*// if $attachid;
-
# Check for the results of an old Bugzilla data corruption bug
- $incomplete_data = 1 if ($added =~ /^\?/ || $removed =~ /^\?/);
+ if (($added eq '?' && $removed eq '?')
+ || ($added =~ /^\? / || $removed =~ /^\? /)) {
+ $incomplete_data = 1;
+ }
# An operation, done by 'who' at time 'when', has a number of
# 'changes' associated with it.
@@ -2973,11 +3782,15 @@
$operation->{'who'} = $who;
$operation->{'when'} = $when;
- $change{'field'} = $field;
$change{'fieldname'} = $fieldname;
$change{'attachid'} = $attachid;
$change{'removed'} = $removed;
$change{'added'} = $added;
+
+ if ($comment_id) {
+ $change{'comment'} = Bugzilla::Comment->new($comment_id);
+ }
+
push (@$changes, \%change);
}
}
@@ -2992,7 +3805,7 @@
# Update the bugs_activity table to reflect changes made in bugs.
sub LogActivityEntry {
- my ($i, $col, $removed, $added, $whoid, $timestamp) = @_;
+ my ($i, $col, $removed, $added, $whoid, $timestamp, $comment_id) = @_;
my $dbh = Bugzilla->dbh;
# in the case of CCs, deps, and keywords, there's a possibility that someone
# might try to add or remove a lot of them at once, which might take more
@@ -3020,171 +3833,29 @@
trick_taint($removestr);
my $fieldid = get_field_id($col);
$dbh->do("INSERT INTO bugs_activity
- (bug_id, who, bug_when, fieldid, removed, added)
- VALUES (?, ?, ?, ?, ?, ?)",
- undef, ($i, $whoid, $timestamp, $fieldid, $removestr, $addstr));
+ (bug_id, who, bug_when, fieldid, removed, added, comment_id)
+ VALUES (?, ?, ?, ?, ?, ?, ?)",
+ undef, ($i, $whoid, $timestamp, $fieldid, $removestr, $addstr, $comment_id));
}
}
-# CountOpenDependencies counts the number of open dependent bugs for a
-# list of bugs and returns a list of bug_id's and their dependency count
-# It takes one parameter:
-# - A list of bug numbers whose dependencies are to be checked
-sub CountOpenDependencies {
- my (@bug_list) = @_;
- my @dependencies;
- my $dbh = Bugzilla->dbh;
+# Convert WebService API and email_in.pl field names to internal DB field
+# names.
+sub map_fields {
+ my ($params, $except) = @_;
- my $sth = $dbh->prepare(
- "SELECT blocked, COUNT(bug_status) " .
- "FROM bugs, dependencies " .
- "WHERE " . $dbh->sql_in('blocked', \@bug_list) .
- "AND bug_id = dependson " .
- "AND bug_status IN (" . join(', ', map {$dbh->quote($_)} BUG_STATE_OPEN) . ") " .
- $dbh->sql_group_by('blocked'));
- $sth->execute();
-
- while (my ($bug_id, $dependencies) = $sth->fetchrow_array()) {
- push(@dependencies, { bug_id => $bug_id,
- dependencies => $dependencies });
- }
-
- return @dependencies;
-}
-
-# If a bug is moved to a product which allows less votes per bug
-# compared to the previous product, extra votes need to be removed.
-sub RemoveVotes {
- my ($id, $who, $reason) = (@_);
- my $dbh = Bugzilla->dbh;
-
- my $whopart = ($who) ? " AND votes.who = $who" : "";
-
- my $sth = $dbh->prepare("SELECT profiles.login_name, " .
- "profiles.userid, votes.vote_count, " .
- "products.votesperuser, products.maxvotesperbug " .
- "FROM profiles " .
- "LEFT JOIN votes ON profiles.userid = votes.who " .
- "LEFT JOIN bugs ON votes.bug_id = bugs.bug_id " .
- "LEFT JOIN products ON products.id = bugs.product_id " .
- "WHERE votes.bug_id = ? " . $whopart);
- $sth->execute($id);
- my @list;
- while (my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = $sth->fetchrow_array()) {
- push(@list, [$name, $userid, $oldvotes, $votesperuser, $maxvotesperbug]);
- }
-
- # @messages stores all emails which have to be sent, if any.
- # This array is passed to the caller which will send these emails itself.
- my @messages = ();
-
- if (scalar(@list)) {
- foreach my $ref (@list) {
- my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = (@$ref);
-
- $maxvotesperbug = min($votesperuser, $maxvotesperbug);
-
- # If this product allows voting and the user's votes are in
- # the acceptable range, then don't do anything.
- next if $votesperuser && $oldvotes <= $maxvotesperbug;
-
- # If the user has more votes on this bug than this product
- # allows, then reduce the number of votes so it fits
- my $newvotes = $maxvotesperbug;
-
- my $removedvotes = $oldvotes - $newvotes;
-
- if ($newvotes) {
- $dbh->do("UPDATE votes SET vote_count = ? " .
- "WHERE bug_id = ? AND who = ?",
- undef, ($newvotes, $id, $userid));
- } else {
- $dbh->do("DELETE FROM votes WHERE bug_id = ? AND who = ?",
- undef, ($id, $userid));
- }
-
- # Notice that we did not make sure that the user fit within the $votesperuser
- # range. This is considered to be an acceptable alternative to losing votes
- # during product moves. Then next time the user attempts to change their votes,
- # they will be forced to fit within the $votesperuser limit.
-
- # Now lets send the e-mail to alert the user to the fact that their votes have
- # been reduced or removed.
- my $vars = {
- 'to' => $name . Bugzilla->params->{'emailsuffix'},
- 'bugid' => $id,
- 'reason' => $reason,
-
- 'votesremoved' => $removedvotes,
- 'votesold' => $oldvotes,
- 'votesnew' => $newvotes,
- };
-
- my $voter = new Bugzilla::User($userid);
- my $template = Bugzilla->template_inner($voter->settings->{'lang'}->{'value'});
-
- my $msg;
- $template->process("email/votes-removed.txt.tmpl", $vars, \$msg);
- push(@messages, $msg);
- }
- Bugzilla->template_inner("");
-
- my $votes = $dbh->selectrow_array("SELECT SUM(vote_count) " .
- "FROM votes WHERE bug_id = ?",
- undef, $id) || 0;
- $dbh->do("UPDATE bugs SET votes = ? WHERE bug_id = ?",
- undef, ($votes, $id));
- }
- # Now return the array containing emails to be sent.
- return \@messages;
-}
-
-# If a user votes for a bug, or the number of votes required to
-# confirm a bug has been reduced, check if the bug is now confirmed.
-sub CheckIfVotedConfirmed {
- my ($id, $who) = (@_);
- my $dbh = Bugzilla->dbh;
-
- # XXX - Use bug methods to update the bug status and everconfirmed.
- my $bug = new Bugzilla::Bug($id);
-
- my ($votes, $status, $everconfirmed, $votestoconfirm, $timestamp) =
- $dbh->selectrow_array("SELECT votes, bug_status, everconfirmed, " .
- " votestoconfirm, NOW() " .
- "FROM bugs INNER JOIN products " .
- " ON products.id = bugs.product_id " .
- "WHERE bugs.bug_id = ?",
- undef, $id);
-
- my $ret = 0;
- if ($votes >= $votestoconfirm && !$everconfirmed) {
- $bug->add_comment('', { type => CMT_POPULAR_VOTES });
- $bug->update();
-
- if ($status eq 'UNCONFIRMED') {
- my $fieldid = get_field_id("bug_status");
- $dbh->do("UPDATE bugs SET bug_status = 'NEW', everconfirmed = 1, " .
- "delta_ts = ? WHERE bug_id = ?",
- undef, ($timestamp, $id));
- $dbh->do("INSERT INTO bugs_activity " .
- "(bug_id, who, bug_when, fieldid, removed, added) " .
- "VALUES (?, ?, ?, ?, ?, ?)",
- undef, ($id, $who, $timestamp, $fieldid, 'UNCONFIRMED', 'NEW'));
+ my %field_values;
+ foreach my $field (keys %$params) {
+ my $field_name;
+ if ($except->{$field}) {
+ $field_name = $field;
}
else {
- $dbh->do("UPDATE bugs SET everconfirmed = 1, delta_ts = ? " .
- "WHERE bug_id = ?", undef, ($timestamp, $id));
+ $field_name = FIELD_MAP->{$field} || $field;
}
-
- my $fieldid = get_field_id("everconfirmed");
- $dbh->do("INSERT INTO bugs_activity " .
- "(bug_id, who, bug_when, fieldid, removed, added) " .
- "VALUES (?, ?, ?, ?, ?, ?)",
- undef, ($id, $who, $timestamp, $fieldid, '0', '1'));
-
- $ret = 1;
+ $field_values{$field_name} = $params->{$field};
}
- return $ret;
+ return \%field_values;
}
################################################################################
@@ -3220,12 +3891,27 @@
} elsif (trim($oldvalue) eq trim($newvalue)) {
return 1;
# numeric fields need to be compared using ==
- } elsif (($field eq 'estimated_time' || $field eq 'remaining_time')
+ } elsif (($field eq 'estimated_time' || $field eq 'remaining_time'
+ || $field eq 'work_time')
&& $oldvalue == $newvalue)
{
return 1;
}
+ my @priv_results;
+ Bugzilla::Hook::process('bug_check_can_change_field',
+ { bug => $self, field => $field,
+ new_value => $newvalue, old_value => $oldvalue,
+ priv_results => \@priv_results });
+ if (my $priv_required = first { $_ > 0 } @priv_results) {
+ $$PrivilegesRequired = $priv_required;
+ return 0;
+ }
+ my $allow_found = first { $_ == 0 } @priv_results;
+ if (defined $allow_found) {
+ return 1;
+ }
+
# Allow anyone to change comments.
if ($field =~ /^longdesc/) {
return 1;
@@ -3235,16 +3921,15 @@
# We store the required permission set into the $PrivilegesRequired
# variable which gets passed to the error template.
#
- # $PrivilegesRequired = 0 : no privileges required;
- # $PrivilegesRequired = 1 : the reporter, assignee or an empowered user;
- # $PrivilegesRequired = 2 : the assignee or an empowered user;
- # $PrivilegesRequired = 3 : an empowered user.
+ # $PrivilegesRequired = PRIVILEGES_REQUIRED_NONE : no privileges required;
+ # $PrivilegesRequired = PRIVILEGES_REQUIRED_REPORTER : the reporter, assignee or an empowered user;
+ # $PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE : the assignee or an empowered user;
+ # $PrivilegesRequired = PRIVILEGES_REQUIRED_EMPOWERED : an empowered user.
# Only users in the time-tracking group can change time-tracking fields.
- if ( grep($_ eq $field, qw(deadline estimated_time remaining_time)) ) {
- my $tt_group = Bugzilla->params->{timetrackinggroup};
- if (!$tt_group || !$user->in_group($tt_group)) {
- $$PrivilegesRequired = 3;
+ if ( grep($_ eq $field, TIMETRACKING_FIELDS) ) {
+ if (!$user->is_timetracker) {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_EMPOWERED;
return 0;
}
}
@@ -3255,12 +3940,8 @@
}
# *Only* users with (product-specific) "canconfirm" privs can confirm bugs.
- if ($field eq 'canconfirm'
- || ($field eq 'bug_status'
- && $oldvalue eq 'UNCONFIRMED'
- && is_open_state($newvalue)))
- {
- $$PrivilegesRequired = 3;
+ if ($self->_changes_everconfirmed($field, $oldvalue, $newvalue)) {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_EMPOWERED;
return $user->in_group('canconfirm', $self->{'product_id'});
}
@@ -3291,26 +3972,38 @@
# in that case we will have already returned 1 above
# when checking for the assignee of the bug.
if ($field eq 'assigned_to') {
- $$PrivilegesRequired = 2;
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
return 0;
}
# - change the QA contact
if ($field eq 'qa_contact') {
- $$PrivilegesRequired = 2;
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
return 0;
}
# - change the target milestone
if ($field eq 'target_milestone') {
- $$PrivilegesRequired = 2;
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
return 0;
}
# - change the priority (unless he could have set it originally)
if ($field eq 'priority'
&& !Bugzilla->params->{'letsubmitterchoosepriority'})
{
- $$PrivilegesRequired = 2;
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
return 0;
}
+ # - unconfirm bugs (confirming them is handled above)
+ if ($field eq 'everconfirmed') {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
+ return 0;
+ }
+ # - change the status from one open state to another
+ if ($field eq 'bug_status'
+ && is_open_state($oldvalue) && is_open_state($newvalue))
+ {
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_ASSIGNEE;
+ return 0;
+ }
# The reporter is allowed to change anything else.
if (!$self->{'error'} && $self->{'reporter_id'} == $user->id) {
@@ -3319,7 +4012,25 @@
# If we haven't returned by this point, then the user doesn't
# have the necessary permissions to change this field.
- $$PrivilegesRequired = 1;
+ $$PrivilegesRequired = PRIVILEGES_REQUIRED_REPORTER;
+ return 0;
+}
+
+# A helper for check_can_change_field
+sub _changes_everconfirmed {
+ my ($self, $field, $old, $new) = @_;
+ return 1 if $field eq 'everconfirmed';
+ if ($field eq 'bug_status') {
+ if ($self->everconfirmed) {
+ # Moving a confirmed bug to UNCONFIRMED will change everconfirmed.
+ return 1 if $new eq 'UNCONFIRMED';
+ }
+ else {
+ # Moving an unconfirmed bug to an open state that isn't
+ # UNCONFIRMED will confirm the bug.
+ return 1 if (is_open_state($new) and $new ne 'UNCONFIRMED');
+ }
+ }
return 0;
}
@@ -3327,59 +4038,6 @@
# Field Validation
#
-# Validates and verifies a bug ID, making sure the number is a
-# positive integer, that it represents an existing bug in the
-# database, and that the user is authorized to access that bug.
-# We detaint the number here, too.
-sub ValidateBugID {
- my ($id, $field) = @_;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- ThrowUserError('improper_bug_id_field_value', { field => $field }) unless defined $id;
-
- # Get rid of leading '#' (number) mark, if present.
- $id =~ s/^\s*#//;
- # Remove whitespace
- $id = trim($id);
-
- # If the ID isn't a number, it might be an alias, so try to convert it.
- my $alias = $id;
- if (!detaint_natural($id)) {
- $id = bug_alias_to_id($alias);
- $id || ThrowUserError("improper_bug_id_field_value",
- {'bug_id' => $alias,
- 'field' => $field });
- }
-
- # Modify the calling code's original variable to contain the trimmed,
- # converted-from-alias ID.
- $_[0] = $id;
-
- # First check that the bug exists
- $dbh->selectrow_array("SELECT bug_id FROM bugs WHERE bug_id = ?", undef, $id)
- || ThrowUserError("bug_id_does_not_exist", {'bug_id' => $id});
-
- unless ($field && $field =~ /^(dependson|blocked|dup_id)$/) {
- check_is_visible($id);
- }
-}
-
-sub check_is_visible {
- my $id = shift;
- my $user = Bugzilla->user;
-
- return if $user->can_see_bug($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' => $id});
- } else {
- ThrowUserError("bug_access_query", {'bug_id' => $id});
- }
-}
-
# Validate and return a hash of dependencies
sub ValidateDependencies {
my $fields = {};
@@ -3451,67 +4109,55 @@
#####################################################################
-# Autoloaded Accessors
+# Custom Field Accessors
#####################################################################
-# Determines whether an attribute access trapped by the AUTOLOAD function
-# is for a valid bug attribute. Bug attributes are properties and methods
-# predefined by this module as well as bug fields for which an accessor
-# can be defined by AUTOLOAD at runtime when the accessor is first accessed.
-#
-# XXX Strangely, some predefined attributes are on the list, but others aren't,
-# and the original code didn't specify why that is. Presumably the only
-# attributes that need to be on this list are those that aren't predefined;
-# we should verify that and update the list accordingly.
-#
-sub _validate_attribute {
- my ($attribute) = @_;
+sub _create_cf_accessors {
+ my ($invocant) = @_;
+ my $class = ref($invocant) || $invocant;
+ return if Bugzilla->request_cache->{"${class}_cf_accessors_created"};
- my @valid_attributes = (
- # Miscellaneous properties and methods.
- qw(error groups product_id component_id
- longdescs milestoneurl attachments
- isopened isunconfirmed
- flag_types num_attachment_flag_types
- show_attachment_flags any_flags_requesteeble),
+ my $fields = Bugzilla->fields({ custom => 1 });
+ foreach my $field (@$fields) {
+ my $accessor = $class->_accessor_for($field);
+ my $name = "${class}::" . $field->name;
+ {
+ no strict 'refs';
+ next if defined *{$name};
+ *{$name} = $accessor;
+ }
+ }
- # Bug fields.
- Bugzilla::Bug->fields
- );
-
- return grep($attribute eq $_, @valid_attributes) ? 1 : 0;
+ Bugzilla->request_cache->{"${class}_cf_accessors_created"} = 1;
}
-sub AUTOLOAD {
- use vars qw($AUTOLOAD);
- my $attr = $AUTOLOAD;
+sub _accessor_for {
+ my ($class, $field) = @_;
+ if ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ return $class->_multi_select_accessor($field->name);
+ }
+ return $class->_cf_accessor($field->name);
+}
- $attr =~ s/.*:://;
- return unless $attr=~ /[^A-Z]/;
- if (!_validate_attribute($attr)) {
- require Carp;
- Carp::confess("invalid bug attribute $attr");
- }
+sub _cf_accessor {
+ my ($class, $field) = @_;
+ my $accessor = sub {
+ my ($self) = @_;
+ return $self->{$field};
+ };
+ return $accessor;
+}
- no strict 'refs';
- *$AUTOLOAD = sub {
- my $self = shift;
-
- return $self->{$attr} if defined $self->{$attr};
-
- $self->{_multi_selects} ||= [Bugzilla->get_fields(
- {custom => 1, type => FIELD_TYPE_MULTI_SELECT })];
- if ( grep($_->name eq $attr, @{$self->{_multi_selects}}) ) {
- $self->{$attr} ||= Bugzilla->dbh->selectcol_arrayref(
- "SELECT value FROM bug_$attr WHERE bug_id = ? ORDER BY value",
- undef, $self->id);
- return $self->{$attr};
- }
-
- return '';
- };
-
- goto &$AUTOLOAD;
+sub _multi_select_accessor {
+ my ($class, $field) = @_;
+ my $accessor = sub {
+ my ($self) = @_;
+ $self->{$field} ||= Bugzilla->dbh->selectcol_arrayref(
+ "SELECT value FROM bug_$field WHERE bug_id = ? ORDER BY value",
+ undef, $self->id);
+ return $self->{$field};
+ };
+ return $accessor;
}
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugMail.pm b/Websites/bugs.webkit.org/Bugzilla/BugMail.pm
index 6f78bc2..55eeeab 100644
--- a/Websites/bugs.webkit.org/Bugzilla/BugMail.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/BugMail.pm
@@ -27,6 +27,9 @@
# J. Paul Reed <preed@sigkill.com>
# Gervase Markham <gerv@gerv.net>
# Byron Jones <bugzilla@glob.com.au>
+# Reed Loden <reed@reedloden.com>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Guy Pyrzak <guy.pyrzak@gmail.com>
use strict;
@@ -37,66 +40,25 @@
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Bug;
-use Bugzilla::Classification;
-use Bugzilla::Product;
-use Bugzilla::Component;
-use Bugzilla::Status;
+use Bugzilla::Comment;
use Bugzilla::Mailer;
+use Bugzilla::Hook;
use Date::Parse;
use Date::Format;
-
-use constant FORMAT_TRIPLE => "%19s|%-28s|%-28s";
-use constant FORMAT_3_SIZE => [19,28,28];
-use constant FORMAT_DOUBLE => "%19s %-55s";
-use constant FORMAT_2_SIZE => [19,55];
+use Scalar::Util qw(blessed);
+use List::MoreUtils qw(uniq);
use constant BIT_DIRECT => 1;
use constant BIT_WATCHING => 2;
-# We need these strings for the X-Bugzilla-Reasons header
-# Note: this hash uses "," rather than "=>" to avoid auto-quoting of the LHS.
-use constant REL_NAMES => {
- REL_ASSIGNEE , "AssignedTo",
- REL_REPORTER , "Reporter",
- REL_QA , "QAcontact",
- REL_CC , "CC",
- REL_VOTER , "Voter",
- REL_GLOBAL_WATCHER, "GlobalWatcher"
-};
-
-# We use this instead of format because format doesn't deal well with
-# multi-byte languages.
-sub multiline_sprintf {
- my ($format, $args, $sizes) = @_;
- my @parts;
- my @my_sizes = @$sizes; # Copy this so we don't modify the input array.
- foreach my $string (@$args) {
- my $size = shift @my_sizes;
- my @pieces = split("\n", wrap_hard($string, $size));
- push(@parts, \@pieces);
- }
-
- my $formatted;
- while (1) {
- # Get the first item of each part.
- my @line = map { shift @$_ } @parts;
- # If they're all undef, we're done.
- last if !grep { defined $_ } @line;
- # Make any single undef item into ''
- @line = map { defined $_ ? $_ : '' } @line;
- # And append a formatted line
- $formatted .= sprintf($format, @line);
- # Remove trailing spaces, or they become lots of =20's in
- # quoted-printable emails.
- $formatted =~ s/\s+$//;
- $formatted .= "\n";
- }
- return $formatted;
-}
-
-sub three_columns {
- return multiline_sprintf(FORMAT_TRIPLE, \@_, FORMAT_3_SIZE);
+sub relationships {
+ my $ref = RELATIONSHIPS;
+ # Clone it so that we don't modify the constant;
+ my %relationships = %$ref;
+ Bugzilla::Hook::process('bugmail_relationships',
+ { relationships => \%relationships });
+ return %relationships;
}
# This is a bit of a hack, basically keeping the old system()
@@ -108,247 +70,66 @@
# roles when the email is sent.
# All the names are email addresses, not userids
# values are scalars, except for cc, which is a list
-# This hash usually comes from the "mailrecipients" var in a template call.
sub Send {
- my ($id, $forced) = (@_);
-
- my @headerlist;
- my %defmailhead;
- my %fielddescription;
-
- my $msg = "";
+ my ($id, $forced, $params) = @_;
+ $params ||= {};
my $dbh = Bugzilla->dbh;
+ my $bug = new Bugzilla::Bug($id);
- # XXX - These variables below are useless. We could use field object
- # methods directly. But we first have to implement a cache in
- # Bugzilla->get_fields to avoid querying the DB all the time.
- foreach my $field (Bugzilla->get_fields({obsolete => 0})) {
- push(@headerlist, $field->name);
- $defmailhead{$field->name} = $field->in_new_bugmail;
- $fielddescription{$field->name} = $field->description;
- }
+ my $start = $bug->lastdiffed;
+ my $end = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- my %values = %{$dbh->selectrow_hashref(
- 'SELECT ' . join(',', editable_bug_fields()) . ', reporter,
- lastdiffed AS start_time, LOCALTIMESTAMP(0) AS end_time
- FROM bugs WHERE bug_id = ?',
- undef, $id)};
+ # Bugzilla::User objects of people in various roles. More than one person
+ # can 'have' a role, if the person in that role has changed, or people are
+ # watching.
+ my @assignees = ($bug->assigned_to);
+ my @qa_contacts = $bug->qa_contact || ();
- my $product = new Bugzilla::Product($values{product_id});
- $values{product} = $product->name;
- if (Bugzilla->params->{'useclassification'}) {
- $values{classification} = Bugzilla::Classification->new($product->classification_id)->name;
- }
- my $component = new Bugzilla::Component($values{component_id});
- $values{component} = $component->name;
-
- my ($start, $end) = ($values{start_time}, $values{end_time});
-
- # User IDs of people in various roles. More than one person can 'have' a
- # role, if the person in that role has changed, or people are watching.
- my $reporter = $values{'reporter'};
- my @assignees = ($values{'assigned_to'});
- my @qa_contacts = ($values{'qa_contact'});
-
- my $cc_users = $dbh->selectall_arrayref(
- "SELECT cc.who, profiles.login_name
- FROM cc
- INNER JOIN profiles
- ON cc.who = profiles.userid
- WHERE bug_id = ?",
- undef, $id);
-
- my (@ccs, @cc_login_names);
- foreach my $cc_user (@$cc_users) {
- my ($user_id, $user_login) = @$cc_user;
- push (@ccs, $user_id);
- push (@cc_login_names, $user_login);
- }
-
+ my @ccs = @{ $bug->cc_users };
# Include the people passed in as being in particular roles.
# This can include people who used to hold those roles.
# At this point, we don't care if there are duplicates in these arrays.
my $changer = $forced->{'changer'};
if ($forced->{'owner'}) {
- push (@assignees, login_to_id($forced->{'owner'}, THROW_ERROR));
+ push (@assignees, Bugzilla::User->check($forced->{'owner'}));
}
if ($forced->{'qacontact'}) {
- push (@qa_contacts, login_to_id($forced->{'qacontact'}, THROW_ERROR));
+ push (@qa_contacts, Bugzilla::User->check($forced->{'qacontact'}));
}
if ($forced->{'cc'}) {
foreach my $cc (@{$forced->{'cc'}}) {
- push(@ccs, login_to_id($cc, THROW_ERROR));
+ push(@ccs, Bugzilla::User->check($cc));
}
}
-
- # Convert to names, for later display
- $values{'changer'} = $changer;
- # If no changer is specified, then it has no name.
- if ($changer) {
- $values{'changername'} = Bugzilla::User->new({name => $changer})->name;
- }
- $values{'assigned_to'} = user_id_to_login($values{'assigned_to'});
- $values{'reporter'} = user_id_to_login($values{'reporter'});
- if ($values{'qa_contact'}) {
- $values{'qa_contact'} = user_id_to_login($values{'qa_contact'});
- }
- $values{'cc'} = join(', ', @cc_login_names);
- $values{'estimated_time'} = format_time_decimal($values{'estimated_time'});
+ my %user_cache = map { $_->id => $_ } (@assignees, @qa_contacts, @ccs);
- if ($values{'deadline'}) {
- $values{'deadline'} = time2str("%Y-%m-%d", str2time($values{'deadline'}));
+ my @diffs;
+ if (!$start) {
+ @diffs = _get_new_bugmail_fields($bug);
}
- my $dependslist = $dbh->selectcol_arrayref(
- 'SELECT dependson FROM dependencies
- WHERE blocked = ? ORDER BY dependson',
- undef, ($id));
-
- $values{'dependson'} = join(",", @$dependslist);
-
- my $blockedlist = $dbh->selectcol_arrayref(
- 'SELECT blocked FROM dependencies
- WHERE dependson = ? ORDER BY blocked',
- undef, ($id));
-
- $values{'blocked'} = join(",", @$blockedlist);
-
- my @args = ($id);
-
- # If lastdiffed is NULL, then we don't limit the search on time.
- my $when_restriction = '';
- if ($start) {
- $when_restriction = ' AND bug_when > ? AND bug_when <= ?';
- push @args, ($start, $end);
+ if ($params->{dep_only}) {
+ push(@diffs, { field_name => 'bug_status',
+ old => $params->{changes}->{bug_status}->[0],
+ new => $params->{changes}->{bug_status}->[1],
+ login_name => $changer->login,
+ blocker => $params->{blocker} },
+ { field_name => 'resolution',
+ old => $params->{changes}->{resolution}->[0],
+ new => $params->{changes}->{resolution}->[1],
+ login_name => $changer->login,
+ blocker => $params->{blocker} });
}
-
- my $diffs = $dbh->selectall_arrayref(
- "SELECT profiles.login_name, profiles.realname, fielddefs.description,
- bugs_activity.bug_when, bugs_activity.removed,
- bugs_activity.added, bugs_activity.attach_id, fielddefs.name
- FROM bugs_activity
- INNER JOIN fielddefs
- ON fielddefs.id = bugs_activity.fieldid
- INNER JOIN profiles
- ON profiles.userid = bugs_activity.who
- WHERE bugs_activity.bug_id = ?
- $when_restriction
- ORDER BY bugs_activity.bug_when", undef, @args);
-
- my @new_depbugs;
- my $difftext = "";
- my $diffheader = "";
- my @diffparts;
- my $lastwho = "";
- my $fullwho;
- my @changedfields;
- foreach my $ref (@$diffs) {
- my ($who, $whoname, $what, $when, $old, $new, $attachid, $fieldname) = (@$ref);
- my $diffpart = {};
- if ($who ne $lastwho) {
- $lastwho = $who;
- $fullwho = $whoname ? "$whoname <$who>" : $who;
- $diffheader = "\n$fullwho changed:\n\n";
- $diffheader .= three_columns("What ", "Removed", "Added");
- $diffheader .= ('-' x 76) . "\n";
- }
- $what =~ s/^(Attachment )?/Attachment #$attachid / if $attachid;
- if( $fieldname eq 'estimated_time' ||
- $fieldname eq 'remaining_time' ) {
- $old = format_time_decimal($old);
- $new = format_time_decimal($new);
- }
- if ($fieldname eq 'dependson') {
- push(@new_depbugs, grep {$_ =~ /^\d+$/} split(/[\s,]+/, $new));
- }
- if ($attachid) {
- ($diffpart->{'isprivate'}) = $dbh->selectrow_array(
- 'SELECT isprivate FROM attachments WHERE attach_id = ?',
- undef, ($attachid));
- }
- $difftext = three_columns($what, $old, $new);
- $diffpart->{'header'} = $diffheader;
- $diffpart->{'fieldname'} = $fieldname;
- $diffpart->{'text'} = $difftext;
- push(@diffparts, $diffpart);
- push(@changedfields, $what);
- }
- $values{'changed_fields'} = join(' ', @changedfields);
-
- my @depbugs;
- my $deptext = "";
- # Do not include data about dependent bugs when they have just been added.
- # Completely skip checking for dependent bugs on bug creation as all
- # dependencies bugs will just have been added.
- if ($start) {
- my $dep_restriction = "";
- if (scalar @new_depbugs) {
- $dep_restriction = "AND bugs_activity.bug_id NOT IN (" .
- join(", ", @new_depbugs) . ")";
- }
-
- my $dependency_diffs = $dbh->selectall_arrayref(
- "SELECT bugs_activity.bug_id, bugs.short_desc, fielddefs.name,
- bugs_activity.removed, bugs_activity.added
- FROM bugs_activity
- INNER JOIN bugs
- ON bugs.bug_id = bugs_activity.bug_id
- INNER JOIN dependencies
- ON bugs_activity.bug_id = dependencies.dependson
- INNER JOIN fielddefs
- ON fielddefs.id = bugs_activity.fieldid
- WHERE dependencies.blocked = ?
- AND (fielddefs.name = 'bug_status'
- OR fielddefs.name = 'resolution')
- $when_restriction
- $dep_restriction
- ORDER BY bugs_activity.bug_when, bugs.bug_id", undef, @args);
-
- my $thisdiff = "";
- my $lastbug = "";
- my $interestingchange = 0;
- foreach my $dependency_diff (@$dependency_diffs) {
- my ($depbug, $summary, $what, $old, $new) = @$dependency_diff;
-
- if ($depbug ne $lastbug) {
- if ($interestingchange) {
- $deptext .= $thisdiff;
- }
- $lastbug = $depbug;
- my $urlbase = Bugzilla->params->{"urlbase"};
- $thisdiff =
- "\nBug $id depends on bug $depbug, which changed state.\n\n" .
- "Bug $depbug Summary: $summary\n" .
- "${urlbase}show_bug.cgi?id=$depbug\n\n";
- $thisdiff .= three_columns("What ", "Old Value", "New Value");
- $thisdiff .= ('-' x 76) . "\n";
- $interestingchange = 0;
- }
- $thisdiff .= three_columns($fielddescription{$what}, $old, $new);
- if ($what eq 'bug_status'
- && is_open_state($old) ne is_open_state($new))
- {
- $interestingchange = 1;
- }
- push(@depbugs, $depbug);
- }
-
- if ($interestingchange) {
- $deptext .= $thisdiff;
- }
- $deptext = trim($deptext);
-
- if ($deptext) {
- my $diffpart = {};
- $diffpart->{'text'} = "\n" . trim("\n\n" . $deptext);
- push(@diffparts, $diffpart);
- }
+ else {
+ push(@diffs, _get_diffs($bug, $end, \%user_cache));
}
- my ($raw_comments, $anyprivate, $count) = get_comments_by_bug($id, $start, $end);
+ my $comments = $bug->comments({ after => $start, to => $end });
+ # Skip empty comments.
+ @$comments = grep { $_->type || $_->body =~ /\S/ } @$comments;
###########################################################################
# Start of email filtering code
@@ -362,71 +143,71 @@
# the relationships in a hash. The keys are userids, the values are an
# array of role constants.
- # Voters
- my $voters = $dbh->selectcol_arrayref(
- "SELECT who FROM votes WHERE bug_id = ?", undef, ($id));
-
- $recipients{$_}->{+REL_VOTER} = BIT_DIRECT foreach (@$voters);
-
# CCs
- $recipients{$_}->{+REL_CC} = BIT_DIRECT foreach (@ccs);
+ $recipients{$_->id}->{+REL_CC} = BIT_DIRECT foreach (@ccs);
# Reporter (there's only ever one)
- $recipients{$reporter}->{+REL_REPORTER} = BIT_DIRECT;
+ $recipients{$bug->reporter->id}->{+REL_REPORTER} = BIT_DIRECT;
# QA Contact
if (Bugzilla->params->{'useqacontact'}) {
foreach (@qa_contacts) {
# QA Contact can be blank; ignore it if so.
- $recipients{$_}->{+REL_QA} = BIT_DIRECT if $_;
+ $recipients{$_->id}->{+REL_QA} = BIT_DIRECT if $_;
}
}
# Assignee
- $recipients{$_}->{+REL_ASSIGNEE} = BIT_DIRECT foreach (@assignees);
+ $recipients{$_->id}->{+REL_ASSIGNEE} = BIT_DIRECT foreach (@assignees);
# The last relevant set of people are those who are being removed from
# their roles in this change. We get their names out of the diffs.
- foreach my $ref (@$diffs) {
- my ($who, $whoname, $what, $when, $old, $new) = (@$ref);
- if ($old) {
- # You can't stop being the reporter, and mail isn't sent if you
- # remove your vote.
+ foreach my $change (@diffs) {
+ if ($change->{old}) {
+ # You can't stop being the reporter, so we don't check that
+ # relationship here.
# Ignore people whose user account has been deleted or renamed.
- if ($what eq "CC") {
- foreach my $cc_user (split(/[\s,]+/, $old)) {
+ if ($change->{field_name} eq 'cc') {
+ foreach my $cc_user (split(/[\s,]+/, $change->{old})) {
my $uid = login_to_id($cc_user);
$recipients{$uid}->{+REL_CC} = BIT_DIRECT if $uid;
}
}
- elsif ($what eq "QAContact") {
- my $uid = login_to_id($old);
+ elsif ($change->{field_name} eq 'qa_contact') {
+ my $uid = login_to_id($change->{old});
$recipients{$uid}->{+REL_QA} = BIT_DIRECT if $uid;
}
- elsif ($what eq "AssignedTo") {
- my $uid = login_to_id($old);
+ elsif ($change->{field_name} eq 'assigned_to') {
+ my $uid = login_to_id($change->{old});
$recipients{$uid}->{+REL_ASSIGNEE} = BIT_DIRECT if $uid;
}
}
}
+
+ # Make sure %user_cache has every user in it so far referenced
+ foreach my $user_id (keys %recipients) {
+ $user_cache{$user_id} ||= new Bugzilla::User($user_id);
+ }
- if (Bugzilla->params->{"supportwatchers"}) {
- # Find all those user-watching anyone on the current list, who is not
- # on it already themselves.
- my $involved = join(",", keys %recipients);
+ Bugzilla::Hook::process('bugmail_recipients',
+ { bug => $bug, recipients => \%recipients,
+ users => \%user_cache, diffs => \@diffs });
- my $userwatchers =
- $dbh->selectall_arrayref("SELECT watcher, watched FROM watch
- WHERE watched IN ($involved)");
+ # Find all those user-watching anyone on the current list, who is not
+ # on it already themselves.
+ my $involved = join(",", keys %recipients);
- # Mark these people as having the role of the person they are watching
- foreach my $watch (@$userwatchers) {
- while (my ($role, $bits) = each %{$recipients{$watch->[1]}}) {
- $recipients{$watch->[0]}->{$role} |= BIT_WATCHING
- if $bits & BIT_DIRECT;
- }
- push (@{$watching{$watch->[0]}}, $watch->[1]);
+ my $userwatchers =
+ $dbh->selectall_arrayref("SELECT watcher, watched FROM watch
+ WHERE watched IN ($involved)");
+
+ # Mark these people as having the role of the person they are watching
+ foreach my $watch (@$userwatchers) {
+ while (my ($role, $bits) = each %{$recipients{$watch->[1]}}) {
+ $recipients{$watch->[0]}->{$role} |= BIT_WATCHING
+ if $bits & BIT_DIRECT;
}
+ push(@{$watching{$watch->[0]}}, $watch->[1]);
}
# Global watcher
@@ -443,38 +224,29 @@
my @sent;
my @excluded;
- # Some comments are language specific. We cache them here.
- my %comments;
+ # The email client will display the Date: header in the desired timezone,
+ # so we can always use UTC here.
+ my $date = $params->{dep_only} ? $end : $bug->delta_ts;
+ $date = format_time($date, '%a, %d %b %Y %T %z', 'UTC');
foreach my $user_id (keys %recipients) {
my %rels_which_want;
my $sent_mail = 0;
-
- my $user = new Bugzilla::User($user_id);
+ $user_cache{$user_id} ||= new Bugzilla::User($user_id);
+ my $user = $user_cache{$user_id};
# Deleted users must be excluded.
next unless $user;
- # What's the language chosen by this user for email?
- my $lang = $user->settings->{'lang'}->{'value'};
-
if ($user->can_see_bug($id)) {
- # It's time to format language specific comments.
- unless (exists $comments{$lang}) {
- Bugzilla->template_inner($lang);
- $comments{$lang} = prepare_comments($raw_comments, $count);
- Bugzilla->template_inner("");
- }
-
# Go through each role the user has and see if they want mail in
# that role.
foreach my $relationship (keys %{$recipients{$user_id}}) {
- if ($user->wants_bug_mail($id,
+ if ($user->wants_bug_mail($bug,
$relationship,
- $diffs,
- $comments{$lang},
- $deptext,
- $changer,
- !$start))
+ $start ? \@diffs : [],
+ $comments,
+ $params->{dep_only},
+ $changer))
{
$rels_which_want{$relationship} =
$recipients{$user_id}->{$relationship};
@@ -486,48 +258,31 @@
# So the user exists, can see the bug, and wants mail in at least
# one role. But do we want to send it to them?
- # If we are using insiders, and the comment is private, only send
- # to insiders
- my $insider_ok = 1;
- $insider_ok = 0 if (Bugzilla->params->{"insidergroup"} &&
- ($anyprivate != 0) &&
- (!$user->groups->{Bugzilla->params->{"insidergroup"}}));
-
- # We shouldn't send mail if this is a dependency mail (i.e. there
- # is something in @depbugs), and any of the depending bugs are not
- # visible to the user. This is to avoid leaking the summaries of
- # confidential bugs.
+ # We shouldn't send mail if this is a dependency mail and the
+ # depending bug is not visible to the user.
+ # This is to avoid leaking the summary of a confidential bug.
my $dep_ok = 1;
- foreach my $dep_id (@depbugs) {
- if (!$user->can_see_bug($dep_id)) {
- $dep_ok = 0;
- last;
- }
+ if ($params->{dep_only}) {
+ $dep_ok = $user->can_see_bug($params->{blocker}->id) ? 1 : 0;
}
- # Make sure the user isn't in the nomail list, and the insider and
- # dep checks passed.
- if ($user->email_enabled &&
- $insider_ok &&
- $dep_ok)
- {
+ # Make sure the user isn't in the nomail list, and the dep check passed.
+ if ($user->email_enabled && $dep_ok) {
# OK, OK, if we must. Email the user.
- $sent_mail = sendMail($user,
- \@headerlist,
- \%rels_which_want,
- \%values,
- \%defmailhead,
- \%fielddescription,
- \@diffparts,
- $comments{$lang},
- $anyprivate,
- ! $start,
- $id,
- exists $watching{$user_id} ?
- $watching{$user_id} : undef);
+ $sent_mail = sendMail(
+ { to => $user,
+ bug => $bug,
+ comments => $comments,
+ date => $date,
+ changer => $changer,
+ watchers => exists $watching{$user_id} ?
+ $watching{$user_id} : undef,
+ diffs => \@diffs,
+ rels_which_want => \%rels_which_want,
+ });
}
}
-
+
if ($sent_mail) {
push(@sent, $user->login);
}
@@ -535,102 +290,54 @@
push(@excluded, $user->login);
}
}
-
- $dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?',
- undef, ($end, $id));
+
+ # When sending bugmail about a blocker being reopened or resolved,
+ # we say nothing about changes in the bug being blocked, so we must
+ # not update lastdiffed in this case.
+ if (!$params->{dep_only}) {
+ $dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?',
+ undef, ($end, $id));
+ $bug->{lastdiffed} = $end;
+ }
return {'sent' => \@sent, 'excluded' => \@excluded};
}
sub sendMail {
- my ($user, $hlRef, $relRef, $valueRef, $dmhRef, $fdRef,
- $diffRef, $newcomments, $anyprivate, $isnew,
- $id, $watchingRef) = @_;
-
- my %values = %$valueRef;
- my @headerlist = @$hlRef;
- my %mailhead = %$dmhRef;
- my %fielddescription = %$fdRef;
- my @diffparts = @$diffRef;
+ my $params = shift;
- # Build difftext (the actions) by verifying the user should see them
- my $difftext = "";
- my $diffheader = "";
- my $add_diff;
+ my $user = $params->{to};
+ my $bug = $params->{bug};
+ my @send_comments = @{ $params->{comments} };
+ my $date = $params->{date};
+ my $changer = $params->{changer};
+ my $watchingRef = $params->{watchers};
+ my @diffs = @{ $params->{diffs} };
+ my $relRef = $params->{rels_which_want};
- foreach my $diff (@diffparts) {
- $add_diff = 0;
+ # Only display changes the user is allowed see.
+ my @display_diffs;
+
+ foreach my $diff (@diffs) {
+ my $add_diff = 0;
- if (exists($diff->{'fieldname'}) &&
- ($diff->{'fieldname'} eq 'estimated_time' ||
- $diff->{'fieldname'} eq 'remaining_time' ||
- $diff->{'fieldname'} eq 'work_time' ||
- $diff->{'fieldname'} eq 'deadline')){
- if ($user->groups->{Bugzilla->params->{"timetrackinggroup"}}) {
- $add_diff = 1;
- }
- } elsif (($diff->{'isprivate'})
- && Bugzilla->params->{'insidergroup'}
- && !($user->groups->{Bugzilla->params->{'insidergroup'}})
- ) {
- $add_diff = 0;
-#if WEBKIT_CHANGES
- # If the only thing we are modifying is the in-rietveld flag, don't
- # include this diff. If multiple flags are being modified,
- # the diff text will have a comma seperating it.
- # This will prevent mail from being sent.
- } elsif ($diff->{'text'} =~ /in-rietveld/ && !($diff->{'text'} =~ /,/)) {
- $add_diff = 0;
-#endif // WEBKIT_CHANGES
- } else {
+ if (grep { $_ eq $diff->{field_name} } TIMETRACKING_FIELDS) {
+ $add_diff = 1 if $user->is_timetracker;
+ }
+ elsif (!$diff->{isprivate} || $user->is_insider) {
$add_diff = 1;
}
-
- if ($add_diff) {
- if (exists($diff->{'header'}) &&
- ($diffheader ne $diff->{'header'})) {
- $diffheader = $diff->{'header'};
- $difftext .= $diffheader;
- }
- $difftext .= $diff->{'text'};
- }
+ push(@display_diffs, $diff) if $add_diff;
}
-
- if ($difftext eq "" && $newcomments eq "" && !$isnew) {
+
+ if (!$user->is_insider) {
+ @send_comments = grep { !$_->is_private } @send_comments;
+ }
+
+ if (!scalar(@display_diffs) && !scalar(@send_comments)) {
# Whoops, no differences!
return 0;
}
-
- # If an attachment was created, then add an URL. (Note: the 'g'lobal
- # replace should work with comments with multiple attachments.)
-
- if ( $newcomments =~ /Created an attachment \(/ ) {
-
- my $showattachurlbase =
- Bugzilla->params->{'urlbase'} . "attachment.cgi?id=";
-
- $newcomments =~ s/(Created an attachment \(id=([0-9]+)\))/$1\n --> \(${showattachurlbase}$2&action=review\)/g;
- }
-
- my $diffs = $difftext . "\n\n" . $newcomments;
- if ($isnew) {
- my $head = "";
- foreach my $f (@headerlist) {
- next unless $mailhead{$f};
- my $value = $values{$f};
- # If there isn't anything to show, don't include this header.
- next unless $value;
- # Only send estimated_time if it is enabled and the user is in the group.
- if (($f ne 'estimated_time' && $f ne 'deadline')
- || $user->groups->{Bugzilla->params->{'timetrackinggroup'}})
- {
- my $desc = $fielddescription{$f};
- $head .= multiline_sprintf(FORMAT_DOUBLE, ["$desc:", $value],
- FORMAT_2_SIZE);
- }
- }
- $diffs = $head . ($difftext ? "\n\n" : "") . $diffs;
- }
my (@reasons, @reasons_watch);
while (my ($relationship, $bits) = each %{$relRef}) {
@@ -638,100 +345,149 @@
push(@reasons_watch, $relationship) if ($bits & BIT_WATCHING);
}
- my @headerrel = map { REL_NAMES->{$_} } @reasons;
- my @watchingrel = map { REL_NAMES->{$_} } @reasons_watch;
+ my %relationships = relationships();
+ my @headerrel = map { $relationships{$_} } @reasons;
+ my @watchingrel = map { $relationships{$_} } @reasons_watch;
push(@headerrel, 'None') unless @headerrel;
push(@watchingrel, 'None') unless @watchingrel;
push @watchingrel, map { user_id_to_login($_) } @$watchingRef;
- my $threadingmarker = build_thread_marker($id, $user->id, $isnew);
-
my $vars = {
- isnew => $isnew,
- to => $user->email,
- bugid => $id,
- alias => Bugzilla->params->{'usebugaliases'} ? $values{'alias'} : "",
- classification => $values{'classification'},
- product => $values{'product'},
- comp => $values{'component'},
- keywords => $values{'keywords'},
- severity => $values{'bug_severity'},
- status => $values{'bug_status'},
- priority => $values{'priority'},
- assignedto => $values{'assigned_to'},
- assignedtoname => Bugzilla::User->new({name => $values{'assigned_to'}})->name,
- targetmilestone => $values{'target_milestone'},
- changedfields => $values{'changed_fields'},
- summary => $values{'short_desc'},
+ date => $date,
+ to_user => $user,
+ bug => $bug,
reasons => \@reasons,
reasons_watch => \@reasons_watch,
reasonsheader => join(" ", @headerrel),
reasonswatchheader => join(" ", @watchingrel),
- changer => $values{'changer'},
- changername => $values{'changername'},
- reporter => $values{'reporter'},
- reportername => Bugzilla::User->new({name => $values{'reporter'}})->name,
- diffs => $diffs,
- threadingmarker => $threadingmarker
+ changer => $changer,
+ diffs => \@display_diffs,
+ changedfields => [uniq map { $_->{field_name} } @display_diffs],
+ new_comments => \@send_comments,
+ threadingmarker => build_thread_marker($bug->id, $user->id, !$bug->lastdiffed),
};
-
- my $msg;
- my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
- $template->process("email/newchangedmail.txt.tmpl", $vars, \$msg)
- || ThrowTemplateError($template->error());
- Bugzilla->template_inner("");
-
+ my $msg = _generate_bugmail($user, $vars);
MessageToMTA($msg);
return 1;
}
-# Get bug comments for the given period.
-sub get_comments_by_bug {
- my ($id, $start, $end) = @_;
- my $dbh = Bugzilla->dbh;
+sub _generate_bugmail {
+ my ($user, $vars) = @_;
+ my $template = Bugzilla->template_inner($user->setting('lang'));
+ my ($msg_text, $msg_html, $msg_header);
+
+ $template->process("email/bugmail-header.txt.tmpl", $vars, \$msg_header)
+ || ThrowTemplateError($template->error());
+ $template->process("email/bugmail.txt.tmpl", $vars, \$msg_text)
+ || ThrowTemplateError($template->error());
- my $result = "";
- my $count = 0;
- my $anyprivate = 0;
-
- # $start will be undef for new bugs, and defined for pre-existing bugs.
- if ($start) {
- # If $start is not NULL, obtain the count-index
- # of this comment for the leading "Comment #xxx" line.
- $count = $dbh->selectrow_array('SELECT COUNT(*) FROM longdescs
- WHERE bug_id = ? AND bug_when <= ?',
- undef, ($id, $start));
+ my @parts = (
+ Email::MIME->create(
+ attributes => {
+ content_type => "text/plain",
+ },
+ body => $msg_text,
+ )
+ );
+ if ($user->setting('email_format') eq 'html') {
+ $template->process("email/bugmail.html.tmpl", $vars, \$msg_html)
+ || ThrowTemplateError($template->error());
+ push @parts, Email::MIME->create(
+ attributes => {
+ content_type => "text/html",
+ },
+ body => $msg_html,
+ );
}
- my $raw = 1; # Do not format comments which are not of type CMT_NORMAL.
- my $comments = Bugzilla::Bug::GetComments($id, "oldest_to_newest", $start, $end, $raw);
-
- if (Bugzilla->params->{'insidergroup'}) {
- $anyprivate = 1 if scalar(grep {$_->{'isprivate'} > 0} @$comments);
+ # TT trims the trailing newline, and threadingmarker may be ignored.
+ my $email = new Email::MIME("$msg_header\n");
+ if (scalar(@parts) == 1) {
+ $email->content_type_set($parts[0]->content_type);
+ } else {
+ $email->content_type_set('multipart/alternative');
}
-
- return ($comments, $anyprivate, $count);
+ $email->parts_set(\@parts);
+ return $email;
}
-# Prepare comments for the given language.
-sub prepare_comments {
- my ($raw_comments, $count) = @_;
+sub _get_diffs {
+ my ($bug, $end, $user_cache) = @_;
+ my $dbh = Bugzilla->dbh;
- my $result = "";
- foreach my $comment (@$raw_comments) {
- if ($count) {
- $result .= "\n\n--- Comment #$count from " . $comment->{'author'}->identity .
- " " . format_time($comment->{'time'}) . " ---\n";
- }
- # Format language specific comments. We don't update $comment->{'body'}
- # directly, otherwise it would grow everytime you call format_comment()
- # with a different language as some text may be appended to the existing one.
- my $body = Bugzilla::Bug::format_comment($comment);
- $result .= ($comment->{'already_wrapped'} ? $body : wrap_comment($body));
- $count++;
+ my @args = ($bug->id);
+ # If lastdiffed is NULL, then we don't limit the search on time.
+ my $when_restriction = '';
+ if ($bug->lastdiffed) {
+ $when_restriction = ' AND bug_when > ? AND bug_when <= ?';
+ push @args, ($bug->lastdiffed, $end);
}
- return $result;
+
+ my $diffs = $dbh->selectall_arrayref(
+ "SELECT fielddefs.name AS field_name,
+ bugs_activity.bug_when, bugs_activity.removed AS old,
+ bugs_activity.added AS new, bugs_activity.attach_id,
+ bugs_activity.comment_id, bugs_activity.who
+ FROM bugs_activity
+ INNER JOIN fielddefs
+ ON fielddefs.id = bugs_activity.fieldid
+ WHERE bugs_activity.bug_id = ?
+ $when_restriction
+ ORDER BY bugs_activity.bug_when", {Slice=>{}}, @args);
+
+ foreach my $diff (@$diffs) {
+ $user_cache->{$diff->{who}} ||= new Bugzilla::User($diff->{who});
+ $diff->{who} = $user_cache->{$diff->{who}};
+ if ($diff->{attach_id}) {
+ $diff->{isprivate} = $dbh->selectrow_array(
+ 'SELECT isprivate FROM attachments WHERE attach_id = ?',
+ undef, $diff->{attach_id});
+ }
+ if ($diff->{field_name} eq 'longdescs.isprivate') {
+ my $comment = Bugzilla::Comment->new($diff->{comment_id});
+ $diff->{num} = $comment->count;
+ $diff->{isprivate} = $diff->{new};
+ }
+ }
+
+ return @$diffs;
+}
+
+sub _get_new_bugmail_fields {
+ my $bug = shift;
+ my @fields = @{ Bugzilla->fields({obsolete => 0, in_new_bugmail => 1}) };
+ my @diffs;
+
+ foreach my $field (@fields) {
+ my $name = $field->name;
+ my $value = $bug->$name;
+
+ if (ref $value eq 'ARRAY') {
+ $value = join(', ', @$value);
+ }
+ elsif (blessed($value) && $value->isa('Bugzilla::User')) {
+ $value = $value->login;
+ }
+ elsif (blessed($value) && $value->isa('Bugzilla::Object')) {
+ $value = $value->name;
+ }
+ elsif ($name eq 'estimated_time') {
+ # "0.00" (which is what we get from the DB) is true,
+ # so we explicitly do a numerical comparison with 0.
+ $value = 0 if $value == 0;
+ }
+ elsif ($name eq 'deadline') {
+ $value = time2str("%Y-%m-%d", str2time($value)) if $value;
+ }
+
+ # If there isn't anything to show, don't include this header.
+ next unless $value;
+
+ push(@diffs, {field_name => $name, new => $value});
+ }
+
+ return @diffs;
}
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugUrl.pm b/Websites/bugs.webkit.org/Bugzilla/BugUrl.pm
new file mode 100644
index 0000000..837c0d4
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/BugUrl.pm
@@ -0,0 +1,208 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+
+package Bugzilla::BugUrl;
+use strict;
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Constants;
+
+use URI::QueryParam;
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_TABLE => 'bug_see_also';
+use constant NAME_FIELD => 'value';
+use constant LIST_ORDER => 'id';
+# See Also is tracked in bugs_activity.
+use constant AUDIT_CREATES => 0;
+use constant AUDIT_UPDATES => 0;
+use constant AUDIT_REMOVES => 0;
+
+use constant DB_COLUMNS => qw(
+ id
+ bug_id
+ value
+ class
+);
+
+# This must be strings with the names of the validations,
+# instead of coderefs, because subclasses override these
+# validators with their own.
+use constant VALIDATORS => {
+ value => '_check_value',
+ bug_id => '_check_bug_id',
+ class => \&_check_class,
+};
+
+# This is the order we go through all of subclasses and
+# pick the first one that should handle the url. New
+# subclasses should be added at the end of the list.
+use constant SUB_CLASSES => qw(
+ Bugzilla::BugUrl::Bugzilla::Local
+ Bugzilla::BugUrl::Bugzilla
+ Bugzilla::BugUrl::Launchpad
+ Bugzilla::BugUrl::Google
+ Bugzilla::BugUrl::Debian
+ Bugzilla::BugUrl::JIRA
+ Bugzilla::BugUrl::Trac
+ Bugzilla::BugUrl::MantisBT
+ Bugzilla::BugUrl::SourceForge
+);
+
+###############################
+#### Accessors ######
+###############################
+
+sub class { return $_[0]->{class} }
+sub bug_id { return $_[0]->{bug_id} }
+
+###############################
+#### Methods ####
+###############################
+
+sub new {
+ my $class = shift;
+ my $param = shift;
+
+ if (ref $param) {
+ my $bug_id = $param->{bug_id};
+ my $name = $param->{name} || $param->{value};
+ if (!defined $bug_id) {
+ ThrowCodeError('bad_arg',
+ { argument => 'bug_id',
+ function => "${class}::new" });
+ }
+ if (!defined $name) {
+ ThrowCodeError('bad_arg',
+ { argument => 'name',
+ function => "${class}::new" });
+ }
+
+ my $condition = 'bug_id = ? AND value = ?';
+ my @values = ($bug_id, $name);
+ $param = { condition => $condition, values => \@values };
+ }
+
+ unshift @_, $param;
+ return $class->SUPER::new(@_);
+}
+
+sub _do_list_select {
+ my $class = shift;
+ my $objects = $class->SUPER::_do_list_select(@_);
+
+ foreach my $object (@$objects) {
+ eval "use " . $object->class; die $@ if $@;
+ bless $object, $object->class;
+ }
+
+ return $objects
+}
+
+# This is an abstract method. It must be overridden
+# in every subclass.
+sub should_handle {
+ my ($class, $input) = @_;
+ ThrowCodeError('unknown_method',
+ { method => "${class}::should_handle" });
+}
+
+sub class_for {
+ my ($class, $value) = @_;
+
+ my $uri = URI->new($value);
+ foreach my $subclass ($class->SUB_CLASSES) {
+ eval "use $subclass";
+ die $@ if $@;
+ return wantarray ? ($subclass, $uri) : $subclass
+ if $subclass->should_handle($uri);
+ }
+
+ ThrowUserError('bug_url_invalid', { url => $value,
+ reason => 'show_bug' });
+}
+
+sub _check_class {
+ my ($class, $subclass) = @_;
+ eval "use $subclass"; die $@ if $@;
+ return $subclass;
+}
+
+sub _check_bug_id {
+ my ($class, $bug_id) = @_;
+
+ my $bug;
+ if (blessed $bug_id) {
+ # We got a bug object passed in, use it
+ $bug = $bug_id;
+ $bug->check_is_visible;
+ }
+ else {
+ # We got a bug id passed in, check it and get the bug object
+ $bug = Bugzilla::Bug->check({ id => $bug_id });
+ }
+
+ return $bug->id;
+}
+
+sub _check_value {
+ my ($class, $uri) = @_;
+
+ my $value = $uri->as_string;
+
+ if (!$value) {
+ ThrowCodeError('param_required',
+ { function => 'add_see_also', param => '$value' });
+ }
+
+ # We assume that the URL is an HTTP URL if there is no (something)://
+ # in front.
+ if (!$uri->scheme) {
+ # This works better than setting $uri->scheme('http'), because
+ # that creates URLs like "http:domain.com" and doesn't properly
+ # differentiate the path from the domain.
+ $uri = new URI("http://$value");
+ }
+ elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
+ ThrowUserError('bug_url_invalid', { url => $value, reason => 'http' });
+ }
+
+ # This stops the following edge cases from being accepted:
+ # * show_bug.cgi?id=1
+ # * /show_bug.cgi?id=1
+ # * http:///show_bug.cgi?id=1
+ if (!$uri->authority or $uri->path !~ m{/}) {
+ ThrowUserError('bug_url_invalid',
+ { url => $value, reason => 'path_only' });
+ }
+
+ if (length($uri->path) > MAX_BUG_URL_LENGTH) {
+ ThrowUserError('bug_url_too_long', { url => $uri->path });
+ }
+
+ return $uri;
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugUrl/Bugzilla.pm b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Bugzilla.pm
new file mode 100644
index 0000000..53f1745
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Bugzilla.pm
@@ -0,0 +1,67 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello.
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::BugUrl::Bugzilla;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->path =~ /show_bug\.cgi$/) ? 1 : 0;
+}
+
+sub _check_value {
+ my ($class, $uri) = @_;
+
+ $uri = $class->SUPER::_check_value($uri);
+
+ my $bug_id = $uri->query_param('id');
+ # We don't currently allow aliases, because we can't check to see
+ # if somebody's putting both an alias link and a numeric ID link.
+ # When we start validating the URL by accessing the other Bugzilla,
+ # we can allow aliases.
+ detaint_natural($bug_id);
+ if (!$bug_id) {
+ my $value = $uri->as_string;
+ ThrowUserError('bug_url_invalid', { url => $value, reason => 'id' });
+ }
+
+ # Make sure that "id" is the only query parameter.
+ $uri->query("id=$bug_id");
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
+}
+
+sub target_bug_id {
+ my ($self) = @_;
+ return new URI($self->name)->query_param('id');
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugUrl/Bugzilla/Local.pm b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Bugzilla/Local.pm
new file mode 100644
index 0000000..99f944f
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Bugzilla/Local.pm
@@ -0,0 +1,111 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello.
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+
+package Bugzilla::BugUrl::Bugzilla::Local;
+use strict;
+use base qw(Bugzilla::BugUrl::Bugzilla);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Initialization ####
+###############################
+
+use constant VALIDATOR_DEPENDENCIES => {
+ value => ['bug_id'],
+};
+
+###############################
+#### Methods ####
+###############################
+
+sub ref_bug_url {
+ my $self = shift;
+
+ if (!exists $self->{ref_bug_url}) {
+ my $ref_bug_id = new URI($self->name)->query_param('id');
+ my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
+ my $ref_value = $self->local_uri($self->bug_id);
+ $self->{ref_bug_url} =
+ new Bugzilla::BugUrl::Bugzilla::Local({ bug_id => $ref_bug->id,
+ value => $ref_value });
+ }
+ return $self->{ref_bug_url};
+}
+
+sub should_handle {
+ my ($class, $uri) = @_;
+
+ # Check if it is either a bug id number or an alias.
+ return 1 if $uri->as_string =~ m/^\w+$/;
+
+ # Check if it is a local Bugzilla uri and call
+ # Bugzilla::BugUrl::Bugzilla to check if it's a valid Bugzilla
+ # see also url.
+ my $canonical_local = URI->new($class->local_uri)->canonical;
+ if ($canonical_local->authority eq $uri->canonical->authority
+ and $canonical_local->path eq $uri->canonical->path)
+ {
+ return $class->SUPER::should_handle($uri);
+ }
+
+ return 0;
+}
+
+sub _check_value {
+ my ($class, $uri, undef, $params) = @_;
+
+ # At this point we are going to treat any word as a
+ # bug id/alias to the local Bugzilla.
+ my $value = $uri->as_string;
+ if ($value =~ m/^\w+$/) {
+ $uri = new URI($class->local_uri($value));
+ } else {
+ # It's not a word, then we have to check
+ # if it's a valid Bugzilla url.
+ $uri = $class->SUPER::_check_value($uri);
+ }
+
+ my $ref_bug_id = $uri->query_param('id');
+ my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
+ my $self_bug_id = $params->{bug_id};
+ $params->{ref_bug} = $ref_bug;
+
+ if ($ref_bug->id == $self_bug_id) {
+ ThrowUserError('see_also_self_reference');
+ }
+
+ my $product = $ref_bug->product_obj;
+ if (!Bugzilla->user->can_edit_product($product->id)) {
+ ThrowUserError("product_edit_denied",
+ { product => $product->name });
+ }
+
+ return $uri;
+}
+
+sub local_uri {
+ my ($self, $bug_id) = @_;
+ $bug_id ||= '';
+ return correct_urlbase() . "show_bug.cgi?id=$bug_id";
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugUrl/Debian.pm b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Debian.pm
new file mode 100644
index 0000000..7f73fee
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Debian.pm
@@ -0,0 +1,63 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello.
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+# Reed Loden <reed@reedloden.com>
+
+package Bugzilla::BugUrl::Debian;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->authority =~ /^bugs.debian.org$/i) ? 1 : 0;
+}
+
+sub _check_value {
+ my $class = shift;
+
+ my $uri = $class->SUPER::_check_value(@_);
+
+ # Debian BTS URLs can look like various things:
+ # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
+ # http://bugs.debian.org/1234
+ my $bug_id;
+ if ($uri->path =~ m|^/(\d+)$|) {
+ $bug_id = $1;
+ }
+ elsif ($uri->path =~ /bugreport\.cgi$/) {
+ $bug_id = $uri->query_param('bug');
+ detaint_natural($bug_id);
+ }
+ if (!$bug_id) {
+ ThrowUserError('bug_url_invalid',
+ { url => $uri->path, reason => 'id' });
+ }
+ # This is the shortest standard URL form for Debian BTS URLs,
+ # and so we reduce all URLs to this.
+ return new URI("http://bugs.debian.org/" . $bug_id);
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugUrl/Google.pm b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Google.pm
new file mode 100644
index 0000000..792699e
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Google.pm
@@ -0,0 +1,65 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello.
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+# Reed Loden <reed@reedloden.com>
+
+package Bugzilla::BugUrl::Google;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->authority =~ /^code.google.com$/i) ? 1 : 0;
+}
+
+sub _check_value {
+ my ($class, $uri) = @_;
+
+ $uri = $class->SUPER::_check_value($uri);
+
+ my $value = $uri->as_string;
+ # Google Code URLs only have one form:
+ # http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234
+ my $project_name;
+ if ($uri->path =~ m|^/p/([^/]+)/issues/detail$|) {
+ $project_name = $1;
+ } else {
+ ThrowUserError('bug_url_invalid', { url => $value });
+ }
+ my $bug_id = $uri->query_param('id');
+ detaint_natural($bug_id);
+ if (!$bug_id) {
+ ThrowUserError('bug_url_invalid', { url => $value, reason => 'id' });
+ }
+ # While Google Code URLs can be either HTTP or HTTPS,
+ # always go with the HTTP scheme, as that's the default.
+ $value = "http://code.google.com/p/" . $project_name .
+ "/issues/detail?id=" . $bug_id;
+
+ return new URI($value);
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugUrl/JIRA.pm b/Websites/bugs.webkit.org/Bugzilla/BugUrl/JIRA.pm
new file mode 100644
index 0000000..d0adcfe
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/BugUrl/JIRA.pm
@@ -0,0 +1,54 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Matt Selsky
+# Portions created by Matt Selsky are Copyright (C) 2010
+# Matt Selsky. All Rights Reserved.
+#
+# Contributor(s): Matt Selsky <selsky@columbia.edu>
+
+package Bugzilla::BugUrl::JIRA;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->path =~ m|/browse/[A-Z][A-Z]+-\d+$|) ? 1 : 0;
+}
+
+sub _check_value {
+ my $class = shift;
+
+ my $uri = $class->SUPER::_check_value(@_);
+
+ # JIRA URLs have only one basic form (but the jira is optional):
+ # https://issues.apache.org/jira/browse/KEY-1234
+ # http://issues.example.com/browse/KEY-1234
+
+ # Make sure there are no query parameters.
+ $uri->query(undef);
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugUrl/Launchpad.pm b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Launchpad.pm
new file mode 100644
index 0000000..a5457fd
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Launchpad.pm
@@ -0,0 +1,59 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello.
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::BugUrl::Launchpad;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->authority =~ /launchpad.net$/) ? 1 : 0;
+}
+
+sub _check_value {
+ my ($class, $uri) = @_;
+
+ $uri = $class->SUPER::_check_value($uri);
+
+ my $value = $uri->as_string;
+ # Launchpad bug URLs can look like various things:
+ # https://bugs.launchpad.net/ubuntu/+bug/1234
+ # https://launchpad.net/bugs/1234
+ # All variations end with either "/bugs/1234" or "/+bug/1234"
+ if ($uri->path =~ m|bugs?/(\d+)$|) {
+ # This is the shortest standard URL form for Launchpad bugs,
+ # and so we reduce all URLs to this.
+ $value = "https://launchpad.net/bugs/$1";
+ }
+ else {
+ ThrowUserError('bug_url_invalid', { url => $value, reason => 'id' });
+ }
+
+ return new URI($value);
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugUrl/MantisBT.pm b/Websites/bugs.webkit.org/Bugzilla/BugUrl/MantisBT.pm
new file mode 100644
index 0000000..c525b0b
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/BugUrl/MantisBT.pm
@@ -0,0 +1,51 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Reed Loden.
+# Portions created by Reed Loden are Copyright (C) 2010
+# Reed Loden. All Rights Reserved.
+#
+# Contributor(s): Reed Loden <reed@reedloden.com>
+
+package Bugzilla::BugUrl::MantisBT;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->path_query =~ m|view\.php\?id=\d+$|) ? 1 : 0;
+}
+
+sub _check_value {
+ my $class = shift;
+
+ my $uri = $class->SUPER::_check_value(@_);
+
+ # MantisBT URLs look like the following ('bugs' directory is optional):
+ # http://www.mantisbt.org/bugs/view.php?id=1234
+
+ # Remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugUrl/SourceForge.pm b/Websites/bugs.webkit.org/Bugzilla/BugUrl/SourceForge.pm
new file mode 100644
index 0000000..fffa96d
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/BugUrl/SourceForge.pm
@@ -0,0 +1,58 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello
+# Portions created by Tiago Mello are Copyright (C) 2011
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+
+package Bugzilla::BugUrl::SourceForge;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->authority =~ /^sourceforge.net$/i
+ and $uri->path =~ m|/tracker/|) ? 1 : 0;
+}
+
+sub _check_value {
+ my $class = shift;
+
+ my $uri = $class->SUPER::_check_value(@_);
+
+ # SourceForge tracker URLs have only one form:
+ # http://sourceforge.net/tracker/?func=detail&aid=111&group_id=111&atid=111
+ if ($uri->query_param('func') eq 'detail' and $uri->query_param('aid')
+ and $uri->query_param('group_id') and $uri->query_param('atid'))
+ {
+ # Remove any # part if there is one.
+ $uri->fragment(undef);
+ return $uri;
+ }
+ else {
+ my $value = $uri->as_string;
+ ThrowUserError('bug_url_invalid', { url => $value });
+ }
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/BugUrl/Trac.pm b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Trac.pm
new file mode 100644
index 0000000..638bd77
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/BugUrl/Trac.pm
@@ -0,0 +1,54 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Matt Selsky
+# Portions created by Matt Selsky are Copyright (C) 2010
+# Matt Selsky. All Rights Reserved.
+#
+# Contributor(s): Matt Selsky <selsky@columbia.edu>
+
+package Bugzilla::BugUrl::Trac;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->path =~ m|/ticket/\d+$|) ? 1 : 0;
+}
+
+sub _check_value {
+ my $class = shift;
+
+ my $uri = $class->SUPER::_check_value(@_);
+
+ # Trac URLs can look like various things:
+ # http://dev.mutt.org/trac/ticket/1234
+ # http://trac.roundcube.net/ticket/1484130
+
+ # Make sure there are no query parameters.
+ $uri->query(undef);
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/CGI.pm b/Websites/bugs.webkit.org/Bugzilla/CGI.pm
index 1799786..e0e1c40 100644
--- a/Websites/bugs.webkit.org/Bugzilla/CGI.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/CGI.pm
@@ -21,48 +21,53 @@
# Byron Jones <bugzilla@glob.com.au>
# Marc Schumann <wurblzap@gmail.com>
-use strict;
-
package Bugzilla::CGI;
-
-BEGIN {
- if ($^O =~ /MSWin32/i) {
- # Help CGI find the correct temp directory as the default list
- # isn't Windows friendly (Bug 248988)
- $ENV{'TMPDIR'} = $ENV{'TEMP'} || $ENV{'TMP'} || "$ENV{'WINDIR'}\\TEMP";
- }
-}
-
-use CGI qw(-no_xhtml -oldstyle_urls :private_tempfiles :unique_headers SERVER_PUSH);
-
+use strict;
use base qw(CGI);
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
+use Bugzilla::Search::Recent;
-# We need to disable output buffering - see bug 179174
-$| = 1;
+use File::Basename;
-# Ignore SIGTERM and SIGPIPE - this prevents DB corruption. If the user closes
-# their browser window while a script is running, the web server sends these
-# signals, and we don't want to die half way through a write.
-$::SIG{TERM} = 'IGNORE';
-$::SIG{PIPE} = 'IGNORE';
+BEGIN {
+ if (ON_WINDOWS) {
+ # Help CGI find the correct temp directory as the default list
+ # isn't Windows friendly (Bug 248988)
+ $ENV{'TMPDIR'} = $ENV{'TEMP'} || $ENV{'TMP'} || "$ENV{'WINDIR'}\\TEMP";
+ }
+ *AUTOLOAD = \&CGI::AUTOLOAD;
+}
-# CGI.pm uses AUTOLOAD, but explicitly defines a DESTROY sub.
-# We need to do so, too, otherwise perl dies when the object is destroyed
-# and we don't have a DESTROY method (because CGI.pm's AUTOLOAD will |die|
-# on getting an unknown sub to try to call)
-sub DESTROY {
- my $self = shift;
- $self->SUPER::DESTROY(@_);
-};
+sub _init_bz_cgi_globals {
+ my $invocant = shift;
+ # We need to disable output buffering - see bug 179174
+ $| = 1;
+
+ # Ignore SIGTERM and SIGPIPE - this prevents DB corruption. If the user closes
+ # their browser window while a script is running, the web server sends these
+ # signals, and we don't want to die half way through a write.
+ $SIG{TERM} = 'IGNORE';
+ $SIG{PIPE} = 'IGNORE';
+
+ # We don't precompile any functions here, that's done specially in
+ # mod_perl code.
+ $invocant->_setup_symbols(qw(:no_xhtml :oldstyle_urls :private_tempfiles
+ :unique_headers));
+}
+
+BEGIN { __PACKAGE__->_init_bz_cgi_globals() if i_am_cgi(); }
sub new {
my ($invocant, @args) = @_;
my $class = ref($invocant) || $invocant;
+ # Under mod_perl, CGI's global variables get reset on each request,
+ # so we need to set them up again every time.
+ $class->_init_bz_cgi_globals() if $ENV{MOD_PERL};
+
my $self = $class->SUPER::new(@args);
# Make sure our outgoing cookie list is empty on each invocation
@@ -72,15 +77,9 @@
$self->charset(Bugzilla->params->{'utf8'} ? 'UTF-8' : '');
# Redirect to urlbase/sslbase if we are not viewing an attachment.
- if (use_attachbase() && i_am_cgi()) {
- my $cgi_file = $self->url('-path_info' => 0, '-query' => 0, '-relative' => 1);
- $cgi_file =~ s/\?$//;
- my $urlbase = Bugzilla->params->{'urlbase'};
- my $sslbase = Bugzilla->params->{'sslbase'};
- my $path_regexp = $sslbase ? qr/^(\Q$urlbase\E|\Q$sslbase\E)/ : qr/^\Q$urlbase\E/;
- if ($cgi_file ne 'attachment.cgi' && $self->self_url !~ /$path_regexp/) {
- $self->redirect_to_urlbase;
- }
+ my $script = basename($0);
+ if ($self->url_is_attachment_base and $script ne 'attachment.cgi') {
+ $self->redirect_to_urlbase();
}
# Check for errors
@@ -119,7 +118,11 @@
my @parameters;
foreach my $key (sort($self->param())) {
# Leave this key out if it's in the exclude list
- next if lsearch(\@exclude, $key) != -1;
+ next if grep { $_ eq $key } @exclude;
+
+ # Remove the Boolean Charts for standard query.cgi fields
+ # They are listed in the query URL already
+ next if $key =~ /^(field|type|value)(-\d+){3}$/;
my $esc_key = url_quote($key);
@@ -137,7 +140,7 @@
sub clean_search_url {
my $self = shift;
- # Delete any empty URL parameter
+ # Delete any empty URL parameter.
my @cgi_params = $self->param;
foreach my $param (@cgi_params) {
@@ -146,18 +149,69 @@
$self->delete("${param}_type");
}
- # Boolean Chart stuff is empty if it's "noop"
- if ($param =~ /\d-\d-\d/ && defined $self->param($param)
- && $self->param($param) eq 'noop')
+ # Custom Search stuff is empty if it's "noop". We also keep around
+ # the old Boolean Chart syntax for backwards-compatibility.
+ if (($param =~ /\d-\d-\d/ || $param =~ /^[[:alpha:]]\d+$/)
+ && defined $self->param($param) && $self->param($param) eq 'noop')
+ {
+ $self->delete($param);
+ }
+
+ # Any "join" for custom search that's an AND can be removed, because
+ # that's the default.
+ if (($param =~ /^j\d+$/ || $param eq 'j_top')
+ && $self->param($param) eq 'AND')
{
$self->delete($param);
}
}
- # Delete certain parameters if the associated parameter is empty.
- $self->delete('bugidtype') if !$self->param('bug_id');
- $self->delete('emailtype1') if !$self->param('email1');
- $self->delete('emailtype2') if !$self->param('email2');
+ # Delete leftovers from the login form
+ $self->delete('Bugzilla_remember', 'GoAheadAndLogIn');
+
+ foreach my $num (1,2,3) {
+ # If there's no value in the email field, delete the related fields.
+ if (!$self->param("email$num")) {
+ foreach my $field (qw(type assigned_to reporter qa_contact cc longdesc)) {
+ $self->delete("email$field$num");
+ }
+ }
+ }
+
+ # chfieldto is set to "Now" by default in query.cgi. But if none
+ # of the other chfield parameters are set, it's meaningless.
+ if (!defined $self->param('chfieldfrom') && !$self->param('chfield')
+ && !defined $self->param('chfieldvalue') && $self->param('chfieldto')
+ && lc($self->param('chfieldto')) eq 'now')
+ {
+ $self->delete('chfieldto');
+ }
+
+ # cmdtype "doit" is the default from query.cgi, but it's only meaningful
+ # if there's a remtype parameter.
+ if (defined $self->param('cmdtype') && $self->param('cmdtype') eq 'doit'
+ && !defined $self->param('remtype'))
+ {
+ $self->delete('cmdtype');
+ }
+
+ # "Reuse same sort as last time" is actually the default, so we don't
+ # need it in the URL.
+ if ($self->param('order')
+ && $self->param('order') eq 'Reuse same sort as last time')
+ {
+ $self->delete('order');
+ }
+
+ # list_id is added in buglist.cgi after calling clean_search_url,
+ # and doesn't need to be saved in saved searches.
+ $self->delete('list_id');
+
+ # And now finally, if query_format is our only parameter, that
+ # really means we have no parameters, so we should delete query_format.
+ if ($self->param('query_format') && scalar($self->param()) == 1) {
+ $self->delete('query_format');
+ }
}
# Overwrite to ensure nph doesn't get set, and unset HEADERS_ONCE
@@ -172,7 +226,8 @@
}
# Set the MIME boundary and content-type
- my $boundary = $param{'-boundary'} || '------- =_aaaaaaaaaa0';
+ my $boundary = $param{'-boundary'}
+ || '------- =_' . generate_random_password(16);
delete $param{'-boundary'};
$self->{'separator'} = "\r\n--$boundary\r\n";
$self->{'final_separator'} = "\r\n--$boundary--\r\n";
@@ -220,30 +275,78 @@
sub header {
my $self = shift;
+ # If there's only one parameter, then it's a Content-Type.
+ if (scalar(@_) == 1) {
+ # Since we're adding parameters below, we have to name it.
+ unshift(@_, '-type' => shift(@_));
+ }
+
# Add the cookies in if we have any
if (scalar(@{$self->{Bugzilla_cookie_list}})) {
- if (scalar(@_) == 1) {
- # if there's only one parameter, then it's a Content-Type.
- # Since we're adding parameters we have to name it.
- unshift(@_, '-type' => shift(@_));
- }
unshift(@_, '-cookie' => $self->{Bugzilla_cookie_list});
}
+ # Add Strict-Transport-Security (STS) header if this response
+ # is over SSL and the strict_transport_security param is turned on.
+ if ($self->https && !$self->url_is_attachment_base
+ && Bugzilla->params->{'strict_transport_security'} ne 'off')
+ {
+ my $sts_opts = 'max-age=' . MAX_STS_AGE;
+ if (Bugzilla->params->{'strict_transport_security'}
+ eq 'include_subdomains')
+ {
+ $sts_opts .= '; includeSubDomains';
+ }
+ unshift(@_, '-strict_transport_security' => $sts_opts);
+ }
+
+ # Add X-Frame-Options header to prevent framing and subsequent
+ # possible clickjacking problems.
+ unless ($self->url_is_attachment_base) {
+ unshift(@_, '-x_frame_options' => 'SAMEORIGIN');
+ }
+
return $self->SUPER::header(@_) || "";
}
-# CGI.pm is not utf8-aware and passes data as bytes instead of UTF-8 strings.
sub param {
my $self = shift;
- if (Bugzilla->params->{'utf8'} && scalar(@_) == 1) {
- if (wantarray) {
- return map { _fix_utf8($_) } $self->SUPER::param(@_);
+
+ # When we are just requesting the value of a parameter...
+ if (scalar(@_) == 1) {
+ my @result = $self->SUPER::param(@_);
+
+ # Also look at the URL parameters, after we look at the POST
+ # parameters. This is to allow things like login-form submissions
+ # with URL parameters in the form's "target" attribute.
+ if (!scalar(@result)
+ && $self->request_method && $self->request_method eq 'POST')
+ {
+ # Some servers fail to set the QUERY_STRING parameter, which
+ # causes undef issues
+ $ENV{'QUERY_STRING'} = '' unless exists $ENV{'QUERY_STRING'};
+ @result = $self->SUPER::url_param(@_);
}
- else {
- return _fix_utf8(scalar $self->SUPER::param(@_));
+
+ # Fix UTF-8-ness of input parameters.
+ if (Bugzilla->params->{'utf8'}) {
+ @result = map { _fix_utf8($_) } @result;
}
+
+ return wantarray ? @result : $result[0];
}
+ # And for various other functions in CGI.pm, we need to correctly
+ # return the URL parameters in addition to the POST parameters when
+ # asked for the list of parameters.
+ elsif (!scalar(@_) && $self->request_method
+ && $self->request_method eq 'POST')
+ {
+ my @post_params = $self->SUPER::param;
+ my @url_params = $self->url_param;
+ my %params = map { $_ => 1 } (@post_params, @url_params);
+ return keys %params;
+ }
+
return $self->SUPER::param(@_);
}
@@ -254,6 +357,14 @@
return $input;
}
+sub should_set {
+ my ($self, $param) = @_;
+ my $set = (defined $self->param($param)
+ or defined $self->param("defined_$param"))
+ ? 1 : 0;
+ return $set;
+}
+
# The various parts of Bugzilla which create cookies don't want to have to
# pass them around to all of the callers. Instead, store them locally here,
# and then output as required from |header|.
@@ -299,25 +410,75 @@
'-value' => 'X');
}
-# Redirect to https if required
-sub require_https {
- my ($self, $url) = @_;
- # Do not create query string if data submitted via XMLRPC
- # since we want the data to be resubmitted over POST method.
- my $query = Bugzilla->usage_mode == USAGE_MODE_WEBSERVICE ? 0 : 1;
- # XMLRPC clients (SOAP::Lite at least) requires 301 to redirect properly
- # and do not work with 302.
- my $status = Bugzilla->usage_mode == USAGE_MODE_WEBSERVICE ? 301 : 302;
- if (defined $url) {
- $url .= $self->url('-path_info' => 1, '-query' => $query, '-relative' => 1);
- } else {
- $url = $self->self_url;
- $url =~ s/^http:/https:/i;
- }
- print $self->redirect(-location => $url, -status => $status);
- # When using XML-RPC with mod_perl, we need the headers sent immediately.
- $self->r->rflush if $ENV{MOD_PERL};
- exit;
+# This helps implement Bugzilla::Search::Recent, and also shortens search
+# URLs that get POSTed to buglist.cgi.
+sub redirect_search_url {
+ my $self = shift;
+ # If we're retreiving an old list, we never need to redirect or
+ # do anything related to Bugzilla::Search::Recent.
+ return if $self->param('regetlastlist');
+
+ my $user = Bugzilla->user;
+
+ if ($user->id) {
+ # There are two conditions that could happen here--we could get a URL
+ # with no list id, and we could get a URL with a list_id that isn't
+ # ours.
+ my $list_id = $self->param('list_id');
+ if ($list_id) {
+ # If we have a valid list_id, no need to redirect or clean.
+ return if Bugzilla::Search::Recent->check_quietly(
+ { id => $list_id });
+ }
+ }
+ elsif ($self->request_method ne 'POST') {
+ # Logged-out users who do a GET don't get a list_id, don't get
+ # their URLs cleaned, and don't get redirected.
+ return;
+ }
+
+ $self->clean_search_url();
+
+ # Make sure we still have params still after cleaning otherwise we
+ # do not want to store a list_id for an empty search.
+ if ($user->id && $self->param) {
+ # Insert a placeholder Bugzilla::Search::Recent, so that we know what
+ # the id of the resulting search will be. This is then pulled out
+ # of the Referer header when viewing show_bug.cgi to know what
+ # bug list we came from.
+ my $recent_search = Bugzilla::Search::Recent->create_placeholder;
+ $self->param('list_id', $recent_search->id);
+ }
+
+ # GET requests that lacked a list_id are always redirected. POST requests
+ # are only redirected if they're under the CGI_URI_LIMIT though.
+ my $uri_length = length($self->self_url());
+ if ($self->request_method() ne 'POST' or $uri_length < CGI_URI_LIMIT) {
+ print $self->redirect(-url => $self->self_url());
+ exit;
+ }
+}
+
+sub redirect_to_https {
+ my $self = shift;
+ my $sslbase = Bugzilla->params->{'sslbase'};
+ # If this is a POST, we don't want ?POSTDATA in the query string.
+ # We expect the client to re-POST, which may be a violation of
+ # the HTTP spec, but the only time we're expecting it often is
+ # in the WebService, and WebService clients usually handle this
+ # correctly.
+ $self->delete('POSTDATA');
+ my $url = $sslbase . $self->url('-path_info' => 1, '-query' => 1,
+ '-relative' => 1);
+
+ # XML-RPC clients (SOAP::Lite at least) require a 301 to redirect properly
+ # and do not work with 302. Our redirect really is permanent anyhow, so
+ # it doesn't hurt to make it a 301.
+ print $self->redirect(-location => $url, -status => 301);
+
+ # When using XML-RPC with mod_perl, we need the headers sent immediately.
+ $self->r->rflush if $ENV{MOD_PERL};
+ exit;
}
# Redirect to the urlbase version of the current URL.
@@ -328,6 +489,61 @@
exit;
}
+sub url_is_attachment_base {
+ my ($self, $id) = @_;
+ return 0 if !use_attachbase() or !i_am_cgi();
+ my $attach_base = Bugzilla->params->{'attachment_base'};
+ # If we're passed an id, we only want one specific attachment base
+ # for a particular bug. If we're not passed an ID, we just want to
+ # know if our current URL matches the attachment_base *pattern*.
+ my $regex;
+ if ($id) {
+ $attach_base =~ s/\%bugid\%/$id/;
+ $regex = quotemeta($attach_base);
+ }
+ else {
+ # In this circumstance we run quotemeta first because we need to
+ # insert an active regex meta-character afterward.
+ $regex = quotemeta($attach_base);
+ $regex =~ s/\\\%bugid\\\%/\\d+/;
+ }
+ $regex = "^$regex";
+ return ($self->self_url =~ $regex) ? 1 : 0;
+}
+
+##########################
+# Vars TIEHASH Interface #
+##########################
+
+# Fix the TIEHASH interface (scalar $cgi->Vars) to return and accept
+# arrayrefs.
+sub STORE {
+ my $self = shift;
+ my ($param, $value) = @_;
+ if (defined $value and ref $value eq 'ARRAY') {
+ return $self->param(-name => $param, -value => $value);
+ }
+ return $self->SUPER::STORE(@_);
+}
+
+sub FETCH {
+ my ($self, $param) = @_;
+ return $self if $param eq 'CGI'; # CGI.pm did this, so we do too.
+ my @result = $self->param($param);
+ return undef if !scalar(@result);
+ return $result[0] if scalar(@result) == 1;
+ return \@result;
+}
+
+# For the Vars TIEHASH interface: the normal CGI.pm DELETE doesn't return
+# the value deleted, but Perl's "delete" expects that value.
+sub DELETE {
+ my ($self, $param) = @_;
+ my $value = $self->FETCH($param);
+ $self->delete($param);
+ return $value;
+}
+
1;
__END__
@@ -390,13 +606,13 @@
As its only argument, it takes the name of the cookie to expire.
-=item C<require_https($baseurl)>
+=item C<redirect_to_https>
-This routine redirects the client to a different location using the https protocol.
-If the client is using XMLRPC, it will not retain the QUERY_STRING since XMLRPC uses POST.
+This routine redirects the client to the https version of the page that
+they're looking at, using the C<sslbase> parameter for the redirection.
-It takes an optional argument which will be used as the base URL. If $baseurl
-is not provided, the current URL is used.
+Generally you should use L<Bugzilla::Util/do_ssl_redirect_if_required>
+instead of calling this directly.
=item C<redirect_to_urlbase>
diff --git a/Websites/bugs.webkit.org/Bugzilla/Chart.pm b/Websites/bugs.webkit.org/Bugzilla/Chart.pm
index a119e4b..dfbf32a 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Chart.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Chart.pm
@@ -67,7 +67,7 @@
#
# The URL encoding is:
# line0=67&line0=73&line1=81&line2=67...
- # &label0=B+/+R+/+NEW&label1=...
+ # &label0=B+/+R+/+CONFIRMED&label1=...
# &select0=1&select3=1...
# &cumulate=1&datefrom=2002-02-03&dateto=2002-04-04&ctype=html...
# >=1&labelgt=Grand+Total
@@ -382,8 +382,7 @@
sub getVisibleSeries {
my %cats;
- # List of groups the user is in; use -1 to make sure it's not empty.
- my $grouplist = join(", ", (-1, values(%{Bugzilla->user->groups})));
+ my $grouplist = Bugzilla->user->groups_as_string;
# Get all visible series
my $dbh = Bugzilla->dbh;
@@ -397,10 +396,10 @@
"LEFT JOIN category_group_map AS cgm " .
" ON series.category = cgm.category_id " .
" AND cgm.group_id NOT IN($grouplist) " .
- "WHERE creator = " . Bugzilla->user->id . " OR " .
- " cgm.category_id IS NULL " .
+ "WHERE creator = ? OR (is_public = 1 AND cgm.category_id IS NULL) " .
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
- 'series.name'));
+ 'series.name'),
+ undef, Bugzilla->user->id);
foreach my $series (@$serieses) {
my ($cat, $subcat, $name, $series_id) = @$series;
$cats{$cat}{$subcat}{$name} = $series_id;
@@ -439,7 +438,7 @@
require Data::Dumper;
print "<pre>Bugzilla::Chart object:\n";
- print Data::Dumper::Dumper($self);
+ print html_quote(Data::Dumper::Dumper($self));
print "</pre>";
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Classification.pm b/Websites/bugs.webkit.org/Bugzilla/Classification.pm
index 37ae3a0..88ec4eb 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Classification.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Classification.pm
@@ -13,77 +13,120 @@
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Tiago R. Mello <timello@async.com.br>
-#
+# Frédéric Buclin <LpSolit@gmail.com>
use strict;
package Bugzilla::Classification;
+use Bugzilla::Constants;
+use Bugzilla::Field;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Product;
+use base qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
+
###############################
#### Initialization ####
###############################
+use constant DB_TABLE => 'classifications';
+use constant LIST_ORDER => 'sortkey, name';
+
use constant DB_COLUMNS => qw(
- classifications.id
- classifications.name
- classifications.description
- classifications.sortkey
+ id
+ name
+ description
+ sortkey
);
-our $columns = join(", ", DB_COLUMNS);
+use constant UPDATE_COLUMNS => qw(
+ name
+ description
+ sortkey
+);
+
+use constant VALIDATORS => {
+ name => \&_check_name,
+ description => \&_check_description,
+ sortkey => \&_check_sortkey,
+};
+
+###############################
+#### Constructors #####
+###############################
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ ThrowUserError("classification_not_deletable") if ($self->id == 1);
+
+ $dbh->bz_start_transaction();
+ # Reclassify products to the default classification, if needed.
+ $dbh->do("UPDATE products SET classification_id = 1
+ WHERE classification_id = ?", undef, $self->id);
+
+ $self->SUPER::remove_from_db();
+
+ $dbh->bz_commit_transaction();
+
+}
+
+###############################
+#### Validators ####
+###############################
+
+sub _check_name {
+ my ($invocant, $name) = @_;
+
+ $name = trim($name);
+ $name || ThrowUserError('classification_not_specified');
+
+ if (length($name) > MAX_CLASSIFICATION_SIZE) {
+ ThrowUserError('classification_name_too_long', {'name' => $name});
+ }
+
+ my $classification = new Bugzilla::Classification({name => $name});
+ if ($classification && (!ref $invocant || $classification->id != $invocant->id)) {
+ ThrowUserError("classification_already_exists", { name => $classification->name });
+ }
+ return $name;
+}
+
+sub _check_description {
+ my ($invocant, $description) = @_;
+
+ $description = trim($description || '');
+ return $description;
+}
+
+sub _check_sortkey {
+ my ($invocant, $sortkey) = @_;
+
+ $sortkey ||= 0;
+ my $stored_sortkey = $sortkey;
+ if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) {
+ ThrowUserError('classification_invalid_sortkey', { 'sortkey' => $stored_sortkey });
+ }
+ return $sortkey;
+}
+
+#####################################
+# Implement Bugzilla::Field::Choice #
+#####################################
+
+use constant FIELD_NAME => 'classification';
+use constant is_default => 0;
+use constant is_active => 1;
###############################
#### Methods ####
###############################
-sub new {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my $self = {};
- bless($self, $class);
- return $self->_init(@_);
-}
-
-sub _init {
- my $self = shift;
- my ($param) = @_;
- my $dbh = Bugzilla->dbh;
-
- my $id = $param unless (ref $param eq 'HASH');
- my $classification;
-
- if (defined $id) {
- detaint_natural($id)
- || ThrowCodeError('param_must_be_numeric',
- {function => 'Bugzilla::Classification::_init'});
-
- $classification = $dbh->selectrow_hashref(qq{
- SELECT $columns FROM classifications
- WHERE id = ?}, undef, $id);
-
- } elsif (defined $param->{'name'}) {
-
- trick_taint($param->{'name'});
- $classification = $dbh->selectrow_hashref(qq{
- SELECT $columns FROM classifications
- WHERE name = ?}, undef, $param->{'name'});
- } else {
- ThrowCodeError('bad_arg',
- {argument => 'param',
- function => 'Bugzilla::Classification::_init'});
- }
-
- return undef unless (defined $classification);
-
- foreach my $field (keys %$classification) {
- $self->{$field} = $classification->{$field};
- }
- return $self;
-}
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
sub product_count {
my $self = shift;
@@ -116,46 +159,9 @@
#### Accessors ####
###############################
-sub id { return $_[0]->{'id'}; }
-sub name { return $_[0]->{'name'}; }
sub description { return $_[0]->{'description'}; }
sub sortkey { return $_[0]->{'sortkey'}; }
-###############################
-#### Subroutines ####
-###############################
-
-sub get_all_classifications {
- my $dbh = Bugzilla->dbh;
-
- my $ids = $dbh->selectcol_arrayref(q{
- SELECT id FROM classifications ORDER BY sortkey, name});
-
- my @classifications;
- foreach my $id (@$ids) {
- push @classifications, new Bugzilla::Classification($id);
- }
- return @classifications;
-}
-
-sub check_classification {
- my ($class_name) = @_;
-
- unless ($class_name) {
- ThrowUserError("classification_not_specified");
- }
-
- my $classification =
- new Bugzilla::Classification({name => $class_name});
-
- unless ($classification) {
- ThrowUserError("classification_doesnt_exist",
- { name => $class_name });
- }
-
- return $classification;
-}
-
1;
__END__
@@ -174,18 +180,18 @@
my $id = $classification->id;
my $name = $classification->name;
my $description = $classification->description;
+ my $sortkey = $classification->sortkey;
my $product_count = $classification->product_count;
my $products = $classification->products;
- my $hash_ref = Bugzilla::Classification::get_all_classifications();
- my $classification = $hash_ref->{1};
-
- my $classification =
- Bugzilla::Classification::check_classification('AcmeClass');
-
=head1 DESCRIPTION
-Classification.pm represents a Classification object.
+Classification.pm represents a classification object. It is an
+implementation of L<Bugzilla::Object>, and thus provides all methods
+that L<Bugzilla::Object> provides.
+
+The methods that are specific to C<Bugzilla::Classification> are listed
+below.
A Classification is a higher-level grouping of Products.
@@ -193,20 +199,6 @@
=over
-=item C<new($param)>
-
- Description: The constructor is used to load an existing
- classification by passing a classification
- id or classification name using a hash.
-
- Params: $param - If you pass an integer, the integer is the
- classification_id from the database that we
- want to read in. If you pass in a hash with
- 'name' key, then the value of the name key
- is the name of a classification from the DB.
-
- Returns: A Bugzilla::Classification object.
-
=item C<product_count()>
Description: Returns the total number of products that belong to
@@ -226,27 +218,4 @@
=back
-=head1 SUBROUTINES
-
-=over
-
-=item C<get_all_classifications()>
-
- Description: Returns all classifications.
-
- Params: none.
-
- Returns: Bugzilla::Classification object list.
-
-=item C<check_classification($classification_name)>
-
- Description: Checks if the classification name passed in is a
- valid classification.
-
- Params: $classification_name - String with a classification name.
-
- Returns: Bugzilla::Classification object.
-
-=back
-
=cut
diff --git a/Websites/bugs.webkit.org/Bugzilla/Comment.pm b/Websites/bugs.webkit.org/Bugzilla/Comment.pm
new file mode 100644
index 0000000..ee342fb
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Comment.pm
@@ -0,0 +1,433 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is James Robson.
+# Portions created by James Robson are Copyright (c) 2009 James Robson.
+# All rights reserved.
+#
+# Contributor(s): James Robson <arbingersys@gmail.com>
+# Christian Legnitto <clegnitto@mozilla.com>
+
+use strict;
+
+package Bugzilla::Comment;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Attachment;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Util;
+
+use Scalar::Util qw(blessed);
+
+###############################
+#### Initialization ####
+###############################
+
+# Creation and updating of comments are audited in longdescs
+# and bugs_activity respectively instead of audit_log.
+use constant AUDIT_CREATES => 0;
+use constant AUDIT_UPDATES => 0;
+
+use constant DB_COLUMNS => qw(
+ comment_id
+ bug_id
+ who
+ bug_when
+ work_time
+ thetext
+ isprivate
+ already_wrapped
+ type
+ extra_data
+);
+
+use constant UPDATE_COLUMNS => qw(
+ isprivate
+ type
+ extra_data
+);
+
+use constant DB_TABLE => 'longdescs';
+use constant ID_FIELD => 'comment_id';
+# In some rare cases, two comments can have identical timestamps. If
+# this happens, we want to be sure that the comment added later shows up
+# later in the sequence.
+use constant LIST_ORDER => 'bug_when, comment_id';
+
+use constant VALIDATORS => {
+ bug_id => \&_check_bug_id,
+ who => \&_check_who,
+ bug_when => \&_check_bug_when,
+ work_time => \&_check_work_time,
+ thetext => \&_check_thetext,
+ isprivate => \&_check_isprivate,
+ extra_data => \&_check_extra_data,
+ type => \&_check_type,
+};
+
+use constant VALIDATOR_DEPENDENCIES => {
+ extra_data => ['type'],
+ bug_id => ['who'],
+ work_time => ['who', 'bug_id'],
+ isprivate => ['who'],
+};
+
+#########################
+# Database Manipulation #
+#########################
+
+sub update {
+ my $self = shift;
+ my $changes = $self->SUPER::update(@_);
+ $self->bug->_sync_fulltext();
+ return $changes;
+}
+
+# Speeds up displays of comment lists by loading all ->author objects
+# at once for a whole list.
+sub preload {
+ my ($class, $comments) = @_;
+ my %user_ids = map { $_->{who} => 1 } @$comments;
+ my $users = Bugzilla::User->new_from_list([keys %user_ids]);
+ my %user_map = map { $_->id => $_ } @$users;
+ foreach my $comment (@$comments) {
+ $comment->{author} = $user_map{$comment->{who}};
+ }
+}
+
+###############################
+#### Accessors ######
+###############################
+
+sub already_wrapped { return $_[0]->{'already_wrapped'}; }
+sub body { return $_[0]->{'thetext'}; }
+sub bug_id { return $_[0]->{'bug_id'}; }
+sub creation_ts { return $_[0]->{'bug_when'}; }
+sub is_private { return $_[0]->{'isprivate'}; }
+sub work_time {
+ # Work time is returned as a string (see bug 607909)
+ return 0 if $_[0]->{'work_time'} + 0 == 0;
+ return $_[0]->{'work_time'};
+}
+sub type { return $_[0]->{'type'}; }
+sub extra_data { return $_[0]->{'extra_data'} }
+
+sub bug {
+ my $self = shift;
+ require Bugzilla::Bug;
+ $self->{bug} ||= new Bugzilla::Bug($self->bug_id);
+ return $self->{bug};
+}
+
+sub is_about_attachment {
+ my ($self) = @_;
+ return 1 if ($self->type == CMT_ATTACHMENT_CREATED
+ or $self->type == CMT_ATTACHMENT_UPDATED);
+ return 0;
+}
+
+sub attachment {
+ my ($self) = @_;
+ return undef if not $self->is_about_attachment;
+ $self->{attachment} ||= new Bugzilla::Attachment($self->extra_data);
+ return $self->{attachment};
+}
+
+sub author {
+ my $self = shift;
+ $self->{'author'} ||= new Bugzilla::User($self->{'who'});
+ return $self->{'author'};
+}
+
+sub body_full {
+ my ($self, $params) = @_;
+ $params ||= {};
+ my $template = Bugzilla->template_inner;
+ my $body;
+ if ($self->type) {
+ $template->process("bug/format_comment.txt.tmpl",
+ { comment => $self, %$params }, \$body)
+ || ThrowTemplateError($template->error());
+ $body =~ s/^X//;
+ }
+ else {
+ $body = $self->body;
+ }
+ if ($params->{wrap} and !$self->already_wrapped) {
+ $body = wrap_comment($body);
+ }
+ return $body;
+}
+
+############
+# Mutators #
+############
+
+sub set_is_private { $_[0]->set('isprivate', $_[1]); }
+sub set_type { $_[0]->set('type', $_[1]); }
+sub set_extra_data { $_[0]->set('extra_data', $_[1]); }
+
+##############
+# Validators #
+##############
+
+sub run_create_validators {
+ my $self = shift;
+ my $params = $self->SUPER::run_create_validators(@_);
+ # Sometimes this run_create_validators is called with parameters that
+ # skip bug_id validation, so it might not exist in the resulting hash.
+ if (defined $params->{bug_id}) {
+ $params->{bug_id} = $params->{bug_id}->id;
+ }
+ return $params;
+}
+
+sub _check_extra_data {
+ my ($invocant, $extra_data, undef, $params) = @_;
+ my $type = blessed($invocant) ? $invocant->type : $params->{type};
+
+ if ($type == CMT_NORMAL) {
+ if (defined $extra_data) {
+ ThrowCodeError('comment_extra_data_not_allowed',
+ { type => $type, extra_data => $extra_data });
+ }
+ }
+ else {
+ if (!defined $extra_data) {
+ ThrowCodeError('comment_extra_data_required', { type => $type });
+ }
+ elsif ($type == CMT_ATTACHMENT_CREATED
+ or $type == CMT_ATTACHMENT_UPDATED)
+ {
+ my $attachment = Bugzilla::Attachment->check({
+ id => $extra_data });
+ $extra_data = $attachment->id;
+ }
+ else {
+ my $original = $extra_data;
+ detaint_natural($extra_data)
+ or ThrowCodeError('comment_extra_data_not_numeric',
+ { type => $type, extra_data => $original });
+ }
+ }
+
+ return $extra_data;
+}
+
+sub _check_type {
+ my ($invocant, $type) = @_;
+ $type ||= CMT_NORMAL;
+ my $original = $type;
+ detaint_natural($type)
+ or ThrowCodeError('comment_type_invalid', { type => $original });
+ return $type;
+}
+
+sub _check_bug_id {
+ my ($invocant, $bug_id) = @_;
+
+ ThrowCodeError('param_required', {function => 'Bugzilla::Comment->create',
+ param => 'bug_id'}) unless $bug_id;
+
+ my $bug;
+ if (blessed $bug_id) {
+ # We got a bug object passed in, use it
+ $bug = $bug_id;
+ $bug->check_is_visible;
+ }
+ else {
+ # We got a bug id passed in, check it and get the bug object
+ $bug = Bugzilla::Bug->check({ id => $bug_id });
+ }
+
+ # Make sure the user can edit the product
+ Bugzilla->user->can_edit_product($bug->{product_id});
+
+ # Make sure the user can comment
+ my $privs;
+ $bug->check_can_change_field('longdesc', 0, 1, \$privs)
+ || ThrowUserError('illegal_change',
+ { field => 'longdesc', privs => $privs });
+ return $bug;
+}
+
+sub _check_who {
+ my ($invocant, $who) = @_;
+ Bugzilla->login(LOGIN_REQUIRED);
+ return Bugzilla->user->id;
+}
+
+sub _check_bug_when {
+ my ($invocant, $when) = @_;
+
+ # Make sure the timestamp is defined, default to a timestamp from the db
+ if (!defined $when) {
+ $when = Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ }
+
+ # Make sure the timestamp parses
+ if (!datetime_from($when)) {
+ ThrowCodeError('invalid_timestamp', { timestamp => $when });
+ }
+
+ return $when;
+}
+
+sub _check_work_time {
+ my ($invocant, $value_in, $field, $params) = @_;
+
+ # Call down to Bugzilla::Object, letting it know negative
+ # values are ok
+ my $time = $invocant->check_time($value_in, $field, $params, 1);
+ my $privs;
+ $params->{bug_id}->check_can_change_field('work_time', 0, $time, \$privs)
+ || ThrowUserError('illegal_change',
+ { field => 'work_time', privs => $privs });
+ return $time;
+}
+
+sub _check_thetext {
+ my ($invocant, $thetext) = @_;
+
+ ThrowCodeError('param_required',{function => 'Bugzilla::Comment->create',
+ param => 'thetext'}) unless defined $thetext;
+
+ # Remove any trailing whitespace. Leading whitespace could be
+ # a valid part of the comment.
+ $thetext =~ s/\s*$//s;
+ $thetext =~ s/\r\n?/\n/g; # Get rid of \r.
+
+ ThrowUserError('comment_too_long') if length($thetext) > MAX_COMMENT_LENGTH;
+ return $thetext;
+}
+
+sub _check_isprivate {
+ my ($invocant, $isprivate) = @_;
+ if ($isprivate && !Bugzilla->user->is_insider) {
+ ThrowUserError('user_not_insider');
+ }
+ return $isprivate ? 1 : 0;
+}
+
+sub count {
+ my ($self) = @_;
+
+ return $self->{'count'} if defined $self->{'count'};
+
+ my $dbh = Bugzilla->dbh;
+ ($self->{'count'}) = $dbh->selectrow_array(
+ "SELECT COUNT(*)
+ FROM longdescs
+ WHERE bug_id = ?
+ AND bug_when <= ?",
+ undef, $self->bug_id, $self->creation_ts);
+
+ return --$self->{'count'};
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Comment - A Comment for a given bug
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Comment;
+
+ my $comment = Bugzilla::Comment->new($comment_id);
+ my $comments = Bugzilla::Comment->new_from_list($comment_ids);
+
+=head1 DESCRIPTION
+
+Bugzilla::Comment represents a comment attached to a bug.
+
+This implements all standard C<Bugzilla::Object> methods. See
+L<Bugzilla::Object> for more details.
+
+=head2 Accessors
+
+=over
+
+=item C<bug_id>
+
+C<int> The ID of the bug to which the comment belongs.
+
+=item C<creation_ts>
+
+C<string> The comment creation timestamp.
+
+=item C<body>
+
+C<string> The body without any special additional text.
+
+=item C<work_time>
+
+C<string> Time spent as related to this comment.
+
+=item C<is_private>
+
+C<boolean> Comment is marked as private
+
+=item C<already_wrapped>
+
+If this comment is stored in the database word-wrapped, this will be C<1>.
+C<0> otherwise.
+
+=item C<author>
+
+L<Bugzilla::User> who created the comment.
+
+=item C<count>
+
+C<int> The position this comment is located in the full list of comments for a bug starting from 0.
+
+=item C<body_full>
+
+=over
+
+=item B<Description>
+
+C<string> Body of the comment, including any special text (such as
+"this bug was marked as a duplicate of...").
+
+=item B<Params>
+
+=over
+
+=item C<is_bugmail>
+
+C<boolean>. C<1> if this comment should be formatted specifically for
+bugmail.
+
+=item C<wrap>
+
+C<boolean>. C<1> if the comment should be returned word-wrapped.
+
+=back
+
+=item B<Returns>
+
+A string, the full text of the comment as it would be displayed to an end-user.
+
+=back
+
+=back
+
+=cut
diff --git a/Websites/bugs.webkit.org/Bugzilla/Component.pm b/Websites/bugs.webkit.org/Bugzilla/Component.pm
index f5719e8..dc3cc1b 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Component.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Component.pm
@@ -17,11 +17,9 @@
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Akamai Technologies <bugzilla-dev@akamai.com>
-use strict;
-
package Bugzilla::Component;
-
-use base qw(Bugzilla::Object);
+use strict;
+use base qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
use Bugzilla::Constants;
use Bugzilla::Util;
@@ -30,11 +28,15 @@
use Bugzilla::FlagType;
use Bugzilla::Series;
+use Scalar::Util qw(blessed);
+
###############################
#### Initialization ####
###############################
use constant DB_TABLE => 'components';
+# This is mostly for the editfields.cgi case where ->get_all is called.
+use constant LIST_ORDER => 'product_id, name';
use constant DB_COLUMNS => qw(
id
@@ -43,13 +45,7 @@
initialowner
initialqacontact
description
-);
-
-use constant REQUIRED_CREATE_FIELDS => qw(
- name
- product
- initialowner
- description
+ isactive
);
use constant UPDATE_COLUMNS => qw(
@@ -57,18 +53,26 @@
initialowner
initialqacontact
description
+ isactive
);
+use constant REQUIRED_FIELD_MAP => {
+ product_id => 'product',
+};
+
use constant VALIDATORS => {
+ create_series => \&Bugzilla::Object::check_boolean,
product => \&_check_product,
initialowner => \&_check_initialowner,
initialqacontact => \&_check_initialqacontact,
description => \&_check_description,
initial_cc => \&_check_cc_list,
+ name => \&_check_name,
+ isactive => \&Bugzilla::Object::check_boolean,
};
-use constant UPDATE_VALIDATORS => {
- name => \&_check_name,
+use constant VALIDATOR_DEPENDENCIES => {
+ name => ['product'],
};
###############################
@@ -79,7 +83,7 @@
my $dbh = Bugzilla->dbh;
my $product;
- if (ref $param) {
+ if (ref $param and !defined $param->{id}) {
$product = $param->{product};
my $name = $param->{name};
if (!defined $product) {
@@ -114,30 +118,23 @@
$class->check_required_create_fields(@_);
my $params = $class->run_create_validators(@_);
my $cc_list = delete $params->{initial_cc};
+ my $create_series = delete $params->{create_series};
+ my $product = delete $params->{product};
+ $params->{product_id} = $product->id;
my $component = $class->insert_create_data($params);
+ $component->{product} = $product;
# We still have to fill the component_cc table.
- $component->_update_cc_list($cc_list);
+ $component->_update_cc_list($cc_list) if $cc_list;
# Create series for the new component.
- $component->_create_series();
+ $component->_create_series() if $create_series;
$dbh->bz_commit_transaction();
return $component;
}
-sub run_create_validators {
- my $class = shift;
- my $params = $class->SUPER::run_create_validators(@_);
-
- my $product = delete $params->{product};
- $params->{product_id} = $product->id;
- $params->{name} = $class->_check_name($params->{name}, $product);
-
- return $params;
-}
-
sub update {
my $self = shift;
my $changes = $self->SUPER::update(@_);
@@ -154,6 +151,8 @@
my $self = shift;
my $dbh = Bugzilla->dbh;
+ $self->_check_if_controller(); # From ChoiceInterface
+
$dbh->bz_start_transaction();
if ($self->bug_count) {
@@ -186,7 +185,8 @@
################################
sub _check_name {
- my ($invocant, $name, $product) = @_;
+ my ($invocant, $name, undef, $params) = @_;
+ my $product = blessed($invocant) ? $invocant->product : $params->{product};
$name = trim($name);
$name || ThrowUserError('component_blank_name');
@@ -195,7 +195,6 @@
ThrowUserError('component_name_too_long', {'name' => $name});
}
- $product = $invocant->product if (ref $invocant);
my $component = new Bugzilla::Component({product => $product, name => $name});
if ($component && (!ref $invocant || $component->id != $invocant->id)) {
ThrowUserError('component_already_exists', { name => $component->name,
@@ -235,6 +234,8 @@
sub _check_product {
my ($invocant, $product) = @_;
+ $product || ThrowCodeError('param_required',
+ { function => "$invocant->create", param => 'product' });
return Bugzilla->user->check_can_admin_product($product->name);
}
@@ -302,6 +303,7 @@
sub set_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
+sub set_is_active { $_[0]->set('isactive', $_[1]); }
sub set_default_assignee {
my ($self, $owner) = @_;
@@ -372,16 +374,14 @@
my $self = shift;
if (!defined $self->{'flag_types'}) {
+ my $flagtypes = Bugzilla::FlagType::match({ product_id => $self->product_id,
+ component_id => $self->id });
+
$self->{'flag_types'} = {};
$self->{'flag_types'}->{'bug'} =
- Bugzilla::FlagType::match({ 'target_type' => 'bug',
- 'product_id' => $self->product_id,
- 'component_id' => $self->id });
-
+ [grep { $_->target_type eq 'bug' } @$flagtypes];
$self->{'flag_types'}->{'attachment'} =
- Bugzilla::FlagType::match({ 'target_type' => 'attachment',
- 'product_id' => $self->product_id,
- 'component_id' => $self->id });
+ [grep { $_->target_type eq 'attachment' } @$flagtypes];
}
return $self->{'flag_types'};
}
@@ -416,10 +416,24 @@
#### Accessors ####
###############################
-sub id { return $_[0]->{'id'}; }
-sub name { return $_[0]->{'name'}; }
sub description { return $_[0]->{'description'}; }
sub product_id { return $_[0]->{'product_id'}; }
+sub is_active { return $_[0]->{'isactive'}; }
+
+##############################################
+# Implement Bugzilla::Field::ChoiceInterface #
+##############################################
+
+use constant FIELD_NAME => 'component';
+use constant is_default => 0;
+
+sub is_set_on_bug {
+ my ($self, $bug) = @_;
+ # We treat it like a hash always, so that we don't have to check if it's
+ # a hash or an object.
+ return 0 if !defined $bug->{component_id};
+ $bug->{component_id} == $self->id ? 1 : 0;
+}
###############################
#### Subroutines ####
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config.pm b/Websites/bugs.webkit.org/Bugzilla/Config.pm
index 619eb05..990fd8d 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config.pm
@@ -34,6 +34,8 @@
use base qw(Exporter);
use Bugzilla::Constants;
+use Bugzilla::Hook;
+use Bugzilla::Install::Filesystem qw(fix_file_permissions);
use Data::Dumper;
use File::Temp;
@@ -46,22 +48,28 @@
);
Exporter::export_ok_tags('admin');
-use vars qw(@param_list);
-
# INITIALISATION CODE
# Perl throws a warning if we use bz_locations() directly after do.
our %params;
# Load in the param definitions
sub _load_params {
my $panels = param_panels();
+ my %hook_panels;
foreach my $panel (keys %$panels) {
my $module = $panels->{$panel};
eval("require $module") || die $@;
- my @new_param_list = "$module"->get_param_list();
- foreach my $item (@new_param_list) {
+ my @new_param_list = $module->get_param_list();
+ $hook_panels{lc($panel)} = { params => \@new_param_list };
+ }
+ # This hook is also called in editparams.cgi. This call here is required
+ # to make SetParam work.
+ Bugzilla::Hook::process('config_modify_panels',
+ { panels => \%hook_panels });
+
+ foreach my $panel (keys %hook_panels) {
+ foreach my $item (@{$hook_panels{$panel}->{params}}) {
$params{$item->{'name'}} = $item;
}
- push(@param_list, @new_param_list);
}
}
# END INIT CODE
@@ -77,7 +85,8 @@
$param_panels->{$module} = "Bugzilla::Config::$module" unless $module eq 'Common';
}
# Now check for any hooked params
- Bugzilla::Hook::process('config', { config => $param_panels });
+ Bugzilla::Hook::process('config_add_panels',
+ { panel_modules => $param_panels });
return $param_panels;
}
@@ -107,33 +116,28 @@
my $answer = Bugzilla->installation_answers;
my $param = read_param_file();
+ my %new_params;
# If we didn't return any param values, then this is a new installation.
my $new_install = !(keys %$param);
# --- UPDATE OLD PARAMS ---
- # Old Bugzilla versions stored the version number in the params file
- # We don't want it, so get rid of it
- delete $param->{'version'};
-
# Change from usebrowserinfo to defaultplatform/defaultopsys combo
if (exists $param->{'usebrowserinfo'}) {
if (!$param->{'usebrowserinfo'}) {
if (!exists $param->{'defaultplatform'}) {
- $param->{'defaultplatform'} = 'Other';
+ $new_params{'defaultplatform'} = 'Other';
}
if (!exists $param->{'defaultopsys'}) {
- $param->{'defaultopsys'} = 'Other';
+ $new_params{'defaultopsys'} = 'Other';
}
}
- delete $param->{'usebrowserinfo'};
}
# Change from a boolean for quips to multi-state
if (exists $param->{'usequip'} && !exists $param->{'enablequips'}) {
- $param->{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
- delete $param->{'usequip'};
+ $new_params{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
}
# Change from old product groups to controls for group_control_map
@@ -141,24 +145,19 @@
if (exists $param->{'usebuggroups'} &&
!exists $param->{'makeproductgroups'})
{
- $param->{'makeproductgroups'} = $param->{'usebuggroups'};
- }
- if (exists $param->{'usebuggroupsentry'}
- && !exists $param->{'useentrygroupdefault'}) {
- $param->{'useentrygroupdefault'} = $param->{'usebuggroupsentry'};
+ $new_params{'makeproductgroups'} = $param->{'usebuggroups'};
}
# Modularise auth code
if (exists $param->{'useLDAP'} && !exists $param->{'loginmethod'}) {
- $param->{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
+ $new_params{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
}
# set verify method to whatever loginmethod was
if (exists $param->{'loginmethod'}
&& !exists $param->{'user_verify_class'})
{
- $param->{'user_verify_class'} = $param->{'loginmethod'};
- delete $param->{'loginmethod'};
+ $new_params{'user_verify_class'} = $param->{'loginmethod'};
}
# Remove quip-display control from parameters
@@ -171,8 +170,7 @@
($param->{'enablequips'} eq 'approved') && do {$new_value = 'moderated';};
($param->{'enablequips'} eq 'frozen') && do {$new_value = 'closed';};
($param->{'enablequips'} eq 'off') && do {$new_value = 'closed';};
- $param->{'quip_list_entry_control'} = $new_value;
- delete $param->{'enablequips'};
+ $new_params{'quip_list_entry_control'} = $new_value;
}
# Old mail_delivery_method choices contained no uppercase characters
@@ -188,14 +186,34 @@
$param->{'mail_delivery_method'} = $translation{$method};
}
+ # Convert the old "ssl" parameter to the new "ssl_redirect" parameter.
+ # Both "authenticated sessions" and "always" turn on "ssl_redirect"
+ # when upgrading.
+ if (exists $param->{'ssl'} and $param->{'ssl'} ne 'never') {
+ $new_params{'ssl_redirect'} = 1;
+ }
+
+ # "specific_search_allow_empty_words" has been renamed to "search_allow_no_criteria".
+ if (exists $param->{'specific_search_allow_empty_words'}) {
+ $new_params{'search_allow_no_criteria'} = $param->{'specific_search_allow_empty_words'};
+ }
+
# --- DEFAULTS FOR NEW PARAMS ---
_load_params unless %params;
- foreach my $item (@param_list) {
- my $name = $item->{'name'};
+ foreach my $name (keys %params) {
+ my $item = $params{$name};
unless (exists $param->{$name}) {
print "New parameter: $name\n" unless $new_install;
- $param->{$name} = $answer->{$name} || $item->{'default'};
+ if (exists $new_params{$name}) {
+ $param->{$name} = $new_params{$name};
+ }
+ elsif (exists $answer->{$name}) {
+ $param->{$name} = $answer->{$name};
+ }
+ else {
+ $param->{$name} = $item->{'default'};
+ }
}
}
@@ -204,21 +222,23 @@
# --- REMOVE OLD PARAMS ---
my %oldparams;
- # Remove any old params, put them in old-params.txt
+ # Remove any old params
foreach my $item (keys %$param) {
- if (!grep($_ eq $item, map ($_->{'name'}, @param_list))) {
- $oldparams{$item} = $param->{$item};
- delete $param->{$item};
+ if (!exists $params{$item}) {
+ $oldparams{$item} = delete $param->{$item};
}
}
+ # Write any old parameters to old-params.txt
+ my $datadir = bz_locations()->{'datadir'};
+ my $old_param_file = "$datadir/old-params.txt";
if (scalar(keys %oldparams)) {
- my $op_file = new IO::File('old-params.txt', '>>', 0600)
- || die "old-params.txt: $!";
+ my $op_file = new IO::File($old_param_file, '>>', 0600)
+ || die "Couldn't create $old_param_file: $!";
print "The following parameters are no longer used in Bugzilla,",
" and so have been\nmoved from your parameters file into",
- " old-params.txt:\n";
+ " $old_param_file:\n";
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 0;
@@ -281,29 +301,13 @@
rename $tmpname, $param_file
or die "Can't rename $tmpname to $param_file: $!";
- ChmodDataFile($param_file, 0666);
+ fix_file_permissions($param_file);
# And now we have to reset the params cache so that Bugzilla will re-read
# them.
delete Bugzilla->request_cache->{params};
}
-# Some files in the data directory must be world readable if and only if
-# we don't have a webserver group. Call this function to do this.
-# This will become a private function once all the datafile handling stuff
-# moves into this package
-
-# This sub is not perldoc'd for that reason - noone should know about it
-sub ChmodDataFile {
- my ($file, $mask) = @_;
- my $perm = 0770;
- if ((stat(bz_locations()->{'datadir'}))[2] & 0002) {
- $perm = 0777;
- }
- $perm = $perm & $mask;
- chmod $perm,$file;
-}
-
sub read_param_file {
my %params;
my $datadir = bz_locations()->{'datadir'};
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/Admin.pm b/Websites/bugs.webkit.org/Bugzilla/Config/Admin.pm
index 838e532..e6141cf 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/Admin.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/Admin.pm
@@ -35,7 +35,7 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::Admin::sortkey = "01";
+our $sortkey = 200;
sub get_param_list {
my $class = shift;
@@ -49,20 +49,14 @@
{
name => 'allowemailchange',
type => 'b',
- default => 0
+ default => 1
},
{
name => 'allowuserdeletion',
type => 'b',
default => 0
- },
-
- {
- name => 'supportwatchers',
- type => 'b',
- default => 0
- } );
+ });
return @param_list;
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/BugMove.pm b/Websites/bugs.webkit.org/Bugzilla/Config/Advanced.pm
similarity index 68%
copy from Websites/bugs.webkit.org/Bugzilla/Config/BugMove.pm
copy to Websites/bugs.webkit.org/Bugzilla/Config/Advanced.pm
index 87f6cbd..941cefc 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/BugMove.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/Advanced.pm
@@ -27,67 +27,42 @@
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
-#
+# Max Kanat-Alexander <mkanat@bugzilla.org>
-package Bugzilla::Config::BugMove;
-
+package Bugzilla::Config::Advanced;
use strict;
use Bugzilla::Config::Common;
-$Bugzilla::Config::BugMove::sortkey = "05";
+our $sortkey = 1700;
-sub get_param_list {
- my $class = shift;
- my @param_list = (
+use constant get_param_list => (
{
- name => 'move-enabled',
- type => 'b',
- default => 0
- },
-
- {
- name => 'move-button-text',
- type => 't',
- default => 'Move To Bugscape'
- },
-
- {
- name => 'move-to-url',
+ name => 'cookiedomain',
type => 't',
default => ''
},
{
- name => 'move-to-address',
+ name => 'inbound_proxies',
type => 't',
- default => 'bugzilla-import'
+ default => '',
+ checker => \&check_ip
},
{
- name => 'moved-from-address',
- type => 't',
- default => 'bugzilla-admin'
- },
-
- {
- name => 'movers',
+ name => 'proxy_url',
type => 't',
default => ''
},
{
- name => 'moved-default-product',
- type => 't',
- default => ''
+ name => 'strict_transport_security',
+ type => 's',
+ choices => ['off', 'this_domain_only', 'include_subdomains'],
+ default => 'off',
+ checker => \&check_multi
},
-
- {
- name => 'moved-default-component',
- type => 't',
- default => ''
- } );
- return @param_list;
-}
+);
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/Attachment.pm b/Websites/bugs.webkit.org/Bugzilla/Config/Attachment.pm
index 999c479..e6e3b7f 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/Attachment.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/Attachment.pm
@@ -35,7 +35,7 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::Attachment::sortkey = "025";
+our $sortkey = 400;
sub get_param_list {
my $class = shift;
@@ -58,17 +58,6 @@
type => 'b',
default => 0
},
- {
- name => 'allow_attach_url',
- type => 'b',
- default => 0
- },
- {
- name => 'maxpatchsize',
- type => 't',
- default => '1000',
- checker => \&check_numeric
- },
{
name => 'maxattachmentsize',
@@ -90,13 +79,6 @@
type => 't',
default => '0',
checker => \&check_numeric
- },
-
- {
- name => 'convert_uncompressed_images',
- type => 'b',
- default => 0,
- checker => \&check_image_converter
} );
return @param_list;
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/Auth.pm b/Websites/bugs.webkit.org/Bugzilla/Config/Auth.pm
index cbd9461..a61cab5 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/Auth.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/Auth.pm
@@ -35,7 +35,7 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::Auth::sortkey = "02";
+our $sortkey = 300;
sub get_param_list {
my $class = shift;
@@ -91,13 +91,6 @@
},
{
- name => 'loginnetmask',
- type => 't',
- default => '0',
- checker => \&check_netmask
- },
-
- {
name => 'requirelogin',
type => 'b',
default => '0'
@@ -128,6 +121,15 @@
type => 't',
default => q:.*:,
checker => \&check_regexp
+ },
+
+ {
+ name => 'password_complexity',
+ type => 's',
+ choices => [ 'no_constraints', 'mixed_letters', 'letters_numbers',
+ 'letters_numbers_specialchars' ],
+ default => 'no_constraints',
+ checker => \&check_multi
} );
return @param_list;
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/BugChange.pm b/Websites/bugs.webkit.org/Bugzilla/Config/BugChange.pm
index aec6e24..4e197c5 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/BugChange.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/BugChange.pm
@@ -36,7 +36,7 @@
use Bugzilla::Config::Common;
use Bugzilla::Status;
-$Bugzilla::Config::BugChange::sortkey = "03";
+our $sortkey = 500;
sub get_param_list {
my $class = shift;
@@ -81,24 +81,12 @@
},
{
- name => 'commentonclearresolution',
- type => 'b',
- default => 0
- },
-
- {
name => 'commentonchange_resolution',
type => 'b',
default => 0
},
{
- name => 'commentonreassignbycomponent',
- type => 'b',
- default => 0
- },
-
- {
name => 'commentonduplicate',
type => 'b',
default => 0
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/BugFields.pm b/Websites/bugs.webkit.org/Bugzilla/Config/BugFields.pm
index db5d0a2..d0de9da 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/BugFields.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/BugFields.pm
@@ -36,7 +36,7 @@
use Bugzilla::Config::Common;
use Bugzilla::Field;
-$Bugzilla::Config::BugFields::sortkey = "04";
+our $sortkey = 600;
sub get_param_list {
my $class = shift;
@@ -54,12 +54,6 @@
},
{
- name => 'showallproducts',
- type => 'b',
- default => 0
- },
-
- {
name => 'usetargetmilestone',
type => 'b',
default => 0
@@ -78,15 +72,15 @@
},
{
- name => 'usevotes',
+ name => 'usebugaliases',
type => 'b',
default => 0
},
{
- name => 'usebugaliases',
+ name => 'use_see_also',
type => 'b',
- default => 0
+ default => 1
},
{
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/Common.pm b/Websites/bugs.webkit.org/Bugzilla/Config/Common.pm
index 5b2cabb..00c6992 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/Common.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/Common.pm
@@ -34,8 +34,8 @@
use strict;
+use Email::Address;
use Socket;
-use Time::Zone;
use Bugzilla::Util;
use Bugzilla::Constants;
@@ -48,10 +48,10 @@
qw(check_multi check_numeric check_regexp check_url check_group
check_sslbase check_priority check_severity check_platform
check_opsys check_shadowdb check_urlbase check_webdotbase
- check_netmask check_user_verify_class check_image_converter
- check_mail_delivery_method check_notification check_timezone check_utf8
- check_bug_status check_smtp_auth
- check_maxattachmentsize
+ check_user_verify_class check_ip
+ check_mail_delivery_method check_notification check_utf8
+ check_bug_status check_smtp_auth check_theschwartz_available
+ check_maxattachmentsize check_email
);
# Checking functions for the various values
@@ -95,6 +95,14 @@
return $@;
}
+sub check_email {
+ my ($value) = @_;
+ if ($value !~ $Email::Address::mailbox) {
+ return "must be a valid email address.";
+ }
+ return "";
+}
+
sub check_sslbase {
my $url = shift;
if ($url ne '') {
@@ -121,6 +129,15 @@
return "";
}
+sub check_ip {
+ my $inbound_proxies = shift;
+ my @proxies = split(/[\s,]+/, $inbound_proxies);
+ foreach my $proxy (@proxies) {
+ validate_ip($proxy) || return "$proxy is not a valid IPv4 or IPv6 address";
+ }
+ return "";
+}
+
sub check_utf8 {
my $utf8 = shift;
# You cannot turn off the UTF-8 parameter if you've already converted
@@ -136,7 +153,7 @@
sub check_priority {
my ($value) = (@_);
my $legal_priorities = get_legal_field_values('priority');
- if (lsearch($legal_priorities, $value) < 0) {
+ if (!grep($_ eq $value, @$legal_priorities)) {
return "Must be a legal priority value: one of " .
join(", ", @$legal_priorities);
}
@@ -146,7 +163,7 @@
sub check_severity {
my ($value) = (@_);
my $legal_severities = get_legal_field_values('bug_severity');
- if (lsearch($legal_severities, $value) < 0) {
+ if (!grep($_ eq $value, @$legal_severities)) {
return "Must be a legal severity value: one of " .
join(", ", @$legal_severities);
}
@@ -156,7 +173,7 @@
sub check_platform {
my ($value) = (@_);
my $legal_platforms = get_legal_field_values('rep_platform');
- if (lsearch(['', @$legal_platforms], $value) < 0) {
+ if (!grep($_ eq $value, '', @$legal_platforms)) {
return "Must be empty or a legal platform value: one of " .
join(", ", @$legal_platforms);
}
@@ -166,7 +183,7 @@
sub check_opsys {
my ($value) = (@_);
my $legal_OS = get_legal_field_values('op_sys');
- if (lsearch(['', @$legal_OS], $value) < 0) {
+ if (!grep($_ eq $value, '', @$legal_OS)) {
return "Must be empty or a legal operating system value: one of " .
join(", ", @$legal_OS);
}
@@ -176,7 +193,7 @@
sub check_bug_status {
my $bug_status = shift;
my @closed_bug_statuses = map {$_->name} closed_bug_statuses();
- if (lsearch(\@closed_bug_statuses, $bug_status) < 0) {
+ if (!grep($_ eq $bug_status, @closed_bug_statuses)) {
return "Must be a valid closed status: one of " . join(', ', @closed_bug_statuses);
}
return "";
@@ -249,21 +266,6 @@
return "";
}
-sub check_netmask {
- my ($mask) = @_;
- my $res = check_numeric($mask);
- return $res if $res;
- if ($mask < 0 || $mask > 32) {
- return "an IPv4 netmask must be between 0 and 32 bits";
- }
- # Note that if we changed the netmask from anything apart from 32, then
- # existing logincookies which aren't for a single IP won't work
- # any more. We can't know which ones they are, though, so they'll just
- # take space until they're periodically cleared, later.
-
- return "";
-}
-
sub check_user_verify_class {
# doeditparams traverses the list of params, and for each one it checks,
# then updates. This means that if one param checker wants to look at
@@ -273,47 +275,39 @@
# the login method as LDAP, we won't notice, but all logins will fail.
# So don't do that.
+ my $params = Bugzilla->params;
my ($list, $entry) = @_;
$list || return 'You need to specify at least one authentication mechanism';
for my $class (split /,\s*/, $list) {
my $res = check_multi($class, $entry);
return $res if $res;
- if ($class eq 'DB') {
- # No params
- }
- elsif ($class eq 'RADIUS') {
- eval "require Authen::Radius";
- return "Error requiring Authen::Radius: '$@'" if $@;
- return "RADIUS servername (RADIUS_server) is missing" unless Bugzilla->params->{"RADIUS_server"};
- return "RADIUS_secret is empty" unless Bugzilla->params->{"RADIUS_secret"};
+ if ($class eq 'RADIUS') {
+ if (!Bugzilla->feature('auth_radius')) {
+ return "RADIUS support is not available. Run checksetup.pl"
+ . " for more details";
+ }
+ return "RADIUS servername (RADIUS_server) is missing"
+ if !$params->{"RADIUS_server"};
+ return "RADIUS_secret is empty" if !$params->{"RADIUS_secret"};
}
elsif ($class eq 'LDAP') {
- eval "require Net::LDAP";
- return "Error requiring Net::LDAP: '$@'" if $@;
- return "LDAP servername (LDAPserver) is missing" unless Bugzilla->params->{"LDAPserver"};
- return "LDAPBaseDN is empty" unless Bugzilla->params->{"LDAPBaseDN"};
- }
- else {
- return "Unknown user_verify_class '$class' in check_user_verify_class";
+ if (!Bugzilla->feature('auth_ldap')) {
+ return "LDAP support is not available. Run checksetup.pl"
+ . " for more details";
+ }
+ return "LDAP servername (LDAPserver) is missing"
+ if !$params->{"LDAPserver"};
+ return "LDAPBaseDN is empty" if !$params->{"LDAPBaseDN"};
}
}
return "";
}
-sub check_image_converter {
- my ($value, $hash) = @_;
- if ($value == 1){
- eval "require Image::Magick";
- return "Error requiring Image::Magick: '$@'" if $@;
- }
- return "";
-}
-
sub check_mail_delivery_method {
my $check = check_multi(@_);
return $check if $check;
my $mailer = shift;
- if ($mailer eq 'sendmail' && $^O =~ /MSWin32/i) {
+ if ($mailer eq 'sendmail' and ON_WINDOWS) {
# look for sendmail.exe
return "Failed to locate " . SENDMAIL_EXE
unless -e SENDMAIL_EXE;
@@ -349,22 +343,28 @@
"about the next stable release, you should select " .
"'latest_stable_release' instead";
}
- return "";
-}
-
-sub check_timezone {
- my $tz = shift;
- unless (defined(tz_offset($tz))) {
- return "must be empty or a legal timezone name, such as PDT or JST";
+ if ($option ne 'disabled' && !Bugzilla->feature('updates')) {
+ return "Some Perl modules are missing to get notifications about " .
+ "new releases. See the output of checksetup.pl for more information";
}
return "";
}
sub check_smtp_auth {
my $username = shift;
- if ($username) {
- eval "require Authen::SASL";
- return "Error requiring Authen::SASL: '$@'" if $@;
+ if ($username and !Bugzilla->feature('smtp_auth')) {
+ return "SMTP Authentication is not available. Run checksetup.pl for"
+ . " more details";
+ }
+ return "";
+}
+
+sub check_theschwartz_available {
+ my $use_queue = shift;
+ if ($use_queue && !Bugzilla->feature('jobqueue')) {
+ return "Using the job queue requires that you have certain Perl"
+ . " modules installed. See the output of checksetup.pl"
+ . " for more information";
}
return "";
}
@@ -446,7 +446,9 @@
=head1 DESCRIPTION
-All parameter checking functions are called with two parameters:
+All parameter checking functions are called with two parameters: the value to
+check, and a hash with the details of the param (type, default etc.) as defined
+in the relevant F<Bugzilla::Config::*> package.
=head2 Functions
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/Core.pm b/Websites/bugs.webkit.org/Bugzilla/Config/Core.pm
index b307dd7..1548dcd 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/Core.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/Core.pm
@@ -35,17 +35,9 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::Core::sortkey = "00";
+our $sortkey = 100;
-sub get_param_list {
- my $class = shift;
- my @param_list = (
- {
- name => 'maintainer',
- type => 't',
- default => 'THE MAINTAINER HAS NOT YET BEEN SET'
- },
-
+use constant get_param_list => (
{
name => 'urlbase',
type => 't',
@@ -54,10 +46,9 @@
},
{
- name => 'docs_urlbase',
- type => 't',
- default => 'docs/%lang%/html/',
- checker => \&check_url
+ name => 'ssl_redirect',
+ type => 'b',
+ default => 0
},
{
@@ -68,66 +59,10 @@
},
{
- name => 'ssl',
- type => 's',
- choices => ['never', 'authenticated sessions', 'always'],
- default => 'never'
- },
-
-
- {
- name => 'cookiedomain',
- type => 't',
- default => ''
- },
-
- {
name => 'cookiepath',
type => 't',
default => '/'
},
-
- {
- name => 'timezone',
- type => 't',
- default => '',
- checker => \&check_timezone
- },
-
- {
- name => 'utf8',
- type => 'b',
- default => '0',
- checker => \&check_utf8
- },
-
- {
- name => 'shutdownhtml',
- type => 'l',
- default => ''
- },
-
- {
- name => 'announcehtml',
- type => 'l',
- default => ''
- },
-
- {
- name => 'proxy_url',
- type => 't',
- default => ''
- },
-
- {
- name => 'upgrade_notification',
- type => 's',
- choices => ['development_snapshot', 'latest_stable_release',
- 'stable_branch_release', 'disabled'],
- default => 'latest_stable_release',
- checker => \&check_notification
- } );
- return @param_list;
-}
+);
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/DependencyGraph.pm b/Websites/bugs.webkit.org/Bugzilla/Config/DependencyGraph.pm
index db784c1..b217bfb 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/DependencyGraph.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/DependencyGraph.pm
@@ -35,7 +35,7 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::DependencyGraph::sortkey = "06";
+our $sortkey = 800;
sub get_param_list {
my $class = shift;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/BugMove.pm b/Websites/bugs.webkit.org/Bugzilla/Config/General.pm
similarity index 67%
copy from Websites/bugs.webkit.org/Bugzilla/Config/BugMove.pm
copy to Websites/bugs.webkit.org/Bugzilla/Config/General.pm
index 87f6cbd..0f04354 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/BugMove.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/General.pm
@@ -27,67 +27,57 @@
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
-#
+# Max Kanat-Alexander <mkanat@bugzilla.org>
-package Bugzilla::Config::BugMove;
-
+package Bugzilla::Config::General;
use strict;
-
use Bugzilla::Config::Common;
-$Bugzilla::Config::BugMove::sortkey = "05";
+our $sortkey = 150;
-sub get_param_list {
- my $class = shift;
- my @param_list = (
+use constant get_param_list => (
{
- name => 'move-enabled',
+ name => 'maintainer',
+ type => 't',
+ no_reset => '1',
+ default => '',
+ checker => \&check_email
+ },
+
+ {
+ name => 'docs_urlbase',
+ type => 't',
+ default => 'docs/%lang%/html/',
+ checker => \&check_url
+ },
+
+ {
+ name => 'utf8',
type => 'b',
- default => 0
+ default => '0',
+ checker => \&check_utf8
},
{
- name => 'move-button-text',
- type => 't',
- default => 'Move To Bugscape'
- },
-
- {
- name => 'move-to-url',
- type => 't',
+ name => 'shutdownhtml',
+ type => 'l',
default => ''
},
{
- name => 'move-to-address',
- type => 't',
- default => 'bugzilla-import'
- },
-
- {
- name => 'moved-from-address',
- type => 't',
- default => 'bugzilla-admin'
- },
-
- {
- name => 'movers',
- type => 't',
+ name => 'announcehtml',
+ type => 'l',
default => ''
},
{
- name => 'moved-default-product',
- type => 't',
- default => ''
+ name => 'upgrade_notification',
+ type => 's',
+ choices => ['development_snapshot', 'latest_stable_release',
+ 'stable_branch_release', 'disabled'],
+ default => 'latest_stable_release',
+ checker => \&check_notification
},
-
- {
- name => 'moved-default-component',
- type => 't',
- default => ''
- } );
- return @param_list;
-}
+);
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/GroupSecurity.pm b/Websites/bugs.webkit.org/Bugzilla/Config/GroupSecurity.pm
index bfc4f22..6296583 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/GroupSecurity.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/GroupSecurity.pm
@@ -36,7 +36,7 @@
use Bugzilla::Config::Common;
use Bugzilla::Group;
-$Bugzilla::Config::GroupSecurity::sortkey = "07";
+our $sortkey = 900;
sub get_param_list {
my $class = shift;
@@ -49,12 +49,6 @@
},
{
- name => 'useentrygroupdefault',
- type => 'b',
- default => 0
- },
-
- {
name => 'chartgroup',
type => 's',
choices => \&_get_all_group_names,
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/LDAP.pm b/Websites/bugs.webkit.org/Bugzilla/Config/LDAP.pm
index a9b4638..e47f923 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/LDAP.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/LDAP.pm
@@ -35,7 +35,7 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::LDAP::sortkey = "09";
+our $sortkey = 1000;
sub get_param_list {
my $class = shift;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/MTA.pm b/Websites/bugs.webkit.org/Bugzilla/Config/MTA.pm
index 37d99d9..c90e5dc7 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/MTA.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/MTA.pm
@@ -36,7 +36,7 @@
use Bugzilla::Config::Common;
use Email::Send;
-$Bugzilla::Config::MTA::sortkey = "10";
+our $sortkey = 1200;
sub get_param_list {
my $class = shift;
@@ -58,9 +58,10 @@
},
{
- name => 'sendmailnow',
+ name => 'use_mailer_queue',
type => 'b',
- default => 1
+ default => 0,
+ checker => \&check_theschwartz_available,
},
{
@@ -90,7 +91,6 @@
default => 7,
checker => \&check_numeric
},
-
{
name => 'globalwatchers',
type => 't',
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/PatchViewer.pm b/Websites/bugs.webkit.org/Bugzilla/Config/PatchViewer.pm
index 8de04ef..6bd9557 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/PatchViewer.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/PatchViewer.pm
@@ -35,7 +35,7 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::PatchViewer::sortkey = "11";
+our $sortkey = 1300;
sub get_param_list {
my $class = shift;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/Query.pm b/Websites/bugs.webkit.org/Bugzilla/Config/Query.pm
index 74e2650..4038c13 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/Query.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/Query.pm
@@ -35,7 +35,7 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::Query::sortkey = "12";
+our $sortkey = 1400;
sub get_param_list {
my $class = shift;
@@ -58,28 +58,34 @@
{
name => 'mybugstemplate',
type => 't',
- default => 'buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailreporter1=1&emailtype1=exact&email1=%userid%&field0-0-0=bug_status&type0-0-0=notequals&value0-0-0=UNCONFIRMED&field0-0-1=reporter&type0-0-1=equals&value0-0-1=%userid%'
+ default => 'buglist.cgi?resolution=---&emailassigned_to1=1&emailreporter1=1&emailtype1=exact&email1=%userid%'
},
{
name => 'defaultquery',
type => 't',
- default => 'bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&order=Importance&long_desc_type=substring'
+ default => 'resolution=---&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&emaillongdesc3=1&order=Importance&long_desc_type=substring'
},
{
- name => 'quicksearch_comment_cutoff',
- type => 't',
- default => '4',
- checker => \&check_numeric
- },
-
- {
- name => 'specific_search_allow_empty_words',
+ name => 'search_allow_no_criteria',
type => 'b',
- default => 0
- }
-
+ default => 1
+ },
+
+ {
+ name => 'default_search_limit',
+ type => 't',
+ default => '500',
+ checker => \&check_numeric
+ },
+
+ {
+ name => 'max_search_results',
+ type => 't',
+ default => '10000',
+ checker => \&check_numeric
+ },
);
return @param_list;
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/RADIUS.pm b/Websites/bugs.webkit.org/Bugzilla/Config/RADIUS.pm
index 6701d6f..bc072a9 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/RADIUS.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/RADIUS.pm
@@ -25,7 +25,7 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::RADIUS::sortkey = "09";
+our $sortkey = 1100;
sub get_param_list {
my $class = shift;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/ShadowDB.pm b/Websites/bugs.webkit.org/Bugzilla/Config/ShadowDB.pm
index f9af4fb..a605b23 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/ShadowDB.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/ShadowDB.pm
@@ -35,7 +35,7 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::ShadowDB::sortkey = "13";
+our $sortkey = 1500;
sub get_param_list {
my $class = shift;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/UserMatch.pm b/Websites/bugs.webkit.org/Bugzilla/Config/UserMatch.pm
index 819247e..38d2cb0 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/UserMatch.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Config/UserMatch.pm
@@ -35,7 +35,7 @@
use Bugzilla::Config::Common;
-$Bugzilla::Config::UserMatch::sortkey = "14";
+our $sortkey = 1600;
sub get_param_list {
my $class = shift;
@@ -47,10 +47,9 @@
},
{
- name => 'usermatchmode',
- type => 's',
- choices => ['off', 'wildcard', 'search'],
- default => 'off'
+ name => 'ajax_user_autocompletion',
+ type => 'b',
+ default => '1',
},
{
diff --git a/Websites/bugs.webkit.org/Bugzilla/Constants.pm b/Websites/bugs.webkit.org/Bugzilla/Constants.pm
index 049630f0..7a4c573f 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Constants.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Constants.pm
@@ -35,10 +35,14 @@
# For bz_locations
use File::Basename;
+use Memoize;
@Bugzilla::Constants::EXPORT = qw(
BUGZILLA_VERSION
+ REMOTE_FILE
+ LOCAL_FILE
+
bz_locations
IS_NULL
@@ -55,9 +59,9 @@
AUTH_LOGINFAILED
AUTH_DISABLED
AUTH_NO_SUCH_USER
+ AUTH_LOCKOUT
USER_PASSWORD_MIN_LENGTH
- USER_PASSWORD_MAX_LENGTH
LOGIN_OPTIONAL
LOGIN_NORMAL
@@ -79,9 +83,9 @@
DEFAULT_COLUMN_LIST
DEFAULT_QUERY_NAME
+ DEFAULT_MILESTONE
- QUERY_LIST
- LIST_OF_BUGS
+ SAVE_NUM_SEARCHES
COMMENT_COLS
COMMENT_COLS_WRAP
@@ -90,27 +94,26 @@
CMT_NORMAL
CMT_DUPE_OF
CMT_HAS_DUPE
- CMT_POPULAR_VOTES
- CMT_MOVED_TO
+ CMT_ATTACHMENT_CREATED
+ CMT_ATTACHMENT_UPDATED
THROW_ERROR
RELATIONSHIPS
- REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_VOTER REL_GLOBAL_WATCHER
+ REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_GLOBAL_WATCHER
REL_ANY
POS_EVENTS
EVT_OTHER EVT_ADDED_REMOVED EVT_COMMENT EVT_ATTACHMENT EVT_ATTACHMENT_DATA
EVT_PROJ_MANAGEMENT EVT_OPENED_CLOSED EVT_KEYWORD EVT_CC EVT_DEPEND_BLOCK
-
+ EVT_BUG_CREATED
+
NEG_EVENTS
EVT_UNCONFIRMED EVT_CHANGED_BY_ME
GLOBAL_EVENTS
EVT_FLAG_REQUESTED EVT_REQUESTED_FLAG
- FULLTEXT_BUGLIST_LIMIT
-
ADMIN_GROUP_NAME
PER_PRODUCT_PRIVILEGES
@@ -123,15 +126,31 @@
FIELD_TYPE_MULTI_SELECT
FIELD_TYPE_TEXTAREA
FIELD_TYPE_DATETIME
+ FIELD_TYPE_BUG_ID
+ FIELD_TYPE_BUG_URLS
+ FIELD_TYPE_KEYWORDS
+
+ EMPTY_DATETIME_REGEX
+
+ ABNORMAL_SELECTS
+
+ TIMETRACKING_FIELDS
USAGE_MODE_BROWSER
USAGE_MODE_CMDLINE
- USAGE_MODE_WEBSERVICE
+ USAGE_MODE_XMLRPC
USAGE_MODE_EMAIL
+ USAGE_MODE_JSON
+ USAGE_MODE_TEST
ERROR_MODE_WEBPAGE
ERROR_MODE_DIE
ERROR_MODE_DIE_SOAP_FAULT
+ ERROR_MODE_JSON_RPC
+ ERROR_MODE_TEST
+
+ COLOR_ERROR
+ COLOR_SUCCESS
INSTALLATION_MODE_INTERACTIVE
INSTALLATION_MODE_NON_INTERACTIVE
@@ -139,19 +158,44 @@
DB_MODULE
ROOT_USER
ON_WINDOWS
+ ON_ACTIVESTATE
MAX_TOKEN_AGE
MAX_LOGINCOOKIE_AGE
+ MAX_SUDO_TOKEN_AGE
+ MAX_LOGIN_ATTEMPTS
+ LOGIN_LOCKOUT_INTERVAL
+ MAX_STS_AGE
SAFE_PROTOCOLS
+ LEGAL_CONTENT_TYPES
MIN_SMALLINT
MAX_SMALLINT
+ MAX_INT_32
MAX_LEN_QUERY_NAME
+ MAX_CLASSIFICATION_SIZE
+ MAX_PRODUCT_SIZE
MAX_MILESTONE_SIZE
MAX_COMPONENT_SIZE
+ MAX_FIELD_VALUE_SIZE
MAX_FREETEXT_LENGTH
+ MAX_BUG_URL_LENGTH
+ MAX_POSSIBLE_DUPLICATES
+
+ PASSWORD_DIGEST_ALGORITHM
+ PASSWORD_SALT_LENGTH
+
+ CGI_URI_LIMIT
+
+ PRIVILEGES_REQUIRED_NONE
+ PRIVILEGES_REQUIRED_REPORTER
+ PRIVILEGES_REQUIRED_ASSIGNEE
+ PRIVILEGES_REQUIRED_EMPOWERED
+
+ AUDIT_CREATE
+ AUDIT_REMOVE
);
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
@@ -159,7 +203,11 @@
# CONSTANTS
#
# Bugzilla version
-use constant BUGZILLA_VERSION => "3.2.3";
+use constant BUGZILLA_VERSION => "4.2.1";
+
+# Location of the remote and local XML files to track new releases.
+use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
+use constant LOCAL_FILE => 'bugzilla-update.xml'; # Relative to datadir.
# These are unique values that are unlikely to match a string or a number,
# to be used in criteria for match() functions and other things. They start
@@ -212,10 +260,10 @@
use constant AUTH_LOGINFAILED => 3;
use constant AUTH_DISABLED => 4;
use constant AUTH_NO_SUCH_USER => 5;
+use constant AUTH_LOCKOUT => 6;
-# The minimum and maximum lengths a password must have.
-use constant USER_PASSWORD_MIN_LENGTH => 3;
-use constant USER_PASSWORD_MAX_LENGTH => 16;
+# The minimum length a password must have.
+use constant USER_PASSWORD_MIN_LENGTH => 6;
use constant LOGIN_OPTIONAL => 0;
use constant LOGIN_NORMAL => 1;
@@ -225,18 +273,6 @@
use constant LOGOUT_CURRENT => 1;
use constant LOGOUT_KEEP_CURRENT => 2;
-use constant contenttypes =>
- {
- "html"=> "text/html" ,
- "rdf" => "application/rdf+xml" ,
- "atom"=> "application/atom+xml" ,
- "xml" => "application/xml" ,
- "js" => "application/x-javascript" ,
- "csv" => "text/csv" ,
- "png" => "image/png" ,
- "ics" => "text/calendar" ,
- };
-
use constant GRANT_DIRECT => 0;
use constant GRANT_REGEXP => 2;
@@ -249,20 +285,23 @@
# The default list of columns for buglist.cgi
use constant DEFAULT_COLUMN_LIST => (
- "bug_severity", "priority", "op_sys","assigned_to",
- "bug_status", "resolution", "short_desc"
+ "product", "component", "assigned_to",
+ "bug_status", "resolution", "short_desc", "changeddate"
);
# Used by query.cgi and buglist.cgi as the named-query name
# for the default settings.
use constant DEFAULT_QUERY_NAME => '(Default query)';
-# The possible types for saved searches.
-use constant QUERY_LIST => 0;
-use constant LIST_OF_BUGS => 1;
+# The default "defaultmilestone" created for products.
+use constant DEFAULT_MILESTONE => '---';
-# The column width (cols attribute) of HTML textareas for inputting comments.
+# How many of the user's most recent searches to save.
+use constant SAVE_NUM_SEARCHES => 10;
+
+# The column width for comment textareas and comments in bugmails.
use constant COMMENT_COLS => 80;
+#if WEBKIT_CHANGES
# The column width at which to wrap comments prior to display -- using
# Perl's Text::Wrap::wrap(). Only Bugzilla/Util.pm's wrap_comment() method
# uses this constant.
@@ -273,6 +312,7 @@
# "white-space: pre-wrap" in the CSS to do line-wrapping instead. We
# arbitrarily choose 8000, which is enough for a 100-line paragraph.
use constant COMMENT_COLS_WRAP => 8000;
+#endif // WEBKIT_CHANGES
# Used in _check_comment(). Gives the max length allowed for a comment.
use constant MAX_COMMENT_LENGTH => 65535;
@@ -280,8 +320,10 @@
use constant CMT_NORMAL => 0;
use constant CMT_DUPE_OF => 1;
use constant CMT_HAS_DUPE => 2;
-use constant CMT_POPULAR_VOTES => 3;
-use constant CMT_MOVED_TO => 4;
+# Type 3 was CMT_POPULAR_VOTES, which moved to the Voting extension.
+# Type 4 was CMT_MOVED_TO, which moved to the OldBugMove extension.
+use constant CMT_ATTACHMENT_CREATED => 5;
+use constant CMT_ATTACHMENT_UPDATED => 6;
# Determine whether a validation routine should return 0 or throw
# an error when the validation fails.
@@ -291,11 +333,20 @@
use constant REL_QA => 1;
use constant REL_REPORTER => 2;
use constant REL_CC => 3;
-use constant REL_VOTER => 4;
+# REL 4 was REL_VOTER, before it was moved ino an extension.
use constant REL_GLOBAL_WATCHER => 5;
-use constant RELATIONSHIPS => REL_ASSIGNEE, REL_QA, REL_REPORTER, REL_CC,
- REL_VOTER, REL_GLOBAL_WATCHER;
+# We need these strings for the X-Bugzilla-Reasons header
+# Note: this hash uses "," rather than "=>" to avoid auto-quoting of the LHS.
+# This should be accessed through Bugzilla::BugMail::relationships() instead
+# of being accessed directly.
+use constant RELATIONSHIPS => {
+ REL_ASSIGNEE , "AssignedTo",
+ REL_REPORTER , "Reporter",
+ REL_QA , "QAcontact",
+ REL_CC , "CC",
+ REL_GLOBAL_WATCHER, "GlobalWatcher"
+};
# Used for global events like EVT_FLAG_REQUESTED
use constant REL_ANY => 100;
@@ -316,11 +367,12 @@
use constant EVT_KEYWORD => 7;
use constant EVT_CC => 8;
use constant EVT_DEPEND_BLOCK => 9;
+use constant EVT_BUG_CREATED => 10;
use constant POS_EVENTS => EVT_OTHER, EVT_ADDED_REMOVED, EVT_COMMENT,
EVT_ATTACHMENT, EVT_ATTACHMENT_DATA,
EVT_PROJ_MANAGEMENT, EVT_OPENED_CLOSED, EVT_KEYWORD,
- EVT_CC, EVT_DEPEND_BLOCK;
+ EVT_CC, EVT_DEPEND_BLOCK, EVT_BUG_CREATED;
use constant EVT_UNCONFIRMED => 50;
use constant EVT_CHANGED_BY_ME => 51;
@@ -334,10 +386,6 @@
use constant GLOBAL_EVENTS => EVT_FLAG_REQUESTED, EVT_REQUESTED_FLAG;
-# Number of bugs to return in a buglist when performing
-# a fulltext search.
-use constant FULLTEXT_BUGLIST_LIMIT => 200;
-
# Default administration group name.
use constant ADMIN_GROUP_NAME => 'admin';
@@ -362,28 +410,84 @@
use constant FIELD_TYPE_MULTI_SELECT => 3;
use constant FIELD_TYPE_TEXTAREA => 4;
use constant FIELD_TYPE_DATETIME => 5;
+use constant FIELD_TYPE_BUG_ID => 6;
+use constant FIELD_TYPE_BUG_URLS => 7;
+use constant FIELD_TYPE_KEYWORDS => 8;
+
+use constant EMPTY_DATETIME_REGEX => qr/^[0\-:\sA-Za-z]+$/;
+
+# See the POD for Bugzilla::Field/is_abnormal to see why these are listed
+# here.
+use constant ABNORMAL_SELECTS => {
+ classification => 1,
+ component => 1,
+ product => 1,
+};
+
+# The fields from fielddefs that are blocked from non-timetracking users.
+# work_time is sometimes called actual_time.
+use constant TIMETRACKING_FIELDS =>
+ qw(estimated_time remaining_time work_time actual_time
+ percentage_complete deadline);
# The maximum number of days a token will remain valid.
use constant MAX_TOKEN_AGE => 3;
# How many days a logincookie will remain valid if not used.
use constant MAX_LOGINCOOKIE_AGE => 30;
+# How many seconds (default is 6 hours) a sudo cookie remains valid.
+use constant MAX_SUDO_TOKEN_AGE => 21600;
+
+# Maximum failed logins to lock account for this IP
+use constant MAX_LOGIN_ATTEMPTS => 5;
+# If the maximum login attempts occur during this many minutes, the
+# account is locked.
+use constant LOGIN_LOCKOUT_INTERVAL => 30;
+
+# The maximum number of seconds the Strict-Transport-Security header
+# will remain valid. Default is one week.
+use constant MAX_STS_AGE => 604800;
# Protocols which are considered as safe.
use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
- 'irc', 'mid', 'news', 'nntp', 'prospero', 'telnet',
- 'view-source', 'wais');
+ 'irc', 'ircs', 'mid', 'news', 'nntp', 'prospero',
+ 'telnet', 'view-source', 'wais');
+
+# Valid MIME types for attachments.
+use constant LEGAL_CONTENT_TYPES => ('application', 'audio', 'image', 'message',
+ 'model', 'multipart', 'text', 'video');
+
+use constant contenttypes =>
+ {
+ "html"=> "text/html" ,
+ "rdf" => "application/rdf+xml" ,
+ "atom"=> "application/atom+xml" ,
+ "xml" => "application/xml" ,
+ "js" => "application/x-javascript" ,
+ "json"=> "application/json" ,
+ "csv" => "text/csv" ,
+ "png" => "image/png" ,
+ "ics" => "text/calendar" ,
+ };
# Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode.
use constant USAGE_MODE_BROWSER => 0;
use constant USAGE_MODE_CMDLINE => 1;
-use constant USAGE_MODE_WEBSERVICE => 2;
+use constant USAGE_MODE_XMLRPC => 2;
use constant USAGE_MODE_EMAIL => 3;
+use constant USAGE_MODE_JSON => 4;
+use constant USAGE_MODE_TEST => 5;
# Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE
# usually). Use with Bugzilla->error_mode.
use constant ERROR_MODE_WEBPAGE => 0;
use constant ERROR_MODE_DIE => 1;
use constant ERROR_MODE_DIE_SOAP_FAULT => 2;
+use constant ERROR_MODE_JSON_RPC => 3;
+use constant ERROR_MODE_TEST => 4;
+
+# The ANSI colors of messages that command-line scripts use
+use constant COLOR_ERROR => 'red';
+use constant COLOR_SUCCESS => 'green';
# The various modes that checksetup.pl can run in.
use constant INSTALLATION_MODE_INTERACTIVE => 0;
@@ -391,17 +495,21 @@
# Data about what we require for different databases.
use constant DB_MODULE => {
- 'mysql' => {db => 'Bugzilla::DB::Mysql', db_version => '4.1.2',
+ # MySQL 5.0.15 was the first production 5.0.x release.
+ 'mysql' => {db => 'Bugzilla::DB::Mysql', db_version => '5.0.15',
dbd => {
package => 'DBD-mysql',
module => 'DBD::mysql',
# Disallow development versions
blacklist => ['_'],
- # For UTF-8 support
- version => '4.00',
+ # For UTF-8 support. 4.001 makes sure that blobs aren't
+ # marked as UTF-8.
+ version => '4.001',
},
name => 'MySQL'},
- 'pg' => {db => 'Bugzilla::DB::Pg', db_version => '8.00.0000',
+ # Also see Bugzilla::DB::Pg::bz_check_server_version, which has special
+ # code to require DBD::Pg 2.17.2 for PostgreSQL 9 and above.
+ 'pg' => {db => 'Bugzilla::DB::Pg', db_version => '8.03.0000',
dbd => {
package => 'DBD-Pg',
module => 'DBD::Pg',
@@ -415,30 +523,86 @@
version => '1.19',
},
name => 'Oracle'},
+ # SQLite 3.6.22 fixes a WHERE clause problem that may affect us.
+ sqlite => {db => 'Bugzilla::DB::Sqlite', db_version => '3.6.22',
+ dbd => {
+ package => 'DBD-SQLite',
+ module => 'DBD::SQLite',
+ # 1.29 is the version that contains 3.6.22.
+ version => '1.29',
+ },
+ name => 'SQLite'},
};
+# True if we're on Win32.
+use constant ON_WINDOWS => ($^O =~ /MSWin32/i) ? 1 : 0;
+# True if we're using ActiveState Perl (as opposed to Strawberry) on Windows.
+use constant ON_ACTIVESTATE => eval { &Win32::BuildNumber };
+
# The user who should be considered "root" when we're giving
# instructions to Bugzilla administrators.
-use constant ROOT_USER => $^O =~ /MSWin32/i ? 'Administrator' : 'root';
-
-# True if we're on Win32.
-use constant ON_WINDOWS => ($^O =~ /MSWin32/i);
+use constant ROOT_USER => ON_WINDOWS ? 'Administrator' : 'root';
use constant MIN_SMALLINT => -32768;
use constant MAX_SMALLINT => 32767;
+use constant MAX_INT_32 => 2147483647;
# The longest that a saved search name can be.
use constant MAX_LEN_QUERY_NAME => 64;
+# The longest classification name allowed.
+use constant MAX_CLASSIFICATION_SIZE => 64;
+
+# The longest product name allowed.
+use constant MAX_PRODUCT_SIZE => 64;
+
# The longest milestone name allowed.
use constant MAX_MILESTONE_SIZE => 20;
# The longest component name allowed.
use constant MAX_COMPONENT_SIZE => 64;
+# The maximum length for values of <select> fields.
+use constant MAX_FIELD_VALUE_SIZE => 64;
+
# Maximum length allowed for free text fields.
use constant MAX_FREETEXT_LENGTH => 255;
+# The longest a bug URL in a BUG_URLS field can be.
+use constant MAX_BUG_URL_LENGTH => 255;
+
+# The largest number of possible duplicates that Bug::possible_duplicates
+# will return.
+use constant MAX_POSSIBLE_DUPLICATES => 25;
+
+# This is the name of the algorithm used to hash passwords before storing
+# them in the database. This can be any string that is valid to pass to
+# Perl's "Digest" module. Note that if you change this, it won't take
+# effect until a user changes his password.
+use constant PASSWORD_DIGEST_ALGORITHM => 'SHA-256';
+# How long of a salt should we use? Note that if you change this, none
+# of your users will be able to log in until they reset their passwords.
+use constant PASSWORD_SALT_LENGTH => 8;
+
+# Certain scripts redirect to GET even if the form was submitted originally
+# via POST such as buglist.cgi. This value determines whether the redirect
+# can be safely done or not based on the web server's URI length setting.
+use constant CGI_URI_LIMIT => 8000;
+
+# If the user isn't allowed to change a field, we must tell him who can.
+# We store the required permission set into the $PrivilegesRequired
+# variable which gets passed to the error template.
+
+use constant PRIVILEGES_REQUIRED_NONE => 0;
+use constant PRIVILEGES_REQUIRED_REPORTER => 1;
+use constant PRIVILEGES_REQUIRED_ASSIGNEE => 2;
+use constant PRIVILEGES_REQUIRED_EMPOWERED => 3;
+
+# Special field values used in the audit_log table to mean either
+# "we just created this object" or "we just deleted this object".
+use constant AUDIT_CREATE => '__create__';
+use constant AUDIT_REMOVE => '__remove__';
+
sub bz_locations {
# We know that Bugzilla/Constants.pm must be in %INC at this point.
# So the only question is, what's the name of the directory
@@ -466,6 +630,7 @@
$datadir = "data";
}
+ $datadir = "$libpath/$datadir";
# We have to return absolute paths for mod_perl.
# That means that if you modify these paths, they must be absolute paths.
return {
@@ -475,20 +640,26 @@
# make sure this still points to the CGIs.
'cgi_path' => $libpath,
'templatedir' => "$libpath/template",
+ 'template_cache' => "$datadir/template",
'project' => $project,
'localconfig' => "$libpath/$localconfig",
- 'datadir' => "$libpath/$datadir",
- 'attachdir' => "$libpath/$datadir/attachments",
+ 'datadir' => $datadir,
+ 'attachdir' => "$datadir/attachments",
'skinsdir' => "$libpath/skins",
+ 'graphsdir' => "$libpath/graphs",
# $webdotdir must be in the web server's tree somewhere. Even if you use a
# local dot, we output images to there. Also, if $webdotdir is
# not relative to the bugzilla root directory, you'll need to
# change showdependencygraph.cgi to set image_url to the correct
# location.
# The script should really generate these graphs directly...
- 'webdotdir' => "$libpath/$datadir/webdot",
+ 'webdotdir' => "$datadir/webdot",
'extensionsdir' => "$libpath/extensions",
};
}
+# This makes us not re-compute all the bz_locations data every time it's
+# called.
+BEGIN { memoize('bz_locations') };
+
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/DB.pm b/Websites/bugs.webkit.org/Bugzilla/DB.pm
index d68c5dc..0c84163 100644
--- a/Websites/bugs.webkit.org/Bugzilla/DB.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/DB.pm
@@ -37,7 +37,7 @@
use Bugzilla::Constants;
use Bugzilla::Install::Requirements;
-use Bugzilla::Install::Util qw(vers_cmp);
+use Bugzilla::Install::Util qw(vers_cmp install_string);
use Bugzilla::Install::Localconfig;
use Bugzilla::Util;
use Bugzilla::Error;
@@ -52,7 +52,6 @@
use constant BLOB_TYPE => DBI::SQL_BLOB;
use constant ISOLATION_LEVEL => 'REPEATABLE READ';
-use constant GROUPBY_REGEXP => '(?:.*\s+AS\s+)?(\w+(\.\w+)?)(?:\s+(ASC|DESC))?$';
# Set default values for what used to be the enum types. These values
# are no longer stored in localconfig. If we are upgrading from a
@@ -66,15 +65,51 @@
use constant ENUM_DEFAULTS => {
bug_severity => ['blocker', 'critical', 'major', 'normal',
'minor', 'trivial', 'enhancement'],
- priority => ["P1","P2","P3","P4","P5"],
+ priority => ["Highest", "High", "Normal", "Low", "Lowest", "---"],
op_sys => ["All","Windows","Mac OS","Linux","Other"],
rep_platform => ["All","PC","Macintosh","Other"],
- bug_status => ["UNCONFIRMED","NEW","ASSIGNED","REOPENED","RESOLVED",
- "VERIFIED","CLOSED"],
- resolution => ["","FIXED","INVALID","WONTFIX", "DUPLICATE","WORKSFORME",
- "MOVED"],
+ bug_status => ["UNCONFIRMED","CONFIRMED","IN_PROGRESS","RESOLVED",
+ "VERIFIED"],
+ resolution => ["","FIXED","INVALID","WONTFIX", "DUPLICATE","WORKSFORME"],
};
+# The character that means "OR" in a boolean fulltext search. If empty,
+# the database doesn't support OR searches in fulltext searches.
+# Used by Bugzilla::Bug::possible_duplicates.
+use constant FULLTEXT_OR => '';
+
+# These are used in regular expressions to mean "the start or end of a word".
+#
+# We don't use [[:<:]] and [[:>:]], even though they mean
+# "start and end of a word" and are supported by both MySQL and PostgreSQL,
+# because they don't work if your search starts or ends with a non-alphanumeric
+# character, and there's a fair chance somebody will want to use the "word"
+# search to search flags for something like "review+".
+#
+# We do use [:almum:] because it is supported by at least MySQL and
+# PostgreSQL, and hopefully will get us as much Unicode support as possible,
+# depending on how well the regexp engines of the various databases support
+# Unicode.
+use constant WORD_START => '(^|[^[:alnum:]])';
+use constant WORD_END => '($|[^[:alnum:]])';
+
+# On most databases, in order to drop an index, you have to first drop
+# the foreign keys that use that index. However, on some databases,
+# dropping the FK immediately before dropping the index causes problems
+# and doesn't need to be done anyway, so those DBs set this to 0.
+use constant INDEX_DROPS_REQUIRE_FK_DROPS => 1;
+
+#####################################################################
+# Overridden Superclass Methods
+#####################################################################
+
+sub quote {
+ my $self = shift;
+ my $retval = $self->SUPER::quote(@_);
+ trick_taint($retval) if defined $retval;
+ return $retval;
+}
+
#####################################################################
# Connection Methods
#####################################################################
@@ -84,22 +119,27 @@
die "Tried to connect to non-existent shadowdb"
unless $params->{'shadowdb'};
- my $lc = Bugzilla->localconfig;
+ # Instead of just passing in a new hashref, we locally modify the
+ # values of "localconfig", because some drivers access it while
+ # connecting.
+ my %connect_params = %{ Bugzilla->localconfig };
+ $connect_params{db_host} = $params->{'shadowdbhost'};
+ $connect_params{db_name} = $params->{'shadowdb'};
+ $connect_params{db_port} = $params->{'shadowdbport'};
+ $connect_params{db_sock} = $params->{'shadowdbsock'};
- return _connect($lc->{db_driver}, $params->{"shadowdbhost"},
- $params->{'shadowdb'}, $params->{"shadowdbport"},
- $params->{"shadowdbsock"}, $lc->{db_user}, $lc->{db_pass});
+ return _connect(\%connect_params);
}
sub connect_main {
my $lc = Bugzilla->localconfig;
- return _connect($lc->{db_driver}, $lc->{db_host}, $lc->{db_name}, $lc->{db_port},
- $lc->{db_sock}, $lc->{db_user}, $lc->{db_pass});
+ return _connect(Bugzilla->localconfig);
}
sub _connect {
- my ($driver, $host, $dbname, $port, $sock, $user, $pass) = @_;
+ my ($params) = @_;
+ my $driver = $params->{db_driver};
my $pkg_module = DB_MODULE->{lc($driver)}->{db};
# do the actual import
@@ -108,7 +148,7 @@
. " localconfig: " . $@);
# instantiate the correct DB specific module
- my $dbh = $pkg_module->new($user, $pass, $host, $dbname, $port, $sock);
+ my $dbh = $pkg_module->new($params);
return $dbh;
}
@@ -128,36 +168,16 @@
my $lc = Bugzilla->localconfig;
my $db = DB_MODULE->{lc($lc->{db_driver})};
+
# Only certain values are allowed for $db_driver.
if (!defined $db) {
die "$lc->{db_driver} is not a valid choice for \$db_driver in"
. bz_locations()->{'localconfig'};
}
- die("It is not safe to run Bugzilla inside the 'mysql' database.\n"
- . "Please pick a different value for \$db_name in localconfig.")
- if $lc->{db_name} eq 'mysql';
-
# Check the existence and version of the DBD that we need.
- my $dbd = $db->{dbd};
- my $sql_server = $db->{name};
- my $sql_want = $db->{db_version};
- unless (have_vers($dbd, $output)) {
- my $command = install_command($dbd);
- my $root = ROOT_USER;
- my $dbd_mod = $dbd->{module};
- my $dbd_ver = $dbd->{version};
- my $version = $dbd_ver ? " $dbd_ver or higher" : '';
- print <<EOT;
-
-For $sql_server, Bugzilla requires that perl's $dbd_mod $dbd_ver or later be
-installed. To install this module, run the following command (as $root):
-
- $command
-
-EOT
- exit;
- }
+ my $dbd = $db->{dbd};
+ _bz_check_dbd($db, $output);
# We don't try to connect to the actual database if $db_check is
# disabled.
@@ -168,28 +188,62 @@
# And now check the version of the database server itself.
my $dbh = _get_no_db_connection();
+ $dbh->bz_check_server_version($db, $output);
- printf("Checking for %15s %-9s ", $sql_server, "(v$sql_want)")
- if $output;
- my $sql_vers = $dbh->bz_server_version;
- $dbh->disconnect;
+ print "\n" if $output;
+}
+
+sub _bz_check_dbd {
+ my ($db, $output) = @_;
+
+ my $dbd = $db->{dbd};
+ unless (have_vers($dbd, $output)) {
+ my $sql_server = $db->{name};
+ my $command = install_command($dbd);
+ my $root = ROOT_USER;
+ my $dbd_mod = $dbd->{module};
+ my $dbd_ver = $dbd->{version};
+ die <<EOT;
+
+For $sql_server, Bugzilla requires that perl's $dbd_mod $dbd_ver or later be
+installed. To install this module, run the following command (as $root):
+
+ $command
+
+EOT
+ }
+}
+
+sub bz_check_server_version {
+ my ($self, $db, $output) = @_;
+
+ my $sql_vers = $self->bz_server_version;
+ $self->disconnect;
+
+ my $sql_want = $db->{db_version};
+ my $version_ok = vers_cmp($sql_vers, $sql_want) > -1 ? 1 : 0;
+
+ my $sql_server = $db->{name};
+ if ($output) {
+ Bugzilla::Install::Requirements::_checking_for({
+ package => $sql_server, wanted => $sql_want,
+ found => $sql_vers, ok => $version_ok });
+ }
# Check what version of the database server is installed and let
# the user know if the version is too old to be used with Bugzilla.
- if ( vers_cmp($sql_vers,$sql_want) > -1 ) {
- print "ok: found v$sql_vers\n" if $output;
- } else {
- print <<EOT;
+ if (!$version_ok) {
+ die <<EOT;
Your $sql_server v$sql_vers is too old. Bugzilla requires version
$sql_want or later of $sql_server. Please download and install a
newer version.
EOT
- exit;
}
- print "\n" if $output;
+ # This is used by subclasses.
+ return $sql_vers;
}
# Note that this function requires that localconfig exist and
@@ -197,7 +251,7 @@
sub bz_create_database {
my $dbh;
# See if we can connect to the actual Bugzilla database.
- my $conn_success = eval { $dbh = connect_main(); };
+ my $conn_success = eval { $dbh = connect_main() };
my $db_name = Bugzilla->localconfig->{db_name};
if (!$conn_success) {
@@ -214,10 +268,9 @@
if (!$success) {
my $error = $dbh->errstr || $@;
chomp($error);
- print STDERR "The '$db_name' database could not be created.",
- " The error returned was:\n\n $error\n\n",
- _bz_connect_error_reasons();
- exit;
+ die "The '$db_name' database could not be created.",
+ " The error returned was:\n\n $error\n\n",
+ _bz_connect_error_reasons();
}
}
@@ -228,19 +281,19 @@
sub _get_no_db_connection {
my ($sql_server) = @_;
my $dbh;
- my $lc = Bugzilla->localconfig;
+ my %connect_params = %{ Bugzilla->localconfig };
+ $connect_params{db_name} = '';
my $conn_success = eval {
- $dbh = _connect($lc->{db_driver}, $lc->{db_host}, '', $lc->{db_port},
- $lc->{db_sock}, $lc->{db_user}, $lc->{db_pass});
+ $dbh = _connect(\%connect_params);
};
if (!$conn_success) {
- my $sql_server = DB_MODULE->{lc($lc->{db_driver})}->{name};
+ my $driver = $connect_params{db_driver};
+ my $sql_server = DB_MODULE->{lc($driver)}->{name};
# Can't use $dbh->errstr because $dbh is undef.
my $error = $DBI::errstr || $@;
chomp($error);
- print STDERR "There was an error connecting to $sql_server:\n\n",
- " $error\n\n", _bz_connect_error_reasons();
- exit;
+ die "There was an error connecting to $sql_server:\n\n",
+ " $error\n\n", _bz_connect_error_reasons(), "\n";
}
return $dbh;
}
@@ -272,9 +325,9 @@
}
# List of abstract methods we are checking the derived class implements
-our @_abstract_methods = qw(REQUIRED_VERSION PROGRAM_NAME DBD_VERSION
- new sql_regexp sql_not_regexp sql_limit sql_to_days
- sql_date_format sql_interval);
+our @_abstract_methods = qw(new sql_regexp sql_not_regexp sql_limit sql_to_days
+ sql_date_format sql_date_math bz_explain
+ sql_group_concat);
# This overridden import method will check implementation of inherited classes
# for missing implementation of abstract methods
@@ -287,7 +340,7 @@
# make sure all abstract methods are implemented
foreach my $meth (@_abstract_methods) {
$pkg->can($meth)
- or croak("Class $pkg does not define method $meth");
+ or die("Class $pkg does not define method $meth");
}
}
@@ -342,6 +395,15 @@
return '(' . join(' || ', @params) . ')';
}
+sub sql_string_until {
+ my ($self, $string, $substring) = @_;
+
+ my $position = $self->sql_position($substring, $string);
+ return "CASE WHEN $position != 0"
+ . " THEN SUBSTR($string, 1, $position - 1)"
+ . " ELSE $string END";
+}
+
sub sql_in {
my ($self, $column_name, $in_list_ref) = @_;
return " $column_name IN (" . join(',', @$in_list_ref) . ") ";
@@ -357,21 +419,34 @@
# make the string lowercase to do case insensitive search
my $lower_text = lc($text);
- # split the text we search for into separate words
- my @words = split(/\s+/, $lower_text);
+ # split the text we're searching for into separate words. As a hack
+ # to allow quicksearch to work, if the field starts and ends with
+ # a double-quote, then we don't split it into words. We can't use
+ # Text::ParseWords here because it gets very confused by unbalanced
+ # quotes, which breaks searches like "don't try this" (because of the
+ # unbalanced single-quote in "don't").
+ my @words;
+ if ($lower_text =~ /^"/ and $lower_text =~ /"$/) {
+ $lower_text =~ s/^"//;
+ $lower_text =~ s/"$//;
+ @words = ($lower_text);
+ }
+ else {
+ @words = split(/\s+/, $lower_text);
+ }
# surround the words with wildcards and SQL quotes so we can use them
# in LIKE search clauses
- @words = map($self->quote("%$_%"), @words);
+ @words = map($self->quote("\%$_\%"), @words);
# untaint words, since they are safe to use now that we've quoted them
- map(trick_taint($_), @words);
+ trick_taint($_) foreach @words;
# turn the words into a set of LIKE search clauses
@words = map("LOWER($column) LIKE $_", @words);
# search for occurrences of all specified words in the column
- return "CASE WHEN (" . join(" AND ", @words) . ") THEN 1 ELSE 0 END";
+ return join (" AND ", @words), "CASE WHEN (" . join(" AND ", @words) . ") THEN 1 ELSE 0 END";
}
#####################################################################
@@ -391,6 +466,15 @@
$table, $column);
}
+sub bz_check_regexp {
+ my ($self, $pattern) = @_;
+
+ eval { $self->do("SELECT " . $self->sql_regexp($self->quote("a"), $pattern, 1)) };
+
+ $@ && ThrowUserError('illegal_regexp',
+ { value => $pattern, dberror => $self->errstr });
+}
+
#####################################################################
# Database Setup
#####################################################################
@@ -401,11 +485,18 @@
# If we haven't ever stored a serialized schema,
# set up the bz_schema table and store it.
$self->_bz_init_schema_storage();
-
+
+ # We don't use bz_table_list here, because that uses _bz_real_schema.
+ # We actually want the table list from the ABSTRACT_SCHEMA in
+ # Bugzilla::DB::Schema.
my @desired_tables = $self->_bz_schema->get_table_list();
+ my $bugs_exists = $self->bz_table_info('bugs');
+ if (!$bugs_exists) {
+ print install_string('db_table_setup'), "\n";
+ }
foreach my $table_name (@desired_tables) {
- $self->bz_add_table($table_name);
+ $self->bz_add_table($table_name, { silently => !$bugs_exists });
}
}
@@ -415,28 +506,59 @@
}
sub bz_populate_enum_tables {
- my ($self) = @_;
+ my ($self) = @_;
+
+ my $any_severities = $self->selectrow_array(
+ 'SELECT 1 FROM bug_severity ' . $self->sql_limit(1));
+ print install_string('db_enum_setup'), "\n " if !$any_severities;
my $enum_values = $self->bz_enum_initial_values();
while (my ($table, $values) = each %$enum_values) {
$self->_bz_populate_enum_table($table, $values);
}
+
+ print "\n" if !$any_severities;
}
sub bz_setup_foreign_keys {
my ($self) = @_;
- # We use _bz_schema because bz_add_table has removed all REFERENCES
- # items from _bz_real_schema.
- my @tables = $self->_bz_schema->get_table_list();
+ # profiles_activity was the first table to get foreign keys,
+ # so if it doesn't have them, then we're setting up FKs
+ # for the first time, and should be quieter about it.
+ my $activity_fk = $self->bz_fk_info('profiles_activity', 'userid');
+ my $any_fks = $activity_fk && $activity_fk->{created};
+ if (!$any_fks) {
+ print get_text('install_fk_setup'), "\n";
+ }
+
+ my @tables = $self->bz_table_list();
foreach my $table (@tables) {
- my @columns = $self->_bz_schema->get_table_columns($table);
+ my @columns = $self->bz_table_columns($table);
+ my %add_fks;
foreach my $column (@columns) {
- my $def = $self->_bz_schema->get_column_abstract($table, $column);
- if ($def->{REFERENCES}) {
- $self->bz_add_fk($table, $column, $def->{REFERENCES});
+ # First we check for any FKs that have created => 0,
+ # in the _bz_real_schema. This also picks up FKs with
+ # created => 1, but bz_add_fks will ignore those.
+ my $fk = $self->bz_fk_info($table, $column);
+ # Then we check the abstract schema to see if there
+ # should be an FK on this column, but one wasn't set in the
+ # _bz_real_schema for some reason. We do this to handle
+ # various problems caused by upgrading from versions
+ # prior to 4.2, and also to handle problems caused
+ # by enabling an extension pre-4.2, disabling it for
+ # the 4.2 upgrade, and then re-enabling it later.
+ unless ($fk && $fk->{created}) {
+ my $standard_def =
+ $self->_bz_schema->get_column_abstract($table, $column);
+ if (exists $standard_def->{REFERENCES}) {
+ $fk = dclone($standard_def->{REFERENCES});
+ }
}
+
+ $add_fks{$column} = $fk if $fk;
}
+ $self->bz_add_fks($table, \%add_fks, { silently => !$any_fks });
}
}
@@ -444,9 +566,9 @@
sub bz_drop_foreign_keys {
my ($self) = @_;
- my @tables = $self->_bz_real_schema->get_table_list();
+ my @tables = $self->bz_table_list();
foreach my $table (@tables) {
- my @columns = $self->_bz_real_schema->get_table_columns($table);
+ my @columns = $self->bz_table_columns($table);
foreach my $column (@columns) {
$self->bz_drop_fk($table, $column);
}
@@ -483,6 +605,20 @@
foreach my $sql (@statements) {
$self->do($sql);
}
+
+ # To make things easier for callers, if they don't specify
+ # a REFERENCES item, we pull it from the _bz_schema if the
+ # column exists there and has a REFERENCES item.
+ # bz_setup_foreign_keys will then add this FK at the end of
+ # Install::DB.
+ my $col_abstract =
+ $self->_bz_schema->get_column_abstract($table, $name);
+ if (exists $col_abstract->{REFERENCES}) {
+ my $new_fk = dclone($col_abstract->{REFERENCES});
+ $new_fk->{created} = 0;
+ $new_def->{REFERENCES} = $new_fk;
+ }
+
$self->_bz_real_schema->set_column($table, $name, $new_def);
$self->_bz_store_real_schema;
}
@@ -490,20 +626,40 @@
sub bz_add_fk {
my ($self, $table, $column, $def) = @_;
+ $self->bz_add_fks($table, { $column => $def });
+}
- my $col_def = $self->bz_column_info($table, $column);
- if (!$col_def->{REFERENCES}) {
- $self->_check_references($table, $column, $def->{TABLE},
- $def->{COLUMN});
- print get_text('install_fk_add',
- { table => $table, column => $column, fk => $def })
- . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- my @sql = $self->_bz_real_schema->get_add_fk_sql($table, $column, $def);
- $self->do($_) foreach @sql;
- $col_def->{REFERENCES} = $def;
- $self->_bz_real_schema->set_column($table, $column, $col_def);
- $self->_bz_store_real_schema;
+sub bz_add_fks {
+ my ($self, $table, $column_fks, $options) = @_;
+
+ my %add_these;
+ foreach my $column (keys %$column_fks) {
+ my $current_fk = $self->bz_fk_info($table, $column);
+ next if ($current_fk and $current_fk->{created});
+ my $new_fk = $column_fks->{$column};
+ $self->_check_references($table, $column, $new_fk);
+ $add_these{$column} = $new_fk;
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE
+ and !$options->{silently})
+ {
+ print get_text('install_fk_add',
+ { table => $table, column => $column,
+ fk => $new_fk }), "\n";
+ }
}
+
+ return if !scalar(keys %add_these);
+
+ my @sql = $self->_bz_real_schema->get_add_fks_sql($table, \%add_these);
+ $self->do($_) foreach @sql;
+
+ foreach my $column (keys %add_these) {
+ my $fk_def = $add_these{$column};
+ $fk_def->{created} = 1;
+ $self->_bz_real_schema->set_fk($table, $column, $fk_def);
+ }
+
+ $self->_bz_store_real_schema();
}
sub bz_alter_column {
@@ -524,6 +680,11 @@
ThrowCodeError('column_not_null_no_default_alter',
{ name => "$table.$name" }) if ($any_nulls);
}
+ # Preserve foreign key definitions in the Schema object when altering
+ # types.
+ if (my $fk = $self->bz_fk_info($table, $name)) {
+ $new_def->{REFERENCES} = $fk;
+ }
$self->bz_alter_column_raw($table, $name, $new_def, $current_def,
$set_nulls_to);
$self->_bz_real_schema->set_column($table, $name, $new_def);
@@ -568,6 +729,15 @@
$self->do($_) foreach (@statements);
}
+sub bz_alter_fk {
+ my ($self, $table, $column, $fk_def) = @_;
+ my $current_fk = $self->bz_fk_info($table, $column);
+ ThrowCodeError('column_alter_nonexistent_fk',
+ { table => $table, column => $column }) if !$current_fk;
+ $self->bz_drop_fk($table, $column);
+ $self->bz_add_fk($table, $column, $fk_def);
+}
+
sub bz_add_index {
my ($self, $table, $name, $definition) = @_;
@@ -605,12 +775,12 @@
}
sub bz_add_table {
- my ($self, $name) = @_;
+ my ($self, $name, $options) = @_;
my $table_exists = $self->bz_table_info($name);
if (!$table_exists) {
- $self->_bz_add_table_raw($name);
+ $self->_bz_add_table_raw($name, $options);
my $table_def = dclone($self->_bz_schema->get_table_abstract($name));
my %fields = @{$table_def->{FIELDS}};
@@ -619,8 +789,11 @@
# initial table creation, because column names have changed
# over history and it's impossible to keep track of that info
# in ABSTRACT_SCHEMA.
- delete $fields{$col}->{REFERENCES};
+ next unless exists $fields{$col}->{REFERENCES};
+ $fields{$col}->{REFERENCES}->{created} =
+ $self->_bz_real_schema->FK_ON_CREATE;
}
+
$self->_bz_real_schema->add_table($name, $table_def);
$self->_bz_store_real_schema;
}
@@ -641,9 +814,13 @@
# Returns: nothing
#
sub _bz_add_table_raw {
- my ($self, $name) = @_;
+ my ($self, $name, $options) = @_;
my @statements = $self->_bz_schema->get_table_ddl($name);
- print "Adding new table $name ...\n" unless i_am_cgi();
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE
+ and !$options->{silently})
+ {
+ print install_string('db_table_new', { table => $name }), "\n";
+ }
$self->do($_) foreach (@statements);
}
@@ -672,12 +849,18 @@
my ($self, $field) = @_;
$self->_bz_add_field_table($field->name,
- $self->_bz_schema->FIELD_TABLE_SCHEMA);
- if ( $field->type == FIELD_TYPE_MULTI_SELECT ) {
- $self->_bz_add_field_table('bug_' . $field->name,
- $self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
- }
+ $self->_bz_schema->FIELD_TABLE_SCHEMA, $field->type);
+ if ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ my $ms_table = "bug_" . $field->name;
+ $self->_bz_add_field_table($ms_table,
+ $self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
+ $self->bz_add_fks($ms_table,
+ { bug_id => {TABLE => 'bugs', COLUMN => 'bug_id',
+ DELETE => 'CASCADE'},
+
+ value => {TABLE => $field->name, COLUMN => 'value'} });
+ }
}
sub bz_drop_field_tables {
@@ -713,27 +896,72 @@
sub bz_drop_fk {
my ($self, $table, $column) = @_;
- my $col_def = $self->bz_column_info($table, $column);
- if ($col_def && exists $col_def->{REFERENCES}) {
- my $def = $col_def->{REFERENCES};
+ my $fk_def = $self->bz_fk_info($table, $column);
+ if ($fk_def and $fk_def->{created}) {
print get_text('install_fk_drop',
- { table => $table, column => $column, fk => $def })
+ { table => $table, column => $column, fk => $fk_def })
. "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- my @sql = $self->_bz_real_schema->get_drop_fk_sql($table,$column,$def);
- $self->do($_) foreach @sql;
- delete $col_def->{REFERENCES};
- $self->_bz_real_schema->set_column($table, $column, $col_def);
+ my @statements =
+ $self->_bz_real_schema->get_drop_fk_sql($table, $column, $fk_def);
+ foreach my $sql (@statements) {
+ # Because this is a deletion, we don't want to die hard if
+ # we fail because of some local customization. If something
+ # is already gone, that's fine with us!
+ eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
+ }
+ # Under normal circumstances, we don't permanently drop the fk--
+ # we want checksetup to re-create it again later. The only
+ # time that FKs get permanently dropped is if the column gets
+ # dropped.
+ $fk_def->{created} = 0;
+ $self->_bz_real_schema->set_fk($table, $column, $fk_def);
$self->_bz_store_real_schema;
}
}
+sub bz_get_related_fks {
+ my ($self, $table, $column) = @_;
+ my @tables = $self->_bz_real_schema->get_table_list();
+ my @related;
+ foreach my $check_table (@tables) {
+ my @columns = $self->bz_table_columns($check_table);
+ foreach my $check_column (@columns) {
+ my $fk = $self->bz_fk_info($check_table, $check_column);
+ if ($fk
+ and (($fk->{TABLE} eq $table and $fk->{COLUMN} eq $column)
+ or ($check_column eq $column and $check_table eq $table)))
+ {
+ push(@related, [$check_table, $check_column, $fk]);
+ }
+ } # foreach $column
+ } # foreach $table
+
+ return \@related;
+}
+
+sub bz_drop_related_fks {
+ my $self = shift;
+ my $related = $self->bz_get_related_fks(@_);
+ foreach my $item (@$related) {
+ my ($table, $column) = @$item;
+ $self->bz_drop_fk($table, $column);
+ }
+ return $related;
+}
+
sub bz_drop_index {
my ($self, $table, $name) = @_;
my $index_exists = $self->bz_index_info($table, $name);
if ($index_exists) {
+ if ($self->INDEX_DROPS_REQUIRE_FK_DROPS) {
+ # We cannot delete an index used by a FK.
+ foreach my $column (@{$index_exists->{FIELDS}}) {
+ $self->bz_drop_related_fks($table, $column);
+ }
+ }
$self->bz_drop_index_raw($table, $name);
$self->_bz_real_schema->delete_index($table, $name);
$self->_bz_store_real_schema;
@@ -789,6 +1017,14 @@
}
}
+sub bz_fk_info {
+ my ($self, $table, $column) = @_;
+ my $col_info = $self->bz_column_info($table, $column);
+ return undef if !$col_info;
+ my $fk = $col_info->{REFERENCES};
+ return $fk;
+}
+
sub bz_rename_column {
my ($self, $table, $old_name, $new_name) = @_;
@@ -822,6 +1058,18 @@
my $new = $self->bz_table_info($new_name);
ThrowCodeError('db_rename_conflict', { old => $old_name,
new => $new_name }) if $new;
+
+ # FKs will all have the wrong names unless we drop and then let them
+ # be re-created later. Under normal circumstances, checksetup.pl will
+ # automatically re-create these dropped FKs at the end of its DB upgrade
+ # run, so we don't need to re-create them in this method.
+ my @columns = $self->bz_table_columns($old_name);
+ foreach my $column (@columns) {
+ # these just return silently if there's no FK to drop
+ $self->bz_drop_fk($old_name, $column);
+ $self->bz_drop_related_fks($old_name, $column);
+ }
+
my @sql = $self->_bz_real_schema->get_rename_table_sql($old_name, $new_name);
print get_text('install_table_rename',
{ old => $old_name, new => $new_name }) . "\n"
@@ -831,6 +1079,16 @@
$self->_bz_store_real_schema;
}
+sub bz_set_next_serial_value {
+ my ($self, $table, $column, $value) = @_;
+ if (!$value) {
+ $value = $self->selectrow_array("SELECT MAX($column) FROM $table") || 0;
+ $value++;
+ }
+ my @sql = $self->_bz_real_schema->get_set_serial_sql($table, $column, $value);
+ $self->do($_) foreach @sql;
+}
+
#####################################################################
# Schema Information Methods
#####################################################################
@@ -899,6 +1157,11 @@
return \%return_indexes;
}
+sub bz_table_list {
+ my ($self) = @_;
+ return $self->_bz_real_schema->get_table_list();
+}
+
#####################################################################
# Protected "Real Database" Schema Information Methods
#####################################################################
@@ -953,7 +1216,10 @@
# what we need in Bugzilla to be safe, for what we do.
# Different DBs have different defaults for their isolation
# level, so we just set it here manually.
- $self->do('SET TRANSACTION ISOLATION LEVEL ' . $self->ISOLATION_LEVEL);
+ if ($self->ISOLATION_LEVEL) {
+ $self->do('SET TRANSACTION ISOLATION LEVEL '
+ . $self->ISOLATION_LEVEL);
+ }
$self->{private_bz_transaction_count} = 1;
}
}
@@ -989,7 +1255,9 @@
#####################################################################
sub db_new {
- my ($class, $dsn, $user, $pass, $override_attrs) = @_;
+ my ($class, $params) = @_;
+ my ($dsn, $user, $pass, $override_attrs) =
+ @$params{qw(dsn user pass attrs)};
# set up default attributes used to connect to the database
# (may be overridden by DB driver implementations)
@@ -1072,7 +1340,7 @@
$self->_bz_add_table_raw('bz_schema');
}
- print "Initializing the new Schema storage...\n";
+ print install_string('db_schema_init'), "\n";
my $sth = $self->prepare("INSERT INTO bz_schema "
." (schema_data, version) VALUES (?,?)");
$sth->bind_param(1, $store_me, $self->BLOB_TYPE);
@@ -1175,14 +1443,13 @@
# If the table is empty...
if (!$table_size) {
+ print " $table";
my $insert = $self->prepare(
"INSERT INTO $sql_table (value,sortkey) VALUES (?,?)");
- print "Inserting values into the '$table' table:\n";
my $sortorder = 0;
my $maxlen = max(map(length($_), @$valuelist)) + 2;
foreach my $value (@$valuelist) {
$sortorder += 100;
- printf "%-${maxlen}s sortkey: $sortorder\n", "'$value'";
$insert->execute($value, $sortorder);
}
}
@@ -1191,33 +1458,55 @@
# This is used before adding a foreign key to a column, to make sure
# that the database won't fail adding the key.
sub _check_references {
- my ($self, $table, $column, $foreign_table, $foreign_column) = @_;
+ my ($self, $table, $column, $fk) = @_;
+ my $foreign_table = $fk->{TABLE};
+ my $foreign_column = $fk->{COLUMN};
+ # We use table aliases because sometimes we join a table to itself,
+ # and we can't use the same table name on both sides of the join.
+ # We also can't use the words "table" or "foreign" because those are
+ # reserved words.
my $bad_values = $self->selectcol_arrayref(
- "SELECT DISTINCT $table.$column
- FROM $table LEFT JOIN $foreign_table
- ON $table.$column = $foreign_table.$foreign_column
- WHERE $foreign_table.$foreign_column IS NULL
- AND $table.$column IS NOT NULL");
+ "SELECT DISTINCT tabl.$column
+ FROM $table AS tabl LEFT JOIN $foreign_table AS forn
+ ON tabl.$column = forn.$foreign_column
+ WHERE forn.$foreign_column IS NULL
+ AND tabl.$column IS NOT NULL");
if (@$bad_values) {
- my $values = join(', ', @$bad_values);
- print <<EOT;
-
-ERROR: There are invalid values for the $column column in the $table
-table. (These values do not exist in the $foreign_table table, in the
-$foreign_column column.)
-
-Before continuing with checksetup, you will need to fix these values,
-either by deleting these rows from the database, or changing the values
-of $column in $table to point to valid values in $foreign_table.$foreign_column.
-
-The bad values from the $table.$column column are:
-$values
-
-EOT
- # I just picked a number above 2, to be considered "abnormal exit."
- exit 3;
+ my $delete_action = $fk->{DELETE} || '';
+ if ($delete_action eq 'CASCADE') {
+ $self->do("DELETE FROM $table WHERE $column IN ("
+ . join(',', ('?') x @$bad_values) . ")",
+ undef, @$bad_values);
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ print "\n", get_text('install_fk_invalid_fixed',
+ { table => $table, column => $column,
+ foreign_table => $foreign_table,
+ foreign_column => $foreign_column,
+ 'values' => $bad_values, action => 'delete' }), "\n";
+ }
+ }
+ elsif ($delete_action eq 'SET NULL') {
+ $self->do("UPDATE $table SET $column = NULL
+ WHERE $column IN ("
+ . join(',', ('?') x @$bad_values) . ")",
+ undef, @$bad_values);
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ print "\n", get_text('install_fk_invalid_fixed',
+ { table => $table, column => $column,
+ foreign_table => $foreign_table,
+ foreign_column => $foreign_column,
+ 'values' => $bad_values, action => 'null' }), "\n";
+ }
+ }
+ else {
+ die "\n", get_text('install_fk_invalid',
+ { table => $table, column => $column,
+ foreign_table => $foreign_table,
+ foreign_column => $foreign_column,
+ 'values' => $bad_values }), "\n";
+ }
}
}
@@ -1518,6 +1807,11 @@
=item C<$pattern> - the regular expression to search for (scalar)
+=item C<$nocheck> - true if the pattern should not be tested; false otherwise (boolean)
+
+=item C<$real_pattern> - the real regular expression to search for.
+This argument is used when C<$pattern> is a placeholder ('?').
+
=back
=item B<Returns>
@@ -1540,13 +1834,7 @@
=item B<Params>
-=over
-
-=item C<$expr> - SQL expression for the text to be searched (scalar)
-
-=item C<$pattern> - the regular expression to search for (scalar)
-
-=back
+Same as L</sql_regexp>.
=item B<Returns>
@@ -1660,13 +1948,13 @@
=back
-=item C<sql_interval>
+=item C<sql_date_math>
=over
=item B<Description>
-Outputs proper SQL syntax for a time interval function.
+Outputs proper SQL syntax for adding some amount of time to a date.
Abstract method, should be overridden by database specific code.
@@ -1674,15 +1962,28 @@
=over
-=item C<$interval> - the time interval requested (e.g. '30') (integer)
+=item C<$date>
-=item C<$units> - the units the interval is in (e.g. 'MINUTE') (string)
+C<string> The date being added to or subtracted from.
+
+=item C<$operator>
+
+C<string> Either C<-> or C<+>, depending on whether you're subtracting
+or adding.
+
+=item C<$interval>
+
+C<integer> The time interval you're adding or subtracting (e.g. C<30>)
+
+=item C<$units>
+
+C<string> the units the interval is in (e.g. 'MINUTE')
=back
=item B<Returns>
-Formatted SQL for interval function (scalar)
+Formatted SQL for adding or subtracting a date and some amount of time (scalar)
=back
@@ -1774,14 +2075,41 @@
=back
+=item C<sql_string_until>
+
+=over
+
+=item B<Description>
+
+Returns SQL for truncating a string at the first occurrence of a certain
+substring.
+
+=item B<Params>
+
+Note that both parameters need to be sql-quoted.
+
+=item C<$string> The string we're truncating
+
+=item C<$substring> The substring we're truncating at.
+
+=back
+
=item C<sql_fulltext_search>
=over
=item B<Description>
-Returns SQL syntax for performing a full text search for specified text
-on a given column.
+Returns one or two SQL expressions for performing a full text search for
+specified text on a given column.
+
+If one value is returned, it is a numeric expression that indicates
+a match with a positive value and a non-match with zero. In this case,
+the DB must support casting numeric expresions to booleans.
+
+If two values are returned, then the first value is a boolean expression
+that indicates the presence of a match, and the second value is a numeric
+expression that can be used for ranking.
There is a ANSI SQL version of this method implemented using LIKE operator,
but it's not a real full text search. DB specific modules should override
diff --git a/Websites/bugs.webkit.org/Bugzilla/DB/Mysql.pm b/Websites/bugs.webkit.org/Bugzilla/DB/Mysql.pm
index 92d1df1..06bf3d8 100644
--- a/Websites/bugs.webkit.org/Bugzilla/DB/Mysql.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/DB/Mysql.pm
@@ -40,8 +40,8 @@
=cut
package Bugzilla::DB::Mysql;
-
use strict;
+use base qw(Bugzilla::DB);
use Bugzilla::Constants;
use Bugzilla::Install::Util qw(install_string);
@@ -50,26 +50,33 @@
use Bugzilla::DB::Schema::Mysql;
use List::Util qw(max);
+use Text::ParseWords;
# This is how many comments of MAX_COMMENT_LENGTH we expect on a single bug.
# In reality, you could have a LOT more comments than this, because
# MAX_COMMENT_LENGTH is big.
use constant MAX_COMMENTS => 50;
-# This module extends the DB interface via inheritance
-use base qw(Bugzilla::DB);
+use constant FULLTEXT_OR => '|';
sub new {
- my ($class, $user, $pass, $host, $dbname, $port, $sock) = @_;
+ my ($class, $params) = @_;
+ my ($user, $pass, $host, $dbname, $port, $sock) =
+ @$params{qw(db_user db_pass db_host db_name db_port db_sock)};
# construct the DSN from the parameters we got
- my $dsn = "DBI:mysql:host=$host;database=$dbname";
+ my $dsn = "dbi:mysql:host=$host;database=$dbname";
$dsn .= ";port=$port" if $port;
$dsn .= ";mysql_socket=$sock" if $sock;
- my $attrs = { mysql_enable_utf8 => Bugzilla->params->{'utf8'} };
+ my %attrs = (
+ mysql_enable_utf8 => Bugzilla->params->{'utf8'},
+ # Needs to be explicitly specified for command-line processes.
+ mysql_auto_reconnect => 1,
+ );
- my $self = $class->db_new($dsn, $user, $pass, $attrs);
+ my $self = $class->db_new({ dsn => $dsn, user => $user,
+ pass => $pass, attrs => \%attrs });
# This makes sure that if the tables are encoded as UTF-8, we
# return their data correctly.
@@ -79,6 +86,9 @@
# a prefix 'private_'. See DBI documentation.
$self->{private_bz_tables_locked} = "";
+ # Needed by TheSchwartz
+ $self->{private_bz_dsn} = $dsn;
+
bless ($self, $class);
# Bug 321645 - disable MySQL strict mode, if set
@@ -116,22 +126,31 @@
}
sub sql_group_concat {
- my ($self, $column, $separator) = @_;
- my $sep_sql;
- if ($separator) {
- $sep_sql = " SEPARATOR $separator";
+ my ($self, $column, $separator, $sort) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+ $sort = 1 if !defined $sort;
+ if ($sort) {
+ my $sort_order = $column;
+ $sort_order =~ s/^DISTINCT\s+//i;
+ $column = "$column ORDER BY $sort_order";
}
- return "GROUP_CONCAT($column$sep_sql)";
+ return "GROUP_CONCAT($column SEPARATOR $separator)";
}
sub sql_regexp {
- my ($self, $expr, $pattern) = @_;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
return "$expr REGEXP $pattern";
}
sub sql_not_regexp {
- my ($self, $expr, $pattern) = @_;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
return "$expr NOT REGEXP $pattern";
}
@@ -156,8 +175,21 @@
my ($self, $column, $text) = @_;
# Add the boolean mode modifier if the search string contains
- # boolean operators.
- my $mode = ($text =~ /[+\-<>()~*"]/ ? "IN BOOLEAN MODE" : "");
+ # boolean operators at the start or end of a word.
+ my $mode = '';
+ if ($text =~ /(?:^|\W)[+\-<>~"()]/ || $text =~ /[()"*](?:$|\W)/) {
+ $mode = 'IN BOOLEAN MODE';
+
+ # quote un-quoted compound words
+ my @words = quotewords('[\s()]+', 'delimiters', $text);
+ foreach my $word (@words) {
+ # match words that have non-word chars in the middle of them
+ if ($word =~ /\w\W+\w/ && $word !~ m/"/) {
+ $word = '"' . $word . '"';
+ }
+ }
+ $text = join('', @words);
+ }
# quote the text for use in the MATCH AGAINST expression
$text = $self->quote($text);
@@ -194,10 +226,10 @@
return "DATE_FORMAT($date, " . $self->quote($format) . ")";
}
-sub sql_interval {
- my ($self, $interval, $units) = @_;
+sub sql_date_math {
+ my ($self, $date, $operator, $interval, $units) = @_;
- return "INTERVAL $interval $units";
+ return "$date $operator INTERVAL $interval $units";
}
sub sql_iposition {
@@ -221,6 +253,30 @@
return "GROUP BY $needed_columns";
}
+sub bz_explain {
+ my ($self, $sql) = @_;
+ my $sth = $self->prepare("EXPLAIN $sql");
+ $sth->execute();
+ my $columns = $sth->{'NAME'};
+ my $lengths = $sth->{'mysql_max_length'};
+ my $format_string = '|';
+ my $i = 0;
+ foreach my $column (@$columns) {
+ # Sometimes the column name is longer than the contents.
+ my $length = max($lengths->[$i], length($column));
+ $format_string .= ' %-' . $length . 's |';
+ $i++;
+ }
+
+ my $first_row = sprintf($format_string, @$columns);
+ my @explain_rows = ($first_row, '-' x length($first_row));
+ while (my $row = $sth->fetchrow_arrayref) {
+ my @fixed = map { defined $_ ? $_ : 'NULL' } @$row;
+ push(@explain_rows, sprintf($format_string, @fixed));
+ }
+
+ return join("\n", @explain_rows);
+}
sub _bz_get_initial_schema {
my ($self) = @_;
@@ -231,6 +287,18 @@
# Database Setup
#####################################################################
+sub bz_check_server_version {
+ my $self = shift;
+
+ my $lc = Bugzilla->localconfig;
+ if (lc(Bugzilla->localconfig->{db_name}) eq 'mysql') {
+ die "It is not safe to run Bugzilla inside a database named 'mysql'.\n"
+ . " Please pick a different value for \$db_name in localconfig.\n";
+ }
+
+ $self->SUPER::bz_check_server_version(@_);
+}
+
sub bz_setup_database {
my ($self) = @_;
@@ -260,47 +328,18 @@
my ($innodb_on) = @{$self->selectcol_arrayref(
q{SHOW VARIABLES LIKE '%have_innodb%'}, {Columns=>[2]})};
if ($innodb_on ne 'YES') {
- print <<EOT;
-InnoDB is disabled in your MySQL installation.
-Bugzilla requires InnoDB to be enabled.
-Please enable it and then re-run checksetup.pl.
-
-EOT
- exit 3;
+ die install_string('mysql_innodb_disabled');
}
- # Figure out if any existing tables are of type ISAM and convert them
- # to type MyISAM if so. ISAM tables are deprecated in MySQL 3.23,
- # which Bugzilla now requires, and they don't support more than 16
- # indexes per table, which Bugzilla needs.
- my $table_status = $self->selectall_arrayref("SHOW TABLE STATUS");
- my @isam_tables;
- foreach my $row (@$table_status) {
- my ($name, $type) = @$row;
- push(@isam_tables, $name) if $type eq "ISAM";
- }
-
- if(scalar(@isam_tables)) {
- print "One or more of the tables in your existing MySQL database are\n"
- . "of type ISAM. ISAM tables are deprecated in MySQL 3.23 and\n"
- . "don't support more than 16 indexes per table, which \n"
- . "Bugzilla needs.\n Converting your ISAM tables to type"
- . " MyISAM:\n\n";
- foreach my $table (@isam_tables) {
- print "Converting table $table... ";
- $self->do("ALTER TABLE $table TYPE = MYISAM");
- print "done.\n";
- }
- print "\nISAM->MyISAM table conversion done.\n\n";
- }
-
my ($sd_index_deleted, $longdescs_index_deleted);
my @tables = $self->bz_table_list_real();
# We want to convert tables to InnoDB, but it's possible that they have
# fulltext indexes on them, and conversion will fail unless we remove
# the indexes.
- if (grep($_ eq 'bugs', @tables)) {
+ if (grep($_ eq 'bugs', @tables)
+ and !grep($_ eq 'bugs_fulltext', @tables))
+ {
if ($self->bz_index_info_real('bugs', 'short_desc')) {
$self->bz_drop_index_raw('bugs', 'short_desc');
}
@@ -309,7 +348,9 @@
$sd_index_deleted = 1; # Used for later schema cleanup.
}
}
- if (grep($_ eq 'longdescs', @tables)) {
+ if (grep($_ eq 'longdescs', @tables)
+ and !grep($_ eq 'bugs_fulltext', @tables))
+ {
if ($self->bz_index_info_real('longdescs', 'thetext')) {
$self->bz_drop_index_raw('longdescs', 'thetext');
}
@@ -320,27 +361,25 @@
}
# Upgrade tables from MyISAM to InnoDB
- my @myisam_tables;
- foreach my $row (@$table_status) {
- my ($name, $type) = @$row;
- if ($type =~ /^MYISAM$/i
- && !grep($_ eq $name, Bugzilla::DB::Schema::Mysql::MYISAM_TABLES))
- {
- push(@myisam_tables, $name) ;
- }
+ my $db_name = Bugzilla->localconfig->{db_name};
+ my $myisam_tables = $self->selectcol_arrayref(
+ 'SELECT TABLE_NAME FROM information_schema.TABLES
+ WHERE TABLE_SCHEMA = ? AND ENGINE = ?',
+ undef, $db_name, 'MyISAM');
+ foreach my $should_be_myisam (Bugzilla::DB::Schema::Mysql::MYISAM_TABLES) {
+ @$myisam_tables = grep { $_ ne $should_be_myisam } @$myisam_tables;
}
- if (scalar @myisam_tables) {
+
+ if (scalar @$myisam_tables) {
print "Bugzilla now uses the InnoDB storage engine in MySQL for",
" most tables.\nConverting tables to InnoDB:\n";
- foreach my $table (@myisam_tables) {
+ foreach my $table (@$myisam_tables) {
print "Converting table $table... ";
- $self->do("ALTER TABLE $table TYPE = InnoDB");
+ $self->do("ALTER TABLE $table ENGINE = InnoDB");
print "done.\n";
}
}
- $self->_after_table_status(\@tables);
-
# Versions of Bugzilla before the existence of Bugzilla::DB::Schema did
# not provide explicit names for the table indexes. This means
# that our upgrades will not be reliable, because we look for the name
@@ -368,17 +407,7 @@
# We just do the check here since this check is a reliable way
# of telling that we are upgrading from a version pre-2.20.
if (grep($_ eq 'bz_schema', $self->bz_table_list_real())) {
- die("\nYou are upgrading from a version before 2.20, but the"
- . " bz_schema\ntable already exists. This means that you"
- . " restored a mysqldump into\nthe Bugzilla database without"
- . " first dropping the already-existing\nBugzilla database,"
- . " at some point. Whenever you restore a Bugzilla\ndatabase"
- . " backup, you must always drop the entire database first.\n\n"
- . "Please drop your Bugzilla database and restore it from a"
- . " backup that\ndoes not contain the bz_schema table. If for"
- . " some reason you cannot\ndo this, you can connect to your"
- . " MySQL database and drop the bz_schema\ntable, as a last"
- . " resort.\n");
+ die install_string('bz_schema_exists_before_220');
}
my $bug_count = $self->selectrow_array("SELECT COUNT(*) FROM bugs");
@@ -392,12 +421,8 @@
# If we're going to take longer than 5 minutes, we let the user know
# and allow them to abort.
if ($rename_time > 5) {
- print "\nWe are about to rename old indexes.\n"
- . "The estimated time to complete renaming is "
- . "$rename_time minutes.\n"
- . "You cannot interrupt this action once it has begun.\n"
- . "If you would like to cancel, press Ctrl-C now..."
- . " (Waiting 45 seconds...)\n\n";
+ print "\n", install_string('mysql_index_renaming',
+ { minutes => $rename_time });
# Wait 45 seconds for them to respond.
sleep(45) unless Bugzilla->installation_answers->{NO_PAUSE};
}
@@ -611,8 +636,11 @@
# 2005-09-24 - bugreport@peshkin.net, bug 307602
# Make sure that default 4G table limit is overridden
- my $row = $self->selectrow_hashref("SHOW TABLE STATUS LIKE 'attach_data'");
- if ($$row{'Create_options'} !~ /MAX_ROWS/i) {
+ my $attach_data_create = $self->selectrow_array(
+ 'SELECT CREATE_OPTIONS FROM information_schema.TABLES
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?',
+ undef, $db_name, 'attach_data');
+ if ($attach_data_create !~ /MAX_ROWS/i) {
print "Converting attach_data maximum size to 100G...\n";
$self->do("ALTER TABLE attach_data
AVG_ROW_LENGTH=1000000,
@@ -624,42 +652,25 @@
# partial-conversion situations can happen, and this handles anything
# that could come up (including having the DB charset be utf8 but not
# the table charsets.
- my $utf_table_status =
- $self->selectall_arrayref("SHOW TABLE STATUS", {Slice=>{}});
- $self->_after_table_status([map($_->{Name}, @$utf_table_status)]);
- my @non_utf8_tables = grep($_->{Collation} !~ /^utf8/, @$utf_table_status);
+ #
+ # TABLE_COLLATION IS NOT NULL prevents us from trying to convert views.
+ my $non_utf8_tables = $self->selectrow_array(
+ "SELECT 1 FROM information_schema.TABLES
+ WHERE TABLE_SCHEMA = ? AND TABLE_COLLATION IS NOT NULL
+ AND TABLE_COLLATION NOT LIKE 'utf8%'
+ LIMIT 1", undef, $db_name);
- if (Bugzilla->params->{'utf8'} && scalar @non_utf8_tables) {
- print <<EOT;
-
-WARNING: We are about to convert your table storage format to UTF8. This
- allows Bugzilla to correctly store and sort international characters.
- However, if you have any non-UTF-8 data in your database,
- it ***WILL BE DELETED*** by this process. So, before
- you continue with checksetup.pl, if you have any non-UTF-8
- data (or even if you're not sure) you should press Ctrl-C now
- to interrupt checksetup.pl, and run contrib/recode.pl to make all
- the data in your database into UTF-8. You should also back up your
- database before continuing. This will affect every single table
- in the database, even non-Bugzilla tables.
-
- If you ever used a version of Bugzilla before 2.22, we STRONGLY
- recommend that you stop checksetup.pl NOW and run contrib/recode.pl.
-
-EOT
+ if (Bugzilla->params->{'utf8'} && $non_utf8_tables) {
+ print "\n", install_string('mysql_utf8_conversion');
if (!Bugzilla->installation_answers->{NO_PAUSE}) {
if (Bugzilla->installation_mode ==
INSTALLATION_MODE_NON_INTERACTIVE)
{
- print <<EOT;
- Re-run checksetup.pl in interactive mode (without an 'answers' file)
- to continue.
-EOT
- exit;
+ die install_string('continue_without_answers'), "\n";
}
else {
- print " Press Enter to continue or Ctrl-C to exit...";
+ print "\n " . install_string('enter_or_ctrl_c');
getc;
}
}
@@ -669,6 +680,7 @@
foreach my $table ($self->bz_table_list_real) {
my $info_sth = $self->prepare("SHOW FULL COLUMNS FROM $table");
$info_sth->execute();
+ my (@binary_sql, @utf8_sql);
while (my $column = $info_sth->fetchrow_hashref) {
# Our conversion code doesn't work on enum fields, but they
# all go away later in checksetup anyway.
@@ -681,31 +693,16 @@
{
my $name = $column->{Field};
- # The code below doesn't work on a field with a FULLTEXT
- # index. So we drop it, which we'd do later anyway.
- if ($table eq 'longdescs' && $name eq 'thetext') {
- $self->bz_drop_index('longdescs',
- 'longdescs_thetext_idx');
- }
- if ($table eq 'bugs' && $name eq 'short_desc') {
- $self->bz_drop_index('bugs', 'bugs_short_desc_idx');
- }
- my %ft_indexes;
- if ($table eq 'bugs_fulltext') {
- %ft_indexes = $self->_bz_real_schema->get_indexes_on_column_abstract(
- 'bugs_fulltext', $name);
- foreach my $index (keys %ft_indexes) {
- $self->bz_drop_index('bugs_fulltext', $index);
- }
- }
+ print "$table.$name needs to be converted to UTF-8...\n";
- print "Converting $table.$name to be stored as UTF-8...\n";
- my $col_info =
+ # These will be automatically re-created at the end
+ # of checksetup.
+ $self->bz_drop_related_fks($table, $name);
+
+ my $col_info =
$self->bz_column_info_real($table, $name);
-
# CHANGE COLUMN doesn't take PRIMARY KEY
delete $col_info->{PRIMARYKEY};
-
my $sql_def = $self->_bz_schema->get_type_ddl($col_info);
# We don't want MySQL to actually try to *convert*
# from our current charset to UTF-8, we just want to
@@ -717,21 +714,39 @@
my $type = $self->_bz_schema->convert_type($col_info->{TYPE});
$binary =~ s/(\Q$type\E)/$1 CHARACTER SET binary/;
$utf8 =~ s/(\Q$type\E)/$1 CHARACTER SET utf8/;
- $self->do("ALTER TABLE $table CHANGE COLUMN $name $name
- $binary");
- $self->do("ALTER TABLE $table CHANGE COLUMN $name $name
- $utf8");
+ push(@binary_sql, "MODIFY COLUMN $name $binary");
+ push(@utf8_sql, "MODIFY COLUMN $name $utf8");
+ }
+ } # foreach column
- if ($table eq 'bugs_fulltext') {
- foreach my $index (keys %ft_indexes) {
- $self->bz_add_index('bugs_fulltext', $index,
- $ft_indexes{$index});
- }
+ if (@binary_sql) {
+ my %indexes = %{ $self->bz_table_indexes($table) };
+ foreach my $index_name (keys %indexes) {
+ my $index = $indexes{$index_name};
+ if ($index->{TYPE} and $index->{TYPE} eq 'FULLTEXT') {
+ $self->bz_drop_index($table, $index_name);
+ }
+ else {
+ delete $indexes{$index_name};
}
}
+
+ print "Converting the $table table to UTF-8...\n";
+ my $bin = "ALTER TABLE $table " . join(', ', @binary_sql);
+ my $utf = "ALTER TABLE $table " . join(', ', @utf8_sql,
+ 'DEFAULT CHARACTER SET utf8');
+ $self->do($bin);
+ $self->do($utf);
+
+ # Re-add any removed FULLTEXT indexes.
+ foreach my $index (keys %indexes) {
+ $self->bz_add_index($table, $index, $indexes{$index});
+ }
+ }
+ else {
+ $self->do("ALTER TABLE $table DEFAULT CHARACTER SET utf8");
}
- $self->do("ALTER TABLE $table DEFAULT CHARACTER SET utf8");
} # foreach my $table (@tables)
}
@@ -743,18 +758,79 @@
if (Bugzilla->params->{'utf8'} && !$self->bz_db_is_utf8) {
$self->_alter_db_charset_to_utf8();
}
+
+ $self->_fix_defaults();
+
+ # Bug 451735 highlighted a bug in bz_drop_index() which didn't
+ # check for FKs before trying to delete an index. Consequently,
+ # the series_creator_idx index was considered to be deleted
+ # despite it was still present in the DB. That's why we have to
+ # force the deletion, bypassing the DB schema.
+ if (!$self->bz_index_info('series', 'series_category_idx')) {
+ if (!$self->bz_index_info('series', 'series_creator_idx')
+ && $self->bz_index_info_real('series', 'series_creator_idx'))
+ {
+ foreach my $column (qw(creator category subcategory name)) {
+ $self->bz_drop_related_fks('series', $column);
+ }
+ $self->bz_drop_index_raw('series', 'series_creator_idx');
+ }
+ }
}
-# There is a bug in MySQL 4.1.0 - 4.1.15 that makes certain SELECT
-# statements fail after a SHOW TABLE STATUS:
-# http://bugs.mysql.com/bug.php?id=13535
-# This is a workaround, a dummy SELECT to reset the LAST_INSERT_ID.
-sub _after_table_status {
- my ($self, $tables) = @_;
- if (grep($_ eq 'bugs', @$tables)
- && $self->bz_column_info_real("bugs", "bug_id"))
- {
- $self->do('SELECT 1 FROM bugs WHERE bug_id IS NULL');
+# When you import a MySQL 3/4 mysqldump into MySQL 5, columns that
+# aren't supposed to have defaults will have defaults. This is only
+# a minor issue, but it makes our tests fail, and it's good to keep
+# the DB actually consistent with what DB::Schema thinks the database
+# looks like. So we remove defaults from columns that aren't supposed
+# to have them
+sub _fix_defaults {
+ my $self = shift;
+ my $maj_version = substr($self->bz_server_version, 0, 1);
+ return if $maj_version < 5;
+
+ # The oldest column that could have this problem is bugs.assigned_to,
+ # so if it doesn't have the problem, we just skip doing this entirely.
+ my $assi_def = $self->_bz_raw_column_info('bugs', 'assigned_to');
+ my $assi_default = $assi_def->{COLUMN_DEF};
+ # This "ne ''" thing is necessary because _raw_column_info seems to
+ # return COLUMN_DEF as an empty string for columns that don't have
+ # a default.
+ return unless (defined $assi_default && $assi_default ne '');
+
+ my %fix_columns;
+ foreach my $table ($self->_bz_real_schema->get_table_list()) {
+ foreach my $column ($self->bz_table_columns($table)) {
+ my $abs_def = $self->bz_column_info($table, $column);
+ # BLOB/TEXT columns never have defaults
+ next if $abs_def->{TYPE} =~ /BLOB|TEXT/i;
+ if (!defined $abs_def->{DEFAULT}) {
+ # Get the exact default from the database without any
+ # "fixing" by bz_column_info_real.
+ my $raw_info = $self->_bz_raw_column_info($table, $column);
+ my $raw_default = $raw_info->{COLUMN_DEF};
+ if (defined $raw_default) {
+ if ($raw_default eq '') {
+ # Only (var)char columns can have empty strings as
+ # defaults, so if we got an empty string for some
+ # other default type, then it's bogus.
+ next unless $abs_def->{TYPE} =~ /char/i;
+ $raw_default = "''";
+ }
+ $fix_columns{$table} ||= [];
+ push(@{ $fix_columns{$table} }, $column);
+ print "$table.$column has incorrect DB default: $raw_default\n";
+ }
+ }
+ } # foreach $column
+ } # foreach $table
+
+ print "Fixing defaults...\n";
+ foreach my $table (reverse sort keys %fix_columns) {
+ my @alters = map("ALTER COLUMN $_ DROP DEFAULT",
+ @{ $fix_columns{$table} });
+ my $sql = "ALTER TABLE $table " . join(',', @alters);
+ $self->do($sql);
}
}
@@ -834,6 +910,12 @@
sub bz_column_info_real {
my ($self, $table, $column) = @_;
+ my $col_data = $self->_bz_raw_column_info($table, $column);
+ return $self->_bz_schema->column_info_to_column($col_data);
+}
+
+sub _bz_raw_column_info {
+ my ($self, $table, $column) = @_;
# DBD::mysql does not support selecting a specific column,
# so we have to get all the columns on the table and find
@@ -849,7 +931,7 @@
if (!defined $col_data) {
return undef;
}
- return $self->_bz_schema->column_info_to_column($col_data);
+ return $col_data;
}
=item C<bz_index_info_real($table, $index)>
@@ -938,11 +1020,12 @@
sub _bz_build_schema_from_disk {
my ($self) = @_;
- print "Building Schema object from database...\n";
-
my $schema = $self->_bz_schema->get_empty_schema();
my @tables = $self->bz_table_list_real();
+ if (@tables) {
+ print "Building Schema object from database...\n";
+ }
foreach my $table (@tables) {
$schema->add_table($table);
my @columns = $self->bz_table_columns_real($table);
@@ -964,4 +1047,5 @@
return $schema;
}
+
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/DB/Oracle.pm b/Websites/bugs.webkit.org/Bugzilla/DB/Oracle.pm
index ef3f62f..2cbd19a 100644
--- a/Websites/bugs.webkit.org/Bugzilla/DB/Oracle.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/DB/Oracle.pm
@@ -35,16 +35,16 @@
=cut
package Bugzilla::DB::Oracle;
-
use strict;
+use base qw(Bugzilla::DB);
use DBD::Oracle;
use DBD::Oracle qw(:ora_types);
+use List::Util qw(max);
+
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
-# This module extends the DB interface via inheritance
-use base qw(Bugzilla::DB);
#####################################################################
# Constants
@@ -52,10 +52,14 @@
use constant EMPTY_STRING => '__BZ_EMPTY_STR__';
use constant ISOLATION_LEVEL => 'READ COMMITTED';
use constant BLOB_TYPE => { ora_type => ORA_BLOB };
-use constant GROUPBY_REGEXP => '((CASE\s+WHEN.+END)|(TO_CHAR\(.+\))|(\(SCORE.+\))|(\(MATCH.+\))|(\w+(\.\w+)?))(\s+AS\s+)?(.*)?$';
+# The max size allowed for LOB fields, in kilobytes.
+use constant MIN_LONG_READ_LEN => 32 * 1024;
+use constant FULLTEXT_OR => ' OR ';
sub new {
- my ($class, $user, $pass, $host, $dbname, $port) = @_;
+ my ($class, $params) = @_;
+ my ($user, $pass, $host, $dbname, $port) =
+ @$params{qw(db_user db_pass db_host db_name db_port)};
# You can never connect to Oracle without a DB name,
# and there is no default DB.
@@ -65,13 +69,16 @@
$ENV{'NLS_LANG'} = '.AL32UTF8' if Bugzilla->params->{'utf8'};
# construct the DSN from the parameters we got
- my $dsn = "DBI:Oracle:host=$host;sid=$dbname";
+ my $dsn = "dbi:Oracle:host=$host;sid=$dbname";
$dsn .= ";port=$port" if $port;
my $attrs = { FetchHashKeyName => 'NAME_lc',
- LongReadLen => ( Bugzilla->params->{'maxattachmentsize'}
- || 1000 ) * 1024,
+ LongReadLen => max(Bugzilla->params->{'maxattachmentsize'},
+ MIN_LONG_READ_LEN) * 1024,
};
- my $self = $class->db_new($dsn, $user, $pass, $attrs);
+ my $self = $class->db_new({ dsn => $dsn, user => $user,
+ pass => $pass, attrs => $attrs });
+ # Needed by TheSchwartz
+ $self->{private_bz_dsn} = $dsn;
bless ($self, $class);
@@ -95,14 +102,45 @@
return $last_insert_id;
}
+sub bz_check_regexp {
+ my ($self, $pattern) = @_;
+
+ eval { $self->do("SELECT 1 FROM DUAL WHERE "
+ . $self->sql_regexp($self->quote("a"), $pattern, 1)) };
+
+ $@ && ThrowUserError('illegal_regexp',
+ { value => $pattern, dberror => $self->errstr });
+}
+
+sub bz_explain {
+ my ($self, $sql) = @_;
+ my $sth = $self->prepare("EXPLAIN PLAN FOR $sql");
+ $sth->execute();
+ my $explain = $self->selectcol_arrayref(
+ "SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY)");
+ return join("\n", @$explain);
+}
+
+sub sql_group_concat {
+ my ($self, $text, $separator) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+ return "group_concat(T_CLOB_DELIM($text, $separator))";
+}
+
sub sql_regexp {
- my ($self, $expr, $pattern) = @_;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
return "REGEXP_LIKE($expr, $pattern)";
}
sub sql_not_regexp {
- my ($self, $expr, $pattern) = @_;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
return "NOT REGEXP_LIKE($expr, $pattern)"
}
@@ -136,7 +174,7 @@
my ($self, $column, $text, $label) = @_;
$text = $self->quote($text);
trick_taint($text);
- return "CONTAINS($column,$text,$label)", "SCORE($label)";
+ return "CONTAINS($column,$text,$label) > 0", "SCORE($label)";
}
sub sql_date_format {
@@ -156,13 +194,15 @@
return "TO_CHAR($date, " . $self->quote($format) . ")";
}
-sub sql_interval {
- my ($self, $interval, $units) = @_;
+sub sql_date_math {
+ my ($self, $date, $operator, $interval, $units) = @_;
+ my $time_sql;
if ($units =~ /YEAR|MONTH/i) {
- return "NUMTOYMINTERVAL($interval,'$units')";
+ $time_sql = "NUMTOYMINTERVAL($interval,'$units')";
} else{
- return "NUMTODSINTERVAL($interval,'$units')";
+ $time_sql = "NUMTODSINTERVAL($interval,'$units')";
}
+ return "$date $operator $time_sql";
}
sub sql_position {
@@ -185,6 +225,15 @@
return "( " . join(" OR ", @in_str) . " )";
}
+sub _bz_add_field_table {
+ my ($self, $name, $schema_ref, $type) = @_;
+ $self->SUPER::_bz_add_field_table($name, $schema_ref);
+ if (defined($type) && $type == FIELD_TYPE_MULTI_SELECT) {
+ my $uk_name = "UK_" . $self->_bz_schema->_hash_identifier($name . '_value');
+ $self->do("ALTER TABLE $name ADD CONSTRAINT $uk_name UNIQUE(value)");
+ }
+}
+
sub bz_drop_table {
my ($self, $name) = @_;
my $table_exists = $self->bz_table_info($name);
@@ -197,7 +246,7 @@
# Dropping all FKs for a specified table.
sub _bz_drop_fks {
my ($self, $table) = @_;
- my @columns = $self->_bz_real_schema->get_table_columns($table);
+ my @columns = $self->bz_table_columns($table);
foreach my $column (@columns) {
$self->bz_drop_fk($table, $column);
}
@@ -229,6 +278,10 @@
sub adjust_statement {
my ($sql) = @_;
+
+ if ($sql =~ /^CREATE OR REPLACE.*/i){
+ return $sql;
+ }
# We can't just assume any occurrence of "''" in $sql is an empty
# string, since "''" can occur inside a string literal as a way of
@@ -295,6 +348,10 @@
# Oracle need no 'AS'
$nonstring =~ s/\bAS\b//ig;
+
+ # Take the first 4000 chars for comparison
+ $nonstring =~ s/\(\s*(longdescs_\d+\.thetext|attachdata_\d+\.thedata)/
+ \(DBMS_LOB.SUBSTR\($1, 4000, 1\)/ig;
# Look for a LIMIT clause
($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
@@ -319,20 +376,17 @@
if ($new_sql !~ /\bWHERE\b/) {
$new_sql = $new_sql." WHERE 1=1";
}
- my ($before_where, $after_where) = split /\bWHERE\b/i,$new_sql;
- if (defined($offset)) {
- if ($new_sql =~ /(.*\s+)FROM(\s+.*)/i) {
- my ($before_from,$after_from) = ($1,$2);
- $before_where = "$before_from FROM ($before_from,"
- . " ROW_NUMBER() OVER (ORDER BY 1) R "
- . " FROM $after_from ) ";
- $after_where = " R BETWEEN $offset+1 AND $limit+$offset";
- }
- } else {
- $after_where = " rownum <=$limit AND ".$after_where;
- }
-
- $new_sql = $before_where." WHERE ".$after_where;
+ my ($before_where, $after_where) = split(/\bWHERE\b/i, $new_sql, 2);
+ if (defined($offset)) {
+ my ($before_from, $after_from) = split(/\bFROM\b/i, $new_sql, 2);
+ $before_where = "$before_from FROM ($before_from,"
+ . " ROW_NUMBER() OVER (ORDER BY 1) R "
+ . " FROM $after_from ) ";
+ $after_where = " R BETWEEN $offset+1 AND $limit+$offset";
+ } else {
+ $after_where = " rownum <=$limit AND ".$after_where;
+ }
+ $new_sql = $before_where." WHERE ".$after_where;
}
return $new_sql;
}
@@ -487,6 +541,88 @@
. " RETURN DATE IS BEGIN RETURN SYSDATE; END;");
$self->do("CREATE OR REPLACE FUNCTION CHAR_LENGTH(COLUMN_NAME VARCHAR2)"
. " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
+
+ # Create types for group_concat
+ my $t_clob_delim = $self->selectcol_arrayref("
+ SELECT TYPE_NAME FROM USER_TYPES WHERE TYPE_NAME=?",
+ undef, 'T_CLOB_DELIM');
+
+ if ( !@$t_clob_delim ) {
+ $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
+ . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256));");
+ }
+
+ $self->do("CREATE OR REPLACE TYPE T_GROUP_CONCAT AS OBJECT
+ ( CLOB_CONTENT CLOB,
+ DELIMITER VARCHAR2(256),
+ STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
+ SCTX IN OUT NOCOPY T_GROUP_CONCAT)
+ RETURN NUMBER,
+ MEMBER FUNCTION ODCIAGGREGATEITERATE(
+ SELF IN OUT NOCOPY T_GROUP_CONCAT,
+ VALUE IN T_CLOB_DELIM)
+ RETURN NUMBER,
+ MEMBER FUNCTION ODCIAGGREGATETERMINATE(
+ SELF IN T_GROUP_CONCAT,
+ RETURNVALUE OUT NOCOPY CLOB,
+ FLAGS IN NUMBER)
+ RETURN NUMBER,
+ MEMBER FUNCTION ODCIAGGREGATEMERGE(
+ SELF IN OUT NOCOPY T_GROUP_CONCAT,
+ CTX2 IN T_GROUP_CONCAT)
+ RETURN NUMBER);");
+
+ $self->do("CREATE OR REPLACE TYPE BODY T_GROUP_CONCAT IS
+ STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
+ SCTX IN OUT NOCOPY T_GROUP_CONCAT)
+ RETURN NUMBER IS
+ BEGIN
+ SCTX := T_GROUP_CONCAT(EMPTY_CLOB(), NULL);
+ DBMS_LOB.CREATETEMPORARY(SCTX.CLOB_CONTENT, TRUE);
+ RETURN ODCICONST.SUCCESS;
+ END;
+ MEMBER FUNCTION ODCIAGGREGATEITERATE(
+ SELF IN OUT NOCOPY T_GROUP_CONCAT,
+ VALUE IN T_CLOB_DELIM)
+ RETURN NUMBER IS
+ BEGIN
+ SELF.DELIMITER := VALUE.P_DELIMITER;
+ DBMS_LOB.WRITEAPPEND(SELF.CLOB_CONTENT,
+ LENGTH(SELF.DELIMITER),
+ SELF.DELIMITER);
+ DBMS_LOB.APPEND(SELF.CLOB_CONTENT, VALUE.P_CONTENT);
+
+ RETURN ODCICONST.SUCCESS;
+ END;
+ MEMBER FUNCTION ODCIAGGREGATETERMINATE(
+ SELF IN T_GROUP_CONCAT,
+ RETURNVALUE OUT NOCOPY CLOB,
+ FLAGS IN NUMBER)
+ RETURN NUMBER IS
+ BEGIN
+ RETURNVALUE := RTRIM(LTRIM(SELF.CLOB_CONTENT,
+ SELF.DELIMITER),
+ SELF.DELIMITER);
+ RETURN ODCICONST.SUCCESS;
+ END;
+ MEMBER FUNCTION ODCIAGGREGATEMERGE(
+ SELF IN OUT NOCOPY T_GROUP_CONCAT,
+ CTX2 IN T_GROUP_CONCAT)
+ RETURN NUMBER IS
+ BEGIN
+ DBMS_LOB.WRITEAPPEND(SELF.CLOB_CONTENT,
+ LENGTH(SELF.DELIMITER),
+ SELF.DELIMITER);
+ DBMS_LOB.APPEND(SELF.CLOB_CONTENT, CTX2.CLOB_CONTENT);
+ RETURN ODCICONST.SUCCESS;
+ END;
+ END;");
+
+ # Create user-defined aggregate function group_concat
+ $self->do("CREATE OR REPLACE FUNCTION GROUP_CONCAT(P_INPUT T_CLOB_DELIM)
+ RETURN CLOB
+ DETERMINISTIC PARALLEL_ENABLE AGGREGATE USING T_GROUP_CONCAT;");
+
# Create a WORLD_LEXER named BZ_LEX for multilingual fulltext search
my $lexer = $self->selectcol_arrayref(
"SELECT pre_name FROM CTXSYS.CTX_PREFERENCES WHERE pre_name = ? AND
@@ -512,6 +648,10 @@
my $fk_name = $self->_bz_schema->_get_fk_name($table,
$column,
$references);
+ # bz_rename_table didn't rename the trigger correctly.
+ if ($table eq 'bug_tag' && $to_table eq 'tags') {
+ $to_table = 'tag';
+ }
if ( $update =~ /CASCADE/i ){
my $trigger_name = uc($fk_name . "_UC");
my $exist_trigger = $self->selectcol_arrayref(
@@ -522,7 +662,7 @@
}
my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
- . " AFTER UPDATE ON ". $to_table
+ . " AFTER UPDATE OF $to_column ON $to_table "
. " REFERENCING "
. " NEW AS NEW "
. " OLD AS OLD "
@@ -538,6 +678,14 @@
}
}
+ # Drop the trigger which causes bug 541553
+ my $trigger_name = "PRODUCTS_MILESTONEURL";
+ my $exist_trigger = $self->selectcol_arrayref(
+ "SELECT OBJECT_NAME FROM USER_OBJECTS
+ WHERE OBJECT_NAME = ?", undef, $trigger_name);
+ if(@$exist_trigger) {
+ $self->do("DROP TRIGGER $trigger_name");
+ }
}
package Bugzilla::DB::Oracle::st;
diff --git a/Websites/bugs.webkit.org/Bugzilla/DB/Pg.pm b/Websites/bugs.webkit.org/Bugzilla/DB/Pg.pm
index 4777ba8..b6be640 100644
--- a/Websites/bugs.webkit.org/Bugzilla/DB/Pg.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/DB/Pg.pm
@@ -52,7 +52,9 @@
use constant BLOB_TYPE => { pg_type => DBD::Pg::PG_BYTEA };
sub new {
- my ($class, $user, $pass, $host, $dbname, $port) = @_;
+ my ($class, $params) = @_;
+ my ($user, $pass, $host, $dbname, $port) =
+ @$params{qw(db_user db_pass db_host db_name db_port)};
# The default database name for PostgreSQL. We have
# to connect to SOME database, even if we have
@@ -60,7 +62,7 @@
$dbname ||= 'template1';
# construct the DSN from the parameters we got
- my $dsn = "DBI:Pg:dbname=$dbname";
+ my $dsn = "dbi:Pg:dbname=$dbname";
$dsn .= ";host=$host" if $host;
$dsn .= ";port=$port" if $port;
@@ -70,11 +72,14 @@
my $attrs = { pg_enable_utf8 => Bugzilla->params->{'utf8'} };
- my $self = $class->db_new($dsn, $user, $pass, $attrs);
+ my $self = $class->db_new({ dsn => $dsn, user => $user,
+ pass => $pass, attrs => $attrs });
# all class local variables stored in DBI derived class needs to have
# a prefix 'private_'. See DBI documentation.
$self->{private_bz_tables_locked} = "";
+ # Needed by TheSchwartz
+ $self->{private_bz_dsn} = $dsn;
bless ($self, $class);
@@ -92,16 +97,45 @@
return $last_insert_id;
}
-sub sql_regexp {
- my ($self, $expr, $pattern) = @_;
+sub sql_group_concat {
+ my ($self, $text, $separator, $sort) = @_;
+ $sort = 1 if !defined $sort;
+ $separator = $self->quote(', ') if !defined $separator;
+ my $sql = "array_accum($text)";
+ if ($sort) {
+ $sql = "array_sort($sql)";
+ }
+ return "array_to_string($sql, $separator)";
+}
- return "$expr ~* $pattern";
+sub sql_istring {
+ my ($self, $string) = @_;
+
+ return "LOWER(${string}::text)";
+}
+
+sub sql_position {
+ my ($self, $fragment, $text) = @_;
+
+ return "POSITION(${fragment}::text IN ${text}::text)";
+}
+
+sub sql_regexp {
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
+
+ return "${expr}::text ~* $pattern";
}
sub sql_not_regexp {
- my ($self, $expr, $pattern) = @_;
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
- return "$expr !~* $pattern"
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
+
+ return "${expr}::text !~* $pattern"
}
sub sql_limit {
@@ -117,7 +151,7 @@
sub sql_from_days {
my ($self, $days) = @_;
- return "TO_TIMESTAMP(${days}::int, 'J')::date";
+ return "TO_TIMESTAMP('$days', 'J')::date";
}
sub sql_to_days {
@@ -143,10 +177,10 @@
return "TO_CHAR($date, " . $self->quote($format) . ")";
}
-sub sql_interval {
- my ($self, $interval, $units) = @_;
+sub sql_date_math {
+ my ($self, $date, $operator, $interval, $units) = @_;
- return "$interval * INTERVAL '1 $units'";
+ return "$date $operator $interval * INTERVAL '1 $units'";
}
sub sql_string_concat {
@@ -167,14 +201,61 @@
return $exists || 0;
}
+sub bz_explain {
+ my ($self, $sql) = @_;
+ my $explain = $self->selectcol_arrayref("EXPLAIN ANALYZE $sql");
+ return join("\n", @$explain);
+}
+
#####################################################################
# Custom Database Setup
#####################################################################
+sub bz_check_server_version {
+ my $self = shift;
+ my ($db) = @_;
+ my $server_version = $self->SUPER::bz_check_server_version(@_);
+ my ($major_version) = $server_version =~ /^(\d+)/;
+ # Pg 9 requires DBD::Pg 2.17.2 in order to properly read bytea values.
+ if ($major_version >= 9) {
+ local $db->{dbd}->{version} = '2.17.2';
+ local $db->{name} = $db->{name} . ' 9+';
+ Bugzilla::DB::_bz_check_dbd(@_);
+ }
+}
+
sub bz_setup_database {
my $self = shift;
$self->SUPER::bz_setup_database(@_);
+ # Custom Functions
+ my $function = 'array_accum';
+ my $array_accum = $self->selectrow_array(
+ 'SELECT 1 FROM pg_proc WHERE proname = ?', undef, $function);
+ if (!$array_accum) {
+ print "Creating function $function...\n";
+ $self->do("CREATE AGGREGATE array_accum (
+ SFUNC = array_append,
+ BASETYPE = anyelement,
+ STYPE = anyarray,
+ INITCOND = '{}'
+ )");
+ }
+
+ $self->do(<<'END');
+CREATE OR REPLACE FUNCTION array_sort(ANYARRAY)
+RETURNS ANYARRAY LANGUAGE SQL
+IMMUTABLE STRICT
+AS $$
+SELECT ARRAY(
+ SELECT $1[s.i] AS each_item
+ FROM
+ generate_series(array_lower($1,1), array_upper($1,1)) AS s(i)
+ ORDER BY each_item
+);
+$$;
+END
+
# PostgreSQL doesn't like having *any* index on the thetext
# field, because it can't have index data longer than 2770
# characters on that field.
@@ -201,15 +282,60 @@
$self->bz_add_index('products', 'products_name_lower_idx',
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
- # bz_rename_column didn't correctly rename the sequence.
- if ($self->bz_column_info('fielddefs', 'id')
- && $self->bz_sequence_exists('fielddefs_fieldid_seq'))
- {
- print "Fixing fielddefs_fieldid_seq sequence...\n";
- $self->do("ALTER TABLE fielddefs_fieldid_seq RENAME TO fielddefs_id_seq");
- $self->do("ALTER TABLE fielddefs ALTER COLUMN id
- SET DEFAULT NEXTVAL('fielddefs_id_seq')");
+ # bz_rename_column and bz_rename_table didn't correctly rename
+ # the sequence.
+ $self->_fix_bad_sequence('fielddefs', 'id', 'fielddefs_fieldid_seq', 'fielddefs_id_seq');
+ # If the 'tags' table still exists, then bz_rename_table()
+ # will fix the sequence for us.
+ if (!$self->bz_table_info('tags')) {
+ my $res = $self->_fix_bad_sequence('tag', 'id', 'tags_id_seq', 'tag_id_seq');
+ # If $res is true, then the sequence has been renamed, meaning that
+ # the primary key must be renamed too.
+ if ($res) {
+ $self->do('ALTER INDEX tags_pkey RENAME TO tag_pkey');
+ }
}
+
+ # Certain sequences got upgraded before we required Pg 8.3, and
+ # so they were not properly associated with their columns.
+ my @tables = $self->bz_table_list_real;
+ foreach my $table (@tables) {
+ my @columns = $self->bz_table_columns_real($table);
+ foreach my $column (@columns) {
+ # All our SERIAL pks have "id" in their name at the end.
+ next unless $column =~ /id$/;
+ my $sequence = "${table}_${column}_seq";
+ if ($self->bz_sequence_exists($sequence)) {
+ my $is_associated = $self->selectrow_array(
+ 'SELECT pg_get_serial_sequence(?,?)',
+ undef, $table, $column);
+ next if $is_associated;
+ print "Fixing $sequence to be associated"
+ . " with $table.$column...\n";
+ $self->do("ALTER SEQUENCE $sequence OWNED BY $table.$column");
+ # In order to produce an exactly identical schema to what
+ # a brand-new checksetup.pl run would produce, we also need
+ # to re-set the default on this column.
+ $self->do("ALTER TABLE $table
+ ALTER COLUMN $column
+ SET DEFAULT nextval('$sequence')");
+ }
+ }
+ }
+}
+
+sub _fix_bad_sequence {
+ my ($self, $table, $column, $old_seq, $new_seq) = @_;
+ if ($self->bz_column_info($table, $column)
+ && $self->bz_sequence_exists($old_seq))
+ {
+ print "Fixing $old_seq sequence...\n";
+ $self->do("ALTER SEQUENCE $old_seq RENAME TO $new_seq");
+ $self->do("ALTER TABLE $table ALTER COLUMN $column
+ SET DEFAULT NEXTVAL('$new_seq')");
+ return 1;
+ }
+ return 0;
}
# Renames things that differ only in case.
diff --git a/Websites/bugs.webkit.org/Bugzilla/DB/Schema.pm b/Websites/bugs.webkit.org/Bugzilla/DB/Schema.pm
index 02e4bfd..00ff4ac 100644
--- a/Websites/bugs.webkit.org/Bugzilla/DB/Schema.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/DB/Schema.pm
@@ -23,6 +23,7 @@
# Lance Larsh <lance.larsh@oracle.com>
# Dennis Melentyev <dennis.melentyev@infopulse.com.ua>
# Akamai Technologies <bugzilla-dev@akamai.com>
+# Elliotte Martin <emartin@everythingsolved.com>
package Bugzilla::DB::Schema;
@@ -43,13 +44,20 @@
use Carp qw(confess);
use Digest::MD5 qw(md5_hex);
use Hash::Util qw(lock_value unlock_hash lock_keys unlock_keys);
+use List::MoreUtils qw(firstidx natatime);
use Safe;
# Historical, needed for SCHEMA_VERSION = '1.00'
use Storable qw(dclone freeze thaw);
-# New SCHEMA_VERSION (2.00) use this
+# New SCHEMA_VERSIONs (2+) use this
use Data::Dumper;
+# Whether or not this database can safely create FKs when doing a
+# CREATE TABLE statement. This is false for most DBs, because they
+# prevent you from creating FKs on tables and columns that don't
+# yet exist. (However, in SQLite it's 1 because SQLite allows that.)
+use constant FK_ON_CREATE => 0;
+
=head1 NAME
Bugzilla::DB::Schema - Abstract database schema for Bugzilla
@@ -206,10 +214,33 @@
=cut
-use constant SCHEMA_VERSION => '2.00';
+use constant SCHEMA_VERSION => 3;
use constant ADD_COLUMN => 'ADD COLUMN';
+# Multiple FKs can be added using ALTER TABLE ADD CONSTRAINT in one
+# SQL statement. This isn't true for all databases.
+use constant MULTIPLE_FKS_IN_ALTER => 1;
# This is a reasonable default that's true for both PostgreSQL and MySQL.
use constant MAX_IDENTIFIER_LEN => 63;
+
+use constant FIELD_TABLE_SCHEMA => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
+ PRIMARYKEY => 1},
+ value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'TRUE'},
+ visibility_value_id => {TYPE => 'INT2'},
+ ],
+ # Note that bz_add_field_table should prepend the table name
+ # to these index names.
+ INDEXES => [
+ value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ sortkey_idx => ['sortkey', 'value'],
+ visibility_value_id_idx => ['visibility_value_id'],
+ ],
+};
+
use constant ABSTRACT_SCHEMA => {
# BUG-RELATED TABLES
@@ -221,8 +252,11 @@
FIELDS => [
bug_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
- assigned_to => {TYPE => 'INT3', NOTNULL => 1},
- bug_file_loc => {TYPE => 'MEDIUMTEXT'},
+ assigned_to => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid'}},
+ bug_file_loc => {TYPE => 'MEDIUMTEXT',
+ NOTNULL => 1, DEFAULT => "''"},
bug_severity => {TYPE => 'varchar(64)', NOTNULL => 1},
bug_status => {TYPE => 'varchar(64)', NOTNULL => 1},
creation_ts => {TYPE => 'DATETIME'},
@@ -230,33 +264,35 @@
short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
op_sys => {TYPE => 'varchar(64)', NOTNULL => 1},
priority => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
+ product_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'products',
+ COLUMN => 'id'}},
rep_platform => {TYPE => 'varchar(64)', NOTNULL => 1},
- reporter => {TYPE => 'INT3', NOTNULL => 1},
+ reporter => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid'}},
version => {TYPE => 'varchar(64)', NOTNULL => 1},
- component_id => {TYPE => 'INT2', NOTNULL => 1},
+ component_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'components',
+ COLUMN => 'id'}},
resolution => {TYPE => 'varchar(64)',
NOTNULL => 1, DEFAULT => "''"},
target_milestone => {TYPE => 'varchar(20)',
NOTNULL => 1, DEFAULT => "'---'"},
- qa_contact => {TYPE => 'INT3'},
+ qa_contact => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid'}},
status_whiteboard => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
DEFAULT => "''"},
- votes => {TYPE => 'INT3', NOTNULL => 1,
- DEFAULT => '0'},
- # Note: keywords field is only a cache; the real data
- # comes from the keywords table
- keywords => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
- DEFAULT => "''"},
lastdiffed => {TYPE => 'DATETIME'},
everconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1},
reporter_accessible => {TYPE => 'BOOLEAN',
NOTNULL => 1, DEFAULT => 'TRUE'},
cclist_accessible => {TYPE => 'BOOLEAN',
NOTNULL => 1, DEFAULT => 'TRUE'},
- estimated_time => {TYPE => 'decimal(5,2)',
+ estimated_time => {TYPE => 'decimal(7,2)',
NOTNULL => 1, DEFAULT => '0'},
- remaining_time => {TYPE => 'decimal(5,2)',
+ remaining_time => {TYPE => 'decimal(7,2)',
NOTNULL => 1, DEFAULT => '0'},
deadline => {TYPE => 'DATETIME'},
alias => {TYPE => 'varchar(20)'},
@@ -278,7 +314,6 @@
bugs_resolution_idx => ['resolution'],
bugs_target_milestone_idx => ['target_milestone'],
bugs_qa_contact_idx => ['qa_contact'],
- bugs_votes_idx => ['votes'],
],
},
@@ -307,27 +342,43 @@
bugs_activity => {
FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- attach_id => {TYPE => 'INT3'},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ attach_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'attachments',
+ COLUMN => 'attach_id',
+ DELETE => 'CASCADE'}},
who => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid'}},
bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
- fieldid => {TYPE => 'INT3', NOTNULL => 1},
- added => {TYPE => 'TINYTEXT'},
+ fieldid => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'fielddefs',
+ COLUMN => 'id'}},
+ added => {TYPE => 'varchar(255)'},
removed => {TYPE => 'TINYTEXT'},
+ comment_id => {TYPE => 'INT3',
+ REFERENCES => { TABLE => 'longdescs',
+ COLUMN => 'comment_id',
+ DELETE => 'CASCADE'}},
],
INDEXES => [
bugs_activity_bug_id_idx => ['bug_id'],
bugs_activity_who_idx => ['who'],
bugs_activity_bug_when_idx => ['bug_when'],
bugs_activity_fieldid_idx => ['fieldid'],
+ bugs_activity_added_idx => ['added'],
],
},
cc => {
FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
who => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
@@ -344,10 +395,15 @@
FIELDS => [
comment_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- who => {TYPE => 'INT3', NOTNULL => 1},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ who => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid'}},
bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
- work_time => {TYPE => 'decimal(5,2)', NOTNULL => 1,
+ work_time => {TYPE => 'decimal(7,2)', NOTNULL => 1,
DEFAULT => '0'},
thetext => {TYPE => 'LONGTEXT', NOTNULL => 1},
isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1,
@@ -367,8 +423,14 @@
dependencies => {
FIELDS => [
- blocked => {TYPE => 'INT3', NOTNULL => 1},
- dependson => {TYPE => 'INT3', NOTNULL => 1},
+ blocked => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ dependson => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
],
INDEXES => [
dependencies_blocked_idx => ['blocked'],
@@ -376,31 +438,20 @@
],
},
- votes => {
- FIELDS => [
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- vote_count => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- votes_who_idx => ['who'],
- votes_bug_id_idx => ['bug_id'],
- ],
- },
-
attachments => {
FIELDS => [
attach_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
creation_ts => {TYPE => 'DATETIME', NOTNULL => 1},
modification_time => {TYPE => 'DATETIME', NOTNULL => 1},
description => {TYPE => 'TINYTEXT', NOTNULL => 1},
mimetype => {TYPE => 'TINYTEXT', NOTNULL => 1},
- ispatch => {TYPE => 'BOOLEAN'},
+ ispatch => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
filename => {TYPE => 'varchar(100)', NOTNULL => 1},
submitter_id => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
@@ -409,8 +460,6 @@
DEFAULT => 'FALSE'},
isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'FALSE'},
- isurl => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
],
INDEXES => [
attachments_bug_id_idx => ['bug_id'],
@@ -422,16 +471,60 @@
attach_data => {
FIELDS => [
id => {TYPE => 'INT3', NOTNULL => 1,
- PRIMARYKEY => 1},
+ PRIMARYKEY => 1,
+ REFERENCES => {TABLE => 'attachments',
+ COLUMN => 'attach_id',
+ DELETE => 'CASCADE'}},
thedata => {TYPE => 'LONGBLOB', NOTNULL => 1},
],
},
duplicates => {
FIELDS => [
- dupe_of => {TYPE => 'INT3', NOTNULL => 1},
+ dupe_of => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
dupe => {TYPE => 'INT3', NOTNULL => 1,
+ PRIMARYKEY => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ ],
+ },
+
+ bug_see_also => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ value => {TYPE => 'varchar(255)', NOTNULL => 1},
+ class => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"},
+ ],
+ INDEXES => [
+ bug_see_also_bug_id_idx => {FIELDS => [qw(bug_id value)],
+ TYPE => 'UNIQUE'},
+ ],
+ },
+
+ # Auditing
+ # --------
+
+ audit_log => {
+ FIELDS => [
+ user_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'SET NULL'}},
+ class => {TYPE => 'varchar(255)', NOTNULL => 1},
+ object_id => {TYPE => 'INT4', NOTNULL => 1},
+ field => {TYPE => 'varchar(64)', NOTNULL => 1},
+ removed => {TYPE => 'MEDIUMTEXT'},
+ added => {TYPE => 'MEDIUMTEXT'},
+ at_time => {TYPE => 'DATETIME', NOTNULL => 1},
],
},
@@ -443,7 +536,7 @@
id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
name => {TYPE => 'varchar(64)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT'},
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
],
INDEXES => [
keyworddefs_name_idx => {FIELDS => ['name'],
@@ -453,8 +546,15 @@
keywords => {
FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- keywordid => {TYPE => 'INT2', NOTNULL => 1},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ keywordid => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'keyworddefs',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+
],
INDEXES => [
keywords_bug_id_idx => {FIELDS => [qw(bug_id keywordid)],
@@ -471,14 +571,27 @@
FIELDS => [
id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
- type_id => {TYPE => 'INT2', NOTNULL => 1},
+ type_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'flagtypes',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
status => {TYPE => 'char(1)', NOTNULL => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- attach_id => {TYPE => 'INT3'},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ attach_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'attachments',
+ COLUMN => 'attach_id',
+ DELETE => 'CASCADE'}},
creation_date => {TYPE => 'DATETIME', NOTNULL => 1},
modification_date => {TYPE => 'DATETIME'},
- setter_id => {TYPE => 'INT3'},
- requestee_id => {TYPE => 'INT3'},
+ setter_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid'}},
+ requestee_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid'}},
],
INDEXES => [
flags_bug_id_idx => [qw(bug_id attach_id)],
@@ -508,8 +621,14 @@
DEFAULT => 'FALSE'},
sortkey => {TYPE => 'INT2', NOTNULL => 1,
DEFAULT => '0'},
- grant_group_id => {TYPE => 'INT3'},
- request_group_id => {TYPE => 'INT3'},
+ grant_group_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'groups',
+ COLUMN => 'id',
+ DELETE => 'SET NULL'}},
+ request_group_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'groups',
+ COLUMN => 'id',
+ DELETE => 'SET NULL'}},
],
},
@@ -518,9 +637,18 @@
# to be set for them.
flaginclusions => {
FIELDS => [
- type_id => {TYPE => 'INT2', NOTNULL => 1},
- product_id => {TYPE => 'INT2'},
- component_id => {TYPE => 'INT2'},
+ type_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'flagtypes',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ product_id => {TYPE => 'INT2',
+ REFERENCES => {TABLE => 'products',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ component_id => {TYPE => 'INT2',
+ REFERENCES => {TABLE => 'components',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
],
INDEXES => [
flaginclusions_type_id_idx =>
@@ -530,9 +658,18 @@
flagexclusions => {
FIELDS => [
- type_id => {TYPE => 'INT2', NOTNULL => 1},
- product_id => {TYPE => 'INT2'},
- component_id => {TYPE => 'INT2'},
+ type_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'flagtypes',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ product_id => {TYPE => 'INT2',
+ REFERENCES => {TABLE => 'products',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ component_id => {TYPE => 'INT2',
+ REFERENCES => {TABLE => 'components',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
],
INDEXES => [
flagexclusions_type_id_idx =>
@@ -560,11 +697,45 @@
DEFAULT => 'FALSE'},
enter_bug => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'FALSE'},
+ buglist => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
+ visibility_field_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'fielddefs',
+ COLUMN => 'id'}},
+ value_field_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'fielddefs',
+ COLUMN => 'id'}},
+ reverse_desc => {TYPE => 'TINYTEXT'},
+ is_mandatory => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
+ is_numeric => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
],
INDEXES => [
fielddefs_name_idx => {FIELDS => ['name'],
TYPE => 'UNIQUE'},
fielddefs_sortkey_idx => ['sortkey'],
+ fielddefs_value_field_id_idx => ['value_field_id'],
+ fielddefs_is_mandatory_idx => ['is_mandatory'],
+ ],
+ },
+
+ # Field Visibility Information
+ # -------------------------
+
+ field_visibility => {
+ FIELDS => [
+ field_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'fielddefs',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ value_id => {TYPE => 'INT2', NOTNULL => 1}
+ ],
+ INDEXES => [
+ field_visibility_field_id_idx => {
+ FIELDS => [qw(field_id value_id)],
+ TYPE => 'UNIQUE'
+ },
],
},
@@ -576,7 +747,12 @@
id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
value => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
+ product_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'products',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'TRUE'},
],
INDEXES => [
versions_product_id_idx => {FIELDS => [qw(product_id value)],
@@ -588,10 +764,15 @@
FIELDS => [
id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
+ product_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'products',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
value => {TYPE => 'varchar(20)', NOTNULL => 1},
sortkey => {TYPE => 'INT2', NOTNULL => 1,
DEFAULT => 0},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'TRUE'},
],
INDEXES => [
milestones_product_id_idx => {FIELDS => [qw(product_id value)],
@@ -604,106 +785,79 @@
bug_status => {
FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
+ @{ dclone(FIELD_TABLE_SCHEMA->{FIELDS}) },
is_open => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+
],
INDEXES => [
bug_status_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
bug_status_sortkey_idx => ['sortkey', 'value'],
+ bug_status_visibility_value_id_idx => ['visibility_value_id'],
],
},
resolution => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
INDEXES => [
resolution_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
resolution_sortkey_idx => ['sortkey', 'value'],
+ resolution_visibility_value_id_idx => ['visibility_value_id'],
],
},
bug_severity => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
INDEXES => [
bug_severity_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
bug_severity_sortkey_idx => ['sortkey', 'value'],
+ bug_severity_visibility_value_id_idx => ['visibility_value_id'],
],
},
priority => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
INDEXES => [
priority_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
priority_sortkey_idx => ['sortkey', 'value'],
+ priority_visibility_value_id_idx => ['visibility_value_id'],
],
},
rep_platform => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
INDEXES => [
rep_platform_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
rep_platform_sortkey_idx => ['sortkey', 'value'],
+ rep_platform_visibility_value_id_idx => ['visibility_value_id'],
],
},
op_sys => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
INDEXES => [
op_sys_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
op_sys_sortkey_idx => ['sortkey', 'value'],
+ op_sys_visibility_value_id_idx => ['visibility_value_id'],
],
},
status_workflow => {
FIELDS => [
# On bug creation, there is no old value.
- old_status => {TYPE => 'INT2'},
- new_status => {TYPE => 'INT2', NOTNULL => 1},
+ old_status => {TYPE => 'INT2',
+ REFERENCES => {TABLE => 'bug_status',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ new_status => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bug_status',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
require_comment => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => 0},
],
INDEXES => [
@@ -733,10 +887,29 @@
mybugslink => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'TRUE'},
extern_id => {TYPE => 'varchar(64)'},
+ is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'TRUE'},
],
INDEXES => [
profiles_login_name_idx => {FIELDS => ['login_name'],
TYPE => 'UNIQUE'},
+ profiles_extern_id_idx => {FIELDS => ['extern_id'],
+ TYPE => 'UNIQUE'}
+ ],
+ },
+
+ profile_search => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ user_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'CASCADE'}},
+ bug_list => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ list_order => {TYPE => 'MEDIUMTEXT'},
+ ],
+ INDEXES => [
+ profile_search_user_id_idx => [qw(user_id)],
],
},
@@ -807,7 +980,6 @@
DELETE => 'CASCADE'}},
name => {TYPE => 'varchar(64)', NOTNULL => 1},
query => {TYPE => 'LONGTEXT', NOTNULL => 1},
- query_type => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0},
],
INDEXES => [
namedqueries_userid_idx => {FIELDS => [qw(userid name)],
@@ -833,6 +1005,36 @@
],
},
+ tag => {
+ FIELDS => [
+ id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1},
+ name => {TYPE => 'varchar(64)', NOTNULL => 1},
+ user_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'CASCADE'}},
+ ],
+ INDEXES => [
+ tag_user_id_idx => {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'},
+ ],
+ },
+
+ bug_tag => {
+ FIELDS => [
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ tag_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'tag',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ ],
+ INDEXES => [
+ bug_tag_bug_id_idx => {FIELDS => [qw(bug_id tag_id)], TYPE => 'UNIQUE'},
+ ],
+ },
+
component_cc => {
FIELDS => [
@@ -840,7 +1042,10 @@
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
- component_id => {TYPE => 'INT2', NOTNULL => 1},
+ component_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'components',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
],
INDEXES => [
component_cc_user_id_idx => {FIELDS => [qw(component_id user_id)],
@@ -859,7 +1064,7 @@
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
- ipaddr => {TYPE => 'varchar(40)', NOTNULL => 1},
+ ipaddr => {TYPE => 'varchar(40)'},
lastused => {TYPE => 'DATETIME', NOTNULL => 1},
],
INDEXES => [
@@ -867,6 +1072,25 @@
],
},
+ login_failure => {
+ FIELDS => [
+ user_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'CASCADE'}},
+ login_time => {TYPE => 'DATETIME', NOTNULL => 1},
+ ip_addr => {TYPE => 'varchar(40)', NOTNULL => 1},
+ ],
+ INDEXES => [
+ # We do lookups by every item in the table simultaneously, but
+ # having an index with all three items would be the same size as
+ # the table. So instead we have an index on just the smallest item,
+ # to speed lookups.
+ login_failure_user_id_idx => ['user_id'],
+ ],
+ },
+
+
# "tokens" stores the tokens users receive when a password or email
# change is requested. Tokens provide an extra measure of security
# for these changes.
@@ -909,12 +1133,22 @@
group_control_map => {
FIELDS => [
- group_id => {TYPE => 'INT3', NOTNULL => 1},
- product_id => {TYPE => 'INT3', NOTNULL => 1},
- entry => {TYPE => 'BOOLEAN', NOTNULL => 1},
- membercontrol => {TYPE => 'BOOLEAN', NOTNULL => 1},
- othercontrol => {TYPE => 'BOOLEAN', NOTNULL => 1},
- canedit => {TYPE => 'BOOLEAN', NOTNULL => 1},
+ group_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ product_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'products',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ entry => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
+ membercontrol => {TYPE => 'INT1', NOTNULL => 1,
+ DEFAULT => CONTROLMAPNA},
+ othercontrol => {TYPE => 'INT1', NOTNULL => 1,
+ DEFAULT => CONTROLMAPNA},
+ canedit => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
editcomponents => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'FALSE'},
editbugs => {TYPE => 'BOOLEAN', NOTNULL => 1,
@@ -938,8 +1172,14 @@
# if GRANT_REGEXP - record was created by evaluating a regexp
user_group_map => {
FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1},
- group_id => {TYPE => 'INT3', NOTNULL => 1},
+ user_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'CASCADE'}},
+ group_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
isbless => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'FALSE'},
grant_type => {TYPE => 'INT1', NOTNULL => 1,
@@ -961,8 +1201,14 @@
# if GROUP_VISIBLE - member groups may see grantor group
group_group_map => {
FIELDS => [
- member_id => {TYPE => 'INT3', NOTNULL => 1},
- grantor_id => {TYPE => 'INT3', NOTNULL => 1},
+ member_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ grantor_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
grant_type => {TYPE => 'INT1', NOTNULL => 1,
DEFAULT => GROUP_MEMBERSHIP},
],
@@ -977,8 +1223,14 @@
# in order to see a bug.
bug_group_map => {
FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- group_id => {TYPE => 'INT3', NOTNULL => 1},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ group_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
],
INDEXES => [
bug_group_map_bug_id_idx =>
@@ -991,8 +1243,14 @@
# in order to see a named query somebody else shares.
namedquery_group_map => {
FIELDS => [
- namedquery_id => {TYPE => 'INT3', NOTNULL => 1},
- group_id => {TYPE => 'INT3', NOTNULL => 1},
+ namedquery_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'namedqueries',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ group_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
],
INDEXES => [
namedquery_group_map_namedquery_id_idx =>
@@ -1003,8 +1261,14 @@
category_group_map => {
FIELDS => [
- category_id => {TYPE => 'INT2', NOTNULL => 1},
- group_id => {TYPE => 'INT3', NOTNULL => 1},
+ category_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'series_categories',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ group_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'groups',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
],
INDEXES => [
category_group_map_category_id_idx =>
@@ -1036,20 +1300,17 @@
PRIMARYKEY => 1},
name => {TYPE => 'varchar(64)', NOTNULL => 1},
classification_id => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '1'},
- description => {TYPE => 'MEDIUMTEXT'},
- milestoneurl => {TYPE => 'TINYTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- disallownew => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 0},
- votesperuser => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => 0},
- maxvotesperbug => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '10000'},
- votestoconfirm => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => 0},
+ DEFAULT => '1',
+ REFERENCES => {TABLE => 'classifications',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 1},
defaultmilestone => {TYPE => 'varchar(20)',
NOTNULL => 1, DEFAULT => "'---'"},
+ allows_unconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'TRUE'},
],
INDEXES => [
products_name_idx => {FIELDS => ['name'],
@@ -1062,7 +1323,10 @@
id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
name => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
+ product_id => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'products',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
initialowner => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid'}},
@@ -1071,6 +1335,8 @@
COLUMN => 'userid',
DELETE => 'SET NULL'}},
description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'TRUE'},
],
INDEXES => [
components_product_id_idx => {FIELDS => [qw(product_id name)],
@@ -1086,26 +1352,37 @@
FIELDS => [
series_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
- creator => {TYPE => 'INT3'},
- category => {TYPE => 'INT2', NOTNULL => 1},
- subcategory => {TYPE => 'INT2', NOTNULL => 1},
+ creator => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'CASCADE'}},
+ category => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'series_categories',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
+ subcategory => {TYPE => 'INT2', NOTNULL => 1,
+ REFERENCES => {TABLE => 'series_categories',
+ COLUMN => 'id',
+ DELETE => 'CASCADE'}},
name => {TYPE => 'varchar(64)', NOTNULL => 1},
frequency => {TYPE => 'INT2', NOTNULL => 1},
- last_viewed => {TYPE => 'DATETIME'},
query => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
is_public => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'FALSE'},
],
INDEXES => [
- series_creator_idx =>
- {FIELDS => [qw(creator category subcategory name)],
- TYPE => 'UNIQUE'},
+ series_creator_idx => ['creator'],
+ series_category_idx => {FIELDS => [qw(category subcategory name)],
+ TYPE => 'UNIQUE'},
],
},
series_data => {
FIELDS => [
- series_id => {TYPE => 'INT3', NOTNULL => 1},
+ series_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'series',
+ COLUMN => 'series_id',
+ DELETE => 'CASCADE'}},
series_date => {TYPE => 'DATETIME', NOTNULL => 1},
series_value => {TYPE => 'INT3', NOTNULL => 1},
],
@@ -1183,6 +1460,8 @@
DELETE => 'CASCADE'}},
subject => {TYPE => 'varchar(128)'},
body => {TYPE => 'MEDIUMTEXT'},
+ mailifnobugs => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
],
},
@@ -1193,7 +1472,10 @@
FIELDS => [
quipid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
- userid => {TYPE => 'INT3'},
+ userid => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'SET NULL'}},
quip => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
approved => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'TRUE'},
@@ -1226,7 +1508,10 @@
setting_value => {
FIELDS => [
- name => {TYPE => 'varchar(32)', NOTNULL => 1},
+ name => {TYPE => 'varchar(32)', NOTNULL => 1,
+ REFERENCES => {TABLE => 'setting',
+ COLUMN => 'name',
+ DELETE => 'CASCADE'}},
value => {TYPE => 'varchar(32)', NOTNULL => 1},
sortindex => {TYPE => 'INT2', NOTNULL => 1},
],
@@ -1244,7 +1529,10 @@
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
- setting_name => {TYPE => 'varchar(32)', NOTNULL => 1},
+ setting_name => {TYPE => 'varchar(32)', NOTNULL => 1,
+ REFERENCES => {TABLE => 'setting',
+ COLUMN => 'name',
+ DELETE => 'CASCADE'}},
setting_value => {TYPE => 'varchar(32)', NOTNULL => 1},
],
INDEXES => [
@@ -1253,6 +1541,93 @@
],
},
+ # THESCHWARTZ TABLES
+ # ------------------
+ # Note: In the standard TheSchwartz schema, most integers are unsigned,
+ # but we didn't implement unsigned ints for Bugzilla schemas, so we
+ # just create signed ints, which should be fine.
+
+ ts_funcmap => {
+ FIELDS => [
+ funcid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1, NOTNULL => 1},
+ funcname => {TYPE => 'varchar(255)', NOTNULL => 1},
+ ],
+ INDEXES => [
+ ts_funcmap_funcname_idx => {FIELDS => ['funcname'],
+ TYPE => 'UNIQUE'},
+ ],
+ },
+
+ ts_job => {
+ FIELDS => [
+ # In a standard TheSchwartz schema, this is a BIGINT, but we
+ # don't have those and I didn't want to add them just for this.
+ jobid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1,
+ NOTNULL => 1},
+ funcid => {TYPE => 'INT4', NOTNULL => 1},
+ # In standard TheSchwartz, this is a MEDIUMBLOB.
+ arg => {TYPE => 'LONGBLOB'},
+ uniqkey => {TYPE => 'varchar(255)'},
+ insert_time => {TYPE => 'INT4'},
+ run_after => {TYPE => 'INT4', NOTNULL => 1},
+ grabbed_until => {TYPE => 'INT4', NOTNULL => 1},
+ priority => {TYPE => 'INT2'},
+ coalesce => {TYPE => 'varchar(255)'},
+ ],
+ INDEXES => [
+ ts_job_funcid_idx => {FIELDS => [qw(funcid uniqkey)],
+ TYPE => 'UNIQUE'},
+ # In a standard TheSchewartz schema, these both go in the other
+ # direction, but there's no reason to have three indexes that
+ # all start with the same column, and our naming scheme doesn't
+ # allow it anyhow.
+ ts_job_run_after_idx => [qw(run_after funcid)],
+ ts_job_coalesce_idx => [qw(coalesce funcid)],
+ ],
+ },
+
+ ts_note => {
+ FIELDS => [
+ # This is a BIGINT in standard TheSchwartz schemas.
+ jobid => {TYPE => 'INT4', NOTNULL => 1},
+ notekey => {TYPE => 'varchar(255)'},
+ value => {TYPE => 'LONGBLOB'},
+ ],
+ INDEXES => [
+ ts_note_jobid_idx => {FIELDS => [qw(jobid notekey)],
+ TYPE => 'UNIQUE'},
+ ],
+ },
+
+ ts_error => {
+ FIELDS => [
+ error_time => {TYPE => 'INT4', NOTNULL => 1},
+ jobid => {TYPE => 'INT4', NOTNULL => 1},
+ message => {TYPE => 'varchar(255)', NOTNULL => 1},
+ funcid => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
+ ],
+ INDEXES => [
+ ts_error_funcid_idx => [qw(funcid error_time)],
+ ts_error_error_time_idx => ['error_time'],
+ ts_error_jobid_idx => ['jobid'],
+ ],
+ },
+
+ ts_exitstatus => {
+ FIELDS => [
+ jobid => {TYPE => 'INTSERIAL', PRIMARYKEY => 1,
+ NOTNULL => 1},
+ funcid => {TYPE => 'INT4', NOTNULL => 1, DEFAULT => 0},
+ status => {TYPE => 'INT2'},
+ completion_time => {TYPE => 'INT4'},
+ delete_after => {TYPE => 'INT4'},
+ ],
+ INDEXES => [
+ ts_exitstatus_funcid_idx => ['funcid'],
+ ts_exitstatus_delete_after_idx => ['delete_after'],
+ ],
+ },
+
# SCHEMA STORAGE
# --------------
@@ -1265,23 +1640,7 @@
};
-use constant FIELD_TABLE_SCHEMA => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- # Note that bz_add_field_table should prepend the table name
- # to these index names.
- INDEXES => [
- value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
- sortkey_idx => ['sortkey', 'value'],
- ],
-};
-
+# Foreign Keys are added in Bugzilla::DB::bz_add_field_tables
use constant MULTI_SELECT_VALUE_TABLE => {
FIELDS => [
bug_id => {TYPE => 'INT3', NOTNULL => 1},
@@ -1292,7 +1651,6 @@
],
};
-
#--------------------------------------------------------------------------
=head1 METHODS
@@ -1384,7 +1742,7 @@
if exists $abstract_schema->{$table};
}
unlock_keys(%$abstract_schema);
- Bugzilla::Hook::process('db_schema-abstract_schema',
+ Bugzilla::Hook::process('db_schema_abstract_schema',
{ schema => $abstract_schema });
unlock_hash(%$abstract_schema);
}
@@ -1490,8 +1848,9 @@
# DEFAULT attribute must appear before any column constraints
# (e.g., NOT NULL), for Oracle
$type_ddl .= " DEFAULT $default" if (defined($default));
- $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL});
+ # PRIMARY KEY must appear before NOT NULL for SQLite.
$type_ddl .= " PRIMARY KEY" if ($finfo->{PRIMARYKEY});
+ $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL});
return($type_ddl);
@@ -1564,11 +1923,33 @@
}
-sub get_add_fk_sql {
- my ($self, $table, $column, $def) = @_;
+sub get_add_fks_sql {
+ my ($self, $table, $column_fks) = @_;
- my $fk_string = $self->get_fk_ddl($table, $column, $def);
- return ("ALTER TABLE $table ADD $fk_string");
+ my @add = $self->_column_fks_to_ddl($table, $column_fks);
+
+ my @sql;
+ if ($self->MULTIPLE_FKS_IN_ALTER) {
+ my $alter = "ALTER TABLE $table ADD " . join(', ADD ', @add);
+ push(@sql, $alter);
+ }
+ else {
+ foreach my $fk_string (@add) {
+ push(@sql, "ALTER TABLE $table ADD $fk_string");
+ }
+ }
+ return @sql;
+}
+
+sub _column_fks_to_ddl {
+ my ($self, $table, $column_fks) = @_;
+ my @ddl;
+ foreach my $column (keys %$column_fks) {
+ my $def = $column_fks->{$column};
+ my $fk_string = $self->get_fk_ddl($table, $column, $def);
+ push(@ddl, $fk_string);
+ }
+ return @ddl;
}
sub get_drop_fk_sql {
@@ -1710,7 +2091,7 @@
return @ddl;
} #eosub--get_table_ddl
-#--------------------------------------------------------------------------
+
sub _get_create_table_ddl {
=item C<_get_create_table_ddl>
@@ -1726,25 +2107,27 @@
my $thash = $self->{schema}{$table};
die "Table $table does not exist in the database schema."
- unless (ref($thash));
+ unless ref $thash;
- my $create_table = "CREATE TABLE $table \(\n";
-
+ my (@col_lines, @fk_lines);
my @fields = @{ $thash->{FIELDS} };
while (@fields) {
my $field = shift(@fields);
my $finfo = shift(@fields);
- $create_table .= "\t$field\t" . $self->get_type_ddl($finfo);
- $create_table .= "," if (@fields);
- $create_table .= "\n";
+ push(@col_lines, "\t$field\t" . $self->get_type_ddl($finfo));
+ if ($self->FK_ON_CREATE and $finfo->{REFERENCES}) {
+ my $fk = $finfo->{REFERENCES};
+ my $fk_ddl = $self->get_fk_ddl($table, $field, $fk);
+ push(@fk_lines, $fk_ddl);
+ }
}
+
+ my $sql = "CREATE TABLE $table (\n" . join(",\n", @col_lines, @fk_lines)
+ . "\n)";
+ return $sql
- $create_table .= "\)";
+}
- return($create_table)
-
-} #eosub--_get_create_table_ddl
-#--------------------------------------------------------------------------
sub _get_create_index_ddl {
=item C<_get_create_index_ddl>
@@ -1798,8 +2181,8 @@
if defined $init_value;
if (defined $definition->{REFERENCES}) {
- push(@statements, $self->get_add_fk_sql($table, $column,
- $definition->{REFERENCES}));
+ push(@statements, $self->get_add_fks_sql($table, { $column =>
+ $definition->{REFERENCES} }));
}
return (@statements);
@@ -1859,7 +2242,8 @@
=cut
- my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
+ my $self = shift;
+ my ($table, $column, $new_def, $set_nulls_to) = @_;
my @statements;
my $old_def = $self->get_column_abstract($table, $column);
@@ -1873,6 +2257,10 @@
my $default = $new_def->{DEFAULT};
my $default_old = $old_def->{DEFAULT};
+
+ if (defined $default) {
+ $default = $specific->{$default} if exists $specific->{$default};
+ }
# This first condition prevents "uninitialized value" errors.
if (!defined $default && !defined $default_old) {
# Do Nothing
@@ -1886,24 +2274,13 @@
elsif ( (defined $default && !defined $default_old) ||
($default ne $default_old) )
{
- $default = $specific->{$default} if exists $specific->{$default};
push(@statements, "ALTER TABLE $table ALTER COLUMN $column "
. " SET DEFAULT $default");
}
# If we went from NULL to NOT NULL.
if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
- my $setdefault;
- # Handle any fields that were NULL before, if we have a default,
- $setdefault = $new_def->{DEFAULT} if exists $new_def->{DEFAULT};
- # But if we have a set_nulls_to, that overrides the DEFAULT
- # (although nobody would usually specify both a default and
- # a set_nulls_to.)
- $setdefault = $set_nulls_to if defined $set_nulls_to;
- if (defined $setdefault) {
- push(@statements, "UPDATE $table SET $column = $setdefault"
- . " WHERE $column IS NULL");
- }
+ push(@statements, $self->_set_nulls_sql(@_));
push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
. " SET NOT NULL");
}
@@ -1925,6 +2302,27 @@
return @statements;
}
+# Helps handle any fields that were NULL before, if we have a default,
+# when doing an ALTER COLUMN.
+sub _set_nulls_sql {
+ my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
+ my $default = $new_def->{DEFAULT};
+ # If we have a set_nulls_to, that overrides the DEFAULT
+ # (although nobody would usually specify both a default and
+ # a set_nulls_to.)
+ $default = $set_nulls_to if defined $set_nulls_to;
+ if (defined $default) {
+ my $specific = $self->{db_specific};
+ $default = $specific->{$default} if exists $specific->{$default};
+ }
+ my @sql;
+ if (defined $default) {
+ push(@sql, "UPDATE $table SET $column = $default"
+ . " WHERE $column IS NULL");
+ }
+ return @sql;
+}
+
sub get_drop_index_ddl {
=item C<get_drop_index_ddl($table, $name)>
@@ -2204,7 +2602,7 @@
my ($self, $table, $column) = @_;
my $abstract_fields = $self->{abstract_schema}{$table}{FIELDS};
- my $name_position = lsearch($abstract_fields, $column);
+ my $name_position = firstidx { $_ eq $column } @$abstract_fields;
die "Attempted to delete nonexistent column ${table}.${column}"
if $name_position == -1;
# Delete the key/value pair from the array.
@@ -2258,6 +2656,28 @@
$self->_set_object($table, $column, $new_def, $fields);
}
+=item C<set_fk($table, $column \%fk_def)>
+
+Sets the C<REFERENCES> item on the specified column.
+
+=cut
+
+sub set_fk {
+ my ($self, $table, $column, $fk_def) = @_;
+ # Don't want to modify the source def before we explicitly set it below.
+ # This is just us being extra-cautious.
+ my $column_def = dclone($self->get_column_abstract($table, $column));
+ die "Tried to set an fk on $table.$column, but that column doesn't exist"
+ if !$column_def;
+ if ($fk_def) {
+ $column_def->{REFERENCES} = $fk_def;
+ }
+ else {
+ delete $column_def->{REFERENCES};
+ }
+ $self->set_column($table, $column, $column_def);
+}
+
sub set_index {
=item C<set_index($table, $name, $definition)>
@@ -2293,7 +2713,7 @@
sub _set_object {
my ($self, $table, $name, $definition, $array_to_change) = @_;
- my $obj_position = lsearch($array_to_change, $name) + 1;
+ my $obj_position = (firstidx { $_ eq $name } @$array_to_change) + 1;
# If the object doesn't exist, then add it.
if (!$obj_position) {
push(@$array_to_change, $name);
@@ -2326,7 +2746,7 @@
my ($self, $table, $name) = @_;
my $indexes = $self->{abstract_schema}{$table}{INDEXES};
- my $name_position = lsearch($indexes, $name);
+ my $name_position = firstidx { $_ eq $name } @$indexes;
die "Attempted to delete nonexistent index $name on the $table table"
if $name_position == -1;
# Delete the key/value pair from the array.
@@ -2410,7 +2830,7 @@
Description: Used for when you've read a serialized Schema off the disk,
and you want a Schema object that represents that data.
Params: $serialized - scalar. The serialized data.
- $version - A number in the format X.YZ. The "version"
+ $version - A number. The "version"
of the Schema that did the serialization.
See the docs for C<SCHEMA_VERSION> for more details.
Returns: A Schema object. It will have the methods of (and work
@@ -2423,7 +2843,7 @@
my ($class, $serialized, $version) = @_;
my $thawed_hash;
- if (int($version) < 2) {
+ if ($version < 2) {
$thawed_hash = thaw($serialized);
}
else {
@@ -2433,6 +2853,22 @@
$thawed_hash = ${$cpt->varglob('VAR1')};
}
+ # Version 2 didn't have the "created" key for REFERENCES items.
+ if ($version < 3) {
+ my $standard = $class->new()->{abstract_schema};
+ foreach my $table_name (keys %$thawed_hash) {
+ my %standard_fields =
+ @{ $standard->{$table_name}->{FIELDS} || [] };
+ my $table = $thawed_hash->{$table_name};
+ my %fields = @{ $table->{FIELDS} || [] };
+ while (my ($field, $def) = each %fields) {
+ if (exists $def->{REFERENCES}) {
+ $def->{REFERENCES}->{created} = 1;
+ }
+ }
+ }
+ }
+
return $class->new(undef, $thawed_hash);
}
@@ -2502,7 +2938,7 @@
=item C<SMALLSERIAL>
-An auto-increment L</INT1>
+An auto-increment L</INT2>
=item C<MEDIUMSERIAL>
diff --git a/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Mysql.pm b/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Mysql.pm
index 6277169..8c9ea2d 100644
--- a/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Mysql.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Mysql.pm
@@ -43,6 +43,9 @@
# that should be interpreted as a BOOLEAN instead of as an INT1 when
# reading in the Schema from the disk. The values are discarded; I just
# used "1" for simplicity.
+#
+# THIS CONSTANT IS ONLY USED FOR UPGRADES FROM 2.18 OR EARLIER. DON'T
+# UPDATE IT TO MODERN COLUMN NAMES OR DEFINITIONS.
use constant BOOLEAN_MAP => {
bugs => {everconfirmed => 1, reporter_accessible => 1,
cclist_accessible => 1, qacontact_accessible => 1,
@@ -178,13 +181,35 @@
delete $new_def_copy{PRIMARYKEY};
}
- my $new_ddl = $self->get_type_ddl(\%new_def_copy);
my @statements;
push(@statements, "UPDATE $table SET $column = $set_nulls_to
WHERE $column IS NULL") if defined $set_nulls_to;
- push(@statements, "ALTER TABLE $table CHANGE COLUMN
+
+ # Calling SET DEFAULT or DROP DEFAULT is *way* faster than calling
+ # CHANGE COLUMN, so just do that if we're just changing the default.
+ my %old_defaultless = %$old_def;
+ my %new_defaultless = %$new_def;
+ delete $old_defaultless{DEFAULT};
+ delete $new_defaultless{DEFAULT};
+ if (!$self->columns_equal($old_def, $new_def)
+ && $self->columns_equal(\%new_defaultless, \%old_defaultless))
+ {
+ if (!defined $new_def->{DEFAULT}) {
+ push(@statements,
+ "ALTER TABLE $table ALTER COLUMN $column DROP DEFAULT");
+ }
+ else {
+ push(@statements, "ALTER TABLE $table ALTER COLUMN $column
+ SET DEFAULT " . $new_def->{DEFAULT});
+ }
+ }
+ else {
+ my $new_ddl = $self->get_type_ddl(\%new_def_copy);
+ push(@statements, "ALTER TABLE $table CHANGE COLUMN
$column $column $new_ddl");
+ }
+
if ($old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY}) {
# Dropping a PRIMARY KEY needs an explicit DROP PRIMARY KEY
push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
@@ -241,6 +266,11 @@
return ($sql);
}
+sub get_set_serial_sql {
+ my ($self, $table, $column, $value) = @_;
+ return ("ALTER TABLE $table AUTO_INCREMENT = $value");
+}
+
# Converts a DBI column_info output to an abstract column definition.
# Expects to only be called by Bugzila::DB::Mysql::_bz_build_schema_from_disk,
# although there's a chance that it will also work properly if called
diff --git a/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Oracle.pm b/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Oracle.pm
index bd5c724..f2d5b8b 100644
--- a/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Oracle.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Oracle.pm
@@ -35,6 +35,7 @@
use Bugzilla::Util;
use constant ADD_COLUMN => 'ADD';
+use constant MULTIPLE_FKS_IN_ALTER => 0;
# Whether this is true or not, this is what it needs to be in order for
# hash_identifier to maintain backwards compatibility with versions before
# 3.2rc2.
@@ -136,37 +137,44 @@
# - Delete CASCADE
# - Delete SET NULL
sub get_fk_ddl {
- my ($self, $table, $column, $references) = @_;
- return "" if !$references;
+ my $self = shift;
+ my $ddl = $self->SUPER::get_fk_ddl(@_);
- my $update = $references->{UPDATE} || 'CASCADE';
- my $delete = $references->{DELETE};
- my $to_table = $references->{TABLE} || confess "No table in reference";
- my $to_column = $references->{COLUMN} || confess "No column in reference";
- my $fk_name = $self->_get_fk_name($table, $column, $references);
+ # iThe Bugzilla Oracle driver implements UPDATE via a trigger.
+ $ddl =~ s/ON UPDATE \S+//i;
+ # RESTRICT is the default for DELETE on Oracle and may not be specified.
+ $ddl =~ s/ON DELETE RESTRICT//i;
- my $fk_string = "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
- . " REFERENCES $to_table($to_column)\n";
-
- $fk_string = $fk_string . " ON DELETE $delete" if $delete;
-
- if ( $update =~ /CASCADE/i ){
- my $tr_str = "CREATE OR REPLACE TRIGGER ${fk_name}_UC"
- . " AFTER UPDATE ON ". $to_table
- . " REFERENCING "
- . " NEW AS NEW "
- . " OLD AS OLD "
- . " FOR EACH ROW "
- . " BEGIN "
- . " UPDATE $table"
- . " SET $column = :NEW.$to_column"
- . " WHERE $column = :OLD.$to_column;"
- . " END ${fk_name}_UC;";
- my $dbh = Bugzilla->dbh;
- $dbh->do($tr_str);
+ return $ddl;
+}
+
+sub get_add_fks_sql {
+ my $self = shift;
+ my ($table, $column_fks) = @_;
+ my @sql = $self->SUPER::get_add_fks_sql(@_);
+
+ foreach my $column (keys %$column_fks) {
+ my $fk = $column_fks->{$column};
+ next if $fk->{UPDATE} && uc($fk->{UPDATE}) ne 'CASCADE';
+ my $fk_name = $self->_get_fk_name($table, $column, $fk);
+ my $to_column = $fk->{COLUMN};
+ my $to_table = $fk->{TABLE};
+
+ my $trigger = <<END;
+CREATE OR REPLACE TRIGGER ${fk_name}_UC
+ AFTER UPDATE OF $to_column ON $to_table
+ REFERENCING NEW AS NEW OLD AS OLD
+ FOR EACH ROW
+ BEGIN
+ UPDATE $table
+ SET $column = :NEW.$to_column
+ WHERE $column = :OLD.$to_column;
+ END ${fk_name}_UC;
+END
+ push(@sql, $trigger);
}
- return $fk_string;
+ return @sql;
}
sub get_drop_fk_sql {
@@ -206,6 +214,10 @@
my $default = $new_def->{DEFAULT};
my $default_old = $old_def->{DEFAULT};
+
+ if (defined $default) {
+ $default = $specific->{$default} if exists $specific->{$default};
+ }
# This first condition prevents "uninitialized value" errors.
if (!defined $default && !defined $default_old) {
# Do Nothing
@@ -219,7 +231,6 @@
elsif ( (defined $default && !defined $default_old) ||
($default ne $default_old) )
{
- $default = $specific->{$default} if exists $specific->{$default};
push(@statements, "ALTER TABLE $table MODIFY $column "
. " DEFAULT $default");
}
@@ -228,7 +239,7 @@
if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
my $setdefault;
# Handle any fields that were NULL before, if we have a default,
- $setdefault = $new_def->{DEFAULT} if exists $new_def->{DEFAULT};
+ $setdefault = $default if defined $default;
# But if we have a set_nulls_to, that overrides the DEFAULT
# (although nobody would usually specify both a default and
# a set_nulls_to.)
@@ -340,17 +351,10 @@
my $def = $self->get_column_abstract($table, $old_name);
if ($def->{TYPE} =~ /SERIAL/i) {
# We have to rename the series also, and fix the default of the series.
- push(@sql, "RENAME ${table}_${old_name}_SEQ TO
- ${table}_${new_name}_seq");
- my $serial_sql =
- "CREATE OR REPLACE TRIGGER ${table}_${new_name}_TR "
- . " BEFORE INSERT ON ${table} "
- . " FOR EACH ROW "
- . " BEGIN "
- . " SELECT ${table}_${new_name}_SEQ.NEXTVAL "
- . " INTO :NEW.${new_name} FROM DUAL; "
- . " END;";
- push(@sql, $serial_sql);
+ my $old_seq = "${table}_${old_name}_SEQ";
+ my $new_seq = "${table}_${new_name}_SEQ";
+ push(@sql, "RENAME $old_seq TO $new_seq");
+ push(@sql, $self->_get_create_trigger_ddl($table, $new_name, $new_seq));
push(@sql, "DROP TRIGGER ${table}_${old_name}_TR");
}
if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL} ) {
@@ -360,6 +364,53 @@
return @sql;
}
+sub get_rename_table_sql {
+ my ($self, $old_name, $new_name) = @_;
+ if (lc($old_name) eq lc($new_name)) {
+ # if the only change is a case change, return an empty list.
+ return ();
+ }
+
+ my @sql = ("ALTER TABLE $old_name RENAME TO $new_name");
+ my @columns = $self->get_table_columns($old_name);
+ foreach my $column (@columns) {
+ my $def = $self->get_column_abstract($old_name, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ # If there's a SERIAL column on this table, we also need
+ # to rename the sequence.
+ my $old_seq = "${old_name}_${column}_SEQ";
+ my $new_seq = "${new_name}_${column}_SEQ";
+ push(@sql, "RENAME $old_seq TO $new_seq");
+ push(@sql, $self->_get_create_trigger_ddl($new_name, $column, $new_seq));
+ push(@sql, "DROP TRIGGER ${old_name}_${column}_TR");
+ }
+ if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL}) {
+ push(@sql, _get_notnull_trigger_ddl($new_name, $column));
+ push(@sql, "DROP TRIGGER ${old_name}_${column}");
+ }
+ }
+
+ return @sql;
+}
+
+sub get_drop_table_ddl {
+ my ($self, $name) = @_;
+ my @sql;
+
+ my @columns = $self->get_table_columns($name);
+ foreach my $column (@columns) {
+ my $def = $self->get_column_abstract($name, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ # If there's a SERIAL column on this table, we also need
+ # to remove the sequence.
+ push(@sql, "DROP SEQUENCE ${name}_${column}_SEQ");
+ }
+ }
+ push(@sql, "DROP TABLE $name CASCADE CONSTRAINTS PURGE");
+
+ return @sql;
+}
+
sub _get_notnull_trigger_ddl {
my ($table, $column) = @_;
@@ -387,17 +438,47 @@
. " NOMAXVALUE "
. " NOCYCLE "
. " NOCACHE";
- my $serial_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
- . " BEFORE INSERT ON ${table} "
- . " FOR EACH ROW "
- . " BEGIN "
- . " SELECT ${seq_name}.NEXTVAL "
- . " INTO :NEW.${column} FROM DUAL; "
- . " END;";
push (@ddl, $seq_sql);
- push (@ddl, $serial_sql);
+ push(@ddl, $self->_get_create_trigger_ddl($table, $column, $seq_name));
return @ddl;
}
+sub _get_create_trigger_ddl {
+ my ($self, $table, $column, $seq_name) = @_;
+ my $serial_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
+ . " BEFORE INSERT ON $table "
+ . " FOR EACH ROW "
+ . " BEGIN "
+ . " SELECT ${seq_name}.NEXTVAL "
+ . " INTO :NEW.$column FROM DUAL; "
+ . " END;";
+ return $serial_sql;
+}
+
+sub get_set_serial_sql {
+ my ($self, $table, $column, $value) = @_;
+ my @sql;
+ my $seq_name = "${table}_${column}_SEQ";
+ push(@sql, "DROP SEQUENCE ${seq_name}");
+ push(@sql, $self->_get_create_seq_ddl($table, $column, $value));
+ return @sql;
+}
+
+sub get_drop_column_ddl {
+ my $self = shift;
+ my ($table, $column) = @_;
+ my @sql;
+ push(@sql, $self->SUPER::get_drop_column_ddl(@_));
+ my $dbh=Bugzilla->dbh;
+ my $trigger_name = uc($table . "_" . $column);
+ my $exist_trigger = $dbh->selectcol_arrayref(
+ "SELECT OBJECT_NAME FROM USER_OBJECTS
+ WHERE OBJECT_NAME = ?", undef, $trigger_name);
+ if(@$exist_trigger) {
+ push(@sql, "DROP TRIGGER $trigger_name");
+ }
+ return @sql;
+}
+
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Pg.pm b/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Pg.pm
index 070c0b03..ef6e567 100644
--- a/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Pg.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Pg.pm
@@ -100,11 +100,9 @@
my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
my $def = $self->get_column_abstract($table, $old_name);
if ($def->{TYPE} =~ /SERIAL/i) {
- # We have to rename the series also, and fix the default of the series.
- push(@sql, "ALTER TABLE ${table}_${old_name}_seq
- RENAME TO ${table}_${new_name}_seq");
- push(@sql, "ALTER TABLE $table ALTER COLUMN $new_name
- SET DEFAULT NEXTVAL('${table}_${new_name}_seq')");
+ # We have to rename the series also.
+ push(@sql, "ALTER SEQUENCE ${table}_${old_name}_seq
+ RENAME TO ${table}_${new_name}_seq");
}
return @sql;
}
@@ -116,7 +114,36 @@
# is case-insensitive and will return an error about a duplicate name
return ();
}
- return ("ALTER TABLE $old_name RENAME TO $new_name");
+
+ my @sql = ("ALTER TABLE $old_name RENAME TO $new_name");
+
+ # If there's a SERIAL column on this table, we also need to rename the
+ # sequence.
+ # If there is a PRIMARY KEY, we need to rename it too.
+ my @columns = $self->get_table_columns($old_name);
+ foreach my $column (@columns) {
+ my $def = $self->get_column_abstract($old_name, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ my $old_seq = "${old_name}_${column}_seq";
+ my $new_seq = "${new_name}_${column}_seq";
+ push(@sql, "ALTER SEQUENCE $old_seq RENAME TO $new_seq");
+ push(@sql, "ALTER TABLE $new_name ALTER COLUMN $column
+ SET DEFAULT NEXTVAL('$new_seq')");
+ }
+ if ($def->{PRIMARYKEY}) {
+ my $old_pk = "${old_name}_pkey";
+ my $new_pk = "${new_name}_pkey";
+ push(@sql, "ALTER INDEX $old_pk RENAME to $new_pk");
+ }
+ }
+
+ return @sql;
+}
+
+sub get_set_serial_sql {
+ my ($self, $table, $column, $value) = @_;
+ return ("SELECT setval('${table}_${column}_seq', $value, false)
+ FROM $table");
}
sub _get_alter_type_sql {
@@ -130,9 +157,10 @@
if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
die("You cannot specify a DEFAULT on a SERIAL-type column.")
if $new_def->{DEFAULT};
- $type =~ s/serial/integer/i;
}
+ $type =~ s/\bserial\b/integer/i;
+
# On Pg, you don't need UNIQUE if you're a PK--it creates
# two identical indexes otherwise.
$type =~ s/unique//i if $new_def->{PRIMARYKEY};
@@ -141,7 +169,8 @@
TYPE $type");
if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
- push(@statements, "CREATE SEQUENCE ${table}_${column}_seq");
+ push(@statements, "CREATE SEQUENCE ${table}_${column}_seq
+ OWNED BY $table.$column");
push(@statements, "SELECT setval('${table}_${column}_seq',
MAX($table.$column))
FROM $table");
@@ -154,10 +183,9 @@
if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
DROP DEFAULT");
- # XXX Pg actually won't let us drop the sequence, even though it's
- # no longer in use. So we harmlessly leave behind a sequence
- # that does nothing.
- #push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
+ push(@statements, "ALTER SEQUENCE ${table}_${column}_seq
+ OWNED BY NONE");
+ push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
}
return @statements;
diff --git a/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Sqlite.pm b/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Sqlite.pm
new file mode 100644
index 0000000..aad1f17
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/DB/Schema/Sqlite.pm
@@ -0,0 +1,312 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+package Bugzilla::DB::Schema::Sqlite;
+use base qw(Bugzilla::DB::Schema);
+
+use Bugzilla::Error;
+use Bugzilla::Util qw(generate_random_password);
+
+use Storable qw(dclone);
+
+use constant FK_ON_CREATE => 1;
+
+sub _initialize {
+
+ my $self = shift;
+
+ $self = $self->SUPER::_initialize(@_);
+
+ $self->{db_specific} = {
+ BOOLEAN => 'integer',
+ FALSE => '0',
+ TRUE => '1',
+
+ INT1 => 'integer',
+ INT2 => 'integer',
+ INT3 => 'integer',
+ INT4 => 'integer',
+
+ SMALLSERIAL => 'SERIAL',
+ MEDIUMSERIAL => 'SERIAL',
+ INTSERIAL => 'SERIAL',
+
+ TINYTEXT => 'text',
+ MEDIUMTEXT => 'text',
+ LONGTEXT => 'text',
+
+ LONGBLOB => 'blob',
+
+ DATETIME => 'DATETIME',
+ };
+
+ $self->_adjust_schema;
+
+ return $self;
+
+}
+
+#################################
+# General SQLite Schema Helpers #
+#################################
+
+sub _sqlite_create_table {
+ my ($self, $table) = @_;
+ return scalar Bugzilla->dbh->selectrow_array(
+ "SELECT sql FROM sqlite_master WHERE name = ? AND type = 'table'",
+ undef, $table);
+}
+
+sub _sqlite_table_lines {
+ my $self = shift;
+ my $table_sql = $self->_sqlite_create_table(@_);
+ $table_sql =~ s/\n*\)$//s;
+ # The $ makes this work even if people some day add crazy stuff to their
+ # schema like multi-column foreign keys.
+ return split(/,\s*$/m, $table_sql);
+}
+
+# This does most of the "heavy lifting" of the schema-altering functions.
+sub _sqlite_alter_schema {
+ my ($self, $table, $create_table, $options) = @_;
+
+ # $create_table is sometimes an array in the form that _sqlite_table_lines
+ # returns.
+ if (ref $create_table) {
+ $create_table = join(',', @$create_table) . "\n)";
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ my $random = generate_random_password(5);
+ my $rename_to = "${table}_$random";
+
+ my @columns = $dbh->bz_table_columns_real($table);
+ push(@columns, $options->{extra_column}) if $options->{extra_column};
+ if (my $exclude = $options->{exclude_column}) {
+ @columns = grep { $_ ne $exclude } @columns;
+ }
+ my @insert_cols = @columns;
+ my @select_cols = @columns;
+ if (my $rename = $options->{rename}) {
+ foreach my $from (keys %$rename) {
+ my $to = $rename->{$from};
+ @insert_cols = map { $_ eq $from ? $to : $_ } @insert_cols;
+ }
+ }
+
+ my $insert_str = join(',', @insert_cols);
+ my $select_str = join(',', @select_cols);
+ my $copy_sql = "INSERT INTO $table ($insert_str)"
+ . " SELECT $select_str FROM $rename_to";
+
+ # We have to turn FKs off before doing this. Otherwise, when we rename
+ # the table, all of the FKs in the other tables will be automatically
+ # updated to point to the renamed table. Note that PRAGMA foreign_keys
+ # can only be set outside of a transaction--otherwise it is a no-op.
+ if ($dbh->bz_in_transaction) {
+ die "can't alter the schema inside of a transaction";
+ }
+ my @sql = (
+ 'PRAGMA foreign_keys = OFF',
+ 'BEGIN EXCLUSIVE TRANSACTION',
+ @{ $options->{pre_sql} || [] },
+ "ALTER TABLE $table RENAME TO $rename_to",
+ $create_table,
+ $copy_sql,
+ "DROP TABLE $rename_to",
+ 'COMMIT TRANSACTION',
+ 'PRAGMA foreign_keys = ON',
+ );
+}
+
+# For finding a particular column's definition in a CREATE TABLE statement.
+sub _sqlite_column_regex {
+ my ($column) = @_;
+ # 1 = Comma at start
+ # 2 = Column name + Space
+ # 3 = Definition
+ # 4 = Ending comma
+ return qr/(^|,)(\s\Q$column\E\s+)(.*?)(,|$)/m;
+}
+
+#############################
+# Schema Setup & Alteration #
+#############################
+
+sub get_create_database_sql {
+ # If we get here, it means there was some error creating the
+ # database file during bz_create_database in Bugzilla::DB,
+ # and we just want to display that error instead of doing
+ # anything else.
+ Bugzilla->dbh;
+ die "Reached an unreachable point";
+}
+
+sub _get_create_table_ddl {
+ my $self = shift;
+ my ($table) = @_;
+ my $ddl = $self->SUPER::_get_create_table_ddl(@_);
+
+ # TheSchwartz uses its own driver to access its tables, meaning
+ # that it doesn't understand "COLLATE bugzilla" and in fact
+ # SQLite throws an error when TheSchwartz tries to access its
+ # own tables, if COLLATE bugzilla is on them. We don't have
+ # to fix this elsewhere currently, because we only create
+ # TheSchwartz's tables, we never modify them.
+ if ($table =~ /^ts_/) {
+ $ddl =~ s/ COLLATE bugzilla//g;
+ }
+ return $ddl;
+}
+
+sub get_type_ddl {
+ my $self = shift;
+ my $def = dclone($_[0]);
+
+ my $ddl = $self->SUPER::get_type_ddl(@_);
+ if ($def->{PRIMARYKEY} and $def->{TYPE} =~ /SERIAL/i) {
+ $ddl =~ s/\bSERIAL\b/integer/;
+ $ddl =~ s/\bPRIMARY KEY\b/PRIMARY KEY AUTOINCREMENT/;
+ }
+ if ($def->{TYPE} =~ /text/i or $def->{TYPE} =~ /char/i) {
+ $ddl .= " COLLATE bugzilla";
+ }
+ # Don't collate DATETIME fields.
+ if ($def->{TYPE} eq 'DATETIME') {
+ $ddl =~ s/\bDATETIME\b/text COLLATE BINARY/;
+ }
+ return $ddl;
+}
+
+sub get_alter_column_ddl {
+ my $self = shift;
+ my ($table, $column, $new_def, $set_nulls_to) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $new_ddl = $self->get_type_ddl($new_def);
+ # When we do ADD COLUMN, columns can show up all on one line separated
+ # by commas, so we have to account for that.
+ my $column_regex = _sqlite_column_regex($column);
+ $table_sql =~ s/$column_regex/$1$2$new_ddl$4/
+ || die "couldn't find $column in $table:\n$table_sql";
+ my @pre_sql = $self->_set_nulls_sql(@_);
+ return $self->_sqlite_alter_schema($table, $table_sql,
+ { pre_sql => \@pre_sql });
+}
+
+sub get_add_column_ddl {
+ my $self = shift;
+ my ($table, $column, $definition, $init_value) = @_;
+ # SQLite can use the normal ADD COLUMN when:
+ # * The column isn't a PK
+ if ($definition->{PRIMARYKEY}) {
+ if ($definition->{NOTNULL} and $definition->{TYPE} !~ /SERIAL/i) {
+ die "You can only add new SERIAL type PKs with SQLite";
+ }
+ my $table_sql = $self->_sqlite_new_column_sql(@_);
+ # This works because _sqlite_alter_schema will exclude the new column
+ # in its INSERT ... SELECT statement, meaning that when the "new"
+ # table is populated, it will have AUTOINCREMENT values generated
+ # for it.
+ return $self->_sqlite_alter_schema($table, $table_sql);
+ }
+ # * The column has a default one way or another. Either it
+ # defaults to NULL (it lacks NOT NULL) or it has a DEFAULT
+ # clause. Since we also require this when doing bz_add_column (in
+ # the way of forcing an init_value for NOT NULL columns with no
+ # default), we first set the init_value as the default and then
+ # alter the column.
+ if ($definition->{NOTNULL} and !defined $definition->{DEFAULT}) {
+ my %with_default = %$definition;
+ $with_default{DEFAULT} = $init_value;
+ my @pre_sql =
+ $self->SUPER::get_add_column_ddl($table, $column, \%with_default);
+ my $table_sql = $self->_sqlite_new_column_sql(@_);
+ return $self->_sqlite_alter_schema($table, $table_sql,
+ { pre_sql => \@pre_sql, extra_column => $column });
+ }
+
+ return $self->SUPER::get_add_column_ddl(@_);
+}
+
+sub _sqlite_new_column_sql {
+ my ($self, $table, $column, $def) = @_;
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $new_ddl = $self->get_type_ddl($def);
+ my $new_line = "\t$column\t$new_ddl";
+ $table_sql =~ s/^(CREATE TABLE \w+ \()/$1\n$new_line,/s
+ || die "Can't find start of CREATE TABLE:\n$table_sql";
+ return $table_sql;
+}
+
+sub get_drop_column_ddl {
+ my ($self, $table, $column) = @_;
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $column_regex = _sqlite_column_regex($column);
+ $table_sql =~ s/$column_regex/$1/
+ || die "Can't find column $column: $table_sql";
+ # Make sure we don't end up with a comma at the end of the definition.
+ $table_sql =~ s/,\s+\)$/\n)/s;
+ return $self->_sqlite_alter_schema($table, $table_sql,
+ { exclude_column => $column });
+}
+
+sub get_rename_column_ddl {
+ my ($self, $table, $old_name, $new_name) = @_;
+ my $table_sql = $self->_sqlite_create_table($table);
+ my $column_regex = _sqlite_column_regex($old_name);
+ $table_sql =~ s/$column_regex/$1\t$new_name\t$3$4/
+ || die "Can't find $old_name: $table_sql";
+ my %rename = ($old_name => $new_name);
+ return $self->_sqlite_alter_schema($table, $table_sql,
+ { rename => \%rename });
+}
+
+################
+# Foreign Keys #
+################
+
+sub get_add_fks_sql {
+ my ($self, $table, $column_fks) = @_;
+ my @clauses = $self->_sqlite_table_lines($table);
+ my @add = $self->_column_fks_to_ddl($table, $column_fks);
+ push(@clauses, @add);
+ return $self->_sqlite_alter_schema($table, \@clauses);
+}
+
+sub get_drop_fk_sql {
+ my ($self, $table, $column, $references) = @_;
+ my @clauses = $self->_sqlite_table_lines($table);
+ my $fk_name = $self->_get_fk_name($table, $column, $references);
+
+ my $line_re = qr/^\s+CONSTRAINT $fk_name /s;
+ grep { $line_re } @clauses
+ or die "Can't find $fk_name: " . join(',', @clauses);
+ @clauses = grep { $_ !~ $line_re } @clauses;
+
+ return $self->_sqlite_alter_schema($table, \@clauses);
+}
+
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/DB/Sqlite.pm b/Websites/bugs.webkit.org/Bugzilla/DB/Sqlite.pm
new file mode 100644
index 0000000..e13fd18
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/DB/Sqlite.pm
@@ -0,0 +1,311 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+package Bugzilla::DB::Sqlite;
+use base qw(Bugzilla::DB);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Install::Util qw(install_string);
+
+use DateTime;
+use POSIX ();
+
+# SQLite only supports the SERIALIZABLE and READ UNCOMMITTED isolation
+# levels. SERIALIZABLE is used by default and SET TRANSACTION ISOLATION
+# LEVEL is not implemented.
+use constant ISOLATION_LEVEL => undef;
+
+# Since we're literally using Perl's regexes, we can use something
+# simpler and more efficient than what Bugzilla::DB uses.
+use constant WORD_START => '(?:^|\W)';
+use constant WORD_END => '(?:$|\W)';
+
+# For some reason, dropping the related FKs causes the index to
+# disappear early, which causes all sorts of problems.
+use constant INDEX_DROPS_REQUIRE_FK_DROPS => 0;
+
+####################################
+# Functions Added To SQLite Itself #
+####################################
+
+# A case-insensitive, Unicode collation for SQLite. This allows us to
+# make all comparisons and sorts case-insensitive (though unfortunately
+# not accent-insensitive).
+sub _sqlite_collate_ci { lc($_[0]) cmp lc($_[1]) }
+
+sub _sqlite_mod { $_[0] % $_[1] }
+
+sub _sqlite_now {
+ my $now = DateTime->now(time_zone => Bugzilla->local_timezone);
+ return $now->ymd . ' ' . $now->hms;
+}
+
+# SQL's POSITION starts its values from 1 instead of 0 (so we add 1).
+sub _sqlite_position {
+ my ($text, $fragment) = @_;
+ if (!defined $text or !defined $fragment) {
+ return undef;
+ }
+ my $pos = index $text, $fragment;
+ return $pos + 1;
+}
+
+sub _sqlite_position_ci {
+ my ($text, $fragment) = @_;
+ return _sqlite_position(lc($text), lc($fragment));
+}
+
+###############
+# Constructor #
+###############
+
+sub new {
+ my ($class, $params) = @_;
+ my $db_name = $params->{db_name};
+
+ # Let people specify paths intead of data/ for the DB.
+ if ($db_name and $db_name !~ m{[\\/]}) {
+ # When the DB is first created, there's a chance that the
+ # data directory doesn't exist at all, because the Install::Filesystem
+ # code happens after DB creation. So we create the directory ourselves
+ # if it doesn't exist.
+ my $datadir = bz_locations()->{datadir};
+ if (!-d $datadir) {
+ mkdir $datadir or warn "$datadir: $!";
+ }
+ if (!-d "$datadir/db/") {
+ mkdir "$datadir/db/" or warn "$datadir/db: $!";
+ }
+ $db_name = bz_locations()->{datadir} . "/db/$db_name";
+ }
+
+ # construct the DSN from the parameters we got
+ my $dsn = "dbi:SQLite:dbname=$db_name";
+
+ my $attrs = {
+ # XXX Should we just enforce this to be always on?
+ sqlite_unicode => Bugzilla->params->{'utf8'},
+ };
+
+ my $self = $class->db_new({ dsn => $dsn, user => '',
+ pass => '', attrs => $attrs });
+ # Needed by TheSchwartz
+ $self->{private_bz_dsn} = $dsn;
+
+ my %pragmas = (
+ # Make sure that the sqlite file doesn't grow without bound.
+ auto_vacuum => 1,
+ encoding => "'UTF-8'",
+ foreign_keys => 'ON',
+ # We want the latest file format.
+ legacy_file_format => 'OFF',
+ # This guarantees that we get column names like "foo"
+ # instead of "table.foo" in selectrow_hashref.
+ short_column_names => 'ON',
+ # The write-ahead log mode in SQLite 3.7 gets us better concurrency,
+ # but breaks backwards-compatibility with older versions of
+ # SQLite. (Which is important because people may also want to use
+ # command-line clients to access and back up their DB.) If you need
+ # better concurrency and don't need 3.6 compatibility, then you can
+ # uncomment this line.
+ #journal_mode => "'WAL'",
+ );
+
+ while (my ($name, $value) = each %pragmas) {
+ $self->do("PRAGMA $name = $value");
+ }
+
+ $self->sqlite_create_collation('bugzilla', \&_sqlite_collate_ci);
+ $self->sqlite_create_function('position', 2, \&_sqlite_position);
+ $self->sqlite_create_function('iposition', 2, \&_sqlite_position_ci);
+ # SQLite has a "substr" function, but other DBs call it "SUBSTRING"
+ # so that's what we use, and I don't know of any way in SQLite to
+ # alias the SQL "substr" function to be called "SUBSTRING".
+ $self->sqlite_create_function('substring', 3, \&CORE::substr);
+ $self->sqlite_create_function('mod', 2, \&_sqlite_mod);
+ $self->sqlite_create_function('now', 0, \&_sqlite_now);
+ $self->sqlite_create_function('localtimestamp', 1, \&_sqlite_now);
+ $self->sqlite_create_function('floor', 1, \&POSIX::floor);
+
+ bless ($self, $class);
+ return $self;
+}
+
+###############
+# SQL Methods #
+###############
+
+sub sql_position {
+ my ($self, $fragment, $text) = @_;
+ return "POSITION($text, $fragment)";
+}
+
+sub sql_iposition {
+ my ($self, $fragment, $text) = @_;
+ return "IPOSITION($text, $fragment)";
+}
+
+# SQLite does not have to GROUP BY the optional columns.
+sub sql_group_by {
+ my ($self, $needed_columns, $optional_columns) = @_;
+ my $expression = "GROUP BY $needed_columns";
+ return $expression;
+}
+
+# XXX SQLite does not support sorting a GROUP_CONCAT, so $sort is unimplemented.
+sub sql_group_concat {
+ my ($self, $column, $separator, $sort) = @_;
+ $separator = $self->quote(', ') if !defined $separator;
+ # In SQLite, a GROUP_CONCAT call with a DISTINCT argument can't
+ # specify its separator, and has to accept the default of ",".
+ if ($column =~ /^DISTINCT/) {
+ return "GROUP_CONCAT($column)";
+ }
+ return "GROUP_CONCAT($column, $separator)";
+}
+
+sub sql_istring {
+ my ($self, $string) = @_;
+ return $string;
+}
+
+sub sql_regexp {
+ my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
+ $real_pattern ||= $pattern;
+
+ $self->bz_check_regexp($real_pattern) if !$nocheck;
+
+ return "$expr REGEXP $pattern";
+}
+
+sub sql_not_regexp {
+ my $self = shift;
+ my $re_expression = $self->sql_regexp(@_);
+ return "NOT($re_expression)";
+}
+
+sub sql_limit {
+ my ($self, $limit, $offset) = @_;
+
+ if (defined($offset)) {
+ return "LIMIT $limit OFFSET $offset";
+ } else {
+ return "LIMIT $limit";
+ }
+}
+
+sub sql_from_days {
+ my ($self, $days) = @_;
+ return "DATETIME($days)";
+}
+
+sub sql_to_days {
+ my ($self, $date) = @_;
+ return "JULIANDAY($date)";
+}
+
+sub sql_date_format {
+ my ($self, $date, $format) = @_;
+ $format = "%Y.%m.%d %H:%M:%s" if !$format;
+ $format =~ s/\%i/\%M/g;
+ return "STRFTIME(" . $self->quote($format) . ", $date)";
+}
+
+sub sql_date_math {
+ my ($self, $date, $operator, $interval, $units) = @_;
+ # We do the || thing (concatenation) so that placeholders work properly.
+ return "DATETIME($date, '$operator' || $interval || ' $units')";
+}
+
+###############
+# bz_ methods #
+###############
+
+sub bz_setup_database {
+ my $self = shift;
+ $self->SUPER::bz_setup_database(@_);
+
+ # If we created TheSchwartz tables with COLLATE bugzilla (during the
+ # 4.1.x development series) re-create them without it.
+ my @tables = $self->bz_table_list();
+ my @ts_tables = grep { /^ts_/ } @tables;
+ my $drop_ok;
+ foreach my $table (@ts_tables) {
+ my $create_table =
+ $self->_bz_real_schema->_sqlite_create_table($table);
+ if ($create_table =~ /COLLATE bugzilla/) {
+ if (!$drop_ok) {
+ _sqlite_jobqueue_drop_message();
+ $drop_ok = 1;
+ }
+ $self->bz_drop_table($table);
+ $self->bz_add_table($table);
+ }
+ }
+}
+
+sub _sqlite_jobqueue_drop_message {
+ # This is not translated because this situation will only happen if
+ # you are updating from a 4.1.x development version of Bugzilla using
+ # SQLite, and we don't want to maintain this string in strings.txt.pl
+ # forever for just this one uncommon circumstance.
+ print <<END;
+WARNING: We have to re-create all the database tables used by jobqueue.pl.
+If there are any pending jobs in the database (that is, emails that
+haven't been sent), they will be deleted.
+
+END
+ unless (Bugzilla->installation_answers->{NO_PAUSE}) {
+ print install_string('enter_or_ctrl_c');
+ getc;
+ }
+}
+
+# XXX This needs to be implemented.
+sub bz_explain { }
+
+sub bz_table_list_real {
+ my $self = shift;
+ my @tables = $self->SUPER::bz_table_list_real(@_);
+ # SQLite includes a sqlite_sequence table in every database that isn't
+ # one of our real tables. We exclude any table that starts with sqlite_,
+ # just to be safe.
+ @tables = grep { $_ !~ /^sqlite_/ } @tables;
+ return @tables;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::DB::Sqlite - Bugzilla database compatibility layer for SQLite
+
+=head1 DESCRIPTION
+
+This module overrides methods of the Bugzilla::DB module with a
+SQLite-specific implementation. It is instantiated by the Bugzilla::DB module
+and should never be used directly.
+
+For interface details see L<Bugzilla::DB> and L<DBI>.
diff --git a/Websites/bugs.webkit.org/Bugzilla/Error.pm b/Websites/bugs.webkit.org/Bugzilla/Error.pm
index d15336a..178f6f9 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Error.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Error.pm
@@ -31,6 +31,9 @@
use Bugzilla::Constants;
use Bugzilla::WebService::Constants;
use Bugzilla::Util;
+
+use Carp;
+use Data::Dumper;
use Date::Format;
# We cannot use $^S to detect if we are in an eval(), because mod_perl
@@ -64,7 +67,7 @@
for (1..75) { $mesg .= "-"; };
$mesg .= "\n[$$] " . time2str("%D %H:%M:%S ", time());
$mesg .= "$name $error ";
- $mesg .= "$ENV{REMOTE_ADDR} " if $ENV{REMOTE_ADDR};
+ $mesg .= remote_ip();
$mesg .= Bugzilla->user->login;
$mesg .= (' actually ' . Bugzilla->sudoer->login) if Bugzilla->sudoer;
$mesg .= "\n";
@@ -89,31 +92,63 @@
}
my $template = Bugzilla->template;
- if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
- print Bugzilla->cgi->header();
- $template->process($name, $vars)
- || ThrowTemplateError($template->error());
- }
- else {
- my $message;
+ my $message;
+ # There are some tests that throw and catch a lot of errors,
+ # and calling $template->process over and over for those errors
+ # is too slow. So instead, we just "die" with a dump of the arguments.
+ if (Bugzilla->error_mode != ERROR_MODE_TEST) {
$template->process($name, $vars, \$message)
|| ThrowTemplateError($template->error());
- if (Bugzilla->error_mode == ERROR_MODE_DIE) {
- die("$message\n");
+ }
+
+ # Let's call the hook first, so that extensions can override
+ # or extend the default behavior, or add their own error codes.
+ require Bugzilla::Hook;
+ Bugzilla::Hook::process('error_catch', { error => $error, vars => $vars,
+ message => \$message });
+
+ if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
+ print Bugzilla->cgi->header();
+ print $message;
+ }
+ elsif (Bugzilla->error_mode == ERROR_MODE_TEST) {
+ die Dumper($vars);
+ }
+ elsif (Bugzilla->error_mode == ERROR_MODE_DIE) {
+ die("$message\n");
+ }
+ elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT
+ || Bugzilla->error_mode == ERROR_MODE_JSON_RPC)
+ {
+ # Clone the hash so we aren't modifying the constant.
+ my %error_map = %{ WS_ERROR_CODE() };
+ Bugzilla::Hook::process('webservice_error_codes',
+ { error_map => \%error_map });
+ my $code = $error_map{$error};
+ if (!$code) {
+ $code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
+ $code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
}
- elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
- # Clone the hash so we aren't modifying the constant.
- my %error_map = %{ WS_ERROR_CODE() };
- require Bugzilla::Hook;
- Bugzilla::Hook::process('webservice-error_codes',
- { error_map => \%error_map });
- my $code = $error_map{$error};
- if (!$code) {
- $code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
- $code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
- }
+
+ if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
die SOAP::Fault->faultcode($code)->faultstring($message);
}
+ else {
+ my $server = Bugzilla->_json_server;
+ # Technically JSON-RPC isn't allowed to have error numbers
+ # higher than 999, but we do this to avoid conflicts with
+ # the internal JSON::RPC error codes.
+ $server->raise_error(code => 100000 + $code,
+ message => $message,
+ id => $server->{_bz_request_id},
+ version => $server->version);
+ # Most JSON-RPC Throw*Error calls happen within an eval inside
+ # of JSON::RPC. So, in that circumstance, instead of exiting,
+ # we die with no message. JSON::RPC checks raise_error before
+ # it checks $@, so it returns the proper error.
+ die if _in_eval();
+ $server->response($server->error_response_header);
+ }
}
exit;
}
@@ -123,6 +158,16 @@
}
sub ThrowCodeError {
+ my (undef, $vars) = @_;
+
+ # Don't show function arguments, in case they contain
+ # confidential data.
+ local $Carp::MaxArgNums = -1;
+ # Don't show the error as coming from Bugzilla::Error, show it
+ # as coming from the caller.
+ local $Carp::CarpInternal{'Bugzilla::Error'} = 1;
+ $vars->{traceback} = Carp::longmess();
+
_throw_error("global/code-error.html.tmpl", @_);
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Extension.pm b/Websites/bugs.webkit.org/Bugzilla/Extension.pm
new file mode 100644
index 0000000..dbfee09
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Extension.pm
@@ -0,0 +1,823 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developers are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension;
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Install::Util qw(
+ extension_code_files extension_template_directory
+ extension_package_directory extension_web_directory);
+
+use File::Basename;
+use File::Spec;
+
+####################
+# Subclass Methods #
+####################
+
+sub new {
+ my ($class, $params) = @_;
+ $params ||= {};
+ bless $params, $class;
+ return $params;
+}
+
+#######################################
+# Class (Bugzilla::Extension) Methods #
+#######################################
+
+sub load {
+ my ($class, $extension_file, $config_file) = @_;
+ my $package;
+
+ # This is needed during checksetup.pl, because Extension packages can
+ # only be loaded once (they return "1" the second time they're loaded,
+ # instead of their name). During checksetup.pl, extensions are loaded
+ # once by Bugzilla::Install::Requirements, and then later again via
+ # Bugzilla->extensions (because of hooks).
+ my $map = Bugzilla->request_cache->{extension_requirement_package_map};
+
+ if ($config_file) {
+ if ($map and defined $map->{$config_file}) {
+ $package = $map->{$config_file};
+ }
+ else {
+ my $name = require $config_file;
+ if ($name =~ /^\d+$/) {
+ ThrowCodeError('extension_must_return_name',
+ { extension => $config_file,
+ returned => $name });
+ }
+ $package = "${class}::$name";
+ }
+
+ __do_call($package, 'modify_inc', $config_file);
+ }
+
+ if ($map and defined $map->{$extension_file}) {
+ $package = $map->{$extension_file};
+ $package->modify_inc($extension_file) if !$config_file;
+ }
+ else {
+ my $name = require $extension_file;
+ if ($name =~ /^\d+$/) {
+ ThrowCodeError('extension_must_return_name',
+ { extension => $extension_file, returned => $name });
+ }
+ $package = "${class}::$name";
+ $package->modify_inc($extension_file) if !$config_file;
+ }
+
+ $class->_validate_package($package, $extension_file);
+ return $package;
+}
+
+sub _validate_package {
+ my ($class, $package, $extension_file) = @_;
+
+ # For extensions from data/extensions/additional, we don't have a file
+ # name, so we fake it.
+ if (!$extension_file) {
+ $extension_file = $package;
+ $extension_file =~ s/::/\//g;
+ $extension_file .= '.pm';
+ }
+
+ if (!eval { $package->NAME }) {
+ ThrowCodeError('extension_no_name',
+ { filename => $extension_file, package => $package });
+ }
+
+ if (!$package->isa($class)) {
+ ThrowCodeError('extension_must_be_subclass',
+ { filename => $extension_file,
+ package => $package,
+ class => $class });
+ }
+}
+
+sub load_all {
+ my $class = shift;
+ my ($file_sets, $extra_packages) = extension_code_files();
+ my @packages;
+ foreach my $file_set (@$file_sets) {
+ my $package = $class->load(@$file_set);
+ push(@packages, $package);
+ }
+
+ # Extensions from data/extensions/additional
+ foreach my $package (@$extra_packages) {
+ # Don't load an "additional" extension if we already have an extension
+ # loaded with that name.
+ next if grep($_ eq $package, @packages);
+ # Untaint the package name
+ $package =~ /([\w:]+)/;
+ $package = $1;
+ eval("require $package") || die $@;
+ $package->_validate_package($package);
+ push(@packages, $package);
+ }
+
+ return \@packages;
+}
+
+# Modifies @INC so that extensions can use modules like
+# "use Bugzilla::Extension::Foo::Bar", when Bar.pm is in the lib/
+# directory of the extension.
+sub modify_inc {
+ my ($class, $file) = @_;
+
+ # Note that this package_dir call is necessary to set things up
+ # for my_inc, even if we didn't take its return value.
+ my $package_dir = __do_call($class, 'package_dir', $file);
+ # Don't modify @INC for extensions that are just files in the extensions/
+ # directory. We don't want Bugzilla's base lib/CGI.pm being loaded as
+ # Bugzilla::Extension::Foo::CGI or any other confusing thing like that.
+ return if $package_dir eq bz_locations->{'extensionsdir'};
+ unshift(@INC, sub { __do_call($class, 'my_inc', @_) });
+}
+
+# This is what gets put into @INC by modify_inc.
+sub my_inc {
+ my ($class, undef, $file) = @_;
+
+ # This avoids infinite recursion in case anything inside of this function
+ # does a "require". (I know for sure that File::Spec->case_tolerant does
+ # a "require" on Windows, for example.)
+ return if $file !~ /^Bugzilla/;
+
+ my $lib_dir = __do_call($class, 'lib_dir');
+ my @class_parts = split('::', $class);
+ my ($vol, $dir, $file_name) = File::Spec->splitpath($file);
+ my @dir_parts = File::Spec->splitdir($dir);
+ # File::Spec::Win32 (any maybe other OSes) add an empty directory at the
+ # end of @dir_parts.
+ @dir_parts = grep { $_ ne '' } @dir_parts;
+ # Validate that this is a sub-package of Bugzilla::Extension::Foo ($class).
+ for (my $i = 0; $i < scalar(@class_parts); $i++) {
+ return if !@dir_parts;
+ if (File::Spec->case_tolerant) {
+ return if lc($class_parts[$i]) ne lc($dir_parts[0]);
+ }
+ else {
+ return if $class_parts[$i] ne $dir_parts[0];
+ }
+ shift(@dir_parts);
+ }
+ # For Bugzilla::Extension::Foo::Bar, this would look something like
+ # extensions/Example/lib/Bar.pm
+ my $resolved_path = File::Spec->catfile($lib_dir, @dir_parts, $file_name);
+ open(my $fh, '<', $resolved_path);
+ return $fh;
+}
+
+####################
+# Instance Methods #
+####################
+
+use constant enabled => 1;
+
+sub lib_dir {
+ my $invocant = shift;
+ my $package_dir = __do_call($invocant, 'package_dir');
+ # For extensions that are just files in the extensions/ directory,
+ # use the base lib/ dir as our "lib_dir". Note that Bugzilla never
+ # uses lib_dir in this case, though, because modify_inc is prevented
+ # from modifying @INC when we're just a file in the extensions/ directory.
+ # So this particular code block exists just to make lib_dir return
+ # something right in case an extension needs it for some odd reason.
+ if ($package_dir eq bz_locations()->{'extensionsdir'}) {
+ return bz_locations->{'ext_libpath'};
+ }
+ return File::Spec->catdir($package_dir, 'lib');
+}
+
+sub template_dir { return extension_template_directory(@_); }
+sub package_dir { return extension_package_directory(@_); }
+sub web_dir { return extension_web_directory(@_); }
+
+######################
+# Helper Subroutines #
+######################
+
+# In order to not conflict with extensions' private subroutines, any helpers
+# here should start with a double underscore.
+
+# This is for methods that can optionally be overridden in Config.pm.
+# It falls back to the local implementation if $class cannot do
+# the method. This is necessary because Config.pm is not a subclass of
+# Bugzilla::Extension.
+sub __do_call {
+ my ($class, $method, @args) = @_;
+ if ($class->can($method)) {
+ return $class->$method(@args);
+ }
+ my $function_ref;
+ { no strict 'refs'; $function_ref = \&{$method}; }
+ return $function_ref->($class, @args);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Extension - Base class for Bugzilla Extensions.
+
+=head1 SYNOPSIS
+
+The following would be in F<extensions/Foo/Extension.pm> or
+F<extensions/Foo.pm>:
+
+ package Bugzilla::Extension::Foo
+ use strict;
+ use base qw(Bugzilla::Extension);
+
+ our $VERSION = '0.02';
+ use constant NAME => 'Foo';
+
+ sub some_hook_name { ... }
+
+ __PACKAGE__->NAME;
+
+Custom templates would go into F<extensions/Foo/template/en/default/>.
+L<Template hooks|/Template Hooks> would go into
+F<extensions/Foo/template/en/default/hook/>.
+
+=head1 DESCRIPTION
+
+This is the base class for all Bugzilla extensions.
+
+=head1 WRITING EXTENSIONS
+
+The L</SYNOPSIS> above gives a pretty good overview of what's basically
+required to write an extension. This section gives more information
+on exactly how extensions work and how you write them. There is also a
+L<wiki page|https://wiki.mozilla.org/Bugzilla:Extension_Notes> with additional HOWTOs, tips and tricks.
+
+=head2 Using F<extensions/create.pl>
+
+There is a script, L<extensions::create>, that will set up the framework
+of a new extension for you. To use it, pick a name for your extension
+and, in the base bugzilla directory, do:
+
+C<extensions/create.pl NAME>
+
+But replace C<NAME> with the name you picked for your extension. That
+will create a new directory in the F<extensions/> directory with the name
+of your extension. The directory will contain a full framework for
+a new extension, with helpful comments in each file describing things
+about them.
+
+=head2 Example Extension
+
+There is a sample extension in F<extensions/Example/> that demonstrates
+most of the things described in this document, so if you find the
+documentation confusing, try just reading the code instead.
+
+=head2 Where Extension Code Goes
+
+Extension code lives under the F<extensions/> directory in Bugzilla.
+
+There are two ways to write extensions:
+
+=over
+
+=item 1
+
+If your extension will have only code and no templates or other files,
+you can create a simple C<.pm> file in the F<extensions/> directory.
+
+For example, if you wanted to create an extension called "Foo" using this
+method, you would put your code into a file called F<extensions/Foo.pm>.
+
+=item 2
+
+If you plan for your extension to have templates and other files, you
+can create a whole directory for your extension, and the main extension
+code would go into a file called F<Extension.pm> in that directory.
+
+For example, if you wanted to create an extension called "Foo" using this
+method, you would put your code into a file called
+F<extensions/Foo/Extension.pm>.
+
+=back
+
+=head2 The Extension C<NAME>.
+
+The "name" of an extension shows up in several places:
+
+=over
+
+=item 1
+
+The name of the package:
+
+C<package Bugzilla::Extension::Foo;>
+
+=item 2
+
+In a C<NAME> constant that B<must> be defined for every extension:
+
+C<< use constant NAME => 'Foo'; >>
+
+=item 3
+
+At the very end of the file:
+
+C<< __PACKAGE__->NAME; >>
+
+You'll notice that though most Perl packages end with C<1;>, Bugzilla
+Extensions must B<always> end with C<< __PACKAGE__->NAME; >>.
+
+=back
+
+The name must be identical in all of those locations.
+
+=head2 Hooks
+
+In L<Bugzilla::Hook>, there is a L<list of hooks|Bugzilla::Hook/HOOKS>.
+These are the various areas of Bugzilla that an extension can "hook" into,
+which allow your extension to perform code during that point in Bugzilla's
+execution.
+
+If your extension wants to implement a hook, all you have to do is
+write a subroutine in your hook package that has the same name as
+the hook. The subroutine will be called as a method on your extension,
+and it will get the arguments specified in the hook's documentation as
+named parameters in a hashref.
+
+For example, here's an implementation of a hook named C<foo_start>
+that gets an argument named C<bar>:
+
+ sub foo_start {
+ my ($self, $args) = @_;
+ my $bar = $args->{bar};
+ print "I got $bar!\n";
+ }
+
+And that would go into your extension's code file--the file that was
+described in the L</Where Extension Code Goes> section above.
+
+During your subroutine, you may want to know what values were passed
+as CGI arguments to the current script, or what arguments were passed to
+the current WebService method. You can get that data via
+L<Bugzilla/input_params>.
+
+=head3 Adding New Hooks To Bugzilla
+
+If you need a new hook for your extension and you want that hook to be
+added to Bugzilla itself, see our development process at
+L<http://wiki.mozilla.org/Bugzilla:Developers>.
+
+In order for a new hook to be accepted into Bugzilla, it has to work,
+it must have documentation in L<Bugzilla::Hook>, and it must have example
+code in F<extensions/Example/Extension.pm>.
+
+One question that is often asked about new hooks is, "Is this the most
+flexible way to implement this hook?" That is, the more power extension
+authors get from a hook, the more likely it is to be accepted into Bugzilla.
+Hooks that only hook a very specific part of Bugzilla will not be accepted
+if their functionality can be accomplished equally well with a more generic
+hook.
+
+=head2 If Your Extension Requires Certain Perl Modules
+
+If there are certain Perl modules that your extension requires in order
+to run, there is a way you can tell Bugzilla this, and then L<checksetup>
+will make sure that those modules are installed, when you run L<checksetup>.
+
+To do this, you need to specify a constant called C<REQUIRED_MODULES>
+in your extension. This constant has the same format as
+L<Bugzilla::Install::Requirements/REQUIRED_MODULES>.
+
+If there are optional modules that add additional functionality to your
+application, you can specify them in a constant called OPTIONAL_MODULES,
+which has the same format as
+L<Bugzilla::Install::Requirements/OPTIONAL_MODULES>.
+
+=head3 If Your Extension Needs Certain Modules In Order To Compile
+
+If your extension needs a particular Perl module in order to
+I<compile>, then you have a "chicken and egg" problem--in order to
+read C<REQUIRED_MODULES>, we have to compile your extension. In order
+to compile your extension, we need to already have the modules in
+C<REQUIRED_MODULES>!
+
+To get around this problem, Bugzilla allows you to have an additional
+file, besides F<Extension.pm>, called F<Config.pm>, that contains
+just C<REQUIRED_MODULES>. If you have a F<Config.pm>, it must also
+contain the C<NAME> constant, instead of your main F<Extension.pm>
+containing the C<NAME> constant.
+
+The contents of the file would look something like this for an extension
+named C<Foo>:
+
+ package Bugzilla::Extension::Foo;
+ use strict;
+ use constant NAME => 'Foo';
+ use constant REQUIRED_MODULES => [
+ {
+ package => 'Some-Package',
+ module => 'Some::Module',
+ version => 0,
+ }
+ ];
+ __PACKAGE__->NAME;
+
+Note that it is I<not> a subclass of C<Bugzilla::Extension>, because
+at the time that module requirements are being checked in L<checksetup>,
+C<Bugzilla::Extension> cannot be loaded. Also, just like F<Extension.pm>,
+it ends with C<< __PACKAGE__->NAME; >>. Note also that it has the
+B<exact same> C<package> name as F<Extension.pm>.
+
+This file may not use any Perl modules other than L<Bugzilla::Constants>,
+L<Bugzilla::Install::Util>, L<Bugzilla::Install::Requirements>, and
+modules that ship with Perl itself.
+
+If you want to define both C<REQUIRED_MODULES> and C<OPTIONAL_MODULES>,
+they must both be in F<Config.pm> or both in F<Extension.pm>.
+
+Every time your extension is loaded by Bugzilla, F<Config.pm> will be
+read and then F<Extension.pm> will be read, so your methods in F<Extension.pm>
+will have access to everything in F<Config.pm>. Don't define anything
+with an identical name in both files, or Perl may throw a warning that
+you are redefining things.
+
+This method of setting C<REQUIRED_MODULES> is of course not available if
+your extension is a single file named C<Foo.pm>.
+
+If any of this is confusing, just look at the code of the Example extension.
+It uses this method to specify requirements.
+
+=head2 Libraries
+
+Extensions often want to have their own Perl modules. Your extension
+can load any Perl module in its F<lib/> directory. (So, if your extension is
+F<extensions/Foo/>, then your Perl modules go into F<extensions/Foo/lib/>.)
+
+However, the C<package> name of your libraries will not work quite
+like normal Perl modules do. F<extensions/Foo/lib/Bar.pm> is
+loaded as C<Bugzilla::Extension::Foo::Bar>. Or, to say it another way,
+C<use Bugzilla::Extension::Foo::Bar;> loads F<extensions/Foo/lib/Bar.pm>,
+which should have C<package Bugzilla::Extension::Foo::Bar;> as its package
+name.
+
+This allows any place in Bugzilla to load your modules, which is important
+for some hooks. It even allows other extensions to load your modules, and
+allows you to install your modules into the global Perl install
+as F<Bugzilla/Extension/Foo/Bar.pm>, if you'd like, which helps allow CPAN
+distribution of Bugzilla extensions.
+
+B<Note:> If you want to C<use> or C<require> a module that's in
+F<extensions/Foo/lib/> at the top level of your F<Extension.pm>,
+you must have a F<Config.pm> (see above) with at least the C<NAME>
+constant defined in it.
+
+=head2 Templates
+
+Extensions store templates in a C<template> subdirectory of the extension.
+(Obviously, this isn't available for extensions that aren't a directory.)
+
+The format of this directory is exactly like the normal layout of Bugzilla's
+C<template> directory--in fact, your extension's C<template> directory
+becomes part of Bugzilla's template "search path" as described in
+L<Bugzilla::Install::Util/template_include_path>.
+
+You can actually include templates in your extension without having any
+C<.pm> files in your extension at all, if you want. (That is, it's entirely
+valid to have an extension that's just template files and no code files.)
+
+Bugzilla's templates are written in a language called Template Toolkit.
+You can find out more about Template Toolkit at L<http://template-toolkit.org>.
+
+There are two ways to extend or modify Bugzilla's templates: you can use
+template hooks (described below) or you can override existing templates
+entirely (described further down).
+
+=head2 Template Hooks
+
+Templates can be extended using a system of "hooks" that add new UI elements
+to a particular area of Bugzilla without modifying the code of the existing
+templates. This is the recommended way for extensions to modify the user
+interface of Bugzilla.
+
+=head3 Which Templates Can Be Hooked
+
+There is no list of template hooks like there is for standard code hooks.
+To find what places in the user interface can be hooked, search for the
+string C<Hook.process> in Bugzilla's templates (in the
+F<template/en/default/> directory). That will also give you the name of
+the hooks--the first argument to C<Hook.process> is the name of the hook.
+(A later section in this document explains how to use that name).
+
+For example, if you see C<Hook.process("additional_header")>, that means
+the name of the hook is C<additional_header>.
+
+=head3 Where Template Hooks Go
+
+To extend templates in your extension using template hooks, you put files into
+the F<template/en/default/hook> directory of your extension. So, if you had an
+extension called "Foo", your template extensions would go into
+F<extensions/Foo/template/en/default/hook/>.
+
+(Note that the base F<template/en/default/hook> directory in Bugzilla itself
+also works, although you would never use that for an extension that you
+intended to distribute.)
+
+The files that go into this directory have a certain name, based on the
+name of the template that is being hooked, and the name of the hook.
+For example, let's imagine that you have an extension named "Foo",
+and you want to use the C<additional_header> hook in
+F<template/en/default/global/header.html.tmpl>. Your code would go into
+F<extensions/Foo/template/en/default/hook/global/header-additional_header.html.tmpl>. Any code you put into that file will happen at the point that
+C<Hook.process("additional_header")> is called in
+F<template/en/default/global/header.html.tmpl>.
+
+As you can see, template extension file names follow a pattern. The
+pattern looks like:
+
+ <templates>/hook/<template path>/<template name>-<hook name>.<template type>.tmpl
+
+=over
+
+=item <templates>
+
+This is the full path to the template directory, like
+F<extensions/Foo/template/en/default>. This works much like normal templates
+do, in the sense that template extensions in C<custom> override template
+extensions in C<default> for your extension, templates for different languages
+can be supplied, etc. Template extensions are searched for and run in the
+order described in L<Bugzilla::Install::Util/template_include_path>.
+
+The difference between normal templates and template hooks is that hooks
+will be run for I<every> extension, whereas for normal templates, Bugzilla
+just takes the first one it finds and stops searching. So while a template
+extension in the C<custom> directory may override the same-named template
+extension in the C<default> directory I<within your Bugzilla extension>,
+it will not override the same-named template extension in the C<default>
+directory of another Bugzilla extension.
+
+=item <template path>
+
+This is the part of the path (excluding the filename) that comes after
+F<template/en/default/> in a template's path. So, for
+F<template/en/default/global/header.html.tmpl>, this would simply be
+C<global>.
+
+=item <template name>
+
+This is the file name of the template, before the C<.html.tmpl> part.
+So, for F<template/en/default/global/header.html.tmpl>, this would be
+C<header>.
+
+=item <hook name>
+
+This is the name of the hook--what you saw in C<Hook.process> inside
+of the template you want to hook. In our example, this is
+C<additional_header>.
+
+=item <template type>
+
+This is what comes after the template name but before C<.tmpl> in the
+template's path. In most cases this is C<html>, but sometimes it's
+C<none>, C<txt>, C<js>, or various other formats, indicating what
+type of output the template has.
+
+=back
+
+=head3 Adding New Template Hooks to Bugzilla
+
+Adding new template hooks is just like adding code hooks (see
+L</Adding New Hooks To Bugzilla>) except that you don't have to
+document them, and including example code is optional.
+
+=head2 Overriding Existing Templates
+
+Sometimes you don't want to extend a template, you just want to replace
+it entirely with your extension's template, or you want to add an entirely
+new template to Bugzilla for your extension to use.
+
+To replace the F<template/en/default/global/banner.html.tmpl> template
+in an extension named "Foo", create a file called
+F<extensions/Foo/template/en/default/global/banner.html.tmpl>. Note that this
+is very similar to the path for a template hook, except that it excludes
+F<hook/>, and the template is named I<exactly> like the standard Bugzilla
+template.
+
+You can also use this method to add entirely new templates. If you have
+an extension named "Foo", and you add a file named
+F<extensions/Foo/template/en/default/foo/bar.html.tmpl>, you can load
+that in your code using C<< $template->process('foo/bar.html.tmpl') >>.
+
+=head3 A Warning About Extensions That You Want To Distribute
+
+You should never override an existing Bugzilla template in an
+extension that you plan to distribute to others, because only one extension
+can override any given template, and which extension will "win" that war
+if there are multiple extensions installed is totally undefined.
+
+However, adding new templates in an extension that you want to distribute
+is fine, though you have to be careful about how you name them, because
+any templates with an identical path and name (say, both called
+F<global/stuff.html.tmpl>) will conflict. The usual way to work around
+this is to put all your custom templates into a template path that's
+named after your extension (since the name of your extension has to be
+unique anyway). So if your extension was named Foo, your custom templates
+would go into F<extensions/Foo/template/en/default/foo/>. The only
+time that doesn't work is with the C<page_before_template> extension, in which
+case your templates should probably be in a directory like
+F<extensions/Foo/template/en/default/page/foo/> so as not to conflict with
+other pages that other extensions might add.
+
+=head2 CSS, JavaScript, and Images
+
+If you include CSS, JavaScript, and images in your extension that are
+served directly to the user (that is, they're not read by a script and
+then printed--they're just linked directly in your HTML), they should go
+into the F<web/> subdirectory of your extension.
+
+So, for example, if you had a CSS file called F<style.css> and your
+extension was called F<Foo>, your file would go into
+F<extensions/Foo/web/style.css>.
+
+=head2 Disabling Your Extension
+
+If you want your extension to be totally ignored by Bugzilla (it will
+not be compiled or seen to exist at all), then create a file called
+C<disabled> in your extension's directory. (If your extension is just
+a file, like F<extensions/Foo.pm>, you cannot use this method to disable
+your extension, and will just have to remove it from the directory if you
+want to totally disable it.) Note that if you are running under mod_perl,
+you may have to restart your web server for this to take effect.
+
+If you want your extension to be compiled and have L<checksetup> check
+for its module pre-requisites, but you don't want the module to be used
+by Bugzilla, then you should make your extension's L</enabled> method
+return C<0> or some false value.
+
+=head1 DISTRIBUTING EXTENSIONS
+
+If you've made an extension and you want to publish it, the first
+thing you'll want to do is package up your extension's code and
+then put a link to it in the appropriate section of
+L<http://wiki.mozilla.org/Bugzilla:Addons>.
+
+=head2 Distributing on CPAN
+
+If you want a centralized distribution point that makes it easy
+for Bugzilla users to install your extension, it is possible to
+distribute your Bugzilla Extension through CPAN.
+
+The details of making a standard CPAN module are too much to
+go into here, but a lot of it is covered in L<perlmodlib>
+and on L<http://www.cpan.org/> among other places.
+
+When you distribute your extension via CPAN, your F<Extension.pm>
+should simply install itself as F<Bugzilla/Extension/Foo.pm>,
+where C<Foo> is the name of your module. You do not need a separate
+F<Config.pm> file, because CPAN itself will handle installing
+the prerequisites of your module, so Bugzilla doesn't have to
+worry about it.
+
+=head3 Templates in extensions distributed on CPAN
+
+If your extension is F</usr/lib/perl5/Bugzilla/Extension/Foo.pm>,
+then Bugzilla will look for templates in the directory
+F</usr/lib/perl5/Bugzilla/Extension/Foo/template/>.
+
+You can change this behavior by overriding the L</template_dir>
+or L</package_dir> methods described lower down in this document.
+
+=head3 Using an extension distributed on CPAN
+
+There is a file named F<data/extensions/additional> in Bugzilla.
+This is a plain-text file. Each line is the name of a module,
+like C<Bugzilla::Extension::Foo>. In addition to the extensions
+in the F<extensions/> directory, each module listed in this file
+will be loaded as a Bugzilla Extension whenever Bugzilla loads or
+uses extensions.
+
+=head1 GETTING HELP WITH WRITING EXTENSIONS
+
+If you are an extension author and you'd like some assistance from other
+extension authors or the Bugzilla development team, you can use the
+normal support channels described at L<http://www.bugzilla.org/support/>.
+
+=head1 ADDITIONAL CONSTANTS
+
+In addition to C<NAME>, there are some other constants you might
+want to define:
+
+=head2 C<$VERSION>
+
+This should be a string that describes what version of your extension
+this is. Something like C<1.0>, C<1.3.4> or a similar string.
+
+There are no particular restrictions on the format of version numbers,
+but you should probably keep them to just numbers and periods, in the
+interest of other software that parses version numbers.
+
+By default, this will be C<undef> if you don't define it.
+
+=head1 SUBCLASS METHODS
+
+In addition to hooks, there are a few methods that your extension can
+define to modify its behavior, if you want:
+
+=head2 Class Methods
+
+These methods are called on your extension's class. (Like
+C<< Bugzilla::Extension::Foo->some_method >>).
+
+=head3 C<new>
+
+Once every request, this method is called on your extension in order
+to create an "instance" of it. (Extensions are treated like objects--they
+are instantiated once per request in Bugzilla, and then methods are
+called on the object.)
+
+=head2 Instance Methods
+
+These are called on an instantiated Extension object.
+
+=head3 C<enabled>
+
+This should return C<1> if this extension's hook code should be run
+by Bugzilla, and C<0> otherwise.
+
+=head3 C<package_dir>
+
+This returns the directory that your extension is located in.
+
+If this is an extension that was installed via CPAN, the directory will
+be the path to F<Bugzilla/Extension/Foo/>, if C<Foo.pm> is the name of your
+extension.
+
+If you want to override this method, and you have a F<Config.pm>, you must
+override this method in F<Config.pm>.
+
+=head3 C<template_dir>
+
+The directory that your package's templates are in.
+
+This defaults to the C<template> subdirectory of the L</package_dir>.
+
+If you want to override this method, and you have a F<Config.pm>, you must
+override this method in F<Config.pm>.
+
+=head3 C<web_dir>
+
+The directory that your package's web related files are in, such as css and javascript.
+
+This defaults to the C<web> subdirectory of the L</package_dir>.
+
+If you want to override this method, and you have a F<Config.pm>, you must
+override this method in F<Config.pm>.
+
+=head3 C<lib_dir>
+
+The directory where your extension's libraries are.
+
+This defaults to the C<lib> subdirectory of the L</package_dir>.
+
+If you want to override this method, and you have a F<Config.pm>, you must
+override this method in F<Config.pm>.
+
+=head1 BUGZILLA::EXTENSION CLASS METHODS
+
+These are used internally by Bugzilla to load and set up extensions.
+If you are an extension author, you don't need to care about these.
+
+=head2 C<load>
+
+Takes two arguments, the path to F<Extension.pm> and the path to F<Config.pm>,
+for an extension. Loads the extension's code packages into memory using
+C<require>, does some sanity-checking on the extension, and returns the
+package name of the loaded extension.
+
+=head2 C<load_all>
+
+Calls L</load> for every enabled extension installed into Bugzilla,
+and returns an arrayref of all the package names that were loaded.
diff --git a/Websites/bugs.webkit.org/Bugzilla/Field.pm b/Websites/bugs.webkit.org/Bugzilla/Field.pm
index 0d74790..dbee5df 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Field.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Field.pm
@@ -15,6 +15,7 @@
# Contributor(s): Dan Mosedale <dmose@mozilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
# Myk Melez <myk@mozilla.org>
+# Greg Hendricks <ghendricks@novell.com>
=head1 NAME
@@ -27,7 +28,7 @@
use Data::Dumper;
# Display information about all fields.
- print Dumper(Bugzilla->get_fields());
+ print Dumper(Bugzilla->fields());
# Display information about non-obsolete custom fields.
print Dumper(Bugzilla->active_custom_fields);
@@ -35,9 +36,9 @@
use Bugzilla::Field;
# Display information about non-obsolete custom fields.
- # Bugzilla->get_fields() is a wrapper around Bugzilla::Field->match(),
- # so both methods take the same arguments.
- print Dumper(Bugzilla::Field->match({ obsolete => 0, custom => 1 }));
+ # Bugzilla->fields() is a wrapper around Bugzilla::Field->get_all(),
+ # with arguments which filter the fields before returning them.
+ print Dumper(Bugzilla->fields({ obsolete => 0, custom => 1 }));
# Create or update a custom field or field definition.
my $field = Bugzilla::Field->create(
@@ -73,9 +74,12 @@
use base qw(Exporter Bugzilla::Object);
@Bugzilla::Field::EXPORT = qw(check_field get_field_id get_legal_field_values);
-use Bugzilla::Util;
use Bugzilla::Constants;
use Bugzilla::Error;
+use Bugzilla::Util;
+use List::MoreUtils qw(any);
+
+use Scalar::Util qw(blessed);
###############################
#### Initialization ####
@@ -84,28 +88,49 @@
use constant DB_TABLE => 'fielddefs';
use constant LIST_ORDER => 'sortkey, name';
-use constant DB_COLUMNS => (
- 'id',
- 'name',
- 'description',
- 'type',
- 'custom',
- 'mailhead',
- 'sortkey',
- 'obsolete',
- 'enter_bug',
+use constant DB_COLUMNS => qw(
+ id
+ name
+ description
+ type
+ custom
+ mailhead
+ sortkey
+ obsolete
+ enter_bug
+ buglist
+ visibility_field_id
+ value_field_id
+ reverse_desc
+ is_mandatory
+ is_numeric
);
-use constant REQUIRED_CREATE_FIELDS => qw(name description);
-
use constant VALIDATORS => {
- custom => \&_check_custom,
- description => \&_check_description,
- enter_bug => \&_check_enter_bug,
- mailhead => \&_check_mailhead,
- obsolete => \&_check_obsolete,
- sortkey => \&_check_sortkey,
- type => \&_check_type,
+ custom => \&_check_custom,
+ description => \&_check_description,
+ enter_bug => \&_check_enter_bug,
+ buglist => \&Bugzilla::Object::check_boolean,
+ mailhead => \&_check_mailhead,
+ name => \&_check_name,
+ obsolete => \&_check_obsolete,
+ reverse_desc => \&_check_reverse_desc,
+ sortkey => \&_check_sortkey,
+ type => \&_check_type,
+ value_field_id => \&_check_value_field_id,
+ visibility_field_id => \&_check_visibility_field_id,
+ visibility_values => \&_check_visibility_values,
+ is_mandatory => \&Bugzilla::Object::check_boolean,
+ is_numeric => \&_check_is_numeric,
+};
+
+use constant VALIDATOR_DEPENDENCIES => {
+ is_numeric => ['type'],
+ name => ['custom'],
+ type => ['custom'],
+ reverse_desc => ['type'],
+ value_field_id => ['type'],
+ visibility_values => ['visibility_field_id'],
};
use constant UPDATE_COLUMNS => qw(
@@ -114,80 +139,144 @@
sortkey
obsolete
enter_bug
+ buglist
+ visibility_field_id
+ value_field_id
+ reverse_desc
+ is_mandatory
+ is_numeric
+ type
);
# How various field types translate into SQL data definitions.
use constant SQL_DEFINITIONS => {
# Using commas because these are constants and they shouldn't
# be auto-quoted by the "=>" operator.
- FIELD_TYPE_FREETEXT, { TYPE => 'varchar(255)' },
+ FIELD_TYPE_FREETEXT, { TYPE => 'varchar(255)',
+ NOTNULL => 1, DEFAULT => "''"},
FIELD_TYPE_SINGLE_SELECT, { TYPE => 'varchar(64)', NOTNULL => 1,
DEFAULT => "'---'" },
- FIELD_TYPE_TEXTAREA, { TYPE => 'MEDIUMTEXT' },
+ FIELD_TYPE_TEXTAREA, { TYPE => 'MEDIUMTEXT',
+ NOTNULL => 1, DEFAULT => "''"},
FIELD_TYPE_DATETIME, { TYPE => 'DATETIME' },
+ FIELD_TYPE_BUG_ID, { TYPE => 'INT3' },
};
# Field definitions for the fields that ship with Bugzilla.
# These are used by populate_field_definitions to populate
# the fielddefs table.
+# 'days_elapsed' is set in populate_field_definitions() itself.
use constant DEFAULT_FIELDS => (
- {name => 'bug_id', desc => 'Bug #', in_new_bugmail => 1},
- {name => 'short_desc', desc => 'Summary', in_new_bugmail => 1},
- {name => 'classification', desc => 'Classification', in_new_bugmail => 1},
- {name => 'product', desc => 'Product', in_new_bugmail => 1},
- {name => 'version', desc => 'Version', in_new_bugmail => 1},
- {name => 'rep_platform', desc => 'Platform', in_new_bugmail => 1},
- {name => 'bug_file_loc', desc => 'URL', in_new_bugmail => 1},
- {name => 'op_sys', desc => 'OS/Version', in_new_bugmail => 1},
- {name => 'bug_status', desc => 'Status', in_new_bugmail => 1},
+ {name => 'bug_id', desc => 'Bug #', in_new_bugmail => 1,
+ buglist => 1, is_numeric => 1},
+ {name => 'short_desc', desc => 'Summary', in_new_bugmail => 1,
+ is_mandatory => 1, buglist => 1},
+ {name => 'classification', desc => 'Classification', in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
+ {name => 'product', desc => 'Product', in_new_bugmail => 1,
+ is_mandatory => 1,
+ type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
+ {name => 'version', desc => 'Version', in_new_bugmail => 1,
+ is_mandatory => 1, buglist => 1},
+ {name => 'rep_platform', desc => 'Platform', in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
+ {name => 'bug_file_loc', desc => 'URL', in_new_bugmail => 1,
+ buglist => 1},
+ {name => 'op_sys', desc => 'OS/Version', in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
+ {name => 'bug_status', desc => 'Status', in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
{name => 'status_whiteboard', desc => 'Status Whiteboard',
- in_new_bugmail => 1},
- {name => 'keywords', desc => 'Keywords', in_new_bugmail => 1},
- {name => 'resolution', desc => 'Resolution'},
- {name => 'bug_severity', desc => 'Severity', in_new_bugmail => 1},
- {name => 'priority', desc => 'Priority', in_new_bugmail => 1},
- {name => 'component', desc => 'Component', in_new_bugmail => 1},
- {name => 'assigned_to', desc => 'AssignedTo', in_new_bugmail => 1},
- {name => 'reporter', desc => 'ReportedBy', in_new_bugmail => 1},
- {name => 'votes', desc => 'Votes'},
- {name => 'qa_contact', desc => 'QAContact', in_new_bugmail => 1},
+ in_new_bugmail => 1, buglist => 1},
+ {name => 'keywords', desc => 'Keywords', in_new_bugmail => 1,
+ type => FIELD_TYPE_KEYWORDS, buglist => 1},
+ {name => 'resolution', desc => 'Resolution',
+ type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
+ {name => 'bug_severity', desc => 'Severity', in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
+ {name => 'priority', desc => 'Priority', in_new_bugmail => 1,
+ type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
+ {name => 'component', desc => 'Component', in_new_bugmail => 1,
+ is_mandatory => 1,
+ type => FIELD_TYPE_SINGLE_SELECT, buglist => 1},
+ {name => 'assigned_to', desc => 'AssignedTo', in_new_bugmail => 1,
+ buglist => 1},
+ {name => 'reporter', desc => 'ReportedBy', in_new_bugmail => 1,
+ buglist => 1},
+ {name => 'qa_contact', desc => 'QAContact', in_new_bugmail => 1,
+ buglist => 1},
{name => 'cc', desc => 'CC', in_new_bugmail => 1},
- {name => 'dependson', desc => 'Depends on', in_new_bugmail => 1},
- {name => 'blocked', desc => 'Blocks', in_new_bugmail => 1},
+ {name => 'dependson', desc => 'Depends on', in_new_bugmail => 1,
+ is_numeric => 1},
+ {name => 'blocked', desc => 'Blocks', in_new_bugmail => 1,
+ is_numeric => 1},
{name => 'attachments.description', desc => 'Attachment description'},
{name => 'attachments.filename', desc => 'Attachment filename'},
{name => 'attachments.mimetype', desc => 'Attachment mime type'},
- {name => 'attachments.ispatch', desc => 'Attachment is patch'},
- {name => 'attachments.isobsolete', desc => 'Attachment is obsolete'},
- {name => 'attachments.isprivate', desc => 'Attachment is private'},
+ {name => 'attachments.ispatch', desc => 'Attachment is patch',
+ is_numeric => 1},
+ {name => 'attachments.isobsolete', desc => 'Attachment is obsolete',
+ is_numeric => 1},
+ {name => 'attachments.isprivate', desc => 'Attachment is private',
+ is_numeric => 1},
{name => 'attachments.submitter', desc => 'Attachment creator'},
- {name => 'target_milestone', desc => 'Target Milestone'},
- {name => 'creation_ts', desc => 'Creation date', in_new_bugmail => 1},
- {name => 'delta_ts', desc => 'Last changed date', in_new_bugmail => 1},
+ {name => 'target_milestone', desc => 'Target Milestone',
+ buglist => 1},
+ {name => 'creation_ts', desc => 'Creation date',
+ buglist => 1},
+ {name => 'delta_ts', desc => 'Last changed date',
+ buglist => 1},
{name => 'longdesc', desc => 'Comment'},
- {name => 'longdescs.isprivate', desc => 'Comment is private'},
- {name => 'alias', desc => 'Alias'},
- {name => 'everconfirmed', desc => 'Ever Confirmed'},
- {name => 'reporter_accessible', desc => 'Reporter Accessible'},
- {name => 'cclist_accessible', desc => 'CC Accessible'},
- {name => 'bug_group', desc => 'Group'},
- {name => 'estimated_time', desc => 'Estimated Hours', in_new_bugmail => 1},
- {name => 'remaining_time', desc => 'Remaining Hours'},
- {name => 'deadline', desc => 'Deadline', in_new_bugmail => 1},
+ {name => 'longdescs.isprivate', desc => 'Comment is private',
+ is_numeric => 1},
+ {name => 'longdescs.count', desc => 'Number of Comments',
+ buglist => 1, is_numeric => 1},
+ {name => 'alias', desc => 'Alias', buglist => 1},
+ {name => 'everconfirmed', desc => 'Ever Confirmed',
+ is_numeric => 1},
+ {name => 'reporter_accessible', desc => 'Reporter Accessible',
+ is_numeric => 1},
+ {name => 'cclist_accessible', desc => 'CC Accessible',
+ is_numeric => 1},
+ {name => 'bug_group', desc => 'Group', in_new_bugmail => 1},
+ {name => 'estimated_time', desc => 'Estimated Hours',
+ in_new_bugmail => 1, buglist => 1, is_numeric => 1},
+ {name => 'remaining_time', desc => 'Remaining Hours', buglist => 1,
+ is_numeric => 1},
+ {name => 'deadline', desc => 'Deadline',
+ type => FIELD_TYPE_DATETIME, in_new_bugmail => 1, buglist => 1},
{name => 'commenter', desc => 'Commenter'},
- {name => 'flagtypes.name', desc => 'Flag'},
+ {name => 'flagtypes.name', desc => 'Flags', buglist => 1},
{name => 'requestees.login_name', desc => 'Flag Requestee'},
{name => 'setters.login_name', desc => 'Flag Setter'},
- {name => 'work_time', desc => 'Hours Worked'},
- {name => 'percentage_complete', desc => 'Percentage Complete'},
+ {name => 'work_time', desc => 'Hours Worked', buglist => 1,
+ is_numeric => 1},
+ {name => 'percentage_complete', desc => 'Percentage Complete',
+ buglist => 1, is_numeric => 1},
{name => 'content', desc => 'Content'},
{name => 'attach_data.thedata', desc => 'Attachment data'},
- {name => 'attachments.isurl', desc => 'Attachment is a URL'},
{name => "owner_idle_time", desc => "Time Since Assignee Touched"},
+ {name => 'see_also', desc => "See Also",
+ type => FIELD_TYPE_BUG_URLS},
+ {name => 'tag', desc => 'Tags'},
);
+################
+# Constructors #
+################
+
+# Override match to add is_select.
+sub match {
+ my $self = shift;
+ my ($params) = @_;
+ if (delete $params->{is_select}) {
+ $params->{type} = [FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT];
+ }
+ return $self->SUPER::match(@_);
+}
+
##############
# Validators #
##############
@@ -203,10 +292,17 @@
sub _check_enter_bug { return $_[1] ? 1 : 0; }
+sub _check_is_numeric {
+ my ($invocant, $value, undef, $params) = @_;
+ my $type = blessed($invocant) ? $invocant->type : $params->{type};
+ return 1 if $type == FIELD_TYPE_BUG_ID;
+ return $value ? 1 : 0;
+}
+
sub _check_mailhead { return $_[1] ? 1 : 0; }
sub _check_name {
- my ($invocant, $name, $is_custom) = @_;
+ my ($class, $name, undef, $params) = @_;
$name = lc(clean_text($name));
$name || ThrowUserError('field_missing_name');
@@ -214,7 +310,7 @@
my $name_regex = qr/^[\w\.]+$/;
# Custom fields have more restrictive name requirements than
# standard fields.
- $name_regex = qr/^\w+$/ if $is_custom;
+ $name_regex = qr/^[a-zA-Z0-9_]+$/ if $params->{custom};
# Custom fields can't be named just "cf_", and there is no normal
# field named just "cf_".
($name =~ $name_regex && $name ne "cf_")
@@ -222,7 +318,7 @@
# If it's custom, prepend cf_ to the custom field name to distinguish
# it from standard fields.
- if ($name !~ /^cf_/ && $is_custom) {
+ if ($name !~ /^cf_/ && $params->{custom}) {
$name = 'cf_' . $name;
}
@@ -249,15 +345,87 @@
}
sub _check_type {
- my ($invocant, $type) = @_;
+ my ($invocant, $type, undef, $params) = @_;
my $saved_type = $type;
# The constant here should be updated every time a new,
# higher field type is added.
- (detaint_natural($type) && $type <= FIELD_TYPE_DATETIME)
+ (detaint_natural($type) && $type <= FIELD_TYPE_KEYWORDS)
|| ThrowCodeError('invalid_customfield_type', { type => $saved_type });
+
+ my $custom = blessed($invocant) ? $invocant->custom : $params->{custom};
+ if ($custom && !$type) {
+ ThrowCodeError('field_type_not_specified');
+ }
+
return $type;
}
+sub _check_value_field_id {
+ my ($invocant, $field_id, undef, $params) = @_;
+ my $is_select = $invocant->is_select($params);
+ if ($field_id && !$is_select) {
+ ThrowUserError('field_value_control_select_only');
+ }
+ return $invocant->_check_visibility_field_id($field_id);
+}
+
+sub _check_visibility_field_id {
+ my ($invocant, $field_id) = @_;
+ $field_id = trim($field_id);
+ return undef if !$field_id;
+ my $field = Bugzilla::Field->check({ id => $field_id });
+ if (blessed($invocant) && $field->id == $invocant->id) {
+ ThrowUserError('field_cant_control_self', { field => $field });
+ }
+ if (!$field->is_select) {
+ ThrowUserError('field_control_must_be_select',
+ { field => $field });
+ }
+ return $field->id;
+}
+
+sub _check_visibility_values {
+ my ($invocant, $values, undef, $params) = @_;
+ my $field;
+ if (blessed $invocant) {
+ $field = $invocant->visibility_field;
+ }
+ elsif ($params->{visibility_field_id}) {
+ $field = $invocant->new($params->{visibility_field_id});
+ }
+ # When no field is set, no values are set.
+ return [] if !$field;
+
+ if (!scalar @$values) {
+ ThrowUserError('field_visibility_values_must_be_selected',
+ { field => $field });
+ }
+
+ my @visibility_values;
+ my $choice = Bugzilla::Field::Choice->type($field);
+ foreach my $value (@$values) {
+ if (!blessed $value) {
+ $value = $choice->check({ id => $value });
+ }
+ push(@visibility_values, $value);
+ }
+
+ return \@visibility_values;
+}
+
+sub _check_reverse_desc {
+ my ($invocant, $reverse_desc, undef, $params) = @_;
+ my $type = blessed($invocant) ? $invocant->type : $params->{type};
+ if ($type != FIELD_TYPE_BUG_ID) {
+ return undef; # store NULL for non-reversible field types
+ }
+
+ $reverse_desc = clean_text($reverse_desc);
+ return $reverse_desc;
+}
+
+sub _check_is_mandatory { return $_[1] ? 1 : 0; }
+
=pod
=head2 Instance Properties
@@ -361,25 +529,300 @@
=over
-=item C<legal_values>
+=item C<buglist>
-A reference to an array with valid active values for this field.
+A boolean specifying whether or not this field is selectable
+as a display or order column in buglist.cgi
=back
=cut
+sub buglist { return $_[0]->{buglist} }
+
+=over
+
+=item C<is_select>
+
+True if this is a C<FIELD_TYPE_SINGLE_SELECT> or C<FIELD_TYPE_MULTI_SELECT>
+field. It is only safe to call L</legal_values> if this is true.
+
+=item C<legal_values>
+
+Valid values for this field, as an array of L<Bugzilla::Field::Choice>
+objects.
+
+=back
+
+=cut
+
+sub is_select {
+ my ($invocant, $params) = @_;
+ # This allows this method to be called by create() validators.
+ my $type = blessed($invocant) ? $invocant->type : $params->{type};
+ return ($type == FIELD_TYPE_SINGLE_SELECT
+ || $type == FIELD_TYPE_MULTI_SELECT) ? 1 : 0
+}
+
+=over
+
+=item C<is_abnormal>
+
+Most fields that have a C<SELECT> L</type> have a certain schema for
+the table that stores their values, the table has the same name as the field,
+and the field's legal values can be edited via F<editvalues.cgi>.
+
+However, some fields do not follow that pattern. Those fields are
+considered "abnormal".
+
+This method returns C<1> if the field is "abnormal", C<0> otherwise.
+
+=back
+
+=cut
+
+sub is_abnormal {
+ my $self = shift;
+ return ABNORMAL_SELECTS->{$self->name} ? 1 : 0;
+}
+
sub legal_values {
my $self = shift;
if (!defined $self->{'legal_values'}) {
- $self->{'legal_values'} = get_legal_field_values($self->name);
+ require Bugzilla::Field::Choice;
+ my @values = Bugzilla::Field::Choice->type($self)->get_all();
+ $self->{'legal_values'} = \@values;
}
return $self->{'legal_values'};
}
=pod
+=over
+
+=item C<is_timetracking>
+
+True if this is a time-tracking field that should only be shown to users
+in the C<timetrackinggroup>.
+
+=back
+
+=cut
+
+sub is_timetracking {
+ my ($self) = @_;
+ return grep($_ eq $self->name, TIMETRACKING_FIELDS) ? 1 : 0;
+}
+
+=pod
+
+=over
+
+=item C<visibility_field>
+
+What field controls this field's visibility? Returns a C<Bugzilla::Field>
+object representing the field that controls this field's visibility.
+
+Returns undef if there is no field that controls this field's visibility.
+
+=back
+
+=cut
+
+sub visibility_field {
+ my $self = shift;
+ if ($self->{visibility_field_id}) {
+ $self->{visibility_field} ||=
+ $self->new($self->{visibility_field_id});
+ }
+ return $self->{visibility_field};
+}
+
+=pod
+
+=over
+
+=item C<visibility_values>
+
+If we have a L</visibility_field>, then what values does that field have to
+be set to in order to show this field? Returns a L<Bugzilla::Field::Choice>
+or undef if there is no C<visibility_field> set.
+
+=back
+
+=cut
+
+sub visibility_values {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ return [] if !$self->{visibility_field_id};
+
+ if (!defined $self->{visibility_values}) {
+ my $visibility_value_ids =
+ $dbh->selectcol_arrayref("SELECT value_id FROM field_visibility
+ WHERE field_id = ?", undef, $self->id);
+
+ $self->{visibility_values} =
+ Bugzilla::Field::Choice->type($self->visibility_field)
+ ->new_from_list($visibility_value_ids);
+ }
+
+ return $self->{visibility_values};
+}
+
+=pod
+
+=over
+
+=item C<controls_visibility_of>
+
+An arrayref of C<Bugzilla::Field> objects, representing fields that this
+field controls the visibility of.
+
+=back
+
+=cut
+
+sub controls_visibility_of {
+ my $self = shift;
+ $self->{controls_visibility_of} ||=
+ Bugzilla::Field->match({ visibility_field_id => $self->id });
+ return $self->{controls_visibility_of};
+}
+
+=pod
+
+=over
+
+=item C<value_field>
+
+The Bugzilla::Field that controls the list of values for this field.
+
+Returns undef if there is no field that controls this field's visibility.
+
+=back
+
+=cut
+
+sub value_field {
+ my $self = shift;
+ if ($self->{value_field_id}) {
+ $self->{value_field} ||= $self->new($self->{value_field_id});
+ }
+ return $self->{value_field};
+}
+
+=pod
+
+=over
+
+=item C<controls_values_of>
+
+An arrayref of C<Bugzilla::Field> objects, representing fields that this
+field controls the values of.
+
+=back
+
+=cut
+
+sub controls_values_of {
+ my $self = shift;
+ $self->{controls_values_of} ||=
+ Bugzilla::Field->match({ value_field_id => $self->id });
+ return $self->{controls_values_of};
+}
+
+=over
+
+=item C<is_visible_on_bug>
+
+See L<Bugzilla::Field::ChoiceInterface>.
+
+=back
+
+=cut
+
+sub is_visible_on_bug {
+ my ($self, $bug) = @_;
+
+ # Always return visible, if this field is not
+ # visibility controlled.
+ return 1 if !$self->{visibility_field_id};
+
+ my $visibility_values = $self->visibility_values;
+
+ return (any { $_->is_set_on_bug($bug) } @$visibility_values) ? 1 : 0;
+}
+
+=over
+
+=item C<is_relationship>
+
+Applies only to fields of type FIELD_TYPE_BUG_ID.
+Checks to see if a reverse relationship description has been set.
+This is the canonical condition to enable reverse link display,
+dependency tree display, and similar functionality.
+
+=back
+
+=cut
+
+sub is_relationship {
+ my $self = shift;
+ my $desc = $self->reverse_desc;
+ if (defined $desc && $desc ne "") {
+ return 1;
+ }
+ return 0;
+}
+
+=over
+
+=item C<reverse_desc>
+
+Applies only to fields of type FIELD_TYPE_BUG_ID.
+Describes the reverse relationship of this field.
+For example, if a BUG_ID field is called "Is a duplicate of",
+the reverse description would be "Duplicates of this bug".
+
+=back
+
+=cut
+
+sub reverse_desc { return $_[0]->{reverse_desc} }
+
+=over
+
+=item C<is_mandatory>
+
+a boolean specifying whether or not the field is mandatory;
+
+=back
+
+=cut
+
+sub is_mandatory { return $_[0]->{is_mandatory} }
+
+=over
+
+=item C<is_numeric>
+
+A boolean specifying whether or not this field logically contains
+numeric (integer, decimal, or boolean) values. By "logically contains" we
+mean that the user inputs numbers into the value of the field in the UI.
+This is mostly used by L<Bugzilla::Search>.
+
+=back
+
+=cut
+
+sub is_numeric { return $_[0]->{is_numeric} }
+
+
+=pod
+
=head2 Instance Mutators
These set the particular field that they are named after.
@@ -400,15 +843,50 @@
=item C<set_in_new_bugmail>
+=item C<set_buglist>
+
+=item C<set_reverse_desc>
+
+=item C<set_visibility_field>
+
+=item C<set_visibility_values>
+
+=item C<set_value_field>
+
+=item C<set_is_mandatory>
+
+
=back
=cut
sub set_description { $_[0]->set('description', $_[1]); }
sub set_enter_bug { $_[0]->set('enter_bug', $_[1]); }
+sub set_is_numeric { $_[0]->set('is_numeric', $_[1]); }
sub set_obsolete { $_[0]->set('obsolete', $_[1]); }
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
sub set_in_new_bugmail { $_[0]->set('mailhead', $_[1]); }
+sub set_buglist { $_[0]->set('buglist', $_[1]); }
+sub set_reverse_desc { $_[0]->set('reverse_desc', $_[1]); }
+sub set_visibility_field {
+ my ($self, $value) = @_;
+ $self->set('visibility_field_id', $value);
+ delete $self->{visibility_field};
+ delete $self->{visibility_values};
+}
+sub set_visibility_values {
+ my ($self, $value_ids) = @_;
+ $self->set('visibility_values', $value_ids);
+}
+sub set_value_field {
+ my ($self, $value) = @_;
+ $self->set('value_field_id', $value);
+ delete $self->{value_field};
+}
+sub set_is_mandatory { $_[0]->set('is_mandatory', $_[1]); }
+
+# This is only used internally by upgrade code in Bugzilla::Field.
+sub _set_type { $_[0]->set('type', $_[1]); }
=pod
@@ -456,16 +934,14 @@
$bugs_query = "SELECT COUNT(*) FROM bug_$name";
}
else {
- $bugs_query = "SELECT COUNT(*) FROM bugs WHERE $name IS NOT NULL
- AND $name != ''";
+ $bugs_query = "SELECT COUNT(*) FROM bugs WHERE $name IS NOT NULL";
+ if ($self->type != FIELD_TYPE_BUG_ID && $self->type != FIELD_TYPE_DATETIME) {
+ $bugs_query .= " AND $name != ''";
+ }
# Ignore the default single select value
if ($self->type == FIELD_TYPE_SINGLE_SELECT) {
$bugs_query .= " AND $name != '---'";
}
- # Ignore blank dates.
- if ($self->type == FIELD_TYPE_DATETIME) {
- $bugs_query .= " AND $name != '00-00-00 00:00:00'";
- }
}
my $has_bugs = $dbh->selectrow_array($bugs_query);
@@ -483,9 +959,7 @@
$dbh->bz_drop_column('bugs', $name);
}
- if ($type == FIELD_TYPE_SINGLE_SELECT
- || $type == FIELD_TYPE_MULTI_SELECT)
- {
+ if ($self->is_select) {
# Delete the table that holds the legal values for this field.
$dbh->bz_drop_field_tables($self);
}
@@ -520,8 +994,13 @@
=item C<enter_bug> - boolean - Whether this field is
editable on the bug creation form. Defaults to 0.
+=item C<buglist> - boolean - Whether this field is
+selectable as a display or order column in bug lists. Defaults to 0.
+
C<obsolete> - boolean - Whether this field is obsolete. Defaults to 0.
+C<is_mandatory> - boolean - Whether this field is mandatory. Defaults to 0.
+
=back
=back
@@ -530,9 +1009,25 @@
sub create {
my $class = shift;
- my $field = $class->SUPER::create(@_);
-
+ my ($params) = @_;
my $dbh = Bugzilla->dbh;
+
+ # This makes sure the "sortkey" validator runs, even if
+ # the parameter isn't sent to create().
+ $params->{sortkey} = undef if !exists $params->{sortkey};
+ $params->{type} ||= 0;
+
+ $dbh->bz_start_transaction();
+ $class->check_required_create_fields(@_);
+ my $field_values = $class->run_create_validators($params);
+ my $visibility_values = delete $field_values->{visibility_values};
+ my $field = $class->insert_create_data($field_values);
+
+ $field->set_visibility_values($visibility_values);
+ $field->_update_visibility_values();
+
+ $dbh->bz_commit_transaction();
+
if ($field->custom) {
my $name = $field->name;
my $type = $field->type;
@@ -541,9 +1036,7 @@
$dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type});
}
- if ($type == FIELD_TYPE_SINGLE_SELECT
- || $type == FIELD_TYPE_MULTI_SELECT)
- {
+ if ($field->is_select) {
# Create the table that holds the legal values for this field.
$dbh->bz_add_field_tables($field);
}
@@ -557,20 +1050,36 @@
return $field;
}
-sub run_create_validators {
- my $class = shift;
+sub update {
+ my $self = shift;
+ my $changes = $self->SUPER::update(@_);
my $dbh = Bugzilla->dbh;
- my $params = $class->SUPER::run_create_validators(@_);
-
- $params->{name} = $class->_check_name($params->{name}, $params->{custom});
- if (!exists $params->{sortkey}) {
- $params->{sortkey} = $dbh->selectrow_array(
- "SELECT MAX(sortkey) + 100 FROM fielddefs") || 100;
+ if ($changes->{value_field_id} && $self->is_select) {
+ $dbh->do("UPDATE " . $self->name . " SET visibility_value_id = NULL");
}
-
- return $params;
+ $self->_update_visibility_values();
+ return $changes;
}
+sub _update_visibility_values {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my @visibility_value_ids = map($_->id, @{$self->visibility_values});
+ $self->_delete_visibility_values();
+ for my $value_id (@visibility_value_ids) {
+ $dbh->do("INSERT INTO field_visibility (field_id, value_id)
+ VALUES (?, ?)", undef, $self->id, $value_id);
+ }
+}
+
+sub _delete_visibility_values {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("DELETE FROM field_visibility WHERE field_id = ?",
+ undef, $self->id);
+ delete $self->{visibility_values};
+}
=pod
@@ -625,6 +1134,10 @@
if ($field) {
$field->set_description($def->{desc});
$field->set_in_new_bugmail($def->{in_new_bugmail});
+ $field->set_buglist($def->{buglist});
+ $field->_set_type($def->{type}) if $def->{type};
+ $field->set_is_mandatory($def->{is_mandatory});
+ $field->set_is_numeric($def->{is_numeric});
$field->update();
}
else {
@@ -632,8 +1145,7 @@
$def->{mailhead} = $def->{in_new_bugmail};
delete $def->{in_new_bugmail};
}
- $def->{description} = $def->{desc};
- delete $def->{desc};
+ $def->{description} = delete $def->{desc};
Bugzilla::Field->create($def);
}
}
@@ -750,13 +1262,19 @@
my $dbh = Bugzilla->dbh;
# If $legalsRef is undefined, we use the default valid values.
+ # Valid values for this check are all possible values.
+ # Using get_legal_values would only return active values, but since
+ # some bugs may have inactive values set, we want to check them too.
unless (defined $legalsRef) {
- $legalsRef = get_legal_field_values($name);
+ $legalsRef = Bugzilla::Field->new({name => $name})->legal_values;
+ my @values = map($_->name, @$legalsRef);
+ $legalsRef = \@values;
+
}
if (!defined($value)
- || trim($value) eq ""
- || lsearch($legalsRef, $value) < 0)
+ or trim($value) eq ""
+ or !grep { $_ eq $value } @$legalsRef)
{
return 0 if $no_warn; # We don't want an error to be thrown; return.
trick_taint($name);
diff --git a/Websites/bugs.webkit.org/Bugzilla/Field/Choice.pm b/Websites/bugs.webkit.org/Bugzilla/Field/Choice.pm
new file mode 100644
index 0000000..773dbd4
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Field/Choice.pm
@@ -0,0 +1,347 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Initial Developer of the Original Code is NASA.
+# Portions created by NASA are Copyright (C) 2006 San Jose State
+# University Foundation. All Rights Reserved.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Greg Hendricks <ghendricks@novell.com>
+
+use strict;
+
+package Bugzilla::Field::Choice;
+
+use base qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
+
+use Bugzilla::Config qw(SetParam write_params);
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field;
+use Bugzilla::Util qw(trim detaint_natural);
+
+use Scalar::Util qw(blessed);
+
+##################
+# Initialization #
+##################
+
+use constant DB_COLUMNS => qw(
+ id
+ value
+ sortkey
+ isactive
+ visibility_value_id
+);
+
+use constant UPDATE_COLUMNS => qw(
+ value
+ sortkey
+ isactive
+ visibility_value_id
+);
+
+use constant NAME_FIELD => 'value';
+use constant LIST_ORDER => 'sortkey, value';
+
+use constant VALIDATORS => {
+ value => \&_check_value,
+ sortkey => \&_check_sortkey,
+ visibility_value_id => \&_check_visibility_value_id,
+ isactive => \&_check_isactive,
+};
+
+use constant CLASS_MAP => {
+ bug_status => 'Bugzilla::Status',
+ classification => 'Bugzilla::Classification',
+ component => 'Bugzilla::Component',
+ product => 'Bugzilla::Product',
+};
+
+use constant DEFAULT_MAP => {
+ op_sys => 'defaultopsys',
+ rep_platform => 'defaultplatform',
+ priority => 'defaultpriority',
+ bug_severity => 'defaultseverity',
+};
+
+#################
+# Class Factory #
+#################
+
+# Bugzilla::Field::Choice is actually an abstract base class. Every field
+# type has its own dynamically-generated class for its values. This allows
+# certain fields to have special types, like how bug_status's values
+# are Bugzilla::Status objects.
+
+sub type {
+ my ($class, $field) = @_;
+ my $field_obj = blessed $field ? $field : Bugzilla::Field->check($field);
+ my $field_name = $field_obj->name;
+
+ if ($class->CLASS_MAP->{$field_name}) {
+ return $class->CLASS_MAP->{$field_name};
+ }
+
+ # For generic classes, we use a lowercase class name, so as
+ # not to interfere with any real subclasses we might make some day.
+ my $package = "Bugzilla::Field::Choice::$field_name";
+ Bugzilla->request_cache->{"field_$package"} = $field_obj;
+
+ # This package only needs to be created once. We check if the DB_TABLE
+ # glob for this package already exists, which tells us whether or not
+ # we need to create the package (this works even under mod_perl, where
+ # this package definition will persist across requests)).
+ if (!defined *{"${package}::DB_TABLE"}) {
+ eval <<EOC;
+ package $package;
+ use base qw(Bugzilla::Field::Choice);
+ use constant DB_TABLE => '$field_name';
+EOC
+ }
+
+ return $package;
+}
+
+################
+# Constructors #
+################
+
+# We just make new() enforce this, which should give developers
+# the understanding that you can't use Bugzilla::Field::Choice
+# without calling type().
+sub new {
+ my $class = shift;
+ if ($class eq 'Bugzilla::Field::Choice') {
+ ThrowCodeError('field_choice_must_use_type');
+ }
+ $class->SUPER::new(@_);
+}
+
+#########################
+# Database Manipulation #
+#########################
+
+# Our subclasses can take more arguments than we normally accept.
+# So, we override create() to remove arguments that aren't valid
+# columns. (Normally Bugzilla::Object dies if you pass arguments
+# that aren't valid columns.)
+sub create {
+ my $class = shift;
+ my ($params) = @_;
+ foreach my $key (keys %$params) {
+ if (!grep {$_ eq $key} $class->_get_db_columns) {
+ delete $params->{$key};
+ }
+ }
+ return $class->SUPER::create(@_);
+}
+
+sub update {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $fname = $self->field->name;
+
+ $dbh->bz_start_transaction();
+
+ my ($changes, $old_self) = $self->SUPER::update(@_);
+ if (exists $changes->{value}) {
+ my ($old, $new) = @{ $changes->{value} };
+ if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
+ $dbh->do("UPDATE bug_$fname SET value = ? WHERE value = ?",
+ undef, $new, $old);
+ }
+ else {
+ $dbh->do("UPDATE bugs SET $fname = ? WHERE $fname = ?",
+ undef, $new, $old);
+ }
+
+ if ($old_self->is_default) {
+ my $param = $self->DEFAULT_MAP->{$self->field->name};
+ SetParam($param, $self->name);
+ write_params();
+ }
+ }
+
+ $dbh->bz_commit_transaction();
+ return wantarray ? ($changes, $old_self) : $changes;
+}
+
+sub remove_from_db {
+ my $self = shift;
+ if ($self->is_default) {
+ ThrowUserError('fieldvalue_is_default',
+ { field => $self->field, value => $self,
+ param_name => $self->DEFAULT_MAP->{$self->field->name},
+ });
+ }
+ if ($self->is_static) {
+ ThrowUserError('fieldvalue_not_deletable',
+ { field => $self->field, value => $self });
+ }
+ if ($self->bug_count) {
+ ThrowUserError("fieldvalue_still_has_bugs",
+ { field => $self->field, value => $self });
+ }
+ $self->_check_if_controller(); # From ChoiceInterface.
+ $self->SUPER::remove_from_db();
+}
+
+############
+# Mutators #
+############
+
+sub set_is_active { $_[0]->set('isactive', $_[1]); }
+sub set_name { $_[0]->set('value', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+sub set_visibility_value {
+ my ($self, $value) = @_;
+ $self->set('visibility_value_id', $value);
+ delete $self->{visibility_value};
+}
+
+##############
+# Validators #
+##############
+
+sub _check_isactive {
+ my ($invocant, $value) = @_;
+ $value = Bugzilla::Object::check_boolean($invocant, $value);
+ if (!$value and ref $invocant) {
+ if ($invocant->is_default) {
+ my $field = $invocant->field;
+ ThrowUserError('fieldvalue_is_default',
+ { value => $invocant, field => $field,
+ param_name => $invocant->DEFAULT_MAP->{$field->name}
+ });
+ }
+ if ($invocant->is_static) {
+ ThrowUserError('fieldvalue_not_deletable',
+ { value => $invocant, field => $invocant->field });
+ }
+ }
+ return $value;
+}
+
+sub _check_value {
+ my ($invocant, $value) = @_;
+
+ my $field = $invocant->field;
+
+ $value = trim($value);
+
+ # Make sure people don't rename static values
+ if (blessed($invocant) && $value ne $invocant->name
+ && $invocant->is_static)
+ {
+ ThrowUserError('fieldvalue_not_editable',
+ { field => $field, old_value => $invocant });
+ }
+
+ ThrowUserError('fieldvalue_undefined') if !defined $value || $value eq "";
+ ThrowUserError('fieldvalue_name_too_long', { value => $value })
+ if length($value) > MAX_FIELD_VALUE_SIZE;
+
+ my $exists = $invocant->type($field)->new({ name => $value });
+ if ($exists && (!blessed($invocant) || $invocant->id != $exists->id)) {
+ ThrowUserError('fieldvalue_already_exists',
+ { field => $field, value => $exists });
+ }
+
+ return $value;
+}
+
+sub _check_sortkey {
+ my ($invocant, $value) = @_;
+ $value = trim($value);
+ return 0 if !$value;
+ # Store for the error message in case detaint_natural clears it.
+ my $orig_value = $value;
+ detaint_natural($value)
+ || ThrowUserError('fieldvalue_sortkey_invalid',
+ { sortkey => $orig_value,
+ field => $invocant->field });
+ return $value;
+}
+
+sub _check_visibility_value_id {
+ my ($invocant, $value_id) = @_;
+ $value_id = trim($value_id);
+ my $field = $invocant->field->value_field;
+ return undef if !$field || !$value_id;
+ my $value_obj = Bugzilla::Field::Choice->type($field)
+ ->check({ id => $value_id });
+ return $value_obj->id;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Field::Choice - A legal value for a <select>-type field.
+
+=head1 SYNOPSIS
+
+ my $field = new Bugzilla::Field({name => 'bug_status'});
+
+ my $choice = new Bugzilla::Field::Choice->type($field)->new(1);
+
+ my $choices = Bugzilla::Field::Choice->type($field)->new_from_list([1,2,3]);
+ my $choices = Bugzilla::Field::Choice->type($field)->get_all();
+ my $choices = Bugzilla::Field::Choice->type($field->match({ sortkey => 10 });
+
+=head1 DESCRIPTION
+
+This is an implementation of L<Bugzilla::Object>, but with a twist.
+You can't call any class methods (such as C<new>, C<create>, etc.)
+directly on C<Bugzilla::Field::Choice> itself. Instead, you have to
+call C<Bugzilla::Field::Choice-E<gt>type($field)> to get the class
+you're going to instantiate, and then you call the methods on that.
+
+We do that because each field has its own database table for its values, so
+each value type needs its own class.
+
+See the L</SYNOPSIS> for examples of how this works.
+
+This class implements L<Bugzilla::Field::ChoiceInterface>, and so all
+methods of that class are also available here.
+
+=head1 METHODS
+
+=head2 Class Factory
+
+In object-oriented design, a "class factory" is a method that picks
+and returns the right class for you, based on an argument that you pass.
+
+=over
+
+=item C<type>
+
+Takes a single argument, which is either the name of a field from the
+C<fielddefs> table, or a L<Bugzilla::Field> object representing a field.
+
+Returns an appropriate subclass of C<Bugzilla::Field::Choice> that you
+can now call class methods on (like C<new>, C<create>, C<match>, etc.)
+
+B<NOTE>: YOU CANNOT CALL CLASS METHODS ON C<Bugzilla::Field::Choice>. You
+must call C<type> to get a class you can call methods on.
+
+=back
+
+=head2 Mutators
+
+This class implements mutators for all of the settable accessors in
+L<Bugzilla::Field::ChoiceInterface>.
diff --git a/Websites/bugs.webkit.org/Bugzilla/Field/ChoiceInterface.pm b/Websites/bugs.webkit.org/Bugzilla/Field/ChoiceInterface.pm
new file mode 100644
index 0000000..87354a1
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Field/ChoiceInterface.pm
@@ -0,0 +1,284 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Initial Developer of the Original Code is NASA.
+# Portions created by NASA are Copyright (C) 2006 San Jose State
+# University Foundation. All Rights Reserved.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Greg Hendricks <ghendricks@novell.com>
+
+package Bugzilla::Field::ChoiceInterface;
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field;
+
+use Scalar::Util qw(blessed);
+
+# Helps implement the "field" accessor without subclasses having to
+# write code.
+sub FIELD_NAME { return $_[0]->DB_TABLE; }
+
+####################
+# Subclass Helpers #
+####################
+
+sub _check_if_controller {
+ my $self = shift;
+ my $vis_fields = $self->controls_visibility_of_fields;
+ my $values = $self->controlled_values_array;
+ if (@$vis_fields || @$values) {
+ ThrowUserError('fieldvalue_is_controller',
+ { value => $self, fields => [map($_->name, @$vis_fields)],
+ vals => $self->controlled_values });
+ }
+}
+
+
+#############
+# Accessors #
+#############
+
+sub is_active { return $_[0]->{'isactive'}; }
+sub sortkey { return $_[0]->{'sortkey'}; }
+
+sub bug_count {
+ my $self = shift;
+ return $self->{bug_count} if defined $self->{bug_count};
+ my $dbh = Bugzilla->dbh;
+ my $fname = $self->field->name;
+ my $count;
+ if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
+ $count = $dbh->selectrow_array("SELECT COUNT(*) FROM bug_$fname
+ WHERE value = ?", undef, $self->name);
+ }
+ else {
+ $count = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs
+ WHERE $fname = ?",
+ undef, $self->name);
+ }
+ $self->{bug_count} = $count;
+ return $count;
+}
+
+sub field {
+ my $invocant = shift;
+ my $class = ref $invocant || $invocant;
+ my $cache = Bugzilla->request_cache;
+ # This is just to make life easier for subclasses. Our auto-generated
+ # subclasses from Bugzilla::Field::Choice->type() already have this set.
+ $cache->{"field_$class"} ||=
+ new Bugzilla::Field({ name => $class->FIELD_NAME });
+ return $cache->{"field_$class"};
+}
+
+sub is_default {
+ my $self = shift;
+ my $name = $self->DEFAULT_MAP->{$self->field->name};
+ # If it doesn't exist in DEFAULT_MAP, then there is no parameter
+ # related to this field.
+ return 0 unless $name;
+ return ($self->name eq Bugzilla->params->{$name}) ? 1 : 0;
+}
+
+sub is_static {
+ my $self = shift;
+ # If we need to special-case Resolution for *anything* else, it should
+ # get its own subclass.
+ if ($self->field->name eq 'resolution') {
+ return grep($_ eq $self->name, ('', 'FIXED', 'DUPLICATE'))
+ ? 1 : 0;
+ }
+ elsif ($self->field->custom) {
+ return $self->name eq '---' ? 1 : 0;
+ }
+ return 0;
+}
+
+sub controls_visibility_of_fields {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!$self->{controls_visibility_of_fields}) {
+ my $ids = $dbh->selectcol_arrayref(
+ "SELECT id FROM fielddefs
+ INNER JOIN field_visibility
+ ON fielddefs.id = field_visibility.field_id
+ WHERE value_id = ? AND visibility_field_id = ?", undef,
+ $self->id, $self->field->id);
+
+ $self->{controls_visibility_of_fields} =
+ Bugzilla::Field->new_from_list($ids);
+ }
+
+ return $self->{controls_visibility_of_fields};
+}
+
+sub visibility_value {
+ my $self = shift;
+ if ($self->{visibility_value_id}) {
+ require Bugzilla::Field::Choice;
+ $self->{visibility_value} ||=
+ Bugzilla::Field::Choice->type($self->field->value_field)->new(
+ $self->{visibility_value_id});
+ }
+ return $self->{visibility_value};
+}
+
+sub controlled_values {
+ my $self = shift;
+ return $self->{controlled_values} if defined $self->{controlled_values};
+ my $fields = $self->field->controls_values_of;
+ my %controlled_values;
+ require Bugzilla::Field::Choice;
+ foreach my $field (@$fields) {
+ $controlled_values{$field->name} =
+ Bugzilla::Field::Choice->type($field)
+ ->match({ visibility_value_id => $self->id });
+ }
+ $self->{controlled_values} = \%controlled_values;
+ return $self->{controlled_values};
+}
+
+sub controlled_values_array {
+ my ($self) = @_;
+ my $values = $self->controlled_values;
+ return [map { @{ $values->{$_} } } keys %$values];
+}
+
+sub is_visible_on_bug {
+ my ($self, $bug) = @_;
+
+ # Values currently set on the bug are always shown.
+ return 1 if $self->is_set_on_bug($bug);
+
+ # Inactive values are, otherwise, never shown.
+ return 0 if !$self->is_active;
+
+ # Values without a visibility value are, otherwise, always shown.
+ my $visibility_value = $self->visibility_value;
+ return 1 if !$visibility_value;
+
+ # Values with a visibility value are only shown if the visibility
+ # value is set on the bug.
+ return $visibility_value->is_set_on_bug($bug);
+}
+
+sub is_set_on_bug {
+ my ($self, $bug) = @_;
+ my $field_name = $self->FIELD_NAME;
+ # This allows bug/create/create.html.tmpl to pass in a hashref that
+ # looks like a bug object.
+ my $value = blessed($bug) ? $bug->$field_name : $bug->{$field_name};
+ return 0 if !defined $value;
+
+ if ($self->field->type == FIELD_TYPE_BUG_URLS
+ or $self->field->type == FIELD_TYPE_MULTI_SELECT)
+ {
+ return grep($_ eq $self->name, @$value) ? 1 : 0;
+ }
+ return $value eq $self->name ? 1 : 0;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Field::ChoiceInterface - Makes an object act like a
+Bugzilla::Field::Choice.
+
+=head1 DESCRIPTION
+
+This is an "interface", in the Java sense (sometimes called a "Role"
+or a "Mixin" in other languages). L<Bugzilla::Field::Choice> is the
+primary implementor of this interface, but other classes also implement
+it if they want to "act like" L<Bugzilla::Field::Choice>.
+
+=head1 METHODS
+
+=head2 Accessors
+
+These are in addition to the standard L<Bugzilla::Object> accessors.
+
+=over
+
+=item C<sortkey>
+
+The key that determines the sort order of this item.
+
+=item C<field>
+
+The L<Bugzilla::Field> object that this field value belongs to.
+
+=item C<is_active>
+
+Whether or not this value should appear as an option on bugs that do
+not already have it set as the current value.
+
+=item C<is_static>
+
+C<0> if this field value can be renamed or deleted, C<1> otherwise.
+
+=item C<is_default>
+
+C<1> if this is the default value for this field, C<0> otherwise.
+
+=item C<bug_count>
+
+An integer count of the number of bugs that have this value set.
+
+=item C<controls_visibility_of_fields>
+
+Returns an arrayref of L<Bugzilla::Field> objects, representing any
+fields whose visibility are controlled by this field value.
+
+=item C<controlled_values>
+
+Tells you which values in B<other> fields appear (become visible) when this
+value is set in its field.
+
+Returns a hashref of arrayrefs. The hash keys are the names of fields,
+and the values are arrays of objects that implement
+C<Bugzilla::Field::ChoiceInterface>, representing values that this value
+controls the visibility of, for that field.
+
+=item C<visibility_value>
+
+Returns an object that implements C<Bugzilla::Field::ChoiceInterface>,
+which represents the value that needs to be set in order for this
+value to appear in the UI.
+
+=item C<is_visible_on_bug>
+
+Returns C<1> if, according to the settings of C<is_active> and
+C<visibility_value>, this value should be displayed as an option
+when viewing a bug. Returns C<0> otherwise.
+
+Takes a single argument, a L<Bugzilla::Bug> object or a hash with
+similar fields to a L<Bugzilla::Bug> object.
+
+=item C<is_set_on_bug>
+
+Returns C<1> if this value is the current value set for its field on
+the passed-in L<Bugzilla::Bug> object (or a hash that looks like a
+L<Bugzilla::Bug>). For multi-valued fields, we return C<1> if
+I<any> of the currently selected values are this value.
+
+Returns C<0> otherwise.
+
+=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/Flag.pm b/Websites/bugs.webkit.org/Bugzilla/Flag.pm
index 69edb81..596423e 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Flag.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Flag.pm
@@ -53,6 +53,9 @@
=cut
+use Scalar::Util qw(blessed);
+use Storable qw(dclone);
+
use Bugzilla::FlagType;
use Bugzilla::Hook;
use Bugzilla::User;
@@ -69,21 +72,40 @@
#### Initialization ####
###############################
-use constant DB_COLUMNS => qw(
- flags.id
- flags.type_id
- flags.bug_id
- flags.attach_id
- flags.requestee_id
- flags.setter_id
- flags.status
-);
-
use constant DB_TABLE => 'flags';
use constant LIST_ORDER => 'id';
+# Flags are tracked in bugs_activity.
+use constant AUDIT_CREATES => 0;
+use constant AUDIT_UPDATES => 0;
+use constant AUDIT_REMOVES => 0;
use constant SKIP_REQUESTEE_ON_ERROR => 1;
+use constant DB_COLUMNS => qw(
+ id
+ type_id
+ bug_id
+ attach_id
+ requestee_id
+ setter_id
+ status
+);
+
+use constant UPDATE_COLUMNS => qw(
+ requestee_id
+ setter_id
+ status
+ type_id
+);
+
+use constant VALIDATORS => {
+};
+
+use constant UPDATE_VALIDATORS => {
+ setter => \&_check_setter,
+ status => \&_check_status,
+};
+
###############################
#### Accessors ######
###############################
@@ -116,11 +138,14 @@
=cut
-sub id { return $_[0]->{'id'}; }
-sub name { return $_[0]->type->name; }
-sub bug_id { return $_[0]->{'bug_id'}; }
-sub attach_id { return $_[0]->{'attach_id'}; }
-sub status { return $_[0]->{'status'}; }
+sub id { return $_[0]->{'id'}; }
+sub name { return $_[0]->type->name; }
+sub type_id { return $_[0]->{'type_id'}; }
+sub bug_id { return $_[0]->{'bug_id'}; }
+sub attach_id { return $_[0]->{'attach_id'}; }
+sub status { return $_[0]->{'status'}; }
+sub setter_id { return $_[0]->{'setter_id'}; }
+sub requestee_id { return $_[0]->{'requestee_id'}; }
###############################
#### Methods ####
@@ -180,10 +205,18 @@
return undef unless $self->attach_id;
require Bugzilla::Attachment;
- $self->{'attachment'} ||= Bugzilla::Attachment->get($self->attach_id);
+ $self->{'attachment'} ||= new Bugzilla::Attachment($self->attach_id);
return $self->{'attachment'};
}
+sub bug {
+ my $self = shift;
+
+ require Bugzilla::Bug;
+ $self->{'bug'} ||= new Bugzilla::Bug($self->bug_id);
+ return $self->{'bug'};
+}
+
################################
## Searching/Retrieving Flags ##
################################
@@ -192,26 +225,6 @@
=over
-=item C<has_flags>
-
-Returns 1 if at least one flag exists in the DB, else 0. This subroutine
-is mainly used to decide to display the "(My )Requests" link in the footer.
-
-=back
-
-=cut
-
-sub has_flags {
- my $dbh = Bugzilla->dbh;
-
- my $has_flags = $dbh->selectrow_array('SELECT 1 FROM flags ' . $dbh->sql_limit(1));
- return $has_flags || 0;
-}
-
-=pod
-
-=over
-
=item C<match($criteria)>
Queries the database for flags matching the given criteria
@@ -268,376 +281,126 @@
# Creating and Modifying
######################################################################
-=pod
+sub set_flag {
+ my ($class, $obj, $params) = @_;
-=over
-
-=item C<validate($bug_id, $attach_id, $skip_requestee_on_error)>
-
-Validates fields containing flag modifications.
-
-If the attachment is new, it has no ID yet and $attach_id is set
-to -1 to force its check anyway.
-
-=back
-
-=cut
-
-sub validate {
- my ($bug_id, $attach_id, $skip_requestee_on_error) = @_;
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
-
- # Get a list of flags to validate. Uses the "map" function
- # to extract flag IDs from form field names by matching fields
- # whose name looks like "flag_type-nnn" (new flags) or "flag-nnn"
- # (existing flags), where "nnn" is the ID, and returning just
- # the ID portion of matching field names.
- my @flagtype_ids = map(/^flag_type-(\d+)$/ ? $1 : (), $cgi->param());
- my @flag_ids = map(/^flag-(\d+)$/ ? $1 : (), $cgi->param());
-
- return unless (scalar(@flagtype_ids) || scalar(@flag_ids));
-
- # No flag reference should exist when changing several bugs at once.
- ThrowCodeError("flags_not_available", { type => 'b' }) unless $bug_id;
-
- # We don't check that these new flags are valid for this bug/attachment,
- # because the bug may be moved into another product meanwhile.
- # This check will be done later when creating new flags, see FormToNewFlags().
-
- if (scalar(@flag_ids)) {
- # No reference to existing flags should exist when creating a new
- # attachment.
- if ($attach_id && ($attach_id < 0)) {
- ThrowCodeError('flags_not_available', { type => 'a' });
- }
-
- # Make sure all existing flags belong to the bug/attachment
- # they pretend to be.
- my $field = ($attach_id) ? "attach_id" : "bug_id";
- my $field_id = $attach_id || $bug_id;
- my $not = ($attach_id) ? "" : "NOT";
-
- my $invalid_data =
- $dbh->selectrow_array(
- "SELECT 1 FROM flags
- WHERE "
- . $dbh->sql_in('id', \@flag_ids)
- . " AND ($field != ? OR attach_id IS $not NULL) "
- . $dbh->sql_limit(1), undef, $field_id);
-
- if ($invalid_data) {
- ThrowCodeError('invalid_flag_association',
- { bug_id => $bug_id,
- attach_id => $attach_id });
- }
+ my ($bug, $attachment);
+ if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
+ $attachment = $obj;
+ $bug = $attachment->bug;
+ }
+ elsif (blessed($obj) && $obj->isa('Bugzilla::Bug')) {
+ $bug = $obj;
+ }
+ else {
+ ThrowCodeError('flag_unexpected_object', { 'caller' => ref $obj });
}
- # Validate new flags.
- foreach my $id (@flagtype_ids) {
- my $status = $cgi->param("flag_type-$id");
- my @requestees = $cgi->param("requestee_type-$id");
- my $private_attachment = $cgi->param('isprivate') ? 1 : 0;
+ # Update (or delete) an existing flag.
+ if ($params->{id}) {
+ my $flag = $class->check({ id => $params->{id} });
+ # Security check: make sure the flag belongs to the bug/attachment.
+ # We don't check that the user editing the flag can see
+ # the bug/attachment. That's the job of the caller.
+ ($attachment && $flag->attach_id && $attachment->id == $flag->attach_id)
+ || (!$attachment && !$flag->attach_id && $bug->id == $flag->bug_id)
+ || ThrowCodeError('invalid_flag_association',
+ { bug_id => $bug->id,
+ attach_id => $attachment ? $attachment->id : undef });
+
+ # Extract the current flag object from the object.
+ my ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
+ # If no flagtype can be found for this flag, this means the bug is being
+ # moved into a product/component where the flag is no longer valid.
+ # So either we can attach the flag to another flagtype having the same
+ # name, or we remove the flag.
+ if (!$obj_flagtype) {
+ my $success = $flag->retarget($obj);
+ return unless $success;
+
+ ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types};
+ push(@{$obj_flagtype->{flags}}, $flag);
+ }
+ my ($obj_flag) = grep { $_->id == $flag->id } @{$obj_flagtype->{flags}};
+ # If the flag has the correct type but cannot be found above, this means
+ # the flag is going to be removed (e.g. because this is a pending request
+ # and the attachment is being marked as obsolete).
+ return unless $obj_flag;
+
+ $class->_validate($obj_flag, $obj_flagtype, $params, $bug, $attachment);
+ }
+ # Create a new flag.
+ elsif ($params->{type_id}) {
# Don't bother validating types the user didn't touch.
- next if $status eq 'X';
+ return if $params->{status} eq 'X';
- # Make sure the flag type exists. If it doesn't, FormToNewFlags()
- # will ignore it, so it's safe to ignore it here.
- my $flag_type = new Bugzilla::FlagType($id);
- next unless $flag_type;
+ my $flagtype = Bugzilla::FlagType->check({ id => $params->{type_id} });
+ # Security check: make sure the flag type belongs to the bug/attachment.
+ ($attachment && $flagtype->target_type eq 'attachment'
+ && scalar(grep { $_->id == $flagtype->id } @{$attachment->flag_types}))
+ || (!$attachment && $flagtype->target_type eq 'bug'
+ && scalar(grep { $_->id == $flagtype->id } @{$bug->flag_types}))
+ || ThrowCodeError('invalid_flag_association',
+ { bug_id => $bug->id,
+ attach_id => $attachment ? $attachment->id : undef });
# Make sure the flag type is active.
- unless ($flag_type->is_active) {
- ThrowCodeError('flag_type_inactive', {'type' => $flag_type->name});
+ $flagtype->is_active
+ || ThrowCodeError('flag_type_inactive', { type => $flagtype->name });
+
+ # Extract the current flagtype object from the object.
+ my ($obj_flagtype) = grep { $_->id == $flagtype->id } @{$obj->flag_types};
+
+ # We cannot create a new flag if there is already one and this
+ # flag type is not multiplicable.
+ if (!$flagtype->is_multiplicable) {
+ if (scalar @{$obj_flagtype->{flags}}) {
+ ThrowUserError('flag_type_not_multiplicable', { type => $flagtype });
+ }
}
- _validate(undef, $flag_type, $status, undef, \@requestees, $private_attachment,
- $bug_id, $attach_id, $skip_requestee_on_error);
+ $class->_validate(undef, $obj_flagtype, $params, $bug, $attachment);
}
-
- # Validate existing flags.
- foreach my $id (@flag_ids) {
- my $status = $cgi->param("flag-$id");
- my @requestees = $cgi->param("requestee-$id");
- my $private_attachment = $cgi->param('isprivate') ? 1 : 0;
-
- # Make sure the flag exists. If it doesn't, process() will ignore it,
- # so it's safe to ignore it here.
- my $flag = new Bugzilla::Flag($id);
- next unless $flag;
-
- _validate($flag, $flag->type, $status, undef, \@requestees, $private_attachment,
- undef, undef, $skip_requestee_on_error);
+ else {
+ ThrowCodeError('param_required', { function => $class . '->set_flag',
+ param => 'id/type_id' });
}
}
sub _validate {
- my ($flag, $flag_type, $status, $setter, $requestees, $private_attachment,
- $bug_id, $attach_id, $skip_requestee_on_error) = @_;
+ my ($class, $flag, $flag_type, $params, $bug, $attachment) = @_;
- # By default, the flag setter (or requester) is the current user.
- $setter ||= Bugzilla->user;
+ # If it's a new flag, let's create it now.
+ my $obj_flag = $flag || bless({ type_id => $flag_type->id,
+ status => '',
+ bug_id => $bug->id,
+ attach_id => $attachment ?
+ $attachment->id : undef},
+ $class);
- my $id = $flag ? $flag->id : $flag_type->id; # Used in the error messages below.
- $bug_id ||= $flag->bug_id;
- $attach_id ||= $flag->attach_id if $flag; # Maybe it's a bug flag.
+ my $old_status = $obj_flag->status;
+ my $old_requestee_id = $obj_flag->requestee_id;
- # Make sure the user chose a valid status.
- grep($status eq $_, qw(X + - ?))
- || ThrowCodeError('flag_status_invalid',
- { id => $id, status => $status });
+ $obj_flag->_set_status($params->{status});
+ $obj_flag->_set_requestee($params->{requestee}, $attachment, $params->{skip_roe});
- # Make sure the user didn't request the flag unless it's requestable.
- # If the flag existed and was requested before it became unrequestable,
- # leave it as is.
- if ($status eq '?'
- && (!$flag || $flag->status ne '?')
- && !$flag_type->is_requestable)
+ # The setter field MUST NOT be updated if neither the status
+ # nor the requestee fields changed.
+ if (($obj_flag->status ne $old_status)
+ # The requestee ID can be undefined.
+ || (($obj_flag->requestee_id || 0) != ($old_requestee_id || 0)))
{
- ThrowCodeError('flag_status_invalid',
- { id => $id, status => $status });
+ $obj_flag->_set_setter($params->{setter});
}
- # Make sure the user didn't specify a requestee unless the flag
- # is specifically requestable. For existing flags, if the requestee
- # was set before the flag became specifically unrequestable, don't
- # let the user change the requestee, but let the user remove it by
- # entering an empty string for the requestee.
- if ($status eq '?' && !$flag_type->is_requesteeble) {
- my $old_requestee = ($flag && $flag->requestee) ?
- $flag->requestee->login : '';
- my $new_requestee = join('', @$requestees);
- if ($new_requestee && $new_requestee ne $old_requestee) {
- ThrowCodeError('flag_requestee_disabled',
- { type => $flag_type });
- }
+ # If the flag is deleted, remove it from the list.
+ if ($obj_flag->status eq 'X') {
+ @{$flag_type->{flags}} = grep { $_->id != $obj_flag->id } @{$flag_type->{flags}};
}
-
- # Make sure the user didn't enter multiple requestees for a flag
- # that can't be requested from more than one person at a time.
- if ($status eq '?'
- && !$flag_type->is_multiplicable
- && scalar(@$requestees) > 1)
- {
- ThrowUserError('flag_not_multiplicable', { type => $flag_type });
- }
-
- # Make sure the requestees are authorized to access the bug
- # (and attachment, if this installation is using the "insider group"
- # feature and the attachment is marked private).
- if ($status eq '?' && $flag_type->is_requesteeble) {
- my $old_requestee = ($flag && $flag->requestee) ?
- $flag->requestee->login : '';
-
- my @legal_requestees;
- foreach my $login (@$requestees) {
- if ($login eq $old_requestee) {
- # This requestee was already set. Leave him alone.
- push(@legal_requestees, $login);
- next;
- }
-
- # We know the requestee exists because we ran
- # Bugzilla::User::match_field before getting here.
- my $requestee = new Bugzilla::User({ name => $login });
-
- # Throw an error if the user can't see the bug.
- # Note that if permissions on this bug are changed,
- # can_see_bug() will refer to old settings.
- if (!$requestee->can_see_bug($bug_id)) {
- next if $skip_requestee_on_error;
- ThrowUserError('flag_requestee_unauthorized',
- { flag_type => $flag_type,
- requestee => $requestee,
- bug_id => $bug_id,
- attach_id => $attach_id });
- }
-
- # Throw an error if the target is a private attachment and
- # the requestee isn't in the group of insiders who can see it.
- if ($attach_id
- && $private_attachment
- && Bugzilla->params->{'insidergroup'}
- && !$requestee->in_group(Bugzilla->params->{'insidergroup'}))
- {
- next if $skip_requestee_on_error;
- ThrowUserError('flag_requestee_unauthorized_attachment',
- { flag_type => $flag_type,
- requestee => $requestee,
- bug_id => $bug_id,
- attach_id => $attach_id });
- }
-
- # Throw an error if the user won't be allowed to set the flag.
- if (!$requestee->can_set_flag($flag_type)) {
- next if $skip_requestee_on_error;
- ThrowUserError('flag_requestee_needs_privs',
- {'requestee' => $requestee,
- 'flagtype' => $flag_type});
- }
-
- # This requestee can be set.
- push(@legal_requestees, $login);
- }
-
- # Update the requestee list for this flag.
- if (scalar(@legal_requestees) < scalar(@$requestees)) {
- my $field_name = 'requestee_type-' . $flag_type->id;
- Bugzilla->cgi->delete($field_name);
- Bugzilla->cgi->param(-name => $field_name, -value => \@legal_requestees);
- }
- }
-
- # Make sure the user is authorized to modify flags, see bug 180879
- # - The flag exists and is unchanged.
- return if ($flag && ($status eq $flag->status));
-
- # - User in the request_group can clear pending requests and set flags
- # and can rerequest set flags.
- return if (($status eq 'X' || $status eq '?')
- && $setter->can_request_flag($flag_type));
-
- # - User in the grant_group can set/clear flags, including "+" and "-".
- return if $setter->can_set_flag($flag_type);
-
- # - Any other flag modification is denied
- ThrowUserError('flag_update_denied',
- { name => $flag_type->name,
- status => $status,
- old_status => $flag ? $flag->status : 'X' });
-}
-
-sub snapshot {
- my ($class, $bug_id, $attach_id) = @_;
-
- my $flags = $class->match({ 'bug_id' => $bug_id,
- 'attach_id' => $attach_id });
- my @summaries;
- foreach my $flag (@$flags) {
- my $summary = $flag->type->name . $flag->status;
- $summary .= "(" . $flag->requestee->login . ")" if $flag->requestee;
- push(@summaries, $summary);
- }
- return @summaries;
-}
-
-
-=pod
-
-=over
-
-=item C<process($bug, $attachment, $timestamp, $hr_vars)>
-
-Processes changes to flags.
-
-The bug and/or the attachment objects are the ones this flag is about,
-the timestamp is the date/time the bug was last touched (so that changes
-to the flag can be stamped with the same date/time).
-
-=back
-
-=cut
-
-sub process {
- my ($class, $bug, $attachment, $timestamp, $hr_vars) = @_;
- my $dbh = Bugzilla->dbh;
- my $cgi = Bugzilla->cgi;
-
- # Make sure the bug (and attachment, if given) exists and is accessible
- # to the current user. Moreover, if an attachment object is passed,
- # make sure it belongs to the given bug.
- return if ($bug->error || ($attachment && $bug->bug_id != $attachment->bug_id));
-
- my $bug_id = $bug->bug_id;
- my $attach_id = $attachment ? $attachment->id : undef;
-
- # Use the date/time we were given if possible (allowing calling code
- # to synchronize the comment's timestamp with those of other records).
- $timestamp ||= $dbh->selectrow_array('SELECT NOW()');
-
- # Take a snapshot of flags before any changes.
- my @old_summaries = $class->snapshot($bug_id, $attach_id);
-
- # Cancel pending requests if we are obsoleting an attachment.
- if ($attachment && $cgi->param('isobsolete')) {
- $class->CancelRequests($bug, $attachment);
- }
-
- # Create new flags and update existing flags.
- my $new_flags = FormToNewFlags($bug, $attachment, $cgi, $hr_vars);
- foreach my $flag (@$new_flags) { create($flag, $bug, $attachment, $timestamp) }
- modify($bug, $attachment, $cgi, $timestamp);
-
- # In case the bug's product/component has changed, clear flags that are
- # no longer valid.
- my $flag_ids = $dbh->selectcol_arrayref(
- "SELECT DISTINCT flags.id
- FROM flags
- INNER JOIN bugs
- ON flags.bug_id = bugs.bug_id
- LEFT JOIN flaginclusions AS i
- ON flags.type_id = i.type_id
- AND (bugs.product_id = i.product_id OR i.product_id IS NULL)
- AND (bugs.component_id = i.component_id OR i.component_id IS NULL)
- WHERE bugs.bug_id = ?
- AND i.type_id IS NULL",
- undef, $bug_id);
-
- my $flags = Bugzilla::Flag->new_from_list($flag_ids);
- foreach my $flag (@$flags) {
- my $is_retargetted = retarget($flag, $bug);
- unless ($is_retargetted) {
- clear($flag, $bug, $flag->attachment);
- $hr_vars->{'message'} = 'flag_cleared';
- }
- }
-
- $flag_ids = $dbh->selectcol_arrayref(
- "SELECT DISTINCT flags.id
- FROM flags, bugs, flagexclusions e
- WHERE bugs.bug_id = ?
- AND flags.bug_id = bugs.bug_id
- AND flags.type_id = e.type_id
- AND (bugs.product_id = e.product_id OR e.product_id IS NULL)
- AND (bugs.component_id = e.component_id OR e.component_id IS NULL)",
- undef, $bug_id);
-
- $flags = Bugzilla::Flag->new_from_list($flag_ids);
- foreach my $flag (@$flags) {
- my $is_retargetted = retarget($flag, $bug);
- clear($flag, $bug, $flag->attachment) unless $is_retargetted;
- }
-
- # Take a snapshot of flags after changes.
- my @new_summaries = $class->snapshot($bug_id, $attach_id);
-
- update_activity($bug_id, $attach_id, $timestamp, \@old_summaries, \@new_summaries);
-
- Bugzilla::Hook::process('flag-end_of_update', { bug => $bug,
- timestamp => $timestamp,
- old_flags => \@old_summaries,
- new_flags => \@new_summaries,
- });
-}
-
-sub update_activity {
- my ($bug_id, $attach_id, $timestamp, $old_summaries, $new_summaries) = @_;
- my $dbh = Bugzilla->dbh;
-
- $old_summaries = join(", ", @$old_summaries);
- $new_summaries = join(", ", @$new_summaries);
- my ($removed, $added) = diff_strings($old_summaries, $new_summaries);
- if ($removed ne $added) {
- my $field_id = get_field_id('flagtypes.name');
- $dbh->do('INSERT INTO bugs_activity
- (bug_id, attach_id, who, bug_when, fieldid, removed, added)
- VALUES (?, ?, ?, ?, ?, ?, ?)',
- undef, ($bug_id, $attach_id, Bugzilla->user->id,
- $timestamp, $field_id, $removed, $added));
-
- $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
- undef, ($timestamp, $bug_id));
+ # Add the newly created flag to the list.
+ elsif (!$obj_flag->id) {
+ push(@{$flag_type->{flags}}, $obj_flag);
}
}
@@ -645,7 +408,7 @@
=over
-=item C<create($flag, $bug, $attachment, $timestamp)>
+=item C<create($flag, $timestamp)>
Creates a flag record in the database.
@@ -654,74 +417,393 @@
=cut
sub create {
- my ($flag, $bug, $attachment, $timestamp) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($class, $flag, $timestamp) = @_;
+ $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT NOW()');
- my $attach_id = $attachment ? $attachment->id : undef;
- my $requestee_id;
- # Be careful! At this point, $flag is *NOT* yet an object!
- $requestee_id = $flag->{'requestee'}->id if $flag->{'requestee'};
+ my $params = {};
+ my @columns = grep { $_ ne 'id' } $class->_get_db_columns;
+ $params->{$_} = $flag->{$_} foreach @columns;
- $dbh->do('INSERT INTO flags (type_id, bug_id, attach_id, requestee_id,
- setter_id, status, creation_date, modification_date)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
- undef, ($flag->{'type'}->id, $bug->bug_id,
- $attach_id, $requestee_id, $flag->{'setter'}->id,
- $flag->{'status'}, $timestamp, $timestamp));
+ $params->{creation_date} = $params->{modification_date} = $timestamp;
- # Now that the new flag has been added to the DB, create a real flag object.
- # This is required to call notify() correctly.
- my $flag_id = $dbh->bz_last_key('flags', 'id');
- $flag = new Bugzilla::Flag($flag_id);
-
- # Send an email notifying the relevant parties about the flag creation.
- if ($flag->requestee && $flag->requestee->wants_mail([EVT_FLAG_REQUESTED])) {
- $flag->{'addressee'} = $flag->requestee;
- }
-
- notify($flag, $bug, $attachment);
-
- # Return the new flag object.
+ $flag = $class->SUPER::create($params);
return $flag;
}
+sub update {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $timestamp = shift || $dbh->selectrow_array('SELECT NOW()');
+
+ my $changes = $self->SUPER::update(@_);
+
+ if (scalar(keys %$changes)) {
+ $dbh->do('UPDATE flags SET modification_date = ? WHERE id = ?',
+ undef, ($timestamp, $self->id));
+ }
+ return $changes;
+}
+
+sub snapshot {
+ my ($class, $flags) = @_;
+
+ my @summaries;
+ foreach my $flag (@$flags) {
+ my $summary = $flag->setter->nick . ':' . $flag->type->name . $flag->status;
+ $summary .= "(" . $flag->requestee->login . ")" if $flag->requestee;
+ push(@summaries, $summary);
+ }
+ return @summaries;
+}
+
+sub update_activity {
+ my ($class, $old_summaries, $new_summaries) = @_;
+
+ my ($removed, $added) = diff_arrays($old_summaries, $new_summaries);
+ if (scalar @$removed || scalar @$added) {
+ # Remove flag requester/setter information
+ foreach (@$removed, @$added) { s/^[^:]+:// }
+
+ $removed = join(", ", @$removed);
+ $added = join(", ", @$added);
+ return ($removed, $added);
+ }
+ return ();
+}
+
+sub update_flags {
+ my ($class, $self, $old_self, $timestamp) = @_;
+
+ my @old_summaries = $class->snapshot($old_self->flags);
+ my %old_flags = map { $_->id => $_ } @{$old_self->flags};
+
+ foreach my $new_flag (@{$self->flags}) {
+ if (!$new_flag->id) {
+ # This is a new flag.
+ my $flag = $class->create($new_flag, $timestamp);
+ $new_flag->{id} = $flag->id;
+ $class->notify($new_flag, undef, $self, $timestamp);
+ }
+ else {
+ my $changes = $new_flag->update($timestamp);
+ if (scalar(keys %$changes)) {
+ $class->notify($new_flag, $old_flags{$new_flag->id}, $self, $timestamp);
+ }
+ delete $old_flags{$new_flag->id};
+ }
+ }
+ # These flags have been deleted.
+ foreach my $old_flag (values %old_flags) {
+ $class->notify(undef, $old_flag, $self, $timestamp);
+ $old_flag->remove_from_db();
+ }
+
+ # If the bug has been moved into another product or component,
+ # we must also take care of attachment flags which are no longer valid,
+ # as well as all bug flags which haven't been forgotten above.
+ if ($self->isa('Bugzilla::Bug')
+ && ($self->{_old_product_name} || $self->{_old_component_name}))
+ {
+ my @removed = $class->force_cleanup($self);
+ push(@old_summaries, @removed);
+ }
+
+ my @new_summaries = $class->snapshot($self->flags);
+ my @changes = $class->update_activity(\@old_summaries, \@new_summaries);
+
+ Bugzilla::Hook::process('flag_end_of_update', { object => $self,
+ timestamp => $timestamp,
+ old_flags => \@old_summaries,
+ new_flags => \@new_summaries,
+ });
+ return @changes;
+}
+
+sub retarget {
+ my ($self, $obj) = @_;
+
+ my @flagtypes = grep { $_->name eq $self->type->name } @{$obj->flag_types};
+
+ my $success = 0;
+ foreach my $flagtype (@flagtypes) {
+ next if !$flagtype->is_active;
+ next if (!$flagtype->is_multiplicable && scalar @{$flagtype->{flags}});
+ next unless (($self->status eq '?' && $self->setter->can_request_flag($flagtype))
+ || $self->setter->can_set_flag($flagtype));
+
+ $self->{type_id} = $flagtype->id;
+ delete $self->{type};
+ $success = 1;
+ last;
+ }
+ return $success;
+}
+
+# In case the bug's product/component has changed, clear flags that are
+# no longer valid.
+sub force_cleanup {
+ my ($class, $bug) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $flag_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT flags.id
+ FROM flags
+ INNER JOIN bugs
+ ON flags.bug_id = bugs.bug_id
+ LEFT JOIN flaginclusions AS i
+ ON flags.type_id = i.type_id
+ AND (bugs.product_id = i.product_id OR i.product_id IS NULL)
+ AND (bugs.component_id = i.component_id OR i.component_id IS NULL)
+ WHERE bugs.bug_id = ? AND i.type_id IS NULL',
+ undef, $bug->id);
+
+ my @removed = $class->force_retarget($flag_ids, $bug);
+
+ $flag_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT flags.id
+ FROM flags, bugs, flagexclusions e
+ WHERE bugs.bug_id = ?
+ AND flags.bug_id = bugs.bug_id
+ AND flags.type_id = e.type_id
+ AND (bugs.product_id = e.product_id OR e.product_id IS NULL)
+ AND (bugs.component_id = e.component_id OR e.component_id IS NULL)',
+ undef, $bug->id);
+
+ push(@removed , $class->force_retarget($flag_ids, $bug));
+ return @removed;
+}
+
+sub force_retarget {
+ my ($class, $flag_ids, $bug) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $flags = $class->new_from_list($flag_ids);
+ my @removed;
+ foreach my $flag (@$flags) {
+ # $bug is undefined when e.g. editing inclusion and exclusion lists.
+ my $obj = $flag->attachment || $bug || $flag->bug;
+ my $is_retargetted = $flag->retarget($obj);
+ if ($is_retargetted) {
+ $dbh->do('UPDATE flags SET type_id = ? WHERE id = ?',
+ undef, ($flag->type_id, $flag->id));
+ }
+ else {
+ # Track deleted attachment flags.
+ push(@removed, $class->snapshot([$flag])) if $flag->attach_id;
+ $class->notify(undef, $flag, $bug || $flag->bug);
+ $flag->remove_from_db();
+ }
+ }
+ return @removed;
+}
+
+###############################
+#### Validators ######
+###############################
+
+sub _set_requestee {
+ my ($self, $requestee, $attachment, $skip_requestee_on_error) = @_;
+
+ $self->{requestee} =
+ $self->_check_requestee($requestee, $attachment, $skip_requestee_on_error);
+
+ $self->{requestee_id} =
+ $self->{requestee} ? $self->{requestee}->id : undef;
+}
+
+sub _set_setter {
+ my ($self, $setter) = @_;
+
+ $self->set('setter', $setter);
+ $self->{setter_id} = $self->setter->id;
+}
+
+sub _set_status {
+ my ($self, $status) = @_;
+
+ # Store the old flag status. It's needed by _check_setter().
+ $self->{_old_status} = $self->status;
+ $self->set('status', $status);
+}
+
+sub _check_requestee {
+ my ($self, $requestee, $attachment, $skip_requestee_on_error) = @_;
+
+ # If the flag status is not "?", then no requestee can be defined.
+ return undef if ($self->status ne '?');
+
+ # Store this value before updating the flag object.
+ my $old_requestee = $self->requestee ? $self->requestee->login : '';
+
+ if ($self->status eq '?' && $requestee) {
+ $requestee = Bugzilla::User->check($requestee);
+ }
+ else {
+ undef $requestee;
+ }
+
+ if ($requestee && $requestee->login ne $old_requestee) {
+ # Make sure the user didn't specify a requestee unless the flag
+ # is specifically requestable. For existing flags, if the requestee
+ # was set before the flag became specifically unrequestable, the
+ # user can either remove him or leave him alone.
+ ThrowCodeError('flag_requestee_disabled', { type => $self->type })
+ if !$self->type->is_requesteeble;
+
+ # Make sure the requestee can see the bug.
+ # Note that can_see_bug() will query the DB, so if the bug
+ # is being added/removed from some groups and these changes
+ # haven't been committed to the DB yet, they won't be taken
+ # into account here. In this case, old restrictions matters.
+ if (!$requestee->can_see_bug($self->bug_id)) {
+ if ($skip_requestee_on_error) {
+ undef $requestee;
+ }
+ else {
+ ThrowUserError('flag_requestee_unauthorized',
+ { flag_type => $self->type,
+ requestee => $requestee,
+ bug_id => $self->bug_id,
+ attach_id => $self->attach_id });
+ }
+ }
+ # Make sure the requestee can see the private attachment.
+ elsif ($self->attach_id && $attachment->isprivate && !$requestee->is_insider) {
+ if ($skip_requestee_on_error) {
+ undef $requestee;
+ }
+ else {
+ ThrowUserError('flag_requestee_unauthorized_attachment',
+ { flag_type => $self->type,
+ requestee => $requestee,
+ bug_id => $self->bug_id,
+ attach_id => $self->attach_id });
+ }
+ }
+ # Make sure the user is allowed to set the flag.
+ elsif (!$requestee->can_set_flag($self->type)) {
+ if ($skip_requestee_on_error) {
+ undef $requestee;
+ }
+ else {
+ ThrowUserError('flag_requestee_needs_privs',
+ {'requestee' => $requestee,
+ 'flagtype' => $self->type});
+ }
+ }
+ }
+ return $requestee;
+}
+
+sub _check_setter {
+ my ($self, $setter) = @_;
+
+ # By default, the currently logged in user is the setter.
+ $setter ||= Bugzilla->user;
+ (blessed($setter) && $setter->isa('Bugzilla::User') && $setter->id)
+ || ThrowCodeError('invalid_user');
+
+ # set_status() has already been called. So this refers
+ # to the new flag status.
+ my $status = $self->status;
+
+ # Make sure the user is authorized to modify flags, see bug 180879:
+ # - The flag exists and is unchanged.
+ # - The flag setter can unset flag.
+ # - Users in the request_group can clear pending requests and set flags
+ # and can rerequest set flags.
+ # - Users in the grant_group can set/clear flags, including "+" and "-".
+ unless (($status eq $self->{_old_status})
+ || ($status eq 'X' && $setter->id == Bugzilla->user->id)
+ || (($status eq 'X' || $status eq '?')
+ && $setter->can_request_flag($self->type))
+ || $setter->can_set_flag($self->type))
+ {
+ ThrowUserError('flag_update_denied',
+ { name => $self->type->name,
+ status => $status,
+ old_status => $self->{_old_status} });
+ }
+
+ # If the request is being retargetted, we don't update
+ # the setter, so that the setter gets the notification.
+ if ($status eq '?' && $self->{_old_status} eq '?') {
+ return $self->setter;
+ }
+ return $setter;
+}
+
+sub _check_status {
+ my ($self, $status) = @_;
+
+ # - Make sure the status is valid.
+ # - Make sure the user didn't request the flag unless it's requestable.
+ # If the flag existed and was requested before it became unrequestable,
+ # leave it as is.
+ if (!grep($status eq $_ , qw(X + - ?))
+ || ($status eq '?' && $self->status ne '?' && !$self->type->is_requestable))
+ {
+ ThrowUserError('flag_status_invalid', { id => $self->id,
+ status => $status });
+ }
+ return $status;
+}
+
+######################################################################
+# Utility Functions
+######################################################################
+
=pod
=over
-=item C<modify($bug, $attachment, $cgi, $timestamp)>
+=item C<extract_flags_from_cgi($bug, $attachment, $hr_vars)>
-Modifies flags in the database when a user changes them.
+Checks whether or not there are new flags to create and returns an
+array of hashes. This array is then passed to Flag::create().
=back
=cut
-sub modify {
- my ($bug, $attachment, $cgi, $timestamp) = @_;
- my $setter = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
+sub extract_flags_from_cgi {
+ my ($class, $bug, $attachment, $vars, $skip) = @_;
+ my $cgi = Bugzilla->cgi;
- # Extract a list of flags from the form data.
- my @ids = map(/^flag-(\d+)$/ ? $1 : (), $cgi->param());
+ my $match_status = Bugzilla::User::match_field({
+ '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
+ }, undef, $skip);
- # Loop over flags and update their record in the database if necessary.
- # Two kinds of changes can happen to a flag: it can be set to a different
- # state, and someone else can be asked to set it. We take care of both
- # those changes.
- my @flags;
- foreach my $id (@ids) {
- my $flag = new Bugzilla::Flag($id);
+ $vars->{'match_field'} = 'requestee';
+ if ($match_status == USER_MATCH_FAILED) {
+ $vars->{'message'} = 'user_match_failed';
+ }
+ elsif ($match_status == USER_MATCH_MULTIPLE) {
+ $vars->{'message'} = 'user_match_multiple';
+ }
+
+ # Extract a list of flag type IDs from field names.
+ my @flagtype_ids = map(/^flag_type-(\d+)$/ ? $1 : (), $cgi->param());
+ @flagtype_ids = grep($cgi->param("flag_type-$_") ne 'X', @flagtype_ids);
+
+ # Extract a list of existing flag IDs.
+ my @flag_ids = map(/^flag-(\d+)$/ ? $1 : (), $cgi->param());
+
+ return () if (!scalar(@flagtype_ids) && !scalar(@flag_ids));
+
+ my (@new_flags, @flags);
+ foreach my $flag_id (@flag_ids) {
+ my $flag = $class->new($flag_id);
# If the flag no longer exists, ignore it.
next unless $flag;
- my $status = $cgi->param("flag-$id");
+ my $status = $cgi->param("flag-$flag_id");
# If the user entered more than one name into the requestee field
# (i.e. they want more than one person to set the flag) we can reuse
# the existing flag for the first person (who may well be the existing
- # requestee), but we have to create new flags for each additional.
- my @requestees = $cgi->param("requestee-$id");
+ # requestee), but we have to create new flags for each additional requestee.
+ my @requestees = $cgi->param("requestee-$flag_id");
my $requestee_email;
if ($status eq "?"
&& scalar(@requestees) > 1
@@ -732,282 +814,39 @@
# Create new flags like the existing one for each additional person.
foreach my $login (@requestees) {
- create({ type => $flag->type,
- setter => $setter,
- status => "?",
- requestee => new Bugzilla::User({ name => $login }) },
- $bug, $attachment, $timestamp);
+ push(@new_flags, { type_id => $flag->type_id,
+ status => "?",
+ requestee => $login,
+ skip_roe => $skip });
}
}
- else {
- $requestee_email = trim($cgi->param("requestee-$id") || '');
+ elsif ($status eq "?" && scalar(@requestees)) {
+ # If there are several requestees and the flag type is not multiplicable,
+ # this will fail. But that's the job of the validator to complain. All
+ # we do here is to extract and convert data from the CGI.
+ $requestee_email = trim($cgi->param("requestee-$flag_id") || '');
}
- # Ignore flags the user didn't change. There are two components here:
- # either the status changes (trivial) or the requestee changes.
- # Change of either field will cause full update of the flag.
-
- my $status_changed = ($status ne $flag->status);
-
- # Requestee is considered changed, if all of the following apply:
- # 1. Flag status is '?' (requested)
- # 2. Flag can have a requestee
- # 3. The requestee specified on the form is different from the
- # requestee specified in the db.
-
- my $old_requestee = $flag->requestee ? $flag->requestee->login : '';
-
- my $requestee_changed =
- ($status eq "?" &&
- $flag->type->is_requesteeble &&
- $old_requestee ne $requestee_email);
-
- next unless ($status_changed || $requestee_changed);
-
- # Since the status is validated, we know it's safe, but it's still
- # tainted, so we have to detaint it before using it in a query.
- trick_taint($status);
-
- if ($status eq '+' || $status eq '-') {
- $dbh->do('UPDATE flags
- SET setter_id = ?, requestee_id = NULL,
- status = ?, modification_date = ?
- WHERE id = ?',
- undef, ($setter->id, $status, $timestamp, $flag->id));
-
- # If the status of the flag was "?", we have to notify
- # the requester (if he wants to).
- my $requester;
- if ($flag->status eq '?') {
- $requester = $flag->setter;
- $flag->{'requester'} = $requester;
- }
- # Now update the flag object with its new values.
- $flag->{'setter'} = $setter;
- $flag->{'requestee'} = undef;
- $flag->{'requestee_id'} = undef;
- $flag->{'status'} = $status;
-
- # Send an email notifying the relevant parties about the fulfillment,
- # including the requester.
- if ($requester && $requester->wants_mail([EVT_REQUESTED_FLAG])) {
- $flag->{'addressee'} = $requester;
- }
-
- notify($flag, $bug, $attachment);
- }
- elsif ($status eq '?') {
- # If the one doing the change is the requestee, then this means he doesn't
- # want to reply to the request and he simply reassigns the request to
- # someone else. In this case, we keep the requester unaltered.
- my $new_setter = $setter;
- if ($flag->requestee && $flag->requestee->id == $setter->id) {
- $new_setter = $flag->setter;
- }
-
- # Get the requestee, if any.
- my $requestee_id;
- if ($requestee_email) {
- $requestee_id = login_to_id($requestee_email);
- $flag->{'requestee'} = new Bugzilla::User($requestee_id);
- $flag->{'requestee_id'} = $requestee_id;
- }
- else {
- # If the status didn't change but we only removed the
- # requestee, we have to clear the requestee field.
- $flag->{'requestee'} = undef;
- $flag->{'requestee_id'} = undef;
- }
-
- # Update the database with the changes.
- $dbh->do('UPDATE flags
- SET setter_id = ?, requestee_id = ?,
- status = ?, modification_date = ?
- WHERE id = ?',
- undef, ($new_setter->id, $requestee_id, $status,
- $timestamp, $flag->id));
-
- # Now update the flag object with its new values.
- $flag->{'setter'} = $new_setter;
- $flag->{'status'} = $status;
-
- # Send an email notifying the relevant parties about the request.
- if ($flag->requestee && $flag->requestee->wants_mail([EVT_FLAG_REQUESTED])) {
- $flag->{'addressee'} = $flag->requestee;
- }
-
- notify($flag, $bug, $attachment);
- }
- elsif ($status eq 'X') {
- clear($flag, $bug, $attachment);
- }
-
- push(@flags, $flag);
+ push(@flags, { id => $flag_id,
+ status => $status,
+ requestee => $requestee_email,
+ skip_roe => $skip });
}
- return \@flags;
-}
-
-=pod
-
-=over
-
-=item C<retarget($flag, $bug)>
-
-Change the type of the flag, if possible. The new flag type must have
-the same name as the current flag type, must exist in the product and
-component the bug is in, and the current settings of the flag must pass
-validation. If no such flag type can be found, the type remains unchanged.
-
-Retargetting flags is a good way to keep flags when moving bugs from one
-product where a flag type is available to another product where the flag
-type is unavailable, but another flag type having the same name exists.
-Most of the time, if they have the same name, this means that they have
-the same meaning, but with different settings.
-
-=back
-
-=cut
-
-sub retarget {
- my ($flag, $bug) = @_;
- my $dbh = Bugzilla->dbh;
-
- # We are looking for flagtypes having the same name as the flagtype
- # to which the current flag belongs, and being in the new product and
- # component of the bug.
- my $flagtypes = Bugzilla::FlagType::match(
- {'name' => $flag->name,
- 'target_type' => $flag->type->target_type,
- 'is_active' => 1,
- 'product_id' => $bug->product_id,
- 'component_id' => $bug->component_id});
-
- # If we found no flagtype, the flag will be deleted.
- return 0 unless scalar(@$flagtypes);
-
- # If we found at least one, change the type of the flag,
- # assuming the setter/requester is allowed to set/request flags
- # belonging to this flagtype.
- my $requestee = $flag->requestee ? [$flag->requestee->login] : [];
- my $is_private = ($flag->attachment) ? $flag->attachment->isprivate : 0;
- my $is_retargetted = 0;
-
- foreach my $flagtype (@$flagtypes) {
- # Get the number of flags of this type already set for this target.
- my $has_flags = __PACKAGE__->count(
- { 'type_id' => $flagtype->id,
- 'bug_id' => $bug->bug_id,
- 'attach_id' => $flag->attach_id });
-
- # Do not create a new flag of this type if this flag type is
- # not multiplicable and already has a flag set.
- next if (!$flagtype->is_multiplicable && $has_flags);
-
- # Check user privileges.
- my $error_mode_cache = Bugzilla->error_mode;
- Bugzilla->error_mode(ERROR_MODE_DIE);
- eval {
- _validate(undef, $flagtype, $flag->status, $flag->setter,
- $requestee, $is_private, $bug->bug_id, $flag->attach_id);
- };
- Bugzilla->error_mode($error_mode_cache);
- # If the validation failed, then we cannot use this flagtype.
- next if ($@);
-
- # Checks are successful, we can retarget the flag to this flagtype.
- $dbh->do('UPDATE flags SET type_id = ? WHERE id = ?',
- undef, ($flagtype->id, $flag->id));
-
- $is_retargetted = 1;
- last;
- }
- return $is_retargetted;
-}
-
-=pod
-
-=over
-
-=item C<clear($flag, $bug, $attachment)>
-
-Remove a flag from the DB.
-
-=back
-
-=cut
-
-sub clear {
- my ($flag, $bug, $attachment) = @_;
- my $dbh = Bugzilla->dbh;
-
- $dbh->do('DELETE FROM flags WHERE id = ?', undef, $flag->id);
-
- # If we cancel a pending request, we have to notify the requester
- # (if he wants to).
- my $requester;
- if ($flag->status eq '?') {
- $requester = $flag->setter;
- $flag->{'requester'} = $requester;
- }
-
- # Now update the flag object to its new values. The last
- # requester/setter and requestee are kept untouched (for the
- # record). Else we could as well delete the flag completely.
- $flag->{'exists'} = 0;
- $flag->{'status'} = "X";
-
- if ($requester && $requester->wants_mail([EVT_REQUESTED_FLAG])) {
- $flag->{'addressee'} = $requester;
- }
-
- notify($flag, $bug, $attachment);
-}
-
-
-######################################################################
-# Utility Functions
-######################################################################
-
-=pod
-
-=over
-
-=item C<FormToNewFlags($bug, $attachment, $cgi, $hr_vars)>
-
-Checks whether or not there are new flags to create and returns an
-array of flag objects. This array is then passed to Flag::create().
-
-=back
-
-=cut
-
-sub FormToNewFlags {
- my ($bug, $attachment, $cgi, $hr_vars) = @_;
- my $dbh = Bugzilla->dbh;
- my $setter = Bugzilla->user;
-
- # Extract a list of flag type IDs from field names.
- my @type_ids = map(/^flag_type-(\d+)$/ ? $1 : (), $cgi->param());
- @type_ids = grep($cgi->param("flag_type-$_") ne 'X', @type_ids);
-
- return () unless scalar(@type_ids);
-
# Get a list of active flag types available for this product/component.
my $flag_types = Bugzilla::FlagType::match(
{ 'product_id' => $bug->{'product_id'},
'component_id' => $bug->{'component_id'},
'is_active' => 1 });
- foreach my $type_id (@type_ids) {
+ foreach my $flagtype_id (@flagtype_ids) {
# Checks if there are unexpected flags for the product/component.
- if (!scalar(grep { $_->id == $type_id } @$flag_types)) {
- $hr_vars->{'message'} = 'unexpected_flag_types';
+ if (!scalar(grep { $_->id == $flagtype_id } @$flag_types)) {
+ $vars->{'message'} = 'unexpected_flag_types';
last;
}
}
- my @flags;
foreach my $flag_type (@$flag_types) {
my $type_id = $flag_type->id;
@@ -1016,10 +855,10 @@
next unless ($flag_type->target_type eq 'bug' xor $attachment);
# We are only interested in flags the user tries to create.
- next unless scalar(grep { $_ == $type_id } @type_ids);
+ next unless scalar(grep { $_ == $type_id } @flagtype_ids);
# Get the number of flags of this type already set for this target.
- my $has_flags = __PACKAGE__->count(
+ my $has_flags = $class->count(
{ 'type_id' => $type_id,
'target_type' => $attachment ? 'attachment' : 'bug',
'bug_id' => $bug->bug_id,
@@ -1033,32 +872,30 @@
trick_taint($status);
my @logins = $cgi->param("requestee_type-$type_id");
- if ($status eq "?" && scalar(@logins) > 0) {
+ if ($status eq "?" && scalar(@logins)) {
foreach my $login (@logins) {
- push (@flags, { type => $flag_type ,
- setter => $setter ,
- status => $status ,
- requestee =>
- new Bugzilla::User({ name => $login }) });
+ push (@new_flags, { type_id => $type_id,
+ status => $status,
+ requestee => $login,
+ skip_roe => $skip });
last unless $flag_type->is_multiplicable;
}
}
else {
- push (@flags, { type => $flag_type ,
- setter => $setter ,
- status => $status });
+ push (@new_flags, { type_id => $type_id,
+ status => $status });
}
}
- # Return the list of flags.
- return \@flags;
+ # Return the list of flags to update and/or to create.
+ return (\@flags, \@new_flags);
}
=pod
=over
-=item C<notify($flag, $bug, $attachment)>
+=item C<notify($flag, $old_flag, $object, $timestamp)>
Sends an email notification about a flag being created, fulfilled
or deleted.
@@ -1068,7 +905,37 @@
=cut
sub notify {
- my ($flag, $bug, $attachment) = @_;
+ my ($class, $flag, $old_flag, $obj, $timestamp) = @_;
+
+ my ($bug, $attachment);
+ if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) {
+ $attachment = $obj;
+ $bug = $attachment->bug;
+ }
+ elsif (blessed($obj) && $obj->isa('Bugzilla::Bug')) {
+ $bug = $obj;
+ }
+ else {
+ # Not a good time to throw an error.
+ return;
+ }
+
+ my $addressee;
+ # If the flag is set to '?', maybe the requestee wants a notification.
+ if ($flag && $flag->requestee_id
+ && (!$old_flag || ($old_flag->requestee_id || 0) != $flag->requestee_id))
+ {
+ if ($flag->requestee->wants_mail([EVT_FLAG_REQUESTED])) {
+ $addressee = $flag->requestee;
+ }
+ }
+ elsif ($old_flag && $old_flag->status eq '?'
+ && (!$flag || $flag->status ne '?'))
+ {
+ if ($old_flag->setter->wants_mail([EVT_REQUESTED_FLAG])) {
+ $addressee = $old_flag->setter;
+ }
+ }
#if WEBKIT_CHANGES
# Don't send a notification when the flag is in-rietveld,
@@ -1076,8 +943,14 @@
return if ($flag->type->name eq 'in-rietveld');
#endif // WEBKIT_CHANGES
- # There is nobody to notify.
- return unless ($flag->{'addressee'} || $flag->type->cc_list);
+ my $cc_list = $flag ? $flag->type->cc_list : $old_flag->type->cc_list;
+ # Is there someone to notify?
+ return unless ($addressee || $cc_list);
+
+ # The email client will display the Date: header in the desired timezone,
+ # so we can always use UTC here.
+ $timestamp ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $timestamp = format_time($timestamp, '%a, %d %b %Y %T %z', 'UTC');
# If the target bug is restricted to one or more groups, then we need
# to make sure we don't send email about it to unauthorized users
@@ -1087,7 +960,7 @@
my $attachment_is_private = $attachment ? $attachment->isprivate : undef;
my %recipients;
- foreach my $cc (split(/[, ]+/, $flag->type->cc_list)) {
+ foreach my $cc (split(/[, ]+/, $cc_list)) {
my $ccuser = new Bugzilla::User({ name => $cc });
next if (scalar(@bug_in_groups) && (!$ccuser || !$ccuser->can_see_bug($bug->bug_id)));
next if $attachment_is_private && (!$ccuser || !$ccuser->is_insider);
@@ -1097,72 +970,80 @@
}
# Only notify if the addressee is allowed to receive the email.
- if ($flag->{'addressee'} && $flag->{'addressee'}->email_enabled) {
- $recipients{$flag->{'addressee'}->email} = $flag->{'addressee'};
+ if ($addressee && $addressee->email_enabled) {
+ $recipients{$addressee->email} = $addressee;
}
# Process and send notification for each recipient.
# If there are users in the CC list who don't have an account,
# use the default language for email notifications.
my $default_lang;
if (grep { !$_ } values %recipients) {
- my $default_user = new Bugzilla::User();
- $default_lang = $default_user->settings->{'lang'}->{'value'};
+ $default_lang = Bugzilla::User->new()->setting('lang');
}
foreach my $to (keys %recipients) {
# Add threadingmarker to allow flag notification emails to be the
# threaded similar to normal bug change emails.
- my $user_id = $recipients{$to} ? $recipients{$to}->id : 0;
- my $threadingmarker = build_thread_marker($bug->id, $user_id);
-
+ my $thread_user_id = $recipients{$to} ? $recipients{$to}->id : 0;
+
my $vars = { 'flag' => $flag,
+ 'old_flag' => $old_flag,
'to' => $to,
+ 'date' => $timestamp,
'bug' => $bug,
'attachment' => $attachment,
- 'threadingmarker' => $threadingmarker };
+ 'threadingmarker' => build_thread_marker($bug->id, $thread_user_id) };
my $lang = $recipients{$to} ?
- $recipients{$to}->settings->{'lang'}->{'value'} : $default_lang;
+ $recipients{$to}->setting('lang') : $default_lang;
my $template = Bugzilla->template_inner($lang);
my $message;
$template->process("request/email.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
- Bugzilla->template_inner("");
MessageToMTA($message);
}
}
-# Cancel all request flags from the attachment being obsoleted.
-sub CancelRequests {
- my ($class, $bug, $attachment, $timestamp) = @_;
- my $dbh = Bugzilla->dbh;
+# This is an internal function used by $bug->flag_types
+# and $attachment->flag_types to collect data about available
+# flag types and existing flags set on them. You should never
+# call this function directly.
+sub _flag_types {
+ my ($class, $vars) = @_;
- my $request_ids =
- $dbh->selectcol_arrayref("SELECT flags.id
- FROM flags
- LEFT JOIN attachments ON flags.attach_id = attachments.attach_id
- WHERE flags.attach_id = ?
- AND flags.status = '?'
- AND attachments.isobsolete = 0",
- undef, $attachment->id);
+ my $target_type = $vars->{target_type};
+ my $flags;
- return if (!scalar(@$request_ids));
+ # Retrieve all existing flags for this bug/attachment.
+ if ($target_type eq 'bug') {
+ my $bug_id = delete $vars->{bug_id};
+ $flags = $class->match({target_type => 'bug', bug_id => $bug_id});
+ }
+ elsif ($target_type eq 'attachment') {
+ my $attach_id = delete $vars->{attach_id};
+ $flags = $class->match({attach_id => $attach_id});
+ }
+ else {
+ ThrowCodeError('bad_arg', {argument => 'target_type',
+ function => $class . '->_flag_types'});
+ }
- # Take a snapshot of flags before any changes.
- my @old_summaries = $class->snapshot($bug->bug_id, $attachment->id)
- if ($timestamp);
- my $flags = Bugzilla::Flag->new_from_list($request_ids);
- foreach my $flag (@$flags) { clear($flag, $bug, $attachment) }
+ # Get all available flag types for the given product and component.
+ my $cache = Bugzilla->request_cache->{flag_types_per_component}->{$vars->{target_type}} ||= {};
+ my $flag_data = $cache->{$vars->{component_id}} ||= Bugzilla::FlagType::match($vars);
+ my $flag_types = dclone($flag_data);
- # If $timestamp is undefined, do not update the activity table
- return unless ($timestamp);
+ $_->{flags} = [] foreach @$flag_types;
+ my %flagtypes = map { $_->id => $_ } @$flag_types;
- # Take a snapshot of flags after any changes.
- my @new_summaries = $class->snapshot($bug->bug_id, $attachment->id);
- update_activity($bug->bug_id, $attachment->id, $timestamp,
- \@old_summaries, \@new_summaries);
+ # Group existing flags per type, and skip those becoming invalid
+ # (which can happen when a bug is being moved into a new product
+ # or component).
+ @$flags = grep { exists $flagtypes{$_->type_id} } @$flags;
+ push(@{$flagtypes{$_->type_id}->{flags}}, $_) foreach @$flags;
+ return $flag_types;
}
=head1 SEE ALSO
diff --git a/Websites/bugs.webkit.org/Bugzilla/FlagType.pm b/Websites/bugs.webkit.org/Bugzilla/FlagType.pm
index 2892a83..b30065a 100644
--- a/Websites/bugs.webkit.org/Bugzilla/FlagType.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/FlagType.pm
@@ -48,7 +48,7 @@
=cut
-use Bugzilla::User;
+use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Group;
@@ -59,57 +59,151 @@
#### Initialization ####
###############################
-=begin private
-
-=head1 PRIVATE VARIABLES/CONSTANTS
-
-=over
-
-=item C<DB_COLUMNS>
-
-basic sets of columns and tables for getting flag types from the
-database.
-
-=back
-
-=cut
+use constant DB_TABLE => 'flagtypes';
+use constant LIST_ORDER => 'sortkey, name';
use constant DB_COLUMNS => qw(
- flagtypes.id
- flagtypes.name
- flagtypes.description
- flagtypes.cc_list
- flagtypes.target_type
- flagtypes.sortkey
- flagtypes.is_active
- flagtypes.is_requestable
- flagtypes.is_requesteeble
- flagtypes.is_multiplicable
- flagtypes.grant_group_id
- flagtypes.request_group_id
+ id
+ name
+ description
+ cc_list
+ target_type
+ sortkey
+ is_active
+ is_requestable
+ is_requesteeble
+ is_multiplicable
+ grant_group_id
+ request_group_id
);
-=pod
+use constant UPDATE_COLUMNS => qw(
+ name
+ description
+ cc_list
+ sortkey
+ is_active
+ is_requestable
+ is_requesteeble
+ is_multiplicable
+ grant_group_id
+ request_group_id
+);
-=over
+use constant VALIDATORS => {
+ name => \&_check_name,
+ description => \&_check_description,
+ cc_list => \&_check_cc_list,
+ target_type => \&_check_target_type,
+ sortkey => \&_check_sortey,
+ is_active => \&Bugzilla::Object::check_boolean,
+ is_requestable => \&Bugzilla::Object::check_boolean,
+ is_requesteeble => \&Bugzilla::Object::check_boolean,
+ is_multiplicable => \&Bugzilla::Object::check_boolean,
+ grant_group => \&_check_group,
+ request_group => \&_check_group,
+};
-=item C<DB_TABLE>
+use constant UPDATE_VALIDATORS => {
+ grant_group_id => \&_check_group,
+ request_group_id => \&_check_group,
+};
+###############################
-Which database(s) is the data coming from?
+sub create {
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
-Note: when adding tables to DB_TABLE, make sure to include the separator
-(i.e. words like "LEFT OUTER JOIN") before the table name, since tables take
-multiple separators based on the join type, and therefore it is not possible
-to join them later using a single known separator.
+ $dbh->bz_start_transaction();
-=back
+ $class->check_required_create_fields(@_);
+ my $params = $class->run_create_validators(@_);
+ # In the DB, only the first character of the target type is stored.
+ $params->{target_type} = substr($params->{target_type}, 0, 1);
-=end private
+ # Extract everything which is not a valid column name.
+ $params->{grant_group_id} = delete $params->{grant_group};
+ $params->{request_group_id} = delete $params->{request_group};
+ my $inclusions = delete $params->{inclusions};
+ my $exclusions = delete $params->{exclusions};
-=cut
+ my $flagtype = $class->insert_create_data($params);
-use constant DB_TABLE => 'flagtypes';
-use constant LIST_ORDER => 'flagtypes.sortkey, flagtypes.name';
+ $flagtype->set_clusions({ inclusions => $inclusions,
+ exclusions => $exclusions });
+ $flagtype->update();
+
+ $dbh->bz_commit_transaction();
+ return $flagtype;
+}
+
+sub update {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $flag_id = $self->id;
+
+ $dbh->bz_start_transaction();
+ my $changes = $self->SUPER::update(@_);
+
+ # Update the flaginclusions and flagexclusions tables.
+ foreach my $category ('inclusions', 'exclusions') {
+ next unless delete $self->{"_update_$category"};
+
+ $dbh->do("DELETE FROM flag$category WHERE type_id = ?", undef, $flag_id);
+
+ my $sth = $dbh->prepare("INSERT INTO flag$category
+ (type_id, product_id, component_id) VALUES (?, ?, ?)");
+
+ foreach my $prod_comp (values %{$self->{$category}}) {
+ my ($prod_id, $comp_id) = split(':', $prod_comp);
+ $prod_id ||= undef;
+ $comp_id ||= undef;
+ $sth->execute($flag_id, $prod_id, $comp_id);
+ }
+ $changes->{$category} = [0, 1];
+ }
+
+ # Clear existing flags for bugs/attachments in categories no longer on
+ # the list of inclusions or that have been added to the list of exclusions.
+ my $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
+ FROM flags
+ INNER JOIN bugs
+ ON flags.bug_id = bugs.bug_id
+ LEFT JOIN flaginclusions AS i
+ ON (flags.type_id = i.type_id
+ AND (bugs.product_id = i.product_id
+ OR i.product_id IS NULL)
+ AND (bugs.component_id = i.component_id
+ OR i.component_id IS NULL))
+ WHERE flags.type_id = ?
+ AND i.type_id IS NULL',
+ undef, $self->id);
+ Bugzilla::Flag->force_retarget($flag_ids);
+
+ $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
+ FROM flags
+ INNER JOIN bugs
+ ON flags.bug_id = bugs.bug_id
+ INNER JOIN flagexclusions AS e
+ ON flags.type_id = e.type_id
+ WHERE flags.type_id = ?
+ AND (bugs.product_id = e.product_id
+ OR e.product_id IS NULL)
+ AND (bugs.component_id = e.component_id
+ OR e.component_id IS NULL)',
+ undef, $self->id);
+ Bugzilla::Flag->force_retarget($flag_ids);
+
+ # Silently remove requestees from flags which are no longer
+ # specifically requestable.
+ if (!$self->is_requesteeble) {
+ $dbh->do('UPDATE flags SET requestee_id = NULL WHERE type_id = ?',
+ undef, $self->id);
+ }
+
+ $dbh->bz_commit_transaction();
+ return $changes;
+}
###############################
#### Accessors ######
@@ -180,10 +274,151 @@
sub request_group_id { return $_[0]->{'request_group_id'}; }
sub grant_group_id { return $_[0]->{'grant_group_id'}; }
+################################
+# Validators
+################################
+
+sub _check_name {
+ my ($invocant, $name) = @_;
+
+ $name = trim($name);
+ ($name && $name !~ /[\s,]/ && length($name) <= 50)
+ || ThrowUserError('flag_type_name_invalid', { name => $name });
+ return $name;
+}
+
+sub _check_description {
+ my ($invocant, $desc) = @_;
+
+ $desc = trim($desc);
+ $desc || ThrowUserError('flag_type_description_invalid');
+ return $desc;
+}
+
+sub _check_cc_list {
+ my ($invocant, $cc_list) = @_;
+
+ length($cc_list) <= 200
+ || ThrowUserError('flag_type_cc_list_invalid', { cc_list => $cc_list });
+
+ my @addresses = split(/[,\s]+/, $cc_list);
+ # We do not call Util::validate_email_syntax because these
+ # addresses do not require to match 'emailregexp' and do not
+ # depend on 'emailsuffix'. So we limit ourselves to a simple
+ # sanity check:
+ # - match the syntax of a fully qualified email address;
+ # - do not contain any illegal character.
+ foreach my $address (@addresses) {
+ ($address =~ /^[\w\.\+\-=]+@[\w\.\-]+\.[\w\-]+$/
+ && $address !~ /[\\\(\)<>&,;:"\[\] \t\r\n\P{ASCII}]/)
+ || ThrowUserError('illegal_email_address',
+ {addr => $address, default => 1});
+ }
+ return $cc_list;
+}
+
+sub _check_target_type {
+ my ($invocant, $target_type) = @_;
+
+ ($target_type eq 'bug' || $target_type eq 'attachment')
+ || ThrowCodeError('flag_type_target_type_invalid', { target_type => $target_type });
+ return $target_type;
+}
+
+sub _check_sortey {
+ my ($invocant, $sortkey) = @_;
+
+ (detaint_natural($sortkey) && $sortkey <= MAX_SMALLINT)
+ || ThrowUserError('flag_type_sortkey_invalid', { sortkey => $sortkey });
+ return $sortkey;
+}
+
+sub _check_group {
+ my ($invocant, $group) = @_;
+ return unless $group;
+
+ trick_taint($group);
+ $group = Bugzilla::Group->check($group);
+ return $group->id;
+}
+
###############################
#### Methods ####
###############################
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_cc_list { $_[0]->set('cc_list', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+sub set_is_active { $_[0]->set('is_active', $_[1]); }
+sub set_is_requestable { $_[0]->set('is_requestable', $_[1]); }
+sub set_is_specifically_requestable { $_[0]->set('is_requesteeble', $_[1]); }
+sub set_is_multiplicable { $_[0]->set('is_multiplicable', $_[1]); }
+sub set_grant_group { $_[0]->set('grant_group_id', $_[1]); }
+sub set_request_group { $_[0]->set('request_group_id', $_[1]); }
+
+sub set_clusions {
+ my ($self, $list) = @_;
+ my $user = Bugzilla->user;
+ my %products;
+ my $params = {};
+
+ # If the user has editcomponents privs, then we only need to make sure
+ # that the product exists.
+ if ($user->in_group('editcomponents')) {
+ $params->{allow_inaccessible} = 1;
+ }
+
+ foreach my $category (keys %$list) {
+ my %clusions;
+ my %clusions_as_hash;
+
+ foreach my $prod_comp (@{$list->{$category} || []}) {
+ my ($prod_id, $comp_id) = split(':', $prod_comp);
+ my $prod_name = '__Any__';
+ my $comp_name = '__Any__';
+ # Does the product exist?
+ if ($prod_id) {
+ detaint_natural($prod_id)
+ || ThrowCodeError('param_must_be_numeric',
+ { function => 'Bugzilla::FlagType::set_clusions' });
+
+ if (!$products{$prod_id}) {
+ $params->{id} = $prod_id;
+ $products{$prod_id} = Bugzilla::Product->check($params);
+ $user->in_group('editcomponents', $prod_id)
+ || ThrowUserError('product_access_denied', $params);
+ }
+ $prod_name = $products{$prod_id}->name;
+
+ # Does the component belong to this product?
+ if ($comp_id) {
+ detaint_natural($comp_id)
+ || ThrowCodeError('param_must_be_numeric',
+ { function => 'Bugzilla::FlagType::set_clusions' });
+
+ my ($component) = grep { $_->id == $comp_id } @{$products{$prod_id}->components}
+ or ThrowUserError('product_unknown_component',
+ { product => $prod_name, comp_id => $comp_id });
+ $comp_name = $component->name;
+ }
+ else {
+ $comp_id = 0;
+ }
+ }
+ else {
+ $prod_id = 0;
+ $comp_id = 0;
+ }
+ $clusions{"$prod_name:$comp_name"} = "$prod_id:$comp_id";
+ $clusions_as_hash{$prod_id}->{$comp_id} = 1;
+ }
+ $self->{$category} = \%clusions;
+ $self->{"${category}_as_hash"} = \%clusions_as_hash;
+ $self->{"_update_$category"} = 1;
+ }
+}
+
=pod
=over
@@ -223,6 +458,7 @@
sub grant_list {
my $self = shift;
+ require Bugzilla::User;
my @custusers;
my @allusers = @{Bugzilla->user->get_userlist};
foreach my $user (@allusers) {
@@ -264,15 +500,33 @@
sub inclusions {
my $self = shift;
- $self->{'inclusions'} ||= get_clusions($self->id, 'in');
- return $self->{'inclusions'};
+ if (!defined $self->{inclusions}) {
+ ($self->{inclusions}, $self->{inclusions_as_hash}) = get_clusions($self->id, 'in');
+ }
+ return $self->{inclusions};
+}
+
+sub inclusions_as_hash {
+ my $self = shift;
+
+ $self->inclusions unless defined $self->{inclusions_as_hash};
+ return $self->{inclusions_as_hash};
}
sub exclusions {
my $self = shift;
- $self->{'exclusions'} ||= get_clusions($self->id, 'ex');
- return $self->{'exclusions'};
+ if (!defined $self->{exclusions}) {
+ ($self->{exclusions}, $self->{exclusions_as_hash}) = get_clusions($self->id, 'ex');
+ }
+ return $self->{exclusions};
+}
+
+sub exclusions_as_hash {
+ my $self = shift;
+
+ $self->exclusions unless defined $self->{exclusions_as_hash};
+ return $self->{exclusions_as_hash};
}
######################################################################
@@ -300,17 +554,18 @@
my $dbh = Bugzilla->dbh;
my $list =
- $dbh->selectall_arrayref("SELECT products.id, products.name, " .
- " components.id, components.name " .
- "FROM flagtypes, flag${type}clusions " .
- "LEFT OUTER JOIN products " .
- " ON flag${type}clusions.product_id = products.id " .
- "LEFT OUTER JOIN components " .
- " ON flag${type}clusions.component_id = components.id " .
- "WHERE flagtypes.id = ? " .
- " AND flag${type}clusions.type_id = flagtypes.id",
+ $dbh->selectall_arrayref("SELECT products.id, products.name,
+ components.id, components.name
+ FROM flagtypes
+ INNER JOIN flag${type}clusions
+ ON flag${type}clusions.type_id = flagtypes.id
+ LEFT JOIN products
+ ON flag${type}clusions.product_id = products.id
+ LEFT JOIN components
+ ON flag${type}clusions.component_id = components.id
+ WHERE flagtypes.id = ?",
undef, $id);
- my %clusions;
+ my (%clusions, %clusions_as_hash);
foreach my $data (@$list) {
my ($product_id, $product_name, $component_id, $component_name) = @$data;
$product_id ||= 0;
@@ -318,8 +573,9 @@
$component_id ||= 0;
$component_name ||= "__Any__";
$clusions{"$product_name:$component_name"} = "$product_id:$component_id";
+ $clusions_as_hash{$product_id}->{$component_id} = 1;
}
- return \%clusions;
+ return (\%clusions, \%clusions_as_hash);
}
=pod
@@ -423,15 +679,13 @@
my $is_active = $criteria->{is_active} ? "1" : "0";
push(@criteria, "flagtypes.is_active = $is_active");
}
- if ($criteria->{product_id} && $criteria->{'component_id'}) {
+ if ($criteria->{product_id}) {
my $product_id = $criteria->{product_id};
- my $component_id = $criteria->{component_id};
# Add inclusions to the query, which simply involves joining the table
# by flag type ID and target product/component.
push(@$tables, "INNER JOIN flaginclusions AS i ON flagtypes.id = i.type_id");
push(@criteria, "(i.product_id = $product_id OR i.product_id IS NULL)");
- push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
# Add exclusions to the query, which is more complicated. First of all,
# we do a LEFT JOIN so we don't miss flag types with no exclusions.
@@ -439,9 +693,19 @@
# component. However, since we want flag types that *aren't* on the
# exclusions list, we add a WHERE criteria to use only records with
# NULL exclusion type, i.e. without any exclusions.
- my $join_clause = "flagtypes.id = e.type_id " .
- "AND (e.product_id = $product_id OR e.product_id IS NULL) " .
- "AND (e.component_id = $component_id OR e.component_id IS NULL)";
+ my $join_clause = "flagtypes.id = e.type_id ";
+
+ my $addl_join_clause = "";
+ if ($criteria->{component_id}) {
+ my $component_id = $criteria->{component_id};
+ push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
+ $join_clause .= "AND (e.component_id = $component_id OR e.component_id IS NULL) ";
+ }
+ else {
+ $addl_join_clause = "AND e.component_id IS NULL OR (i.component_id = e.component_id) ";
+ }
+ $join_clause .= "AND ((e.product_id = $product_id $addl_join_clause) OR e.product_id IS NULL)";
+
push(@$tables, "LEFT JOIN flagexclusions AS e ON ($join_clause)");
push(@criteria, "e.type_id IS NULL");
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Group.pm b/Websites/bugs.webkit.org/Bugzilla/Group.pm
index d9f49c0..b7532fe 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Group.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Group.pm
@@ -60,8 +60,6 @@
icon_url => \&_check_icon_url,
};
-use constant REQUIRED_CREATE_FIELDS => qw(name description isbuggroup);
-
use constant UPDATE_COLUMNS => qw(
name
description
@@ -84,26 +82,54 @@
sub is_active { return $_[0]->{'isactive'}; }
sub icon_url { return $_[0]->{'icon_url'}; }
+sub bugs {
+ my $self = shift;
+ return $self->{bugs} if exists $self->{bugs};
+ my $bug_ids = Bugzilla->dbh->selectcol_arrayref(
+ 'SELECT bug_id FROM bug_group_map WHERE group_id = ?',
+ undef, $self->id);
+ require Bugzilla::Bug;
+ $self->{bugs} = Bugzilla::Bug->new_from_list($bug_ids);
+ return $self->{bugs};
+}
+
sub members_direct {
my ($self) = @_;
- return $self->{members_direct} if defined $self->{members_direct};
- my $dbh = Bugzilla->dbh;
- my $user_ids = $dbh->selectcol_arrayref(
- "SELECT user_group_map.user_id
- FROM user_group_map
- WHERE user_group_map.group_id = ?
- AND grant_type = " . GRANT_DIRECT . "
- AND isbless = 0", undef, $self->id);
- require Bugzilla::User;
- $self->{members_direct} = Bugzilla::User->new_from_list($user_ids);
+ $self->{members_direct} ||= $self->_get_members(GRANT_DIRECT);
return $self->{members_direct};
}
+sub members_non_inherited {
+ my ($self) = @_;
+ $self->{members_non_inherited} ||= $self->_get_members();
+ return $self->{members_non_inherited};
+}
+
+# A helper for members_direct and members_non_inherited
+sub _get_members {
+ my ($self, $grant_type) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $grant_clause = $grant_type ? "AND grant_type = $grant_type" : "";
+ my $user_ids = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT user_id
+ FROM user_group_map
+ WHERE isbless = 0 $grant_clause AND group_id = ?", undef, $self->id);
+ require Bugzilla::User;
+ return Bugzilla::User->new_from_list($user_ids);
+}
+
+sub flag_types {
+ my $self = shift;
+ require Bugzilla::FlagType;
+ $self->{flag_types} ||= Bugzilla::FlagType::match({ group => $self->id });
+ return $self->{flag_types};
+}
+
sub grant_direct {
my ($self, $type) = @_;
$self->{grant_direct} ||= {};
return $self->{grant_direct}->{$type}
- if defined $self->{members_direct}->{$type};
+ if defined $self->{grant_direct}->{$type};
my $dbh = Bugzilla->dbh;
my $ids = $dbh->selectcol_arrayref(
@@ -131,10 +157,44 @@
return $self->{granted_by_direct}->{$type};
}
+sub products {
+ my $self = shift;
+ return $self->{products} if exists $self->{products};
+ my $product_data = Bugzilla->dbh->selectall_arrayref(
+ 'SELECT product_id, entry, membercontrol, othercontrol,
+ canedit, editcomponents, editbugs, canconfirm
+ FROM group_control_map WHERE group_id = ?', {Slice=>{}},
+ $self->id);
+ my @ids = map { $_->{product_id} } @$product_data;
+ require Bugzilla::Product;
+ my $products = Bugzilla::Product->new_from_list(\@ids);
+ my %data_map = map { $_->{product_id} => $_ } @$product_data;
+ my @retval;
+ foreach my $product (@$products) {
+ # Data doesn't need to contain product_id--we already have
+ # the product object.
+ delete $data_map{$product->id}->{product_id};
+ push(@retval, { controls => $data_map{$product->id},
+ product => $product });
+ }
+ $self->{products} = \@retval;
+ return $self->{products};
+}
+
###############################
#### Methods ####
###############################
+sub check_members_are_visible {
+ my $self = shift;
+ my $user = Bugzilla->user;
+ return if !Bugzilla->params->{'usevisibilitygroups'};
+ my $is_visible = grep { $_->id == $_ } @{ $user->visible_groups_inherited };
+ if (!$is_visible) {
+ ThrowUserError('group_not_visible', { group => $self });
+ }
+}
+
sub set_description { $_[0]->set('description', $_[1]); }
sub set_is_active { $_[0]->set('isactive', $_[1]); }
sub set_name { $_[0]->set('name', $_[1]); }
@@ -143,6 +203,8 @@
sub update {
my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
my $changes = $self->SUPER::update(@_);
if (exists $changes->{name}) {
@@ -162,9 +224,76 @@
&& $changes->{isactive}->[1]);
$self->_rederive_regexp() if exists $changes->{userregexp};
+
+ Bugzilla::Hook::process('group_end_of_update',
+ { group => $self, changes => $changes });
+ $dbh->bz_commit_transaction();
return $changes;
}
+sub check_remove {
+ my ($self, $params) = @_;
+
+ # System groups cannot be deleted!
+ if (!$self->is_bug_group) {
+ ThrowUserError("system_group_not_deletable", { name => $self->name });
+ }
+
+ # Groups having a special role cannot be deleted.
+ my @special_groups;
+ foreach my $special_group (GROUP_PARAMS) {
+ if ($self->name eq Bugzilla->params->{$special_group}) {
+ push(@special_groups, $special_group);
+ }
+ }
+ if (scalar(@special_groups)) {
+ ThrowUserError('group_has_special_role',
+ { name => $self->name,
+ groups => \@special_groups });
+ }
+
+ return if $params->{'test_only'};
+
+ my $cantdelete = 0;
+
+ my $users = $self->members_non_inherited;
+ if (scalar(@$users) && !$params->{'remove_from_users'}) {
+ $cantdelete = 1;
+ }
+
+ my $bugs = $self->bugs;
+ if (scalar(@$bugs) && !$params->{'remove_from_bugs'}) {
+ $cantdelete = 1;
+ }
+
+ my $products = $self->products;
+ if (scalar(@$products) && !$params->{'remove_from_products'}) {
+ $cantdelete = 1;
+ }
+
+ my $flag_types = $self->flag_types;
+ if (scalar(@$flag_types) && !$params->{'remove_from_flags'}) {
+ $cantdelete = 1;
+ }
+
+ ThrowUserError('group_cannot_delete', { group => $self }) if $cantdelete;
+}
+
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ $self->check_remove(@_);
+ $dbh->bz_start_transaction();
+ Bugzilla::Hook::process('group_before_delete', { group => $self });
+ $dbh->do('DELETE FROM whine_schedules
+ WHERE mailto_type = ? AND mailto = ?',
+ undef, MAILTO_GROUP, $self->id);
+ # All the other tables will be handled by foreign keys when we
+ # drop the main "groups" row.
+ $self->SUPER::remove_from_db(@_);
+ $dbh->bz_commit_transaction();
+}
+
# Add missing entries in bug_group_map for bugs created while
# a mandatory group was disabled and which is now enabled again.
sub _enforce_mandatory {
@@ -198,23 +327,59 @@
sub _rederive_regexp {
my ($self) = @_;
- RederiveRegexp($self->user_regexp, $self->id);
+
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare("SELECT userid, login_name, group_id
+ FROM profiles
+ LEFT JOIN user_group_map
+ ON user_group_map.user_id = profiles.userid
+ AND group_id = ?
+ AND grant_type = ?
+ AND isbless = 0");
+ my $sthadd = $dbh->prepare("INSERT INTO user_group_map
+ (user_id, group_id, grant_type, isbless)
+ VALUES (?, ?, ?, 0)");
+ my $sthdel = $dbh->prepare("DELETE FROM user_group_map
+ WHERE user_id = ? AND group_id = ?
+ AND grant_type = ? and isbless = 0");
+ $sth->execute($self->id, GRANT_REGEXP);
+ my $regexp = $self->user_regexp;
+ while (my ($uid, $login, $present) = $sth->fetchrow_array) {
+ if ($regexp ne '' and $login =~ /$regexp/i) {
+ $sthadd->execute($uid, $self->id, GRANT_REGEXP) unless $present;
+ } else {
+ $sthdel->execute($uid, $self->id, GRANT_REGEXP) if $present;
+ }
+ }
}
-sub members_non_inherited {
- my ($self) = @_;
- return $self->{members_non_inherited}
- if exists $self->{members_non_inherited};
+sub flatten_group_membership {
+ my ($self, @groups) = @_;
- my $member_ids = Bugzilla->dbh->selectcol_arrayref(
- 'SELECT DISTINCT user_id FROM user_group_map
- WHERE isbless = 0 AND group_id = ?',
- undef, $self->id) || [];
- require Bugzilla::User;
- $self->{members_non_inherited} = Bugzilla::User->new_from_list($member_ids);
- return $self->{members_non_inherited};
+ my $dbh = Bugzilla->dbh;
+ my $sth;
+ my @groupidstocheck = @groups;
+ my %groupidschecked = ();
+ $sth = $dbh->prepare("SELECT member_id FROM group_group_map
+ WHERE grantor_id = ?
+ AND grant_type = " . GROUP_MEMBERSHIP);
+ while (my $node = shift @groupidstocheck) {
+ $sth->execute($node);
+ my $member;
+ while (($member) = $sth->fetchrow_array) {
+ if (!$groupidschecked{$member}) {
+ $groupidschecked{$member} = 1;
+ push @groupidstocheck, $member;
+ push @groups, $member unless grep $_ == $member, @groups;
+ }
+ }
+ }
+ return \@groups;
}
+
+
+
################################
##### Module Subroutines ###
################################
@@ -224,8 +389,13 @@
my ($params) = @_;
my $dbh = Bugzilla->dbh;
- print get_text('install_group_create', { name => $params->{name} }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ my $silently = delete $params->{silently};
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE and !$silently) {
+ print get_text('install_group_create', { name => $params->{name} }),
+ "\n";
+ }
+
+ $dbh->bz_start_transaction();
my $group = $class->SUPER::create(@_);
@@ -244,6 +414,9 @@
}
$group->_rederive_regexp() if $group->user_regexp;
+
+ Bugzilla::Hook::process('group_end_of_create', { group => $group });
+ $dbh->bz_commit_transaction();
return $group;
}
@@ -266,33 +439,19 @@
return $ret;
}
-# This sub is not perldoc'ed because we expect it to go away and
-# just become the _rederive_regexp private method.
-sub RederiveRegexp {
- my ($regexp, $gid) = @_;
- my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare("SELECT userid, login_name, group_id
- FROM profiles
- LEFT JOIN user_group_map
- ON user_group_map.user_id = profiles.userid
- AND group_id = ?
- AND grant_type = ?
- AND isbless = 0");
- my $sthadd = $dbh->prepare("INSERT INTO user_group_map
- (user_id, group_id, grant_type, isbless)
- VALUES (?, ?, ?, 0)");
- my $sthdel = $dbh->prepare("DELETE FROM user_group_map
- WHERE user_id = ? AND group_id = ?
- AND grant_type = ? and isbless = 0");
- $sth->execute($gid, GRANT_REGEXP);
- while (my ($uid, $login, $present) = $sth->fetchrow_array()) {
- if (($regexp =~ /\S+/) && ($login =~ m/$regexp/i))
- {
- $sthadd->execute($uid, $gid, GRANT_REGEXP) unless $present;
- } else {
- $sthdel->execute($uid, $gid, GRANT_REGEXP) if $present;
- }
- }
+sub check_no_disclose {
+ my ($class, $params) = @_;
+ my $action = delete $params->{action};
+
+ $action =~ /^(?:add|remove)$/
+ or ThrowCodeError('bad_arg', { argument => $action,
+ function => "${class}::check_no_disclose" });
+
+ $params->{_error} = ($action eq 'add') ? 'group_restriction_not_allowed'
+ : 'group_invalid_removal';
+
+ my $group = $class->check($params);
+ return $group;
}
###############################
@@ -304,7 +463,7 @@
$name = trim($name);
$name || ThrowUserError("empty_group_name");
# If we're creating a Group or changing the name...
- if (!ref($invocant) || $invocant->name ne $name) {
+ if (!ref($invocant) || lc($invocant->name) ne lc($name)) {
my $exists = new Bugzilla::Group({name => $name });
ThrowUserError("group_exists", { name => $name }) if $exists;
}
@@ -377,22 +536,116 @@
=item C<ValidateGroupName($name, @users)>
- Description: ValidateGroupName checks to see if ANY of the users
- in the provided list of user objects can see the
- named group.
+Description: ValidateGroupName checks to see if ANY of the users
+ in the provided list of user objects can see the
+ named group.
- Params: $name - String with the group name.
- @users - An array with Bugzilla::User objects.
+Params: $name - String with the group name.
+ @users - An array with Bugzilla::User objects.
- Returns: It returns the group id if successful
- and undef otherwise.
+Returns: It returns the group id if successful
+ and undef otherwise.
=back
+
=head1 METHODS
=over
+=item C<check_no_disclose>
+
+=over
+
+=item B<Description>
+
+Throws an error if the user cannot add or remove this group to/from a given
+bug, but doesn't specify if this is because the group doesn't exist, or the
+user is not allowed to edit this group restriction.
+
+=item B<Params>
+
+This method takes a single hashref as argument, with the following keys:
+
+=over
+
+=item C<name>
+
+C<string> The name of the group to add or remove.
+
+=item C<bug_id>
+
+C<integer> The ID of the bug to which the group change applies.
+
+=item C<product>
+
+C<string> The name of the product the bug belongs to.
+
+=item C<action>
+
+C<string> Must be either C<add> or C<remove>, depending on whether the group
+must be added or removed from the bug. Any other value will generate an error.
+
+=back
+
+=item C<Returns>
+
+A C<Bugzilla::Group> object on success, else an error is thrown.
+
+=back
+
+=item C<check_members_are_visible>
+
+Throws an error if this group is not visible (according to
+visibility groups) to the currently-logged-in user.
+
+=item C<check_remove>
+
+=over
+
+=item B<Description>
+
+Determines whether it's OK to remove this group from the database, and
+throws an error if it's not OK.
+
+=item B<Params>
+
+=over
+
+=item C<test_only>
+
+C<boolean> If you want to only check if the group can be deleted I<at all>,
+under any circumstances, specify C<test_only> to just do the most basic tests
+(the other parameters will be ignored in this situation, as those tests won't
+be run).
+
+=item C<remove_from_users>
+
+C<boolean> True if it would be OK to remove all users who are in this group
+from this group.
+
+=item C<remove_from_bugs>
+
+C<boolean> True if it would be OK to remove all bugs that are in this group
+from this group.
+
+=item C<remove_from_flags>
+
+C<boolean> True if it would be OK to stop all flagtypes that reference
+this group from referencing this group (e.g., as their grantgroup or
+requestgroup).
+
+=item C<remove_from_products>
+
+C<boolean> True if it would be OK to remove this group from all group controls
+on products.
+
+=back
+
+=item B<Returns> (nothing)
+
+=back
+
=item C<members_non_inherited>
Returns an arrayref of L<Bugzilla::User> objects representing people who are
@@ -400,4 +653,12 @@
the group regular expression, or they have been actually added to the
group manually.
+=item C<flatten_group_membership>
+
+Accepts a list of groups and returns a list of all the groups whose members
+inherit membership in any group on the list. So, we can determine if a user
+is in any of the groups input to flatten_group_membership by querying the
+user_group_map for any user with DIRECT or REGEXP membership IN() the list
+of groups returned.
+
=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/Hook.pm b/Websites/bugs.webkit.org/Bugzilla/Hook.pm
index de33cf5..da17946 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Hook.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Hook.pm
@@ -18,65 +18,39 @@
# Rights Reserved.
#
# Contributor(s): Zach Lipton <zach@zachlipton.com>
-#
+# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Hook;
-
-use Bugzilla::Constants;
-use Bugzilla::Util;
-use Bugzilla::Error;
-
use strict;
sub process {
my ($name, $args) = @_;
-
- # get a list of all extensions
- my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
-
- # check each extension to see if it uses the hook
- # if so, invoke the extension source file:
- foreach my $extension (@extensions) {
- # all of these variables come directly from code or directory names.
- # If there's malicious data here, we have much bigger issues to
- # worry about, so we can safely detaint them:
- trick_taint($extension);
- # Skip CVS directories and any hidden files/dirs.
- next if $extension =~ m{/CVS$} || $extension =~ m{/\.[^/]+$};
- next if -e "$extension/disabled";
- if (-e $extension.'/code/'.$name.'.pl') {
- Bugzilla->hook_args($args);
- # Allow extensions to load their own libraries.
- local @INC = ("$extension/lib", @INC);
- do($extension.'/code/'.$name.'.pl');
- ThrowCodeError('extension_invalid',
- { errstr => $@, name => $name, extension => $extension }) if $@;
- # Flush stored data.
- Bugzilla->hook_args({});
+
+ _entering($name);
+
+ foreach my $extension (@{ Bugzilla->extensions }) {
+ if ($extension->can($name)) {
+ $extension->$name($args);
}
}
+
+ _leaving($name);
}
-sub enabled_plugins {
- my $extdir = bz_locations()->{'extensionsdir'};
- my @extensions = glob("$extdir/*");
- my %enabled;
- foreach my $extension (@extensions) {
- trick_taint($extension);
- my $extname = $extension;
- $extname =~ s{^\Q$extdir\E/}{};
- next if $extname eq 'CVS' || $extname =~ /^\./;
- next if -e "$extension/disabled";
- # Allow extensions to load their own libraries.
- local @INC = ("$extension/lib", @INC);
- $enabled{$extname} = do("$extension/info.pl");
- ThrowCodeError('extension_invalid',
- { errstr => $@, name => 'version',
- extension => $extension }) if $@;
+sub in {
+ my $hook_name = shift;
+ my $currently_in = Bugzilla->request_cache->{hook_stack}->[-1] || '';
+ return $hook_name eq $currently_in ? 1 : 0;
+}
- }
+sub _entering {
+ my ($hook_name) = @_;
+ my $hook_stack = Bugzilla->request_cache->{hook_stack} ||= [];
+ push(@$hook_stack, $hook_name);
+}
- return \%enabled;
+sub _leaving {
+ pop @{ Bugzilla->request_cache->{hook_stack} };
}
1;
@@ -101,36 +75,19 @@
to perform additional functions, it uses Bugzilla::Hook's L</process>
subroutine to invoke any extension code if installed.
-There is a sample extension in F<extensions/example/> that demonstrates
-most of the things described in this document, as well as many of the
-hooks available.
+The implementation of extensions is described in L<Bugzilla::Extension>.
+
+There is sample code for every hook in the Example extension, located in
+F<extensions/Example/Extension.pm>.
=head2 How Hooks Work
-When a hook named C<HOOK_NAME> is run, Bugzilla will attempt to invoke any
-source files named F<extensions/*/code/HOOK_NAME.pl>.
+When a hook named C<HOOK_NAME> is run, Bugzilla looks through all
+enabled L<extensions|Bugzilla::Extension> for extensions that implement
+a subroutine named C<HOOK_NAME>.
-So, for example, if your extension is called "testopia", and you
-want to have code run during the L</install-update_db> hook, you
-would have a file called F<extensions/testopia/code/install-update_db.pl>
-that contained perl code to run during that hook.
-
-=head2 Arguments Passed to Hooks
-
-Some L<hooks|/HOOKS> have params that are passed to them.
-
-These params are accessible through L<Bugzilla/hook_args>.
-That returns a hashref. Very frequently, if you want your
-hook to do anything, you have to modify these variables.
-
-=head2 Versioning Extensions
-
-Every extension must have a file in its root called F<info.pl>.
-This file must return a hash when called with C<do>.
-The hash must contain a 'version' key with the current version of the
-extension. Extension authors can also add any extra infomration to this hash if
-required, by adding a new key beginning with x_ which will not be used the
-core Bugzilla code.
+See L<Bugzilla::Extension> for more details about how an extension
+can run code during a hook.
=head1 SUBROUTINES
@@ -144,8 +101,8 @@
Invoke any code hooks with a matching name from any installed extensions.
-See C<customization.xml> in the Bugzilla Guide for more information on
-Bugzilla's extension mechanism.
+See L<Bugzilla::Extension> for more information on Bugzilla's extension
+mechanism.
=item B<Params>
@@ -154,7 +111,7 @@
=item C<$name> - The name of the hook to invoke.
=item C<$args> - A hashref. The named args to pass to the hook.
-They will be accessible to the hook via L<Bugzilla/hook_args>.
+They will be passed as arguments to the hook method in the extension.
=back
@@ -170,7 +127,136 @@
in alphabetical order, but some related hooks are near each other instead
of being alphabetical.
-=head2 bug-end_of_update
+=head2 admin_editusers_action
+
+This hook allows you to add additional actions to the admin Users page.
+
+Params:
+
+=over
+
+=item C<vars>
+
+You can add as many new key/value pairs as you want to this hashref.
+It will be passed to the template.
+
+=item C<action>
+
+A text which indicates the different behaviors that editusers.cgi will have.
+With this hook you can change the behavior of an action or add new actions.
+
+=item C<user>
+
+This is a Bugzilla::User object of the user.
+
+=back
+
+=head2 attachment_process_data
+
+This happens at the very beginning process of the attachment creation.
+You can edit the attachment content itself as well as all attributes
+of the attachment, before they are validated and inserted into the DB.
+
+Params:
+
+=over
+
+=item C<data> - A reference pointing either to the content of the file
+being uploaded or pointing to the filehandle associated with the file.
+
+=item C<attributes> - A hashref whose keys are the same as the input to
+L<Bugzilla::Attachment/create>. The data in this hashref hasn't been validated
+yet.
+
+=back
+
+=head2 auth_login_methods
+
+This allows you to add new login types to Bugzilla.
+(See L<Bugzilla::Auth::Login>.)
+
+Params:
+
+=over
+
+=item C<modules>
+
+This is a hash--a mapping from login-type "names" to the actual module on
+disk. The keys will be all the values that were passed to
+L<Bugzilla::Auth/login> for the C<Login> parameter. The values are the
+actual path to the module on disk. (For example, if the key is C<DB>, the
+value is F<Bugzilla/Auth/Login/DB.pm>.)
+
+For your extension, the path will start with
+F<Bugzilla/Extension/Foo/>, where "Foo" is the name of your Extension.
+(See the code in the example extension.)
+
+If your login type is in the hash as a key, you should set that key to the
+right path to your module. That module's C<new> method will be called,
+probably with empty parameters. If your login type is I<not> in the hash,
+you should not set it.
+
+You will be prevented from adding new keys to the hash, so make sure your
+key is in there before you modify it. (In other words, you can't add in
+login methods that weren't passed to L<Bugzilla::Auth/login>.)
+
+=back
+
+=head2 auth_verify_methods
+
+This works just like L</auth_login_methods> except it's for
+login verification methods (See L<Bugzilla::Auth::Verify>.) It also
+takes a C<modules> parameter, just like L</auth_login_methods>.
+
+=head2 bug_columns
+
+B<DEPRECATED> Use L</object_columns> instead.
+
+This allows you to add new fields that will show up in every L<Bugzilla::Bug>
+object. Note that you will also need to use the L</bug_fields> hook in
+conjunction with this hook to make this work.
+
+Params:
+
+=over
+
+=item C<columns> - An arrayref containing an array of column names. Push
+your column name(s) onto the array.
+
+=back
+
+=head2 bug_end_of_create
+
+This happens at the end of L<Bugzilla::Bug/create>, after all other changes are
+made to the database. This occurs inside a database transaction.
+
+Params:
+
+=over
+
+=item C<bug> - The created bug object.
+
+=item C<timestamp> - The timestamp used for all updates in this transaction,
+as a SQL date string.
+
+=back
+
+=head2 bug_end_of_create_validators
+
+This happens during L<Bugzilla::Bug/create>, after all parameters have
+been validated, but before anything has been inserted into the database.
+
+Params:
+
+=over
+
+=item C<params>
+
+A hashref. The validated parameters passed to C<create>.
+
+=back
+
+=head2 bug_end_of_update
This happens at the end of L<Bugzilla::Bug/update>, after all other changes are
made to the database. This generally occurs inside a database transaction.
@@ -179,21 +265,174 @@
=over
-=item C<bug> - The changed bug object, with all fields set to their updated
-values.
+=item C<bug>
-=item C<timestamp> - The timestamp used for all updates in this transaction.
+The changed bug object, with all fields set to their updated values.
-=item C<changes> - The hash of changed fields.
-C<$changes-E<gt>{field} = [old, new]>
+=item C<old_bug>
+
+A bug object pulled from the database before the fields were set to
+their updated values (so it has the old values available for each field).
+
+=item C<timestamp>
+
+The timestamp used for all updates in this transaction, as a SQL date
+string.
+
+=item C<changes>
+
+The hash of changed fields. C<< $changes->{field} = [old, new] >>
=back
-=head2 buglist-columns
+=head2 bug_check_can_change_field
-This happens in buglist.cgi after the standard columns have been defined and
-right before the display column determination. It gives you the opportunity
-to add additional display columns.
+This hook controls what fields users are allowed to change. You can add code
+here for site-specific policy changes and other customizations.
+
+This hook is only executed if the field's new and old values differ.
+
+Any denies take priority over any allows. So, if another extension denies
+a change but yours allows the change, the other extension's deny will
+override your extension's allow.
+
+Params:
+
+=over
+
+=item C<bug>
+
+L<Bugzilla::Bug> - The current bug object that this field is changing on.
+
+=item C<field>
+
+The name (from the C<fielddefs> table) of the field that we are checking.
+
+=item C<new_value>
+
+The new value that the field is being changed to.
+
+=item C<old_value>
+
+The old value that the field is being changed from.
+
+=item C<priv_results>
+
+C<array> - This is how you explicitly allow or deny a change. You should only
+push something into this array if you want to explicitly allow or explicitly
+deny the change, and thus skip all other permission checks that would otherwise
+happen after this hook is called. If you don't care about the field change,
+then don't push anything into the array.
+
+The pushed value should be a choice from the following constants:
+
+=over
+
+=item C<PRIVILEGES_REQUIRED_NONE>
+
+No privileges required. This explicitly B<allows> a change.
+
+=item C<PRIVILEGES_REQUIRED_REPORTER>
+
+User is not the reporter, assignee or an empowered user, so B<deny>.
+
+=item C<PRIVILEGES_REQUIRED_ASSIGNEE>
+
+User is not the assignee or an empowered user, so B<deny>.
+
+=item C<PRIVILEGES_REQUIRED_EMPOWERED>
+
+User is not a sufficiently empowered user, so B<deny>.
+
+=back
+
+=back
+
+=head2 bug_fields
+
+Allows the addition of database fields from the bugs table to the standard
+list of allowable fields in a L<Bugzilla::Bug> object, so that
+you can call the field as a method.
+
+Note: You should add here the names of any fields you added in L</bug_columns>.
+
+Params:
+
+=over
+
+=item C<columns> - A arrayref containing an array of column names. Push
+your column name(s) onto the array.
+
+=back
+
+=head2 bug_format_comment
+
+Allows you to do custom parsing on comments before they are displayed. You do
+this by returning two regular expressions: one that matches the section you
+want to replace, and then another that says what you want to replace that
+match with.
+
+The matching and replacement will be run with the C</g> switch on the regex.
+
+Params:
+
+=over
+
+=item C<regexes>
+
+An arrayref of hashrefs.
+
+You should push a hashref containing two keys (C<match> and C<replace>)
+in to this array. C<match> is the regular expression that matches the
+text you want to replace, C<replace> is what you want to replace that
+text with. (This gets passed into a regular expression like
+C<s/$match/$replace/>.)
+
+Instead of specifying a regular expression for C<replace> you can also
+return a coderef (a reference to a subroutine). If you want to use
+backreferences (using C<$1>, C<$2>, etc. in your C<replace>), you have to use
+this method--it won't work if you specify C<$1>, C<$2> in a regular expression
+for C<replace>. Your subroutine will get a hashref as its only argument. This
+hashref contains a single key, C<matches>. C<matches> is an arrayref that
+contains C<$1>, C<$2>, C<$3>, etc. in order, up to C<$10>. Your subroutine
+should return what you want to replace the full C<match> with. (See the code
+example for this hook if you want to see how this actually all works in code.
+It's simpler than it sounds.)
+
+B<You are responsible for HTML-escaping your returned data.> Failing to
+do so could open a security hole in Bugzilla.
+
+=item C<text>
+
+A B<reference> to the exact text that you are parsing.
+
+Generally you should not modify this yourself. Instead you should be
+returning regular expressions using the C<regexes> array.
+
+The text has not been parsed in any way. (So, for example, it is not
+HTML-escaped. You get "&", not "&".)
+
+=item C<bug>
+
+The L<Bugzilla::Bug> object that this comment is on. Sometimes this is
+C<undef>, meaning that we are parsing text that is not on a bug.
+
+=item C<comment>
+
+A L<Bugzilla::Comment> object representing the comment you are about to
+parse.
+
+Sometimes this is C<undef>, meaning that we are parsing text that is
+not a bug comment (but could still be some other part of a bug, like
+the summary line).
+
+=back
+
+=head2 buglist_columns
+
+This happens in L<Bugzilla::Search/COLUMNS>, which determines legal bug
+list columns for F<buglist.cgi> and F<colchange.cgi>. It gives you the
+opportunity to add additional display columns.
Params:
@@ -217,23 +456,226 @@
=back
-=head2 colchange-columns
+=head2 buglist_column_joins
-This happens in F<colchange.cgi> right after the list of possible display
-columns have been defined and gives you the opportunity to add additional
-display columns to the list of selectable columns.
+This allows you to join additional tables to display additional columns
+in buglists. This hook is generally used in combination with the
+C<buglist_columns> hook.
Params:
=over
-=item C<columns> - An arrayref containing an array of column IDs. Any IDs
-added by this hook must have been defined in the the buglist-columns hook.
-See L</buglist-columns>.
+=item C<column_joins> - A hashref containing data to return back to
+L<Bugzilla::Search>. This hashref contains names of the columns as keys and
+a hashref about table to join as values. This hashref has the following keys:
+
+=over
+
+=item C<table> - The name of the additional table to join.
+
+=item C<as> - (optional) The alias used for the additional table. This alias
+must not conflict with an existing alias already used in the query.
+
+=item C<from> - (optional) The name of the column in the C<bugs> table which
+the additional table should be linked to. If omitted, C<bug_id> will be used.
+
+=item C<to> - (optional) The name of the column in the additional table which
+should be linked to the column in the C<bugs> table, see C<from> above.
+If omitted, C<bug_id> will be used.
+
+=item C<join> - (optional) Either INNER or LEFT. Determine how the additional
+table should be joined with the C<bugs> table. If omitted, LEFT is used.
=back
-=head2 enter_bug-entrydefaultvars
+=back
+
+=head2 search_operator_field_override
+
+This allows you to modify L<Bugzilla::Search/OPERATOR_FIELD_OVERRIDE>,
+which determines the search functions for fields. It allows you to specify
+custom search functionality for certain fields.
+
+See L<Bugzilla::Search/OPERATOR_FIELD_OVERRIDE> for reference and see
+the code in the example extension.
+
+Note that the interface to this hook is B<UNSTABLE> and it may change in the
+future.
+
+Params:
+
+=over
+
+=item C<operators> - See L<Bugzilla::Search/OPERATOR_FIELD_OVERRIDE> to get
+an idea of the structure.
+
+=item C<search> - The L<Bugzilla::Search> object.
+
+=back
+
+=head2 bugmail_recipients
+
+This allows you to modify the list of users who are going to be receiving
+a particular bugmail. It also allows you to specify why they are receiving
+the bugmail.
+
+Users' bugmail preferences will be applied to any users that you add
+to the list. (So, for example, if you add somebody as though they were
+a CC on the bug, and their preferences state that they don't get email
+when they are a CC, they won't get email.)
+
+This hook is called before watchers or globalwatchers are added to the
+recipient list.
+
+Params:
+
+=over
+
+=item C<bug>
+
+The L<Bugzilla::Bug> that bugmail is being sent about.
+
+=item C<recipients>
+
+This is a hashref. The keys are numeric user ids from the C<profiles>
+table in the database, for each user who should be receiving this bugmail.
+The values are hashrefs. The keys in I<these> hashrefs correspond to
+the "relationship" that the user has to the bug they're being emailed
+about, and the value should always be C<1>. The "relationships"
+are described by the various C<REL_> constants in L<Bugzilla::Constants>.
+
+Here's an example of adding userid C<123> to the recipient list
+as though he were on the CC list:
+
+ $recipients->{123}->{+REL_CC} = 1
+
+(We use C<+> in front of C<REL_CC> so that Perl interprets it as a constant
+instead of as a string.)
+
+=item C<users>
+
+This is a hash of L<Bugzilla::User> objects, keyed by id. This is so you can
+find out more information about any of the user ids in the C<recipients> hash.
+Every id in the incoming C<recipients> hash will have an object in here.
+(But if you add additional recipients to the C<recipients> hash, you are
+B<not> required to add them to this hash.)
+
+=item C<diffs>
+
+This is a list of hashes, each hash representing a change to the bug. Each
+hash has the following members: C<field_name>, C<bug_when>, C<old>, C<new>
+and C<who> (a L<Bugzilla::User>). If appropriate, there will also be
+C<attach_id> or C<comment_id>; if either is present, there will be
+C<isprivate>. See C<_get_diffs> in F<Bugzilla/BugMail.pm> to see exactly how
+it is populated. Warning: the format and existence of the "diffs" parameter
+is subject to change in future releases of Bugzilla.
+
+=back
+
+=head2 bugmail_relationships
+
+There are various sorts of "relationships" that a user can have to a bug,
+such as Assignee, CC, etc. If you want to add a new type of relationship,
+you should use this hook.
+
+Params:
+
+=over
+
+=item C<relationships>
+
+A hashref, where the keys are numbers and the values are strings.
+
+The keys represent a numeric identifier for the relationship. The
+numeric identifier should be a negative number between -1 and -127.
+The number must be unique across all extensions. (Negative numbers
+are used so as not to conflict with relationship identifiers in Bugzilla
+itself.)
+
+The value is the "name" of this relationship that will show up in email
+headers in bugmails. The "name" should be short and should contain no
+spaces.
+
+=back
+
+
+=head2 config_add_panels
+
+If you want to add new panels to the Parameters administrative interface,
+this is where you do it.
+
+Params:
+
+=over
+
+=item C<panel_modules>
+
+A hashref, where the keys are the "name" of the panel and the value
+is the Perl module representing that panel. For example, if
+the name is C<Auth>, the value would be C<Bugzilla::Config::Auth>.
+
+For your extension, the Perl module would start with
+C<Bugzilla::Extension::Foo>, where "Foo" is the name of your Extension.
+(See the code in the example extension.)
+
+=back
+
+=head2 config_modify_panels
+
+This is how you modify already-existing panels in the Parameters
+administrative interface. For example, if you wanted to add a new
+Auth method (modifying Bugzilla::Config::Auth) this is how you'd
+do it.
+
+Params:
+
+=over
+
+=item C<panels>
+
+A hashref, where the keys are lower-case panel "names" (like C<auth>,
+C<admin>, etc.) and the values are hashrefs. The hashref contains a
+single key, C<params>. C<params> is an arrayref--the return value from
+C<get_param_list> for that module. You can modify C<params> and
+your changes will be reflected in the interface.
+
+Adding new keys to C<panels> will have no effect. You should use
+L</config_add_panels> if you want to add new panels.
+
+=back
+
+=head2 email_in_before_parse
+
+This happens right after an inbound email is converted into an Email::MIME
+object, but before we start parsing the email to extract field data. This
+means the email has already been decoded for you. It gives you a chance
+to interact with the email itself before L<email_in> starts parsing its content.
+
+=over
+
+=item C<mail> - An Email::MIME object. The decoded incoming email.
+
+=item C<fields> - A hashref. The hash which will contain extracted data.
+
+=back
+
+=head2 email_in_after_parse
+
+This happens after all the data has been extracted from the email, but before
+the reporter is validated, during L<email_in>. This lets you do things after
+the normal parsing of the email, such as sanitizing field data, changing the
+user account being used to file a bug, etc.
+
+=over
+
+=item C<fields> - A hashref. The hash containing the extracted field data.
+
+=back
+
+=head2 enter_bug_entrydefaultvars
+
+B<DEPRECATED> - Use L</template_before_process> instead.
This happens right before the template is loaded on enter_bug.cgi.
@@ -245,11 +687,42 @@
=back
-=head2 flag-end_of_update
+=head2 error_catch
-This happens at the end of L<Bugzilla::Flag/process>, after all other changes
-are made to the database and after emails are sent. It gives you a before/after
-snapshot of flags so you can react to specific flag changes.
+This hook allows extensions to catch errors thrown by Bugzilla and
+take the appropriate actions.
+
+Params:
+
+=over
+
+=item C<error>
+
+A string representing the error code thrown by Bugzilla. This string
+matches the C<error> variable in C<global/user-error.html.tmpl> and
+C<global/code-error.html.tmpl>.
+
+=item C<message>
+
+If the error mode is set to C<ERROR_MODE_WEBPAGE>, you get a reference to
+the whole HTML page with the error message in it, including its header and
+footer. If you need to extract the error message itself, you can do it by
+looking at the content of the table cell whose ID is C<error_msg>.
+If the error mode is not set to C<ERROR_MODE_WEBPAGE>, you get a reference
+to the error message itself.
+
+=item C<vars>
+
+This hash contains all the data passed to the error template. Its content
+depends on the error thrown.
+
+=back
+
+=head2 flag_end_of_update
+
+This happens at the end of L<Bugzilla::Flag/update_flags>, after all other
+changes are made to the database and after emails are sent. It gives you a
+before/after snapshot of flags so you can react to specific flag changes.
This generally occurs inside a database transaction.
Note that the interface to this hook is B<UNSTABLE> and it may change in the
@@ -259,9 +732,10 @@
=over
-=item C<bug> - The changed bug object.
+=item C<object> - The changed bug or attachment object.
-=item C<timestamp> - The timestamp used for all updates in this transaction.
+=item C<timestamp> - The timestamp used for all updates in this transaction,
+as a SQL date string.
=item C<old_flags> - The snapshot of flag summaries from before the change.
@@ -271,7 +745,54 @@
=back
-=head2 install-before_final_checks
+=head2 group_before_delete
+
+This happens in L<Bugzilla::Group/remove_from_db>, after we've confirmed
+that the group can be deleted, but before any rows have actually
+been removed from the database. This occurs inside a database
+transaction.
+
+Params:
+
+=over
+
+=item C<group> - The L<Bugzilla::Group> being deleted.
+
+=back
+
+=head2 group_end_of_create
+
+This happens at the end of L<Bugzilla::Group/create>, after all other
+changes are made to the database. This occurs inside a database transaction.
+
+Params:
+
+=over
+
+=item C<group>
+
+The new L<Bugzilla::Group> object that was just created.
+
+=back
+
+=head2 group_end_of_update
+
+This happens at the end of L<Bugzilla::Group/update>, after all other
+changes are made to the database. This occurs inside a database transaction.
+
+Params:
+
+=over
+
+=item C<group> - The changed L<Bugzilla::Group> object, with all fields set
+to their updated values.
+
+=item C<changes> - The hash of changed fields.
+C<< $changes->{$field} = [$old, $new] >>
+
+=back
+
+=head2 install_before_final_checks
Allows execution of custom code before the final checks are done in
checksetup.pl.
@@ -283,47 +804,127 @@
=item C<silent>
A flag that indicates whether or not checksetup is running in silent mode.
+If this is true, messages that are I<always> printed by checksetup.pl should be
+suppressed, but messages about any changes that are just being done this one
+time should be printed.
=back
-=head2 install-requirements
+=head2 install_filesystem
-Because of the way Bugzilla installation works, there can't be a normal
-hook during the time that F<checksetup.pl> checks what modules are
-installed. (C<Bugzilla::Hook> needs to have those modules installed--it's
-a chicken-and-egg problem.)
+Allows for additional files and directories to be added to the
+list of files and directories already managed by checksetup.pl.
+You will be able to also set permissions for the files and
+directories using this hook. You can also use this hook to create
+appropriate .htaccess files for any directory to secure its contents.
+For examples see L<FILESYSTEM> in L<Bugzilla::Install::Filesystem>.
-So instead of the way hooks normally work, this hook just looks for two
-subroutines (or constants, since all constants are just subroutines) in
-your file, called C<OPTIONAL_MODULES> and C<REQUIRED_MODULES>,
-which should return arrayrefs in the same format as C<OPTIONAL_MODULES> and
-C<REQUIRED_MODULES> in L<Bugzilla::Install::Requirements>.
+Params:
-These subroutines will be passed an arrayref that contains the current
-Bugzilla requirements of the same type, in case you want to modify
-Bugzilla's requirements somehow. (Probably the most common would be to
-alter a version number or the "feature" element of C<OPTIONAL_MODULES>.)
+=over
-F<checksetup.pl> will add these requirements to its own.
+=item C<files>
-Please remember--if you put something in C<REQUIRED_MODULES>, then
-F<checksetup.pl> B<cannot complete> unless the user has that module
-installed! So use C<OPTIONAL_MODULES> whenever you can.
+Hash reference of files that are already present when your extension was
+installed but need to have specific permissions set. Each file key
+points to another hash reference containing the following settings.
-=head2 install-update_db
+Params:
+
+=over
+
+=item C<perms> - Permissions to be set on the file.
+
+=back
+
+=item C<create_dirs>
+
+Hash reference containing the name of each directory that will be created,
+pointing at its default permissions.
+
+=item C<non_recurse_dirs>
+
+Hash reference containing directories that we want to set the perms on, but not
+recurse through. These are directories not created in checksetup.pl. Each directory
+key's value is the permissions to be set on the directory.
+
+=item C<recurse_dirs>
+
+Hash reference of directories that will have permissions set for each item inside
+each of the directories, including the directory itself. Each directory key
+points to another hash reference containing the following settings.
+
+Params:
+
+=over
+
+=item C<files> - Permissions to be set on any files beneath the directory.
+
+=item C<dirs> - Permissions to be set on the directory itself and any directories
+beneath it.
+
+=back
+
+=item C<create_files>
+
+Hash reference of additional files to be created. Each file key points to another
+hash reference containing the following settings.
+
+Params:
+
+=over
+
+=item C<perms> - The permissions to be set on the file itself.
+
+=item C<contents> - The contents to be added to the file or leave blank for an
+empty file.
+
+=back
+
+=item C<htaccess>
+
+Hash reference containing htaccess files to be created. You can set the permissions
+for the htaccess as well as the contents of the file. Each file key points to another
+hash reference containing the following settings.
+
+Params:
+
+=over
+
+=item C<perms> - Permissions to be set on the htaccess file.
+
+=item C<contents> - Contents of the htaccess file. It can be set manually or
+use L<HT_DEFAULT_DENY> defined in L<Bugzilla::Install::Filesystem> to deny all
+by default.
+
+=back
+
+=back
+
+=head2 install_update_db
This happens at the very end of all the tables being updated
during an installation or upgrade. If you need to modify your custom
-schema, do it here. No params are passed.
+schema or add new columns to existing tables, do it here. No params are
+passed.
-=head2 db_schema-abstract_schema
+=head2 install_update_db_fielddefs
+
+This is used to update the schema of the fielddefs table before
+any other schema changes take place. No params are passed.
+
+This hook should only be used for updating the schema of the C<fielddefs>
+table. Do not modify any other table in this hook. To modify other tables, use
+the L</install_update_db> hook.
+
+=head2 db_schema_abstract_schema
This allows you to add tables to Bugzilla. Note that we recommend that you
-prefix the names of your tables with some word, so that they don't conflict
-with any future Bugzilla tables.
+prefix the names of your tables with some word (preferably the name of
+your Extension), so that they don't conflict with any future Bugzilla tables.
If you wish to add new I<columns> to existing Bugzilla tables, do that
-in L</install-update_db>.
+in L</install_update_db>.
Params:
@@ -336,7 +937,373 @@
=back
-=head2 product-confirm_delete
+=head2 job_map
+
+Bugzilla has a system - L<Bugzilla::JobQueue> - for running jobs
+asynchronously, if the administrator has set it up. This hook allows the
+addition of mappings from job names to handler classes, so an extension can
+fire off jobs.
+
+Params:
+
+=over
+
+=item C<job_map> - The job map hash. Key: the name of the job, as should be
+passed to Bugzilla->job_queue->insert(). Value: the name of the Perl module
+which implements the task (an instance of L<TheSchwartz::Worker>).
+
+=back
+
+=head2 mailer_before_send
+
+Called right before L<Bugzilla::Mailer> sends a message to the MTA.
+
+Params:
+
+=over
+
+=item C<email> - The C<Email::MIME> object that's about to be sent.
+
+=item C<mailer_args> - An arrayref that's passed as C<mailer_args> to
+L<Email::Send/new>.
+
+=back
+
+=head2 object_before_create
+
+This happens at the beginning of L<Bugzilla::Object/create>.
+
+Params:
+
+=over
+
+=item C<class>
+
+The name of the class that C<create> was called on. You can check this
+like C<< if ($class->isa('Some::Class')) >> in your code, to perform specific
+tasks before C<create> for only certain classes.
+
+=item C<params>
+
+A hashref. The set of named parameters passed to C<create>.
+
+=back
+
+
+=head2 object_before_delete
+
+This happens in L<Bugzilla::Object/remove_from_db>, after we've confirmed
+that the object can be deleted, but before any rows have actually
+been removed from the database. This sometimes occurs inside a database
+transaction.
+
+Params:
+
+=over
+
+=item C<object> - The L<Bugzilla::Object> being deleted. You will probably
+want to check its type like C<< $object->isa('Some::Class') >> before doing
+anything with it.
+
+=back
+
+
+=head2 object_before_set
+
+Called during L<Bugzilla::Object/set>, before any actual work is done.
+You can use this to perform actions before a value is changed for
+specific fields on certain types of objects.
+
+Params:
+
+=over
+
+=item C<object>
+
+The object that C<set> was called on. You will probably want to
+do something like C<< if ($object->isa('Some::Class')) >> in your code to
+limit your changes to only certain subclasses of Bugzilla::Object.
+
+=item C<field>
+
+The name of the field being updated in the object.
+
+=item C<value>
+
+The value being set on the object.
+
+=back
+
+=head2 object_columns
+
+This hook allows you to add new "fields" to existing Bugzilla objects,
+that correspond to columns in their tables.
+
+For example, if you added an C<example> column to the "bugs" table, you
+would have to also add an C<example> field to the C<Bugzilla::Bug> object
+in order to access that data via Bug objects.
+
+Don't do anything slow inside this hook--it's called several times on
+every page of Bugzilla.
+
+Params:
+
+=over
+
+=item C<class>
+
+The name of the class that this hook is being called on. You can check this
+like C<< if ($class->isa('Some::Class')) >> in your code, to add new
+fields only for certain classes.
+
+=item C<columns>
+
+An arrayref. Add the string names of columns to this array to add new
+values to objects.
+
+For example, if you add an C<example> column to a particular table
+(using L</install_update_db>), and then push the string C<example> into
+this array for the object that uses that table, then you can access the
+information in that column via C<< $object->{example} >> on all objects
+of that type.
+
+This arrayref does not contain the standard column names--you cannot modify
+or remove standard object columns using this hook.
+
+=back
+
+=head2 object_end_of_create
+
+Called at the end of L<Bugzilla::Object/create>, after all other changes are
+made to the database. This occurs inside a database transaction.
+
+Params:
+
+=over
+
+=item C<class>
+
+The name of the class that C<create> was called on. You can check this
+like C<< if ($class->isa('Some::Class')) >> in your code, to perform specific
+tasks for only certain classes.
+
+=item C<object>
+
+The created object.
+
+=back
+
+=head2 object_end_of_create_validators
+
+Called at the end of L<Bugzilla::Object/run_create_validators>. You can
+use this to run additional validation when creating an object.
+
+If a subclass has overridden C<run_create_validators>, then this usually
+happens I<before> the subclass does its custom validation.
+
+Params:
+
+=over
+
+=item C<class>
+
+The name of the class that C<create> was called on. You can check this
+like C<< if ($class->isa('Some::Class')) >> in your code, to perform specific
+tasks for only certain classes.
+
+=item C<params>
+
+A hashref. The set of named parameters passed to C<create>, modified and
+validated by the C<VALIDATORS> specified for the object.
+
+=back
+
+
+=head2 object_end_of_set
+
+Called during L<Bugzilla::Object/set>, after all the code of the function
+has completed (so the value has been validated and the field has been set
+to the new value). You can use this to perform actions after a value is
+changed for specific fields on certain types of objects.
+
+The new value is not specifically passed to this hook because you can
+get it as C<< $object->{$field} >>.
+
+Params:
+
+=over
+
+=item C<object>
+
+The object that C<set> was called on. You will probably want to
+do something like C<< if ($object->isa('Some::Class')) >> in your code to
+limit your changes to only certain subclasses of Bugzilla::Object.
+
+=item C<field>
+
+The name of the field that was updated in the object.
+
+=back
+
+
+=head2 object_end_of_set_all
+
+This happens at the end of L<Bugzilla::Object/set_all>. This is a
+good place to call custom set_ functions on objects, or to make changes
+to an object before C<update()> is called.
+
+Params:
+
+=over
+
+=item C<object>
+
+The L<Bugzilla::Object> which is being updated. You will probably want to
+do something like C<< if ($object->isa('Some::Class')) >> in your code to
+limit your changes to only certain subclasses of Bugzilla::Object.
+
+=item C<params>
+
+A hashref. The set of named parameters passed to C<set_all>.
+
+=back
+
+=head2 object_end_of_update
+
+Called during L<Bugzilla::Object/update>, after changes are made
+to the database, but while still inside a transaction.
+
+Params:
+
+=over
+
+=item C<object>
+
+The object that C<update> was called on. You will probably want to
+do something like C<< if ($object->isa('Some::Class')) >> in your code to
+limit your changes to only certain subclasses of Bugzilla::Object.
+
+=item C<old_object>
+
+The object as it was before it was updated.
+
+=item C<changes>
+
+The fields that have been changed, in the same format that
+L<Bugzilla::Object/update> returns.
+
+=back
+
+=head2 object_update_columns
+
+If you've added fields to bugs via L</object_columns>, then this
+hook allows you to say which of those columns should be updated in the
+database when L<Bugzilla::Object/update> is called on the object.
+
+If you don't use this hook, then your custom columns won't be modified in
+the database by Bugzilla.
+
+Params:
+
+=over
+
+=item C<object>
+
+The object that is about to be updated. You should check this
+like C<< if ($object->isa('Some::Class')) >> in your code, to modify
+the "update columns" only for certain classes.
+
+=item C<columns>
+
+An arrayref. Add the string names of columns to this array to allow
+that column to be updated when C<update()> is called on the object.
+
+This arrayref does not contain the standard column names--you cannot stop
+standard columns from being updated by using this hook.
+
+=back
+
+=head2 object_validators
+
+Allows you to add new items to L<Bugzilla::Object/VALIDATORS> for
+particular classes.
+
+Params:
+
+=over
+
+=item C<class>
+
+The name of the class that C<VALIDATORS> was called on. You can check this
+like C<< if ($class->isa('Some::Class')) >> in your code, to add
+validators only for certain classes.
+
+=item C<validators>
+
+A hashref, where the keys are database columns and the values are subroutine
+references. You can add new validators or modify existing ones. If you modify
+an existing one, you should remember to call the original validator
+inside of your modified validator. (This way, several extensions can all
+modify the same validator.)
+
+=back
+
+
+=head2 page_before_template
+
+This is a simple way to add your own pages to Bugzilla. This hooks C<page.cgi>,
+which loads templates from F<template/en/default/pages>. For example,
+C<page.cgi?id=fields.html> loads F<template/en/default/pages/fields.html.tmpl>.
+
+This hook is called right before the template is loaded, so that you can
+pass your own variables to your own pages.
+
+You can also use this to implement complex custom pages, by doing your own
+output and then calling C<exit> at the end of the hook, thus preventing
+the normal C<page.cgi> behavior from occurring.
+
+Params:
+
+=over
+
+=item C<page_id>
+
+This is the name of the page being loaded, like C<fields.html>.
+
+Note that if two extensions use the same name, it is uncertain which will
+override the others, so you should be careful with how you name your pages.
+Usually extensions prefix their pages with a directory named after their
+extension, so for an extension named "Foo", page ids usually look like
+C<foo/mypage.html>.
+
+=item C<vars>
+
+This is a hashref--put variables into here if you want them passed to
+your template.
+
+=back
+
+
+=head2 post_bug_after_creation
+
+B<DEPRECATED> (Use L</bug_end_of_create> instead.)
+
+This happens after a bug is created and before bug mail is sent
+during C<post_bug.cgi>. Note that this only happens during C<post_bug.cgi>,
+it doesn't happen during any of the other methods of creating a bug.
+
+Params:
+
+=over
+
+=item C<vars> - The template vars hashref.
+
+=back
+
+
+=head2 product_confirm_delete
+
+B<DEPRECATED> - Use L</template_before_process> instead.
Called before displaying the confirmation message when deleting a product.
@@ -348,6 +1315,178 @@
=back
+=head2 product_end_of_create
+
+Called right after a new product has been created, allowing additional
+changes to be made to the new product's attributes. This occurs inside of
+a database transaction, so if the hook throws an error, all previous
+changes will be rolled back, including the creation of the new product.
+(However, note that such rollbacks should not normally be used, as
+some databases that Bugzilla supports have very bad rollback performance.
+If you want to validate input and throw errors before the Product is created,
+use L</object_end_of_create_validators> instead, or add a validator
+using L</object_validators>.)
+
+Params:
+
+=over
+
+=item C<product> - The new L<Bugzilla::Product> object that was just created.
+
+=back
+
+=head2 quicksearch_map
+
+This hook allows you to alter the Quicksearch syntax to include e.g. special
+searches for custom fields you have.
+
+Params:
+
+=over
+
+=item C<map> - a hash where the key is the name you want to use in
+Quicksearch, and the value is the name from the C<fielddefs> table that you
+want it to map to. You can modify existing mappings or add new ones.
+
+=back
+
+=head2 sanitycheck_check
+
+This hook allows for extra sanity checks to be added, for use by
+F<sanitycheck.cgi>.
+
+Params:
+
+=over
+
+=item C<status> - a CODEREF that allows status messages to be displayed
+to the user. (F<sanitycheck.cgi>'s C<Status>)
+
+=back
+
+=head2 sanitycheck_repair
+
+This hook allows for extra sanity check repairs to be made, for use by
+F<sanitycheck.cgi>.
+
+Params:
+
+=over
+
+=item C<status> - a CODEREF that allows status messages to be displayed
+to the user. (F<sanitycheck.cgi>'s C<Status>)
+
+=back
+
+=head2 template_before_create
+
+This hook allows you to modify the configuration of L<Bugzilla::Template>
+objects before they are created. For example, you could add a new
+global template variable this way.
+
+Params:
+
+=over
+
+=item C<config>
+
+A hashref--the configuration that will be passed to L<Template/new>.
+See L<http://template-toolkit.org/docs/modules/Template.html#section_CONFIGURATION_SUMMARY>
+for information on how this configuration variable is structured (or just
+look at the code for C<create> in L<Bugzilla::Template>.)
+
+=back
+
+=head2 template_before_process
+
+This hook is called any time Bugzilla processes a template file, including
+calls to C<< $template->process >>, C<PROCESS> statements in templates,
+and C<INCLUDE> statements in templates. It is not called when templates
+process a C<BLOCK>, only when they process a file.
+
+This hook allows you to define additional variables that will be available to
+the template being processed, or to modify the variables that are currently
+in the template. It works exactly as though you inserted code to modify
+template variables at the top of a template.
+
+You probably want to restrict this hook to operating only if a certain
+file is being processed (which is why you get a C<file> argument
+below). Otherwise, modifying the C<vars> argument will affect every single
+template in Bugzilla.
+
+B<Note:> This hook is not called if you are already in this hook.
+(That is, it won't call itself recursively.) This prevents infinite
+recursion in situations where this hook needs to process a template
+(such as if this hook throws an error).
+
+Params:
+
+=over
+
+=item C<vars>
+
+This is the entire set of variables that the current template can see.
+Technically, this is a L<Template::Stash> object, but you can just
+use it like a hashref if you want.
+
+=item C<file>
+
+The name of the template file being processed. This is relative to the
+main template directory for the language (i.e. for
+F<template/en/default/bug/show.html.tmpl>, this variable will contain
+C<bug/show.html.tmpl>).
+
+=item C<context>
+
+A L<Template::Context> object. Usually you will not have to use this, but
+if you need information about the template itself (other than just its
+name), you can get it from here.
+
+=back
+
+=head2 user_preferences
+
+This hook allows you to add additional panels to the User Preferences page,
+and validate data displayed and returned from these panels. It works in
+combination with the C<tabs> hook available in the
+F<template/en/default/account/prefs/prefs.html.tmpl> template. To make it
+work, you must define two templates in your extension:
+F<extensions/Foo/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl>
+contains a list of additional panels to include.
+F<extensions/Foo/template/en/default/account/prefs/bar.html.tmpl> contains
+the content of the panel itself. See the C<Example> extension to see how
+things work.
+
+Params:
+
+=over
+
+=item C<current_tab>
+
+The name of the current panel being viewed by the user. You should always
+make sure that the name of the panel matches what you expect it to be.
+Else you could be interacting with the panel of another extension.
+
+=item C<save_changes>
+
+A boolean which is true when data should be validated and the DB updated
+accordingly. This means the user clicked the "Submit Changes" button.
+
+=item C<handled>
+
+This is a B<reference> to a scalar, not a scalar. (So you would set it like
+C<$$handled = 1>, not like C<$handled = 1>.) Set this to a true value to let
+Bugzilla know that the passed-in panel is valid and that you have handled it.
+(Otherwise, Bugzilla will throw an error that the panel is invalid.) Don't set
+this to true if you didn't handle the panel listed in C<current_tab>.
+
+=item C<vars>
+
+You can add as many new key/value pairs as you want to this hashref.
+It will be passed to the template.
+
+=back
+
=head2 webservice
This hook allows you to add your own modules to the WebService. (See
@@ -359,20 +1498,20 @@
=item C<dispatch>
-A hashref that you can specify the names of your modules and what Perl
+A hashref where you can specify the names of your modules and which Perl
module handles the functions for that module. (This is actually sent to
L<SOAP::Lite/dispatch_with>. You can see how that's used in F<xmlrpc.cgi>.)
-The Perl module name must start with C<extensions::yourextension::lib::>
-(replace C<yourextension> with the name of your extension). The C<package>
-declaration inside that module must also start with
-C<extensions::yourextension::lib::> in that module's code.
+The Perl module name will most likely start with C<Bugzilla::Extension::Foo::>
+(where "Foo" is the name of your extension).
Example:
- $dispatch->{Example} = "extensions::example::lib::Example";
+ $dispatch->{'Example.Blah'} = "Bugzilla::Extension::Example::Webservice::Blah";
-And then you'd have a module F<extensions/example/lib/Example.pm>
+And then you'd have a module F<extensions/Example/lib/Webservice/Blah.pm>,
+and could call methods from that module like C<Example.Blah.Foo()> using
+the WebServices interface.
It's recommended that all the keys you put in C<dispatch> start with the
name of your extension, so that you don't conflict with the standard Bugzilla
@@ -381,7 +1520,7 @@
=back
-=head2 webservice-error_codes
+=head2 webservice_error_codes
If your webservice extension throws custom errors, you can set numeric
codes for those errors here.
@@ -399,3 +1538,7 @@
See L<Bugzilla::WebService::Constants/WS_ERROR_CODE> for an example.
=back
+
+=head1 SEE ALSO
+
+L<Bugzilla::Extension>
diff --git a/Websites/bugs.webkit.org/Bugzilla/Install.pm b/Websites/bugs.webkit.org/Bugzilla/Install.pm
index c70f8a8..ce8fe6b 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Install.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Install.pm
@@ -26,6 +26,8 @@
use strict;
+use Bugzilla::Component;
+use Bugzilla::Config qw(:admin);
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Group;
@@ -35,6 +37,24 @@
use Bugzilla::Util qw(get_text);
use Bugzilla::Version;
+use constant STATUS_WORKFLOW => (
+ [undef, 'UNCONFIRMED'],
+ [undef, 'CONFIRMED'],
+ [undef, 'IN_PROGRESS'],
+ ['UNCONFIRMED', 'CONFIRMED'],
+ ['UNCONFIRMED', 'IN_PROGRESS'],
+ ['UNCONFIRMED', 'RESOLVED'],
+ ['CONFIRMED', 'IN_PROGRESS'],
+ ['CONFIRMED', 'RESOLVED'],
+ ['IN_PROGRESS', 'CONFIRMED'],
+ ['IN_PROGRESS', 'RESOLVED'],
+ ['RESOLVED', 'UNCONFIRMED'],
+ ['RESOLVED', 'CONFIRMED'],
+ ['RESOLVED', 'VERIFIED'],
+ ['VERIFIED', 'UNCONFIRMED'],
+ ['VERIFIED', 'CONFIRMED'],
+);
+
sub SETTINGS {
return {
# 2005-03-03 travis@sedsystems.ca -- Bug 41972
@@ -62,7 +82,17 @@
default => ${Bugzilla->languages}[0] },
# 2007-07-02 altlist@gmail.com -- Bug 225731
quote_replies => { options => ['quoted_reply', 'simple_reply', 'off'],
- default => "quoted_reply" }
+ default => "quoted_reply" },
+ # 2009-02-01 mozilla@matt.mchenryfamily.org -- Bug 398473
+ comment_box_position => { options => ['before_comments', 'after_comments'],
+ default => 'before_comments' },
+ # 2008-08-27 LpSolit@gmail.com -- Bug 182238
+ timezone => { subclass => 'Timezone', default => 'local' },
+ # 2011-02-07 dkl@mozilla.com -- Bug 580490
+ quicksearch_fulltext => { options => ['on', 'off'], default => 'on' },
+ # 2011-06-21 glob@mozilla.com -- Bug 589128
+ email_format => { options => ['html', 'text_only'],
+ default => 'html' },
}
};
@@ -105,14 +135,29 @@
description => 'Can confirm a bug or mark it a duplicate'
},
{
- name => 'bz_canusewhines',
- description => 'User can configure whine reports for self'
+ name => 'bz_canusewhineatothers',
+ description => 'Can configure whine reports for other users',
+ },
+ {
+ name => 'bz_canusewhines',
+ description => 'User can configure whine reports for self',
+ # inherited_by means that users in the groups listed below are
+ # automatically members of bz_canusewhines.
+ inherited_by => ['editbugs', 'bz_canusewhineatothers'],
},
{
name => 'bz_sudoers',
- description => 'Can perform actions as other users'
+ description => 'Can perform actions as other users',
},
- # There are also other groups created in update_system_groups.
+ {
+ name => 'bz_sudo_protect',
+ description => 'Can not be impersonated by other users',
+ inherited_by => ['bz_sudoers'],
+ },
+ {
+ name => 'bz_quip_moderators',
+ description => 'Can moderate quips',
+ },
);
use constant DEFAULT_CLASSIFICATION => {
@@ -124,7 +169,10 @@
name => 'TestProduct',
description => 'This is a test product.'
. ' This ought to be blown away and replaced with real stuff in a'
- . ' finished installation of bugzilla.'
+ . ' finished installation of bugzilla.',
+ version => Bugzilla::Version::DEFAULT_VERSION,
+ classification => 'Unclassified',
+ defaultmilestone => DEFAULT_MILESTONE,
};
use constant DEFAULT_COMPONENT => {
@@ -135,136 +183,117 @@
};
sub update_settings {
+ my $dbh = Bugzilla->dbh;
+ # If we're setting up settings for the first time, we want to be quieter.
+ my $any_settings = $dbh->selectrow_array(
+ 'SELECT 1 FROM setting ' . $dbh->sql_limit(1));
+ if (!$any_settings) {
+ print get_text('install_setting_setup'), "\n";
+ }
+
my %settings = %{SETTINGS()};
foreach my $setting (keys %settings) {
add_setting($setting,
$settings{$setting}->{options},
$settings{$setting}->{default},
- $settings{$setting}->{subclass});
+ $settings{$setting}->{subclass}, undef,
+ !$any_settings);
}
}
sub update_system_groups {
my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+
+ # If there is no editbugs group, this is the first time we're
+ # adding groups.
+ my $editbugs_exists = new Bugzilla::Group({ name => 'editbugs' });
+ if (!$editbugs_exists) {
+ print get_text('install_groups_setup'), "\n";
+ }
+
# Create most of the system groups
foreach my $definition (SYSTEM_GROUPS) {
my $exists = new Bugzilla::Group({ name => $definition->{name} });
- $definition->{isbuggroup} = 0;
- Bugzilla::Group->create($definition) unless $exists;
- }
-
- # Certain groups need something done after they are created. We do
- # that here.
-
- # Make sure people who can whine at others can also whine.
- if (!new Bugzilla::Group({name => 'bz_canusewhineatothers'})) {
- my $whineatothers = Bugzilla::Group->create({
- name => 'bz_canusewhineatothers',
- description => 'Can configure whine reports for other users',
- isbuggroup => 0 });
- my $whine = new Bugzilla::Group({ name => 'bz_canusewhines' });
-
- $dbh->do('INSERT INTO group_group_map (grantor_id, member_id)
- VALUES (?,?)', undef, $whine->id, $whineatothers->id);
- }
-
- # Make sure sudoers are automatically protected from being sudoed.
- if (!new Bugzilla::Group({name => 'bz_sudo_protect'})) {
- my $sudo_protect = Bugzilla::Group->create({
- name => 'bz_sudo_protect',
- description => 'Can not be impersonated by other users',
- isbuggroup => 0 });
- my $sudo = new Bugzilla::Group({ name => 'bz_sudoers' });
- $dbh->do('INSERT INTO group_group_map (grantor_id, member_id)
- VALUES (?,?)', undef, $sudo_protect->id, $sudo->id);
- }
-
- # Re-evaluate all regexps, to keep them up-to-date.
- my $sth = $dbh->prepare(
- "SELECT profiles.userid, profiles.login_name, groups.id,
- groups.userregexp, user_group_map.group_id
- FROM (profiles CROSS JOIN groups)
- LEFT JOIN user_group_map
- ON user_group_map.user_id = profiles.userid
- AND user_group_map.group_id = groups.id
- AND user_group_map.grant_type = ?
- WHERE userregexp != '' OR user_group_map.group_id IS NOT NULL");
-
- my $sth_add = $dbh->prepare(
- "INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
- VALUES (?, ?, 0, " . GRANT_REGEXP . ")");
-
- my $sth_del = $dbh->prepare(
- "DELETE FROM user_group_map
- WHERE user_id = ? AND group_id = ? AND isbless = 0
- AND grant_type = " . GRANT_REGEXP);
-
- $sth->execute(GRANT_REGEXP);
- while (my ($uid, $login, $gid, $rexp, $present) = $sth->fetchrow_array()) {
- if ($login =~ m/$rexp/i) {
- $sth_add->execute($uid, $gid) unless $present;
- } else {
- $sth_del->execute($uid, $gid) if $present;
+ if (!$exists) {
+ $definition->{isbuggroup} = 0;
+ $definition->{silently} = !$editbugs_exists;
+ my $inherited_by = delete $definition->{inherited_by};
+ my $created = Bugzilla::Group->create($definition);
+ # Each group in inherited_by is automatically a member of this
+ # group.
+ if ($inherited_by) {
+ foreach my $name (@$inherited_by) {
+ my $member = Bugzilla::Group->check($name);
+ $dbh->do('INSERT INTO group_group_map (grantor_id,
+ member_id) VALUES (?,?)',
+ undef, $created->id, $member->id);
+ }
+ }
}
}
+ $dbh->bz_commit_transaction();
+}
+
+sub create_default_classification {
+ my $dbh = Bugzilla->dbh;
+
+ # Make the default Classification if it doesn't already exist.
+ if (!$dbh->selectrow_array('SELECT 1 FROM classifications')) {
+ print get_text('install_default_classification',
+ { name => DEFAULT_CLASSIFICATION->{name} }) . "\n";
+ Bugzilla::Classification->create(DEFAULT_CLASSIFICATION);
+ }
}
# This function should be called only after creating the admin user.
sub create_default_product {
my $dbh = Bugzilla->dbh;
- # Make the default Classification if it doesn't already exist.
- if (!$dbh->selectrow_array('SELECT 1 FROM classifications')) {
- my $class = DEFAULT_CLASSIFICATION;
- print get_text('install_default_classification',
- { name => $class->{name} }) . "\n";
- $dbh->do('INSERT INTO classifications (name, description)
- VALUES (?, ?)',
- undef, $class->{name}, $class->{description});
- }
-
# And same for the default product/component.
if (!$dbh->selectrow_array('SELECT 1 FROM products')) {
- my $default_prod = DEFAULT_PRODUCT;
print get_text('install_default_product',
- { name => $default_prod->{name} }) . "\n";
+ { name => DEFAULT_PRODUCT->{name} }) . "\n";
- $dbh->do(q{INSERT INTO products (name, description)
- VALUES (?,?)},
- undef, $default_prod->{name}, $default_prod->{description});
+ my $product = Bugzilla::Product->create(DEFAULT_PRODUCT);
- my $product = new Bugzilla::Product({name => $default_prod->{name}});
-
- # The default version.
- Bugzilla::Version::create(Bugzilla::Version::DEFAULT_VERSION, $product);
-
- # And we automatically insert the default milestone.
- $dbh->do(q{INSERT INTO milestones (product_id, value, sortkey)
- SELECT id, defaultmilestone, 0
- FROM products});
-
- # Get the user who will be the owner of the Product.
- # We pick the admin with the lowest id, or we insert
- # an invalid "0" into the database, just so that we can
- # create the component.
+ # Get the user who will be the owner of the Component.
+ # We pick the admin with the lowest id, which is probably the
+ # admin checksetup.pl just created.
my $admin_group = new Bugzilla::Group({name => 'admin'});
my ($admin_id) = $dbh->selectrow_array(
'SELECT user_id FROM user_group_map WHERE group_id = ?
ORDER BY user_id ' . $dbh->sql_limit(1),
- undef, $admin_group->id) || 0;
-
- my $default_comp = DEFAULT_COMPONENT;
+ undef, $admin_group->id);
+ my $admin = Bugzilla::User->new($admin_id);
- $dbh->do("INSERT INTO components (name, product_id, description,
- initialowner)
- VALUES (?, ?, ?, ?)", undef, $default_comp->{name},
- $product->id, $default_comp->{description}, $admin_id);
+ Bugzilla::Component->create({
+ %{ DEFAULT_COMPONENT() }, product => $product,
+ initialowner => $admin->login });
}
}
+sub init_workflow {
+ my $dbh = Bugzilla->dbh;
+ my $has_workflow = $dbh->selectrow_array('SELECT 1 FROM status_workflow');
+ return if $has_workflow;
+
+ print get_text('install_workflow_init'), "\n";
+
+ my %status_ids = @{ $dbh->selectcol_arrayref(
+ 'SELECT value, id FROM bug_status', {Columns=>[1,2]}) };
+
+ foreach my $pair (STATUS_WORKFLOW) {
+ my $old_id = $pair->[0] ? $status_ids{$pair->[0]} : undef;
+ my $new_id = $status_ids{$pair->[1]};
+ $dbh->do('INSERT INTO status_workflow (old_status, new_status)
+ VALUES (?,?)', undef, $old_id, $new_id);
+ }
+}
+
sub create_admin {
my ($params) = @_;
my $dbh = Bugzilla->dbh;
@@ -272,7 +301,7 @@
my $admin_group = new Bugzilla::Group({ name => 'admin' });
my $admin_inheritors =
- Bugzilla::User->flatten_group_membership($admin_group->id);
+ Bugzilla::Group->flatten_group_membership($admin_group->id);
my $admin_group_ids = join(',', @$admin_inheritors);
my ($admin_count) = $dbh->selectrow_array(
@@ -325,14 +354,12 @@
$user = ref($user) ? $user
: new Bugzilla::User(login_to_id($user, THROW_ERROR));
- my $admin_group = new Bugzilla::Group({ name => 'admin' });
-
- # Admins get explicit membership and bless capability for the admin group
- $dbh->selectrow_array("SELECT id FROM groups WHERE name = 'admin'");
-
my $group_insert = $dbh->prepare(
'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
VALUES (?, ?, ?, ?)');
+
+ # Admins get explicit membership and bless capability for the admin group
+ my $admin_group = new Bugzilla::Group({ name => 'admin' });
# These are run in an eval so that we can ignore the error of somebody
# already being granted these things.
eval {
@@ -349,7 +376,15 @@
$group_insert->execute($user->id, $editusers->id, 0, GRANT_DIRECT);
};
- print "\n", get_text('install_admin_created', { user => $user }), "\n";
+ # If there is no maintainer set, make this user the maintainer.
+ if (!Bugzilla->params->{'maintainer'}) {
+ SetParam('maintainer', $user->email);
+ write_params();
+ }
+
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ print "\n", get_text('install_admin_created', { user => $user }), "\n";
+ }
}
sub _prompt_for_password {
@@ -440,9 +475,14 @@
Returns: nothing.
+=item C<create_default_classification>
+
+Creates the default "Unclassified" L<Classification|Bugzilla::Classification>
+if it doesn't already exist
+
=item C<create_default_product()>
-Description: Creates the default product and classification if
+Description: Creates the default product and component if
they don't exist.
Params: none
diff --git a/Websites/bugs.webkit.org/Bugzilla/Install/CPAN.pm b/Websites/bugs.webkit.org/Bugzilla/Install/CPAN.pm
index b37e6d4..31bd7f8 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Install/CPAN.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Install/CPAN.pm
@@ -21,16 +21,49 @@
package Bugzilla::Install::CPAN;
use strict;
use base qw(Exporter);
-our @EXPORT = qw(set_cpan_config install_module BZ_LIB);
+our @EXPORT = qw(
+ BZ_LIB
+
+ check_cpan_requirements
+ set_cpan_config
+ install_module
+);
use Bugzilla::Constants;
+use Bugzilla::Install::Requirements qw(have_vers);
use Bugzilla::Install::Util qw(bin_loc install_string);
+use Config;
use CPAN;
use Cwd qw(abs_path);
use File::Path qw(rmtree);
use List::Util qw(shuffle);
+# These are required for install-module.pl to be able to install
+# all modules properly.
+use constant REQUIREMENTS => (
+ {
+ module => 'CPAN',
+ package => 'CPAN',
+ version => '1.81',
+ },
+ {
+ # When Module::Build isn't installed, the YAML module allows
+ # CPAN to read META.yml to determine that Module::Build first
+ # needs to be installed to compile a module.
+ module => 'YAML',
+ package => 'YAML',
+ version => 0,
+ },
+ {
+ # Many modules on CPAN are now built with Dist::Zilla, which
+ # unfortunately means they require this version of EU::MM to install.
+ module => 'ExtUtils::MakeMaker',
+ package => 'ExtUtils-MakeMaker',
+ version => '6.31',
+ },
+);
+
# We need the absolute path of ext_libpath, because CPAN chdirs around
# and so we can't use a relative directory.
#
@@ -46,13 +79,16 @@
auto_commit => 0,
# We always force builds, so there's no reason to cache them.
build_cache => 0,
+ build_requires_install_policy => 'yes',
cache_metadata => 1,
+ colorize_output => 1,
+ colorize_print => 'bold',
index_expire => 1,
scan_cache => 'atstart',
inhibit_startup_message => 1,
- mbuild_install_build_command => './Build',
+ bzip2 => bin_loc('bzip2'),
curl => bin_loc('curl'),
gzip => bin_loc('gzip'),
links => bin_loc('links'),
@@ -67,14 +103,65 @@
http://cpan.pair.com/
http://mirror.hiwaay.net/CPAN/
ftp://ftp.dc.aleron.net/pub/CPAN/
- http://perl.secsup.org/
- http://mirrors.kernel.org/cpan/)],
+ http://mirrors.kernel.org/cpan/
+ http://mirrors2.kernel.org/cpan/)],
};
+sub check_cpan_requirements {
+ my ($original_dir, $original_args) = @_;
+
+ _require_compiler();
+
+ my @install;
+ foreach my $module (REQUIREMENTS) {
+ my $installed = have_vers($module, 1);
+ push(@install, $module) if !$installed;
+ }
+
+ return if !@install;
+
+ my $restart_required;
+ foreach my $module (@install) {
+ $restart_required = 1 if $module->{module} eq 'CPAN';
+ install_module($module->{module}, 1);
+ }
+
+ if ($restart_required) {
+ chdir $original_dir;
+ exec($^X, $0, @$original_args);
+ }
+}
+
+sub _require_compiler {
+ my @errors;
+
+ my $cc_name = $Config{cc};
+ my $cc_exists = bin_loc($cc_name);
+
+ if (!$cc_exists) {
+ push(@errors, install_string('install_no_compiler'));
+ }
+
+ my $make_name = $CPAN::Config->{make};
+ my $make_exists = bin_loc($make_name);
+
+ if (!$make_exists) {
+ push(@errors, install_string('install_no_make'));
+ }
+
+ die @errors if @errors;
+}
+
sub install_module {
- my ($name, $notest) = @_;
+ my ($name, $test) = @_;
my $bzlib = BZ_LIB;
+ # Make Module::AutoInstall install all dependencies and never prompt.
+ local $ENV{PERL_AUTOINSTALL} = '--alldeps';
+ # This makes Net::SSLeay not prompt the user, if it gets installed.
+ # It also makes any other MakeMaker prompts accept their defaults.
+ local $ENV{PERL_MM_USE_DEFAULT} = 1;
+
# Certain modules require special stuff in order to not prompt us.
my $original_makepl = $CPAN::Config->{makepl_arg};
# This one's a regex in case we're doing Template::Plugin::GD and it
@@ -85,21 +172,34 @@
elsif ($name eq 'XML::Twig') {
$CPAN::Config->{makepl_arg} = "-n $original_makepl";
}
- elsif ($name eq 'Net::LDAP') {
- $CPAN::Config->{makepl_arg} .= " --skipdeps";
- }
elsif ($name eq 'SOAP::Lite') {
$CPAN::Config->{makepl_arg} .= " --noprompt";
}
my $module = CPAN::Shell->expand('Module', $name);
+ if (!$module) {
+ die install_string('no_such_module', { module => $name }) . "\n";
+ }
+ my $version = $module->cpan_version;
+ my $module_name = $name;
+
+ if ($name eq 'LWP::UserAgent' && $^V lt v5.8.8) {
+ # LWP 6.x requires Perl 5.8.8 or newer.
+ # As PAUSE only indexes the very last version of each module,
+ # we have to specify the path to the tarball ourselves.
+ $name = 'GAAS/libwww-perl-5.837.tar.gz';
+ # This tarball contains LWP::UserAgent 5.835.
+ $version = '5.835';
+ }
+
print install_string('install_module',
- { module => $name, version => $module->cpan_version }) . "\n";
- if ($notest) {
- CPAN::Shell->notest('install', $name);
+ { module => $module_name, version => $version }) . "\n";
+
+ if ($test) {
+ CPAN::Shell->force('install', $name);
}
else {
- CPAN::Shell->force('install', $name);
+ CPAN::Shell->notest('install', $name);
}
# If it installed any binaries in the Bugzilla directory, delete them.
@@ -137,7 +237,7 @@
# If we can't make one, we finally try to use the Bugzilla directory.
if (!-w $dir) {
- print "WARNING: Using the Bugzilla directory as the CPAN home.\n";
+ print STDERR install_string('cpan_bugzilla_home'), "\n";
$dir = "$bzlib/.cpan";
}
}
@@ -152,6 +252,8 @@
# Unless specified, we install the modules into the Bugzilla directory.
if (!$do_global) {
+ require Config;
+
$CPAN::Config->{makepl_arg} .= " LIB=\"$bzlib\""
. " INSTALLMAN1DIR=\"$bzlib/man/man1\""
. " INSTALLMAN3DIR=\"$bzlib/man/man3\""
@@ -162,7 +264,10 @@
# INSTALLDIRS=perl is set because that makes sure that MakeMaker
# always uses the directories we've specified here.
. " INSTALLDIRS=perl";
- $CPAN::Config->{mbuild_arg} = "--install_base \"$bzlib\"";
+ $CPAN::Config->{mbuild_arg} = " --install_base \"$bzlib\""
+ . " --install_path lib=\"$bzlib\""
+ . " --install_path arch=\"$bzlib/$Config::Config{archname}\"";
+ $CPAN::Config->{mbuild_install_arg} = $CPAN::Config->{mbuild_arg};
# When we're not root, sometimes newer versions of CPAN will
# try to read/modify things that belong to root, unless we set
@@ -213,7 +318,7 @@
use Bugzilla::Install::CPAN;
set_cpan_config();
- install_module('Module::Name', 1);
+ install_module('Module::Name');
=head1 DESCRIPTION
@@ -240,8 +345,9 @@
=item C<$name> - The name of the module, just like you'd pass to the
C<install> command in the CPAN shell.
-=item C<$notest> - If true, we skip running tests on this module. This
-can greatly speed up the installation time.
+=item C<$test> - If true, we run tests on this module before installing,
+but we still force the install if the tests fail. This is only used
+when we internally install a newer CPAN module.
=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/Install/DB.pm b/Websites/bugs.webkit.org/Bugzilla/Install/DB.pm
index 1bda135..6b9dd65 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Install/DB.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Install/DB.pm
@@ -24,13 +24,19 @@
use Bugzilla::Constants;
use Bugzilla::Hook;
+use Bugzilla::Install ();
use Bugzilla::Install::Util qw(indicate_progress install_string);
use Bugzilla::Util;
use Bugzilla::Series;
+use Bugzilla::BugUrl;
+use Bugzilla::Field;
use Date::Parse;
use Date::Format;
use IO::File;
+use List::MoreUtils qw(uniq);
+use URI;
+use URI::QueryParam;
# NOTE: This is NOT the function for general table updates. See
# update_table_definitions for that. This is only for the fielddefs table.
@@ -86,6 +92,41 @@
}
}
+ $dbh->bz_add_column('fielddefs', 'visibility_field_id', {TYPE => 'INT3'});
+ $dbh->bz_add_column('fielddefs', 'value_field_id', {TYPE => 'INT3'});
+ $dbh->bz_add_index('fielddefs', 'fielddefs_value_field_id_idx',
+ ['value_field_id']);
+
+ # Bug 344878
+ if (!$dbh->bz_column_info('fielddefs', 'buglist')) {
+ $dbh->bz_add_column('fielddefs', 'buglist',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ # Set non-multiselect custom fields as valid buglist fields
+ # Note that default fields will be handled in Field.pm
+ $dbh->do('UPDATE fielddefs SET buglist = 1 WHERE custom = 1 AND type != ' . FIELD_TYPE_MULTI_SELECT);
+ }
+
+ #2008-08-26 elliotte_martin@yahoo.com - Bug 251556
+ $dbh->bz_add_column('fielddefs', 'reverse_desc', {TYPE => 'TINYTEXT'});
+
+ $dbh->do('UPDATE fielddefs SET buglist = 1
+ WHERE custom = 1 AND type = ' . FIELD_TYPE_MULTI_SELECT);
+
+ $dbh->bz_add_column('fielddefs', 'is_mandatory',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_add_index('fielddefs', 'fielddefs_is_mandatory_idx',
+ ['is_mandatory']);
+
+ # 2010-04-05 dkl@redhat.com - Bug 479400
+ _migrate_field_visibility_value();
+
+ $dbh->bz_add_column('fielddefs', 'is_numeric',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->do('UPDATE fielddefs SET is_numeric = 1 WHERE type = '
+ . FIELD_TYPE_BUG_ID);
+
+ Bugzilla::Hook::process('install_update_db_fielddefs');
+
# Remember, this is not the function for adding general table changes.
# That is below. Add new changes to the fielddefs table above this
# comment.
@@ -125,7 +166,6 @@
_add_bug_vote_cache();
_update_product_name_definition();
- _add_bug_keyword_cache();
$dbh->bz_add_column('profiles', 'disabledtext',
{TYPE => 'MEDIUMTEXT', NOTNULL => 1}, '');
@@ -148,11 +188,6 @@
$dbh->bz_add_column('bugs', 'everconfirmed',
{TYPE => 'BOOLEAN', NOTNULL => 1}, 1);
- $dbh->bz_add_column('products', 'maxvotesperbug',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '10000'});
- $dbh->bz_add_column('products', 'votestoconfirm',
- {TYPE => 'INT2', NOTNULL => 1}, 0);
-
_populate_milestones_table();
# 2000-03-22 Changed the default value for target_milestone to be "---"
@@ -341,10 +376,10 @@
# Add defaults for some fields that should have them but didn't.
$dbh->bz_alter_column('bugs', 'status_whiteboard',
{TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
- $dbh->bz_alter_column('bugs', 'keywords',
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
- $dbh->bz_alter_column('bugs', 'votes',
- {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'});
+ if ($dbh->bz_column_info('bugs', 'votes')) {
+ $dbh->bz_alter_column('bugs', 'votes',
+ {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'});
+ }
$dbh->bz_alter_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'});
@@ -398,23 +433,16 @@
_fix_attachments_submitter_id_idx();
_copy_attachments_thedata_to_attach_data();
_fix_broken_all_closed_series();
-
# 2005-08-14 bugreport@peshkin.net -- Bug 304583
# Get rid of leftover DERIVED group permissions
use constant GRANT_DERIVED => 1;
$dbh->do("DELETE FROM user_group_map WHERE grant_type = " . GRANT_DERIVED);
+ _rederive_regex_groups();
+
# PUBLIC is a reserved word in Oracle.
$dbh->bz_rename_column('series', 'public', 'is_public');
- # 2005-09-28 bugreport@peshkin.net Bug 149504
- $dbh->bz_add_column('attachments', 'isurl',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
-
- # 2005-10-21 LpSolit@gmail.com - Bug 313020
- $dbh->bz_add_column('namedqueries', 'query_type',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
-
# 2005-11-04 LpSolit@gmail.com - Bug 305927
$dbh->bz_alter_column('groups', 'userregexp',
{TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"});
@@ -441,14 +469,21 @@
_move_data_nomail_into_db();
# The products table lacked sensible defaults.
- $dbh->bz_alter_column('products', 'milestoneurl',
- {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"});
- $dbh->bz_alter_column('products', 'disallownew',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
- $dbh->bz_alter_column('products', 'votesperuser',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
- $dbh->bz_alter_column('products', 'votestoconfirm',
- {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ if ($dbh->bz_column_info('products', 'milestoneurl')) {
+ $dbh->bz_alter_column('products', 'milestoneurl',
+ {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"});
+ }
+ if ($dbh->bz_column_info('products', 'disallownew')){
+ $dbh->bz_alter_column('products', 'disallownew',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
+
+ if ($dbh->bz_column_info('products', 'votesperuser')) {
+ $dbh->bz_alter_column('products', 'votesperuser',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_alter_column('products', 'votestoconfirm',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ }
+ }
# 2006-08-04 LpSolit@gmail.com - Bug 305941
$dbh->bz_drop_column('profiles', 'refreshed_when');
@@ -501,7 +536,7 @@
_fix_uppercase_index_names();
# 2007-05-17 LpSolit@gmail.com - Bug 344965
- _initialize_workflow($old_params);
+ _initialize_workflow_for_upgrade($old_params);
# 2007-08-08 LpSolit@gmail.com - Bug 332149
$dbh->bz_add_column('groups', 'icon_url', {TYPE => 'TINYTEXT'});
@@ -515,10 +550,6 @@
# 2007-09-09 LpSolit@gmail.com - Bug 99215
_fix_attachment_modification_date();
- # This had the wrong definition in DB::Schema.
- $dbh->bz_alter_column('namedqueries', 'query_type',
- {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
-
$dbh->bz_drop_index('longdescs', 'longdescs_thetext_idx');
_populate_bugs_fulltext();
@@ -526,11 +557,130 @@
$dbh->bz_alter_column('series', 'query',
{ TYPE => 'MEDIUMTEXT', NOTNULL => 1 });
+ # Add FK to multi select field tables
+ _add_foreign_keys_to_multiselects();
+
+ # 2008-07-28 tfu@redhat.com - Bug 431669
+ $dbh->bz_alter_column('group_control_map', 'product_id',
+ { TYPE => 'INT2', NOTNULL => 1 });
+
+ # 2008-09-07 LpSolit@gmail.com - Bug 452893
+ _fix_illegal_flag_modification_dates();
+
+ _add_visiblity_value_to_value_tables();
+
+ # 2009-03-02 arbingersys@gmail.com - Bug 423613
+ _add_extern_id_index();
+
+ # 2009-03-31 LpSolit@gmail.com - Bug 478972
+ $dbh->bz_alter_column('group_control_map', 'entry',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_alter_column('group_control_map', 'canedit',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+
+ # 2009-01-16 oreomike@gmail.com - Bug 302420
+ $dbh->bz_add_column('whine_events', 'mailifnobugs',
+ { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+
+ _convert_disallownew_to_isactive();
+
+ $dbh->bz_alter_column('bugs_activity', 'added',
+ { TYPE => 'varchar(255)' });
+ $dbh->bz_add_index('bugs_activity', 'bugs_activity_added_idx', ['added']);
+
+ # 2009-09-28 LpSolit@gmail.com - Bug 519032
+ $dbh->bz_drop_column('series', 'last_viewed');
+
+ # 2009-09-28 LpSolit@gmail.com - Bug 399073
+ _fix_logincookies_ipaddr();
+
+ # 2009-11-01 LpSolit@gmail.com - Bug 525025
+ _fix_invalid_custom_field_names();
+
+ _set_attachment_comment_types();
+
+ $dbh->bz_drop_column('products', 'milestoneurl');
+
+ _add_allows_unconfirmed_to_product_table();
+ _convert_flagtypes_fks_to_set_null();
+ _fix_decimal_types();
+ _fix_series_creator_fk();
+
+ # 2009-11-14 dkl@redhat.com - Bug 310450
+ $dbh->bz_add_column('bugs_activity', 'comment_id', {TYPE => 'INT3'});
+
+ # 2010-04-07 LpSolit@gmail.com - Bug 69621
+ $dbh->bz_drop_column('bugs', 'keywords');
+
+ # 2010-05-07 ewong@pw-wspx.org - Bug 463945
+ $dbh->bz_alter_column('group_control_map', 'membercontrol',
+ {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA});
+ $dbh->bz_alter_column('group_control_map', 'othercontrol',
+ {TYPE => 'INT1', NOTNULL => 1, DEFAULT => CONTROLMAPNA});
+
+ # Add NOT NULL to some columns that need it, and DEFAULT to
+ # attachments.ispatch.
+ $dbh->bz_alter_column('attachments', 'ispatch',
+ { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+ $dbh->bz_alter_column('keyworddefs', 'description',
+ { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, '');
+ $dbh->bz_alter_column('products', 'description',
+ { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, '');
+
+ # Change the default of allows_unconfirmed to TRUE as part
+ # of the new workflow.
+ $dbh->bz_alter_column('products', 'allows_unconfirmed',
+ { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE' });
+
+ # 2010-07-18 LpSolit@gmail.com - Bug 119703
+ _remove_attachment_isurl();
+
+ # 2009-05-07 ghendricks@novell.com - Bug 77193
+ _add_isactive_to_product_fields();
+
+ # 2010-10-09 LpSolit@gmail.com - Bug 505165
+ $dbh->bz_alter_column('flags', 'setter_id', {TYPE => 'INT3', NOTNULL => 1});
+
+ # 2010-10-09 LpSolit@gmail.com - Bug 451735
+ _fix_series_indexes();
+
+ $dbh->bz_add_column('bug_see_also', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+
+ _rename_tags_to_tag();
+
+ # 2011-01-29 LpSolit@gmail.com - Bug 616185
+ _migrate_user_tags();
+
+ _populate_bug_see_also_class();
+
+ # 2011-06-15 dkl@mozilla.com - Bug 658929
+ _migrate_disabledtext_boolean();
+
+ # 2011-10-11 miketosh - Bug 690173
+ _on_delete_set_null_for_audit_log_userid();
+
+ # 2011-11-28 dkl@mozilla.com - Bug 685611
+ _fix_notnull_defaults();
+
+ # 2012-02-15 LpSolit@gmail.com - Bug 722113
+ if ($dbh->bz_index_info('profile_search', 'profile_search_user_id')) {
+ $dbh->bz_drop_index('profile_search', 'profile_search_user_id');
+ $dbh->bz_add_index('profile_search', 'profile_search_user_id_idx', [qw(user_id)]);
+ }
+
################################################################
# New --TABLE-- changes should go *** A B O V E *** this point #
################################################################
- Bugzilla::Hook::process('install-update_db');
+ Bugzilla::Hook::process('install_update_db');
+
+ # We do this here because otherwise the foreign key from
+ # products.classification_id to classifications.id will fail
+ # (because products.classification_id defaults to "1", so on upgraded
+ # installations it's already been set before the first Classification
+ # exists).
+ Bugzilla::Install::create_default_classification();
$dbh->bz_setup_foreign_keys();
}
@@ -549,10 +699,11 @@
$dbh->bz_add_column('bugs', 'qa_contact', {TYPE => 'INT3'});
$dbh->bz_add_column('bugs', 'status_whiteboard',
{TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
- $dbh->bz_add_column('products', 'disallownew',
- {TYPE => 'BOOLEAN', NOTNULL => 1}, 0);
- $dbh->bz_add_column('products', 'milestoneurl',
- {TYPE => 'TINYTEXT', NOTNULL => 1}, '');
+ if (!$dbh->bz_column_info('products', 'isactive')){
+ $dbh->bz_add_column('products', 'disallownew',
+ {TYPE => 'BOOLEAN', NOTNULL => 1}, 0);
+ }
+
$dbh->bz_add_column('components', 'initialqacontact',
{TYPE => 'TINYTEXT'});
$dbh->bz_add_column('components', 'description',
@@ -570,14 +721,14 @@
# (P.S. All is not lost; it appears that the latest betas of MySQL
# support a new table format which will allow 32 indices.)
- $dbh->bz_drop_column('bugs', 'area');
- if (!$dbh->bz_column_info('bugs', 'votes')) {
+ if ($dbh->bz_column_info('bugs', 'area')) {
+ $dbh->bz_drop_column('bugs', 'area');
$dbh->bz_add_column('bugs', 'votes', {TYPE => 'INT3', NOTNULL => 1,
DEFAULT => 0});
$dbh->bz_add_index('bugs', 'bugs_votes_idx', [qw(votes)]);
+ $dbh->bz_add_column('products', 'votesperuser',
+ {TYPE => 'INT2', NOTNULL => 1}, 0);
}
- $dbh->bz_add_column('products', 'votesperuser',
- {TYPE => 'INT2', NOTNULL => 1}, 0);
}
sub _update_product_name_definition {
@@ -604,46 +755,6 @@
}
}
-sub _add_bug_keyword_cache {
- my $dbh = Bugzilla->dbh;
- # 2000-01-16 Added a "keywords" field to the bugs table, which
- # contains a string copy of the entries of the keywords table for this
- # bug. This is so that I can easily sort and display a keywords
- # column in bug lists.
-
- if (!$dbh->bz_column_info('bugs', 'keywords')) {
- $dbh->bz_add_column('bugs', 'keywords',
- {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"});
-
- my @kwords;
- print "Making sure 'keywords' field of table 'bugs' is empty...\n";
- $dbh->do("UPDATE bugs SET keywords = '' WHERE keywords != ''");
- print "Repopulating 'keywords' field of table 'bugs'...\n";
- my $sth = $dbh->prepare("SELECT keywords.bug_id, keyworddefs.name " .
- "FROM keywords, keyworddefs " .
- "WHERE keyworddefs.id = keywords.keywordid " .
- "ORDER BY keywords.bug_id, keyworddefs.name");
- $sth->execute;
- my @list;
- my $bugid = 0;
- my @row;
- while (1) {
- my ($b, $k) = ($sth->fetchrow_array());
- if (!defined $b || $b ne $bugid) {
- if (@list) {
- $dbh->do("UPDATE bugs SET keywords = " .
- $dbh->quote(join(', ', @list)) .
- " WHERE bug_id = $bugid");
- }
- last if !$b;
- $bugid = $b;
- @list = ();
- }
- push(@list, $k);
- }
- }
-}
-
# A helper for the function below.
sub _write_one_longdesc {
my ($id, $who, $when, $buffer) = (@_);
@@ -812,9 +923,11 @@
["votes", "who"],
["longdescs", "who"]) {
my ($table, $field) = (@$i);
- print " Updating $table.$field...\n";
- $dbh->do("UPDATE $table SET $field = $u1 " .
- "WHERE $field = $u2");
+ if ($dbh->bz_table_info($table)) {
+ print " Updating $table.$field...\n";
+ $dbh->do("UPDATE $table SET $field = $u1 " .
+ "WHERE $field = $u2");
+ }
}
$dbh->do("DELETE FROM profiles WHERE userid = $u2");
}
@@ -972,6 +1085,7 @@
# 2000-11-27 For Bugzilla 2.5 and later. Copy data from 'comments' to
# 'longdescs' - the new name of the comments table.
if ($dbh->bz_table_info('comments')) {
+ print "Copying data from 'comments' to 'longdescs'...\n";
my $quoted_when = $dbh->quote_identifier('when');
$dbh->do("INSERT INTO longdescs (bug_when, bug_id, who, thetext)
SELECT $quoted_when, bug_id, who, comment
@@ -1179,13 +1293,14 @@
#
# Use the ip, not the hostname, in the logincookies table
if ($dbh->bz_column_info("logincookies", "hostname")) {
+ print "Clearing the logincookies table...\n";
# We've changed what we match against, so all entries are now invalid
$dbh->do("DELETE FROM logincookies");
# Now update the logincookies schema
$dbh->bz_drop_column("logincookies", "hostname");
$dbh->bz_add_column("logincookies", "ipaddr",
- {TYPE => 'varchar(40)', NOTNULL => 1}, '');
+ {TYPE => 'varchar(40)'});
}
}
@@ -1204,15 +1319,7 @@
$dbh->do("INSERT INTO quips (quip) VALUES (?)", undef, $_);
}
- print <<EOT;
-
-Quips are now stored in the database, rather than in an external file.
-The quips previously stored in $datadir/comments have been copied into
-the database, and that file has been renamed to $datadir/comments.bak
-You may delete the renamed file once you have confirmed that all your
-quips were moved successfully.
-
-EOT
+ print "\n", install_string('update_quips', { data => $datadir }), "\n";
$comments->close;
rename("$datadir/comments", "$datadir/comments.bak")
|| warn "Failed to rename: $!";
@@ -1786,9 +1893,9 @@
if (length($tryflagname) > 50) {
my $lastchanceflagname = (substr $tryflagname, 0, 47) . '...';
if (defined($flagtypes{$lastchanceflagname})) {
- print " ... last attempt as \"$lastchanceflagname\" still failed.'\n",
- "Rename the flag by hand and run checksetup.pl again.\n";
- die("Bad flag type name $flagname");
+ print " ... last attempt as \"$lastchanceflagname\" still failed.'\n";
+ die install_string('update_flags_bad_name',
+ { flag => $flagname }), "\n";
}
$tryflagname = $lastchanceflagname;
}
@@ -1804,14 +1911,20 @@
sub _setup_usebuggroups_backward_compatibility {
my $dbh = Bugzilla->dbh;
+
+ # Don't run this on newer Bugzillas. This is a reliable test because
+ # the longdescs table existed in 2.16 (which had usebuggroups)
+ # but not in 2.18, and this code happens between 2.16 and 2.18.
+ return if $dbh->bz_column_info('longdescs', 'already_wrapped');
+
# 2002-11-24 - bugreport@peshkin.net - bug 147275
#
# If group_control_map is empty, backward-compatibility
# usebuggroups-equivalent records should be created.
- my $entry = Bugzilla->params->{'useentrygroupdefault'};
my ($maps_exist) = $dbh->selectrow_array(
"SELECT DISTINCT 1 FROM group_control_map");
if (!$maps_exist) {
+ print "Converting old usebuggroups controls...\n";
# Initially populate group_control_map.
# First, get all the existing products and their groups.
my $sth = $dbh->prepare("SELECT groups.id, products.id, groups.name,
@@ -1825,11 +1938,9 @@
if ($groupname eq $productname) {
# Product and group have same name.
$dbh->do("INSERT INTO group_control_map " .
- "(group_id, product_id, entry, membercontrol, " .
- "othercontrol, canedit) " .
- "VALUES ($groupid, $productid, $entry, " .
- CONTROLMAPDEFAULT . ", " .
- CONTROLMAPNA . ", 0)");
+ "(group_id, product_id, membercontrol, othercontrol) " .
+ "VALUES (?, ?, ?, ?)", undef,
+ ($groupid, $productid, CONTROLMAPDEFAULT, CONTROLMAPNA));
} else {
# See if this group is a product group at all.
my $sth2 = $dbh->prepare("SELECT id FROM products
@@ -1840,11 +1951,9 @@
# If there is no product with the same name as this
# group, then it is permitted for all products.
$dbh->do("INSERT INTO group_control_map " .
- "(group_id, product_id, entry, membercontrol, " .
- "othercontrol, canedit) " .
- "VALUES ($groupid, $productid, 0, " .
- CONTROLMAPSHOWN . ", " .
- CONTROLMAPNA . ", 0)");
+ "(group_id, product_id, membercontrol, othercontrol) " .
+ "VALUES (?, ?, ?, ?)", undef,
+ ($groupid, $productid, CONTROLMAPSHOWN, CONTROLMAPNA));
}
}
}
@@ -1916,9 +2025,11 @@
my $all_name = "-All-";
my $open_name = "All Open";
+ $dbh->bz_start_transaction();
my $products = $dbh->selectall_arrayref("SELECT name FROM products");
foreach my $product ((map { $_->[0] } @$products), "-All-") {
+ print "$product:\n";
# First, create the series
my %queries;
my %seriesids;
@@ -1967,8 +2078,9 @@
my %data;
my $last_date = "";
- while (<$in>) {
- if (/^(\d+\|.*)/) {
+ my @lines = <$in>;
+ while (my $line = shift @lines) {
+ if ($line =~ /^(\d+\|.*)/) {
my @numbers = split(/\||\r/, $1);
# Only take the first line for each date; it was possible to
@@ -1991,6 +2103,9 @@
$in->close;
+ my $total_items = (scalar(@fields) + 1)
+ * scalar(keys %{ $data{'NEW'} });
+ my $count = 0;
foreach my $field (@fields, $open_name) {
# Insert values into series_data: series_id, date, value
my %fielddata = %{$data{$field}};
@@ -2002,6 +2117,8 @@
# We prepared this above
$seriesdatasth->execute($seriesids{$field},
$date, $fielddata{$date} || 0);
+ indicate_progress({ total => $total_items,
+ current => ++$count, every => 100 });
}
}
@@ -2028,6 +2145,8 @@
}
}
}
+
+ $dbh->bz_commit_transaction();
}
}
@@ -2096,7 +2215,7 @@
# and attachment.cgi now takes them out, but old ones need converting.
my $ref = $dbh->bz_column_info("attachments", "filename");
if ($ref->{TYPE} ne 'varchar(100)') {
- print "Removing paths from filenames in attachments table...\n";
+ print "Removing paths from filenames in attachments table...";
my $sth = $dbh->prepare("SELECT attach_id, filename FROM attachments " .
"WHERE " . $dbh->sql_position(q{'/'}, 'filename') . " > 0 OR " .
@@ -2112,8 +2231,6 @@
print "Done.\n";
- print "Resizing attachments.filename from mediumtext to",
- " varchar(100).\n";
$dbh->bz_alter_column("attachments", "filename",
{TYPE => 'varchar(100)', NOTNULL => 1});
}
@@ -2127,9 +2244,9 @@
#
# Renaming the 'count' column in the votes table because Sybase doesn't
# like it
- if ($dbh->bz_column_info('votes', 'count')) {
- $dbh->bz_rename_column('votes', 'count', 'vote_count');
- }
+ return if !$dbh->bz_table_info('votes');
+ return if $dbh->bz_column_info('votes', 'count');
+ $dbh->bz_rename_column('votes', 'count', 'vote_count');
}
sub _fix_group_with_empty_name {
@@ -2169,17 +2286,9 @@
my ($source, $target) = @_;
my $dbh = Bugzilla->dbh;
- my $sth1 = $dbh->prepare("SELECT user_id, relationship FROM email_setting
- WHERE event = $source");
- my $sth2 = $dbh->prepare("INSERT into email_setting " .
- "(user_id, relationship, event) VALUES (" .
- "?, ?, $target)");
-
- $sth1->execute();
-
- while (my ($userid, $relationship) = $sth1->fetchrow_array()) {
- $sth2->execute($userid, $relationship);
- }
+ $dbh->do("INSERT INTO email_setting (user_id, relationship, event)
+ SELECT user_id, relationship, $target FROM email_setting
+ WHERE event = $source");
}
sub _migrate_email_prefs_to_new_table {
@@ -2195,7 +2304,9 @@
"Reporter" => REL_REPORTER,
"QAcontact" => REL_QA,
"CClist" => REL_CC,
- "Voter" => REL_VOTER);
+ # REL_VOTER was "4" before it was moved to an
+ # extension.
+ "Voter" => 4);
my %events = ("Removeme" => EVT_ADDED_REMOVED,
"Comments" => EVT_COMMENT,
@@ -2295,10 +2406,11 @@
foreach my $desc (keys %events) {
my $event = $events{$desc};
- my $sth = $dbh->prepare("SELECT COUNT(*) FROM email_setting
- WHERE event = $event");
- $sth->execute();
- if (!($sth->fetchrow_arrayref()->[0])) {
+ my $have_events = $dbh->selectrow_array(
+ "SELECT 1 FROM email_setting WHERE event = $event "
+ . $dbh->sql_limit(1));
+
+ if (!$have_events) {
# No settings in the table yet, so we assume that this is the
# first time it's being set.
print "Initializing \"$desc\" email_setting ...\n";
@@ -2562,6 +2674,54 @@
} # if (@$broken_nonopen_series)
}
+# This needs to happen at two times: when we upgrade from 2.16 (thus creating
+# user_group_map), and when we kill derived gruops in the DB.
+sub _rederive_regex_groups {
+ my $dbh = Bugzilla->dbh;
+
+ my $regex_groups_exist = $dbh->selectrow_array(
+ "SELECT 1 FROM groups WHERE userregexp = '' " . $dbh->sql_limit(1));
+ return if !$regex_groups_exist;
+
+ my $regex_derivations = $dbh->selectrow_array(
+ 'SELECT 1 FROM user_group_map WHERE grant_type = ' . GRANT_REGEXP
+ . ' ' . $dbh->sql_limit(1));
+ return if $regex_derivations;
+
+ print "Deriving regex group memberships...\n";
+
+ # Re-evaluate all regexps, to keep them up-to-date.
+ my $sth = $dbh->prepare(
+ "SELECT profiles.userid, profiles.login_name, groups.id,
+ groups.userregexp, user_group_map.group_id
+ FROM (profiles CROSS JOIN groups)
+ LEFT JOIN user_group_map
+ ON user_group_map.user_id = profiles.userid
+ AND user_group_map.group_id = groups.id
+ AND user_group_map.grant_type = ?
+ WHERE userregexp != '' OR user_group_map.group_id IS NOT NULL");
+
+ my $sth_add = $dbh->prepare(
+ "INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
+ VALUES (?, ?, 0, " . GRANT_REGEXP . ")");
+
+ my $sth_del = $dbh->prepare(
+ "DELETE FROM user_group_map
+ WHERE user_id = ? AND group_id = ? AND isbless = 0
+ AND grant_type = " . GRANT_REGEXP);
+
+ $sth->execute(GRANT_REGEXP);
+ while (my ($uid, $login, $gid, $rexp, $present) =
+ $sth->fetchrow_array())
+ {
+ if ($login =~ m/$rexp/i) {
+ $sth_add->execute($uid, $gid) unless $present;
+ } else {
+ $sth_del->execute($uid, $gid) if $present;
+ }
+ }
+}
+
sub _clean_control_characters_from_short_desc {
my $dbh = Bugzilla->dbh;
@@ -2619,12 +2779,7 @@
FROM bugs WHERE CHAR_LENGTH(short_desc) > 255');
if (@$long_summary_bugs) {
- print <<EOT;
-
-WARNING: Some of your bugs had summaries longer than 255 characters.
-They have had their original summary copied into a comment, and then
-the summary was truncated to 255 characters. The affected bug numbers were:
-EOT
+ print "\n", install_string('update_summary_truncated');
my $comment_sth = $dbh->prepare(
'INSERT INTO longdescs (bug_id, who, thetext, bug_when)
VALUES (?, ?, ?, NOW())');
@@ -2633,10 +2788,9 @@
my @affected_bugs;
foreach my $bug (@$long_summary_bugs) {
my ($bug_id, $summary, $reporter_id) = @$bug;
- my $summary_comment = "The original summary for this bug"
- . " was longer than 255 characters, and so it was truncated"
- . " when Bugzilla was upgraded. The original summary was:"
- . "\n\n$summary";
+ my $summary_comment =
+ install_string('update_summary_truncate_comment',
+ { summary => $summary });
$comment_sth->execute($bug_id, $reporter_id, $summary_comment);
my $short_summary = substr($summary, 0, 252) . "...";
$desc_sth->execute($short_summary, $bug_id);
@@ -2709,24 +2863,20 @@
SET disable_mail = 1
WHERE userid = ?');
foreach my $user_to_check (keys %nomail) {
- my $uid;
- if ($uid = Bugzilla::User::login_to_id($user_to_check)) {
- my $user = new Bugzilla::User($uid);
- print "\tDisabling email for user ", $user->email, "\n";
- $query->execute($user->id);
- delete $nomail{$user->email};
- }
+ my $uid = $dbh->selectrow_array(
+ 'SELECT userid FROM profiles WHERE login_name = ?',
+ undef, $user_to_check);
+ next if !$uid;
+ print "\tDisabling email for user $user_to_check\n";
+ $query->execute($uid);
+ delete $nomail{$user_to_check};
}
# If there are any nomail entries remaining, move them to nomail.bad
# and say something to the user.
if (scalar(keys %nomail)) {
- print <<EOT;
-
-WARNING: The following users were listed in data/nomail, but do not
-have an account here. The unmatched entries have been moved
-to $datadir/nomail.bad:
-EOT
+ print "\n", install_string('update_nomail_bad',
+ { data => $datadir }), "\n";
my $nomail_bad = new IO::File("$datadir/nomail.bad", '>>');
foreach my $unknown_user (keys %nomail) {
print "\t$unknown_user\n";
@@ -2795,7 +2945,7 @@
}
}
-sub _initialize_workflow {
+sub _initialize_workflow_for_upgrade {
my $old_params = shift;
my $dbh = Bugzilla->dbh;
@@ -2808,11 +2958,8 @@
# and mark these statuses as 'closed', even if some of these statuses are
# expected to be open statuses. Bug statuses we have no information about
# are left as 'open'.
- my @closed_statuses =
- @{$dbh->selectcol_arrayref('SELECT DISTINCT bug_status FROM bugs
- WHERE resolution != ?', undef, '')};
-
- # Append the default list of closed statuses *unless* we detect at least
+ #
+ # We append the default list of closed statuses *unless* we detect at least
# one closed state in the DB (i.e. with is_open = 0). This would mean that
# the DB has already been updated at least once and maybe the admin decided
# that e.g. 'RESOLVED' is now an open state, in which case we don't want to
@@ -2823,6 +2970,9 @@
WHERE is_open = 0');
if (!$num_closed_states) {
+ my @closed_statuses =
+ @{$dbh->selectcol_arrayref('SELECT DISTINCT bug_status FROM bugs
+ WHERE resolution != ?', undef, '')};
@closed_statuses =
map {$dbh->quote($_)} (@closed_statuses, qw(RESOLVED VERIFIED CLOSED));
@@ -2831,6 +2981,11 @@
join(', ', @closed_statuses) . ')');
}
+ # We only populate the workflow here if we're upgrading from a version
+ # before 4.0 (which is where init_workflow was added). This was the
+ # first schema change done for 4.0, so we check this.
+ return if $dbh->bz_column_info('bugs_activity', 'comment_id');
+
# Populate the status_workflow table. We do nothing if the table already
# has entries. If all bug status transitions have been deleted, the
# workflow will be restored to its default schema.
@@ -2850,7 +3005,7 @@
# confirmed bugs, so we use this parameter here.
my $reassign = $old_params->{'commentonreassign'} || 0;
- # This is the default workflow.
+ # This is the default workflow for upgrading installations.
my @workflow = ([undef, 'UNCONFIRMED', $create],
[undef, 'NEW', $create],
[undef, 'ASSIGNED', $create],
@@ -2894,7 +3049,25 @@
# Make sure the bug status used by the 'duplicate_or_move_bug_status'
# parameter has all the required transitions set.
- Bugzilla::Status::add_missing_bug_status_transitions();
+ my $dup_status = Bugzilla->params->{'duplicate_or_move_bug_status'};
+ my $status_id = $dbh->selectrow_array(
+ 'SELECT id FROM bug_status WHERE value = ?', undef, $dup_status);
+ # There's a minor chance that this status isn't in the DB.
+ $status_id || return;
+
+ my $missing_statuses = $dbh->selectcol_arrayref(
+ 'SELECT id FROM bug_status
+ LEFT JOIN status_workflow ON old_status = id
+ AND new_status = ?
+ WHERE old_status IS NULL', undef, $status_id);
+
+ my $sth = $dbh->prepare('INSERT INTO status_workflow
+ (old_status, new_status) VALUES (?, ?)');
+
+ foreach my $old_status_id (@$missing_statuses) {
+ next if ($old_status_id == $status_id);
+ $sth->execute($old_status_id, $status_id);
+ }
}
sub _make_lang_setting_dynamic {
@@ -2975,11 +3148,11 @@
WHERE CHAR_LENGTH($field_name) > ?", {Columns=>[1,2]}, $max_length) };
if (scalar keys %contents) {
- print install_string('install_data_too_long',
- { column => $field_name,
- id_column => $id_field,
- table => $table_name,
- max_length => $max_length });
+ my $error = install_string('install_data_too_long',
+ { column => $field_name,
+ id_column => $id_field,
+ table => $table_name,
+ max_length => $max_length });
foreach my $id (keys %contents) {
my $string = $contents{$id};
# Don't dump the whole string--it could be 16MB.
@@ -2987,67 +3160,529 @@
$string = substr($string, 0, 30) . "..."
. substr($string, -30) . "\n";
}
- print "$id: $string\n";
+ $error .= "$id: $string\n";
}
- exit 3;
+ die $error;
}
}
+sub _add_foreign_keys_to_multiselects {
+ my $dbh = Bugzilla->dbh;
+
+ my $names = $dbh->selectcol_arrayref(
+ 'SELECT name
+ FROM fielddefs
+ WHERE type = ' . FIELD_TYPE_MULTI_SELECT);
+
+ foreach my $name (@$names) {
+ $dbh->bz_add_fk("bug_$name", "bug_id",
+ {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'});
+
+ $dbh->bz_add_fk("bug_$name", "value",
+ {TABLE => $name, COLUMN => 'value', DELETE => 'RESTRICT'});
+ }
+}
+
+# This subroutine is used in multiple places (for times when we update
+# the text of comments), so it takes an argument, $bug_ids, which causes
+# it to update bugs_fulltext for those bug_ids instead of populating the
+# whole table.
sub _populate_bugs_fulltext {
+ my $bug_ids = shift;
my $dbh = Bugzilla->dbh;
my $fulltext = $dbh->selectrow_array('SELECT 1 FROM bugs_fulltext '
. $dbh->sql_limit(1));
- # We only populate the table if it's empty...
- if (!$fulltext) {
- # ... and if there are bugs in the bugs table.
- my $bug_ids = $dbh->selectcol_arrayref('SELECT bug_id FROM bugs');
+ # We only populate the table if it's empty or if we've been given a
+ # set of bug ids.
+ if ($bug_ids or !$fulltext) {
+ $bug_ids ||= $dbh->selectcol_arrayref('SELECT bug_id FROM bugs');
+ # If there are no bugs in the bugs table, there's nothing to populate.
return if !@$bug_ids;
+ my $num_bugs = scalar @$bug_ids;
- # Populating bugs_fulltext can be very slow for large installs,
- # so we special-case any DB that supports GROUP_CONCAT, which is
- # a much faster way to do things.
- if (UNIVERSAL::can($dbh, 'sql_group_concat')) {
- print "Populating bugs_fulltext...";
- print " (this can take a long time.)\n";
- $dbh->do(
- q{INSERT INTO bugs_fulltext (bug_id, short_desc, comments,
- comments_noprivate)
- SELECT bugs.bug_id, bugs.short_desc, }
- . $dbh->sql_group_concat('longdescs.thetext', '\'\n\'')
- . ', ' . $dbh->sql_group_concat('nopriv.thetext', '\'\n\'') .
- q{ FROM bugs
- LEFT JOIN longdescs
- ON bugs.bug_id = longdescs.bug_id
- LEFT JOIN longdescs AS nopriv
- ON longdescs.comment_id = nopriv.comment_id
- AND nopriv.isprivate = 0 }
- . $dbh->sql_group_by('bugs.bug_id', 'bugs.short_desc'));
- }
- # The slow way, without group_concat.
- else {
- print "Populating bugs_fulltext.short_desc...\n";
- $dbh->do('INSERT INTO bugs_fulltext (bug_id, short_desc)
- SELECT bug_id, short_desc FROM bugs');
-
- my $count = 1;
- my $sth_all = $dbh->prepare('SELECT thetext FROM longdescs
- WHERE bug_id = ?');
- my $sth_nopriv = $dbh->prepare(
- 'SELECT thetext FROM longdescs
- WHERE bug_id = ? AND isprivate = 0');
- my $sth_update = $dbh->prepare(
- 'UPDATE bugs_fulltext SET comments = ?, comments_noprivate = ?
- WHERE bug_id = ?');
-
- print "Populating bugs_fulltext comment fields...\n";
- foreach my $id (@$bug_ids) {
- my $all = $dbh->selectcol_arrayref($sth_all, undef, $id);
- my $nopriv = $dbh->selectcol_arrayref($sth_nopriv, undef, $id);
- $sth_update->execute(join("\n", @$all), join("\n", @$nopriv), $id);
- indicate_progress({ total => scalar @$bug_ids, every => 100,
- current => $count++ });
+ my $command = "INSERT";
+ my $where = "";
+ if ($fulltext) {
+ print "Updating bugs_fulltext for $num_bugs bugs...\n";
+ $where = "WHERE " . $dbh->sql_in('bugs.bug_id', $bug_ids);
+ # It turns out that doing a REPLACE INTO is up to 10x faster
+ # than any other possible method of updating the table, in MySQL,
+ # which matters a LOT for large installations.
+ if ($dbh->isa('Bugzilla::DB::Mysql')) {
+ $command = "REPLACE";
}
- print "\n";
+ else {
+ $dbh->do("DELETE FROM bugs_fulltext WHERE "
+ . $dbh->sql_in('bug_id', $bug_ids));
+ }
+ }
+ else {
+ print "Populating bugs_fulltext with $num_bugs entries...";
+ print " (this can take a long time.)\n";
+ }
+ my $newline = $dbh->quote("\n");
+ $dbh->do(
+ qq{$command INTO bugs_fulltext (bug_id, short_desc, comments,
+ comments_noprivate)
+ SELECT bugs.bug_id, bugs.short_desc, }
+ . $dbh->sql_group_concat('longdescs.thetext', $newline, 0)
+ . ', ' . $dbh->sql_group_concat('nopriv.thetext', $newline, 0) .
+ qq{ FROM bugs
+ LEFT JOIN longdescs
+ ON bugs.bug_id = longdescs.bug_id
+ LEFT JOIN longdescs AS nopriv
+ ON longdescs.comment_id = nopriv.comment_id
+ AND nopriv.isprivate = 0
+ $where }
+ . $dbh->sql_group_by('bugs.bug_id', 'bugs.short_desc'));
+ }
+}
+
+sub _fix_illegal_flag_modification_dates {
+ my $dbh = Bugzilla->dbh;
+
+ my $rows = $dbh->do('UPDATE flags SET modification_date = creation_date
+ WHERE modification_date < creation_date');
+ # If no rows are affected, $dbh->do returns 0E0 instead of 0.
+ print "$rows flags had an illegal modification date. Fixed!\n" if ($rows =~ /^\d+$/);
+}
+
+sub _add_visiblity_value_to_value_tables {
+ my $dbh = Bugzilla->dbh;
+ my @standard_fields =
+ qw(bug_status resolution priority bug_severity op_sys rep_platform);
+ my $custom_fields = $dbh->selectcol_arrayref(
+ 'SELECT name FROM fielddefs WHERE custom = 1 AND type IN(?,?)',
+ undef, FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT);
+ foreach my $field (@standard_fields, @$custom_fields) {
+ $dbh->bz_add_column($field, 'visibility_value_id', {TYPE => 'INT2'});
+ $dbh->bz_add_index($field, "${field}_visibility_value_id_idx",
+ ['visibility_value_id']);
+ }
+}
+
+sub _add_extern_id_index {
+ my $dbh = Bugzilla->dbh;
+ if (!$dbh->bz_index_info('profiles', 'profiles_extern_id_idx')) {
+ # Some Bugzillas have a multiple empty strings in extern_id,
+ # which need to be converted to NULLs before we add the index.
+ $dbh->do("UPDATE profiles SET extern_id = NULL WHERE extern_id = ''");
+ $dbh->bz_add_index('profiles', 'profiles_extern_id_idx',
+ {TYPE => 'UNIQUE', FIELDS => [qw(extern_id)]});
+ }
+}
+
+sub _convert_disallownew_to_isactive {
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->bz_column_info('products', 'disallownew')){
+ $dbh->bz_add_column('products', 'isactive',
+ { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+
+ # isactive is the boolean reverse of disallownew.
+ $dbh->do('UPDATE products SET isactive = 0 WHERE disallownew = 1');
+ $dbh->do('UPDATE products SET isactive = 1 WHERE disallownew = 0');
+
+ $dbh->bz_drop_column('products','disallownew');
+ }
+}
+
+sub _fix_logincookies_ipaddr {
+ my $dbh = Bugzilla->dbh;
+ return if !$dbh->bz_column_info('logincookies', 'ipaddr')->{NOTNULL};
+
+ $dbh->bz_alter_column('logincookies', 'ipaddr', {TYPE => 'varchar(40)'});
+ $dbh->do('UPDATE logincookies SET ipaddr = NULL WHERE ipaddr = ?',
+ undef, '0.0.0.0');
+}
+
+sub _fix_invalid_custom_field_names {
+ my $fields = Bugzilla->fields({ custom => 1 });
+
+ foreach my $field (@$fields) {
+ next if $field->name =~ /^[a-zA-Z0-9_]+$/;
+ # The field name is illegal and can break the DB. Kill the field!
+ $field->set_obsolete(1);
+ print install_string('update_cf_invalid_name',
+ { field => $field->name }), "\n";
+ eval { $field->remove_from_db(); };
+ warn $@ if $@;
+ }
+}
+
+sub _set_attachment_comment_type {
+ my ($type, $string) = @_;
+ my $dbh = Bugzilla->dbh;
+ # We check if there are any comments of this type already, first,
+ # because this is faster than a full LIKE search on the comments,
+ # and currently this will run every time we run checksetup.
+ my $test = $dbh->selectrow_array(
+ "SELECT 1 FROM longdescs WHERE type = $type " . $dbh->sql_limit(1));
+ return [] if $test;
+ my %comments = @{ $dbh->selectcol_arrayref(
+ "SELECT comment_id, thetext FROM longdescs
+ WHERE thetext LIKE '$string%'",
+ {Columns=>[1,2]}) };
+ my @comment_ids = keys %comments;
+ return [] if !scalar @comment_ids;
+ my $what = "update";
+ if ($type == CMT_ATTACHMENT_CREATED) {
+ $what = "creation";
+ }
+ print "Setting the type field on attachment $what comments...\n";
+ my $sth = $dbh->prepare(
+ 'UPDATE longdescs SET thetext = ?, type = ?, extra_data = ?
+ WHERE comment_id = ?');
+ my $count = 0;
+ my $total = scalar @comment_ids;
+ foreach my $id (@comment_ids) {
+ $count++;
+ my $text = $comments{$id};
+ next if $text !~ /^\Q$string\E(\d+)/;
+ my $attachment_id = $1;
+ my @lines = split("\n", $text);
+ if ($type == CMT_ATTACHMENT_CREATED) {
+ # Now we have to remove the text up until we find a line that's
+ # just a single newline, because the old "Created an attachment"
+ # text included the attachment description underneath it, and in
+ # Bugzillas before 2.20, that could be wrapped into multiple lines,
+ # in the database.
+ while (1) {
+ my $line = shift @lines;
+ last if (!defined $line or trim($line) eq '');
+ }
+ }
+ else {
+ # However, the "From update of attachment" line is always just
+ # one line--the first line of the comment.
+ shift @lines;
+ }
+ $text = join("\n", @lines);
+ $sth->execute($text, $type, $attachment_id, $id);
+ indicate_progress({ total => $total, current => $count,
+ every => 25 });
+ }
+ return \@comment_ids;
+}
+
+sub _set_attachment_comment_types {
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ my $created_ids = _set_attachment_comment_type(
+ CMT_ATTACHMENT_CREATED, 'Created an attachment (id=');
+ my $updated_ids = _set_attachment_comment_type(
+ CMT_ATTACHMENT_UPDATED, '(From update of attachment ');
+ $dbh->bz_commit_transaction();
+ return unless (@$created_ids or @$updated_ids);
+
+ my @comment_ids = (@$created_ids, @$updated_ids);
+
+ my $bug_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT bug_id FROM longdescs WHERE '
+ . $dbh->sql_in('comment_id', \@comment_ids));
+ _populate_bugs_fulltext($bug_ids);
+}
+
+sub _add_allows_unconfirmed_to_product_table {
+ my $dbh = Bugzilla->dbh;
+ if (!$dbh->bz_column_info('products', 'allows_unconfirmed')) {
+ $dbh->bz_add_column('products', 'allows_unconfirmed',
+ { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' });
+ if ($dbh->bz_column_info('products', 'votestoconfirm')) {
+ $dbh->do('UPDATE products SET allows_unconfirmed = 1
+ WHERE votestoconfirm > 0');
+ }
+ }
+}
+
+sub _convert_flagtypes_fks_to_set_null {
+ my $dbh = Bugzilla->dbh;
+ foreach my $column (qw(request_group_id grant_group_id)) {
+ my $fk = $dbh->bz_fk_info('flagtypes', $column);
+ if ($fk and !defined $fk->{DELETE}) {
+ $fk->{DELETE} = 'SET NULL';
+ $dbh->bz_alter_fk('flagtypes', $column, $fk);
+ }
+ }
+}
+
+sub _fix_decimal_types {
+ my $dbh = Bugzilla->dbh;
+ my $type = {TYPE => 'decimal(7,2)', NOTNULL => 1, DEFAULT => '0'};
+ $dbh->bz_alter_column('bugs', 'estimated_time', $type);
+ $dbh->bz_alter_column('bugs', 'remaining_time', $type);
+ $dbh->bz_alter_column('longdescs', 'work_time', $type);
+}
+
+sub _fix_series_creator_fk {
+ my $dbh = Bugzilla->dbh;
+ my $fk = $dbh->bz_fk_info('series', 'creator');
+ if ($fk and $fk->{DELETE} eq 'SET NULL') {
+ $fk->{DELETE} = 'CASCADE';
+ $dbh->bz_alter_fk('series', 'creator', $fk);
+ }
+}
+
+sub _remove_attachment_isurl {
+ my $dbh = Bugzilla->dbh;
+
+ if ($dbh->bz_column_info('attachments', 'isurl')) {
+ # Now all attachments must have a filename.
+ $dbh->do('UPDATE attachments SET filename = ? WHERE isurl = 1',
+ undef, 'url.txt');
+ $dbh->bz_drop_column('attachments', 'isurl');
+ $dbh->do("DELETE FROM fielddefs WHERE name='attachments.isurl'");
+ }
+}
+
+sub _add_isactive_to_product_fields {
+ my $dbh = Bugzilla->dbh;
+
+ # If we add the isactive column all values should start off as active
+ if (!$dbh->bz_column_info('components', 'isactive')) {
+ $dbh->bz_add_column('components', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ }
+
+ if (!$dbh->bz_column_info('versions', 'isactive')) {
+ $dbh->bz_add_column('versions', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ }
+
+ if (!$dbh->bz_column_info('milestones', 'isactive')) {
+ $dbh->bz_add_column('milestones', 'isactive',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ }
+}
+
+sub _migrate_field_visibility_value {
+ my $dbh = Bugzilla->dbh;
+
+ if ($dbh->bz_column_info('fielddefs', 'visibility_value_id')) {
+ print "Populating new field_visibility table...\n";
+
+ $dbh->bz_start_transaction();
+
+ my %results =
+ @{ $dbh->selectcol_arrayref(
+ "SELECT id, visibility_value_id FROM fielddefs
+ WHERE visibility_value_id IS NOT NULL",
+ { Columns => [1,2] }) };
+
+ my $insert_sth =
+ $dbh->prepare("INSERT INTO field_visibility (field_id, value_id)
+ VALUES (?, ?)");
+
+ foreach my $id (keys %results) {
+ $insert_sth->execute($id, $results{$id});
+ }
+
+ $dbh->bz_commit_transaction();
+ $dbh->bz_drop_column('fielddefs', 'visibility_value_id');
+ }
+}
+
+sub _fix_series_indexes {
+ my $dbh = Bugzilla->dbh;
+ return if $dbh->bz_index_info('series', 'series_category_idx');
+
+ $dbh->bz_drop_index('series', 'series_creator_idx');
+
+ # Fix duplicated names under the same category/subcategory before
+ # adding the more restrictive index.
+ my $duplicated_series = $dbh->selectall_arrayref(
+ 'SELECT s1.series_id, s1.category, s1.subcategory, s1.name
+ FROM series AS s1
+ INNER JOIN series AS s2
+ ON s1.category = s2.category
+ AND s1.subcategory = s2.subcategory
+ AND s1.name = s2.name
+ WHERE s1.series_id != s2.series_id');
+ my $sth_series_update = $dbh->prepare('UPDATE series SET name = ? WHERE series_id = ?');
+ my $sth_series_query = $dbh->prepare('SELECT 1 FROM series WHERE name = ?
+ AND category = ? AND subcategory = ?');
+
+ my %renamed_series;
+ foreach my $series (@$duplicated_series) {
+ my ($series_id, $category, $subcategory, $name) = @$series;
+ # Leave the first series alone, then rename duplicated ones.
+ if ($renamed_series{"${category}_${subcategory}_${name}"}++) {
+ print "Renaming series ${category}/${subcategory}/${name}...\n";
+ my $c = 0;
+ my $exists = 1;
+ while ($exists) {
+ $sth_series_query->execute($name . ++$c, $category, $subcategory);
+ $exists = $sth_series_query->fetchrow_array;
+ }
+ $sth_series_update->execute($name . $c, $series_id);
+ }
+ }
+
+ $dbh->bz_add_index('series', 'series_creator_idx', ['creator']);
+ $dbh->bz_add_index('series', 'series_category_idx',
+ {FIELDS => [qw(category subcategory name)], TYPE => 'UNIQUE'});
+}
+
+sub _migrate_user_tags {
+ my $dbh = Bugzilla->dbh;
+ return unless $dbh->bz_column_info('namedqueries', 'query_type');
+
+ my $tags = $dbh->selectall_arrayref('SELECT id, userid, name, query
+ FROM namedqueries
+ WHERE query_type != 0');
+
+ my $sth_tags = $dbh->prepare(
+ 'INSERT INTO tag (user_id, name) VALUES (?, ?)');
+ my $sth_tag_id = $dbh->prepare(
+ 'SELECT id FROM tag WHERE user_id = ? AND name = ?');
+ my $sth_bug_tag = $dbh->prepare('INSERT INTO bug_tag (bug_id, tag_id)
+ VALUES (?, ?)');
+ my $sth_nq = $dbh->prepare('UPDATE namedqueries SET query = ?
+ WHERE id = ?');
+ my $sth_nq_footer = $dbh->prepare(
+ 'DELETE FROM namedqueries_link_in_footer
+ WHERE user_id = ? AND namedquery_id = ?');
+
+ if (scalar @$tags) {
+ print install_string('update_queries_to_tags'), "\n";
+ }
+
+ my $total = scalar(@$tags);
+ my $current = 0;
+
+ $dbh->bz_start_transaction();
+ foreach my $tag (@$tags) {
+ my ($query_id, $user_id, $name, $query) = @$tag;
+ # Tags are all lowercase.
+ my $tag_name = lc($name);
+
+ $sth_tags->execute($user_id, $tag_name);
+
+ my $tag_id = $dbh->selectrow_array($sth_tag_id,
+ undef, $user_id, $tag_name);
+
+ indicate_progress({ current => ++$current, total => $total,
+ every => 25 });
+
+ my $uri = URI->new("buglist.cgi?$query", 'http');
+ my $bug_id_list = $uri->query_param_delete('bug_id');
+ if (!$bug_id_list) {
+ warn "No bug_id param for tag $name from user $user_id: $query";
+ next;
+ }
+ my @bug_ids = split(/[\s,]+/, $bug_id_list);
+ # Make sure that things like "001" get converted to "1"
+ @bug_ids = map { int($_) } @bug_ids;
+ # And remove duplicates
+ @bug_ids = uniq @bug_ids;
+ foreach my $bug_id (@bug_ids) {
+ # If "int" above failed this might be undef. We also
+ # don't want to accept bug 0.
+ next if !$bug_id;
+ $sth_bug_tag->execute($bug_id, $tag_id);
+ }
+
+ # Existing tags may be used in whines, or shared with
+ # other users. So we convert them rather than delete them.
+ $uri->query_param('tag', $tag_name);
+ $sth_nq->execute($uri->query, $query_id);
+ # But we don't keep showing them in the footer.
+ $sth_nq_footer->execute($user_id, $query_id);
+ }
+
+ $dbh->bz_commit_transaction();
+
+ $dbh->bz_drop_column('namedqueries', 'query_type');
+}
+
+sub _populate_bug_see_also_class {
+ my $dbh = Bugzilla->dbh;
+
+ if ($dbh->bz_column_info('bug_see_also', 'class')) {
+ # The length was incorrectly set to 64 instead of 255.
+ $dbh->bz_alter_column('bug_see_also', 'class',
+ {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
+ return;
+ }
+
+ $dbh->bz_add_column('bug_see_also', 'class',
+ {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"}, '');
+
+ my $result = $dbh->selectall_arrayref(
+ "SELECT id, value FROM bug_see_also");
+
+ my $update_sth =
+ $dbh->prepare("UPDATE bug_see_also SET class = ? WHERE id = ?");
+
+ $dbh->bz_start_transaction();
+ foreach my $see_also (@$result) {
+ my ($id, $value) = @$see_also;
+ my $class = Bugzilla::BugUrl->class_for($value);
+ $update_sth->execute($class, $id);
+ }
+ $dbh->bz_commit_transaction();
+}
+
+sub _migrate_disabledtext_boolean {
+ my $dbh = Bugzilla->dbh;
+ if (!$dbh->bz_column_info('profiles', 'is_enabled')) {
+ $dbh->bz_add_column("profiles", 'is_enabled',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+ $dbh->do("UPDATE profiles SET is_enabled = 0
+ WHERE disabledtext != ''");
+ }
+}
+
+sub _rename_tags_to_tag {
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->bz_table_info('tags')) {
+ # If we get here, it's because the schema created "tag" as an empty
+ # table while "tags" still exists. We get rid of the empty
+ # tag table so we can do the rename over the top of it.
+ $dbh->bz_drop_table('tag');
+ $dbh->bz_drop_index('tags', 'tags_user_id_idx');
+ $dbh->bz_rename_table('tags','tag');
+ $dbh->bz_add_index('tag', 'tag_user_id_idx',
+ {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'});
+ }
+ if (my $bug_tag_fk = $dbh->bz_fk_info('bug_tag', 'tag_id')) {
+ # bz_rename_table() didn't handle FKs correctly.
+ if ($bug_tag_fk->{TABLE} eq 'tags') {
+ $bug_tag_fk->{TABLE} = 'tag';
+ $dbh->bz_alter_fk('bug_tag', 'tag_id', $bug_tag_fk);
+ }
+ }
+}
+
+sub _on_delete_set_null_for_audit_log_userid {
+ my $dbh = Bugzilla->dbh;
+ my $fk = $dbh->bz_fk_info('audit_log', 'user_id');
+ if ($fk and !defined $fk->{DELETE}) {
+ $fk->{DELETE} = 'SET NULL';
+ $dbh->bz_alter_fk('audit_log', 'user_id', $fk);
+ }
+}
+
+sub _fix_notnull_defaults {
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_alter_column('bugs', 'bug_file_loc',
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
+ DEFAULT => "''"}, '');
+
+ my $custom_fields = Bugzilla::Field->match({
+ custom => 1, type => [ FIELD_TYPE_FREETEXT, FIELD_TYPE_TEXTAREA ]
+ });
+
+ foreach my $field (@$custom_fields) {
+ if ($field->type == FIELD_TYPE_FREETEXT) {
+ $dbh->bz_alter_column('bugs', $field->name,
+ {TYPE => 'varchar(255)', NOTNULL => 1,
+ DEFAULT => "''"}, '');
+ }
+ if ($field->type == FIELD_TYPE_TEXTAREA) {
+ $dbh->bz_alter_column('bugs', $field->name,
+ {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
+ DEFAULT => "''"}, '');
}
}
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Install/Filesystem.pm b/Websites/bugs.webkit.org/Bugzilla/Install/Filesystem.pm
index 54350be..c5215ec 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Install/Filesystem.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Install/Filesystem.pm
@@ -30,11 +30,14 @@
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Install::Localconfig;
+use Bugzilla::Install::Util qw(install_string);
use Bugzilla::Util;
+use Bugzilla::Hook;
use File::Find;
use File::Path;
use File::Basename;
+use File::Copy qw(move);
use IO::File;
use POSIX ();
@@ -43,18 +46,73 @@
update_filesystem
create_htaccess
fix_all_file_permissions
+ fix_dir_permissions
+ fix_file_permissions
);
+use constant HT_DEFAULT_DENY => <<EOT;
+# nothing in this directory is retrievable unless overridden by an .htaccess
+# in a subdirectory
+deny from all
+EOT
+
+###############
+# Permissions #
+###############
+
+# Used by the permissions "constants" below.
+sub _suexec { Bugzilla->localconfig->{'use_suexec'} };
+sub _group { Bugzilla->localconfig->{'webservergroup'} };
+
+# Writeable by the owner only.
+use constant OWNER_WRITE => 0600;
+# Executable by the owner only.
+use constant OWNER_EXECUTE => 0700;
+# A directory which is only writeable by the owner.
+use constant DIR_OWNER_WRITE => 0700;
+
+# A cgi script that the webserver can execute.
+sub WS_EXECUTE { _group() ? 0750 : 0755 };
+# A file that is read by cgi scripts, but is not ever read
+# directly by the webserver.
+sub CGI_READ { _group() ? 0640 : 0644 };
+# A file that is written to by cgi scripts, but is not ever
+# read or written directly by the webserver.
+sub CGI_WRITE { _group() ? 0660 : 0666 };
+# A file that is served directly by the web server.
+sub WS_SERVE { (_group() and !_suexec()) ? 0640 : 0644 };
+
+# A directory whose contents can be read or served by the
+# webserver (so even directories containing cgi scripts
+# would have this permission).
+sub DIR_WS_SERVE { (_group() and !_suexec()) ? 0750 : 0755 };
+# A directory that is read by cgi scripts, but is never accessed
+# directly by the webserver
+sub DIR_CGI_READ { _group() ? 0750 : 0755 };
+# A directory that is written to by cgi scripts, but where the
+# scripts never needs to overwrite files created by other
+# users.
+sub DIR_CGI_WRITE { _group() ? 0770 : 01777 };
+# A directory that is written to by cgi scripts, where the
+# scripts need to overwrite files created by other users.
+sub DIR_CGI_OVERWRITE { _group() ? 0770 : 0777 };
+
+# This can be combined (using "|") with other permissions for
+# directories that, in addition to their normal permissions (such
+# as DIR_CGI_WRITE) also have content served directly from them
+# (or their subdirectories) to the user, via the webserver.
+sub DIR_ALSO_WS_SERVE { _suexec() ? 0001 : 0 };
+
# This looks like a constant because it effectively is, but
# it has to call other subroutines and read the current filesystem,
# so it's defined as a sub. This is not exported, so it doesn't have
# a perldoc. However, look at the various hashes defined inside this
# function to understand what it returns. (There are comments throughout.)
#
-# The rationale for the file permissions is that the web server generally
-# runs as apache, so the cgi scripts should not be writable for apache,
-# otherwise someone may find it possible to change the cgis when exploiting
-# some security flaw somewhere (not necessarily in Bugzilla!)
+# The rationale for the file permissions is that there is a group the
+# web server executes the scripts as, so the cgi scripts should not be writable
+# by this group. Otherwise someone may find it possible to change the cgis
+# when exploiting some security flaw somewhere (not necessarily in Bugzilla!)
sub FILESYSTEM {
my $datadir = bz_locations()->{'datadir'};
my $attachdir = bz_locations()->{'attachdir'};
@@ -64,33 +122,16 @@
my $libdir = bz_locations()->{'libpath'};
my $extlib = bz_locations()->{'ext_libpath'};
my $skinsdir = bz_locations()->{'skinsdir'};
+ my $localconfig = bz_locations()->{'localconfig'};
+ my $template_cache = bz_locations()->{'template_cache'};
+ my $graphsdir = bz_locations()->{'graphsdir'};
- my $ws_group = Bugzilla->localconfig->{'webservergroup'};
-
- # The set of permissions that we use:
-
- # FILES
- # Executable by the web server
- my $ws_executable = $ws_group ? 0750 : 0755;
- # Executable by the owner only.
- my $owner_executable = 0700;
- # Readable by the web server.
- my $ws_readable = $ws_group ? 0640 : 0644;
- # Readable by the owner only.
- my $owner_readable = 0600;
- # Writeable by the web server.
- my $ws_writeable = $ws_group ? 0660 : 0666;
-
- # DIRECTORIES
- # Readable by the web server.
- my $ws_dir_readable = $ws_group ? 0750 : 0755;
- # Readable only by the owner.
- my $owner_dir_readable = 0700;
- # Writeable by the web server.
- my $ws_dir_writeable = $ws_group ? 0770 : 01777;
- # The web server can overwrite files owned by other users,
- # in this directory.
- my $ws_dir_full_control = $ws_group ? 0770 : 0777;
+ # We want to set the permissions the same for all localconfig files
+ # across all PROJECTs, so we do something special with $localconfig,
+ # lower down in the permissions section.
+ if ($ENV{PROJECT}) {
+ $localconfig =~ s/\.\Q$ENV{PROJECT}\E$//;
+ }
# Note: When being processed by checksetup, these have their permissions
# set in this order: %all_dirs, %recurse_dirs, %all_files.
@@ -102,35 +143,50 @@
# --- FILE PERMISSIONS (Non-created files) --- #
my %files = (
- '*' => { perms => $ws_readable },
- '*.cgi' => { perms => $ws_executable },
- 'whineatnews.pl' => { perms => $ws_executable },
- 'collectstats.pl' => { perms => $ws_executable },
- 'checksetup.pl' => { perms => $owner_executable },
- 'importxml.pl' => { perms => $ws_executable },
- 'runtests.pl' => { perms => $owner_executable },
- 'testserver.pl' => { perms => $ws_executable },
- 'whine.pl' => { perms => $ws_executable },
- 'customfield.pl' => { perms => $owner_executable },
- 'email_in.pl' => { perms => $ws_executable },
- 'sanitycheck.pl' => { perms => $ws_executable },
- 'install-module.pl' => { perms => $owner_executable },
+ '*' => { perms => OWNER_WRITE },
+ # Some .pl files are WS_EXECUTE because we want
+ # users to be able to cron them or otherwise run
+ # them as a secure user, like the webserver owner.
+ '*.cgi' => { perms => WS_EXECUTE },
+ 'whineatnews.pl' => { perms => WS_EXECUTE },
+ 'collectstats.pl' => { perms => WS_EXECUTE },
+ 'importxml.pl' => { perms => WS_EXECUTE },
+ 'testserver.pl' => { perms => WS_EXECUTE },
+ 'whine.pl' => { perms => WS_EXECUTE },
+ 'email_in.pl' => { perms => WS_EXECUTE },
+ 'sanitycheck.pl' => { perms => WS_EXECUTE },
+ 'checksetup.pl' => { perms => OWNER_EXECUTE },
+ 'runtests.pl' => { perms => OWNER_EXECUTE },
+ 'jobqueue.pl' => { perms => OWNER_EXECUTE },
+ 'migrate.pl' => { perms => OWNER_EXECUTE },
+ 'install-module.pl' => { perms => OWNER_EXECUTE },
- 'docs/makedocs.pl' => { perms => $owner_executable },
- 'docs/style.css' => { perms => $ws_readable },
- 'docs/*/rel_notes.txt' => { perms => $ws_readable },
- 'docs/*/README.docs' => { perms => $owner_readable },
- "$datadir/bugzilla-update.xml" => { perms => $ws_writeable },
- "$datadir/params" => { perms => $ws_writeable },
- "$datadir/mailer.testfile" => { perms => $ws_writeable },
+ 'Bugzilla.pm' => { perms => CGI_READ },
+ "$localconfig*" => { perms => CGI_READ },
+ 'bugzilla.dtd' => { perms => WS_SERVE },
+ 'mod_perl.pl' => { perms => WS_SERVE },
+ 'robots.txt' => { perms => WS_SERVE },
+ '.htaccess' => { perms => WS_SERVE },
+
+ 'contrib/README' => { perms => OWNER_WRITE },
+ 'contrib/*/README' => { perms => OWNER_WRITE },
+ 'docs/bugzilla.ent' => { perms => OWNER_WRITE },
+ 'docs/makedocs.pl' => { perms => OWNER_EXECUTE },
+ 'docs/style.css' => { perms => WS_SERVE },
+ 'docs/*/rel_notes.txt' => { perms => WS_SERVE },
+ 'docs/*/README.docs' => { perms => OWNER_WRITE },
+ "$datadir/params" => { perms => CGI_WRITE },
+ "$datadir/old-params.txt" => { perms => OWNER_WRITE },
+ "$extensionsdir/create.pl" => { perms => OWNER_EXECUTE },
+ "$extensionsdir/*/*.pl" => { perms => WS_EXECUTE },
);
# Directories that we want to set the perms on, but not
# recurse through. These are directories we didn't create
# in checkesetup.pl.
my %non_recurse_dirs = (
- '.' => $ws_dir_readable,
- docs => $ws_dir_readable,
+ '.' => DIR_WS_SERVE,
+ docs => DIR_WS_SERVE,
);
# This sets the permissions for each item inside each of these
@@ -139,50 +195,68 @@
# the webserver.
my %recurse_dirs = (
# Writeable directories
- "$datadir/template" => { files => $ws_readable,
- dirs => $ws_dir_full_control },
- $attachdir => { files => $ws_writeable,
- dirs => $ws_dir_writeable },
- $webdotdir => { files => $ws_writeable,
- dirs => $ws_dir_writeable },
- graphs => { files => $ws_writeable,
- dirs => $ws_dir_writeable },
+ $template_cache => { files => CGI_READ,
+ dirs => DIR_CGI_OVERWRITE },
+ $attachdir => { files => CGI_WRITE,
+ dirs => DIR_CGI_WRITE },
+ $webdotdir => { files => WS_SERVE,
+ dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE },
+ $graphsdir => { files => WS_SERVE,
+ dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE },
+ "$datadir/db" => { files => CGI_WRITE,
+ dirs => DIR_CGI_WRITE },
# Readable directories
- "$datadir/mining" => { files => $ws_readable,
- dirs => $ws_dir_readable },
- "$datadir/duplicates" => { files => $ws_readable,
- dirs => $ws_dir_readable },
- "$libdir/Bugzilla" => { files => $ws_readable,
- dirs => $ws_dir_readable },
- $extlib => { files => $ws_readable,
- dirs => $ws_dir_readable },
- $templatedir => { files => $ws_readable,
- dirs => $ws_dir_readable },
- $extensionsdir => { files => $ws_readable,
- dirs => $ws_dir_readable },
- images => { files => $ws_readable,
- dirs => $ws_dir_readable },
- css => { files => $ws_readable,
- dirs => $ws_dir_readable },
- js => { files => $ws_readable,
- dirs => $ws_dir_readable },
- $skinsdir => { files => $ws_readable,
- dirs => $ws_dir_readable },
- t => { files => $owner_readable,
- dirs => $owner_dir_readable },
- 'docs/*/html' => { files => $ws_readable,
- dirs => $ws_dir_readable },
- 'docs/*/pdf' => { files => $ws_readable,
- dirs => $ws_dir_readable },
- 'docs/*/txt' => { files => $ws_readable,
- dirs => $ws_dir_readable },
- 'docs/*/images' => { files => $ws_readable,
- dirs => $ws_dir_readable },
- 'docs/lib' => { files => $owner_readable,
- dirs => $owner_dir_readable },
- 'docs/*/xml' => { files => $owner_readable,
- dirs => $owner_dir_readable },
+ "$datadir/mining" => { files => CGI_READ,
+ dirs => DIR_CGI_READ },
+ "$libdir/Bugzilla" => { files => CGI_READ,
+ dirs => DIR_CGI_READ },
+ $extlib => { files => CGI_READ,
+ dirs => DIR_CGI_READ },
+ $templatedir => { files => CGI_READ,
+ dirs => DIR_CGI_READ },
+ # Directories in the extensions/ dir are WS_SERVE so that
+ # the web/ directories can be served by the web server.
+ # But, for extra security, we deny direct webserver access to
+ # the lib/ and template/ directories of extensions.
+ $extensionsdir => { files => CGI_READ,
+ dirs => DIR_WS_SERVE },
+ "$extensionsdir/*/lib" => { files => CGI_READ,
+ dirs => DIR_CGI_READ },
+ "$extensionsdir/*/template" => { files => CGI_READ,
+ dirs => DIR_CGI_READ },
+
+ # Content served directly by the webserver
+ images => { files => WS_SERVE,
+ dirs => DIR_WS_SERVE },
+ js => { files => WS_SERVE,
+ dirs => DIR_WS_SERVE },
+ $skinsdir => { files => WS_SERVE,
+ dirs => DIR_WS_SERVE },
+ 'docs/*/html' => { files => WS_SERVE,
+ dirs => DIR_WS_SERVE },
+ 'docs/*/pdf' => { files => WS_SERVE,
+ dirs => DIR_WS_SERVE },
+ 'docs/*/txt' => { files => WS_SERVE,
+ dirs => DIR_WS_SERVE },
+ 'docs/*/images' => { files => WS_SERVE,
+ dirs => DIR_WS_SERVE },
+ "$extensionsdir/*/web" => { files => WS_SERVE,
+ dirs => DIR_WS_SERVE },
+
+ # Directories only for the owner, not for the webserver.
+ '.bzr' => { files => OWNER_WRITE,
+ dirs => DIR_OWNER_WRITE },
+ t => { files => OWNER_WRITE,
+ dirs => DIR_OWNER_WRITE },
+ xt => { files => OWNER_WRITE,
+ dirs => DIR_OWNER_WRITE },
+ 'docs/lib' => { files => OWNER_WRITE,
+ dirs => DIR_OWNER_WRITE },
+ 'docs/*/xml' => { files => OWNER_WRITE,
+ dirs => DIR_OWNER_WRITE },
+ 'contrib' => { files => OWNER_EXECUTE,
+ dirs => DIR_OWNER_WRITE, },
);
# --- FILES TO CREATE --- #
@@ -190,40 +264,39 @@
# The name of each directory that we should actually *create*,
# pointing at its default permissions.
my %create_dirs = (
- $datadir => $ws_dir_full_control,
- "$datadir/mining" => $ws_dir_readable,
- "$datadir/duplicates" => $ws_dir_readable,
- $attachdir => $ws_dir_writeable,
- $extensionsdir => $ws_dir_readable,
- graphs => $ws_dir_writeable,
- $webdotdir => $ws_dir_writeable,
- "$skinsdir/custom" => $ws_dir_readable,
- "$skinsdir/contrib" => $ws_dir_readable,
+ # This is DIR_ALSO_WS_SERVE because it contains $webdotdir.
+ $datadir => DIR_CGI_OVERWRITE | DIR_ALSO_WS_SERVE,
+ # Directories that are read-only for cgi scripts
+ "$datadir/mining" => DIR_CGI_READ,
+ "$datadir/extensions" => DIR_CGI_READ,
+ $extensionsdir => DIR_CGI_READ,
+ # Directories that cgi scripts can write to.
+ "$datadir/db" => DIR_CGI_WRITE,
+ $attachdir => DIR_CGI_WRITE,
+ $graphsdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE,
+ $webdotdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE,
+ # Directories that contain content served directly by the web server.
+ "$skinsdir/custom" => DIR_WS_SERVE,
+ "$skinsdir/contrib" => DIR_WS_SERVE,
);
# The name of each file, pointing at its default permissions and
# default contents.
- my %create_files = ();
-
- # Each standard stylesheet has an associated custom stylesheet that
- # we create. Also, we create placeholders for standard stylesheets
- # for contrib skins which don't provide them themselves.
- foreach my $skin_dir ("$skinsdir/custom", <$skinsdir/contrib/*>) {
- next if basename($skin_dir) =~ /^cvs$/i;
- $create_dirs{"$skin_dir/yui"} = $ws_dir_readable;
- foreach my $base_css (<$skinsdir/standard/*.css>) {
- _add_custom_css($skin_dir, basename($base_css), \%create_files, $ws_readable);
- }
- foreach my $dir_css (<$skinsdir/standard/*/*.css>) {
- $dir_css =~ s{.+?([^/]+/[^/]+)$}{$1};
- _add_custom_css($skin_dir, $dir_css, \%create_files, $ws_readable);
- }
- }
+ my %create_files = (
+ "$datadir/extensions/additional" => { perms => CGI_READ,
+ contents => '' },
+ # We create this file so that it always has the right owner
+ # and permissions. Otherwise, the webserver creates it as
+ # owned by itself, which can cause problems if jobqueue.pl
+ # or something else is not running as the webserver or root.
+ "$datadir/mailer.testfile" => { perms => CGI_WRITE,
+ contents => '' },
+ );
# Because checksetup controls the creation of index.html separately
# from all other files, it gets its very own hash.
my %index_html = (
- 'index.html' => { perms => $ws_readable, contents => <<EOT
+ 'index.html' => { perms => WS_SERVE, contents => <<EOT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
@@ -240,31 +313,40 @@
# Because checksetup controls the .htaccess creation separately
# by a localconfig variable, these go in a separate variable from
# %create_files.
- my $ht_default_deny = <<EOT;
-# nothing in this directory is retrievable unless overridden by an .htaccess
-# in a subdirectory
-deny from all
-EOT
-
+ #
+ # Note that these get WS_SERVE as their permission
+ # because they're *read* by the webserver, even though they're not
+ # actually, themselves, served.
my %htaccess = (
- "$attachdir/.htaccess" => { perms => $ws_readable,
- contents => $ht_default_deny },
- "$libdir/Bugzilla/.htaccess" => { perms => $ws_readable,
- contents => $ht_default_deny },
- "$extlib/.htaccess" => { perms => $ws_readable,
- contents => $ht_default_deny },
- "$templatedir/.htaccess" => { perms => $ws_readable,
- contents => $ht_default_deny },
+ "$attachdir/.htaccess" => { perms => WS_SERVE,
+ contents => HT_DEFAULT_DENY },
+ "$libdir/Bugzilla/.htaccess" => { perms => WS_SERVE,
+ contents => HT_DEFAULT_DENY },
+ "$extlib/.htaccess" => { perms => WS_SERVE,
+ contents => HT_DEFAULT_DENY },
+ "$templatedir/.htaccess" => { perms => WS_SERVE,
+ contents => HT_DEFAULT_DENY },
+ 'contrib/.htaccess' => { perms => WS_SERVE,
+ contents => HT_DEFAULT_DENY },
+ 't/.htaccess' => { perms => WS_SERVE,
+ contents => HT_DEFAULT_DENY },
+ 'xt/.htaccess' => { perms => WS_SERVE,
+ contents => HT_DEFAULT_DENY },
+ "$datadir/.htaccess" => { perms => WS_SERVE,
+ contents => HT_DEFAULT_DENY },
- '.htaccess' => { perms => $ws_readable, contents => <<EOT
-# Don't allow people to retrieve non-cgi executable files or our private data
-<FilesMatch ^(.*\\.pm|.*\\.pl|.*localconfig.*)\$>
- deny from all
+ "$graphsdir/.htaccess" => { perms => WS_SERVE, contents => <<EOT
+# Allow access to .png and .gif files.
+<FilesMatch (\\.gif|\\.png)\$>
+ Allow from all
</FilesMatch>
+
+# And no directory listings, either.
+Deny from all
EOT
},
- "$webdotdir/.htaccess" => { perms => $ws_readable, contents => <<EOT
+ "$webdotdir/.htaccess" => { perms => WS_SERVE, contents => <<EOT
# Restrict access to .dot files to the public webdot server at research.att.com
# if research.att.com ever changes their IP, or if you use a different
# webdot server, you'll need to edit this
@@ -282,24 +364,17 @@
Deny from all
EOT
},
-
- # Even though $datadir may not (and should not) be accessible from the
- # web server, we can't know for sure, so create the .htaccess anyway.
- # It's harmless if it isn't accessible...
- "$datadir/.htaccess" => { perms => $ws_readable, contents => <<EOT
-# Nothing in this directory is retrievable unless overridden by an .htaccess
-# in a subdirectory; the only exception is duplicates.rdf, which is used by
-# duplicates.xul and must be accessible from the web server
-deny from all
-<Files duplicates.rdf>
- allow from all
-</Files>
-EOT
-
-
- },
);
+ Bugzilla::Hook::process('install_filesystem', {
+ files => \%files,
+ create_dirs => \%create_dirs,
+ non_recurse_dirs => \%non_recurse_dirs,
+ recurse_dirs => \%recurse_dirs,
+ create_files => \%create_files,
+ htaccess => \%htaccess,
+ });
+
my %all_files = (%create_files, %htaccess, %index_html, %files);
my %all_dirs = (%create_dirs, %non_recurse_dirs);
@@ -322,10 +397,11 @@
my %files = %{$fs->{create_files}};
my $datadir = bz_locations->{'datadir'};
+ my $graphsdir = bz_locations->{'graphsdir'};
# If the graphs/ directory doesn't exist, we're upgrading from
# a version old enough that we need to update the $datadir/mining
# format.
- if (-d "$datadir/mining" && !-d 'graphs') {
+ if (-d "$datadir/mining" && !-d $graphsdir) {
_update_old_charts($datadir);
}
@@ -335,13 +411,26 @@
foreach my $dir (sort keys %dirs) {
unless (-d $dir) {
print "Creating $dir directory...\n";
- mkdir $dir || die $!;
+ mkdir $dir or die "mkdir $dir failed: $!";
# For some reason, passing in the permissions to "mkdir"
# doesn't work right, but doing a "chmod" does.
- chmod $dirs{$dir}, $dir || die $!;
+ chmod $dirs{$dir}, $dir or warn "Cannot chmod $dir: $!";
}
}
+ # Move the testfile if we can't write to it, so that we can re-create
+ # it with the correct permissions below.
+ my $testfile = "$datadir/mailer.testfile";
+ if (-e $testfile and !-w $testfile) {
+ _rename_file($testfile, "$testfile.old");
+ }
+
+ # If old-params.txt exists in the root directory, move it to datadir.
+ my $oldparamsfile = "old_params.txt";
+ if (-e $oldparamsfile) {
+ _rename_file($oldparamsfile, "$datadir/$oldparamsfile");
+ }
+
_create_files(%files);
if ($params->{index_html}) {
_create_files(%{$fs->{index_html}});
@@ -372,44 +461,67 @@
unlink "$datadir/versioncache";
}
+ if (-e "$datadir/duplicates.rdf") {
+ print "Removing duplicates.rdf...\n";
+ unlink "$datadir/duplicates.rdf";
+ unlink "$datadir/duplicates-old.rdf";
+ }
+
+ if (-e "$datadir/duplicates") {
+ print "Removing duplicates directory...\n";
+ rmtree("$datadir/duplicates");
+ }
+
+ _remove_empty_css_files();
+ _convert_single_file_skins();
}
-# A simple helper for creating "empty" CSS files.
-sub _add_custom_css {
- my ($skin_dir, $path, $create_files, $perms) = @_;
- $create_files->{"$skin_dir/$path"} = { perms => $perms, contents => <<EOT
+sub _remove_empty_css_files {
+ my $skinsdir = bz_locations()->{'skinsdir'};
+ foreach my $css_file (glob("$skinsdir/custom/*.css"),
+ glob("$skinsdir/contrib/*/*.css"))
+ {
+ _remove_empty_css($css_file);
+ }
+}
+
+# A simple helper for the update code that removes "empty" CSS files.
+sub _remove_empty_css {
+ my ($file) = @_;
+ my $basename = basename($file);
+ my $empty_contents = <<EOT;
/*
- * Custom rules for $path.
+ * Custom rules for $basename.
* The rules you put here override rules in that stylesheet.
*/
EOT
+ if (length($empty_contents) == -s $file) {
+ open(my $fh, '<', $file) or warn "$file: $!";
+ my $file_contents;
+ { local $/; $file_contents = <$fh>; }
+ if ($file_contents eq $empty_contents) {
+ print install_string('file_remove', { name => $file }), "\n";
+ unlink $file or warn "$file: $!";
+ }
};
}
+# We used to allow a single css file in the skins/contrib/ directory
+# to be a whole skin.
+sub _convert_single_file_skins {
+ my $skinsdir = bz_locations()->{'skinsdir'};
+ foreach my $skin_file (glob "$skinsdir/contrib/*.css") {
+ my $dir_name = $skin_file;
+ $dir_name =~ s/\.css$//;
+ mkdir $dir_name or warn "$dir_name: $!";
+ _rename_file($skin_file, "$dir_name/global.css");
+ }
+}
+
sub create_htaccess {
_create_files(%{FILESYSTEM()->{htaccess}});
# Repair old .htaccess files
- my $htaccess = new IO::File('.htaccess', 'r') || die ".htaccess: $!";
- my $old_data;
- { local $/; $old_data = <$htaccess>; }
- $htaccess->close;
-
- my $repaired = 0;
- if ($old_data =~ s/\|localconfig\|/\|.*localconfig.*\|/) {
- $repaired = 1;
- }
- if ($old_data !~ /\(\.\*\\\.pm\|/) {
- $old_data =~ s/\(/(.*\\.pm\|/;
- $repaired = 1;
- }
- if ($repaired) {
- print "Repairing .htaccess...\n";
- $htaccess = new IO::File('.htaccess', 'w') || die $!;
- print $htaccess $old_data;
- $htaccess->close;
- }
-
my $webdot_dir = bz_locations()->{'webdotdir'};
# The public webdot IP address changed.
@@ -427,6 +539,17 @@
}
}
+sub _rename_file {
+ my ($from, $to) = @_;
+ print install_string('file_rename', { from => $from, to => $to }), "\n";
+ if (-e $to) {
+ warn "$to already exists, not moving\n";
+ }
+ else {
+ move($from, $to) or warn $!;
+ }
+}
+
# A helper for the above functions.
sub _create_files {
my (%files) = @_;
@@ -523,12 +646,40 @@
}
}
+sub fix_dir_permissions {
+ my ($dir) = @_;
+ return if ON_WINDOWS;
+ # Note that _get_owner_and_group is always silent here.
+ my ($owner_id, $group_id) = _get_owner_and_group();
+
+ my $perms;
+ my $fs = FILESYSTEM();
+ if ($perms = $fs->{recurse_dirs}->{$dir}) {
+ _fix_perms_recursively($dir, $owner_id, $group_id, $perms);
+ }
+ elsif ($perms = $fs->{all_dirs}->{$dir}) {
+ _fix_perms($dir, $owner_id, $group_id, $perms);
+ }
+ else {
+ # Do nothing. We know nothing about this directory.
+ warn "Unknown directory $dir";
+ }
+}
+
+sub fix_file_permissions {
+ my ($file) = @_;
+ return if ON_WINDOWS;
+ my $perms = FILESYSTEM()->{all_files}->{$file}->{perms};
+ # Note that _get_owner_and_group is always silent here.
+ my ($owner_id, $group_id) = _get_owner_and_group();
+ _fix_perms($file, $owner_id, $group_id, $perms);
+}
sub fix_all_file_permissions {
my ($output) = @_;
- my $ws_group = Bugzilla->localconfig->{'webservergroup'};
- my $group_id = _check_web_server_group($ws_group, $output);
+ # _get_owner_and_group also checks that the webservergroup is valid.
+ my ($owner_id, $group_id) = _get_owner_and_group($output);
return if ON_WINDOWS;
@@ -539,30 +690,18 @@
print get_text('install_file_perms_fix') . "\n" if $output;
- my $owner_id = POSIX::getuid();
- $group_id = POSIX::getgid() unless defined $group_id;
-
foreach my $dir (sort keys %dirs) {
next unless -d $dir;
_fix_perms($dir, $owner_id, $group_id, $dirs{$dir});
}
- foreach my $dir (sort keys %recurse_dirs) {
- next unless -d $dir;
- # Set permissions on the directory itself.
- my $perms = $recurse_dirs{$dir};
- _fix_perms($dir, $owner_id, $group_id, $perms->{dirs});
- # Now recurse through the directory and set the correct permissions
- # on subdirectories and files.
- find({ no_chdir => 1, wanted => sub {
- my $name = $File::Find::name;
- if (-d $name) {
- _fix_perms($name, $owner_id, $group_id, $perms->{dirs});
- }
- else {
- _fix_perms($name, $owner_id, $group_id, $perms->{files});
- }
- }}, $dir);
+ foreach my $pattern (sort keys %recurse_dirs) {
+ my $perms = $recurse_dirs{$pattern};
+ # %recurse_dirs supports globs
+ foreach my $dir (glob $pattern) {
+ next unless -d $dir;
+ _fix_perms_recursively($dir, $owner_id, $group_id, $perms);
+ }
}
foreach my $file (sort keys %files) {
@@ -578,6 +717,16 @@
_fix_cvs_dirs($owner_id, '.');
}
+sub _get_owner_and_group {
+ my ($output) = @_;
+ my $group_id = _check_web_server_group($output);
+ return () if ON_WINDOWS;
+
+ my $owner_id = POSIX::getuid();
+ $group_id = POSIX::getgid() unless defined $group_id;
+ return ($owner_id, $group_id);
+}
+
# A helper for fix_all_file_permissions
sub _fix_cvs_dirs {
my ($owner_id, $dir) = @_;
@@ -585,8 +734,13 @@
find({ no_chdir => 1, wanted => sub {
my $name = $File::Find::name;
if ($File::Find::dir =~ /\/CVS/ || $_ eq '.cvsignore'
- || (-d $name && $_ eq 'CVS')) {
- _fix_perms($name, $owner_id, $owner_gid, 0700);
+ || (-d $name && $_ =~ /CVS$/))
+ {
+ my $perms = 0600;
+ if (-d $name) {
+ $perms = 0700;
+ }
+ _fix_perms($name, $owner_id, $owner_gid, $perms);
}
}}, $dir);
}
@@ -594,15 +748,39 @@
sub _fix_perms {
my ($name, $owner, $group, $perms) = @_;
#printf ("Changing $name to %o\n", $perms);
- chown $owner, $group, $name
- || warn "Failed to change ownership of $name: $!";
+
+ # The webserver should never try to chown files.
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ chown $owner, $group, $name
+ or warn install_string('chown_failed', { path => $name,
+ error => $! }) . "\n";
+ }
chmod $perms, $name
- || warn "Failed to change permissions of $name: $!";
+ or warn install_string('chmod_failed', { path => $name,
+ error => $! }) . "\n";
+}
+
+sub _fix_perms_recursively {
+ my ($dir, $owner_id, $group_id, $perms) = @_;
+ # Set permissions on the directory itself.
+ _fix_perms($dir, $owner_id, $group_id, $perms->{dirs});
+ # Now recurse through the directory and set the correct permissions
+ # on subdirectories and files.
+ find({ no_chdir => 1, wanted => sub {
+ my $name = $File::Find::name;
+ if (-d $name) {
+ _fix_perms($name, $owner_id, $group_id, $perms->{dirs});
+ }
+ else {
+ _fix_perms($name, $owner_id, $group_id, $perms->{files});
+ }
+ }}, $dir);
}
sub _check_web_server_group {
- my ($group, $output) = @_;
+ my ($output) = @_;
+ my $group = Bugzilla->localconfig->{'webservergroup'};
my $filename = bz_locations()->{'localconfig'};
my $group_id;
@@ -686,4 +864,16 @@
Returns: nothing
+=item C<fix_dir_permissions>
+
+Given the name of a directory, its permissions will be fixed according to
+how they are supposed to be set in Bugzilla's current configuration.
+If it fails to set the permissions, a warning will be printed to STDERR.
+
+=item C<fix_file_permissions>
+
+Given the name of a file, its permissions will be fixed according to
+how they are supposed to be set in Bugzilla's current configuration.
+If it fails to set the permissions, a warning will be printed to STDERR.
+
=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/Install/Localconfig.pm b/Websites/bugs.webkit.org/Bugzilla/Install/Localconfig.pm
index 97d93ae..1544e6f 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Install/Localconfig.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Install/Localconfig.pm
@@ -31,13 +31,14 @@
use strict;
use Bugzilla::Constants;
-use Bugzilla::Install::Util qw(bin_loc);
-use Bugzilla::Util qw(generate_random_password);
+use Bugzilla::Install::Util qw(bin_loc install_string);
+use Bugzilla::Util qw(generate_random_password wrap_hard);
use Data::Dumper;
use File::Basename qw(dirname);
use IO::File;
use Safe;
+use Term::ANSIColor;
use base qw(Exporter);
@@ -50,164 +51,71 @@
{
name => 'create_htaccess',
default => 1,
- desc => <<EOT
-# If you are using Apache as your web server, Bugzilla can create .htaccess
-# files for you that will instruct Apache not to serve files that shouldn't
-# be accessed from the web browser (like your local configuration data and non-cgi
-# executable files). For this to work, the directory your Bugzilla
-# installation is in must be within the jurisdiction of a <Directory> block
-# in the httpd.conf file that has 'AllowOverride Limit' in it. If it has
-# 'AllowOverride All' or other options with Limit, that's fine.
-# (Older Apache installations may use an access.conf file to store these
-# <Directory> blocks.)
-# If this is set to 1, Bugzilla will create these files if they don't exist.
-# If this is set to 0, Bugzilla will not create these files.
-EOT
},
{
name => 'webservergroup',
default => ON_WINDOWS ? '' : 'apache',
- desc => q{# This is the group your web server runs as.
-# If you have a Windows box, ignore this setting.
-# If you do not have access to the group your web server runs under,
-# set this to "". If you do set this to "", then your Bugzilla installation
-# will be _VERY_ insecure, because some files will be world readable/writable,
-# and so anyone who can get local access to your machine can do whatever they
-# want. You should only have this set to "" if this is a testing installation
-# and you cannot set this up any other way. YOU HAVE BEEN WARNED!
-# If you set this to anything other than "", you will need to run checksetup.pl
-# as} . ROOT_USER . qq{, or as a user who is a member of the specified group.\n}
+ },
+ {
+ name => 'use_suexec',
+ default => 0,
},
{
name => 'db_driver',
default => 'mysql',
- desc => <<EOT
-# What SQL database to use. Default is mysql. List of supported databases
-# can be obtained by listing Bugzilla/DB directory - every module corresponds
-# to one supported database and the name corresponds to a driver name.
-EOT
},
{
name => 'db_host',
- default => 'localhost',
- desc =>
- "# The DNS name of the host that the database server runs on.\n"
+ default => 'localhost',
},
{
name => 'db_name',
default => 'bugs',
- desc => "# The name of the database\n"
},
{
name => 'db_user',
default => 'bugs',
- desc => "# Who we connect to the database as.\n"
},
{
name => 'db_pass',
default => '',
- desc => <<EOT
-# Enter your database password here. It's normally advisable to specify
-# a password for your bugzilla database user.
-# If you use apostrophe (') or a backslash (\\) in your password, you'll
-# need to escape it by preceding it with a '\\' character. (\\') or (\\)
-# (Far simpler just not to use those characters.)
-EOT
},
{
name => 'db_port',
default => 0,
- desc => <<EOT
-# Sometimes the database server is running on a non-standard port. If that's
-# the case for your database server, set this to the port number that your
-# database server is running on. Setting this to 0 means "use the default
-# port for my database server."
-EOT
},
{
name => 'db_sock',
default => '',
- desc => <<EOT
-# MySQL Only: Enter a path to the unix socket for MySQL. If this is
-# blank, then MySQL's compiled-in default will be used. You probably
-# want that.
-EOT
},
{
name => 'db_check',
default => 1,
- desc => <<EOT
-# Should checksetup.pl try to verify that your database setup is correct?
-# (with some combinations of database servers/Perl modules/moonphase this
-# doesn't work)
-EOT
},
{
name => 'index_html',
default => 0,
- desc => <<EOT
-# With the introduction of a configurable index page using the
-# template toolkit, Bugzilla's main index page is now index.cgi.
-# Most web servers will allow you to use index.cgi as a directory
-# index, and many come preconfigured that way, but if yours doesn't
-# then you'll need an index.html file that provides redirection
-# to index.cgi. Setting \$index_html to 1 below will allow
-# checksetup.pl to create one for you if it doesn't exist.
-# NOTE: checksetup.pl will not replace an existing file, so if you
-# wish to have checksetup.pl create one for you, you must
-# make sure that index.html doesn't already exist
-EOT
},
{
name => 'cvsbin',
- default => \&_get_default_cvsbin,
- desc => <<EOT
-# For some optional functions of Bugzilla (such as the pretty-print patch
-# viewer), we need the cvs binary to access files and revisions.
-# Because it's possible that this program is not in your path, you can specify
-# its location here. Please specify the full path to the executable.
-EOT
+ default => sub { bin_loc('cvs') },
},
{
name => 'interdiffbin',
- default => \&_get_default_interdiffbin,
- desc => <<EOT
-# For some optional functions of Bugzilla (such as the pretty-print patch
-# viewer), we need the interdiff binary to make diffs between two patches.
-# Because it's possible that this program is not in your path, you can specify
-# its location here. Please specify the full path to the executable.
-EOT
+ default => sub { bin_loc('interdiff') },
},
{
name => 'diffpath',
- default => \&_get_default_diffpath,
- desc => <<EOT
-# The interdiff feature needs diff, so we have to have that path.
-# Please specify the directory name only; do not use trailing slash.
-EOT
+ default => sub { dirname(bin_loc('diff')) },
},
{
name => 'site_wide_secret',
# 64 characters is roughly the equivalent of a 384-bit key, which
# is larger than anybody would ever be able to brute-force.
default => sub { generate_random_password(64) },
- desc => <<EOT
-# This secret key is used by your installation for the creation and
-# validation of encrypted tokens to prevent unsolicited changes,
-# such as bug changes. A random string is generated by default.
-# It's very important that this key is kept secret. It also must be
-# very long.
-EOT
},
);
-use constant OLD_LOCALCONFIG_VARS => qw(
- mysqlpath
- contenttypes
- pages
- severities platforms opsys priorities
-);
-
sub read_localconfig {
my ($include_deprecated) = @_;
my $filename = bz_locations()->{'localconfig'};
@@ -221,23 +129,31 @@
$s->rdo($filename);
if ($@ || $!) {
my $err_msg = $@ ? $@ : $!;
- die <<EOT;
-An error has occurred while reading your 'localconfig' file. The text of
-the error message is:
-
-$err_msg
-
-Please fix the error in your 'localconfig' file. Alternately, rename your
-'localconfig' file, rerun checksetup.pl, and re-enter your answers.
-
- \$ mv -f localconfig localconfig.old
- \$ ./checksetup.pl
-EOT
+ die install_string('error_localconfig_read',
+ { error => $err_msg, localconfig => $filename }), "\n";
}
- my @vars = map($_->{name}, LOCALCONFIG_VARS);
- push(@vars, OLD_LOCALCONFIG_VARS) if $include_deprecated;
- foreach my $var (@vars) {
+ my @read_symbols;
+ if ($include_deprecated) {
+ # First we have to get the whole symbol table
+ my $safe_root = $s->root;
+ my %safe_package;
+ { no strict 'refs'; %safe_package = %{$safe_root . "::"}; }
+ # And now we read the contents of every var in the symbol table.
+ # However:
+ # * We only include symbols that start with an alphanumeric
+ # character. This excludes symbols like "_<./localconfig"
+ # that show up in some perls.
+ # * We ignore the INC symbol, which exists in every package.
+ # * Perl 5.10 imports a lot of random symbols that all
+ # contain "::", and we want to ignore those.
+ @read_symbols = grep { /^[A-Za-z0-1]/ and !/^INC$/ and !/::/ }
+ (keys %safe_package);
+ }
+ else {
+ @read_symbols = map($_->{name}, LOCALCONFIG_VARS);
+ }
+ foreach my $var (@read_symbols) {
my $glob = $s->varglob($var);
# We can't get the type of a variable out of a Safe automatically.
# We can only get the glob itself. So we figure out its type this
@@ -250,10 +166,10 @@
if (defined $$glob) {
$localconfig{$var} = $$glob;
}
- elsif (defined @$glob) {
+ elsif (@$glob) {
$localconfig{$var} = \@$glob;
}
- elsif (defined %$glob) {
+ elsif (%$glob) {
$localconfig{$var} = \%$glob;
}
}
@@ -307,74 +223,62 @@
if (!defined $value) {
push(@new_vars, $name);
$var->{default} = &{$var->{default}} if ref($var->{default}) eq 'CODE';
- $localconfig->{$name} = $answer->{$name} || $var->{default};
+ if (exists $answer->{$name}) {
+ $localconfig->{$name} = $answer->{$name};
+ }
+ else {
+ $localconfig->{$name} = $var->{default};
+ }
}
}
- my @old_vars;
- foreach my $name (OLD_LOCALCONFIG_VARS) {
- push(@old_vars, $name) if defined $localconfig->{$name};
+ if (!$localconfig->{'interdiffbin'} && $output) {
+ print "\n", install_string('patchutils_missing'), "\n";
}
- if (!$localconfig->{'interdiffbin'} && $output) {
- print <<EOT
-
-OPTIONAL NOTE: If you want to be able to use the 'difference between two
-patches' feature of Bugzilla (which requires the PatchReader Perl module
-as well), you should install patchutils from:
-
- http://cyberelk.net/tim/patchutils/
-
-EOT
+ my @old_vars;
+ foreach my $var (keys %$localconfig) {
+ push(@old_vars, $var) if !grep($_->{name} eq $var, LOCALCONFIG_VARS);
}
my $filename = bz_locations->{'localconfig'};
+ # Move any custom or old variables into a separate file.
if (scalar @old_vars) {
+ my $filename_old = "$filename.old";
+ open(my $old_file, ">>:utf8", $filename_old)
+ or die "$filename_old: $!";
+ local $Data::Dumper::Purity = 1;
+ foreach my $var (@old_vars) {
+ print $old_file Data::Dumper->Dump([$localconfig->{$var}],
+ ["*$var"]) . "\n\n";
+ }
+ close $old_file;
my $oldstuff = join(', ', @old_vars);
- print <<EOT
-
-The following variables are no longer used in $filename, and
-should be removed: $oldstuff
-
-EOT
+ print install_string('lc_old_vars',
+ { localconfig => $filename, old_file => $filename_old,
+ vars => $oldstuff }), "\n";
}
- if (scalar @new_vars) {
- my $filename = bz_locations->{'localconfig'};
- my $fh = new IO::File($filename, '>>') || die "$filename: $!";
- $fh->seek(0, SEEK_END);
- foreach my $var (LOCALCONFIG_VARS) {
- if (grep($_ eq $var->{name}, @new_vars)) {
- print $fh "\n", $var->{desc},
- Data::Dumper->Dump([$localconfig->{$var->{name}}],
- ["*$var->{name}"]);
- }
- }
- # When updating site_wide_secret to the new value, don't
- # leave the old value behind.
- if (grep { $_ eq 'site_wide_secret' } @new_vars) {
- my $read = new IO::File($filename, '<') || die "$filename: $!";
- my $text;
- { local $/; $text = <$read> }
- $read->close;
- $text =~ s/^\$site_wide_secret = '\w{256}';$//ms;
- my $write = new IO::File($filename, '>') || die "$filename: $!";
- print $write $text;
- $write->close;
- }
+ # Re-write localconfig
+ open(my $fh, ">:utf8", $filename) or die "$filename: $!";
+ foreach my $var (LOCALCONFIG_VARS) {
+ my $name = $var->{name};
+ my $desc = install_string("localconfig_$name", { root => ROOT_USER });
+ chomp($desc);
+ # Make the description into a comment.
+ $desc =~ s/^/# /mg;
+ print $fh $desc, "\n",
+ Data::Dumper->Dump([$localconfig->{$name}],
+ ["*$name"]), "\n";
+ }
+ if (@new_vars) {
my $newstuff = join(', ', @new_vars);
- print <<EOT;
-
-This version of Bugzilla contains some variables that you may want to
-change and adapt to your local settings. Please edit the file
-$filename and rerun checksetup.pl.
-
-The following variables are new to $filename since you last ran
-checksetup.pl: $newstuff
-
-EOT
+ print "\n";
+ print colored(install_string('lc_new_vars', { localconfig => $filename,
+ new_vars => wrap_hard($newstuff, 70) }),
+ COLOR_ERROR), "\n";
exit;
}
@@ -384,13 +288,6 @@
return { old_vars => \@old_vars, new_vars => \@new_vars };
}
-sub _get_default_cvsbin { return bin_loc('cvs') }
-sub _get_default_interdiffbin { return bin_loc('interdiff') }
-sub _get_default_diffpath {
- my $diff_bin = bin_loc('diff');
- return dirname($diff_bin);
-}
-
1;
__END__
@@ -438,31 +335,46 @@
=over
-=item C<read_localconfig($include_deprecated)>
+=item C<read_localconfig>
-Description: Reads the localconfig file and returns all valid
- values in a hashref.
+=over
-Params: C<$include_deprecated> - C<true> if you want the returned
- hashref to also include variables listed in
- C<OLD_LOCALCONFIG_VARS>, if they exist. Generally
- this is only for use by C<update_localconfig>.
+=item B<Description>
-Returns: A hashref of the localconfig variables. If an array
- is defined, it will be an arrayref in the returned hash. If a
- hash is defined, it will be a hashref in the returned hash.
- Only includes variables specified in C<LOCALCONFIG_VARS>
- (and C<OLD_LOCALCONFIG_VARS> if C<$include_deprecated> is
- specified).
+Reads the localconfig file and returns all valid values in a hashref.
-=item C<update_localconfig({ output =E<gt> 1 })>
+=item B<Params>
+
+=over
+
+=item C<$include_deprecated>
+
+C<true> if you want the returned hashref to include *any* variable
+currently defined in localconfig, even if it doesn't exist in
+C<LOCALCONFIG_VARS>. Generally this is is only for use
+by L</update_localconfig>.
+
+=back
+
+=item B<Returns>
+
+A hashref of the localconfig variables. If an array is defined in
+localconfig, it will be an arrayref in the returned hash. If a
+hash is defined, it will be a hashref in the returned hash.
+Only includes variables specified in C<LOCALCONFIG_VARS>, unless
+C<$include_deprecated> is true.
+
+=back
+
+
+=item C<update_localconfig>
Description: Adds any new variables to localconfig that aren't
currently defined there. Also optionally prints out
a message about vars that *should* be there and aren't.
Exits the program if it adds any new vars.
-Params: C<output> - C<true> if the function should display informational
+Params: C<$output> - C<true> if the function should display informational
output and warnings. It will always display errors or
any message which would cause program execution to halt.
diff --git a/Websites/bugs.webkit.org/Bugzilla/Install/Requirements.pm b/Websites/bugs.webkit.org/Bugzilla/Install/Requirements.pm
index d018139..1e7fc97 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Install/Requirements.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Install/Requirements.pm
@@ -25,22 +25,57 @@
use strict;
-use Bugzilla::Install::Util qw(vers_cmp install_string);
+use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(vers_cmp install_string bin_loc
+ extension_requirement_packages);
use List::Util qw(max);
use Safe;
+use Term::ANSIColor;
use base qw(Exporter);
our @EXPORT = qw(
REQUIRED_MODULES
OPTIONAL_MODULES
+ FEATURE_FILES
check_requirements
check_graphviz
have_vers
install_command
+ map_files_to_features
);
-use Bugzilla::Constants;
+# This is how many *'s are in the top of each "box" message printed
+# by checksetup.pl.
+use constant TABLE_WIDTH => 71;
+
+# Optional Apache modules that have no Perl component to them.
+# If these are installed, Bugzilla has additional functionality.
+#
+# The keys are the names of the modules, the values are what the module
+# is called in the output of "apachectl -t -D DUMP_MODULES".
+use constant APACHE_MODULES => {
+ mod_headers => 'headers_module',
+ mod_env => 'env_module',
+ mod_expires => 'expires_module',
+};
+
+# These are all of the binaries that we could possibly use that can
+# give us info about which Apache modules are installed.
+# If we can't use "apachectl", the "httpd" binary itself takes the same
+# parameters. Note that on Debian and Gentoo, there is an "apache2ctl",
+# but it takes different parameters on each of those two distros, so we
+# don't use apache2ctl.
+use constant APACHE => qw(apachectl httpd apache2 apache);
+
+# If we don't find any of the above binaries in the normal PATH,
+# these are extra places we look.
+use constant APACHE_PATH => [qw(
+ /usr/sbin
+ /usr/local/sbin
+ /usr/libexec
+ /usr/local/libexec
+)];
# The below two constants are subroutines so that they can implement
# a hook. Other than that they are actually constants.
@@ -59,66 +94,113 @@
{
package => 'CGI.pm',
module => 'CGI',
- # Perl 5.10 requires CGI 3.33 due to a taint issue when
- # uploading attachments, see bug 416382.
- # Require CGI 3.21 for -httponly support, see bug 368502.
- version => (vers_cmp($perl_ver, '5.10') > -1) ? '3.33' : '3.21'
+ # 3.51 fixes a security problem that affects Bugzilla.
+ # (bug 591165)
+ version => '3.51',
+ },
+ {
+ package => 'Digest-SHA',
+ module => 'Digest::SHA',
+ version => 0
},
{
package => 'TimeDate',
module => 'Date::Format',
version => '2.21'
},
+ # 0.28 fixed some important bugs in DateTime.
{
- package => 'PathTools',
- module => 'File::Spec',
- version => '0.84'
+ package => 'DateTime',
+ module => 'DateTime',
+ version => '0.28'
+ },
+ # 0.79 is required to work on Windows Vista and Windows Server 2008.
+ # As correctly detecting the flavor of Windows is not easy,
+ # we require this version for all Windows installations.
+ # 0.71 fixes a major bug affecting all platforms.
+ {
+ package => 'DateTime-TimeZone',
+ module => 'DateTime::TimeZone',
+ version => ON_WINDOWS ? '0.79' : '0.71'
},
{
package => 'DBI',
module => 'DBI',
- version => '1.41'
+ version => (vers_cmp($perl_ver, '5.13.3') > -1) ? '1.614' : '1.41'
},
+ # 2.22 fixes various problems related to UTF8 strings in hash keys,
+ # as well as line endings on Windows.
{
package => 'Template-Toolkit',
module => 'Template',
- version => '2.15'
+ version => '2.22'
},
{
package => 'Email-Send',
module => 'Email::Send',
- version => ON_WINDOWS ? '2.16' : '2.00'
+ version => ON_WINDOWS ? '2.16' : '2.00',
+ blacklist => ['^2\.196$']
},
{
package => 'Email-MIME',
module => 'Email::MIME',
- version => '1.861'
+ # This fixes a memory leak in walk_parts that affected jobqueue.pl.
+ version => '1.904'
},
{
- package => 'Email-MIME-Modifier',
- module => 'Email::MIME::Modifier',
- version => '1.442'
+ package => 'URI',
+ module => 'URI',
+ # This version properly handles a semicolon as the delimiter
+ # in a URL query string.
+ version => '1.37',
+ },
+ {
+ package => 'List-MoreUtils',
+ module => 'List::MoreUtils',
+ version => 0.22,
+ },
+ {
+ package => 'Math-Random-ISAAC',
+ module => 'Math::Random::ISAAC',
+ version => '1.0.1',
},
);
- my $all_modules = _get_extension_requirements(
- 'REQUIRED_MODULES', \@modules);
- return $all_modules;
+ if (ON_WINDOWS) {
+ push(@modules, {
+ package => 'Win32',
+ module => 'Win32',
+ # 0.35 fixes a memory leak in GetOSVersion, which we use.
+ version => 0.35,
+ },
+ {
+ package => 'Win32-API',
+ module => 'Win32::API',
+ # 0.55 fixes a bug with char* that might affect Bugzilla::RNG.
+ version => '0.55',
+ });
+ }
+
+ my $extra_modules = _get_extension_requirements('REQUIRED_MODULES');
+ push(@modules, @$extra_modules);
+ return \@modules;
};
sub OPTIONAL_MODULES {
+ my $perl_ver = sprintf('%vd', $^V);
my @modules = (
{
package => 'GD',
module => 'GD',
version => '1.20',
- feature => 'Graphical Reports, New Charts, Old Charts'
+ feature => [qw(graphical_reports new_charts old_charts)],
},
{
package => 'Chart',
- module => 'Chart::Base',
- version => '1.0',
- feature => 'New Charts, Old Charts'
+ module => 'Chart::Lines',
+ # Versions below 2.1 cannot be detected accurately.
+ version => '2.1',
+ feature => [qw(new_charts old_charts)],
},
{
package => 'Template-GD',
@@ -126,89 +208,116 @@
# on Template-Toolkits after 2.14, and still works with 2.14 and lower.
module => 'Template::Plugin::GD::Image',
version => 0,
- feature => 'Graphical Reports'
+ feature => ['graphical_reports'],
},
{
package => 'GDTextUtil',
module => 'GD::Text',
version => 0,
- feature => 'Graphical Reports'
+ feature => ['graphical_reports'],
},
{
package => 'GDGraph',
module => 'GD::Graph',
version => 0,
- feature => 'Graphical Reports'
- },
- {
- package => 'XML-Twig',
- module => 'XML::Twig',
- version => 0,
- feature => 'Move Bugs Between Installations'
+ feature => ['graphical_reports'],
},
{
package => 'MIME-tools',
# MIME::Parser is packaged as MIME::Tools on ActiveState Perl
module => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
version => '5.406',
- feature => 'Move Bugs Between Installations'
+ feature => ['moving'],
},
{
package => 'libwww-perl',
module => 'LWP::UserAgent',
version => 0,
- feature => 'Automatic Update Notifications'
+ feature => ['updates'],
+ },
+ {
+ package => 'XML-Twig',
+ module => 'XML::Twig',
+ version => 0,
+ feature => ['moving', 'updates'],
},
{
package => 'PatchReader',
module => 'PatchReader',
- version => '0.9.4',
- feature => 'Patch Viewer'
- },
- {
- package => 'PerlMagick',
- module => 'Image::Magick',
- version => 0,
- feature => 'Optionally Convert BMP Attachments to PNGs'
+ # 0.9.6 fixes two notable bugs and significantly improves the UX.
+ version => '0.9.6',
+ feature => ['patch_viewer'],
},
{
package => 'perl-ldap',
module => 'Net::LDAP',
version => 0,
- feature => 'LDAP Authentication'
+ feature => ['auth_ldap'],
},
{
package => 'Authen-SASL',
module => 'Authen::SASL',
version => 0,
- feature => 'SMTP Authentication'
+ feature => ['smtp_auth'],
},
{
package => 'RadiusPerl',
module => 'Authen::Radius',
version => 0,
- feature => 'RADIUS Authentication'
+ feature => ['auth_radius'],
},
{
package => 'SOAP-Lite',
module => 'SOAP::Lite',
+ # Fixes various bugs, including 542931 and 552353 + stops
+ # throwing warnings with Perl 5.12.
+ version => '0.712',
+ feature => ['xmlrpc'],
+ },
+ {
+ package => 'JSON-RPC',
+ module => 'JSON::RPC',
version => 0,
- # These versions (0.70 -> 0.710.05) are affected by bug 468009
- blacklist => ['^0\.70', '^0\.710?\.0[1-5]$'],
- feature => 'XML-RPC Interface'
+ feature => ['jsonrpc'],
+ },
+ {
+ package => 'JSON-XS',
+ module => 'JSON::XS',
+ # 2.0 is the first version that will work with JSON::RPC.
+ version => '2.0',
+ feature => ['jsonrpc_faster'],
+ },
+ {
+ package => 'Test-Taint',
+ module => 'Test::Taint',
+ version => 0,
+ feature => ['jsonrpc', 'xmlrpc'],
},
{
# We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
package => 'HTML-Parser',
module => 'HTML::Parser',
- version => '3.40',
- feature => 'More HTML in Product/Group Descriptions'
+ version => (vers_cmp($perl_ver, '5.13.3') > -1) ? '3.67' : '3.40',
+ feature => ['html_desc'],
},
{
package => 'HTML-Scrubber',
module => 'HTML::Scrubber',
version => 0,
- feature => 'More HTML in Product/Group Descriptions'
+ feature => ['html_desc'],
+ },
+ {
+ # we need version 2.21 of Encode for mime_name
+ package => 'Encode',
+ module => 'Encode',
+ version => 2.21,
+ feature => ['detect_charset'],
+ },
+ {
+ package => 'Encode-Detect',
+ module => 'Encode::Detect',
+ version => 0,
+ feature => ['detect_charset'],
},
# Inbound Email
@@ -216,13 +325,27 @@
package => 'Email-MIME-Attachment-Stripper',
module => 'Email::MIME::Attachment::Stripper',
version => 0,
- feature => 'Inbound Email'
+ feature => ['inbound_email'],
},
{
package => 'Email-Reply',
module => 'Email::Reply',
version => 0,
- feature => 'Inbound Email'
+ feature => ['inbound_email'],
+ },
+
+ # Mail Queueing
+ {
+ package => 'TheSchwartz',
+ module => 'TheSchwartz',
+ version => 0,
+ feature => ['jobqueue'],
+ },
+ {
+ package => 'Daemon-Generic',
+ module => 'Daemon::Generic',
+ version => 0,
+ feature => ['jobqueue'],
},
# mod_perl
@@ -230,46 +353,52 @@
package => 'mod_perl',
module => 'mod_perl2',
version => '1.999022',
- feature => 'mod_perl'
+ feature => ['mod_perl'],
},
{
- package => 'Math-Random-Secure',
- module => 'Math::Random::Secure',
- version => '0.05',
- feature => 'Improve cookie and token security',
+ package => 'Apache-SizeLimit',
+ module => 'Apache2::SizeLimit',
+ # 0.96 properly determines process size on Linux.
+ version => '0.96',
+ feature => ['mod_perl'],
},
);
- my $all_modules = _get_extension_requirements(
- 'OPTIONAL_MODULES', \@modules);
- return $all_modules;
+ my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
+ push(@modules, @$extra_modules);
+ return \@modules;
};
-# This implements the install-requirements hook described in Bugzilla::Hook.
+# This maps features to the files that require that feature in order
+# to compile. It is used by t/001compile.t and mod_perl.pl.
+use constant FEATURE_FILES => (
+ jsonrpc => ['Bugzilla/WebService/Server/JSONRPC.pm', 'jsonrpc.cgi'],
+ xmlrpc => ['Bugzilla/WebService/Server/XMLRPC.pm', 'xmlrpc.cgi',
+ 'Bugzilla/WebService.pm', 'Bugzilla/WebService/*.pm'],
+ moving => ['importxml.pl'],
+ auth_ldap => ['Bugzilla/Auth/Verify/LDAP.pm'],
+ auth_radius => ['Bugzilla/Auth/Verify/RADIUS.pm'],
+ inbound_email => ['email_in.pl'],
+ jobqueue => ['Bugzilla/Job/*', 'Bugzilla/JobQueue.pm',
+ 'Bugzilla/JobQueue/*', 'jobqueue.pl'],
+ patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'],
+ updates => ['Bugzilla/Update.pm'],
+);
+
+# This implements the REQUIRED_MODULES and OPTIONAL_MODULES stuff
+# described in in Bugzilla::Extension.
sub _get_extension_requirements {
- my ($function, $base_modules) = @_;
- my @all_modules;
- # get a list of all extensions
- my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
- foreach my $extension (@extensions) {
- my $file = "$extension/code/install-requirements.pl";
- if (-e $file) {
- my $safe = new Safe;
- # This is a very liberal Safe.
- $safe->permit(qw(:browse require entereval caller));
- $safe->rdo($file);
- if ($@) {
- warn $@;
- next;
- }
- my $modules = eval { &{$safe->varglob($function)}($base_modules) };
- next unless $modules;
- push(@all_modules, @$modules);
+ my ($function) = @_;
+
+ my $packages = extension_requirement_packages();
+ my @modules;
+ foreach my $package (@$packages) {
+ if ($package->can($function)) {
+ my $extra_modules = $package->$function;
+ push(@modules, @$extra_modules);
}
}
-
- unshift(@all_modules, @$base_modules);
- return \@all_modules;
+ return \@modules;
};
sub check_requirements {
@@ -290,6 +419,8 @@
print "\n", install_string('checking_optional'), "\n" if $output;
my $missing_optional = _check_missing(OPTIONAL_MODULES, $output);
+ my $missing_apache = _missing_apache_modules(APACHE_MODULES, $output);
+
# If we're running on Windows, reset the input line terminator so that
# console input works properly - loading CGI tends to mess it up
$/ = "\015\012" if ON_WINDOWS;
@@ -300,6 +431,7 @@
one_dbd => $have_one_dbd,
missing => $missing,
optional => $missing_optional,
+ apache => $missing_apache,
any_missing => !$pass || scalar(@$missing_optional),
};
}
@@ -318,177 +450,192 @@
return \@missing;
}
-# Returns the build ID of ActivePerl. If several versions of
-# ActivePerl are installed, it won't be able to know which one
-# you are currently running. But that's our best guess.
-sub _get_activestate_build_id {
- eval 'use Win32::TieRegistry';
- return 0 if $@;
- my $key = Win32::TieRegistry->new('LMachine\Software\ActiveState\ActivePerl')
- or return 0;
- return $key->GetValue("CurrentVersion");
+sub _missing_apache_modules {
+ my ($modules, $output) = @_;
+ my $apachectl = _get_apachectl();
+ return [] if !$apachectl;
+ my $command = "$apachectl -t -D DUMP_MODULES";
+ my $cmd_info = `$command 2>&1`;
+ # If apachectl returned a value greater than 0, then there was an
+ # error parsing Apache's configuration, and we can't check modules.
+ my $retval = $?;
+ if ($retval > 0) {
+ print STDERR install_string('apachectl_failed',
+ { command => $command, root => ROOT_USER }), "\n";
+ return [];
+ }
+ my @missing;
+ foreach my $module (keys %$modules) {
+ my $ok = _check_apache_module($module, $modules->{$module},
+ $cmd_info, $output);
+ push(@missing, $module) if !$ok;
+ }
+ return \@missing;
+}
+
+sub _get_apachectl {
+ foreach my $bin_name (APACHE) {
+ my $bin = bin_loc($bin_name);
+ return $bin if $bin;
+ }
+ # Try again with a possibly different path.
+ foreach my $bin_name (APACHE) {
+ my $bin = bin_loc($bin_name, APACHE_PATH);
+ return $bin if $bin;
+ }
+ return undef;
+}
+
+sub _check_apache_module {
+ my ($module, $config_name, $mod_info, $output) = @_;
+ my $ok;
+ if ($mod_info =~ /^\s+\Q$config_name\E\b/m) {
+ $ok = 1;
+ }
+ if ($output) {
+ _checking_for({ package => $module, ok => $ok });
+ }
+ return $ok;
}
sub print_module_instructions {
my ($check_results, $output) = @_;
- # We only print these notes if we have to.
- if ((!$output && @{$check_results->{missing}})
- || ($output && $check_results->{any_missing}))
- {
-
- if (ON_WINDOWS) {
+ # First we print the long explanatory messages.
- print "\n* NOTE: You must run any commands listed below as "
- . ROOT_USER . ".\n\n";
-
- my $perl_ver = sprintf('%vd', $^V);
-
- # URL when running Perl 5.8.x.
- my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
- my $repo_up_cmd =
-'* *';
- # Packages for Perl 5.10 are not compatible with Perl 5.8.
- if (vers_cmp($perl_ver, '5.10') > -1) {
- $url_to_theory58S = 'http://cpan.uwinnipeg.ca/PPMPackages/10xx/';
- }
- # ActivePerl older than revision 819 require an additional command.
- if (_get_activestate_build_id() < 819) {
- $repo_up_cmd = <<EOT;
-* *
-* Then you have to do (also as an Administrator): *
-* *
-* ppm repo up theory58S *
-* *
-* Do that last command over and over until you see "theory58S" at the *
-* top of the displayed list. *
-EOT
- }
- print <<EOT;
-***********************************************************************
-* Note For Windows Users *
-***********************************************************************
-* In order to install the modules listed below, you first have to run *
-* the following command as an Administrator: *
-* *
-* ppm repo add theory58S $url_to_theory58S
-$repo_up_cmd
-***********************************************************************
-EOT
- }
- }
-
- # Required Modules
- if (my @missing = @{$check_results->{missing}}) {
- print <<EOT;
-***********************************************************************
-* REQUIRED MODULES *
-***********************************************************************
-* Bugzilla requires you to install some Perl modules which are either *
-* missing from your system, or the version on your system is too old. *
-* *
-* The latest versions of each module can be installed by running the *
-* commands below. *
-***********************************************************************
-EOT
-
- print "COMMANDS:\n\n";
- foreach my $package (@missing) {
- my $command = install_command($package);
- print " $command\n";
- }
- print "\n";
+ if (scalar @{$check_results->{missing}}) {
+ print install_string('modules_message_required');
}
if (!$check_results->{one_dbd}) {
- print <<EOT;
-***********************************************************************
-* DATABASE ACCESS *
-***********************************************************************
-* In order to access your database, Bugzilla requires that the *
-* correct "DBD" module be installed for the database that you are *
-* running. *
-* *
-* Pick and run the correct command below for the database that you *
-* plan to use with Bugzilla. *
-***********************************************************************
-COMMANDS:
-
-EOT
-
- my %db_modules = %{DB_MODULE()};
- foreach my $db (keys %db_modules) {
- my $command = install_command($db_modules{$db}->{dbd});
- printf "%10s: \%s\n", $db_modules{$db}->{name}, $command;
- print ' ' x 12 . "Minimum version required: "
- . $db_modules{$db}->{dbd}->{version} . "\n";
- }
- print "\n";
+ print install_string('modules_message_db');
}
- return unless $output;
-
- if (my @missing = @{$check_results->{optional}}) {
- print <<EOT;
-**********************************************************************
-* OPTIONAL MODULES *
-**********************************************************************
-* Certain Perl modules are not required by Bugzilla, but by *
-* installing the latest version you gain access to additional *
-* features. *
-* *
-* The optional modules you do not have installed are listed below, *
-* with the name of the feature they enable. If you want to install *
-* one of these modules, just run the appropriate command in the *
-* "COMMANDS TO INSTALL" section. *
-**********************************************************************
-
-EOT
+ if (my @missing = @{$check_results->{optional}} and $output) {
+ print install_string('modules_message_optional');
# Now we have to determine how large the table cols will be.
my $longest_name = max(map(length($_->{package}), @missing));
# The first column header is at least 11 characters long.
$longest_name = 11 if $longest_name < 11;
- # The table is 71 characters long. There are seven mandatory
+ # The table is TABLE_WIDTH characters long. There are seven mandatory
# characters (* and space) in the string. So, we have a total
- # of 64 characters to work with.
- my $remaining_space = 64 - $longest_name;
- print '*' x 71 . "\n";
+ # of TABLE_WIDTH - 7 characters to work with.
+ my $remaining_space = (TABLE_WIDTH - 7) - $longest_name;
+ print '*' x TABLE_WIDTH . "\n";
printf "* \%${longest_name}s * %-${remaining_space}s *\n",
'MODULE NAME', 'ENABLES FEATURE(S)';
- print '*' x 71 . "\n";
+ print '*' x TABLE_WIDTH . "\n";
foreach my $package (@missing) {
printf "* \%${longest_name}s * %-${remaining_space}s *\n",
- $package->{package}, $package->{feature};
+ $package->{package},
+ _translate_feature($package->{feature});
}
- print '*' x 71 . "\n";
+ }
- print "COMMANDS TO INSTALL:\n\n";
+ if (my @missing = @{ $check_results->{apache} }) {
+ print install_string('modules_message_apache');
+ my $missing_string = join(', ', @missing);
+ my $size = TABLE_WIDTH - 7;
+ printf "* \%-${size}s *\n", $missing_string;
+ my $spaces = TABLE_WIDTH - 2;
+ print "*", (' ' x $spaces), "*\n";
+ }
+
+ my $need_module_instructions =
+ ( (!$output and @{$check_results->{missing}})
+ or ($output and $check_results->{any_missing}) ) ? 1 : 0;
+
+ # We only print the PPM repository note if we have to.
+ my $perl_ver = sprintf('%vd', $^V);
+ if ($need_module_instructions && ON_ACTIVESTATE && vers_cmp($perl_ver, '5.12') < 0) {
+ # URL when running Perl 5.8.x.
+ my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
+ # Packages for Perl 5.10 are not compatible with Perl 5.8.
+ if (vers_cmp($perl_ver, '5.10') > -1) {
+ $url_to_theory58S = 'http://cpan.uwinnipeg.ca/PPMPackages/10xx/';
+ }
+ print colored(
+ install_string('ppm_repo_add',
+ { theory_url => $url_to_theory58S }),
+ COLOR_ERROR);
+
+ # ActivePerls older than revision 819 require an additional command.
+ if (ON_ACTIVESTATE < 819) {
+ print install_string('ppm_repo_up');
+ }
+ }
+
+ if ($need_module_instructions or @{ $check_results->{apache} }) {
+ # If any output was required, we want to close the "table"
+ print "*" x TABLE_WIDTH . "\n";
+ }
+
+ # And now we print the actual installation commands.
+
+ if (my @missing = @{$check_results->{optional}} and $output) {
+ print install_string('commands_optional') . "\n\n";
foreach my $module (@missing) {
my $command = install_command($module);
printf "%15s: $command\n", $module->{package};
}
+ print "\n";
}
- if ($output && $check_results->{any_missing} && !ON_WINDOWS) {
+ if (!$check_results->{one_dbd}) {
+ print install_string('commands_dbd') . "\n";
+ my %db_modules = %{DB_MODULE()};
+ foreach my $db (keys %db_modules) {
+ my $command = install_command($db_modules{$db}->{dbd});
+ printf "%10s: \%s\n", $db_modules{$db}->{name}, $command;
+ }
+ print "\n";
+ }
+
+ if (my @missing = @{$check_results->{missing}}) {
+ print colored(install_string('commands_required'), COLOR_ERROR), "\n";
+ foreach my $package (@missing) {
+ my $command = install_command($package);
+ print " $command\n";
+ }
+ }
+
+ if ($output && $check_results->{any_missing} && !ON_ACTIVESTATE
+ && !$check_results->{hide_all})
+ {
print install_string('install_all', { perl => $^X });
}
+ if (!$check_results->{pass}) {
+ print colored(install_string('installation_failed'), COLOR_ERROR),
+ "\n\n";
+ }
+}
+
+sub _translate_feature {
+ my $features = shift;
+ my @strings;
+ foreach my $feature (@$features) {
+ push(@strings, install_string("feature_$feature"));
+ }
+ return join(', ', @strings);
}
sub check_graphviz {
my ($output) = @_;
- return 1 if (Bugzilla->params->{'webdotbase'} =~ /^https?:/);
+ my $webdotbase = Bugzilla->params->{'webdotbase'};
+ return 1 if $webdotbase =~ /^https?:/;
- printf("Checking for %15s %-9s ", "GraphViz", "(any)") if $output;
+ my $return;
+ $return = 1 if -x $webdotbase;
- my $return = 0;
- if(-x Bugzilla->params->{'webdotbase'}) {
- print "ok: found\n" if $output;
- $return = 1;
- } else {
- print "not a valid executable: " . Bugzilla->params->{'webdotbase'} . "\n";
+ if ($output) {
+ _checking_for({ package => 'GraphViz', ok => $return });
+ }
+
+ if (!$return) {
+ print install_string('bad_executable', { bin => $webdotbase }), "\n";
}
my $webdotdir = bz_locations()->{'webdotdir'};
@@ -497,8 +644,8 @@
my $htaccess = new IO::File("$webdotdir/.htaccess", 'r')
|| die "$webdotdir/.htaccess: " . $!;
if (!grep(/png/, $htaccess->getlines)) {
- print "Dependency graph images are not accessible.\n";
- print "delete $webdotdir/.htaccess and re-run checksetup.pl to fix.\n";
+ print STDERR install_string('webdot_bad_htaccess',
+ { dir => $webdotdir }), "\n";
}
$htaccess->close;
}
@@ -519,9 +666,14 @@
my $wanted = $params->{version};
eval "require $module;";
+ # Don't let loading a module change the output-encoding of STDOUT
+ # or STDERR. (CGI.pm tries to set "binmode" on these file handles when
+ # it's loaded, and other modules may do the same in the future.)
+ Bugzilla::Install::Util::set_output_encoding();
- # VERSION is provided by UNIVERSAL::
- my $vnum = eval { $module->VERSION } || -1;
+ # VERSION is provided by UNIVERSAL::, and can be called even if
+ # the module isn't loaded.
+ my $vnum = $module->VERSION || -1;
# CGI's versioning scheme went 2.75, 2.751, 2.752, 2.753, 2.76
# That breaks the standard version tests, so we need to manually correct
@@ -529,16 +681,9 @@
if ($module eq 'CGI' && $vnum =~ /(2\.7\d)(\d+)/) {
$vnum = $1 . "." . $2;
}
-
- my $vstr;
- if ($vnum eq "-1") { # string compare just in case it's non-numeric
- $vstr = install_string('module_not_found');
- }
- elsif (vers_cmp($vnum,"0") > -1) {
- $vstr = install_string('module_found', { ver => $vnum });
- }
- else {
- $vstr = install_string('module_unknown_version');
+ # CPAN did a similar thing, where it has versions like 1.9304.
+ if ($module eq 'CPAN' and $vnum =~ /^(\d\.\d{2})\d{2}$/) {
+ $vnum = $1;
}
my $vok = (vers_cmp($vnum,$wanted) > -1);
@@ -549,23 +694,58 @@
}
if ($output) {
- my $ok = $vok ? install_string('module_ok') : '';
- my $black_string = $blacklisted ? install_string('blacklisted') : '';
- my $want_string = $wanted ? "v$wanted" : install_string('any');
-
- $ok = "$ok:" if $ok;
- printf "%s %19s %-9s $ok $vstr $black_string\n",
- install_string('checking_for'), $package, "($want_string)";
+ _checking_for({
+ package => $package, ok => $vok, wanted => $wanted,
+ found => $vnum, blacklisted => $blacklisted
+ });
}
return $vok ? 1 : 0;
}
+sub _checking_for {
+ my ($params) = @_;
+ my ($package, $ok, $wanted, $blacklisted, $found) =
+ @$params{qw(package ok wanted blacklisted found)};
+
+ my $ok_string = $ok ? install_string('module_ok') : '';
+
+ # If we're actually checking versions (like for Perl modules), then
+ # we have some rather complex logic to determine what we want to
+ # show. If we're not checking versions (like for GraphViz) we just
+ # show "ok" or "not found".
+ if (exists $params->{found}) {
+ my $found_string;
+ # We do a string compare in case it's non-numeric. We make sure
+ # it's not a version object as negative versions are forbidden.
+ if ($found && !ref($found) && $found eq '-1') {
+ $found_string = install_string('module_not_found');
+ }
+ elsif ($found) {
+ $found_string = install_string('module_found', { ver => $found });
+ }
+ else {
+ $found_string = install_string('module_unknown_version');
+ }
+ $ok_string = $ok ? "$ok_string: $found_string" : $found_string;
+ }
+ elsif (!$ok) {
+ $ok_string = install_string('module_not_found');
+ }
+
+ my $black_string = $blacklisted ? install_string('blacklisted') : '';
+ my $want_string = $wanted ? "v$wanted" : install_string('any');
+
+ my $str = sprintf "%s %20s %-11s $ok_string $black_string\n",
+ install_string('checking_for'), $package, "($want_string)";
+ print $ok ? $str : colored($str, COLOR_ERROR);
+}
+
sub install_command {
my $module = shift;
my ($command, $package);
- if (ON_WINDOWS) {
+ if (ON_ACTIVESTATE) {
$command = 'ppm install %s';
$package = $module->{package};
}
@@ -578,6 +758,21 @@
return sprintf $command, $package;
}
+# This does a reverse mapping for FEATURE_FILES.
+sub map_files_to_features {
+ my %features = FEATURE_FILES;
+ my %files;
+ foreach my $feature (keys %features) {
+ my @my_files = @{ $features{$feature} };
+ foreach my $pattern (@my_files) {
+ foreach my $file (glob $pattern) {
+ $files{$file} = $feature;
+ }
+ }
+ }
+ return \%files;
+}
+
1;
__END__
@@ -595,16 +790,42 @@
=head1 CONSTANTS
-=over 4
+=over
=item C<REQUIRED_MODULES>
An arrayref of hashrefs that describes the perl modules required by
-Bugzilla. The hashes have two keys, C<name> and C<version>, which
-represent the name of the module and the version that we require.
+Bugzilla. The hashes have three keys:
+
+=over
+
+=item C<package> - The name of the Perl package that you'd find on
+CPAN for this requirement.
+
+=item C<module> - The name of a module that can be passed to the
+C<install> command in C<CPAN.pm> to install this module.
+
+=item C<version> - The version of this module that we require, or C<0>
+if any version is acceptable.
=back
+=item C<OPTIONAL_MODULES>
+
+An arrayref of hashrefs that describes the perl modules that add
+additional features to Bugzilla if installed. Its hashes have all
+the fields of L</REQUIRED_MODULES>, plus a C<feature> item--an arrayref
+of strings that describe what features require this module.
+
+=item C<FEATURE_FILES>
+
+A hashref that describes what files should only be compiled if a certain
+feature is enabled. The feature is the key, and the values are arrayrefs
+of file names (which are passed to C<glob>, so shell patterns work).
+
+=back
+
+
=head1 SUBROUTINES
=over 4
@@ -641,10 +862,12 @@
=item C<optional> - The same as C<missing>, but for optional modules.
+=item C<apache> - The name of each optional Apache module that is missing.
+
=item C<have_one_dbd> - True if at least one C<DBD::> module is installed.
-=item C<any_missing> - True if there are any missing modules, even optional
-modules.
+=item C<any_missing> - True if there are any missing Perl modules, even
+optional modules.
=back
@@ -687,4 +910,9 @@
Returns: nothing
+=item C<map_files_to_features>
+
+Returns a hashref where file names are the keys and the value is the feature
+that must be enabled in order to compile that file.
+
=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/Install/Util.pm b/Websites/bugs.webkit.org/Bugzilla/Install/Util.pm
index 9cec8c4..bd89425 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Install/Util.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Install/Util.pm
@@ -28,35 +28,54 @@
use Bugzilla::Constants;
+use Encode;
+use ExtUtils::MM ();
use File::Basename;
+use File::Spec;
use POSIX qw(setlocale LC_CTYPE);
use Safe;
+use Scalar::Util qw(tainted);
+use Term::ANSIColor qw(colored);
+use PerlIO;
use base qw(Exporter);
our @EXPORT_OK = qw(
bin_loc
get_version_and_os
+ extension_code_files
+ extension_package_directory
+ extension_requirement_packages
+ extension_template_directory
+ extension_web_directory
indicate_progress
install_string
include_languages
+ success
template_include_path
vers_cmp
- get_console_locale
+ init_console
);
sub bin_loc {
- my ($bin) = @_;
- return '' if ON_WINDOWS;
- # Don't print any errors from "which"
- open(my $saveerr, ">&STDERR");
- open(STDERR, '>/dev/null');
- my $loc = `which $bin`;
- close(STDERR);
- open(STDERR, ">&", $saveerr);
- my $exit_code = $? >> 8; # See the perlvar manpage.
- return '' if $exit_code > 0;
- chomp($loc);
- return $loc;
+ my ($bin, $path) = @_;
+
+ # If the binary is a full path...
+ if ($bin =~ m{[/\\]}) {
+ return MM->maybe_command($bin) || '';
+ }
+
+ # Otherwise we look for it in the path in a cross-platform way.
+ my @path = $path ? @$path : File::Spec->path;
+ foreach my $dir (@path) {
+ next if !-d $dir;
+ my $full_path = File::Spec->catfile($dir, $bin);
+ # MM is an alias for ExtUtils::MM. maybe_command is nice
+ # because it checks .com, .bat, .exe (etc.) on Windows.
+ my $command = MM->maybe_command($full_path);
+ return $command if $command;
+ }
+
+ return '';
}
sub get_version_and_os {
@@ -75,6 +94,175 @@
os_ver => $os_details[3] };
}
+sub _extension_paths {
+ my $dir = bz_locations()->{'extensionsdir'};
+ my @extension_items = glob("$dir/*");
+ my @paths;
+ foreach my $item (@extension_items) {
+ my $basename = basename($item);
+ # Skip CVS directories and any hidden files/dirs.
+ next if ($basename eq 'CVS' or $basename =~ /^\./);
+ if (-d $item) {
+ if (!-e "$item/disabled") {
+ push(@paths, $item);
+ }
+ }
+ elsif ($item =~ /\.pm$/i) {
+ push(@paths, $item);
+ }
+ }
+ return @paths;
+}
+
+sub extension_code_files {
+ my ($requirements_only) = @_;
+ my @files;
+ foreach my $path (_extension_paths()) {
+ my @load_files;
+ if (-d $path) {
+ my $extension_file = "$path/Extension.pm";
+ my $config_file = "$path/Config.pm";
+ if (-e $extension_file) {
+ push(@load_files, $extension_file);
+ }
+ if (-e $config_file) {
+ push(@load_files, $config_file);
+ }
+
+ # Don't load Extension.pm if we just want Config.pm and
+ # we found both.
+ if ($requirements_only and scalar(@load_files) == 2) {
+ shift(@load_files);
+ }
+ }
+ else {
+ push(@load_files, $path);
+ }
+ next if !scalar(@load_files);
+ # We know that these paths are safe, because they came from
+ # extensionsdir and we checked them specifically for their format.
+ # Also, the only thing we ever do with them is pass them to "require".
+ trick_taint($_) foreach @load_files;
+ push(@files, \@load_files);
+ }
+
+ my @additional;
+ my $datadir = bz_locations()->{'datadir'};
+ my $addl_file = "$datadir/extensions/additional";
+ if (-e $addl_file) {
+ open(my $fh, '<', $addl_file) || die "$addl_file: $!";
+ @additional = map { trim($_) } <$fh>;
+ close($fh);
+ }
+ return (\@files, \@additional);
+}
+
+# Used by _get_extension_requirements in Bugzilla::Install::Requirements.
+sub extension_requirement_packages {
+ # If we're in a .cgi script or some time that's not the requirements phase,
+ # just use Bugzilla->extensions. This avoids running the below code during
+ # a normal Bugzilla page, which is important because the below code
+ # doesn't actually function right if it runs after
+ # Bugzilla::Extension->load_all (because stuff has already been loaded).
+ # (This matters because almost every page calls Bugzilla->feature, which
+ # calls OPTIONAL_MODULES, which calls this method.)
+ #
+ # We check if Bugzilla.pm is already loaded, instead of doing a "require",
+ # because we *do* want the code lower down to run during the Requirements
+ # phase of checksetup.pl, instead of Bugzilla->extensions, and Bugzilla.pm
+ # actually *can* be loaded during the Requirements phase if all the
+ # requirements have already been installed.
+ if ($INC{'Bugzilla.pm'}) {
+ return Bugzilla->extensions;
+ }
+ my $packages = _cache()->{extension_requirement_packages};
+ return $packages if $packages;
+ $packages = [];
+ my %package_map;
+
+ my ($file_sets, $extra_packages) = extension_code_files('requirements only');
+ foreach my $file_set (@$file_sets) {
+ my $file = shift @$file_set;
+ my $name = require $file;
+ if ($name =~ /^\d+$/) {
+ die install_string('extension_must_return_name',
+ { file => $file, returned => $name });
+ }
+ my $package = "Bugzilla::Extension::$name";
+ if ($package->can('package_dir')) {
+ $package->package_dir($file);
+ }
+ else {
+ extension_package_directory($package, $file);
+ }
+ $package_map{$file} = $package;
+ push(@$packages, $package);
+ }
+ foreach my $package (@$extra_packages) {
+ eval("require $package") || die $@;
+ push(@$packages, $package);
+ }
+
+ _cache()->{extension_requirement_packages} = $packages;
+ # Used by Bugzilla::Extension->load if it's called after this method
+ # (which only happens during checksetup.pl, currently).
+ _cache()->{extension_requirement_package_map} = \%package_map;
+ return $packages;
+}
+
+# Used in this file and in Bugzilla::Extension.
+sub extension_template_directory {
+ my $extension = shift;
+ my $class = ref($extension) || $extension;
+ my $base_dir = extension_package_directory($class);
+ if ($base_dir eq bz_locations->{'extensionsdir'}) {
+ return bz_locations->{'templatedir'};
+ }
+ return "$base_dir/template";
+}
+
+# Used in this file and in Bugzilla::Extension.
+sub extension_web_directory {
+ my $extension = shift;
+ my $class = ref($extension) || $extension;
+ my $base_dir = extension_package_directory($class);
+ return "$base_dir/web";
+}
+
+# For extensions that are in the extensions/ dir, this both sets and fetches
+# the name of the directory that stores an extension's "stuff". We need this
+# when determining the template directory for extensions (or other things
+# that are relative to the extension's base directory).
+sub extension_package_directory {
+ my ($invocant, $file) = @_;
+ my $class = ref($invocant) || $invocant;
+
+ # $file is set on the first invocation, store the value in the extension's
+ # package for retrieval on subsequent calls
+ my $var;
+ {
+ no warnings 'once';
+ no strict 'refs';
+ $var = \${"${class}::EXTENSION_PACKAGE_DIR"};
+ }
+ if ($file) {
+ $$var = dirname($file);
+ }
+ my $value = $$var;
+
+ # This is for extensions loaded from data/extensions/additional.
+ if (!$value) {
+ my $short_path = $class;
+ $short_path =~ s/::/\//g;
+ $short_path .= ".pm";
+ my $long_path = $INC{$short_path};
+ die "$short_path is not in \%INC" if !$long_path;
+ $value = $long_path;
+ $value =~ s/\.pm//;
+ }
+ return $value;
+}
+
sub indicate_progress {
my ($params) = @_;
my $current = $params->{current};
@@ -89,8 +277,8 @@
sub install_string {
my ($string_id, $vars) = @_;
- _cache()->{template_include_path} ||= template_include_path();
- my $path = _cache()->{template_include_path};
+ _cache()->{install_string_path} ||= template_include_path();
+ my $path = _cache()->{install_string_path};
my $string_template;
# Find the first template that defines this string.
@@ -104,12 +292,14 @@
die "No language defines the string '$string_id'"
if !defined $string_template;
+ utf8::decode($string_template) if !utf8::is_utf8($string_template);
+
$vars ||= {};
my @replace_keys = keys %$vars;
foreach my $key (@replace_keys) {
my $replacement = $vars->{$key};
die "'$key' in '$string_id' is tainted: '$replacement'"
- if is_tainted($replacement);
+ if tainted($replacement);
# We don't want people to start getting clever and inserting
# ##variable## into their values. So we check if any other
# key is listed in the *replacement* string, before doing
@@ -120,82 +310,178 @@
}
$string_template =~ s/\Q##$key##\E/$replacement/g;
}
-
+
return $string_template;
}
-sub include_languages {
- my ($params) = @_;
- $params ||= {};
+sub _wanted_languages {
+ my ($requested, @wanted);
- # Basically, the way this works is that we have a list of languages
- # that we *want*, and a list of languages that Bugzilla actually
- # supports. The caller tells us what languages they want, by setting
- # $ENV{HTTP_ACCEPT_LANGUAGE} or $params->{only_language}. The languages
- # we support are those specified in $params->{use_languages}. Otherwise
- # we support every language installed in the template/ directory.
-
- my @wanted;
- if ($params->{only_language}) {
- @wanted = ($params->{only_language});
+ # Checking SERVER_SOFTWARE is the same as i_am_cgi() in Bugzilla::Util.
+ if (exists $ENV{'SERVER_SOFTWARE'}) {
+ my $cgi = eval { Bugzilla->cgi } || eval { require CGI; return CGI->new() };
+ $requested = $cgi->http('Accept-Language') || '';
+ my $lang = $cgi->cookie('LANG');
+ push(@wanted, $lang) if $lang;
}
else {
- @wanted = _sort_accept_language($ENV{'HTTP_ACCEPT_LANGUAGE'} || '');
+ $requested = get_console_locale();
}
-
- my @supported;
- if (defined $params->{use_languages}) {
- @supported = @{$params->{use_languages}};
- }
- else {
- my @dirs = glob(bz_locations()->{'templatedir'} . "/*");
- @dirs = map(basename($_), @dirs);
- @supported = grep($_ ne 'CVS', @dirs);
- }
-
- my @usedlanguages;
- foreach my $wanted (@wanted) {
+
+ push(@wanted, _sort_accept_language($requested));
+ return \@wanted;
+}
+
+sub _wanted_to_actual_languages {
+ my ($wanted, $supported) = @_;
+
+ my @actual;
+ foreach my $lang (@$wanted) {
# If we support the language we want, or *any version* of
- # the language we want, it gets pushed into @usedlanguages.
+ # the language we want, it gets pushed into @actual.
#
# Per RFC 1766 and RFC 2616, things like 'en' match 'en-us' and
# 'en-uk', but not the other way around. (This is unfortunately
# not very clearly stated in those RFC; see comment just over 14.5
# in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)
- if(my @found = grep /^\Q$wanted\E(-.+)?$/i, @supported) {
- push (@usedlanguages, @found);
- }
+ my @found = grep(/^\Q$lang\E(-.+)?$/i, @$supported);
+ push(@actual, @found) if @found;
}
# We always include English at the bottom if it's not there, even if
- # somebody removed it from use_languages.
- if (!grep($_ eq 'en', @usedlanguages)) {
- push(@usedlanguages, 'en');
+ # it wasn't selected by the user.
+ if (!grep($_ eq 'en', @actual)) {
+ push(@actual, 'en');
}
- return @usedlanguages;
+ return \@actual;
}
+
+sub supported_languages {
+ my $cache = _cache();
+ return $cache->{supported_languages} if $cache->{supported_languages};
+
+ my @dirs = glob(bz_locations()->{'templatedir'} . "/*");
+ my @languages;
+ foreach my $dir (@dirs) {
+ # It's a language directory only if it contains "default" or
+ # "custom". This auto-excludes CVS directories as well.
+ next if (!-d "$dir/default" and !-d "$dir/custom");
+ my $lang = basename($dir);
+ # Check for language tag format conforming to RFC 1766.
+ next unless $lang =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/;
+ push(@languages, $lang);
+ }
+
+ $cache->{supported_languages} = \@languages;
+ return \@languages;
+}
+
+sub include_languages {
+ my ($params) = @_;
+
+ # Basically, the way this works is that we have a list of languages
+ # that we *want*, and a list of languages that Bugzilla actually
+ # supports.
+ my $wanted;
+ if ($params->{language}) {
+ # We can pass several languages at once as an arrayref
+ # or a single language.
+ $wanted = $params->{language};
+ $wanted = [$wanted] unless ref $wanted;
+ }
+ else {
+ $wanted = _wanted_languages();
+ }
+ my $supported = supported_languages();
+ my $actual = _wanted_to_actual_languages($wanted, $supported);
+ return @$actual;
+}
+
+# Used by template_include_path
+sub _template_lang_directories {
+ my ($languages, $templatedir) = @_;
-sub template_include_path {
- my @usedlanguages = include_languages(@_);
- # Now, we add template directories in the order they will be searched:
-
+ my @add = qw(custom default);
+ my $project = bz_locations->{'project'};
+ unshift(@add, $project) if $project;
+
+ my @result;
+ foreach my $lang (@$languages) {
+ foreach my $dir (@add) {
+ my $full_dir = "$templatedir/$lang/$dir";
+ if (-d $full_dir) {
+ trick_taint($full_dir);
+ push(@result, $full_dir);
+ }
+ }
+ }
+ return @result;
+}
+
+# Used by template_include_path.
+sub _template_base_directories {
# First, we add extension template directories, because extension templates
# override standard templates. Extensions may be localized in the same way
# that Bugzilla templates are localized.
- my @include_path;
- my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
- foreach my $extension (@extensions) {
- foreach my $lang (@usedlanguages) {
- _add_language_set(\@include_path, $lang, "$extension/template");
+ #
+ # We use extension_requirement_packages instead of Bugzilla->extensions
+ # because this fucntion is called during the requirements phase of
+ # installation (so Bugzilla->extensions isn't available).
+ my $extensions = extension_requirement_packages();
+ my @template_dirs;
+ foreach my $extension (@$extensions) {
+ my $dir;
+ # If there's a template_dir method available in the extension
+ # package, then call it. Note that this has to be defined in
+ # Config.pm for extensions that have a Config.pm, to be effective
+ # during the Requirements phase of checksetup.pl.
+ if ($extension->can('template_dir')) {
+ $dir = $extension->template_dir;
+ }
+ else {
+ $dir = extension_template_directory($extension);
+ }
+ if (-d $dir) {
+ push(@template_dirs, $dir);
}
}
-
- # Then, we add normal template directories, sorted by language.
- foreach my $lang (@usedlanguages) {
- _add_language_set(\@include_path, $lang);
+
+ # Extensions may also contain *only* templates, in which case they
+ # won't show up in extension_requirement_packages.
+ foreach my $path (_extension_paths()) {
+ next if !-d $path;
+ if (!-e "$path/Extension.pm" and !-e "$path/Config.pm"
+ and -d "$path/template")
+ {
+ push(@template_dirs, "$path/template");
+ }
}
-
+
+
+ push(@template_dirs, bz_locations()->{'templatedir'});
+ return \@template_dirs;
+}
+
+sub template_include_path {
+ my ($params) = @_;
+ my @used_languages = include_languages($params);
+ # Now, we add template directories in the order they will be searched:
+ my $template_dirs = _template_base_directories();
+
+ my @include_path;
+ foreach my $template_dir (@$template_dirs) {
+ my @lang_dirs = _template_lang_directories(\@used_languages,
+ $template_dir);
+ # Hooks get each set of extension directories separately.
+ if ($params->{hook}) {
+ push(@include_path, \@lang_dirs);
+ }
+ # Whereas everything else just gets a whole INCLUDE_PATH.
+ else {
+ push(@include_path, @lang_dirs);
+ }
+ }
return \@include_path;
}
@@ -242,6 +528,12 @@
@A <=> @B;
}
+sub no_checksetup_from_cgi {
+ print "Content-Type: text/html; charset=UTF-8\r\n\r\n";
+ print install_string('no_checksetup_from_cgi');
+ exit;
+}
+
######################
# Helper Subroutines #
######################
@@ -257,24 +549,6 @@
return $strings{$string_id};
}
-# Used by template_include_path.
-sub _add_language_set {
- my ($array, $lang, $templatedir) = @_;
-
- $templatedir ||= bz_locations()->{'templatedir'};
- my @add = ("$templatedir/$lang/custom", "$templatedir/$lang/default");
-
- my $project = bz_locations->{'project'};
- unshift(@add, "$templatedir/$lang/$project") if $project;
-
- foreach my $dir (@add) {
- if (-d $dir) {
- trick_taint($dir);
- push(@$array, $dir);
- }
- }
-}
-
# Make an ordered list out of a HTTP Accept-Language header (see RFC 2616, 14.4)
# We ignore '*' and <language-range>;q=0
# For languages with the same priority q the order remains unchanged.
@@ -330,14 +604,97 @@
return $locale;
}
+sub set_output_encoding {
+ # If we've already set an encoding layer on STDOUT, don't
+ # add another one.
+ my @stdout_layers = PerlIO::get_layers(STDOUT);
+ return if grep(/^encoding/, @stdout_layers);
+
+ my $encoding;
+ if (ON_WINDOWS and eval { require Win32::Console }) {
+ # Although setlocale() works on Windows, it doesn't always return
+ # the current *console's* encoding. So we use OutputCP here instead,
+ # when we can.
+ $encoding = Win32::Console::OutputCP();
+ }
+ else {
+ my $locale = setlocale(LC_CTYPE);
+ if ($locale =~ /\.([^\.]+)$/) {
+ $encoding = $1;
+ }
+ }
+ $encoding = "cp$encoding" if ON_WINDOWS;
+
+ $encoding = Encode::resolve_alias($encoding) if $encoding;
+ if ($encoding and $encoding !~ /utf-8/i) {
+ binmode STDOUT, ":encoding($encoding)";
+ binmode STDERR, ":encoding($encoding)";
+ }
+ else {
+ binmode STDOUT, ':utf8';
+ binmode STDERR, ':utf8';
+ }
+}
+
+sub init_console {
+ eval { ON_WINDOWS && require Win32::Console::ANSI; };
+ $ENV{'ANSI_COLORS_DISABLED'} = 1 if ($@ || !-t *STDOUT);
+ $SIG{__DIE__} = \&_console_die;
+ prevent_windows_dialog_boxes();
+ set_output_encoding();
+}
+
+sub _console_die {
+ my ($message) = @_;
+ # $^S means "we are in an eval"
+ if ($^S) {
+ die $message;
+ }
+ # Remove newlines from the message before we color it, and then
+ # add them back in on display. Otherwise the ANSI escape code
+ # for resetting the color comes after the newline, and Perl thinks
+ # that it should put "at Bugzilla/Install.pm line 1234" after the
+ # message.
+ $message =~ s/\n+$//;
+ # We put quotes around the message to stringify any object exceptions,
+ # like Template::Exception.
+ die colored("$message", COLOR_ERROR) . "\n";
+}
+
+sub success {
+ my ($message) = @_;
+ print colored($message, COLOR_SUCCESS), "\n";
+}
+
+sub prevent_windows_dialog_boxes {
+ # This code comes from http://bugs.activestate.com/show_bug.cgi?id=82183
+ # and prevents Perl modules from popping up dialog boxes, particularly
+ # during checksetup (since loading DBD::Oracle during checksetup when
+ # Oracle isn't installed causes a scary popup and pauses checksetup).
+ #
+ # Win32::API ships with ActiveState by default, though there could
+ # theoretically be a Windows installation without it, I suppose.
+ if (ON_WINDOWS and eval { require Win32::API }) {
+ # Call kernel32.SetErrorMode with arguments that mean:
+ # "The system does not display the critical-error-handler message box.
+ # Instead, the system sends the error to the calling process." and
+ # "A child process inherits the error mode of its parent process."
+ my $SetErrorMode = Win32::API->new('kernel32', 'SetErrorMode',
+ 'I', 'I');
+ my $SEM_FAILCRITICALERRORS = 0x0001;
+ my $SEM_NOGPFAULTERRORBOX = 0x0002;
+ $SetErrorMode->Call($SEM_FAILCRITICALERRORS | $SEM_NOGPFAULTERRORBOX);
+ }
+}
# This is like request_cache, but it's used only by installation code
-# for setup.cgi and things like that.
+# for checksetup.pl and things like that.
our $_cache = {};
sub _cache {
- if ($ENV{MOD_PERL}) {
- require Apache2::RequestUtil;
- return Apache2::RequestUtil->request->pnotes();
+ # If the normal request_cache is available (which happens any time
+ # after the requirements phase) then we should use that.
+ if (eval { Bugzilla->request_cache; }) {
+ return Bugzilla->request_cache;
}
return $_cache;
}
@@ -354,8 +711,13 @@
return (defined($_[0]));
}
-sub is_tainted {
- return not eval { my $foo = join('',@_), kill 0; 1; };
+sub trim {
+ my ($str) = @_;
+ if ($str) {
+ $str =~ s/^\s+//g;
+ $str =~ s/\s+$//g;
+ }
+ return $str;
}
__END__
@@ -397,6 +759,10 @@
Returns the language to use based on the LC_CTYPE value returned by the OS.
If LC_CTYPE is of the form fr-CH, then fr is appended to the list.
+=item C<init_console>
+
+Sets the C<ANSI_COLORS_DISABLED> and C<HTTP_ACCEPT_LANGUAGE> environment variables.
+
=item C<indicate_progress>
=over
diff --git a/Websites/bugs.webkit.org/Bugzilla/Job/Mailer.pm b/Websites/bugs.webkit.org/Bugzilla/Job/Mailer.pm
new file mode 100644
index 0000000..09c3873
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Job/Mailer.pm
@@ -0,0 +1,57 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# Mozilla Corporation. All Rights Reserved.
+#
+# Contributor(s):
+# Mark Smith <mark@mozilla.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Job::Mailer;
+use strict;
+use Bugzilla::Mailer;
+BEGIN { eval "use base qw(TheSchwartz::Worker)"; }
+
+# The longest we expect a job to possibly take, in seconds.
+use constant grab_for => 300;
+# We don't want email to fail permanently very easily. Retry for 30 days.
+use constant max_retries => 725;
+
+# The first few retries happen quickly, but after that we wait an hour for
+# each retry.
+sub retry_delay {
+ my ($class, $num_retries) = @_;
+ if ($num_retries < 5) {
+ return (10, 30, 60, 300, 600)[$num_retries];
+ }
+ # One hour
+ return 60*60;
+}
+
+sub work {
+ my ($class, $job) = @_;
+ my $msg = $job->arg->{msg};
+ my $success = eval { MessageToMTA($msg, 1); 1; };
+ if (!$success) {
+ $job->failed($@);
+ undef $@;
+ }
+ else {
+ $job->completed;
+ }
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/JobQueue.pm b/Websites/bugs.webkit.org/Bugzilla/JobQueue.pm
new file mode 100644
index 0000000..7ea6783
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/JobQueue.pm
@@ -0,0 +1,126 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# Mozilla Corporation. All Rights Reserved.
+#
+# Contributor(s):
+# Mark Smith <mark@mozilla.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::JobQueue;
+
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Install::Util qw(install_string);
+use base qw(TheSchwartz);
+
+# This maps job names for Bugzilla::JobQueue to the appropriate modules.
+# If you add new types of jobs, you should add a mapping here.
+use constant JOB_MAP => {
+ send_mail => 'Bugzilla::Job::Mailer',
+};
+
+# Without a driver cache TheSchwartz opens a new database connection
+# for each email it sends. This cached connection doesn't persist
+# across requests.
+use constant DRIVER_CACHE_TIME => 300; # 5 minutes
+
+sub job_map {
+ if (!defined(Bugzilla->request_cache->{job_map})) {
+ my $job_map = JOB_MAP;
+ Bugzilla::Hook::process('job_map', { job_map => $job_map });
+ Bugzilla->request_cache->{job_map} = $job_map;
+ }
+
+ return Bugzilla->request_cache->{job_map};
+}
+
+sub new {
+ my $class = shift;
+
+ if (!Bugzilla->feature('jobqueue')) {
+ ThrowCodeError('feature_disabled', { feature => 'jobqueue' });
+ }
+
+ my $lc = Bugzilla->localconfig;
+ # We need to use the main DB as TheSchwartz module is going
+ # to write to it.
+ my $self = $class->SUPER::new(
+ databases => [{
+ dsn => Bugzilla->dbh_main->{private_bz_dsn},
+ user => $lc->{db_user},
+ pass => $lc->{db_pass},
+ prefix => 'ts_',
+ }],
+ driver_cache_expiration => DRIVER_CACHE_TIME,
+ );
+
+ return $self;
+}
+
+# A way to get access to the underlying databases directly.
+sub bz_databases {
+ my $self = shift;
+ my @hashes = keys %{ $self->{databases} };
+ return map { $self->driver_for($_) } @hashes;
+}
+
+# inserts a job into the queue to be processed and returns immediately
+sub insert {
+ my $self = shift;
+ my $job = shift;
+
+ my $mapped_job = Bugzilla::JobQueue->job_map()->{$job};
+ ThrowCodeError('jobqueue_no_job_mapping', { job => $job })
+ if !$mapped_job;
+ unshift(@_, $mapped_job);
+
+ my $retval = $self->SUPER::insert(@_);
+ # XXX Need to get an error message here if insert fails, but
+ # I don't see any way to do that in TheSchwartz.
+ ThrowCodeError('jobqueue_insert_failed', { job => $job, errmsg => $@ })
+ if !$retval;
+
+ return $retval;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::JobQueue - Interface between Bugzilla and TheSchwartz.
+
+=head1 SYNOPSIS
+
+ use Bugzilla;
+
+ my $obj = Bugzilla->job_queue();
+ $obj->insert('send_mail', { msg => $message });
+
+=head1 DESCRIPTION
+
+Certain tasks should be done asyncronously. The job queue system allows
+Bugzilla to use some sort of service to schedule jobs to happen asyncronously.
+
+=head2 Inserting a Job
+
+See the synopsis above for an easy to follow example on how to insert a
+job into the queue. Give it a name and some arguments and the job will
+be sent away to be done later.
diff --git a/Websites/bugs.webkit.org/Bugzilla/JobQueue/Runner.pm b/Websites/bugs.webkit.org/Bugzilla/JobQueue/Runner.pm
new file mode 100644
index 0000000..26755e7
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/JobQueue/Runner.pm
@@ -0,0 +1,246 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# Mozilla Corporation. All Rights Reserved.
+#
+# Contributor(s):
+# Mark Smith <mark@mozilla.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# XXX In order to support Windows, we have to make gd_redirect_output
+# use Log4Perl or something instead of calling "logger". We probably
+# also need to use Win32::Daemon or something like that to daemonize.
+
+package Bugzilla::JobQueue::Runner;
+
+use strict;
+use Cwd qw(abs_path);
+use File::Basename;
+use File::Copy;
+use Pod::Usage;
+
+use Bugzilla::Constants;
+use Bugzilla::JobQueue;
+use Bugzilla::Util qw(get_text);
+BEGIN { eval "use base qw(Daemon::Generic)"; }
+
+our $VERSION = BUGZILLA_VERSION;
+
+# Info we need to install/uninstall the daemon.
+our $chkconfig = "/sbin/chkconfig";
+our $initd = "/etc/init.d";
+our $initscript = "bugzilla-queue";
+
+# The Daemon::Generic docs say that it uses all sorts of
+# things from gd_preconfig, but in fact it does not. The
+# only thing it uses from gd_preconfig is the "pidfile"
+# config parameter.
+sub gd_preconfig {
+ my $self = shift;
+
+ my $pidfile = $self->{gd_args}{pidfile};
+ if (!$pidfile) {
+ $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname}
+ . ".pid";
+ }
+ return (pidfile => $pidfile);
+}
+
+# All config other than the pidfile has to be done in gd_getopt
+# in order for it to be set up early enough.
+sub gd_getopt {
+ my $self = shift;
+
+ $self->SUPER::gd_getopt();
+
+ if ($self->{gd_args}{progname}) {
+ $self->{gd_progname} = $self->{gd_args}{progname};
+ }
+ else {
+ $self->{gd_progname} = basename($0);
+ }
+
+ # There are places that Daemon Generic's new() uses $0 instead of
+ # gd_progname, which it really shouldn't, but this hack fixes it.
+ $self->{_original_zero} = $0;
+ $0 = $self->{gd_progname};
+}
+
+sub gd_postconfig {
+ my $self = shift;
+ # See the hack above in gd_getopt. This just reverses it
+ # in case anything else needs the accurate $0.
+ $0 = delete $self->{_original_zero};
+}
+
+sub gd_more_opt {
+ my $self = shift;
+ return (
+ 'pidfile=s' => \$self->{gd_args}{pidfile},
+ 'n=s' => \$self->{gd_args}{progname},
+ );
+}
+
+sub gd_usage {
+ pod2usage({ -verbose => 0, -exitval => 'NOEXIT' });
+ return 0
+}
+
+sub gd_can_install {
+ my $self = shift;
+
+ my $source_file;
+ if ( -e "/etc/SuSE-release" ) {
+ $source_file = "contrib/$initscript.suse";
+ } else {
+ $source_file = "contrib/$initscript.rhel";
+ }
+ my $dest_file = "$initd/$initscript";
+ my $sysconfig = '/etc/sysconfig';
+ my $config_file = "$sysconfig/$initscript";
+
+ if (!-x $chkconfig or !-d $initd) {
+ return $self->SUPER::gd_can_install(@_);
+ }
+
+ return sub {
+ if (!-w $initd) {
+ print "You must run the 'install' command as root.\n";
+ return;
+ }
+ if (-e $dest_file) {
+ print "$initscript already in $initd.\n";
+ }
+ else {
+ copy($source_file, $dest_file)
+ or die "Could not copy $source_file to $dest_file: $!";
+ chmod(0755, $dest_file)
+ or die "Could not change permissions on $dest_file: $!";
+ }
+
+ system($chkconfig, '--add', $initscript);
+ print "$initscript installed.",
+ " To start the daemon, do \"$dest_file start\" as root.\n";
+
+ if (-d $sysconfig and -w $sysconfig) {
+ if (-e $config_file) {
+ print "$config_file already exists.\n";
+ return;
+ }
+
+ open(my $config_fh, ">", $config_file)
+ or die "Could not write to $config_file: $!";
+ my $directory = abs_path(dirname($self->{_original_zero}));
+ my $owner_id = (stat $self->{_original_zero})[4];
+ my $owner = getpwuid($owner_id);
+ print $config_fh <<END;
+#!/bin/sh
+BUGZILLA="$directory"
+USER=$owner
+END
+ close($config_fh);
+ }
+ else {
+ print "Please edit $dest_file to configure the daemon.\n";
+ }
+ }
+}
+
+sub gd_can_uninstall {
+ my $self = shift;
+
+ if (-x $chkconfig and -d $initd) {
+ return sub {
+ if (!-e "$initd/$initscript") {
+ print "$initscript not installed.\n";
+ return;
+ }
+ system($chkconfig, '--del', $initscript);
+ print "$initscript disabled.",
+ " To stop it, run: $initd/$initscript stop\n";
+ }
+ }
+
+ return $self->SUPER::gd_can_install(@_);
+}
+
+sub gd_check {
+ my $self = shift;
+
+ # Get a count of all the jobs currently in the queue.
+ my $jq = Bugzilla->job_queue();
+ my @dbs = $jq->bz_databases();
+ my $count = 0;
+ foreach my $driver (@dbs) {
+ $count += $driver->select_one('SELECT COUNT(*) FROM ts_job', []);
+ }
+ print get_text('job_queue_depth', { count => $count }) . "\n";
+}
+
+sub gd_setup_signals {
+ my $self = shift;
+ $self->SUPER::gd_setup_signals();
+ $SIG{TERM} = sub { $self->gd_quit_event(); }
+}
+
+sub gd_other_cmd {
+ my ($self) = shift;
+ if ($ARGV[0] eq "once") {
+ $self->_do_work("work_once");
+
+ exit(0);
+ }
+
+ $self->SUPER::gd_other_cmd();
+}
+
+sub gd_run {
+ my $self = shift;
+
+ $self->_do_work("work");
+}
+
+sub _do_work {
+ my ($self, $fn) = @_;
+
+ my $jq = Bugzilla->job_queue();
+ $jq->set_verbose($self->{debug});
+ foreach my $module (values %{ Bugzilla::JobQueue->job_map() }) {
+ eval "use $module";
+ $jq->can_do($module);
+ }
+
+ $jq->$fn;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::JobQueue::Runner - A class representing the daemon that runs the
+job queue.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::JobQueue::Runner;
+ Bugzilla::JobQueue::Runner->new();
+
+=head1 DESCRIPTION
+
+This is a subclass of L<Daemon::Generic> that is used by L<jobqueue>
+to run the Bugzilla job queue.
diff --git a/Websites/bugs.webkit.org/Bugzilla/Keyword.pm b/Websites/bugs.webkit.org/Bugzilla/Keyword.pm
index 2152b33..e2ecc29 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Keyword.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Keyword.pm
@@ -35,8 +35,6 @@
use constant DB_TABLE => 'keyworddefs';
-use constant REQUIRED_CREATE_FIELDS => qw(name description);
-
use constant VALIDATORS => {
name => \&_check_name,
description => \&_check_description,
@@ -74,17 +72,12 @@
#### Subroutines ######
###############################
-sub keyword_count {
- my ($count) =
- Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM keyworddefs');
- return $count;
-}
-
sub get_all_with_bug_count {
my $class = shift;
my $dbh = Bugzilla->dbh;
my $keywords =
- $dbh->selectall_arrayref('SELECT ' . join(', ', DB_COLUMNS) . ',
+ $dbh->selectall_arrayref('SELECT '
+ . join(', ', $class->_get_db_columns) . ',
COUNT(keywords.bug_id) AS bug_count
FROM keyworddefs
LEFT JOIN keywords
@@ -111,7 +104,9 @@
my ($self, $name) = @_;
$name = trim($name);
- $name eq "" && ThrowUserError("keyword_blank_name");
+ if (!defined $name or $name eq "") {
+ ThrowUserError("keyword_blank_name");
+ }
if ($name =~ /[\s,]/) {
ThrowUserError("keyword_invalid_name");
}
@@ -129,7 +124,9 @@
sub _check_description {
my ($self, $desc) = @_;
$desc = trim($desc);
- $desc eq '' && ThrowUserError("keyword_blank_description");
+ if (!defined $desc or $desc eq '') {
+ ThrowUserError("keyword_blank_description");
+ }
return $desc;
}
@@ -145,8 +142,6 @@
use Bugzilla::Keyword;
- my $count = Bugzilla::Keyword::keyword_count;
-
my $description = $keyword->description;
my $keywords = Bugzilla::Keyword->get_all_with_bug_count();
@@ -166,14 +161,6 @@
=over
-=item C<keyword_count()>
-
- Description: A utility function to get the total number
- of keywords defined. Mostly used to see
- if there are any keywords defined at all.
- Params: none
- Returns: An integer, the count of keywords.
-
=item C<get_all_with_bug_count()>
Description: Returns all defined keywords. This is an efficient way
diff --git a/Websites/bugs.webkit.org/Bugzilla/Mailer.pm b/Websites/bugs.webkit.org/Bugzilla/Mailer.pm
index 645e65e..7e42cb6 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Mailer.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Mailer.pm
@@ -39,6 +39,7 @@
use Bugzilla::Constants;
use Bugzilla::Error;
+use Bugzilla::Hook;
use Bugzilla::Util;
use Date::Format qw(time2str);
@@ -47,15 +48,18 @@
use Encode::MIME::Header;
use Email::Address;
use Email::MIME;
-# Loading this gives us encoding_set.
-use Email::MIME::Modifier;
use Email::Send;
sub MessageToMTA {
- my ($msg) = (@_);
+ my ($msg, $send_now) = (@_);
my $method = Bugzilla->params->{'mail_delivery_method'};
return if $method eq 'None';
+ if (Bugzilla->params->{'use_mailer_queue'} and !$send_now) {
+ Bugzilla->job_queue->insert('send_mail', { msg => $msg });
+ return;
+ }
+
my $email;
if (ref $msg) {
$email = $msg;
@@ -71,6 +75,14 @@
$email = new Email::MIME($msg);
}
+ # We add this header to uniquely identify all email that we
+ # send as coming from this Bugzilla installation.
+ #
+ # We don't use correct_urlbase, because we want this URL to
+ # *always* be the same for this Bugzilla, in every email,
+ # even if the admin changes the "ssl_redirect" parameter some day.
+ $email->header_set('X-Bugzilla-URL', Bugzilla->params->{'urlbase'});
+
# We add this header to mark the mail as "auto-generated" and
# thus to hopefully avoid auto replies.
$email->header_set('Auto-Submitted', 'auto-generated');
@@ -79,7 +91,11 @@
my ($part) = @_;
return if $part->parts > 1; # Top-level
my $content_type = $part->content_type || '';
- if ($content_type !~ /;/) {
+ $content_type =~ /charset=['"](.+)['"]/;
+ # If no charset is defined or is the default us-ascii,
+ # then we encode the email to UTF-8 if Bugzilla has utf8 enabled.
+ # XXX - This is a hack to workaround bug 723944.
+ if (!$1 || $1 eq 'us-ascii') {
my $body = $part->body;
if (Bugzilla->params->{'utf8'}) {
$part->charset_set('UTF-8');
@@ -131,8 +147,6 @@
push(@args, "-f$from_email") if $from_email;
}
}
- push(@args, "-ODeliveryMode=deferred")
- if !Bugzilla->params->{"sendmailnow"};
}
else {
# Sendmail will automatically append our hostname to the From
@@ -145,7 +159,7 @@
# Sendmail adds a Date: header also, but others may not.
if (!defined $email->header('Date')) {
- $email->header_set('Date', time2str("%a, %e %b %Y %T %z", time()));
+ $email->header_set('Date', time2str("%a, %d %b %Y %T %z", time()));
}
}
@@ -157,6 +171,9 @@
Debug => Bugzilla->params->{'smtp_debug'};
}
+ Bugzilla::Hook::process('mailer_before_send',
+ { email => $email, mailer_args => \@args });
+
if ($method eq "Test") {
my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
open TESTFILE, '>>', $filename;
@@ -195,7 +212,9 @@
$threadingmarker = "Message-ID: <bug-$bug_id-$user_id$sitespec>";
}
else {
- $threadingmarker = "In-Reply-To: <bug-$bug_id-$user_id$sitespec>" .
+ my $rand_bits = generate_random_password(10);
+ $threadingmarker = "Message-ID: <bug-$bug_id-$user_id-$rand_bits$sitespec>" .
+ "\nIn-Reply-To: <bug-$bug_id-$user_id$sitespec>" .
"\nReferences: <bug-$bug_id-$user_id$sitespec>";
}
diff --git a/Websites/bugs.webkit.org/Bugzilla/Migrate.pm b/Websites/bugs.webkit.org/Bugzilla/Migrate.pm
new file mode 100644
index 0000000..ee0dcab
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Migrate.pm
@@ -0,0 +1,1174 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is The Bugzilla Migration Tool.
+#
+# The Initial Developer of the Original Code is Lambda Research
+# Corporation. Portions created by the Initial Developer are Copyright
+# (C) 2009 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Migrate;
+use strict;
+
+use Bugzilla::Attachment;
+use Bugzilla::Bug qw(LogActivityEntry);
+use Bugzilla::Component;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Install::Requirements ();
+use Bugzilla::Install::Util qw(indicate_progress);
+use Bugzilla::Product;
+use Bugzilla::Util qw(get_text trim generate_random_password);
+use Bugzilla::User ();
+use Bugzilla::Status ();
+use Bugzilla::Version;
+
+use Data::Dumper;
+use Date::Parse;
+use DateTime;
+use Fcntl qw(SEEK_SET);
+use File::Basename;
+use List::Util qw(first);
+use Safe;
+
+use constant CUSTOM_FIELDS => {};
+use constant REQUIRED_MODULES => [];
+use constant NON_COMMENT_FIELDS => ();
+
+use constant CONFIG_VARS => (
+ {
+ name => 'translate_fields',
+ default => {},
+ desc => <<'END',
+# This maps field names in your bug-tracker to Bugzilla field names. If a field
+# has the same name in your bug-tracker and Bugzilla (case-insensitively), it
+# doesn't need a mapping here. If a field isn't listed here and doesn't have
+# an equivalent field in Bugzilla, its data will be added to the initial
+# description of each bug migrated. If the right side is an empty string, it
+# means "just put the value of this field into the initial description of the
+# bug".
+#
+# Generally, you can keep the defaults, here.
+#
+# If you want to know the internal names of various Bugzilla fields
+# (as used on the right side here), see the fielddefs table in the Bugzilla
+# database.
+#
+# If you are mapping to any custom fields in Bugzilla, you have to create
+# the custom fields using Bugzilla Administration interface before you run
+# migrate.pl. However, if they are drop down or multi-select fields, you
+# don't have to populate the list of values--migrate.pl will do that for you.
+# Some migrators create certain custom fields by default. If you see a
+# field name starting with "cf_" on the right side of this configuration
+# variable by default, then that field will be automatically created by
+# the migrator and you don't have to worry about it.
+END
+ },
+ {
+ name => 'translate_values',
+ default => {},
+ desc => <<'END',
+# This configuration variable allows you to say that a particular field
+# value in your current bug-tracker should be translated to a different
+# value when it's imported into Bugzilla.
+#
+# The value of this variable should look something like this:
+#
+# {
+# bug_status => {
+# # Translate "Handled" into "RESOLVED".
+# "Handled" => "RESOLVED",
+# "In Progress" => "IN_PROGRESS",
+# },
+#
+# priority => {
+# # Translate "Serious" into "Highest"
+# "Serious" => "Highest",
+# },
+# };
+#
+# Values are translated case-insensitively, so "foo" will match "Foo", "FOO",
+# and "foo".
+#
+# Note that the field names used are *Bugzilla* field names (from the fielddefs
+# table in the database), not the field names from your current bug-tracker.
+#
+# The special field name "user" will be used to translate any field that
+# can contain a user, including reporter, assigned_to, qa_contact, and cc.
+# You should use "user" instead of specifying reporter, assigned_to, etc.
+# manually.
+#
+# The special field "bug_status_resolution" can be used to give certain
+# statuses in your bug-tracker a resolution in Bugzilla. So, for example,
+# you could translate the "fixed" status in your Bugzilla to "RESOLVED"
+# in the "bug_status" field, and then put "fixed => 'FIXED'" in the
+# "bug_status_resolution" field to translated a "fixed" bug into
+# RESOLVED FIXED in Bugzilla.
+#
+# Values that don't get translated will be imported as-is.
+END
+ },
+ {
+ name => 'starting_bug_id',
+ default => 0,
+ desc => <<'END',
+# What bug ID do you want the first imported bug to get? If you set this to
+# 0, then the imported bug ids will just start right after the current
+# bug ids. If you use this configuration variable, you must make sure that
+# nobody else is using your Bugzilla while you run the migration, or a new
+# bug filed by a user might take this ID instead.
+END
+ },
+ {
+ name => 'timezone',
+ default => 'local',
+ desc => <<'END',
+# If migrate.pl comes across any dates without timezones, while doing the
+# migration, what timezone should we assume those dates are in?
+# The best format for this variable is something like "America/Los Angeles".
+# However, time zone abbreviations (like PST, PDT, etc.) are also acceptable,
+# but will result in a less-accurate conversion of times and dates.
+#
+# The special value "local" means "use the same timezone as the system I
+# am running this script on now".
+END
+ },
+);
+
+use constant USER_FIELDS => qw(user assigned_to qa_contact reporter cc);
+
+#########################
+# Main Migration Method #
+#########################
+
+sub do_migration {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ # On MySQL, setting serial values implicitly commits a transaction,
+ # so we want to do it up here, outside of any transaction. This also
+ # has the advantage of loading the config before anything else is done.
+ if ($self->config('starting_bug_id')) {
+ $dbh->bz_set_next_serial_value('bugs', 'bug_id',
+ $self->config('starting_bug_id'));
+ }
+ $dbh->bz_start_transaction();
+
+ # Read Other Database
+ my $users = $self->users;
+ my $products = $self->products;
+ my $bugs = $self->bugs;
+ $self->after_read();
+
+ $self->translate_all_bugs($bugs);
+
+ Bugzilla->set_user(Bugzilla::User->super_user);
+
+ # Insert into Bugzilla
+ $self->before_insert();
+ $self->insert_users($users);
+ $self->insert_products($products);
+ $self->create_custom_fields();
+ $self->create_legal_values($bugs);
+ $self->insert_bugs($bugs);
+ $self->after_insert();
+ if ($self->dry_run) {
+ $dbh->bz_rollback_transaction();
+ $self->reset_serial_values();
+ }
+ else {
+ $dbh->bz_commit_transaction();
+ }
+}
+
+################
+# Constructors #
+################
+
+sub new {
+ my ($class) = @_;
+ my $self = { };
+ bless $self, $class;
+ return $self;
+}
+
+sub load {
+ my ($class, $from) = @_;
+ my $libdir = bz_locations()->{libpath};
+ my @migration_modules = glob("$libdir/Bugzilla/Migrate/*");
+ my ($module) = grep { basename($_) =~ /^\Q$from\E\.pm$/i }
+ @migration_modules;
+ if (!$module) {
+ ThrowUserError('migrate_from_invalid', { from => $from });
+ }
+ require $module;
+ my $canonical_name = _canonical_name($module);
+ return "Bugzilla::Migrate::$canonical_name"->new;
+}
+
+#############
+# Accessors #
+#############
+
+sub name {
+ my $self = shift;
+ return _canonical_name(ref $self);
+}
+
+sub dry_run {
+ my ($self, $value) = @_;
+ if (scalar(@_) > 1) {
+ $self->{dry_run} = $value;
+ }
+ return $self->{dry_run} || 0;
+}
+
+
+sub verbose {
+ my ($self, $value) = @_;
+ if (scalar(@_) > 1) {
+ $self->{verbose} = $value;
+ }
+ return $self->{verbose} || 0;
+}
+
+sub debug {
+ my ($self, $value, $level) = @_;
+ $level ||= 1;
+ if ($self->verbose >= $level) {
+ $value = Dumper($value) if ref $value;
+ print STDERR $value, "\n";
+ }
+}
+
+sub bug_fields {
+ my $self = shift;
+ $self->{bug_fields} ||= Bugzilla->fields({ by_name => 1 });
+ return $self->{bug_fields};
+}
+
+sub users {
+ my $self = shift;
+ if (!exists $self->{users}) {
+ print get_text('migrate_reading_users'), "\n";
+ $self->{users} = $self->_read_users();
+ }
+ return $self->{users};
+}
+
+sub products {
+ my $self = shift;
+ if (!exists $self->{products}) {
+ print get_text('migrate_reading_products'), "\n";
+ $self->{products} = $self->_read_products();
+ }
+ return $self->{products};
+}
+
+sub bugs {
+ my $self = shift;
+ if (!exists $self->{bugs}) {
+ print get_text('migrate_reading_bugs'), "\n";
+ $self->{bugs} = $self->_read_bugs();
+ }
+ return $self->{bugs};
+}
+
+###########
+# Methods #
+###########
+
+sub check_requirements {
+ my $self = shift;
+ my $missing = Bugzilla::Install::Requirements::_check_missing(
+ $self->REQUIRED_MODULES, 1);
+ my %results = (
+ apache => [],
+ pass => @$missing ? 0 : 1,
+ missing => $missing,
+ any_missing => @$missing ? 1 : 0,
+ hide_all => 1,
+ # These are just for compatibility with print_module_instructions
+ one_dbd => 1,
+ optional => [],
+ );
+ Bugzilla::Install::Requirements::print_module_instructions(
+ \%results, 1);
+ exit(1) if @$missing;
+}
+
+sub reset_serial_values {
+ my $self = shift;
+ return if $self->{serial_values_reset};
+ my $dbh = Bugzilla->dbh;
+ my %reset = (
+ 'bugs' => 'bug_id',
+ 'attachments' => 'attach_id',
+ 'profiles' => 'userid',
+ 'longdescs' => 'comment_id',
+ 'products' => 'id',
+ 'components' => 'id',
+ 'versions' => 'id',
+ 'milestones' => 'id',
+ );
+ my @select_fields = grep { $_->is_select } (values %{ $self->bug_fields });
+ foreach my $field (@select_fields) {
+ next if $field->is_abnormal;
+ $reset{$field->name} = 'id';
+ }
+
+ while (my ($table, $column) = each %reset) {
+ $dbh->bz_set_next_serial_value($table, $column);
+ }
+
+ $self->{serial_values_reset} = 1;
+}
+
+###################
+# Bug Translation #
+###################
+
+sub translate_all_bugs {
+ my ($self, $bugs) = @_;
+ print get_text('migrate_translating_bugs'), "\n";
+ # We modify the array in place so that $self->bugs will return the
+ # modified bugs, in case $self->before_insert wants them.
+ my $num_bugs = scalar(@$bugs);
+ for (my $i = 0; $i < $num_bugs; $i++) {
+ $bugs->[$i] = $self->translate_bug($bugs->[$i]);
+ }
+}
+
+sub translate_bug {
+ my ($self, $fields) = @_;
+ my (%bug, %other_fields);
+ my $original_status;
+ foreach my $field (keys %$fields) {
+ my $value = delete $fields->{$field};
+ my $bz_field = $self->translate_field($field);
+ if ($bz_field) {
+ $bug{$bz_field} = $self->translate_value($bz_field, $value);
+ if ($bz_field eq 'bug_status') {
+ $original_status = $value;
+ }
+ }
+ else {
+ $other_fields{$field} = $value;
+ }
+ }
+
+ if (defined $original_status and !defined $bug{resolution}
+ and $self->map_value('bug_status_resolution', $original_status))
+ {
+ $bug{resolution} = $self->map_value('bug_status_resolution',
+ $original_status);
+ }
+
+ $bug{comment} = $self->_generate_description(\%bug, \%other_fields);
+
+ return wantarray ? (\%bug, \%other_fields) : \%bug;
+}
+
+sub _generate_description {
+ my ($self, $bug, $fields) = @_;
+
+ my $description = "";
+ foreach my $field (sort keys %$fields) {
+ next if grep($_ eq $field, $self->NON_COMMENT_FIELDS);
+ my $value = delete $fields->{$field};
+ next if $value eq '';
+ $description .= "$field: $value\n";
+ }
+ $description .= "\n" if $description;
+
+ return $description . $bug->{comment};
+}
+
+sub translate_field {
+ my ($self, $field) = @_;
+ my $mapped = $self->config('translate_fields')->{$field};
+ return $mapped if defined $mapped;
+ ($mapped) = grep { lc($_) eq lc($field) } (keys %{ $self->bug_fields });
+ return $mapped;
+}
+
+sub parse_date {
+ my ($self, $date) = @_;
+ my @time = strptime($date);
+ # Handle times with timezones that strptime doesn't know about.
+ if (!scalar @time) {
+ $date =~ s/\s+\S+$//;
+ @time = strptime($date);
+ }
+ my $tz;
+ if ($time[6]) {
+ $tz = Bugzilla->local_timezone->offset_as_string($time[6]);
+ }
+ else {
+ $tz = $self->config('timezone');
+ $tz =~ s/\s/_/g;
+ if ($tz eq 'local') {
+ $tz = Bugzilla->local_timezone;
+ }
+ }
+ my $dt = DateTime->new({
+ year => $time[5] + 1900,
+ month => $time[4] + 1,
+ day => $time[3],
+ hour => $time[2],
+ minute => $time[1],
+ second => int($time[0]),
+ time_zone => $tz,
+ });
+ $dt->set_time_zone(Bugzilla->local_timezone);
+ return $dt->iso8601;
+}
+
+sub translate_value {
+ my ($self, $field, $value) = @_;
+
+ if (!defined $value) {
+ warn("Got undefined value for $field\n");
+ $value = '';
+ }
+
+ if (ref($value) eq 'ARRAY') {
+ return [ map($self->translate_value($field, $_), @$value) ];
+ }
+
+
+ if (defined $self->map_value($field, $value)) {
+ return $self->map_value($field, $value);
+ }
+
+ if (grep($_ eq $field, USER_FIELDS)) {
+ if (defined $self->map_value('user', $value)) {
+ return $self->map_value('user', $value);
+ }
+ }
+
+ my $field_obj = $self->bug_fields->{$field};
+ if ($field eq 'creation_ts' or $field eq 'delta_ts'
+ or ($field_obj and $field_obj->type == FIELD_TYPE_DATETIME))
+ {
+ $value = trim($value);
+ return undef if !$value;
+ return $self->parse_date($value);
+ }
+
+ return $value;
+}
+
+
+sub map_value {
+ my ($self, $field, $value) = @_;
+ return $self->_value_map->{$field}->{lc($value)};
+}
+
+sub _value_map {
+ my $self = shift;
+ if (!defined $self->{_value_map}) {
+ # Lowercase all values to make them case-insensitive.
+ my %map;
+ my $translation = $self->config('translate_values');
+ foreach my $field (keys %$translation) {
+ my $value_mapping = $translation->{$field};
+ foreach my $value (keys %$value_mapping) {
+ $map{$field}->{lc($value)} = $value_mapping->{$value};
+ }
+ }
+ $self->{_value_map} = \%map;
+ }
+ return $self->{_value_map};
+}
+
+#################
+# Configuration #
+#################
+
+sub config {
+ my ($self, $var) = @_;
+ if (!exists $self->{config}) {
+ $self->{config} = $self->read_config;
+ }
+ return $self->{config}->{$var};
+}
+
+sub config_file_name {
+ my $self = shift;
+ my $name = $self->name;
+ my $dir = bz_locations()->{datadir};
+ return "$dir/migrate-$name.cfg"
+}
+
+sub read_config {
+ my ($self) = @_;
+ my $file = $self->config_file_name;
+ if (!-e $file) {
+ $self->write_config();
+ ThrowUserError('migrate_config_created', { file => $file });
+ }
+ open(my $fh, "<", $file) || die "$file: $!";
+ my $safe = new Safe;
+ $safe->rdo($file);
+ my @read_symbols = map($_->{name}, $self->CONFIG_VARS);
+ my %config;
+ foreach my $var (@read_symbols) {
+ my $glob = $safe->varglob($var);
+ $config{$var} = $$glob;
+ }
+ return \%config;
+}
+
+sub write_config {
+ my ($self) = @_;
+ my $file = $self->config_file_name;
+ open(my $fh, ">", $file) || die "$file: $!";
+ # Fixed indentation
+ local $Data::Dumper::Indent = 1;
+ local $Data::Dumper::Quotekeys = 0;
+ local $Data::Dumper::Sortkeys = 1;
+ foreach my $var ($self->CONFIG_VARS) {
+ print $fh "\n", $var->{desc},
+ Data::Dumper->Dump([$var->{default}], [$var->{name}]);
+ }
+ close($fh);
+}
+
+####################################
+# Default Implementations of Hooks #
+####################################
+
+sub after_insert {}
+sub before_insert {}
+sub after_read {}
+
+#############
+# Inserters #
+#############
+
+sub insert_users {
+ my ($self, $users) = @_;
+ foreach my $user (@$users) {
+ next if new Bugzilla::User({ name => $user->{login_name} });
+ my $generated_password;
+ if (!defined $user->{cryptpassword}) {
+ $generated_password = lc(generate_random_password());
+ $user->{cryptpassword} = $generated_password;
+ }
+ my $created = Bugzilla::User->create($user);
+ print get_text('migrate_user_created',
+ { created => $created,
+ password => $generated_password }), "\n";
+ }
+}
+
+# XXX This should also insert Classifications.
+sub insert_products {
+ my ($self, $products) = @_;
+ foreach my $product (@$products) {
+ my $components = delete $product->{components};
+
+ my $created_prod = new Bugzilla::Product({ name => $product->{name} });
+ if (!$created_prod) {
+ $created_prod = Bugzilla::Product->create($product);
+ print get_text('migrate_product_created',
+ { created => $created_prod }), "\n";
+ }
+
+ foreach my $component (@$components) {
+ next if new Bugzilla::Component({ product => $created_prod,
+ name => $component->{name} });
+ my $created_comp = Bugzilla::Component->create(
+ { %$component, product => $created_prod });
+ print ' ', get_text('migrate_component_created',
+ { comp => $created_comp,
+ product => $created_prod }), "\n";
+ }
+ }
+}
+
+sub create_custom_fields {
+ my $self = shift;
+ foreach my $field (keys %{ $self->CUSTOM_FIELDS }) {
+ next if new Bugzilla::Field({ name => $field });
+ my %values = %{ $self->CUSTOM_FIELDS->{$field} };
+ # We set these all here for the dry-run case.
+ my $created = { %values, name => $field, custom => 1 };
+ if (!$self->dry_run) {
+ $created = Bugzilla::Field->create($created);
+ }
+ print get_text('migrate_field_created', { field => $created }), "\n";
+ }
+ delete $self->{bug_fields};
+}
+
+sub create_legal_values {
+ my ($self, $bugs) = @_;
+ my @select_fields = grep($_->is_select, values %{ $self->bug_fields });
+
+ # Get all the values in use on all the bugs we're importing.
+ my (%values, %product_values);
+ foreach my $bug (@$bugs) {
+ foreach my $field (@select_fields) {
+ my $name = $field->name;
+ next if !defined $bug->{$name};
+ $values{$name}->{$bug->{$name}} = 1;
+ }
+ foreach my $field (qw(version target_milestone)) {
+ # Fix per-product bug values here, because it's easier than
+ # doing it during _insert_bugs.
+ if (!defined $bug->{$field} or trim($bug->{$field}) eq '') {
+ my $accessor = $field;
+ $accessor =~ s/^target_//; $accessor .= "s";
+ my $product = Bugzilla::Product->check($bug->{product});
+ $bug->{$field} = $product->$accessor->[0]->name;
+ next;
+ }
+ $product_values{$bug->{product}}->{$field}->{$bug->{$field}} = 1;
+ }
+ }
+
+ foreach my $field (@select_fields) {
+ next if $field->is_abnormal;
+ my $name = $field->name;
+ foreach my $value (keys %{ $values{$name} }) {
+ next if Bugzilla::Field::Choice->type($field)->new({ name => $value });
+ Bugzilla::Field::Choice->type($field)->create({ value => $value });
+ print get_text('migrate_value_created',
+ { field => $field, value => $value }), "\n";
+ }
+ }
+
+ foreach my $product (keys %product_values) {
+ my $prod_obj = Bugzilla::Product->check($product);
+ foreach my $version (keys %{ $product_values{$product}->{version} }) {
+ next if new Bugzilla::Version({ product => $prod_obj,
+ name => $version });
+ my $created = Bugzilla::Version->create({ product => $prod_obj,
+ value => $version });
+ my $field = $self->bug_fields->{version};
+ print get_text('migrate_value_created', { product => $prod_obj,
+ field => $field,
+ value => $created->name }), "\n";
+ }
+ foreach my $milestone (keys %{ $product_values{$product}->{target_milestone} }) {
+ next if new Bugzilla::Milestone({ product => $prod_obj,
+ name => $milestone });
+ my $created = Bugzilla::Milestone->create(
+ { product => $prod_obj, value => $milestone });
+ my $field = $self->bug_fields->{target_milestone};
+ print get_text('migrate_value_created', { product => $prod_obj,
+ field => $field,
+ value => $created->name }), "\n";
+
+ }
+ }
+
+}
+
+sub insert_bugs {
+ my ($self, $bugs) = @_;
+ my $dbh = Bugzilla->dbh;
+ print get_text('migrate_creating_bugs'), "\n";
+
+ my $init_statuses = Bugzilla::Status->can_change_to();
+ my %allowed_statuses = map { lc($_->name) => 1 } @$init_statuses;
+ # Bypass the question of whether or not we can file UNCONFIRMED
+ # in any product by simply picking a non-UNCONFIRMED status as our
+ # default for bugs that don't have a status specified.
+ my $default_status = first { $_->name ne 'UNCONFIRMED' } @$init_statuses;
+ # Use the first resolution that's not blank.
+ my $default_resolution =
+ first { $_->name ne '' }
+ @{ $self->bug_fields->{resolution}->legal_values };
+
+ # Set the values of any required drop-down fields that aren't set.
+ my @standard_drop_downs = grep { !$_->custom and $_->is_select }
+ (values %{ $self->bug_fields });
+ # Make bug_status get set before resolution.
+ @standard_drop_downs = sort { $a->name cmp $b->name } @standard_drop_downs;
+ # Cache all statuses for setting the resolution.
+ my %statuses = map { lc($_->name) => $_ } Bugzilla::Status->get_all;
+
+ my $total = scalar @$bugs;
+ my $count = 1;
+ foreach my $bug (@$bugs) {
+ my $comments = delete $bug->{comments};
+ my $history = delete $bug->{history};
+ my $attachments = delete $bug->{attachments};
+
+ $self->debug($bug, 3);
+
+ foreach my $field (@standard_drop_downs) {
+ next if $field->is_abnormal;
+ my $field_name = $field->name;
+ if (!defined $bug->{$field_name}) {
+ # If there's a default value for this, then just let create()
+ # pick it.
+ next if grep($_->is_default, @{ $field->legal_values });
+ # Otherwise, pick the first valid value if this is a required
+ # field.
+ if ($field_name eq 'bug_status') {
+ $bug->{bug_status} = $default_status;
+ }
+ elsif ($field_name eq 'resolution') {
+ my $status = $statuses{lc($bug->{bug_status})};
+ if (!$status->is_open) {
+ $bug->{resolution} = $default_resolution;
+ }
+ }
+ else {
+ $bug->{$field_name} = $field->legal_values->[0]->name;
+ }
+ }
+ }
+
+ my $product = Bugzilla::Product->check($bug->{product});
+
+ # If this isn't a legal starting status, or if the bug has a
+ # resolution, then those will have to be set after creating the bug.
+ # We make them into objects so that we can normalize their names.
+ my ($set_status, $set_resolution);
+ if (defined $bug->{resolution}) {
+ $set_resolution = Bugzilla::Field::Choice->type('resolution')
+ ->new({ name => delete $bug->{resolution} });
+ }
+ if (!$allowed_statuses{lc($bug->{bug_status})}) {
+ $set_status = new Bugzilla::Status({ name => $bug->{bug_status} });
+ # Set the starting status to some status that Bugzilla will
+ # accept. We're going to overwrite it immediately afterward.
+ $bug->{bug_status} = $default_status;
+ }
+
+ # If we're in dry-run mode, our custom fields haven't been created
+ # yet, so we shouldn't try to set them on creation.
+ if ($self->dry_run) {
+ foreach my $field (keys %{ $self->CUSTOM_FIELDS }) {
+ delete $bug->{$field};
+ }
+ }
+
+ # File the bug as the reporter.
+ my $super_user = Bugzilla->user;
+ my $reporter = Bugzilla::User->check($bug->{reporter});
+ # Allow the user to file a bug in any product, no matter his current
+ # permissions.
+ $reporter->{groups} = $super_user->groups;
+ Bugzilla->set_user($reporter);
+ my $created = Bugzilla::Bug->create($bug);
+ $self->debug('Created bug ' . $created->id);
+ Bugzilla->set_user($super_user);
+
+ if (defined $bug->{creation_ts}) {
+ $dbh->do('UPDATE bugs SET creation_ts = ?, delta_ts = ?
+ WHERE bug_id = ?', undef, $bug->{creation_ts},
+ $bug->{creation_ts}, $created->id);
+ }
+ if (defined $bug->{delta_ts}) {
+ $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+ undef, $bug->{delta_ts}, $created->id);
+ }
+ # We don't need to send email for imported bugs.
+ $dbh->do('UPDATE bugs SET lastdiffed = delta_ts WHERE bug_id = ?',
+ undef, $created->id);
+
+ # We don't use set_ and update() because that would create
+ # a bugs_activity entry that we don't want.
+ if ($set_status) {
+ $dbh->do('UPDATE bugs SET bug_status = ? WHERE bug_id = ?',
+ undef, $set_status->name, $created->id);
+ }
+ if ($set_resolution) {
+ $dbh->do('UPDATE bugs SET resolution = ? WHERE bug_id = ?',
+ undef, $set_resolution->name, $created->id);
+ }
+
+ $self->_insert_comments($created, $comments);
+ $self->_insert_history($created, $history);
+ $self->_insert_attachments($created, $attachments);
+
+ # bugs_fulltext isn't transactional, so if we're in a dry-run we
+ # need to delete anything that we put in there.
+ if ($self->dry_run) {
+ $dbh->do('DELETE FROM bugs_fulltext WHERE bug_id = ?',
+ undef, $created->id);
+ }
+
+ if (!$self->verbose) {
+ indicate_progress({ current => $count++, every => 5, total => $total });
+ }
+ }
+}
+
+sub _insert_comments {
+ my ($self, $bug, $comments) = @_;
+ return if !$comments;
+ $self->debug(' Inserting comments:', 2);
+ foreach my $comment (@$comments) {
+ $self->debug($comment, 3);
+ my %copy = %$comment;
+ # XXX In the future, if we have a Bugzilla::Comment->create, this
+ # should use it.
+ my $who = Bugzilla::User->check(delete $copy{who});
+ $copy{who} = $who->id;
+ $copy{bug_id} = $bug->id;
+ $self->_do_table_insert('longdescs', \%copy);
+ $self->debug(" Inserted comment from " . $who->login, 2);
+ }
+ $bug->_sync_fulltext();
+}
+
+sub _insert_history {
+ my ($self, $bug, $history) = @_;
+ return if !$history;
+ $self->debug(' Inserting history:', 2);
+ foreach my $item (@$history) {
+ $self->debug($item, 3);
+ my $who = Bugzilla::User->check($item->{who});
+ LogActivityEntry($bug->id, $item->{field}, $item->{removed},
+ $item->{added}, $who->id, $item->{bug_when});
+ $self->debug(" $item->{field} change from " . $who->login, 2);
+ }
+}
+
+sub _insert_attachments {
+ my ($self, $bug, $attachments) = @_;
+ return if !$attachments;
+ $self->debug(' Inserting attachments:', 2);
+ foreach my $attachment (@$attachments) {
+ $self->debug($attachment, 3);
+ # Make sure that our pointer is at the beginning of the file,
+ # because usually it will be at the end, having just been fully
+ # written to.
+ if (ref $attachment->{data}) {
+ $attachment->{data}->seek(0, SEEK_SET);
+ }
+
+ my $submitter = Bugzilla::User->check(delete $attachment->{submitter});
+ my $super_user = Bugzilla->user;
+ # Make sure the submitter can attach this attachment no matter what.
+ $submitter->{groups} = $super_user->groups;
+ Bugzilla->set_user($submitter);
+ my $created =
+ Bugzilla::Attachment->create({ %$attachment, bug => $bug });
+ $self->debug(' Attachment ' . $created->description . ' from '
+ . $submitter->login, 2);
+ Bugzilla->set_user($super_user);
+ }
+}
+
+sub _do_table_insert {
+ my ($self, $table, $hash) = @_;
+ my @fields = keys %$hash;
+ my @questions = ('?') x @fields;
+ my @values = map { $hash->{$_} } @fields;
+ my $field_sql = join(',', @fields);
+ my $question_sql = join(',', @questions);
+ Bugzilla->dbh->do("INSERT INTO $table ($field_sql) VALUES ($question_sql)",
+ undef, @values);
+}
+
+######################
+# Helper Subroutines #
+######################
+
+sub _canonical_name {
+ my ($module) = @_;
+ $module =~ s{::}{/}g;
+ $module = basename($module);
+ $module =~ s/\.pm$//g;
+ return $module;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Migrate - Functions to migrate from other databases
+
+=head1 DESCRIPTION
+
+This module acts as a base class for the various modules that migrate
+from other bug-trackers.
+
+The documentation for this module exists mostly to assist people in
+creating new migrators for other bug-trackers than the ones currently
+supported.
+
+=head1 HOW MIGRATION WORKS
+
+Before writing anything to the Bugzilla database, the migrator will read
+everything from the other bug-tracker's database. Here's the exact order
+of what happens:
+
+=over
+
+=item 1
+
+Users are read from the other bug-tracker.
+
+=item 2
+
+Products are read from the other bug-tracker.
+
+=item 3
+
+Bugs are read from the other bug-tracker.
+
+=item 4
+
+The L</after_read> method is called.
+
+=item 5
+
+All bugs are translated from the other bug-tracker's fields/values
+into Bugzilla's fields values using L</translate_bug>.
+
+=item 6
+
+Users are inserted into Bugzilla.
+
+=item 7
+
+Products are inserted into Bugzilla.
+
+=item 8
+
+Some migrators need to create custom fields before migrating, and
+so that happens here.
+
+=item 9
+
+Any legal values that need to be created for any drop-down or
+multi-select fields are created. This is done by reading all the
+values on every bug that was read in and creating any values that
+don't already exist in Bugzilla for every drop-down or multi-select
+field on each bug. This includes creating any product versions and
+milestones that need to be created.
+
+=item 10
+
+Bugs are inserted into Bugzilla.
+
+=item 11
+
+The L</after_insert> method is called.
+
+=back
+
+Everything happens in one big transaction, so in general, if there are
+any errors during the process, nothing will be changed.
+
+The migrator never creates anything that already exists. So users, products,
+components, etc. that already exist will just be re-used by this script,
+not re-created.
+
+=head1 CONSTRUCTOR
+
+=head2 load
+
+Called like C<< Bugzilla::Migrate->load('Module') >>. Returns a new
+C<Bugzilla::Migrate> object that can be used to migrate from the
+requested bug-tracker.
+
+=head1 METHODS YOUR SUBCLASS CAN USE
+
+=head2 config
+
+Takes a single parameter, a string, and returns the value of the
+configuration variable with that name (always a scalar). The first time
+you call C<config>, if the configuration file hasn't been read, it will
+be read in.
+
+=head2 debug
+
+If the user hasn't specified C<--verbose> on the command line, this
+does nothing.
+
+Takes two arguments:
+
+The first argument is a string or reference to print to C<STDERR>.
+If it's a reference, L<Data::Dumper> will be used to print the
+data structure.
+
+The second argument is a number--the string will only be printed if the
+user specified C<--verbose> at least that many times on the command line.
+
+=head2 parse_date
+
+(Note: Usually you don't need to call this, because L</translate_bug>
+handles date translations for you, for bug data.)
+
+Parses a date string and returns a formatted date string that can be inserted
+into the database. If the input date is missing a timezone, the "timezone"
+configuration parameter will be used as the timezone of the date.
+
+=head2 translate_bug
+
+(Note: Normally you don't have to call this yourself, as
+C<Bugzilla::Migrate> does it for you.)
+
+Uses the C<$translate_fields> and C<$translate_values> configuration variables
+to convert a hashref of "other bug-tracker" fields into Bugzilla fields.
+It takes one argument, the hashref to convert. Any unrecognized fields will
+have their value prepended to the C<comment> element in the returned
+hashref, unless they are listed in L</NON_COMMENT_FIELDS>.
+
+In scalar context, returns the translated bug. In array context,
+returns both the translated bug and a second hashref containing the values
+of any untranslated fields that were listed in C<NON_COMMENT_FIELDS>.
+
+B<Note:> To save memory, the hashref that you pass in will be destroyed
+(all keys will be deleted).
+
+=head2 translate_value
+
+(Note: Generally you only need to use this during L</_read_products>
+and L</_read_users> if necessary, because the data returned from
+L</_read_bugs> will be put through L</translate_bug>.)
+
+Uses the C<$translate_values> configuration variable to convert
+field values from your bug-tracker to Bugzilla. Takes two arguments,
+the first being a field name and the second being a value. If the value
+is an arrayref, C<translate_value> will be called recursively on all
+the array elements.
+
+Also, any date field will be converted into ISO 8601 format, for
+inserting into the database.
+
+=head2 translate_field
+
+(Note: Normally you don't need to use this, because L</translate_bug>
+handles it for you.)
+
+Translates a field name in your bug-tracker to a field name in Bugzilla,
+using the rules described in the description of the C<$translate_fields>
+configuration variable.
+
+Takes a single argument--the name of a field to translate.
+
+Returns C<undef> if the field could not be translated.
+
+=head1 METHODS YOU MUST IMPLEMENT
+
+These are methods that subclasses must implement:
+
+=head2 _read_bugs
+
+Should return an arrayref of hashes. The hashes will be passed to
+L<Bugzilla::Bug/create> to create bugs in Bugzilla. In addition to
+the normal C<create> fields, the hashes can contain three additional
+items:
+
+=over
+
+=item comments
+
+An arrayref of hashes, representing comments to be added to the
+database. The keys should be the names of columns in the longdescs
+table that you want to set for each comment. C<who> must be a
+username instead of a user id, though.
+
+You don't need to specify a value for the C<bug_id> column.
+
+=item history
+
+An arrayref of hashes, representing the history of changes made
+to this bug. The keys should be the names of columns in the
+bugs_activity table to set for each change. C<who> must be a username
+instead of a user id, though, and C<field> (containing the name of some field)
+is taken instead of C<fieldid>.
+
+You don't need to specify a value for the C<bug_id> column.
+
+=item attachments
+
+An arrayref of hashes, representing values to pass to
+L<Bugzilla::Attachment/create>. (Remember that the C<data> argument
+must be a file handle--we recommend using L<IO::File/new_tmpfile> to create
+anonymous temporary files for this purpose.) You should specify a
+C<submitter> argument containing the username of the attachment's submitter.
+
+You don't need to specify a value for the the C<bug> argument.
+
+=back
+
+=head2 _read_products
+
+Should return an arrayref of hashes to pass to L<Bugzilla::Product/create>.
+In addition to the normal C<create> fields, this also accepts an additional
+argument, C<components>, which is an arrayref of hashes to pass to
+L<Bugzilla::Component/create> (though you do not need to specify the
+C<product> argument for L<Bugzilla::Component/create>).
+
+=head2 _read_users
+
+Should return an arrayref of hashes to be passed to
+L<Bugzilla::User/create>.
+
+=head1 METHODS YOU MIGHT WANT TO IMPLEMENT
+
+These are methods that you may want to override in your migrator.
+All of these methods are called on an instantiated L<Bugzilla::Migrate>
+object of your subclass by L<Bugzilla::Migrate> itself.
+
+=head2 REQUIRED_MODULES
+
+Returns an arrayref of Perl modules that must be installed in order
+for your migrator to run, in the same format as
+L<Bugzilla::Install::Requirements/REQUIRED_MODULES>.
+
+=head2 CUSTOM_FIELDS
+
+Returns a hashref, where the keys are the names of custom fields
+to create in the database before inserting bugs. The values of the
+hashref are the arguments (other than "name") that should be passed
+to Bugzilla::Field->create() when creating the field. (C<< custom => 1 >>
+will be specified automatically for you, so you don't need to specify it.)
+
+=head2 CONFIG_VARS
+
+This should return an array (not an arrayref) in the same format as
+L<Bugzilla::Install::Localconfig/LOCALCONFIG_VARS>, describing
+configuration variables for migrating from your bug-tracker. You should
+always include the default C<CONFIG_VARS> (by calling
+$self->SUPER::CONFIG_VARS) as part of your return value, if you
+override this method.
+
+=head2 NON_COMMENT_FIELDS
+
+An array (not an arrayref). If there are fields that are not translated
+and yet shouldn't be added to the initial description of the bug when
+translating bugs, then they should be listed here. See L</translate_bug> for
+more detail.
+
+=head2 after_read
+
+This is run after all data is read from the other bug-tracker, but
+before the bug fields/values have been translated, and before any data
+is inserted into Bugzilla. The default implementation does nothing.
+
+=head2 before_insert
+
+This is called after all bugs are translated from their "other bug-tracker"
+values to Bugzilla values, but before any data is inserted into the database
+or any custom fields are created. The default implementation does nothing.
+
+=head2 after_insert
+
+This is run after all data is inserted into Bugzilla. The default
+implementation does nothing.
diff --git a/Websites/bugs.webkit.org/Bugzilla/Migrate/Gnats.pm b/Websites/bugs.webkit.org/Bugzilla/Migrate/Gnats.pm
new file mode 100644
index 0000000..db628b7
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Migrate/Gnats.pm
@@ -0,0 +1,712 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is The Bugzilla Migration Tool.
+#
+# The Initial Developer of the Original Code is Lambda Research
+# Corporation. Portions created by the Initial Developer are Copyright
+# (C) 2009 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Migrate::Gnats;
+use strict;
+use base qw(Bugzilla::Migrate);
+
+use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(indicate_progress);
+use Bugzilla::Util qw(format_time trim generate_random_password);
+
+use Email::Address;
+use Email::MIME;
+use File::Basename;
+use IO::File;
+use List::MoreUtils qw(firstidx);
+use List::Util qw(first);
+
+use constant REQUIRED_MODULES => [
+ {
+ package => 'Email-Simple-FromHandle',
+ module => 'Email::Simple::FromHandle',
+ # This version added seekable handles.
+ version => 0.050,
+ },
+];
+
+use constant FIELD_MAP => {
+ 'Number' => 'bug_id',
+ 'Category' => 'product',
+ 'Synopsis' => 'short_desc',
+ 'Responsible' => 'assigned_to',
+ 'State' => 'bug_status',
+ 'Class' => 'cf_type',
+ 'Classification' => '',
+ 'Originator' => 'reporter',
+ 'Arrival-Date' => 'creation_ts',
+ 'Last-Modified' => 'delta_ts',
+ 'Release' => 'version',
+ 'Severity' => 'bug_severity',
+ 'Description' => 'comment',
+};
+
+use constant VALUE_MAP => {
+ bug_severity => {
+ 'serious' => 'major',
+ 'cosmetic' => 'trivial',
+ 'new-feature' => 'enhancement',
+ 'non-critical' => 'normal',
+ },
+ bug_status => {
+ 'open' => 'CONFIRMED',
+ 'analyzed' => 'IN_PROGRESS',
+ 'suspended' => 'RESOLVED',
+ 'feedback' => 'RESOLVED',
+ 'released' => 'VERIFIED',
+ },
+ bug_status_resolution => {
+ 'feedback' => 'FIXED',
+ 'released' => 'FIXED',
+ 'closed' => 'FIXED',
+ 'suspended' => 'LATER',
+ },
+ priority => {
+ 'medium' => 'Normal',
+ },
+};
+
+use constant GNATS_CONFIG_VARS => (
+ {
+ name => 'gnats_path',
+ default => '/var/lib/gnats',
+ desc => <<END,
+# The path to the directory that contains the GNATS database.
+END
+ },
+ {
+ name => 'default_email_domain',
+ default => 'example.com',
+ desc => <<'END',
+# Some GNATS users do not have full email addresses, but Bugzilla requires
+# every user to have an email address. What domain should be appended to
+# usernames that don't have emails, to make them into email addresses?
+# (For example, if you leave this at the default, "unknown" would become
+# "unknown@example.com".)
+END
+ },
+ {
+ name => 'component_name',
+ default => 'General',
+ desc => <<'END',
+# GNATS has only "Category" to classify bugs. However, Bugzilla has a
+# multi-level system of Products that contain Components. When importing
+# GNATS categories, they become a Product with one Component. What should
+# the name of that Component be?
+END
+ },
+ {
+ name => 'version_regex',
+ default => '',
+ desc => <<'END',
+# In GNATS, the "version" field can contain almost anything. However, in
+# Bugzilla, it's a drop-down, so you don't want too many choices in there.
+# If you specify a regular expression here, versions will be tested against
+# this regular expression, and if they match, the first match (the first set
+# of parentheses in the regular expression, also called "$1") will be used
+# as the version value for the bug instead of the full version value specified
+# in GNATS.
+END
+ },
+ {
+ name => 'default_originator',
+ default => 'gnats-admin',
+ desc => <<'END',
+# Sometimes, a PR has no valid Originator, so we fall back to the From
+# header of the email. If the From header also isn't a valid username
+# (is just a name with spaces in it--we can't convert that to an email
+# address) then this username (which can either be a GNATS username or an
+# email address) will be considered to be the Originator of the PR.
+END
+ }
+);
+
+sub CONFIG_VARS {
+ my $self = shift;
+ my @vars = (GNATS_CONFIG_VARS, $self->SUPER::CONFIG_VARS);
+ my $field_map = first { $_->{name} eq 'translate_fields' } @vars;
+ $field_map->{default} = FIELD_MAP;
+ my $value_map = first { $_->{name} eq 'translate_values' } @vars;
+ $value_map->{default} = VALUE_MAP;
+ return @vars;
+}
+
+# Directories that aren't projects, or that we shouldn't be parsing
+use constant SKIP_DIRECTORIES => qw(
+ gnats-adm
+ gnats-queue
+ pending
+);
+
+use constant NON_COMMENT_FIELDS => qw(
+ Audit-Trail
+ Closed-Date
+ Confidential
+ Unformatted
+ attachments
+);
+
+# Certain fields can contain things that look like fields in them,
+# because they might contain quoted emails. To avoid mis-parsing,
+# we list out here the exact order of fields at the end of a PR
+# and wait for the next field to consider that we actually have
+# a field to parse.
+use constant END_FIELD_ORDER => qw(
+ Description
+ How-To-Repeat
+ Fix
+ Release-Note
+ Audit-Trail
+ Unformatted
+);
+
+use constant CUSTOM_FIELDS => {
+ cf_type => {
+ type => FIELD_TYPE_SINGLE_SELECT,
+ description => 'Type',
+ },
+};
+
+use constant FIELD_REGEX => qr/^>(\S+):\s*(.*)$/;
+
+# Used for bugs that have no Synopsis.
+use constant NO_SUBJECT => "(no subject)";
+
+# This is the divider that GNATS uses between attachments in its database
+# files. It's missign two hyphens at the beginning because MIME Emails use
+# -- to start boundaries.
+use constant GNATS_BOUNDARY => '----gnatsweb-attachment----';
+
+use constant LONG_VERSION_LENGTH => 32;
+
+#########
+# Hooks #
+#########
+
+sub before_insert {
+ my $self = shift;
+
+ # gnats_id isn't a valid User::create field, and we don't need it
+ # anymore now.
+ delete $_->{gnats_id} foreach @{ $self->users };
+
+ # Grab a version out of a bug for each product, so that there is a
+ # valid "version" argument for Bugzilla::Product->create.
+ foreach my $product (@{ $self->products }) {
+ my $bug = first { $_->{product} eq $product->{name} and $_->{version} }
+ @{ $self->bugs };
+ if (defined $bug) {
+ $product->{version} = $bug->{version};
+ }
+ else {
+ $product->{version} = 'unspecified';
+ }
+ }
+}
+
+#########
+# Users #
+#########
+
+sub _read_users {
+ my $self = shift;
+ my $path = $self->config('gnats_path');
+ my $file = "$path/gnats-adm/responsible";
+ $self->debug("Reading users from $file");
+ my $default_domain = $self->config('default_email_domain');
+ open(my $users_fh, '<', $file) || die "$file: $!";
+ my @users;
+ foreach my $line (<$users_fh>) {
+ $line = trim($line);
+ next if $line =~ /^#/;
+ my ($id, $name, $email) = split(':', $line, 3);
+ $email ||= "$id\@$default_domain";
+ # We can't call our own translate_value, because that depends on
+ # the existence of user_map, which doesn't exist until after
+ # this method. However, we still want to translate any users found.
+ $email = $self->SUPER::translate_value('user', $email);
+ push(@users, { realname => $name, login_name => $email,
+ gnats_id => $id });
+ }
+ close($users_fh);
+ return \@users;
+}
+
+sub user_map {
+ my $self = shift;
+ $self->{user_map} ||= { map { $_->{gnats_id} => $_->{login_name} }
+ @{ $self->users } };
+ return $self->{user_map};
+}
+
+sub add_user {
+ my ($self, $id, $email) = @_;
+ return if defined $self->user_map->{$id};
+ $self->user_map->{$id} = $email;
+ push(@{ $self->users }, { login_name => $email, gnats_id => $id });
+}
+
+sub user_to_email {
+ my ($self, $value) = @_;
+ if (defined $self->user_map->{$value}) {
+ $value = $self->user_map->{$value};
+ }
+ elsif ($value !~ /@/) {
+ my $domain = $self->config('default_email_domain');
+ $value = "$value\@$domain";
+ }
+ return $value;
+}
+
+############
+# Products #
+############
+
+sub _read_products {
+ my $self = shift;
+ my $path = $self->config('gnats_path');
+ my $file = "$path/gnats-adm/categories";
+ $self->debug("Reading categories from $file");
+
+ open(my $categories_fh, '<', $file) || die "$file: $!";
+ my @products;
+ foreach my $line (<$categories_fh>) {
+ $line = trim($line);
+ next if $line =~ /^#/;
+ my ($name, $description, $assigned_to, $cc) = split(':', $line, 4);
+ my %product = ( name => $name, description => $description );
+
+ my @initial_cc = split(',', $cc);
+ @initial_cc = @{ $self->translate_value('user', \@initial_cc) };
+ $assigned_to = $self->translate_value('user', $assigned_to);
+ my %component = ( name => $self->config('component_name'),
+ description => $description,
+ initialowner => $assigned_to,
+ initial_cc => \@initial_cc );
+ $product{components} = [\%component];
+ push(@products, \%product);
+ }
+ close($categories_fh);
+ return \@products;
+}
+
+################
+# Reading Bugs #
+################
+
+sub _read_bugs {
+ my $self = shift;
+ my $path = $self->config('gnats_path');
+ my @directories = glob("$path/*");
+ my @bugs;
+ foreach my $directory (@directories) {
+ next if !-d $directory;
+ my $name = basename($directory);
+ next if grep($_ eq $name, SKIP_DIRECTORIES);
+ push(@bugs, @{ $self->_parse_project($directory) });
+ }
+ @bugs = sort { $a->{Number} <=> $b->{Number} } @bugs;
+ return \@bugs;
+}
+
+sub _parse_project {
+ my ($self, $directory) = @_;
+ my @files = glob("$directory/*");
+
+ $self->debug("Reading Project: $directory");
+ # Sometimes other files get into gnats directories.
+ @files = grep { basename($_) =~ /^\d+$/ } @files;
+ my @bugs;
+ my $count = 1;
+ my $total = scalar @files;
+ print basename($directory) . ":\n";
+ foreach my $file (@files) {
+ push(@bugs, $self->_parse_bug_file($file));
+ if (!$self->verbose) {
+ indicate_progress({ current => $count++, every => 5,
+ total => $total });
+ }
+ }
+ return \@bugs;
+}
+
+sub _parse_bug_file {
+ my ($self, $file) = @_;
+ $self->debug("Reading $file");
+ open(my $fh, "<", $file) || die "$file: $!";
+ my $email = Email::Simple::FromHandle->new($fh);
+ my $fields = $self->_get_gnats_field_data($email);
+ # We parse attachments here instead of during translate_bug,
+ # because otherwise we'd be taking up huge amounts of memory storing
+ # all the raw attachment data in memory.
+ $fields->{attachments} = $self->_parse_attachments($fields);
+ close($fh);
+ return $fields;
+}
+
+sub _get_gnats_field_data {
+ my ($self, $email) = @_;
+ my ($current_field, @value_lines, %fields);
+ $email->reset_handle();
+ my $handle = $email->handle;
+ foreach my $line (<$handle>) {
+ # If this line starts a field name
+ if ($line =~ FIELD_REGEX) {
+ my ($new_field, $rest_of_line) = ($1, $2);
+
+ # If this is one of the last few PR fields, then make sure
+ # that we're getting our fields in the right order.
+ my $new_field_valid = 1;
+ my $search_for = $current_field || '';
+ my $current_field_pos = firstidx { $_ eq $search_for }
+ END_FIELD_ORDER;
+ if ($current_field_pos > -1) {
+ my $new_field_pos = firstidx { $_ eq $new_field }
+ END_FIELD_ORDER;
+ # We accept any field, as long as it's later than this one.
+ $new_field_valid = $new_field_pos > $current_field_pos ? 1 : 0;
+ }
+
+ if ($new_field_valid) {
+ if ($current_field) {
+ $fields{$current_field} = _handle_lines(\@value_lines);
+ @value_lines = ();
+ }
+ $current_field = $new_field;
+ $line = $rest_of_line;
+ }
+ }
+ push(@value_lines, $line) if defined $line;
+ }
+ $fields{$current_field} = _handle_lines(\@value_lines);
+ $fields{cc} = [$email->header('Cc')] if $email->header('Cc');
+
+ # If the Originator is invalid and we don't have a translation for it,
+ # use the From header instead.
+ my $originator = $self->translate_value('reporter', $fields{Originator},
+ { check_only => 1 });
+ if ($originator !~ Bugzilla->params->{emailregexp}) {
+ # We use the raw header sometimes, because it looks like "From: user"
+ # which Email::Address won't parse but we can still use.
+ my $address = $email->header('From');
+ my ($parsed) = Email::Address->parse($address);
+ if ($parsed) {
+ $address = $parsed->address;
+ }
+ if ($address) {
+ $self->debug(
+ "PR $fields{Number} had an Originator that was not a valid"
+ . " user ($fields{Originator}). Using From ($address)"
+ . " instead.\n");
+ my $address_email = $self->translate_value('reporter', $address,
+ { check_only => 1 });
+ if ($address_email !~ Bugzilla->params->{emailregexp}) {
+ $self->debug(" From was also invalid, using default_originator.\n");
+ $address = $self->config('default_originator');
+ }
+ $fields{Originator} = $address;
+ }
+ }
+
+ $self->debug(\%fields, 3);
+ return \%fields;
+}
+
+sub _handle_lines {
+ my ($lines) = @_;
+ my $value = join('', @$lines);
+ $value =~ s/\s+$//;
+ return $value;
+}
+
+####################
+# Translating Bugs #
+####################
+
+sub translate_bug {
+ my ($self, $fields) = @_;
+
+ my ($bug, $other_fields) = $self->SUPER::translate_bug($fields);
+
+ $bug->{attachments} = delete $other_fields->{attachments};
+
+ if (defined $other_fields->{_add_to_comment}) {
+ $bug->{comment} .= delete $other_fields->{_add_to_comment};
+ }
+
+ my ($changes, $extra_comment) =
+ $self->_parse_audit_trail($bug, $other_fields->{'Audit-Trail'});
+
+ my @comments;
+ foreach my $change (@$changes) {
+ if (exists $change->{comment}) {
+ push(@comments, {
+ thetext => $change->{comment},
+ who => $change->{who},
+ bug_when => $change->{bug_when} });
+ delete $change->{comment};
+ }
+ }
+ $bug->{history} = $changes;
+
+ if (trim($extra_comment)) {
+ push(@comments, { thetext => $extra_comment, who => $bug->{reporter},
+ bug_when => $bug->{delta_ts} || $bug->{creation_ts} });
+ }
+ $bug->{comments} = \@comments;
+
+ $bug->{component} = $self->config('component_name');
+ if (!$bug->{short_desc}) {
+ $bug->{short_desc} = NO_SUBJECT;
+ }
+
+ foreach my $attachment (@{ $bug->{attachments} || [] }) {
+ $attachment->{submitter} = $bug->{reporter};
+ $attachment->{creation_ts} = $bug->{creation_ts};
+ }
+
+ $self->debug($bug, 3);
+ return $bug;
+}
+
+sub _parse_audit_trail {
+ my ($self, $bug, $audit_trail) = @_;
+ return [] if !trim($audit_trail);
+ $self->debug(" Parsing audit trail...", 2);
+
+ if ($audit_trail !~ /^\S+-Changed-\S+:/ms) {
+ # This is just a comment from the bug's creator.
+ $self->debug(" Audit trail is just a comment.", 2);
+ return ([], $audit_trail);
+ }
+
+ my (@changes, %current_data, $current_column, $on_why);
+ my $extra_comment = '';
+ my $current_field;
+ my @all_lines = split("\n", $audit_trail);
+ foreach my $line (@all_lines) {
+ # GNATS history looks like:
+ # Status-Changed-From-To: open->closed
+ # Status-Changed-By: jack
+ # Status-Changed-When: Mon May 12 14:46:59 2003
+ # Status-Changed-Why:
+ # This is some comment here about the change.
+ if ($line =~ /^(\S+)-Changed-(\S+):(.*)/) {
+ my ($field, $column, $value) = ($1, $2, $3);
+ my $bz_field = $self->translate_field($field);
+ # If it's not a field we're importing, we don't care about
+ # its history.
+ next if !$bz_field;
+ # GNATS doesn't track values for description changes,
+ # unfortunately, and that's the only information we'd be able to
+ # use in Bugzilla for the audit trail on that field.
+ next if $bz_field eq 'comment';
+ $current_field = $bz_field if !$current_field;
+ if ($bz_field ne $current_field) {
+ $self->_store_audit_change(
+ \@changes, $current_field, \%current_data);
+ %current_data = ();
+ $current_field = $bz_field;
+ }
+ $value = trim($value);
+ $self->debug(" $bz_field $column: $value", 3);
+ if ($column eq 'From-To') {
+ my ($from, $to) = split('->', $value, 2);
+ # Sometimes there's just a - instead of a -> between the values.
+ if (!defined($to)) {
+ ($from, $to) = split('-', $value, 2);
+ }
+ $current_data{added} = $to;
+ $current_data{removed} = $from;
+ }
+ elsif ($column eq 'By') {
+ my $email = $self->translate_value('user', $value);
+ # Sometimes we hit users in the audit trail that we haven't
+ # seen anywhere else.
+ $current_data{who} = $email;
+ }
+ elsif ($column eq 'When') {
+ $current_data{bug_when} = $self->parse_date($value);
+ }
+ if ($column eq 'Why') {
+ $value = '' if !defined $value;
+ $current_data{comment} = $value;
+ $on_why = 1;
+ }
+ else {
+ $on_why = 0;
+ }
+ }
+ elsif ($on_why) {
+ # "Why" lines are indented four characters.
+ $line =~ s/^\s{4}//;
+ $current_data{comment} .= "$line\n";
+ }
+ else {
+ $self->debug(
+ "Extra Audit-Trail line on $bug->{product} $bug->{bug_id}:"
+ . " $line\n", 2);
+ $extra_comment .= "$line\n";
+ }
+ }
+ $self->_store_audit_change(\@changes, $current_field, \%current_data);
+ return (\@changes, $extra_comment);
+}
+
+sub _store_audit_change {
+ my ($self, $changes, $old_field, $current_data) = @_;
+
+ $current_data->{field} = $old_field;
+ $current_data->{removed} =
+ $self->translate_value($old_field, $current_data->{removed});
+ $current_data->{added} =
+ $self->translate_value($old_field, $current_data->{added});
+ push(@$changes, { %$current_data });
+}
+
+sub _parse_attachments {
+ my ($self, $fields) = @_;
+ my $unformatted = delete $fields->{'Unformatted'};
+ my $gnats_boundary = GNATS_BOUNDARY;
+ # A sanity checker to make sure that we're parsing attachments right.
+ my $num_attachments = 0;
+ $num_attachments++ while ($unformatted =~ /\Q$gnats_boundary\E/g);
+ # Sometimes there's a GNATS_BOUNDARY that is on the same line as other data.
+ $unformatted =~ s/(\S\s*)\Q$gnats_boundary\E$/$1\n$gnats_boundary/mg;
+ # Often the "Unformatted" section starts with stuff before
+ # ----gnatsweb-attachment---- that isn't necessary.
+ $unformatted =~ s/^\s*From:.+?Reply-to:[^\n]+//s;
+ $unformatted = trim($unformatted);
+ return [] if !$unformatted;
+ $self->debug('Reading attachments...', 2);
+ my $boundary = generate_random_password(48);
+ $unformatted =~ s/\Q$gnats_boundary\E/--$boundary/g;
+ # Sometimes the whole Unformatted section is indented by exactly
+ # one space, and needs to be fixed.
+ if ($unformatted =~ /--\Q$boundary\E\n /) {
+ $unformatted =~ s/^ //mg;
+ }
+ $unformatted = <<END;
+From: nobody
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="$boundary"
+
+This is a multi-part message in MIME format.
+--$boundary
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+$unformatted
+--$boundary--
+END
+ my $email = new Email::MIME(\$unformatted);
+ my @parts = $email->parts;
+ # Remove the fake body.
+ my $part1 = shift @parts;
+ if ($part1->body) {
+ $self->debug(" Additional Unformatted data found on "
+ . $fields->{Category} . " bug " . $fields->{Number});
+ $self->debug($part1->body, 3);
+ $fields->{_add_comment} .= "\n\nUnformatted:\n" . $part1->body;
+ }
+
+ my @attachments;
+ foreach my $part (@parts) {
+ $self->debug(' Parsing attachment: ' . $part->filename);
+ my $temp_fh = IO::File->new_tmpfile or die ("Can't create tempfile: $!");
+ $temp_fh->binmode;
+ print $temp_fh $part->body;
+ my $content_type = $part->content_type;
+ $content_type =~ s/; name=.+$//;
+ my $attachment = { filename => $part->filename,
+ description => $part->filename,
+ mimetype => $content_type,
+ data => $temp_fh };
+ $self->debug($attachment, 3);
+ push(@attachments, $attachment);
+ }
+
+ if (scalar(@attachments) ne $num_attachments) {
+ warn "WARNING: Expected $num_attachments attachments but got "
+ . scalar(@attachments) . "\n" ;
+ $self->debug($unformatted, 3);
+ }
+ return \@attachments;
+}
+
+sub translate_value {
+ my $self = shift;
+ my ($field, $value, $options) = @_;
+ my $original_value = $value;
+ $options ||= {};
+
+ if (!ref($value) and grep($_ eq $field, $self->USER_FIELDS)) {
+ if ($value =~ /(\S+\@\S+)/) {
+ $value = $1;
+ $value =~ s/^<//;
+ $value =~ s/>$//;
+ }
+ else {
+ # Sometimes names have extra stuff on the end like "(Somebody's Name)"
+ $value =~ s/\s+\(.+\)$//;
+ # Sometimes user fields look like "(user)" instead of just "user".
+ $value =~ s/^\((.+)\)$/$1/;
+ $value = trim($value);
+ }
+ }
+
+ if ($field eq 'version' and $value ne '') {
+ my $version_re = $self->config('version_regex');
+ if ($version_re and $value =~ $version_re) {
+ $value = $1;
+ }
+ # In the GNATS that I tested this with, there were many extremely long
+ # values for "version" that caused some import problems (they were
+ # longer than the max allowed version value). So if the version value
+ # is longer than 32 characters, pull out the first thing that looks
+ # like a version number.
+ elsif (length($value) > LONG_VERSION_LENGTH) {
+ $value =~ s/^.+?\b(\d[\w\.]+)\b.+$/$1/;
+ }
+ }
+
+ my @args = @_;
+ $args[1] = $value;
+
+ $value = $self->SUPER::translate_value(@args);
+ return $value if ref $value;
+
+ if (grep($_ eq $field, $self->USER_FIELDS)) {
+ my $from_value = $value;
+ $value = $self->user_to_email($value);
+ $args[1] = $value;
+ # If we got something new from user_to_email, do any necessary
+ # translation of it.
+ $value = $self->SUPER::translate_value(@args);
+ if (!$options->{check_only}) {
+ $self->add_user($from_value, $value);
+ }
+ }
+
+ return $value;
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Milestone.pm b/Websites/bugs.webkit.org/Bugzilla/Milestone.pm
index fc44cf1..92bc219 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Milestone.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Milestone.pm
@@ -26,6 +26,8 @@
use Bugzilla::Util;
use Bugzilla::Error;
+use Scalar::Util qw(blessed);
+
################################
##### Initialization #####
################################
@@ -41,25 +43,28 @@
value
product_id
sortkey
+ isactive
);
-use constant REQUIRED_CREATE_FIELDS => qw(
- name
- product
-);
+use constant REQUIRED_FIELD_MAP => {
+ product_id => 'product',
+};
use constant UPDATE_COLUMNS => qw(
value
sortkey
+ isactive
);
use constant VALIDATORS => {
- product => \&_check_product,
- sortkey => \&_check_sortkey,
+ product => \&_check_product,
+ sortkey => \&_check_sortkey,
+ value => \&_check_value,
+ isactive => \&Bugzilla::Object::check_boolean,
};
-use constant UPDATE_VALIDATORS => {
- value => \&_check_value,
+use constant VALIDATOR_DEPENDENCIES => {
+ value => ['product'],
};
################################
@@ -94,14 +99,10 @@
}
sub run_create_validators {
- my $class = shift;
+ my $class = shift;
my $params = $class->SUPER::run_create_validators(@_);
-
my $product = delete $params->{product};
$params->{product_id} = $product->id;
- $params->{value} = $class->_check_value($params->{name}, $product);
- delete $params->{name};
-
return $params;
}
@@ -165,7 +166,8 @@
################################
sub _check_value {
- my ($invocant, $name, $product) = @_;
+ my ($invocant, $name, undef, $params) = @_;
+ my $product = blessed($invocant) ? $invocant->product : $params->{product};
$name = trim($name);
$name || ThrowUserError('milestone_blank_name');
@@ -173,7 +175,6 @@
ThrowUserError('milestone_name_too_long', {name => $name});
}
- $product = $invocant->product if (ref $invocant);
my $milestone = new Bugzilla::Milestone({product => $product, name => $name});
if ($milestone && (!ref $invocant || $milestone->id != $invocant->id)) {
ThrowUserError('milestone_already_exists', { name => $milestone->name,
@@ -196,6 +197,8 @@
sub _check_product {
my ($invocant, $product) = @_;
+ $product || ThrowCodeError('param_required',
+ { function => "$invocant->create", param => "product" });
return Bugzilla->user->check_can_admin_product($product->name);
}
@@ -203,8 +206,9 @@
# Methods
################################
-sub set_name { $_[0]->set('value', $_[1]); }
-sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+sub set_name { $_[0]->set('value', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+sub set_is_active { $_[0]->set('isactive', $_[1]); }
sub bug_count {
my $self = shift;
@@ -226,6 +230,7 @@
sub name { return $_[0]->{'value'}; }
sub product_id { return $_[0]->{'product_id'}; }
sub sortkey { return $_[0]->{'sortkey'}; }
+sub is_active { return $_[0]->{'isactive'}; }
sub product {
my $self = shift;
@@ -255,7 +260,7 @@
my $sortkey = $milestone->sortkey;
my $milestone = Bugzilla::Milestone->create(
- { name => $name, product => $product, sortkey => $sortkey });
+ { value => $name, product => $product, sortkey => $sortkey });
$milestone->set_name($new_name);
$milestone->set_sortkey($new_sortkey);
@@ -361,11 +366,11 @@
=over
-=item C<create({name => $name, product => $product, sortkey => $sortkey})>
+=item C<create({value => $value, product => $product, sortkey => $sortkey})>
Description: Create a new milestone for the given product.
- Params: $name - name of the new milestone (string). This name
+ Params: $value - name of the new milestone (string). This name
must be unique within the product.
$product - a Bugzilla::Product object.
$sortkey - the sortkey of the new milestone (signed integer)
diff --git a/Websites/bugs.webkit.org/Bugzilla/Object.pm b/Websites/bugs.webkit.org/Bugzilla/Object.pm
index 7f39b81..422a2ff 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Object.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Object.pm
@@ -24,10 +24,13 @@
package Bugzilla::Object;
use Bugzilla::Constants;
+use Bugzilla::Hook;
use Bugzilla::Util;
use Bugzilla::Error;
use Date::Parse;
+use List::MoreUtils qw(part);
+use Scalar::Util qw(blessed);
use constant NAME_FIELD => 'name';
use constant ID_FIELD => 'id';
@@ -36,6 +39,18 @@
use constant UPDATE_VALIDATORS => {};
use constant NUMERIC_COLUMNS => ();
use constant DATE_COLUMNS => ();
+use constant VALIDATOR_DEPENDENCIES => {};
+# XXX At some point, this will be joined with FIELD_MAP.
+use constant REQUIRED_FIELD_MAP => {};
+use constant EXTRA_REQUIRED_FIELDS => ();
+use constant AUDIT_CREATES => 1;
+use constant AUDIT_UPDATES => 1;
+use constant AUDIT_REMOVES => 1;
+
+# This allows the JSON-RPC interface to return Bugzilla::Object instances
+# as though they were hashes. In the future, this may be modified to return
+# less information.
+sub TO_JSON { return { %{ $_[0] } }; }
###############################
#### Initialization ####
@@ -58,12 +73,15 @@
my $class = shift;
my ($param) = @_;
my $dbh = Bugzilla->dbh;
- my $columns = join(',', $class->DB_COLUMNS);
+ my $columns = join(',', $class->_get_db_columns);
my $table = $class->DB_TABLE;
my $name_field = $class->NAME_FIELD;
my $id_field = $class->ID_FIELD;
- my $id = $param unless (ref $param eq 'HASH');
+ my $id = $param;
+ if (ref $param eq 'HASH') {
+ $id = $param->{id};
+ }
my $object;
if (defined $id) {
@@ -73,6 +91,9 @@
|| ThrowCodeError('param_must_be_numeric',
{function => $class . '::_init'});
+ # Too large integers make PostgreSQL crash.
+ return if $id > MAX_INT_32;
+
$object = $dbh->selectrow_hashref(qq{
SELECT $columns FROM $table
WHERE $id_field = ?}, undef, $id);
@@ -114,14 +135,29 @@
if (!ref $param) {
$param = { name => $param };
}
- # Don't allow empty names.
- if (exists $param->{name}) {
- $param->{name} = trim($param->{name});
- $param->{name} || ThrowUserError('object_name_not_specified',
- { class => $class });
+
+ # Don't allow empty names or ids.
+ my $check_param = exists $param->{id} ? 'id' : 'name';
+ $param->{$check_param} = trim($param->{$check_param});
+ # If somebody passes us "0", we want to throw an error like
+ # "there is no X with the name 0". This is true even for ids. So here,
+ # we only check if the parameter is undefined or empty.
+ if (!defined $param->{$check_param} or $param->{$check_param} eq '') {
+ ThrowUserError('object_not_specified', { class => $class });
}
- my $obj = $class->new($param)
- || ThrowUserError('object_does_not_exist', {%$param, class => $class});
+
+ my $obj = $class->new($param);
+ if (!$obj) {
+ # We don't want to override the normal template "user" object if
+ # "user" is one of the params.
+ delete $param->{user};
+ if (my $error = delete $param->{_error}) {
+ ThrowUserError($error, { %$param, class => $class });
+ }
+ else {
+ ThrowUserError('object_does_not_exist', { %$param, class => $class });
+ }
+ }
return $obj;
}
@@ -136,6 +172,8 @@
detaint_natural($id) ||
ThrowCodeError('param_must_be_numeric',
{function => $class . '::new_from_list'});
+ # Too large integers make PostgreSQL crash.
+ next if $id > MAX_INT_32;
push(@detainted_ids, $id);
}
# We don't do $invocant->match because some classes have
@@ -155,10 +193,44 @@
return [$class->get_all] if !$criteria;
- my (@terms, @values);
+ my (@terms, @values, $postamble);
foreach my $field (keys %$criteria) {
- $class->_check_field($field, 'match');
my $value = $criteria->{$field};
+
+ # allow for LIMIT and OFFSET expressions via the criteria.
+ next if $field eq 'OFFSET';
+ if ( $field eq 'LIMIT' ) {
+ next unless defined $value;
+ detaint_natural($value)
+ or ThrowCodeError('param_must_be_numeric',
+ { param => 'LIMIT',
+ function => "${class}::match" });
+ my $offset;
+ if (defined $criteria->{OFFSET}) {
+ $offset = $criteria->{OFFSET};
+ detaint_signed($offset)
+ or ThrowCodeError('param_must_be_numeric',
+ { param => 'OFFSET',
+ function => "${class}::match" });
+ }
+ $postamble = $dbh->sql_limit($value, $offset);
+ next;
+ }
+ elsif ( $field eq 'WHERE' ) {
+ # the WHERE value is a hashref where the keys are
+ # "column_name operator ?" and values are the placeholder's
+ # value (either a scalar or an array of values).
+ foreach my $k (keys %$value) {
+ push(@terms, $k);
+ my @this_value = ref($value->{$k}) ? @{ $value->{$k} }
+ : ($value->{$k});
+ push(@values, @this_value);
+ }
+ next;
+ }
+
+ $class->_check_field($field, 'match');
+
if (ref $value eq 'ARRAY') {
# IN () is invalid SQL, and if we have an empty list
# to match against, we're just returning an empty
@@ -181,14 +253,14 @@
}
}
- my $where = join(' AND ', @terms);
- return $class->_do_list_select($where, \@values);
+ my $where = join(' AND ', @terms) if scalar @terms;
+ return $class->_do_list_select($where, \@values, $postamble);
}
sub _do_list_select {
- my ($class, $where, $values) = @_;
+ my ($class, $where, $values, $postamble) = @_;
my $table = $class->DB_TABLE;
- my $cols = join(',', $class->DB_COLUMNS);
+ my $cols = join(',', $class->_get_db_columns);
my $order = $class->LIST_ORDER;
my $sql = "SELECT $cols FROM $table";
@@ -196,9 +268,16 @@
$sql .= " WHERE $where ";
}
$sql .= " ORDER BY $order";
-
+
+ $sql .= " $postamble" if $postamble;
+
my $dbh = Bugzilla->dbh;
- my $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @$values);
+ # Sometimes the values are tainted, but we don't want to untaint them
+ # for the caller. So we copy the array. It's safe to untaint because
+ # they're only used in placeholders here.
+ my @untainted = @{ $values || [] };
+ trick_taint($_) foreach @untainted;
+ my $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @untainted);
bless ($_, $class) foreach @$objects;
return $objects
}
@@ -218,13 +297,18 @@
my ($self, $field, $value) = @_;
# This method is protected. It's used to help implement set_ functions.
- caller->isa('Bugzilla::Object')
+ my $caller = caller;
+ $caller->isa('Bugzilla::Object') || $caller->isa('Bugzilla::Extension')
|| ThrowCodeError('protection_violation',
{ caller => caller,
superclass => __PACKAGE__,
function => 'Bugzilla::Object->set' });
- my %validators = (%{$self->VALIDATORS}, %{$self->UPDATE_VALIDATORS});
+ Bugzilla::Hook::process('object_before_set',
+ { object => $self, field => $field,
+ value => $value });
+
+ my %validators = (%{$self->_get_validators}, %{$self->UPDATE_VALIDATORS});
if (exists $validators{$field}) {
my $validator = $validators{$field};
$value = $self->$validator($value, $field);
@@ -236,6 +320,28 @@
}
$self->{$field} = $value;
+
+ Bugzilla::Hook::process('object_end_of_set',
+ { object => $self, field => $field });
+}
+
+sub set_all {
+ my ($self, $params) = @_;
+
+ # Don't let setters modify the values in $params for the caller.
+ my %field_values = %$params;
+
+ my @sorted_names = $self->_sort_by_dep(keys %field_values);
+ foreach my $key (@sorted_names) {
+ # It's possible for one set_ method to delete a key from $params
+ # for another set method, so if that's happened, we don't call the
+ # other set method.
+ next if !exists $field_values{$key};
+ my $method = "set_$key";
+ $self->$method($field_values{$key}, \%field_values);
+ }
+ Bugzilla::Hook::process('object_end_of_set_all',
+ { object => $self, params => \%field_values });
}
sub update {
@@ -248,11 +354,17 @@
$dbh->bz_start_transaction();
my $old_self = $self->new($self->id);
-
+
+ my @all_columns = $self->UPDATE_COLUMNS;
+ my @hook_columns;
+ Bugzilla::Hook::process('object_update_columns',
+ { object => $self, columns => \@hook_columns });
+ push(@all_columns, @hook_columns);
+
my %numeric = map { $_ => 1 } $self->NUMERIC_COLUMNS;
my %date = map { $_ => 1 } $self->DATE_COLUMNS;
my (@update_columns, @values, %changes);
- foreach my $column ($self->UPDATE_COLUMNS) {
+ foreach my $column (@all_columns) {
my ($old, $new) = ($old_self->{$column}, $self->{$column});
# This has to be written this way in order to allow us to set a field
# from undef or to undef, and avoid warnings about comparing an undef
@@ -279,15 +391,78 @@
$dbh->do("UPDATE $table SET $columns WHERE $id_field = ?", undef,
@values, $self->id) if @values;
+ Bugzilla::Hook::process('object_end_of_update',
+ { object => $self, old_object => $old_self,
+ changes => \%changes });
+
+ $self->audit_log(\%changes) if $self->AUDIT_UPDATES;
+
$dbh->bz_commit_transaction();
+ if (wantarray) {
+ return (\%changes, $old_self);
+ }
+
return \%changes;
}
+sub remove_from_db {
+ my $self = shift;
+ Bugzilla::Hook::process('object_before_delete', { object => $self });
+ my $table = $self->DB_TABLE;
+ my $id_field = $self->ID_FIELD;
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ $self->audit_log(AUDIT_REMOVE) if $self->AUDIT_REMOVES;
+ $dbh->do("DELETE FROM $table WHERE $id_field = ?", undef, $self->id);
+ $dbh->bz_commit_transaction();
+ undef $self;
+}
+
+sub audit_log {
+ my ($self, $changes) = @_;
+ my $class = ref $self;
+ my $dbh = Bugzilla->dbh;
+ my $user_id = Bugzilla->user->id || undef;
+ my $sth = $dbh->prepare(
+ 'INSERT INTO audit_log (user_id, class, object_id, field,
+ removed, added, at_time)
+ VALUES (?,?,?,?,?,?,LOCALTIMESTAMP(0))');
+ # During creation or removal, $changes is actually just a string
+ # indicating whether we're creating or removing the object.
+ if ($changes eq AUDIT_CREATE or $changes eq AUDIT_REMOVE) {
+ # We put the object's name in the "added" or "removed" field.
+ # We do this thing with NAME_FIELD because $self->name returns
+ # the wrong thing for Bugzilla::User.
+ my $name = $self->{$self->NAME_FIELD};
+ my @added_removed = $changes eq AUDIT_CREATE ? (undef, $name)
+ : ($name, undef);
+ $sth->execute($user_id, $class, $self->id, $changes, @added_removed);
+ return;
+ }
+
+ # During update, it's the actual %changes hash produced by update().
+ foreach my $field (keys %$changes) {
+ # Skip private changes.
+ next if $field =~ /^_/;
+ my ($from, $to) = @{ $changes->{$field} };
+ $sth->execute($user_id, $class, $self->id, $field, $from, $to);
+ }
+}
+
###############################
#### Subroutines ######
###############################
+sub any_exist {
+ my $class = shift;
+ my $table = $class->DB_TABLE;
+ my $dbh = Bugzilla->dbh;
+ my $any_exist = $dbh->selectrow_array(
+ "SELECT 1 FROM $table " . $dbh->sql_limit(1));
+ return $any_exist ? 1 : 0;
+}
+
sub create {
my ($class, $params) = @_;
my $dbh = Bugzilla->dbh;
@@ -315,36 +490,52 @@
sub check_required_create_fields {
my ($class, $params) = @_;
- foreach my $field ($class->REQUIRED_CREATE_FIELDS) {
- ThrowCodeError('param_required',
- { function => "${class}->create", param => $field })
- if !exists $params->{$field};
+ # This hook happens here so that even subclasses that don't call
+ # SUPER::create are still affected by the hook.
+ Bugzilla::Hook::process('object_before_create', { class => $class,
+ params => $params });
+
+ my @check_fields = $class->_required_create_fields();
+ foreach my $field (@check_fields) {
+ $params->{$field} = undef if !exists $params->{$field};
}
}
sub run_create_validators {
- my ($class, $params) = @_;
+ my ($class, $params, $options) = @_;
- my $validators = $class->VALIDATORS;
+ my $validators = $class->_get_validators;
+ my %field_values = %$params;
- my %field_values;
- # We do the sort just to make sure that validation always
- # happens in a consistent order.
- foreach my $field (sort keys %$params) {
+ # Make a hash skiplist for easier searching later
+ my %skip_list = map { $_ => 1 } @{ $options->{skip} || [] };
+
+ # Get the sorted field names
+ my @sorted_names = $class->_sort_by_dep(keys %field_values);
+
+ # Remove the skipped names
+ my @unskipped = grep { !$skip_list{$_} } @sorted_names;
+
+ foreach my $field (@unskipped) {
my $value;
if (exists $validators->{$field}) {
my $validator = $validators->{$field};
- $value = $class->$validator($params->{$field}, $field);
+ $value = $class->$validator($field_values{$field}, $field,
+ \%field_values);
}
else {
- $value = $params->{$field};
+ $value = $field_values{$field};
}
+
# We want people to be able to explicitly set fields to NULL,
# and that means they can be set to undef.
trick_taint($value) if defined $value && !ref($value);
$field_values{$field} = $value;
}
+ Bugzilla::Hook::process('object_end_of_create_validators',
+ { class => $class, params => \%field_values });
+
return \%field_values;
}
@@ -365,7 +556,14 @@
$dbh->do("INSERT INTO $table (" . join(', ', @field_names)
. ") VALUES ($qmarks)", undef, @values);
my $id = $dbh->bz_last_key($table, $class->ID_FIELD);
- return $class->new($id);
+
+ my $object = $class->new($id);
+
+ Bugzilla::Hook::process('object_end_of_create', { class => $class,
+ object => $object });
+ $object->audit_log(AUDIT_CREATE) if $object->AUDIT_CREATES;
+
+ return $object;
}
sub get_all {
@@ -379,6 +577,173 @@
sub check_boolean { return $_[1] ? 1 : 0 }
+sub check_time {
+ my ($invocant, $value, $field, $params, $allow_negative) = @_;
+
+ # If we don't have a current value default to zero
+ my $current = blessed($invocant) ? $invocant->{$field}
+ : 0;
+ $current ||= 0;
+
+ # Get the new value or zero if it isn't defined
+ $value = trim($value) || 0;
+
+ # Make sure the new value is well formed
+ _validate_time($value, $field, $allow_negative);
+
+ return $value;
+}
+
+
+###################
+# General Helpers #
+###################
+
+sub _validate_time {
+ my ($time, $field, $allow_negative) = @_;
+
+ # regexp verifies one or more digits, optionally followed by a period and
+ # zero or more digits, OR we have a period followed by one or more digits
+ # (allow negatives, though, so people can back out errors in time reporting)
+ if ($time !~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) {
+ ThrowUserError("number_not_numeric",
+ {field => $field, num => "$time"});
+ }
+
+ # Callers can optionally allow negative times
+ if ( ($time < 0) && !$allow_negative ) {
+ ThrowUserError("number_too_small",
+ {field => $field, num => "$time", min_num => "0"});
+ }
+
+ if ($time > 99999.99) {
+ ThrowUserError("number_too_large",
+ {field => $field, num => "$time", max_num => "99999.99"});
+ }
+}
+
+# Sorts fields according to VALIDATOR_DEPENDENCIES. This is not a
+# traditional topological sort, because a "dependency" does not
+# *have* to be in the list--it just has to be earlier than its dependent
+# if it *is* in the list.
+sub _sort_by_dep {
+ my ($invocant, @fields) = @_;
+
+ my $dependencies = $invocant->VALIDATOR_DEPENDENCIES;
+ my ($has_deps, $no_deps) = part { $dependencies->{$_} ? 0 : 1 } @fields;
+
+ # For fields with no dependencies, we sort them alphabetically,
+ # so that validation always happens in a consistent order.
+ # Fields with no dependencies come at the start of the list.
+ my @result = sort @{ $no_deps || [] };
+
+ # Fields with dependencies all go at the end of the list, and if
+ # they have dependencies on *each other*, then they have to be
+ # sorted properly. We go through $has_deps in sorted order to be
+ # sure that fields always validate in a consistent order.
+ foreach my $field (sort @{ $has_deps || [] }) {
+ if (!grep { $_ eq $field } @result) {
+ _insert_dep_field($field, $has_deps, $dependencies, \@result);
+ }
+ }
+ return @result;
+}
+
+sub _insert_dep_field {
+ my ($field, $insert_me, $dependencies, $result, $loop_tracking) = @_;
+
+ if ($loop_tracking->{$field}) {
+ ThrowCodeError('object_dep_sort_loop',
+ { field => $field,
+ considered => [keys %$loop_tracking] });
+ }
+ $loop_tracking->{$field} = 1;
+
+ my $required_fields = $dependencies->{$field};
+ # Imagine Field A requires field B...
+ foreach my $required_field (@$required_fields) {
+ # If our dependency is already satisfied, we're good.
+ next if grep { $_ eq $required_field } @$result;
+
+ # If our dependency is not in the remaining fields to insert,
+ # then we're also OK.
+ next if !grep { $_ eq $required_field } @$insert_me;
+
+ # So, at this point, we know that Field B is in $insert_me.
+ # So let's put the required field into the result.
+ _insert_dep_field($required_field, $insert_me, $dependencies,
+ $result, $loop_tracking);
+ }
+ push(@$result, $field);
+}
+
+####################
+# Constant Helpers #
+####################
+
+# For some classes, some constants take time to generate, so we cache them
+# and only access them through the below methods. This also allows certain
+# hooks to only run once per request instead of multiple times on each
+# page.
+
+sub _get_db_columns {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $cache = Bugzilla->request_cache;
+ my $cache_key = "object_${class}_db_columns";
+ return @{ $cache->{$cache_key} } if $cache->{$cache_key};
+ # Currently you can only add new columns using object_columns, not
+ # remove or modify existing columns, because removing columns would
+ # almost certainly cause Bugzilla to function improperly.
+ my @add_columns;
+ Bugzilla::Hook::process('object_columns',
+ { class => $class, columns => \@add_columns });
+ my @columns = ($invocant->DB_COLUMNS, @add_columns);
+ $cache->{$cache_key} = \@columns;
+ return @{ $cache->{$cache_key} };
+}
+
+# This method is private and should only be called by Bugzilla::Object.
+sub _get_validators {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $cache = Bugzilla->request_cache;
+ my $cache_key = "object_${class}_validators";
+ return $cache->{$cache_key} if $cache->{$cache_key};
+ # We copy this into a hash so that the hook doesn't modify the constant.
+ # (That could be bad in mod_perl.)
+ my %validators = %{ $invocant->VALIDATORS };
+ Bugzilla::Hook::process('object_validators',
+ { class => $class, validators => \%validators });
+ $cache->{$cache_key} = \%validators;
+ return $cache->{$cache_key};
+}
+
+# These are all the fields that need to be checked, always, when
+# calling create(), because they have no DEFAULT and they are marked
+# NOT NULL.
+sub _required_create_fields {
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+ my $table = $class->DB_TABLE;
+
+ my @columns = $dbh->bz_table_columns($table);
+ my @required;
+ foreach my $column (@columns) {
+ my $def = $dbh->bz_column_info($table, $column);
+ if ($def->{NOTNULL} and !defined $def->{DEFAULT}
+ # SERIAL fields effectively have a DEFAULT, but they're not
+ # listed as having a DEFAULT in DB::Schema.
+ and $def->{TYPE} !~ /serial/i)
+ {
+ my $field = $class->REQUIRED_FIELD_MAP->{$column} || $column;
+ push(@required, $field);
+ }
+ }
+ push(@required, $class->EXTRA_REQUIRED_FIELDS);
+ return @required;
+}
+
1;
__END__
@@ -425,6 +790,12 @@
The names of the columns that you want to read out of the database
and into this object. This should be an array.
+I<Note>: Though normally you will never need to access this constant's data
+directly in your subclass, if you do, you should access it by calling the
+C<_get_db_columns> method instead of accessing the constant directly. (The
+only exception to this rule is calling C<SUPER::DB_COLUMNS> from within
+your own C<DB_COLUMNS> subroutine in a subclass.)
+
=item C<NAME_FIELD>
The name of the column that should be considered to be the unique
@@ -444,11 +815,6 @@
in. This should be the name of a database column. Defaults to
L</NAME_FIELD>.
-=item C<REQUIRED_CREATE_FIELDS>
-
-The list of fields that B<must> be specified when the user calls
-C<create()>. This should be an array.
-
=item C<VALIDATORS>
A hashref that points to a function that will validate each param to
@@ -479,12 +845,69 @@
L<Bugzilla::Bug> has good examples in its code of when to use this.
+=item C<VALIDATOR_DEPENDENCIES>
+
+During L</create> and L</set_all>, validators are normally called in
+a somewhat-random order. If you need one field to be validated and set
+before another field, this constant is how you do it, by saying that
+one field "depends" on the value of other fields.
+
+This is a hashref, where the keys are field names and the values are
+arrayrefs of field names. You specify what fields a field depends on using
+the arrayrefs. So, for example, to say that a C<component> field depends
+on the C<product> field being set, you would do:
+
+ component => ['product']
+
=item C<UPDATE_COLUMNS>
A list of columns to update when L</update> is called.
If a field can't be changed, it shouldn't be listed here. (For example,
the L</ID_FIELD> usually can't be updated.)
+=item C<REQUIRED_FIELD_MAP>
+
+This is a hashref that maps database column names to L</create> argument
+names. You only need to specify values for fields where the argument passed
+to L</create> has a different name in the database than it does in the
+L</create> arguments. (For example, L<Bugzilla::Bug/create> takes a
+C<product> argument, but the column name in the C<bugs> table is
+C<product_id>.)
+
+=item C<EXTRA_REQUIRED_FIELDS>
+
+Normally, Bugzilla::Object automatically figures out which fields
+are required for L</create>. It then I<always> runs those fields' validators,
+even if those fields weren't passed as arguments to L</create>. That way,
+any default values or required checks can be done for those fields by
+the validators.
+
+L</create> figures out which fields are required by looking for database
+columns in the L</DB_TABLE> that are NOT NULL and have no DEFAULT set.
+However, there are some fields that this check doesn't work for:
+
+=over
+
+=item *
+
+Fields that have database defaults (or are marked NULL in the database)
+but actually have different defaults specified by validators. (For example,
+the qa_contact field in the C<bugs> table can be NULL, so it won't be
+caught as being required. However, in reality it defaults to the
+component's initial_qa_contact.)
+
+=item *
+
+Fields that have defaults that should be set by validators, but are
+actually stored in a table different from L</DB_TABLE> (like the "cc"
+field for bugs, which defaults to the "initialcc" of the Component, but won't
+be caught as a normal required field because it's in a separate table.)
+
+=back
+
+Any field matching the above criteria needs to have its name listed in
+this constant. For an example of use, see the code of L<Bugzilla::Bug>.
+
=item C<NUMERIC_COLUMNS>
When L</update> is called, it compares each column in the object to its
@@ -524,7 +947,9 @@
If you pass in a hashref, you can pass a C<name> key. The
value of the C<name> key is the case-insensitive name of the object
-(from L</NAME_FIELD>) in the DB.
+(from L</NAME_FIELD>) in the DB. You can also pass in an C<id> key
+which will be interpreted as the id of the object you want (overriding the
+C<name> key).
B<Additional Parameters Available for Subclasses>
@@ -614,6 +1039,26 @@
which means "give me objects where this field is NULL or NOT NULL,
respectively."
+In addition to the column keys, there are a few special keys that
+can be used to rig the underlying database queries. These are
+C<LIMIT>, C<OFFSET>, and C<WHERE>.
+
+The value for the C<LIMIT> key is expected to be an integer defining
+the number of objects to return, while the value for C<OFFSET> defines
+the position, relative to the number of objects the query would normally
+return, at which to begin the result set. If C<OFFSET> is defined without
+a corresponding C<LIMIT> it is silently ignored.
+
+The C<WHERE> key provides a mechanism for adding arbitrary WHERE
+clauses to the underlying query. Its value is expected to a hash
+reference whose keys are the columns, operators and placeholders, and the
+values are the placeholders' bind value. For example:
+
+ WHERE => { 'some_column >= ?' => $some_value }
+
+would constrain the query to only those objects in the table whose
+'some_column' column has a value greater than or equal to $some_value.
+
If you don't specify any criteria, calling this function is the same
as doing C<[$class-E<gt>get_all]>.
@@ -636,17 +1081,13 @@
are invalid.
Params: C<$params> - hashref - A value to put in each database
- field for this object. Certain values must be set (the
- ones specified in L</REQUIRED_CREATE_FIELDS>), and
- the function will throw a Code Error if you don't set
- them.
+ field for this object.
Returns: The Object just created in the database.
Notes: In order for this function to work in your subclass,
your subclass's L</ID_FIELD> must be of C<SERIAL>
- type in the database. Your subclass also must
- define L</REQUIRED_CREATE_FIELDS> and L</VALIDATORS>.
+ type in the database.
Subclass Implementors: This function basically just
calls L</check_required_create_fields>, then
@@ -661,8 +1102,10 @@
=item B<Description>
-Part of L</create>. Throws an error if any of the L</REQUIRED_CREATE_FIELDS>
-have not been specified in C<$params>
+Part of L</create>. Modifies the incoming C<$params> argument so that
+any field that does not have a database default will be checked
+later by L</run_create_validators>, even if that field wasn't specified
+as an argument to L</create>.
=item B<Params>
@@ -684,7 +1127,11 @@
of their input parameters. This method is B<only> called
by L</create>.
-Params: The same as L</create>.
+Params: C<$params> - hashref - A value to put in each database
+ field for this object.
+ C<$options> - hashref - Processing options. Currently
+ the only option supported is B<skip>, which can be
+ used to specify a list of fields to not validate.
Returns: A hash, in a similar format as C<$params>, except that
these are the values to be inserted into the database,
@@ -711,6 +1158,8 @@
=item B<Returns>
+B<In scalar context:>
+
A hashref showing what changed during the update. The keys are the column
names from L</UPDATE_COLUMNS>. If a field was not changed, it will not be
in the hash at all. If the field was changed, the key will point to an arrayref.
@@ -719,14 +1168,27 @@
If there were no changes, we return a reference to an empty hash.
-=back
+B<In array context:>
+
+Returns a list, where the first item is the above hashref. The second item
+is the object as it was in the database before update() was called. (This
+is mostly useful to subclasses of C<Bugzilla::Object> that are implementing
+C<update>.)
=back
-=head2 Subclass Helpers
+=item C<remove_from_db>
-These functions are intended only for use by subclasses. If
-you call them from anywhere else, they will throw a C<CodeError>.
+Removes this object from the database. Will throw an error if you can't
+remove it for some reason. The object will then be destroyed, as it is
+not safe to use the object after it has been removed from the database.
+
+=back
+
+=head2 Mutators
+
+These are used for updating the values in objects, before calling
+C<update>.
=over
@@ -747,9 +1209,11 @@
the validator for this particular field. C<_set_global_validator> does not
return anything.
-
See L</VALIDATORS> for more information.
+B<NOTE>: This function is intended only for use by subclasses. If
+you call it from anywhere else, it will throw a C<CodeError>.
+
=item B<Params>
=over
@@ -765,6 +1229,27 @@
=back
+
+=item C<set_all>
+
+=over
+
+=item B<Description>
+
+This is a convenience function which is simpler than calling many different
+C<set_> functions in a row. You pass a hashref of parameters and it calls
+C<set_$key($value)> for every item in the hashref.
+
+=item B<Params>
+
+Takes a hashref of the fields that need to be set, pointing to the value
+that should be passed to the C<set_> function that is called.
+
+=item B<Returns> (nothing)
+
+=back
+
+
=back
=head2 Simple Validators
@@ -785,6 +1270,11 @@
=over
+=item C<any_exist>
+
+Returns C<1> if there are any of these objects in the database,
+C<0> otherwise.
+
=item C<get_all>
Description: Returns all objects in this table from the database.
diff --git a/Websites/bugs.webkit.org/Bugzilla/Product.pm b/Websites/bugs.webkit.org/Bugzilla/Product.pm
index 95a0e38..a0079a0 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Product.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Product.pm
@@ -13,22 +13,27 @@
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Tiago R. Mello <timello@async.com.br>
-
-use strict;
+# Frédéric Buclin <LpSolit@gmail.com>
package Bugzilla::Product;
-
-use Bugzilla::Version;
-use Bugzilla::Milestone;
+use strict;
+use base qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
use Bugzilla::Constants;
use Bugzilla::Util;
-use Bugzilla::Group;
use Bugzilla::Error;
-
+use Bugzilla::Group;
+use Bugzilla::Version;
+use Bugzilla::Milestone;
+use Bugzilla::Field;
+use Bugzilla::Status;
use Bugzilla::Install::Requirements;
+use Bugzilla::Mailer;
+use Bugzilla::Series;
+use Bugzilla::Hook;
+use Bugzilla::FlagType;
-use base qw(Bugzilla::Object);
+use Scalar::Util qw(blessed);
use constant DEFAULT_CLASSIFICATION_ID => 1;
@@ -39,27 +44,77 @@
use constant DB_TABLE => 'products';
use constant DB_COLUMNS => qw(
- products.id
- products.name
- products.classification_id
- products.description
- products.milestoneurl
- products.disallownew
- products.votesperuser
- products.maxvotesperbug
- products.votestoconfirm
- products.defaultmilestone
+ id
+ name
+ classification_id
+ description
+ isactive
+ defaultmilestone
+ allows_unconfirmed
);
+use constant UPDATE_COLUMNS => qw(
+ name
+ description
+ defaultmilestone
+ isactive
+ allows_unconfirmed
+);
+
+use constant VALIDATORS => {
+ allows_unconfirmed => \&Bugzilla::Object::check_boolean,
+ classification => \&_check_classification,
+ name => \&_check_name,
+ description => \&_check_description,
+ version => \&_check_version,
+ defaultmilestone => \&_check_default_milestone,
+ isactive => \&Bugzilla::Object::check_boolean,
+ create_series => \&Bugzilla::Object::check_boolean
+};
+
###############################
#### Constructors #####
###############################
+sub create {
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ $class->check_required_create_fields(@_);
+
+ my $params = $class->run_create_validators(@_);
+ # Some fields do not exist in the DB as is.
+ if (defined $params->{classification}) {
+ $params->{classification_id} = delete $params->{classification};
+ }
+ my $version = delete $params->{version};
+ my $create_series = delete $params->{create_series};
+
+ my $product = $class->insert_create_data($params);
+ Bugzilla->user->clear_product_cache();
+
+ # Add the new version and milestone into the DB as valid values.
+ Bugzilla::Version->create({ value => $version, product => $product });
+ Bugzilla::Milestone->create({ value => $product->default_milestone,
+ product => $product });
+
+ # Create groups and series for the new product, if requested.
+ $product->_create_bug_group() if Bugzilla->params->{'makeproductgroups'};
+ $product->_create_series() if $create_series;
+
+ Bugzilla::Hook::process('product_end_of_create', { product => $product });
+
+ $dbh->bz_commit_transaction();
+ return $product;
+}
+
# This is considerably faster than calling new_from_list three times
# for each product in the list, particularly with hundreds or thousands
# of products.
sub preload {
- my ($products) = @_;
+ my ($products, $preload_flagtypes) = @_;
my %prods = map { $_->id => $_ } @$products;
my @prod_ids = keys %prods;
return unless @prod_ids;
@@ -76,12 +131,417 @@
push(@{$prods{$product_id}->{"${field}s"}}, $obj);
}
}
+ if ($preload_flagtypes) {
+ $_->flag_types foreach @$products;
+ }
}
+sub update {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ # Don't update the DB if something goes wrong below -> transaction.
+ $dbh->bz_start_transaction();
+ my ($changes, $old_self) = $self->SUPER::update(@_);
+
+ # Also update group settings.
+ if ($self->{check_group_controls}) {
+ require Bugzilla::Bug;
+ import Bugzilla::Bug qw(LogActivityEntry);
+
+ my $old_settings = $old_self->group_controls;
+ my $new_settings = $self->group_controls;
+ my $timestamp = $dbh->selectrow_array('SELECT NOW()');
+
+ foreach my $gid (keys %$new_settings) {
+ my $old_setting = $old_settings->{$gid} || {};
+ my $new_setting = $new_settings->{$gid};
+ # If all new settings are 0 for a given group, we delete the entry
+ # from group_control_map, so we have to track it here.
+ my $all_zero = 1;
+ my @fields;
+ my @values;
+
+ foreach my $field ('entry', 'membercontrol', 'othercontrol', 'canedit',
+ 'editcomponents', 'editbugs', 'canconfirm')
+ {
+ my $old_value = $old_setting->{$field};
+ my $new_value = $new_setting->{$field};
+ $all_zero = 0 if $new_value;
+ next if (defined $old_value && $old_value == $new_value);
+ push(@fields, $field);
+ # The value has already been validated.
+ detaint_natural($new_value);
+ push(@values, $new_value);
+ }
+ # Is there anything to update?
+ next unless scalar @fields;
+
+ if ($all_zero) {
+ $dbh->do('DELETE FROM group_control_map
+ WHERE product_id = ? AND group_id = ?',
+ undef, $self->id, $gid);
+ }
+ else {
+ if (exists $old_setting->{group}) {
+ # There is already an entry in the DB.
+ my $set_fields = join(', ', map {"$_ = ?"} @fields);
+ $dbh->do("UPDATE group_control_map SET $set_fields
+ WHERE product_id = ? AND group_id = ?",
+ undef, (@values, $self->id, $gid));
+ }
+ else {
+ # No entry yet.
+ my $fields = join(', ', @fields);
+ # +2 because of the product and group IDs.
+ my $qmarks = join(',', ('?') x (scalar @fields + 2));
+ $dbh->do("INSERT INTO group_control_map (product_id, group_id, $fields)
+ VALUES ($qmarks)", undef, ($self->id, $gid, @values));
+ }
+ }
+
+ # If the group is mandatory, restrict all bugs to it.
+ if ($new_setting->{membercontrol} == CONTROLMAPMANDATORY) {
+ my $bug_ids =
+ $dbh->selectcol_arrayref('SELECT bugs.bug_id
+ FROM bugs
+ LEFT JOIN bug_group_map
+ ON bug_group_map.bug_id = bugs.bug_id
+ AND group_id = ?
+ WHERE product_id = ?
+ AND bug_group_map.bug_id IS NULL',
+ undef, $gid, $self->id);
+
+ if (scalar @$bug_ids) {
+ my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id)
+ VALUES (?, ?)');
+
+ foreach my $bug_id (@$bug_ids) {
+ $sth->execute($bug_id, $gid);
+ # Add this change to the bug history.
+ LogActivityEntry($bug_id, 'bug_group', '',
+ $new_setting->{group}->name,
+ Bugzilla->user->id, $timestamp);
+ }
+ push(@{$changes->{'_group_controls'}->{'now_mandatory'}},
+ {name => $new_setting->{group}->name,
+ bug_count => scalar @$bug_ids});
+ }
+ }
+ # If the group can no longer be used to restrict bugs, remove them.
+ elsif ($new_setting->{membercontrol} == CONTROLMAPNA) {
+ my $bug_ids =
+ $dbh->selectcol_arrayref('SELECT bugs.bug_id
+ FROM bugs
+ INNER JOIN bug_group_map
+ ON bug_group_map.bug_id = bugs.bug_id
+ WHERE product_id = ? AND group_id = ?',
+ undef, $self->id, $gid);
+
+ if (scalar @$bug_ids) {
+ $dbh->do('DELETE FROM bug_group_map WHERE group_id = ? AND ' .
+ $dbh->sql_in('bug_id', $bug_ids), undef, $gid);
+
+ # Add this change to the bug history.
+ foreach my $bug_id (@$bug_ids) {
+ LogActivityEntry($bug_id, 'bug_group',
+ $old_setting->{group}->name, '',
+ Bugzilla->user->id, $timestamp);
+ }
+ push(@{$changes->{'_group_controls'}->{'now_na'}},
+ {name => $old_setting->{group}->name,
+ bug_count => scalar @$bug_ids});
+ }
+ }
+ }
+
+ delete $self->{groups_available};
+ delete $self->{groups_mandatory};
+ }
+ $dbh->bz_commit_transaction();
+ # Changes have been committed.
+ delete $self->{check_group_controls};
+ Bugzilla->user->clear_product_cache();
+
+ return $changes;
+}
+
+sub remove_from_db {
+ my ($self, $params) = @_;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+
+ $self->_check_if_controller();
+
+ if ($self->bug_count) {
+ if (Bugzilla->params->{'allowbugdeletion'}) {
+ require Bugzilla::Bug;
+ foreach my $bug_id (@{$self->bug_ids}) {
+ # Note that we allow the user to delete bugs he can't see,
+ # which is okay, because he's deleting the whole Product.
+ my $bug = new Bugzilla::Bug($bug_id);
+ $bug->remove_from_db();
+ }
+ }
+ else {
+ ThrowUserError('product_has_bugs', { nb => $self->bug_count });
+ }
+ }
+
+ if ($params->{delete_series}) {
+ my $series_ids =
+ $dbh->selectcol_arrayref('SELECT series_id
+ FROM series
+ INNER JOIN series_categories
+ ON series_categories.id = series.category
+ WHERE series_categories.name = ?',
+ undef, $self->name);
+
+ if (scalar @$series_ids) {
+ $dbh->do('DELETE FROM series WHERE ' . $dbh->sql_in('series_id', $series_ids));
+ }
+
+ # If no subcategory uses this product name, completely purge it.
+ my $in_use =
+ $dbh->selectrow_array('SELECT 1
+ FROM series
+ INNER JOIN series_categories
+ ON series_categories.id = series.subcategory
+ WHERE series_categories.name = ? ' .
+ $dbh->sql_limit(1),
+ undef, $self->name);
+ if (!$in_use) {
+ $dbh->do('DELETE FROM series_categories WHERE name = ?', undef, $self->name);
+ }
+ }
+
+ $dbh->do("DELETE FROM products WHERE id = ?", undef, $self->id);
+
+ $dbh->bz_commit_transaction();
+
+ # We have to delete these internal variables, else we get
+ # the old lists of products and classifications again.
+ delete $user->{selectable_products};
+ delete $user->{selectable_classifications};
+
+}
+
+###############################
+#### Validators ####
+###############################
+
+sub _check_classification {
+ my ($invocant, $classification_name) = @_;
+
+ my $classification_id = 1;
+ if (Bugzilla->params->{'useclassification'}) {
+ my $classification = Bugzilla::Classification->check($classification_name);
+ $classification_id = $classification->id;
+ }
+ return $classification_id;
+}
+
+sub _check_name {
+ my ($invocant, $name) = @_;
+
+ $name = trim($name);
+ $name || ThrowUserError('product_blank_name');
+
+ if (length($name) > MAX_PRODUCT_SIZE) {
+ ThrowUserError('product_name_too_long', {'name' => $name});
+ }
+
+ my $product = new Bugzilla::Product({name => $name});
+ if ($product && (!ref $invocant || $product->id != $invocant->id)) {
+ # Check for exact case sensitive match:
+ if ($product->name eq $name) {
+ ThrowUserError('product_name_already_in_use', {'product' => $product->name});
+ }
+ else {
+ ThrowUserError('product_name_diff_in_case', {'product' => $name,
+ 'existing_product' => $product->name});
+ }
+ }
+ return $name;
+}
+
+sub _check_description {
+ my ($invocant, $description) = @_;
+
+ $description = trim($description);
+ $description || ThrowUserError('product_must_have_description');
+ return $description;
+}
+
+sub _check_version {
+ my ($invocant, $version) = @_;
+
+ $version = trim($version);
+ $version || ThrowUserError('product_must_have_version');
+ # We will check the version length when Bugzilla::Version->create will do it.
+ return $version;
+}
+
+sub _check_default_milestone {
+ my ($invocant, $milestone) = @_;
+
+ # Do nothing if target milestones are not in use.
+ unless (Bugzilla->params->{'usetargetmilestone'}) {
+ return (ref $invocant) ? $invocant->default_milestone : '---';
+ }
+
+ $milestone = trim($milestone);
+
+ if (ref $invocant) {
+ # The default milestone must be one of the existing milestones.
+ my $mil_obj = new Bugzilla::Milestone({name => $milestone, product => $invocant});
+
+ $mil_obj || ThrowUserError('product_must_define_defaultmilestone',
+ {product => $invocant->name,
+ milestone => $milestone});
+ }
+ else {
+ $milestone ||= '---';
+ }
+ return $milestone;
+}
+
+sub _check_milestone_url {
+ my ($invocant, $url) = @_;
+
+ # Do nothing if target milestones are not in use.
+ unless (Bugzilla->params->{'usetargetmilestone'}) {
+ return (ref $invocant) ? $invocant->milestone_url : '';
+ }
+
+ $url = trim($url || '');
+ return $url;
+}
+
+#####################################
+# Implement Bugzilla::Field::Choice #
+#####################################
+
+use constant FIELD_NAME => 'product';
+use constant is_default => 0;
+
###############################
#### Methods ####
###############################
+sub _create_bug_group {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $group_name = $self->name;
+ while (new Bugzilla::Group({name => $group_name})) {
+ $group_name .= '_';
+ }
+ my $group_description = get_text('bug_group_description', {product => $self});
+
+ my $group = Bugzilla::Group->create({name => $group_name,
+ description => $group_description,
+ isbuggroup => 1});
+
+ # Associate the new group and new product.
+ $dbh->do('INSERT INTO group_control_map
+ (group_id, product_id, membercontrol, othercontrol)
+ VALUES (?, ?, ?, ?)',
+ undef, ($group->id, $self->id, CONTROLMAPDEFAULT, CONTROLMAPNA));
+}
+
+sub _create_series {
+ my $self = shift;
+
+ my @series;
+ # We do every status, every resolution, and an "opened" one as well.
+ foreach my $bug_status (@{get_legal_field_values('bug_status')}) {
+ push(@series, [$bug_status, "bug_status=" . url_quote($bug_status)]);
+ }
+
+ foreach my $resolution (@{get_legal_field_values('resolution')}) {
+ next if !$resolution;
+ push(@series, [$resolution, "resolution=" . url_quote($resolution)]);
+ }
+
+ my @openedstatuses = BUG_STATE_OPEN;
+ my $query = join("&", map { "bug_status=" . url_quote($_) } @openedstatuses);
+ push(@series, [get_text('series_all_open'), $query]);
+
+ foreach my $sdata (@series) {
+ my $series = new Bugzilla::Series(undef, $self->name,
+ get_text('series_subcategory'),
+ $sdata->[0], Bugzilla->user->id, 1,
+ $sdata->[1] . "&product=" . url_quote($self->name), 1);
+ $series->writeToDatabase();
+ }
+}
+
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_default_milestone { $_[0]->set('defaultmilestone', $_[1]); }
+sub set_is_active { $_[0]->set('isactive', $_[1]); }
+sub set_allows_unconfirmed { $_[0]->set('allows_unconfirmed', $_[1]); }
+
+sub set_group_controls {
+ my ($self, $group, $settings) = @_;
+
+ $group->is_active_bug_group
+ || ThrowUserError('product_illegal_group', {group => $group});
+
+ scalar(keys %$settings)
+ || ThrowCodeError('product_empty_group_controls', {group => $group});
+
+ # We store current settings for this group.
+ my $gs = $self->group_controls->{$group->id};
+ # If there is no entry for this group yet, create a default hash.
+ unless (defined $gs) {
+ $gs = { entry => 0,
+ membercontrol => CONTROLMAPNA,
+ othercontrol => CONTROLMAPNA,
+ canedit => 0,
+ editcomponents => 0,
+ editbugs => 0,
+ canconfirm => 0,
+ group => $group };
+ }
+
+ # Both settings must be defined, or none of them can be updated.
+ if (defined $settings->{membercontrol} && defined $settings->{othercontrol}) {
+ # Legality of control combination is a function of
+ # membercontrol\othercontrol
+ # NA SH DE MA
+ # NA + - - -
+ # SH + + + +
+ # DE + - + +
+ # MA - - - +
+ foreach my $field ('membercontrol', 'othercontrol') {
+ my ($is_legal) = grep { $settings->{$field} == $_ }
+ (CONTROLMAPNA, CONTROLMAPSHOWN, CONTROLMAPDEFAULT, CONTROLMAPMANDATORY);
+ defined $is_legal || ThrowCodeError('product_illegal_group_control',
+ { field => $field, value => $settings->{$field} });
+ }
+ unless ($settings->{membercontrol} == $settings->{othercontrol}
+ || $settings->{membercontrol} == CONTROLMAPSHOWN
+ || ($settings->{membercontrol} == CONTROLMAPDEFAULT
+ && $settings->{othercontrol} != CONTROLMAPSHOWN))
+ {
+ ThrowUserError('illegal_group_control_combination', {groupname => $group->name});
+ }
+ $gs->{membercontrol} = $settings->{membercontrol};
+ $gs->{othercontrol} = $settings->{othercontrol};
+ }
+
+ foreach my $field ('entry', 'canedit', 'editcomponents', 'editbugs', 'canconfirm') {
+ next unless defined $settings->{$field};
+ $gs->{$field} = $settings->{$field} ? 1 : 0;
+ }
+ $self->{group_controls}->{$group->id} = $gs;
+ $self->{check_group_controls} = 1;
+}
+
sub components {
my $self = shift;
my $dbh = Bugzilla->dbh;
@@ -99,25 +559,33 @@
}
sub group_controls {
- my $self = shift;
+ my ($self, $full_data) = @_;
my $dbh = Bugzilla->dbh;
- if (!defined $self->{group_controls}) {
- my $query = qq{SELECT
- groups.id,
- group_control_map.entry,
- group_control_map.membercontrol,
- group_control_map.othercontrol,
- group_control_map.canedit,
- group_control_map.editcomponents,
- group_control_map.editbugs,
- group_control_map.canconfirm
- FROM groups
- LEFT JOIN group_control_map
- ON groups.id = group_control_map.group_id
- WHERE group_control_map.product_id = ?
- AND groups.isbuggroup != 0
- ORDER BY groups.name};
+ # By default, we don't return groups which are not listed in
+ # group_control_map. If $full_data is true, then we also
+ # return groups whose settings could be set for the product.
+ my $where_or_and = 'WHERE';
+ my $and_or_where = 'AND';
+ if ($full_data) {
+ $where_or_and = 'AND';
+ $and_or_where = 'WHERE';
+ }
+
+ # If $full_data is true, we collect all the data in all cases,
+ # even if the cache is already populated.
+ # $full_data is never used except in the very special case where
+ # all configurable bug groups are displayed to administrators,
+ # so we don't care about collecting all the data again in this case.
+ if (!defined $self->{group_controls} || $full_data) {
+ # Include name to the list, to allow us sorting data more easily.
+ my $query = qq{SELECT id, name, entry, membercontrol, othercontrol,
+ canedit, editcomponents, editbugs, canconfirm
+ FROM groups
+ LEFT JOIN group_control_map
+ ON id = group_id
+ $where_or_and product_id = ?
+ $and_or_where isbuggroup = 1};
$self->{group_controls} =
$dbh->selectall_hashref($query, 'id', undef, $self->id);
@@ -126,24 +594,105 @@
my $groups = Bugzilla::Group->new_from_list(\@gids);
$self->{group_controls}->{$_->id}->{group} = $_ foreach @$groups;
}
+
+ # We never cache bug counts, for the same reason as above.
+ if ($full_data) {
+ my $counts =
+ $dbh->selectall_arrayref('SELECT group_id, COUNT(bugs.bug_id) AS bug_count
+ FROM bug_group_map
+ INNER JOIN bugs
+ ON bugs.bug_id = bug_group_map.bug_id
+ WHERE bugs.product_id = ? ' .
+ $dbh->sql_group_by('group_id'),
+ {'Slice' => {}}, $self->id);
+ foreach my $data (@$counts) {
+ $self->{group_controls}->{$data->{group_id}}->{bug_count} = $data->{bug_count};
+ }
+ }
return $self->{group_controls};
}
-sub groups_mandatory_for {
- my ($self, $user) = @_;
- my $groups = $user->groups_as_string;
+sub groups_available {
+ my ($self) = @_;
+ return $self->{groups_available} if defined $self->{groups_available};
+ my $dbh = Bugzilla->dbh;
+ my $shown = CONTROLMAPSHOWN;
+ my $default = CONTROLMAPDEFAULT;
+ my %member_groups = @{ $dbh->selectcol_arrayref(
+ "SELECT group_id, membercontrol
+ FROM group_control_map
+ INNER JOIN groups ON group_control_map.group_id = groups.id
+ WHERE isbuggroup = 1 AND isactive = 1 AND product_id = ?
+ AND (membercontrol = $shown OR membercontrol = $default)
+ AND " . Bugzilla->user->groups_in_sql(),
+ {Columns=>[1,2]}, $self->id) };
+ # We don't need to check the group membership here, because we only
+ # add these groups to the list below if the group isn't already listed
+ # for membercontrol.
+ my %other_groups = @{ $dbh->selectcol_arrayref(
+ "SELECT group_id, othercontrol
+ FROM group_control_map
+ INNER JOIN groups ON group_control_map.group_id = groups.id
+ WHERE isbuggroup = 1 AND isactive = 1 AND product_id = ?
+ AND (othercontrol = $shown OR othercontrol = $default)",
+ {Columns=>[1,2]}, $self->id) };
+
+ # If the user is a member, then we use the membercontrol value.
+ # Otherwise, we use the othercontrol value.
+ my %all_groups = %member_groups;
+ foreach my $id (keys %other_groups) {
+ if (!defined $all_groups{$id}) {
+ $all_groups{$id} = $other_groups{$id};
+ }
+ }
+
+ my $available = Bugzilla::Group->new_from_list([keys %all_groups]);
+ foreach my $group (@$available) {
+ $group->{is_default} = 1 if $all_groups{$group->id} == $default;
+ }
+
+ $self->{groups_available} = $available;
+ return $self->{groups_available};
+}
+
+sub groups_mandatory {
+ my ($self) = @_;
+ return $self->{groups_mandatory} if $self->{groups_mandatory};
+ my $groups = Bugzilla->user->groups_as_string;
my $mandatory = CONTROLMAPMANDATORY;
# For membercontrol we don't check group_id IN, because if membercontrol
# is Mandatory, the group is Mandatory for everybody, regardless of their
# group membership.
my $ids = Bugzilla->dbh->selectcol_arrayref(
- "SELECT group_id FROM group_control_map
- WHERE product_id = ?
+ "SELECT group_id
+ FROM group_control_map
+ INNER JOIN groups ON group_control_map.group_id = groups.id
+ WHERE product_id = ? AND isactive = 1
AND (membercontrol = $mandatory
OR (othercontrol = $mandatory
AND group_id NOT IN ($groups)))",
undef, $self->id);
- return Bugzilla::Group->new_from_list($ids);
+ $self->{groups_mandatory} = Bugzilla::Group->new_from_list($ids);
+ return $self->{groups_mandatory};
+}
+
+# We don't just check groups_valid, because we want to know specifically
+# if this group can be validly set by the currently-logged-in user.
+sub group_is_settable {
+ my ($self, $group) = @_;
+
+ return 0 unless ($group->is_active && $group->is_bug_group);
+
+ my $is_mandatory = grep { $group->id == $_->id }
+ @{ $self->groups_mandatory };
+ my $is_available = grep { $group->id == $_->id }
+ @{ $self->groups_available };
+ return ($is_mandatory or $is_available) ? 1 : 0;
+}
+
+sub group_is_valid {
+ my ($self, $group) = @_;
+ return grep($_->id == $group->id, @{ $self->groups_valid }) ? 1 : 0;
}
sub groups_valid {
@@ -232,32 +781,53 @@
sub flag_types {
my $self = shift;
- if (!defined $self->{'flag_types'}) {
- $self->{'flag_types'} = {};
- foreach my $type ('bug', 'attachment') {
- my %flagtypes;
- foreach my $component (@{$self->components}) {
- foreach my $flagtype (@{$component->flag_types->{$type}}) {
- $flagtypes{$flagtype->{'id'}} ||= $flagtype;
- }
+ return $self->{'flag_types'} if defined $self->{'flag_types'};
+
+ # We cache flag types to avoid useless calls to get_clusions().
+ my $cache = Bugzilla->request_cache->{flag_types_per_product} ||= {};
+ $self->{flag_types} = {};
+ my $prod_id = $self->id;
+ my $flagtypes = Bugzilla::FlagType::match({ product_id => $prod_id });
+
+ foreach my $type ('bug', 'attachment') {
+ my @flags = grep { $_->target_type eq $type } @$flagtypes;
+ $self->{flag_types}->{$type} = \@flags;
+
+ # Also populate component flag types, while we are here.
+ foreach my $comp (@{$self->components}) {
+ $comp->{flag_types} ||= {};
+ my $comp_id = $comp->id;
+
+ foreach my $flag (@flags) {
+ my $flag_id = $flag->id;
+ $cache->{$flag_id} ||= $flag;
+ my $i = $cache->{$flag_id}->inclusions_as_hash;
+ my $e = $cache->{$flag_id}->exclusions_as_hash;
+ my $included = $i->{0}->{0} || $i->{0}->{$comp_id}
+ || $i->{$prod_id}->{0} || $i->{$prod_id}->{$comp_id};
+ my $excluded = $e->{0}->{0} || $e->{0}->{$comp_id}
+ || $e->{$prod_id}->{0} || $e->{$prod_id}->{$comp_id};
+ push(@{$comp->{flag_types}->{$type}}, $flag) if ($included && !$excluded);
}
- $self->{'flag_types'}->{$type} = [sort { $a->{'sortkey'} <=> $b->{'sortkey'}
- || $a->{'name'} cmp $b->{'name'} } values %flagtypes];
}
}
return $self->{'flag_types'};
}
+sub classification {
+ my $self = shift;
+ $self->{'classification'} ||=
+ new Bugzilla::Classification($self->classification_id);
+ return $self->{'classification'};
+}
+
###############################
#### Accessors ######
###############################
+sub allows_unconfirmed { return $_[0]->{'allows_unconfirmed'}; }
sub description { return $_[0]->{'description'}; }
-sub milestone_url { return $_[0]->{'milestoneurl'}; }
-sub disallow_new { return $_[0]->{'disallownew'}; }
-sub votes_per_user { return $_[0]->{'votesperuser'}; }
-sub max_votes_per_bug { return $_[0]->{'maxvotesperbug'}; }
-sub votes_to_confirm { return $_[0]->{'votestoconfirm'}; }
+sub is_active { return $_[0]->{'isactive'}; }
sub default_milestone { return $_[0]->{'defaultmilestone'}; }
sub classification_id { return $_[0]->{'classification_id'}; }
@@ -265,16 +835,18 @@
#### Subroutines ######
###############################
-sub check_product {
- my ($product_name) = @_;
-
- unless ($product_name) {
- ThrowUserError('product_not_specified');
+sub check {
+ my ($class, $params) = @_;
+ $params = { name => $params } if !ref $params;
+ if (!$params->{allow_inaccessible}) {
+ $params->{_error} = 'product_access_denied';
}
- my $product = new Bugzilla::Product({name => $product_name});
- unless ($product) {
- ThrowUserError('product_doesnt_exist',
- {'product' => $product_name});
+ my $product = $class->SUPER::check($params);
+
+ if (!$params->{allow_inaccessible}
+ && !Bugzilla->user->can_access_product($product))
+ {
+ ThrowUserError('product_access_denied', $params);
}
return $product;
}
@@ -302,17 +874,15 @@
my $bug_ids = $product->bug_ids();
my $has_access = $product->user_has_access($user);
my $flag_types = $product->flag_types();
+ my $classification = $product->classification();
my $id = $product->id;
my $name = $product->name;
my $description = $product->description;
- my $milestoneurl = $product->milestone_url;
- my disallownew = $product->disallow_new;
- my votesperuser = $product->votes_per_user;
- my maxvotesperbug = $product->max_votes_per_bug;
- my votestoconfirm = $product->votes_to_confirm;
+ my isactive = $product->is_active;
my $defaultmilestone = $product->default_milestone;
my $classificationid = $product->classification_id;
+ my $allows_unconfirmed = $product->allows_unconfirmed;
=head1 DESCRIPTION
@@ -341,28 +911,56 @@
Description: Returns a hash (group id as key) with all product
group controls.
- Params: none.
+ Params: $full_data (optional, false by default) - when true,
+ the number of bugs per group applicable to the product
+ is also returned. Moreover, bug groups which have no
+ special settings for the product are also returned.
Returns: A hash with group id as key and hash containing
a Bugzilla::Group object and the properties of group
relative to the product.
-=item C<groups_mandatory_for>
+=item C<groups_available>
+
+Tells you what groups are set to Default or Shown for the
+currently-logged-in user (taking into account both OtherControl and
+MemberControl). Returns an arrayref of L<Bugzilla::Group> objects with
+an extra hash keys set, C<is_default>, which is true if the group
+is set to Default for the currently-logged-in user.
+
+=item C<groups_mandatory>
+
+Tells you what groups are mandatory for bugs in this product, for the
+currently-logged-in user. Returns an arrayref of C<Bugzilla::Group> objects.
+
+=item C<group_is_settable>
=over
=item B<Description>
-Tells you what groups are mandatory for bugs in this product.
+Tells you whether or not the currently-logged-in user can set a group
+on a bug (whether or not they match the MemberControl/OtherControl
+settings for a group in this product). Groups that are C<Mandatory> for
+the currently-loggeed-in user are also acceptable since from Bugzilla's
+perspective, there's no problem with "setting" a Mandatory group on
+a bug. (In fact, the user I<must> set the Mandatory group on the bug.)
=item B<Params>
-C<$user> - The user who you want to check.
+=over
-=item B<Returns> An arrayref of C<Bugzilla::Group> objects.
+=item C<$group> - A L<Bugzilla::Group> object.
=back
+=item B<Returns>
+
+C<1> if the group is valid in this product, C<0> otherwise.
+
+=back
+
+
=item C<groups_valid>
=over
@@ -371,7 +969,9 @@
Returns an arrayref of L<Bugzilla::Group> objects, representing groups
that bugs could validly be restricted to within this product. Used mostly
-by L<Bugzilla::Bug> to assure that you're adding valid groups to a bug.
+when you need the list of all possible groups that could be set in a product
+by anybody, disregarding whether or not the groups are active or who the
+currently logged-in user is.
B<Note>: This doesn't check whether or not the current user can add/remove
bugs to/from these groups. It just tells you that bugs I<could be in> these
@@ -383,6 +983,13 @@
=back
+=item C<group_is_valid>
+
+Returns C<1> if the passed-in L<Bugzilla::Group> or group id could be set
+on a bug by I<anybody>, in this product. Even inactive groups are considered
+valid. (This is a shortcut for searching L</groups_valid> to find out if
+a group is valid in a particular product.)
+
=item C<versions>
Description: Returns all valid versions for that product.
@@ -436,6 +1043,14 @@
Returns: Two references to an array of flagtype objects.
+=item C<classification()>
+
+ Description: Returns the classification the product belongs to.
+
+ Params: none.
+
+ Returns: A Bugzilla::Classification object.
+
=back
=head1 SUBROUTINES
@@ -448,18 +1063,12 @@
L</milestones>, L</components>, and L</versions>, which is much faster
than calling those accessors on every item in the array individually.
+If the 2nd argument passed to C<preload> is true, flag types for these
+products and their components are also preloaded.
+
This function is not exported, so must be called like
C<Bugzilla::Product::preload($products)>.
-=item C<check_product($product_name)>
-
- Description: Checks if the product name was passed in and if is a valid
- product.
-
- Params: $product_name - String with a product name.
-
- Returns: Bugzilla::Product object.
-
=back
=head1 SEE ALSO
diff --git a/Websites/bugs.webkit.org/Bugzilla/RNG.pm b/Websites/bugs.webkit.org/Bugzilla/RNG.pm
new file mode 100644
index 0000000..caa63ba
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/RNG.pm
@@ -0,0 +1,233 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Google Inc.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::RNG;
+use strict;
+use base qw(Exporter);
+use Bugzilla::Constants qw(ON_WINDOWS);
+
+use IO::File;
+use Math::Random::ISAAC;
+use if ON_WINDOWS, 'Win32::API';
+
+our $RNG;
+our @EXPORT_OK = qw(rand srand irand);
+
+# ISAAC, a 32-bit generator, should only be capable of generating numbers
+# between 0 and 2^32 - 1. We want _to_float to generate numbers possibly
+# including 0, but always less than 1.0. Dividing the integer produced
+# by irand() by this number should do that exactly.
+use constant DIVIDE_BY => 2**32;
+
+# How many bytes of seed to read.
+use constant SEED_SIZE => 16; # 128 bits.
+
+#################
+# Windows Stuff #
+#################
+
+# The type of cryptographic service provider we want to use.
+# This doesn't really matter for our purposes, so we just pick
+# PROV_RSA_FULL, which seems reasonable. For more info, see
+# http://msdn.microsoft.com/en-us/library/aa380244(v=VS.85).aspx
+use constant PROV_RSA_FULL => 1;
+
+# Flags for CryptGenRandom:
+# Don't ever display a UI to the user, just fail if one would be needed.
+use constant CRYPT_SILENT => 64;
+# Don't require existing public/private keypairs.
+use constant CRYPT_VERIFYCONTEXT => 0xF0000000;
+
+# For some reason, BOOLEAN doesn't work properly as a return type with
+# Win32::API.
+use constant RTLGENRANDOM_PROTO => <<END;
+INT SystemFunction036(
+ PVOID RandomBuffer,
+ ULONG RandomBufferLength
+)
+END
+
+#################
+# RNG Functions #
+#################
+
+sub rand (;$) {
+ my ($limit) = @_;
+ my $int = irand();
+ return _to_float($int, $limit);
+}
+
+sub irand (;$) {
+ my ($limit) = @_;
+ Bugzilla::RNG::srand() if !defined $RNG;
+ my $int = $RNG->irand();
+ if (defined $limit) {
+ # We can't just use the mod operator because it will bias
+ # our output. Search for "modulo bias" on the Internet for
+ # details. This is slower than mod(), but does not have a bias,
+ # as demonstrated by Math::Random::Secure's uniform.t test.
+ return int(_to_float($int, $limit));
+ }
+ return $int;
+}
+
+sub srand (;$) {
+ my ($value) = @_;
+ # Remove any RNG that might already have been made.
+ $RNG = undef;
+ my %args;
+ if (defined $value) {
+ $args{seed} = $value;
+ }
+ $RNG = _create_rng(\%args);
+}
+
+sub _to_float {
+ my ($integer, $limit) = @_;
+ $limit ||= 1;
+ return ($integer / DIVIDE_BY) * $limit;
+}
+
+##########################
+# Seed and PRNG Creation #
+##########################
+
+sub _create_rng {
+ my ($params) = @_;
+
+ if (!defined $params->{seed}) {
+ $params->{seed} = _get_seed();
+ }
+
+ _check_seed($params->{seed});
+
+ my @seed_ints = unpack('L*', $params->{seed});
+
+ my $rng = Math::Random::ISAAC->new(@seed_ints);
+
+ # It's faster to skip the frontend interface of Math::Random::ISAAC
+ # and just use the backend directly. However, in case the internal
+ # code of Math::Random::ISAAC changes at some point, we do make sure
+ # that the {backend} element actually exists first.
+ return $rng->{backend} ? $rng->{backend} : $rng;
+}
+
+sub _check_seed {
+ my ($seed) = @_;
+ if (length($seed) < 8) {
+ warn "Your seed is less than 8 bytes (64 bits). It could be"
+ . " easy to crack";
+ }
+ # If it looks like we were seeded with a 32-bit integer, warn the
+ # user that they are making a dangerous, easily-crackable mistake.
+ elsif (length($seed) <= 10 and $seed =~ /^\d+$/) {
+ warn "RNG seeded with a 32-bit integer, this is easy to crack";
+ }
+}
+
+sub _get_seed {
+ return _windows_seed() if ON_WINDOWS;
+
+ if (-r '/dev/urandom') {
+ return _read_seed_from('/dev/urandom');
+ }
+
+ return _read_seed_from('/dev/random');
+}
+
+sub _read_seed_from {
+ my ($from) = @_;
+
+ my $fh = IO::File->new($from, "r") or die "$from: $!";
+ my $buffer;
+ $fh->read($buffer, SEED_SIZE);
+ if (length($buffer) < SEED_SIZE) {
+ die "Could not read enough seed bytes from $from, got only "
+ . length($buffer);
+ }
+ $fh->close;
+ return $buffer;
+}
+
+sub _windows_seed {
+ my ($major, $minor) = (Win32::GetOSVersion())[1,2];
+ if ($major < 5) {
+ die "Bugzilla does not support versions of Windows before"
+ . " Windows 2000";
+ }
+ # This means Windows 2000.
+ if ($major == 5 and $minor == 0) {
+ return _win2k_seed();
+ }
+
+ my $rtlgenrand = Win32::API->new('advapi32', RTLGENRANDOM_PROTO);
+ if (!defined $rtlgenrand) {
+ die "Could not import RtlGenRand: $^E";
+ }
+ my $buffer = chr(0) x SEED_SIZE;
+ my $result = $rtlgenrand->Call($buffer, SEED_SIZE);
+ if (!$result) {
+ die "RtlGenRand failed: $^E";
+ }
+ return $buffer;
+}
+
+sub _win2k_seed {
+ my $crypt_acquire = Win32::API->new(
+ "advapi32", 'CryptAcquireContext', 'PPPNN', 'I');
+ if (!defined $crypt_acquire) {
+ die "Could not import CryptAcquireContext: $^E";
+ }
+
+ my $crypt_release = Win32::API->new(
+ "advapi32", 'CryptReleaseContext', 'NN', 'I');
+ if (!defined $crypt_release) {
+ die "Could not import CryptReleaseContext: $^E";
+ }
+
+ my $crypt_gen_random = Win32::API->new(
+ "advapi32", 'CryptGenRandom', 'NNP', 'I');
+ if (!defined $crypt_gen_random) {
+ die "Could not import CryptGenRandom: $^E";
+ }
+
+ my $context = chr(0) x Win32::API::Type->sizeof('PULONG');
+ my $acquire_result = $crypt_acquire->Call(
+ $context, 0, 0, PROV_RSA_FULL, CRYPT_SILENT | CRYPT_VERIFYCONTEXT);
+ if (!defined $acquire_result) {
+ die "CryptAcquireContext failed: $^E";
+ }
+
+ my $pack_type = Win32::API::Type::packing('PULONG');
+ $context = unpack($pack_type, $context);
+
+ my $buffer = chr(0) x SEED_SIZE;
+ my $rand_result = $crypt_gen_random->Call($context, SEED_SIZE, $buffer);
+ my $rand_error = $^E;
+ # We don't check this if it fails, we don't care.
+ $crypt_release->Call($context, 0);
+ if (!defined $rand_result) {
+ die "CryptGenRandom failed: $rand_error";
+ }
+ return $buffer;
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Search.pm b/Websites/bugs.webkit.org/Bugzilla/Search.pm
index 499cc07..1097b32 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Search.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Search.pm
@@ -28,12 +28,18 @@
# Joel Peshkin <bugreport@peshkin.net>
# Lance Larsh <lance.larsh@oracle.com>
# Jesse Clark <jjclark1982@gmail.com>
+# Rémi Zara <remi_zara@mac.com>
+# Reed Loden <reed@reedloden.com>
use strict;
package Bugzilla::Search;
use base qw(Exporter);
-@Bugzilla::Search::EXPORT = qw(IsValidQueryType);
+@Bugzilla::Search::EXPORT = qw(
+ IsValidQueryType
+ split_order_term
+ translate_old_column
+);
use Bugzilla::Error;
use Bugzilla::Util;
@@ -41,521 +47,26 @@
use Bugzilla::Group;
use Bugzilla::User;
use Bugzilla::Field;
+use Bugzilla::Search::Clause;
+use Bugzilla::Search::Condition qw(condition);
use Bugzilla::Status;
use Bugzilla::Keyword;
+use Data::Dumper;
use Date::Format;
use Date::Parse;
+use Scalar::Util qw(blessed);
+use List::MoreUtils qw(all part uniq);
+use POSIX qw(INT_MAX);
+use Storable qw(dclone);
-# Some fields are not sorted on themselves, but on other fields.
-# We need to have a list of these fields and what they map to.
-# Each field points to an array that contains the fields mapped
-# to, in order.
-use constant SPECIAL_ORDER => {
- 'bugs.target_milestone' => [ 'ms_order.sortkey','ms_order.value' ],
- 'bugs.bug_status' => [ 'bug_status.sortkey','bug_status.value' ],
- 'bugs.rep_platform' => [ 'rep_platform.sortkey','rep_platform.value' ],
- 'bugs.priority' => [ 'priority.sortkey','priority.value' ],
- 'bugs.op_sys' => [ 'op_sys.sortkey','op_sys.value' ],
- 'bugs.resolution' => [ 'resolution.sortkey', 'resolution.value' ],
- 'bugs.bug_severity' => [ 'bug_severity.sortkey','bug_severity.value' ]
-};
-
-# When we add certain fields to the ORDER BY, we need to then add a
-# table join to the FROM statement. This hash maps input fields to
-# the join statements that need to be added.
-use constant SPECIAL_ORDER_JOIN => {
- 'bugs.target_milestone' => 'LEFT JOIN milestones AS ms_order ON ms_order.value = bugs.target_milestone AND ms_order.product_id = bugs.product_id',
- 'bugs.bug_status' => 'LEFT JOIN bug_status ON bug_status.value = bugs.bug_status',
- 'bugs.rep_platform' => 'LEFT JOIN rep_platform ON rep_platform.value = bugs.rep_platform',
- 'bugs.priority' => 'LEFT JOIN priority ON priority.value = bugs.priority',
- 'bugs.op_sys' => 'LEFT JOIN op_sys ON op_sys.value = bugs.op_sys',
- 'bugs.resolution' => 'LEFT JOIN resolution ON resolution.value = bugs.resolution',
- 'bugs.bug_severity' => 'LEFT JOIN bug_severity ON bug_severity.value = bugs.bug_severity'
-};
-
-# Create a new Search
-# Note that the param argument may be modified by Bugzilla::Search
-sub new {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
-
- my $self = { @_ };
- bless($self, $class);
-
- $self->init();
-
- return $self;
-}
-
-sub init {
- my $self = shift;
- my $fieldsref = $self->{'fields'};
- my $params = $self->{'params'};
- $self->{'user'} ||= Bugzilla->user;
- my $user = $self->{'user'};
-
- my $orderref = $self->{'order'} || 0;
- my @inputorder;
- @inputorder = @$orderref if $orderref;
- my @orderby;
-
- my $debug = 0;
- my @debugdata;
- if ($params->param('debug')) { $debug = 1; }
-
- my @fields;
- my @supptables;
- my @wherepart;
- my @having;
- my @groupby;
- @fields = @$fieldsref if $fieldsref;
- my @specialchart;
- my @andlist;
- my %chartfields;
-
- my %special_order = %{SPECIAL_ORDER()};
- my %special_order_join = %{SPECIAL_ORDER_JOIN()};
-
- my @select_fields = Bugzilla->get_fields({ type => FIELD_TYPE_SINGLE_SELECT,
- obsolete => 0 });
-
- my @multi_select_fields = Bugzilla->get_fields({ type => FIELD_TYPE_MULTI_SELECT,
- obsolete => 0 });
- foreach my $field (@select_fields) {
- my $name = $field->name;
- $special_order{"bugs.$name"} = [ "$name.sortkey", "$name.value" ],
- $special_order_join{"bugs.$name"} =
- "LEFT JOIN $name ON $name.value = bugs.$name";
- }
-
- my $dbh = Bugzilla->dbh;
-
- # First, deal with all the old hard-coded non-chart-based poop.
- if (grep(/map_assigned_to/, @$fieldsref)) {
- push @supptables, "INNER JOIN profiles AS map_assigned_to " .
- "ON bugs.assigned_to = map_assigned_to.userid";
- }
-
- if (grep(/map_reporter/, @$fieldsref)) {
- push @supptables, "INNER JOIN profiles AS map_reporter " .
- "ON bugs.reporter = map_reporter.userid";
- }
-
- if (grep(/map_qa_contact/, @$fieldsref)) {
- push @supptables, "LEFT JOIN profiles AS map_qa_contact " .
- "ON bugs.qa_contact = map_qa_contact.userid";
- }
-
- if (lsearch($fieldsref, 'map_products.name') >= 0) {
- push @supptables, "INNER JOIN products AS map_products " .
- "ON bugs.product_id = map_products.id";
- }
-
- if (lsearch($fieldsref, 'map_classifications.name') >= 0) {
- push @supptables, "INNER JOIN products AS map_products " .
- "ON bugs.product_id = map_products.id";
- push @supptables,
- "INNER JOIN classifications AS map_classifications " .
- "ON map_products.classification_id = map_classifications.id";
- }
-
- if (lsearch($fieldsref, 'map_components.name') >= 0) {
- push @supptables, "INNER JOIN components AS map_components " .
- "ON bugs.component_id = map_components.id";
- }
-
- if (grep($_ =~/AS (actual_time|percentage_complete)$/, @$fieldsref)) {
- push(@supptables, "LEFT JOIN longdescs AS ldtime " .
- "ON ldtime.bug_id = bugs.bug_id");
- }
-
- my $minvotes;
- if (defined $params->param('votes')) {
- my $c = trim($params->param('votes'));
- if ($c ne "") {
- if ($c !~ /^[0-9]*$/) {
- ThrowUserError("illegal_at_least_x_votes",
- { value => $c });
- }
- push(@specialchart, ["votes", "greaterthan", $c - 1]);
- }
- }
-
- if ($params->param('bug_id')) {
- my $type = "anyexact";
- if ($params->param('bugidtype') && $params->param('bugidtype') eq 'exclude') {
- $type = "nowords";
- }
- push(@specialchart, ["bug_id", $type, join(',', $params->param('bug_id'))]);
- }
-
- # If the user has selected all of either status or resolution, change to
- # selecting none. This is functionally equivalent, but quite a lot faster.
- # Also, if the status is __open__ or __closed__, translate those
- # into their equivalent lists of open and closed statuses.
- if ($params->param('bug_status')) {
- my @bug_statuses = $params->param('bug_status');
- my @legal_statuses = @{get_legal_field_values('bug_status')};
- if (scalar(@bug_statuses) == scalar(@legal_statuses)
- || $bug_statuses[0] eq "__all__")
- {
- $params->delete('bug_status');
- }
- elsif ($bug_statuses[0] eq '__open__') {
- $params->param('bug_status', grep(is_open_state($_),
- @legal_statuses));
- }
- elsif ($bug_statuses[0] eq "__closed__") {
- $params->param('bug_status', grep(!is_open_state($_),
- @legal_statuses));
- }
- }
-
- if ($params->param('resolution')) {
- my @resolutions = $params->param('resolution');
- my $legal_resolutions = get_legal_field_values('resolution');
- if (scalar(@resolutions) == scalar(@$legal_resolutions)) {
- $params->delete('resolution');
- }
- }
-
- my @legal_fields = ("product", "version", "rep_platform", "op_sys",
- "bug_status", "resolution", "priority", "bug_severity",
- "assigned_to", "reporter", "component", "classification",
- "target_milestone", "bug_group");
-
- # Include custom select fields.
- push(@legal_fields, map { $_->name } @select_fields);
- push(@legal_fields, map { $_->name } @multi_select_fields);
-
- foreach my $field ($params->param()) {
- if (lsearch(\@legal_fields, $field) != -1) {
- push(@specialchart, [$field, "anyexact",
- join(',', $params->param($field))]);
- }
- }
-
- if ($params->param('keywords')) {
- my $t = $params->param('keywords_type');
- if (!$t || $t eq "or") {
- $t = "anywords";
- }
- push(@specialchart, ["keywords", $t, $params->param('keywords')]);
- }
-
- foreach my $id ("1", "2") {
- if (!defined ($params->param("email$id"))) {
- next;
- }
- my $email = trim($params->param("email$id"));
- if ($email eq "") {
- next;
- }
- my $type = $params->param("emailtype$id");
- if ($type eq "exact") {
- $type = "anyexact";
- foreach my $name (split(',', $email)) {
- $name = trim($name);
- if ($name) {
- login_to_id($name, THROW_ERROR);
- }
- }
- }
-
- my @clist;
- foreach my $field ("assigned_to", "reporter", "cc", "qa_contact") {
- if ($params->param("email$field$id")) {
- push(@clist, $field, $type, $email);
- }
- }
- if ($params->param("emaillongdesc$id")) {
- push(@clist, "commenter", $type, $email);
- }
- if (@clist) {
- push(@specialchart, \@clist);
- } else {
- ThrowUserError("missing_email_type",
- { email => $email });
- }
- }
-
- my $chfieldfrom = trim(lc($params->param('chfieldfrom'))) || '';
- my $chfieldto = trim(lc($params->param('chfieldto'))) || '';
- $chfieldfrom = '' if ($chfieldfrom eq 'now');
- $chfieldto = '' if ($chfieldto eq 'now');
- my @chfield = $params->param('chfield');
- my $chvalue = trim($params->param('chfieldvalue')) || '';
-
- # 2003-05-20: The 'changedin' field is no longer in the UI, but we continue
- # to process it because it will appear in stored queries and bookmarks.
- my $changedin = trim($params->param('changedin')) || '';
- if ($changedin) {
- if ($changedin !~ /^[0-9]*$/) {
- ThrowUserError("illegal_changed_in_last_x_days",
- { value => $changedin });
- }
-
- if (!$chfieldfrom
- && !$chfieldto
- && scalar(@chfield) == 1
- && $chfield[0] eq "[Bug creation]")
- {
- # Deal with the special case where the query is using changedin
- # to get bugs created in the last n days by converting the value
- # into its equivalent for the chfieldfrom parameter.
- $chfieldfrom = "-" . ($changedin - 1) . "d";
- }
- else {
- # Oh boy, the general case. Who knows why the user included
- # the changedin parameter, but do our best to comply.
- push(@specialchart, ["changedin", "lessthan", $changedin + 1]);
- }
- }
-
- if ($chfieldfrom ne '' || $chfieldto ne '') {
- my $sql_chfrom = $chfieldfrom ? $dbh->quote(SqlifyDate($chfieldfrom)):'';
- my $sql_chto = $chfieldto ? $dbh->quote(SqlifyDate($chfieldto)) :'';
- my $sql_chvalue = $chvalue ne '' ? $dbh->quote($chvalue) : '';
- trick_taint($sql_chvalue);
- if(!@chfield) {
- push(@wherepart, "bugs.delta_ts >= $sql_chfrom") if ($sql_chfrom);
- push(@wherepart, "bugs.delta_ts <= $sql_chto") if ($sql_chto);
- } else {
- my $bug_creation_clause;
- my @list;
- my @actlist;
- foreach my $f (@chfield) {
- if ($f eq "[Bug creation]") {
- # Treat [Bug creation] differently because we need to look
- # at bugs.creation_ts rather than the bugs_activity table.
- my @l;
- push(@l, "bugs.creation_ts >= $sql_chfrom") if($sql_chfrom);
- push(@l, "bugs.creation_ts <= $sql_chto") if($sql_chto);
- $bug_creation_clause = "(" . join(' AND ', @l) . ")";
- } else {
- push(@actlist, get_field_id($f));
- }
- }
-
- # @actlist won't have any elements if the only field being searched
- # is [Bug creation] (in which case we don't need bugs_activity).
- if(@actlist) {
- my $extra = " actcheck.bug_id = bugs.bug_id";
- push(@list, "(actcheck.bug_when IS NOT NULL)");
- if($sql_chfrom) {
- $extra .= " AND actcheck.bug_when >= $sql_chfrom";
- }
- if($sql_chto) {
- $extra .= " AND actcheck.bug_when <= $sql_chto";
- }
- if($sql_chvalue) {
- $extra .= " AND actcheck.added = $sql_chvalue";
- }
- push(@supptables, "LEFT JOIN bugs_activity AS actcheck " .
- "ON $extra AND "
- . $dbh->sql_in('actcheck.fieldid', \@actlist));
- }
-
- # Now that we're done using @list to determine if there are any
- # regular fields to search (and thus we need bugs_activity),
- # add the [Bug creation] criterion to the list so we can OR it
- # together with the others.
- push(@list, $bug_creation_clause) if $bug_creation_clause;
-
- push(@wherepart, "(" . join(" OR ", @list) . ")");
- }
- }
-
- my $sql_deadlinefrom;
- my $sql_deadlineto;
- if ($user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
- my $deadlinefrom;
- my $deadlineto;
-
- if ($params->param('deadlinefrom')){
- $deadlinefrom = $params->param('deadlinefrom');
- validate_date($deadlinefrom)
- || ThrowUserError('illegal_date', {date => $deadlinefrom,
- format => 'YYYY-MM-DD'});
- $sql_deadlinefrom = $dbh->quote($deadlinefrom);
- trick_taint($sql_deadlinefrom);
- push(@wherepart, "bugs.deadline >= $sql_deadlinefrom");
- }
-
- if ($params->param('deadlineto')){
- $deadlineto = $params->param('deadlineto');
- validate_date($deadlineto)
- || ThrowUserError('illegal_date', {date => $deadlineto,
- format => 'YYYY-MM-DD'});
- $sql_deadlineto = $dbh->quote($deadlineto);
- trick_taint($sql_deadlineto);
- push(@wherepart, "bugs.deadline <= $sql_deadlineto");
- }
- }
-
- foreach my $f ("short_desc", "long_desc", "bug_file_loc",
- "status_whiteboard") {
- if (defined $params->param($f)) {
- my $s = trim($params->param($f));
- if ($s ne "") {
- my $n = $f;
- my $q = $dbh->quote($s);
- trick_taint($q);
- my $type = $params->param($f . "_type");
- push(@specialchart, [$f, $type, $s]);
- }
- }
- }
-
- if (defined $params->param('content')) {
- push(@specialchart, ['content', 'matches', $params->param('content')]);
- }
-
- my $multi_fields = join('|', map($_->name, @multi_select_fields));
-
- my $chartid;
- my $sequence = 0;
- # $type_id is used by the code that queries for attachment flags.
- my $type_id = 0;
- my $f;
- my $ff;
- my $t;
- my $q;
- my $v;
- my $term;
- my %funcsbykey;
- my %func_args = (
- 'chartid' => \$chartid,
- 'sequence' => \$sequence,
- 'f' => \$f,
- 'ff' => \$ff,
- 't' => \$t,
- 'v' => \$v,
- 'q' => \$q,
- 'term' => \$term,
- 'funcsbykey' => \%funcsbykey,
- 'supptables' => \@supptables,
- 'wherepart' => \@wherepart,
- 'having' => \@having,
- 'groupby' => \@groupby,
- 'chartfields' => \%chartfields,
- 'fields' => \@fields,
- );
- my @funcdefs = (
- "^(?:assigned_to|reporter|qa_contact),(?:notequals|equals|anyexact),%group\\.([^%]+)%" => \&_contact_exact_group,
- "^(?:assigned_to|reporter|qa_contact),(?:equals|anyexact),(%\\w+%)" => \&_contact_exact,
- "^(?:assigned_to|reporter|qa_contact),(?:notequals),(%\\w+%)" => \&_contact_notequals,
- "^(assigned_to|reporter),(?!changed)" => \&_assigned_to_reporter_nonchanged,
- "^qa_contact,(?!changed)" => \&_qa_contact_nonchanged,
- "^(?:cc),(?:notequals|equals|anyexact),%group\\.([^%]+)%" => \&_cc_exact_group,
- "^cc,(?:equals|anyexact),(%\\w+%)" => \&_cc_exact,
- "^cc,(?:notequals),(%\\w+%)" => \&_cc_notequals,
- "^cc,(?!changed)" => \&_cc_nonchanged,
- "^long_?desc,changedby" => \&_long_desc_changedby,
- "^long_?desc,changedbefore" => \&_long_desc_changedbefore_after,
- "^long_?desc,changedafter" => \&_long_desc_changedbefore_after,
- "^content,matches" => \&_content_matches,
- "^content," => sub { ThrowUserError("search_content_without_matches"); },
- "^(?:deadline|creation_ts|delta_ts),(?:lessthan|greaterthan|equals|notequals),(?:-|\\+)?(?:\\d+)(?:[dDwWmMyY])\$" => \&_timestamp_compare,
- "^commenter,(?:equals|anyexact),(%\\w+%)" => \&_commenter_exact,
- "^commenter," => \&_commenter,
- "^long_?desc," => \&_long_desc,
- "^longdescs\.isprivate," => \&_longdescs_isprivate,
- "^work_time,changedby" => \&_work_time_changedby,
- "^work_time,changedbefore" => \&_work_time_changedbefore_after,
- "^work_time,changedafter" => \&_work_time_changedbefore_after,
- "^work_time," => \&_work_time,
- "^percentage_complete," => \&_percentage_complete,
- "^bug_group,(?!changed)" => \&_bug_group_nonchanged,
- "^attach_data\.thedata,changed" => \&_attach_data_thedata_changed,
- "^attach_data\.thedata," => \&_attach_data_thedata,
- "^attachments\.submitter," => \&_attachments_submitter,
- "^attachments\..*," => \&_attachments,
- "^flagtypes.name," => \&_flagtypes_name,
- "^requestees.login_name," => \&_requestees_login_name,
- "^setters.login_name," => \&_setters_login_name,
- "^(changedin|days_elapsed)," => \&_changedin_days_elapsed,
- "^component,(?!changed)" => \&_component_nonchanged,
- "^product,(?!changed)" => \&_product_nonchanged,
- "^classification,(?!changed)" => \&_classification_nonchanged,
- "^keywords,(?!changed)" => \&_keywords_nonchanged,
- "^dependson,(?!changed)" => \&_dependson_nonchanged,
- "^blocked,(?!changed)" => \&_blocked_nonchanged,
- "^alias,(?!changed)" => \&_alias_nonchanged,
- "^owner_idle_time,(greaterthan|lessthan)" => \&_owner_idle_time_greater_less,
- "^($multi_fields),(?:notequals|notregexp|notsubstring|nowords|nowordssubstr)" => \&_multiselect_negative,
- "^($multi_fields),(?:allwords|allwordssubstr|anyexact)" => \&_multiselect_multiple,
- "^($multi_fields),(?!changed)" => \&_multiselect_nonchanged,
- ",equals" => \&_equals,
- ",notequals" => \&_notequals,
- ",casesubstring" => \&_casesubstring,
- ",substring" => \&_substring,
- ",substr" => \&_substring,
- ",notsubstring" => \&_notsubstring,
- ",regexp" => \&_regexp,
- ",notregexp" => \&_notregexp,
- ",lessthan" => \&_lessthan,
- ",matches" => sub { ThrowUserError("search_content_without_matches"); },
- ",greaterthan" => \&_greaterthan,
- ",anyexact" => \&_anyexact,
- ",anywordssubstr" => \&_anywordsubstr,
- ",allwordssubstr" => \&_allwordssubstr,
- ",nowordssubstr" => \&_nowordssubstr,
- ",anywords" => \&_anywords,
- ",allwords" => \&_allwords,
- ",nowords" => \&_nowords,
- ",(changedbefore|changedafter)" => \&_changedbefore_changedafter,
- ",(changedfrom|changedto)" => \&_changedfrom_changedto,
- ",changedby" => \&_changedby,
- );
- my @funcnames;
- while (@funcdefs) {
- my $key = shift(@funcdefs);
- my $value = shift(@funcdefs);
- if ($key =~ /^[^,]*$/) {
- die "All defs in %funcs must have a comma in their name: $key";
- }
- if (exists $funcsbykey{$key}) {
- die "Duplicate key in %funcs: $key";
- }
- $funcsbykey{$key} = $value;
- push(@funcnames, $key);
- }
-
- # first we delete any sign of "Chart #-1" from the HTML form hash
- # since we want to guarantee the user didn't hide something here
- my @badcharts = grep /^(field|type|value)-1-/, $params->param();
- foreach my $field (@badcharts) {
- $params->delete($field);
- }
-
- # now we take our special chart and stuff it into the form hash
- my $chart = -1;
- my $row = 0;
- foreach my $ref (@specialchart) {
- my $col = 0;
- while (@$ref) {
- $params->param("field$chart-$row-$col", shift(@$ref));
- $params->param("type$chart-$row-$col", shift(@$ref));
- $params->param("value$chart-$row-$col", shift(@$ref));
- if ($debug) {
- push(@debugdata, "$row-$col = " .
- $params->param("field$chart-$row-$col") . ' | ' .
- $params->param("type$chart-$row-$col") . ' | ' .
- $params->param("value$chart-$row-$col") . ' *');
- }
- $col++;
-
- }
- $row++;
- }
-
-
+# Description Of Boolean Charts
+# -----------------------------
+#
# A boolean chart is a way of representing the terms in a logical
# expression. Bugzilla builds SQL queries depending on how you enter
# terms into the boolean chart. Boolean charts are represented in
-# urls as tree-tuples of (chart id, row, column). The query form
+# urls as three-tuples of (chart id, row, column). The query form
# (query.cgi) may contain an arbitrary number of boolean charts where
# each chart represents a clause in a SQL query.
#
@@ -615,254 +126,1870 @@
# bar@blah.org
# --------------------------------------------------------------
-# $chartid is the number of the current chart whose SQL we're constructing
-# $row is the current row of the current chart
+#############
+# Constants #
+#############
-# names for table aliases are constructed using $chartid and $row
-# SELECT blah FROM $table "$table_$chartid_$row" WHERE ....
+# When doing searches, NULL datetimes are treated as this date.
+use constant EMPTY_DATETIME => '1970-01-01 00:00:00';
-# $f = field of table in bug db (e.g. bug_id, reporter, etc)
-# $ff = qualified field name (field name prefixed by table)
-# e.g. bugs_activity.bug_id
-# $t = type of query. e.g. "equal to", "changed after", case sensitive substr"
-# $v = value - value the user typed in to the form
-# $q = sanitized version of user input trick_taint(($dbh->quote($v)))
-# @supptables = Tables and/or table aliases used in query
-# %suppseen = A hash used to store all the tables in supptables to weed
-# out duplicates.
-# @supplist = A list used to accumulate all the JOIN clauses for each
-# chart to merge the ON sections of each.
-# $suppstring = String which is pasted into query containing all table names
+# This is the regex for real numbers from Regexp::Common, modified to be
+# more readable.
+use constant NUMBER_REGEX => qr/
+ ^[+-]? # A sign, optionally.
- # get a list of field names to verify the user-submitted chart fields against
- %chartfields = @{$dbh->selectcol_arrayref(
- q{SELECT name, id FROM fielddefs}, { Columns=>[1,2] })};
+ (?=\d|\.) # Then either a digit or "."
+ \d* # Followed by many other digits
+ (?:
+ \. # Followed possibly by some decimal places
+ (?:\d*)
+ )?
+
+ (?: # Followed possibly by an exponent.
+ [Ee]
+ [+-]?
+ \d+
+ )?
+ $
+/x;
- $row = 0;
- for ($chart=-1 ;
- $chart < 0 || $params->param("field$chart-0-0") ;
- $chart++) {
- $chartid = $chart >= 0 ? $chart : "";
- my @chartandlist = ();
- for ($row = 0 ;
- $params->param("field$chart-$row-0") ;
- $row++) {
- my @orlist;
- for (my $col = 0 ;
- $params->param("field$chart-$row-$col") ;
- $col++) {
- $f = $params->param("field$chart-$row-$col") || "noop";
- $t = $params->param("type$chart-$row-$col") || "noop";
- $v = $params->param("value$chart-$row-$col");
- $v = "" if !defined $v;
- $v = trim($v);
- if ($f eq "noop" || $t eq "noop" || $v eq "") {
- next;
- }
- # chart -1 is generated by other code above, not from the user-
- # submitted form, so we'll blindly accept any values in chart -1
- if ((!$chartfields{$f}) && ($chart != -1)) {
- ThrowCodeError("invalid_field_name", {field => $f});
- }
+# If you specify a search type in the boolean charts, this describes
+# which operator maps to which internal function here.
+use constant OPERATORS => {
+ equals => \&_simple_operator,
+ notequals => \&_simple_operator,
+ casesubstring => \&_casesubstring,
+ substring => \&_substring,
+ substr => \&_substring,
+ notsubstring => \&_notsubstring,
+ regexp => \&_regexp,
+ notregexp => \&_notregexp,
+ lessthan => \&_simple_operator,
+ lessthaneq => \&_simple_operator,
+ matches => sub { ThrowUserError("search_content_without_matches"); },
+ notmatches => sub { ThrowUserError("search_content_without_matches"); },
+ greaterthan => \&_simple_operator,
+ greaterthaneq => \&_simple_operator,
+ anyexact => \&_anyexact,
+ anywordssubstr => \&_anywordsubstr,
+ allwordssubstr => \&_allwordssubstr,
+ nowordssubstr => \&_nowordssubstr,
+ anywords => \&_anywords,
+ allwords => \&_allwords,
+ nowords => \&_nowords,
+ changedbefore => \&_changedbefore_changedafter,
+ changedafter => \&_changedbefore_changedafter,
+ changedfrom => \&_changedfrom_changedto,
+ changedto => \&_changedfrom_changedto,
+ changedby => \&_changedby,
+};
- # This is either from the internal chart (in which case we
- # already know about it), or it was in %chartfields, so it is
- # a valid field name, which means that it's ok.
- trick_taint($f);
- $q = $dbh->quote($v);
- trick_taint($q);
- my $rhs = $v;
- $rhs =~ tr/,//;
- my $func;
- $term = undef;
- foreach my $key (@funcnames) {
- if ("$f,$t,$rhs" =~ m/$key/) {
- my $ref = $funcsbykey{$key};
- if ($debug) {
- push(@debugdata, "$key ($f / $t / $rhs) =>");
- }
- $ff = $f;
- if ($f !~ /\./) {
- $ff = "bugs.$f";
- }
- $self->$ref(%func_args);
- if ($debug) {
- push(@debugdata, "$f / $t / $v / " .
- ($term || "undef") . " *");
- }
- if ($term) {
- last;
- }
- }
- }
- if ($term) {
- push(@orlist, $term);
- }
- else {
- # This field and this type don't work together.
- ThrowCodeError("field_type_mismatch",
- { field => $params->param("field$chart-$row-$col"),
- type => $params->param("type$chart-$row-$col"),
- });
- }
- }
- if (@orlist) {
- @orlist = map("($_)", @orlist) if (scalar(@orlist) > 1);
- push(@chartandlist, "(" . join(" OR ", @orlist) . ")");
- }
- }
- if (@chartandlist) {
- if ($params->param("negate$chart")) {
- push(@andlist, "NOT(" . join(" AND ", @chartandlist) . ")");
- } else {
- push(@andlist, "(" . join(" AND ", @chartandlist) . ")");
- }
- }
- }
+# Some operators are really just standard SQL operators, and are
+# all implemented by the _simple_operator function, which uses this
+# constant.
+use constant SIMPLE_OPERATORS => {
+ equals => '=',
+ notequals => '!=',
+ greaterthan => '>',
+ greaterthaneq => '>=',
+ lessthan => '<',
+ lessthaneq => "<=",
+};
- # The ORDER BY clause goes last, but can require modifications
- # to other parts of the query, so we want to create it before we
- # write the FROM clause.
- foreach my $orderitem (@inputorder) {
- # Some fields have 'AS' aliases. The aliases go in the ORDER BY,
- # not the whole fields.
- # XXX - Ideally, we would get just the aliases in @inputorder,
- # and we'd never have to deal with this.
- if ($orderitem =~ /\s+AS\s+(.+)$/i) {
- $orderitem = $1;
- }
- BuildOrderBy(\%special_order, $orderitem, \@orderby);
- }
- # Now JOIN the correct tables in the FROM clause.
- # This is done separately from the above because it's
- # cleaner to do it this way.
- foreach my $orderitem (@inputorder) {
- # Grab the part without ASC or DESC.
- my @splitfield = split(/\s+/, $orderitem);
- if ($special_order_join{$splitfield[0]}) {
- push(@supptables, $special_order_join{$splitfield[0]});
- }
- }
+# Most operators just reverse by removing or adding "not" from/to them.
+# However, some operators reverse in a different way, so those are listed
+# here.
+use constant OPERATOR_REVERSE => {
+ nowords => 'anywords',
+ nowordssubstr => 'anywordssubstr',
+ anywords => 'nowords',
+ anywordssubstr => 'nowordssubstr',
+ lessthan => 'greaterthaneq',
+ lessthaneq => 'greaterthan',
+ greaterthan => 'lessthaneq',
+ greaterthaneq => 'lessthan',
+ # The following don't currently have reversals:
+ # casesubstring, anyexact, allwords, allwordssubstr
+};
- my %suppseen = ("bugs" => 1);
- my $suppstring = "bugs";
- my @supplist = (" ");
- foreach my $str (@supptables) {
+# For these operators, even if a field is numeric (is_numeric returns true),
+# we won't treat the input like a number.
+use constant NON_NUMERIC_OPERATORS => qw(
+ changedafter
+ changedbefore
+ changedfrom
+ changedto
+ regexp
+ notregexp
+);
- if ($str =~ /^(LEFT|INNER|RIGHT)\s+JOIN/i) {
- $str =~ /^(.*?)\s+ON\s+(.*)$/i;
- my ($leftside, $rightside) = ($1, $2);
- if (defined $suppseen{$leftside}) {
- $supplist[$suppseen{$leftside}] .= " AND ($rightside)";
- } else {
- $suppseen{$leftside} = scalar @supplist;
- push @supplist, " $leftside ON ($rightside)";
- }
- } else {
- # Do not accept implicit joins using comma operator
- # as they are not DB agnostic
- ThrowCodeError("comma_operator_deprecated");
- }
- }
- $suppstring .= join('', @supplist);
+use constant MULTI_SELECT_OVERRIDE => {
+ notequals => \&_multiselect_negative,
+ notregexp => \&_multiselect_negative,
+ notsubstring => \&_multiselect_negative,
+ nowords => \&_multiselect_negative,
+ nowordssubstr => \&_multiselect_negative,
- # Make sure we create a legal SQL query.
- @andlist = ("1 = 1") if !@andlist;
+ allwords => \&_multiselect_multiple,
+ allwordssubstr => \&_multiselect_multiple,
+ anyexact => \&_multiselect_multiple,
+ anywords => \&_multiselect_multiple,
+ anywordssubstr => \&_multiselect_multiple,
+
+ _non_changed => \&_multiselect_nonchanged,
+};
- my $query = "SELECT " . join(', ', @fields) .
- " FROM $suppstring" .
- " LEFT JOIN bug_group_map " .
- " ON bug_group_map.bug_id = bugs.bug_id ";
+use constant OPERATOR_FIELD_OVERRIDE => {
+ # User fields
+ 'attachments.submitter' => {
+ _non_changed => \&_user_nonchanged,
+ },
+ assigned_to => {
+ _non_changed => \&_user_nonchanged,
+ },
+ cc => {
+ _non_changed => \&_user_nonchanged,
+ },
+ commenter => {
+ _non_changed => \&_user_nonchanged,
+ },
+ reporter => {
+ _non_changed => \&_user_nonchanged,
+ },
+ 'requestees.login_name' => {
+ _non_changed => \&_user_nonchanged,
+ },
+ 'setters.login_name' => {
+ _non_changed => \&_user_nonchanged,
+ },
+ qa_contact => {
+ _non_changed => \&_user_nonchanged,
+ },
+
+ # General Bug Fields
+ alias => { _non_changed => \&_nullable },
+ 'attach_data.thedata' => MULTI_SELECT_OVERRIDE,
+ # We check all attachment fields against this.
+ attachments => MULTI_SELECT_OVERRIDE,
+ blocked => MULTI_SELECT_OVERRIDE,
+ bug_file_loc => { _non_changed => \&_nullable },
+ bug_group => MULTI_SELECT_OVERRIDE,
+ classification => {
+ _non_changed => \&_classification_nonchanged,
+ },
+ component => {
+ _non_changed => \&_component_nonchanged,
+ },
+ content => {
+ matches => \&_content_matches,
+ notmatches => \&_content_matches,
+ _default => sub { ThrowUserError("search_content_without_matches"); },
+ },
+ days_elapsed => {
+ _default => \&_days_elapsed,
+ },
+ dependson => MULTI_SELECT_OVERRIDE,
+ keywords => MULTI_SELECT_OVERRIDE,
+ 'flagtypes.name' => MULTI_SELECT_OVERRIDE,
+ longdesc => {
+ %{ MULTI_SELECT_OVERRIDE() },
+ changedby => \&_long_desc_changedby,
+ changedbefore => \&_long_desc_changedbefore_after,
+ changedafter => \&_long_desc_changedbefore_after,
+ },
+ 'longdescs.count' => {
+ changedby => \&_long_desc_changedby,
+ changedbefore => \&_long_desc_changedbefore_after,
+ changedafter => \&_long_desc_changedbefore_after,
+ changedfrom => \&_invalid_combination,
+ changedto => \&_invalid_combination,
+ _default => \&_long_descs_count,
+ },
+ 'longdescs.isprivate' => MULTI_SELECT_OVERRIDE,
+ owner_idle_time => {
+ greaterthan => \&_owner_idle_time_greater_less,
+ greaterthaneq => \&_owner_idle_time_greater_less,
+ lessthan => \&_owner_idle_time_greater_less,
+ lessthaneq => \&_owner_idle_time_greater_less,
+ _default => \&_invalid_combination,
+ },
+ product => {
+ _non_changed => \&_product_nonchanged,
+ },
+ tag => MULTI_SELECT_OVERRIDE,
+
+ # Timetracking Fields
+ deadline => { _non_changed => \&_deadline },
+ percentage_complete => {
+ _non_changed => \&_percentage_complete,
+ },
+ work_time => {
+ changedby => \&_work_time_changedby,
+ changedbefore => \&_work_time_changedbefore_after,
+ changedafter => \&_work_time_changedbefore_after,
+ _default => \&_work_time,
+ },
+
+ # Custom Fields
+ FIELD_TYPE_FREETEXT, { _non_changed => \&_nullable },
+ FIELD_TYPE_BUG_ID, { _non_changed => \&_nullable_int },
+ FIELD_TYPE_DATETIME, { _non_changed => \&_nullable_datetime },
+ FIELD_TYPE_TEXTAREA, { _non_changed => \&_nullable },
+ FIELD_TYPE_MULTI_SELECT, MULTI_SELECT_OVERRIDE,
+ FIELD_TYPE_BUG_URLS, MULTI_SELECT_OVERRIDE,
+};
- if ($user->id) {
- if (%{$user->groups}) {
- $query .= " AND bug_group_map.group_id NOT IN (" . join(',', values(%{$user->groups})) . ") ";
+# These are fields where special action is taken depending on the
+# *value* passed in to the chart, sometimes.
+use constant SPECIAL_PARSING => {
+ # Pronoun Fields (Ones that can accept %user%, etc.)
+ assigned_to => \&_contact_pronoun,
+ cc => \&_cc_pronoun,
+ commenter => \&_commenter_pronoun,
+ qa_contact => \&_contact_pronoun,
+ reporter => \&_contact_pronoun,
+
+ # Date Fields that accept the 1d, 1w, 1m, 1y, etc. format.
+ creation_ts => \&_timestamp_translate,
+ deadline => \&_timestamp_translate,
+ delta_ts => \&_timestamp_translate,
+};
+
+# Information about fields that represent "users", used by _user_nonchanged.
+# There are other user fields than the ones listed here, but those use
+# defaults in _user_nonchanged.
+use constant USER_FIELDS => {
+ 'attachments.submitter' => {
+ field => 'submitter_id',
+ join => { table => 'attachments' },
+ isprivate => 1,
+ },
+ cc => {
+ field => 'who',
+ join => { table => 'cc' },
+ },
+ commenter => {
+ field => 'who',
+ join => { table => 'longdescs', join => 'INNER' },
+ isprivate => 1,
+ },
+ qa_contact => {
+ nullable => 1,
+ },
+ 'requestees.login_name' => {
+ nullable => 1,
+ field => 'requestee_id',
+ join => { table => 'flags' },
+ },
+ 'setters.login_name' => {
+ field => 'setter_id',
+ join => { table => 'flags' },
+ },
+};
+
+# Backwards compatibility for times that we changed the names of fields
+# or URL parameters.
+use constant FIELD_MAP => {
+ 'attachments.thedata' => 'attach_data.thedata',
+ bugidtype => 'bug_id_type',
+ changedin => 'days_elapsed',
+ long_desc => 'longdesc',
+};
+
+# Some fields are not sorted on themselves, but on other fields.
+# We need to have a list of these fields and what they map to.
+use constant SPECIAL_ORDER => {
+ 'target_milestone' => {
+ order => ['map_target_milestone.sortkey','map_target_milestone.value'],
+ join => {
+ table => 'milestones',
+ from => 'target_milestone',
+ to => 'value',
+ extra => ['bugs.product_id = map_target_milestone.product_id'],
+ join => 'INNER',
}
+ },
+};
- $query .= " LEFT JOIN cc ON cc.bug_id = bugs.bug_id AND cc.who = " . $user->id;
+# Certain columns require other columns to come before them
+# in _select_columns, and should be put there if they're not there.
+use constant COLUMN_DEPENDS => {
+ classification => ['product'],
+ percentage_complete => ['actual_time', 'remaining_time'],
+};
+
+# This describes tables that must be joined when you want to display
+# certain columns in the buglist. For the most part, Search.pm uses
+# DB::Schema to figure out what needs to be joined, but for some
+# fields it needs a little help.
+use constant COLUMN_JOINS => {
+ actual_time => {
+ table => '(SELECT bug_id, SUM(work_time) AS total'
+ . ' FROM longdescs GROUP BY bug_id)',
+ join => 'INNER',
+ },
+ assigned_to => {
+ from => 'assigned_to',
+ to => 'userid',
+ table => 'profiles',
+ join => 'INNER',
+ },
+ reporter => {
+ from => 'reporter',
+ to => 'userid',
+ table => 'profiles',
+ join => 'INNER',
+ },
+ qa_contact => {
+ from => 'qa_contact',
+ to => 'userid',
+ table => 'profiles',
+ },
+ component => {
+ from => 'component_id',
+ to => 'id',
+ table => 'components',
+ join => 'INNER',
+ },
+ product => {
+ from => 'product_id',
+ to => 'id',
+ table => 'products',
+ join => 'INNER',
+ },
+ classification => {
+ table => 'classifications',
+ from => 'map_product.classification_id',
+ to => 'id',
+ join => 'INNER',
+ },
+ 'flagtypes.name' => {
+ as => 'map_flags',
+ table => 'flags',
+ extra => ['map_flags.attach_id IS NULL'],
+ then_to => {
+ as => 'map_flagtypes',
+ table => 'flagtypes',
+ from => 'map_flags.type_id',
+ to => 'id',
+ },
+ },
+ keywords => {
+ table => 'keywords',
+ then_to => {
+ as => 'map_keyworddefs',
+ table => 'keyworddefs',
+ from => 'map_keywords.keywordid',
+ to => 'id',
+ },
+ },
+ 'longdescs.count' => {
+ table => 'longdescs',
+ join => 'INNER',
+ },
+};
+
+# This constant defines the columns that can be selected in a query
+# and/or displayed in a bug list. Column records include the following
+# fields:
+#
+# 1. id: a unique identifier by which the column is referred in code;
+#
+# 2. name: The name of the column in the database (may also be an expression
+# that returns the value of the column);
+#
+# 3. title: The title of the column as displayed to users.
+#
+# Note: There are a few hacks in the code that deviate from these definitions.
+# In particular, the redundant short_desc column is removed when the
+# client requests "all" columns.
+#
+# This is really a constant--that is, once it's been called once, the value
+# will always be the same unless somebody adds a new custom field. But
+# we have to do a lot of work inside the subroutine to get the data,
+# and we don't want it to happen at compile time, so we have it as a
+# subroutine.
+sub COLUMNS {
+ my $invocant = shift;
+ my $user = blessed($invocant) ? $invocant->_user : Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+ my $cache = Bugzilla->request_cache;
+
+ if (defined $cache->{search_columns}->{$user->id}) {
+ return $cache->{search_columns}->{$user->id};
}
- $query .= " WHERE " . join(' AND ', (@wherepart, @andlist)) .
- " AND bugs.creation_ts IS NOT NULL AND ((bug_group_map.group_id IS NULL)";
+ # These are columns that don't exist in fielddefs, but are valid buglist
+ # columns. (Also see near the bottom of this function for the definition
+ # of short_short_desc.)
+ my %columns = (
+ relevance => { title => 'Relevance' },
+ assigned_to_realname => { title => 'Assignee' },
+ reporter_realname => { title => 'Reporter' },
+ qa_contact_realname => { title => 'QA Contact' },
+ );
- if ($user->id) {
- my $userid = $user->id;
- $query .= " OR (bugs.reporter_accessible = 1 AND bugs.reporter = $userid) " .
- " OR (bugs.cclist_accessible = 1 AND cc.who IS NOT NULL) " .
- " OR (bugs.assigned_to = $userid) ";
- if (Bugzilla->params->{'useqacontact'}) {
- $query .= "OR (bugs.qa_contact = $userid) ";
+ # Next we define columns that have special SQL instead of just something
+ # like "bugs.bug_id".
+ my $total_time = "(map_actual_time.total + bugs.remaining_time)";
+ my %special_sql = (
+ deadline => $dbh->sql_date_format('bugs.deadline', '%Y-%m-%d'),
+ actual_time => 'map_actual_time.total',
+
+ # "FLOOR" is in there to turn this into an integer, making searches
+ # totally predictable. Otherwise you get floating-point numbers that
+ # are rather hard to search reliably if you're asking for exact
+ # numbers.
+ percentage_complete =>
+ "(CASE WHEN $total_time = 0"
+ . " THEN 0"
+ . " ELSE FLOOR(100 * (map_actual_time.total / $total_time))"
+ . " END)",
+
+ 'flagtypes.name' => $dbh->sql_group_concat('DISTINCT '
+ . $dbh->sql_string_concat('map_flagtypes.name', 'map_flags.status')),
+
+ 'keywords' => $dbh->sql_group_concat('DISTINCT map_keyworddefs.name'),
+
+ 'longdescs.count' => 'COUNT(DISTINCT map_longdescs_count.comment_id)',
+ );
+
+ # Backward-compatibility for old field names. Goes new_name => old_name.
+ # These are here and not in translate_old_column because the rest of the
+ # code actually still uses the old names, while the fielddefs table uses
+ # the new names (which is not the case for the fields handled by
+ # translate_old_column).
+ my %old_names = (
+ creation_ts => 'opendate',
+ delta_ts => 'changeddate',
+ work_time => 'actual_time',
+ );
+
+ # Fields that are email addresses
+ my @email_fields = qw(assigned_to reporter qa_contact);
+ # Other fields that are stored in the bugs table as an id, but
+ # should be displayed using their name.
+ my @id_fields = qw(product component classification);
+
+ foreach my $col (@email_fields) {
+ my $sql = "map_${col}.login_name";
+ if (!$user->id) {
+ $sql = $dbh->sql_string_until($sql, $dbh->quote('@'));
}
+ $special_sql{$col} = $sql;
+ $columns{"${col}_realname"}->{name} = "map_${col}.realname";
}
- foreach my $field (@fields, @orderby) {
- next if ($field =~ /(AVG|SUM|COUNT|MAX|MIN|VARIANCE)\s*\(/i ||
- $field =~ /^\d+$/ || $field eq "bugs.bug_id" ||
- $field =~ /^(relevance|actual_time|percentage_complete)/);
- # The structure of fields is of the form:
- # [foo AS] {bar | bar.baz} [ASC | DESC]
- # Only the mandatory part bar OR bar.baz is of interest.
- # But for Oracle, it needs the real name part instead.
- my $regexp = $dbh->GROUPBY_REGEXP;
- if ($field =~ /$regexp/i) {
- push(@groupby, $1) if !grep($_ eq $1, @groupby);
+ foreach my $col (@id_fields) {
+ $special_sql{$col} = "map_${col}.name";
+ }
+
+ # Do the actual column-getting from fielddefs, now.
+ my @fields = @{ Bugzilla->fields({ obsolete => 0, buglist => 1 }) };
+ foreach my $field (@fields) {
+ my $id = $field->name;
+ $id = $old_names{$id} if exists $old_names{$id};
+ my $sql;
+ if (exists $special_sql{$id}) {
+ $sql = $special_sql{$id};
}
- }
- $query .= ") " . $dbh->sql_group_by("bugs.bug_id", join(', ', @groupby));
-
-
- if (@having) {
- $query .= " HAVING " . join(" AND ", @having);
+ elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ $sql = $dbh->sql_group_concat(
+ 'DISTINCT map_' . $field->name . '.value');
+ }
+ else {
+ $sql = 'bugs.' . $field->name;
+ }
+ $columns{$id} = { name => $sql, title => $field->description };
}
- if (@orderby) {
- $query .= " ORDER BY " . join(',', @orderby);
- }
+ # The short_short_desc column is identical to short_desc
+ $columns{'short_short_desc'} = $columns{'short_desc'};
- $self->{'sql'} = $query;
- $self->{'debugdata'} = \@debugdata;
+ Bugzilla::Hook::process('buglist_columns', { columns => \%columns });
+
+ $cache->{search_columns}->{$user->id} = \%columns;
+ return $cache->{search_columns}->{$user->id};
}
-###############################################################################
-# Helper functions for the init() method.
-###############################################################################
+sub REPORT_COLUMNS {
+ my $invocant = shift;
+ my $user = blessed($invocant) ? $invocant->_user : Bugzilla->user;
+
+ my $columns = dclone(blessed($invocant) ? $invocant->COLUMNS : COLUMNS);
+ # There's no reason to support reporting on unique fields.
+ # Also, some other fields don't make very good reporting axises,
+ # or simply don't work with the current reporting system.
+ my @no_report_columns =
+ qw(bug_id alias short_short_desc opendate changeddate
+ flagtypes.name keywords relevance);
+
+ # Multi-select fields are not currently supported.
+ my @multi_selects = @{Bugzilla->fields(
+ { obsolete => 0, type => FIELD_TYPE_MULTI_SELECT })};
+ push(@no_report_columns, map { $_->name } @multi_selects);
+
+ # If you're not a time-tracker, you can't use time-tracking
+ # columns.
+ if (!$user->is_timetracker) {
+ push(@no_report_columns, TIMETRACKING_FIELDS);
+ }
+
+ foreach my $name (@no_report_columns) {
+ delete $columns->{$name};
+ }
+ return $columns;
+}
+
+# These are fields that never go into the GROUP BY on any DB. bug_id
+# is here because it *always* goes into the GROUP BY as the first item,
+# so it should be skipped when determining extra GROUP BY columns.
+use constant GROUP_BY_SKIP => qw(
+ bug_id
+ flagtypes.name
+ keywords
+ longdescs.count
+ percentage_complete
+);
+
+###############
+# Constructor #
+###############
+
+# Note that the params argument may be modified by Bugzilla::Search
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+
+ my $self = { @_ };
+ bless($self, $class);
+ $self->{'user'} ||= Bugzilla->user;
+
+ # There are certain behaviors of the CGI "Vars" hash that we don't want.
+ # In particular, if you put a single-value arrayref into it, later you
+ # get back out a string, which breaks anyexact charts (because they
+ # need arrays even for individual items, or we will re-trigger bug 67036).
+ #
+ # We can't just untie the hash--that would give us a hash with no values.
+ # We have to manually copy the hash into a new one, and we have to always
+ # do it, because there's no way to know if we were passed a tied hash
+ # or not.
+ my $params_in = $self->_params;
+ my %params = map { $_ => $params_in->{$_} } keys %$params_in;
+ $self->{params} = \%params;
+
+ return $self;
+}
+
+
+####################
+# Public Accessors #
+####################
+
+sub sql {
+ my ($self) = @_;
+ return $self->{sql} if $self->{sql};
+ my $dbh = Bugzilla->dbh;
+
+ my ($joins, $clause) = $self->_charts_to_conditions();
+ my $select = join(', ', $self->_sql_select);
+ my $from = $self->_sql_from($joins);
+ my $where = $self->_sql_where($clause);
+ my $group_by = $dbh->sql_group_by($self->_sql_group_by);
+ my $order_by = $self->_sql_order_by
+ ? "\nORDER BY " . join(', ', $self->_sql_order_by) : '';
+ my $limit = $self->_sql_limit;
+ $limit = "\n$limit" if $limit;
+
+ my $query = <<END;
+SELECT $select
+ FROM $from
+ WHERE $where
+$group_by$order_by$limit
+END
+ $self->{sql} = $query;
+ return $self->{sql};
+}
+
+sub search_description {
+ my ($self, $params) = @_;
+ my $desc = $self->{'search_description'} ||= [];
+ if ($params) {
+ push(@$desc, $params);
+ }
+ # Make sure that the description has actually been generated if
+ # people are asking for the whole thing.
+ else {
+ $self->sql;
+ }
+ return $self->{'search_description'};
+}
+
+sub boolean_charts_to_custom_search {
+ my ($self, $cgi_buffer) = @_;
+ my @as_params = $self->_boolean_charts->as_params;
+
+ # We need to start our new ids after the last custom search "f" id.
+ # We can just pick the last id in the array because they are sorted
+ # numerically.
+ my $last_id = ($self->_field_ids)[-1];
+ my $count = defined($last_id) ? $last_id + 1 : 0;
+ foreach my $param_set (@as_params) {
+ foreach my $name (keys %$param_set) {
+ my $value = $param_set->{$name};
+ next if !defined $value;
+ $cgi_buffer->param($name . $count, $value);
+ }
+ $count++;
+ }
+}
+
+######################
+# Internal Accessors #
+######################
+
+# Fields that are legal for boolean charts of any kind.
+sub _chart_fields {
+ my ($self) = @_;
+
+ if (!$self->{chart_fields}) {
+ my $chart_fields = Bugzilla->fields({ by_name => 1 });
+
+ if (!$self->_user->is_timetracker) {
+ foreach my $tt_field (TIMETRACKING_FIELDS) {
+ delete $chart_fields->{$tt_field};
+ }
+ }
+ $self->{chart_fields} = $chart_fields;
+ }
+ return $self->{chart_fields};
+}
+
+# There are various places in Search.pm that we need to know the list of
+# valid multi-select fields--or really, fields that are stored like
+# multi-selects, which includes BUG_URLS fields.
+sub _multi_select_fields {
+ my ($self) = @_;
+ $self->{multi_select_fields} ||= Bugzilla->fields({
+ by_name => 1,
+ type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_BUG_URLS]});
+ return $self->{multi_select_fields};
+}
+
+# $self->{params} contains values that could be undef, could be a string,
+# or could be an arrayref. Sometimes we want that value as an array,
+# always.
+sub _param_array {
+ my ($self, $name) = @_;
+ my $value = $self->_params->{$name};
+ if (!defined $value) {
+ return ();
+ }
+ if (ref($value) eq 'ARRAY') {
+ return @$value;
+ }
+ return ($value);
+}
+
+sub _params { $_[0]->{params} }
+sub _user { return $_[0]->{user} }
+sub _sharer_id { $_[0]->{sharer} }
+
+##############################
+# Internal Accessors: SELECT #
+##############################
+
+# These are the fields the user has chosen to display on the buglist,
+# exactly as they were passed to new().
+sub _input_columns { @{ $_[0]->{'fields'} || [] } }
+
+# These are columns that are also going to be in the SELECT for one reason
+# or another, but weren't actually requested by the caller.
+sub _extra_columns {
+ my ($self) = @_;
+ # Everything that's going to be in the ORDER BY must also be
+ # in the SELECT.
+ push(@{ $self->{extra_columns} }, $self->_input_order_columns);
+ return @{ $self->{extra_columns} };
+}
+
+# For search functions to modify extra_columns. It doesn't matter if
+# people push the same column onto this array multiple times, because
+# _select_columns will call "uniq" on its final result.
+sub _add_extra_column {
+ my ($self, $column) = @_;
+ push(@{ $self->{extra_columns} }, $column);
+}
+
+# These are the columns that we're going to be actually SELECTing.
+sub _select_columns {
+ my ($self) = @_;
+ return @{ $self->{select_columns} } if $self->{select_columns};
+
+ my @select_columns;
+ foreach my $column ($self->_input_columns, $self->_extra_columns) {
+ if (my $add_first = COLUMN_DEPENDS->{$column}) {
+ push(@select_columns, @$add_first);
+ }
+ push(@select_columns, $column);
+ }
+
+ $self->{select_columns} = [uniq @select_columns];
+ return @{ $self->{select_columns} };
+}
+
+# This takes _select_columns and translates it into the actual SQL that
+# will go into the SELECT clause.
+sub _sql_select {
+ my ($self) = @_;
+ my @sql_fields;
+ foreach my $column ($self->_select_columns) {
+ my $alias = $column;
+ # Aliases cannot contain dots in them. We convert them to underscores.
+ $alias =~ s/\./_/g;
+ my $sql = $self->COLUMNS->{$column}->{name} . " AS $alias";
+ push(@sql_fields, $sql);
+ }
+ return @sql_fields;
+}
+
+################################
+# Internal Accessors: ORDER BY #
+################################
+
+# The "order" that was requested by the consumer, exactly as it was
+# requested.
+sub _input_order { @{ $_[0]->{'order'} || [] } }
+# The input order with just the column names, and no ASC or DESC.
+sub _input_order_columns {
+ my ($self) = @_;
+ return map { (split_order_term($_))[0] } $self->_input_order;
+}
+
+# A hashref that describes all the special stuff that has to be done
+# for various fields if they go into the ORDER BY clause.
+sub _special_order {
+ my ($self) = @_;
+ return $self->{special_order} if $self->{special_order};
+
+ my %special_order = %{ SPECIAL_ORDER() };
+ my $select_fields = Bugzilla->fields({ type => FIELD_TYPE_SINGLE_SELECT });
+ foreach my $field (@$select_fields) {
+ next if $field->is_abnormal;
+ my $name = $field->name;
+ $special_order{$name} = {
+ order => ["map_$name.sortkey", "map_$name.value"],
+ join => {
+ table => $name,
+ from => "bugs.$name",
+ to => "value",
+ join => 'INNER',
+ }
+ };
+ }
+ $self->{special_order} = \%special_order;
+ return $self->{special_order};
+}
+
+sub _sql_order_by {
+ my ($self) = @_;
+ if (!$self->{sql_order_by}) {
+ my @order_by = map { $self->_translate_order_by_column($_) }
+ $self->_input_order;
+ $self->{sql_order_by} = \@order_by;
+ }
+ return @{ $self->{sql_order_by} };
+}
+
+sub _translate_order_by_column {
+ my ($self, $order_by_item) = @_;
+
+ my ($field, $direction) = split_order_term($order_by_item);
+
+ $direction = '' if lc($direction) eq 'asc';
+ my $special_order = $self->_special_order->{$field}->{order};
+ # Standard fields have underscores in their SELECT alias instead
+ # of a period (because aliases can't have periods).
+ $field =~ s/\./_/g;
+ my @items = $special_order ? @$special_order : $field;
+ if (lc($direction) eq 'desc') {
+ @items = map { "$_ DESC" } @items;
+ }
+ return @items;
+}
+
+#############################
+# Internal Accessors: LIMIT #
+#############################
+
+sub _sql_limit {
+ my ($self) = @_;
+ my $limit = $self->_params->{limit};
+ my $offset = $self->_params->{offset};
+
+ my $max_results = Bugzilla->params->{'max_search_results'};
+ if (!$self->{allow_unlimited} && (!$limit || $limit > $max_results)) {
+ $limit = $max_results;
+ }
+
+ if (defined($offset) && !$limit) {
+ $limit = INT_MAX;
+ }
+ if (defined $limit) {
+ detaint_natural($limit)
+ || ThrowCodeError('param_must_be_numeric',
+ { function => 'Bugzilla::Search::new',
+ param => 'limit' });
+ if (defined $offset) {
+ detaint_natural($offset)
+ || ThrowCodeError('param_must_be_numeric',
+ { function => 'Bugzilla::Search::new',
+ param => 'offset' });
+ }
+ return Bugzilla->dbh->sql_limit($limit, $offset);
+ }
+ return '';
+}
+
+############################
+# Internal Accessors: FROM #
+############################
+
+sub _column_join {
+ my ($self, $field) = @_;
+ # The _realname fields require the same join as the username fields.
+ $field =~ s/_realname$//;
+ my $column_joins = $self->_get_column_joins();
+ my $join_info = $column_joins->{$field};
+ if ($join_info) {
+ # Don't allow callers to modify the constant.
+ $join_info = dclone($join_info);
+ }
+ else {
+ if ($self->_multi_select_fields->{$field}) {
+ $join_info = { table => "bug_$field" };
+ }
+ }
+ if ($join_info and !$join_info->{as}) {
+ $join_info = dclone($join_info);
+ $join_info->{as} = "map_$field";
+ }
+ return $join_info ? $join_info : ();
+}
+
+# Sometimes we join the same table more than once. In this case, we
+# want to AND all the various critiera that were used in both joins.
+sub _combine_joins {
+ my ($self, $joins) = @_;
+ my @result;
+ while(my $join = shift @$joins) {
+ my $name = $join->{as};
+ my ($others_like_me, $the_rest) = part { $_->{as} eq $name ? 0 : 1 }
+ @$joins;
+ if ($others_like_me) {
+ my $from = $join->{from};
+ my $to = $join->{to};
+ # Sanity check to make sure that we have the same from and to
+ # for all the same-named joins.
+ if ($from) {
+ all { $_->{from} eq $from } @$others_like_me
+ or die "Not all same-named joins have identical 'from': "
+ . Dumper($join, $others_like_me);
+ }
+ if ($to) {
+ all { $_->{to} eq $to } @$others_like_me
+ or die "Not all same-named joins have identical 'to': "
+ . Dumper($join, $others_like_me);
+ }
+
+ # We don't need to call uniq here--translate_join will do that
+ # for us.
+ my @conditions = map { @{ $_->{extra} || [] } }
+ ($join, @$others_like_me);
+ $join->{extra} = \@conditions;
+ $joins = $the_rest;
+ }
+ push(@result, $join);
+ }
+
+ return @result;
+}
+
+# Takes all the "then_to" items and just puts them as the next item in
+# the array. Right now this only does one level of "then_to", but we
+# could re-write this to handle then_to recursively if we need more levels.
+sub _extract_then_to {
+ my ($self, $joins) = @_;
+ my @result;
+ foreach my $join (@$joins) {
+ push(@result, $join);
+ if (my $then_to = $join->{then_to}) {
+ push(@result, $then_to);
+ }
+ }
+ return @result;
+}
+
+# JOIN statements for the SELECT and ORDER BY columns. This should not be
+# called until the moment it is needed, because _select_columns might be
+# modified by the charts.
+sub _select_order_joins {
+ my ($self) = @_;
+ my @joins;
+ foreach my $field ($self->_select_columns) {
+ my @column_join = $self->_column_join($field);
+ push(@joins, @column_join);
+ }
+ foreach my $field ($self->_input_order_columns) {
+ my $join_info = $self->_special_order->{$field}->{join};
+ if ($join_info) {
+ # Don't let callers modify SPECIAL_ORDER.
+ $join_info = dclone($join_info);
+ if (!$join_info->{as}) {
+ $join_info->{as} = "map_$field";
+ }
+ push(@joins, $join_info);
+ }
+ }
+ return @joins;
+}
+
+# These are the joins that are *always* in the FROM clause.
+sub _standard_joins {
+ my ($self) = @_;
+ my $user = $self->_user;
+ my @joins;
+
+ my $security_join = {
+ table => 'bug_group_map',
+ as => 'security_map',
+ };
+ push(@joins, $security_join);
+
+ if ($user->id) {
+ $security_join->{extra} =
+ ["NOT (" . $user->groups_in_sql('security_map.group_id') . ")"];
+
+ my $security_cc_join = {
+ table => 'cc',
+ as => 'security_cc',
+ extra => ['security_cc.who = ' . $user->id],
+ };
+ push(@joins, $security_cc_join);
+ }
+
+ return @joins;
+}
+
+sub _sql_from {
+ my ($self, $joins_input) = @_;
+ my @joins = ($self->_standard_joins, $self->_select_order_joins,
+ @$joins_input);
+ @joins = $self->_extract_then_to(\@joins);
+ @joins = $self->_combine_joins(\@joins);
+ my @join_sql = map { $self->_translate_join($_) } @joins;
+ return "bugs\n" . join("\n", @join_sql);
+}
+
+# This takes a join data structure and turns it into actual JOIN SQL.
+sub _translate_join {
+ my ($self, $join_info) = @_;
+
+ die "join with no table: " . Dumper($join_info) if !$join_info->{table};
+ die "join with no 'as': " . Dumper($join_info) if !$join_info->{as};
+
+ my $from_table = "bugs";
+ my $from = $join_info->{from} || "bug_id";
+ if ($from =~ /^(\w+)\.(\w+)$/) {
+ ($from_table, $from) = ($1, $2);
+ }
+ my $table = $join_info->{table};
+ my $name = $join_info->{as};
+ my $to = $join_info->{to} || "bug_id";
+ my $join = $join_info->{join} || 'LEFT';
+ my @extra = @{ $join_info->{extra} || [] };
+ $name =~ s/\./_/g;
+
+ # If a term contains ORs, we need to put parens around the condition.
+ # This is a pretty weak test, but it's actually OK to put parens
+ # around too many things.
+ @extra = map { $_ =~ /\bOR\b/i ? "($_)" : $_ } @extra;
+ my $extra_condition = join(' AND ', uniq @extra);
+ if ($extra_condition) {
+ $extra_condition = " AND $extra_condition";
+ }
+
+ my @join_sql = "$join JOIN $table AS $name"
+ . " ON $from_table.$from = $name.$to$extra_condition";
+ return @join_sql;
+}
+
+#############################
+# Internal Accessors: WHERE #
+#############################
+
+# Note: There's also quite a bit of stuff that affects the WHERE clause
+# in the "Internal Accessors: Boolean Charts" section.
+
+# The terms that are always in the WHERE clause. These implement bug
+# group security.
+sub _standard_where {
+ my ($self) = @_;
+ # If replication lags badly between the shadow db and the main DB,
+ # it's possible for bugs to show up in searches before their group
+ # controls are properly set. To prevent this, when initially creating
+ # bugs we set their creation_ts to NULL, and don't give them a creation_ts
+ # until their group controls are set. So if a bug has a NULL creation_ts,
+ # it shouldn't show up in searches at all.
+ my @where = ('bugs.creation_ts IS NOT NULL');
+
+ my $security_term = 'security_map.group_id IS NULL';
+
+ my $user = $self->_user;
+ if ($user->id) {
+ my $userid = $user->id;
+ # This indentation makes the resulting SQL more readable.
+ $security_term .= <<END;
+
+ OR (bugs.reporter_accessible = 1 AND bugs.reporter = $userid)
+ OR (bugs.cclist_accessible = 1 AND security_cc.who IS NOT NULL)
+ OR bugs.assigned_to = $userid
+END
+ if (Bugzilla->params->{'useqacontact'}) {
+ $security_term.= " OR bugs.qa_contact = $userid";
+ }
+ $security_term = "($security_term)";
+ }
+
+ push(@where, $security_term);
+
+ return @where;
+}
+
+sub _sql_where {
+ my ($self, $main_clause) = @_;
+ # The newline and this particular spacing makes the resulting
+ # SQL a bit more readable for debugging.
+ my $where = join("\n AND ", $self->_standard_where);
+ my $clause_sql = $main_clause->as_string;
+ if ($clause_sql) {
+ $where .= "\n AND " . $clause_sql;
+ }
+ elsif (!Bugzilla->params->{'search_allow_no_criteria'}
+ && !$self->{allow_unlimited})
+ {
+ ThrowUserError('buglist_parameters_required');
+ }
+ return $where;
+}
+
+################################
+# Internal Accessors: GROUP BY #
+################################
+
+# And these are the fields that we have to do GROUP BY for in DBs
+# that are more strict about putting everything into GROUP BY.
+sub _sql_group_by {
+ my ($self) = @_;
+
+ # Strict DBs require every element from the SELECT to be in the GROUP BY,
+ # unless that element is being used in an aggregate function.
+ my @extra_group_by;
+ foreach my $column ($self->_select_columns) {
+ next if $self->_skip_group_by->{$column};
+ my $sql = $self->COLUMNS->{$column}->{name};
+ push(@extra_group_by, $sql);
+ }
+
+ # And all items from ORDER BY must be in the GROUP BY. The above loop
+ # doesn't catch items that were put into the ORDER BY from SPECIAL_ORDER.
+ foreach my $column ($self->_input_order_columns) {
+ my $special_order = $self->_special_order->{$column}->{order};
+ next if !$special_order;
+ push(@extra_group_by, @$special_order);
+ }
+
+ @extra_group_by = uniq @extra_group_by;
+
+ # bug_id is the only field we actually group by.
+ return ('bugs.bug_id', join(',', @extra_group_by));
+}
+
+# A helper for _sql_group_by.
+sub _skip_group_by {
+ my ($self) = @_;
+ return $self->{skip_group_by} if $self->{skip_group_by};
+ my @skip_list = GROUP_BY_SKIP;
+ push(@skip_list, keys %{ $self->_multi_select_fields });
+ my %skip_hash = map { $_ => 1 } @skip_list;
+ $self->{skip_group_by} = \%skip_hash;
+ return $self->{skip_group_by};
+}
+
+##############################################
+# Internal Accessors: Special Params Parsing #
+##############################################
+
+# Backwards compatibility for old field names.
+sub _convert_old_params {
+ my ($self) = @_;
+ my $params = $self->_params;
+
+ # bugidtype has different values in modern Search.pm.
+ if (defined $params->{'bugidtype'}) {
+ my $value = $params->{'bugidtype'};
+ $params->{'bugidtype'} = $value eq 'exclude' ? 'nowords' : 'anyexact';
+ }
+
+ foreach my $old_name (keys %{ FIELD_MAP() }) {
+ if (defined $params->{$old_name}) {
+ my $new_name = FIELD_MAP->{$old_name};
+ $params->{$new_name} = delete $params->{$old_name};
+ }
+ }
+}
+
+# This parses all the standard search parameters except for the boolean
+# charts.
+sub _special_charts {
+ my ($self) = @_;
+ $self->_convert_old_params();
+ $self->_special_parse_bug_status();
+ $self->_special_parse_resolution();
+ my $clause = new Bugzilla::Search::Clause();
+ $clause->add( $self->_parse_basic_fields() );
+ $clause->add( $self->_special_parse_email() );
+ $clause->add( $self->_special_parse_chfield() );
+ $clause->add( $self->_special_parse_deadline() );
+ return $clause;
+}
+
+sub _parse_basic_fields {
+ my ($self) = @_;
+ my $params = $self->_params;
+ my $chart_fields = $self->_chart_fields;
+
+ my $clause = new Bugzilla::Search::Clause();
+ foreach my $field_name (keys %$chart_fields) {
+ # CGI params shouldn't have periods in them, so we only accept
+ # period-separated fields with underscores where the periods go.
+ my $param_name = $field_name;
+ $param_name =~ s/\./_/g;
+ my @values = $self->_param_array($param_name);
+ next if !@values;
+ my $default_op = $param_name eq 'content' ? 'matches' : 'anyexact';
+ my $operator = $params->{"${param_name}_type"} || $default_op;
+ # Fields that are displayed as multi-selects are passed as arrays,
+ # so that they can properly search values that contain commas.
+ # However, other fields are sent as strings, so that they are properly
+ # split on commas if required.
+ my $field = $chart_fields->{$field_name};
+ my $pass_value;
+ if ($field->is_select or $field->name eq 'version'
+ or $field->name eq 'target_milestone')
+ {
+ $pass_value = \@values;
+ }
+ else {
+ $pass_value = join(',', @values);
+ }
+ $clause->add($field_name, $operator, $pass_value);
+ }
+ return $clause;
+}
+
+sub _special_parse_bug_status {
+ my ($self) = @_;
+ my $params = $self->_params;
+ return if !defined $params->{'bug_status'};
+ # We want to allow the bug_status_type parameter to work normally,
+ # meaning that this special code should only be activated if we are
+ # doing the normal "anyexact" search on bug_status.
+ return if (defined $params->{'bug_status_type'}
+ and $params->{'bug_status_type'} ne 'anyexact');
+
+ my @bug_status = $self->_param_array('bug_status');
+ # Also include inactive bug statuses, as you can query them.
+ my $legal_statuses = $self->_chart_fields->{'bug_status'}->legal_values;
+
+ # If the status contains __open__ or __closed__, translate those
+ # into their equivalent lists of open and closed statuses.
+ if (grep { $_ eq '__open__' } @bug_status) {
+ my @open = grep { $_->is_open } @$legal_statuses;
+ @open = map { $_->name } @open;
+ push(@bug_status, @open);
+ }
+ if (grep { $_ eq '__closed__' } @bug_status) {
+ my @closed = grep { not $_->is_open } @$legal_statuses;
+ @closed = map { $_->name } @closed;
+ push(@bug_status, @closed);
+ }
+
+ @bug_status = uniq @bug_status;
+ my $all = grep { $_ eq "__all__" } @bug_status;
+ # This will also handle removing __open__ and __closed__ for us
+ # (__all__ too, which is why we check for it above, first).
+ @bug_status = _valid_values(\@bug_status, $legal_statuses);
+
+ # If the user has selected every status, change to selecting none.
+ # This is functionally equivalent, but quite a lot faster.
+ if ($all or scalar(@bug_status) == scalar(@$legal_statuses)) {
+ delete $params->{'bug_status'};
+ }
+ else {
+ $params->{'bug_status'} = \@bug_status;
+ }
+}
+
+sub _special_parse_chfield {
+ my ($self) = @_;
+ my $params = $self->_params;
+
+ my $date_from = trim(lc($params->{'chfieldfrom'} || ''));
+ my $date_to = trim(lc($params->{'chfieldto'} || ''));
+ $date_from = '' if $date_from eq 'now';
+ $date_to = '' if $date_to eq 'now';
+ my @fields = $self->_param_array('chfield');
+ my $value_to = $params->{'chfieldvalue'};
+ $value_to = '' if !defined $value_to;
+
+ @fields = map { $_ eq '[Bug creation]' ? 'creation_ts' : $_ } @fields;
+
+ my $clause = new Bugzilla::Search::Clause();
+
+ # It is always safe and useful to push delta_ts into the charts
+ # if there is a "from" date specified. It doesn't conflict with
+ # searching [Bug creation], because a bug's delta_ts is set to
+ # its creation_ts when it is created. So this just gives the
+ # database an additional index to possibly choose, on a table that
+ # is smaller than bugs_activity.
+ if ($date_from ne '') {
+ $clause->add('delta_ts', 'greaterthaneq', $date_from);
+ }
+ # It's not normally safe to do it for "to" dates, though--"chfieldto" means
+ # "a field that changed before this date", and delta_ts could be either
+ # later or earlier than that, if we're searching for the time that a field
+ # changed. However, chfieldto all by itself, without any chfieldvalue or
+ # chfield, means "just search delta_ts", and so we still want that to
+ # work.
+ if ($date_to ne '' and !@fields and $value_to eq '') {
+ $clause->add('delta_ts', 'lessthaneq', $date_to);
+ }
+
+ # Basically, we construct the chart like:
+ #
+ # (added_for_field1 = value OR added_for_field2 = value)
+ # AND (date_field1_changed >= date_from OR date_field2_changed >= date_from)
+ # AND (date_field1_changed <= date_to OR date_field2_changed <= date_to)
+ #
+ # Theoretically, all we *really* would need to do is look for the field id
+ # in the bugs_activity table, because we've already limited the search
+ # by delta_ts above, but there's no chart to do that, so we check the
+ # change date of the fields.
+
+ if ($value_to ne '') {
+ my $value_clause = new Bugzilla::Search::Clause('OR');
+ foreach my $field (@fields) {
+ $value_clause->add($field, 'changedto', $value_to);
+ }
+ $clause->add($value_clause);
+ }
+
+ if ($date_from ne '') {
+ my $from_clause = new Bugzilla::Search::Clause('OR');
+ foreach my $field (@fields) {
+ $from_clause->add($field, 'changedafter', $date_from);
+ }
+ $clause->add($from_clause);
+ }
+ if ($date_to ne '') {
+ # chfieldto is supposed to be a relative date or a date of the form
+ # YYYY-MM-DD, i.e. without the time appended to it. We append the
+ # time ourselves so that the end date is correctly taken into account.
+ $date_to .= ' 23:59:59' if $date_to =~ /^\d{4}-\d{1,2}-\d{1,2}$/;
+
+ my $to_clause = new Bugzilla::Search::Clause('OR');
+ foreach my $field (@fields) {
+ $to_clause->add($field, 'changedbefore', $date_to);
+ }
+ $clause->add($to_clause);
+ }
+
+ return $clause;
+}
+
+sub _special_parse_deadline {
+ my ($self) = @_;
+ return if !$self->_user->is_timetracker;
+ my $params = $self->_params;
+
+ my $clause = new Bugzilla::Search::Clause();
+ if (my $from = $params->{'deadlinefrom'}) {
+ $clause->add('deadline', 'greaterthaneq', $from);
+ }
+ if (my $to = $params->{'deadlineto'}) {
+ $clause->add('deadline', 'lessthaneq', $to);
+ }
+
+ return $clause;
+}
+
+sub _special_parse_email {
+ my ($self) = @_;
+ my $params = $self->_params;
+
+ my @email_params = grep { $_ =~ /^email\d+$/ } keys %$params;
+
+ my $clause = new Bugzilla::Search::Clause();
+ foreach my $param (@email_params) {
+ $param =~ /(\d+)$/;
+ my $id = $1;
+ my $email = trim($params->{"email$id"});
+ next if !$email;
+ my $type = $params->{"emailtype$id"} || 'anyexact';
+ $type = "anyexact" if $type eq "exact";
+
+ my $or_clause = new Bugzilla::Search::Clause('OR');
+ foreach my $field (qw(assigned_to reporter cc qa_contact)) {
+ if ($params->{"email$field$id"}) {
+ $or_clause->add($field, $type, $email);
+ }
+ }
+ if ($params->{"emaillongdesc$id"}) {
+ $or_clause->add("commenter", $type, $email);
+ }
+
+ $clause->add($or_clause);
+ }
+
+ return $clause;
+}
+
+sub _special_parse_resolution {
+ my ($self) = @_;
+ my $params = $self->_params;
+ return if !defined $params->{'resolution'};
+
+ my @resolution = $self->_param_array('resolution');
+ my $legal_resolutions = $self->_chart_fields->{resolution}->legal_values;
+ @resolution = _valid_values(\@resolution, $legal_resolutions, '---');
+ if (scalar(@resolution) == scalar(@$legal_resolutions)) {
+ delete $params->{'resolution'};
+ }
+}
+
+sub _valid_values {
+ my ($input, $valid, $extra_value) = @_;
+ my @result;
+ foreach my $item (@$input) {
+ $item = trim($item);
+ if (defined $extra_value and $item eq $extra_value) {
+ push(@result, $item);
+ }
+ elsif (grep { $_->name eq $item } @$valid) {
+ push(@result, $item);
+ }
+ }
+ return @result;
+}
+
+######################################
+# Internal Accessors: Boolean Charts #
+######################################
+
+sub _charts_to_conditions {
+ my ($self) = @_;
+
+ my $clause = $self->_charts;
+ my @joins;
+ $clause->walk_conditions(sub {
+ my ($condition) = @_;
+ return if !$condition->translated;
+ push(@joins, @{ $condition->translated->{joins} });
+ });
+ return (\@joins, $clause);
+}
+
+sub _charts {
+ my ($self) = @_;
+
+ my $clause = $self->_params_to_data_structure();
+ my $chart_id = 0;
+ $clause->walk_conditions(sub { $self->_handle_chart($chart_id++, @_) });
+ return $clause;
+}
+
+sub _params_to_data_structure {
+ my ($self) = @_;
+
+ # First we get the "special" charts, representing all the normal
+ # field son the search page. This may modify _params, so it needs to
+ # happen first.
+ my $clause = $self->_special_charts;
+
+ # Then we process the old Boolean Charts input format.
+ $clause->add( $self->_boolean_charts );
+
+ # And then process the modern "custom search" format.
+ $clause->add( $self->_custom_search );
+
+ return $clause;
+}
+
+sub _boolean_charts {
+ my ($self) = @_;
+
+ my $params = $self->_params;
+ my @param_list = keys %$params;
+
+ my @all_field_params = grep { /^field-?\d+/ } @param_list;
+ my @chart_ids = map { /^field(-?\d+)/; $1 } @all_field_params;
+ @chart_ids = sort { $a <=> $b } uniq @chart_ids;
+
+ my $clause = new Bugzilla::Search::Clause();
+ foreach my $chart_id (@chart_ids) {
+ my @all_and = grep { /^field$chart_id-\d+/ } @param_list;
+ my @and_ids = map { /^field$chart_id-(\d+)/; $1 } @all_and;
+ @and_ids = sort { $a <=> $b } uniq @and_ids;
+
+ my $and_clause = new Bugzilla::Search::Clause();
+ foreach my $and_id (@and_ids) {
+ my @all_or = grep { /^field$chart_id-$and_id-\d+/ } @param_list;
+ my @or_ids = map { /^field$chart_id-$and_id-(\d+)/; $1 } @all_or;
+ @or_ids = sort { $a <=> $b } uniq @or_ids;
+
+ my $or_clause = new Bugzilla::Search::Clause('OR');
+ foreach my $or_id (@or_ids) {
+ my $identifier = "$chart_id-$and_id-$or_id";
+ my $field = $params->{"field$identifier"};
+ my $operator = $params->{"type$identifier"};
+ my $value = $params->{"value$identifier"};
+ $or_clause->add($field, $operator, $value);
+ }
+ $and_clause->add($or_clause);
+ $and_clause->negate(1) if $params->{"negate$chart_id"};
+ }
+ $clause->add($and_clause);
+ }
+
+ return $clause;
+}
+
+sub _custom_search {
+ my ($self) = @_;
+ my $params = $self->_params;
+
+ my $current_clause = new Bugzilla::Search::Clause($params->{j_top});
+ my @clause_stack;
+ foreach my $id ($self->_field_ids) {
+ my $field = $params->{"f$id"};
+ if ($field eq 'OP') {
+ my $joiner = $params->{"j$id"};
+ my $new_clause = new Bugzilla::Search::Clause($joiner);
+ $new_clause->negate($params->{"n$id"});
+ $current_clause->add($new_clause);
+ push(@clause_stack, $current_clause);
+ $current_clause = $new_clause;
+ next;
+ }
+ if ($field eq 'CP') {
+ $current_clause = pop @clause_stack;
+ ThrowCodeError('search_cp_without_op', { id => $id })
+ if !$current_clause;
+ next;
+ }
+
+ my $operator = $params->{"o$id"};
+ my $value = $params->{"v$id"};
+ my $condition = condition($field, $operator, $value);
+ $condition->negate($params->{"n$id"});
+ $current_clause->add($condition);
+ }
+
+ # We allow people to specify more OPs than CPs, so at the end of the
+ # loop our top clause may be still in the stack instead of being
+ # $current_clause.
+ return $clause_stack[0] || $current_clause;
+}
+
+sub _field_ids {
+ my ($self) = @_;
+ my $params = $self->_params;
+ my @param_list = keys %$params;
+
+ my @field_params = grep { /^f\d+$/ } @param_list;
+ my @field_ids = map { /(\d+)/; $1 } @field_params;
+ @field_ids = sort { $a <=> $b } @field_ids;
+ return @field_ids;
+}
+
+sub _handle_chart {
+ my ($self, $chart_id, $condition) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $params = $self->_params;
+ my ($field, $operator, $value) = $condition->fov;
+
+ $field = FIELD_MAP->{$field} || $field;
+
+ return if (!defined $field or !defined $operator or !defined $value);
+
+ my $string_value;
+ if (ref $value eq 'ARRAY') {
+ # Trim input and ignore blank values.
+ @$value = map { trim($_) } @$value;
+ @$value = grep { defined $_ and $_ ne '' } @$value;
+ return if !@$value;
+ $string_value = join(',', @$value);
+ }
+ else {
+ return if $value eq '';
+ $string_value = $value;
+ }
+
+ $self->_chart_fields->{$field}
+ or ThrowCodeError("invalid_field_name", { field => $field });
+ trick_taint($field);
+
+ # This is the field as you'd reference it in a SQL statement.
+ my $full_field = $field =~ /\./ ? $field : "bugs.$field";
+
+ # "value" and "quoted" are for search functions that always operate
+ # on a scalar string and never care if they were passed multiple
+ # parameters. If the user does pass multiple parameters, they will
+ # become a space-separated string for those search functions.
+ #
+ # all_values is for search functions that do operate
+ # on multiple values, like anyexact.
+
+ my %search_args = (
+ chart_id => $chart_id,
+ sequence => $chart_id,
+ field => $field,
+ full_field => $full_field,
+ operator => $operator,
+ value => $string_value,
+ all_values => $value,
+ joins => [],
+ );
+ $search_args{quoted} = $self->_quote_unless_numeric(\%search_args);
+ # This should add a "term" selement to %search_args.
+ $self->do_search_function(\%search_args);
+
+ # If term is left empty, then this means the criteria
+ # has no effect and can be ignored.
+ return unless $search_args{term};
+
+ # All the things here that don't get pulled out of
+ # %search_args are their original values before
+ # do_search_function modified them.
+ $self->search_description({
+ field => $field, type => $operator,
+ value => $string_value, term => $search_args{term},
+ });
+
+ $condition->translated(\%search_args);
+}
+
+##################################
+# do_search_function And Helpers #
+##################################
+
+# This takes information about the current boolean chart and translates
+# it into SQL, using the constants at the top of this file.
+sub do_search_function {
+ my ($self, $args) = @_;
+ my ($field, $operator) = @$args{qw(field operator)};
+
+ if (my $parse_func = SPECIAL_PARSING->{$field}) {
+ $self->$parse_func($args);
+ # Some parsing functions set $term, though most do not.
+ # For the ones that set $term, we don't need to do any further
+ # parsing.
+ return if $args->{term};
+ }
+
+ my $operator_field_override = $self->_get_operator_field_override();
+ my $override = $operator_field_override->{$field};
+ # Attachment fields get special handling, if they don't have a specific
+ # individual override.
+ if (!$override and $field =~ /^attachments\./) {
+ $override = $operator_field_override->{attachments};
+ }
+ # If there's still no override, check for an override on the field's type.
+ if (!$override) {
+ my $field_obj = $self->_chart_fields->{$field};
+ $override = $operator_field_override->{$field_obj->type};
+ }
+
+ if ($override) {
+ my $search_func = $self->_pick_override_function($override, $operator);
+ $self->$search_func($args) if $search_func;
+ }
+
+ # Some search functions set $term, and some don't. For the ones that
+ # don't (or for fields that don't have overrides) we now call the
+ # direct operator function from OPERATORS.
+ if (!defined $args->{term}) {
+ $self->_do_operator_function($args);
+ }
+
+ if (!defined $args->{term}) {
+ # This field and this type don't work together. Generally,
+ # this should never be reached, because it should be handled
+ # explicitly by OPERATOR_FIELD_OVERRIDE.
+ ThrowUserError("search_field_operator_invalid",
+ { field => $field, operator => $operator });
+ }
+}
+
+# A helper for various search functions that need to run operator
+# functions directly.
+sub _do_operator_function {
+ my ($self, $func_args) = @_;
+ my $operator = $func_args->{operator};
+ my $operator_func = OPERATORS->{$operator};
+ $self->$operator_func($func_args);
+}
+
+sub _reverse_operator {
+ my ($self, $operator) = @_;
+ my $reverse = OPERATOR_REVERSE->{$operator};
+ return $reverse if $reverse;
+ if ($operator =~ s/^not//) {
+ return $operator;
+ }
+ return "not$operator";
+}
+
+sub _pick_override_function {
+ my ($self, $override, $operator) = @_;
+ my $search_func = $override->{$operator};
+
+ if (!$search_func) {
+ # If we don't find an override for one specific operator,
+ # then there are some special override types:
+ # _non_changed: For any operator that doesn't have the word
+ # "changed" in it
+ # _default: Overrides all operators that aren't explicitly specified.
+ if ($override->{_non_changed} and $operator !~ /changed/) {
+ $search_func = $override->{_non_changed};
+ }
+ elsif ($override->{_default}) {
+ $search_func = $override->{_default};
+ }
+ }
+
+ return $search_func;
+}
+
+sub _get_operator_field_override {
+ my $self = shift;
+ my $cache = Bugzilla->request_cache;
+
+ return $cache->{operator_field_override}
+ if defined $cache->{operator_field_override};
+
+ my %operator_field_override = %{ OPERATOR_FIELD_OVERRIDE() };
+ Bugzilla::Hook::process('search_operator_field_override',
+ { search => $self,
+ operators => \%operator_field_override });
+
+ $cache->{operator_field_override} = \%operator_field_override;
+ return $cache->{operator_field_override};
+}
+
+sub _get_column_joins {
+ my $self = shift;
+ my $cache = Bugzilla->request_cache;
+
+ return $cache->{column_joins} if defined $cache->{column_joins};
+
+ my %column_joins = %{ COLUMN_JOINS() };
+ Bugzilla::Hook::process('buglist_column_joins',
+ { column_joins => \%column_joins });
+
+ $cache->{column_joins} = \%column_joins;
+ return $cache->{column_joins};
+}
+
+###########################
+# Search Function Helpers #
+###########################
+
+# When we're doing a numeric search against a numeric column, we want to
+# just put a number into the SQL instead of a string. On most DBs, this
+# is just a performance optimization, but on SQLite it actually changes
+# the behavior of some searches.
+sub _quote_unless_numeric {
+ my ($self, $args, $value) = @_;
+ if (!defined $value) {
+ $value = $args->{value};
+ }
+ my ($field, $operator) = @$args{qw(field operator)};
+
+ my $numeric_operator = !grep { $_ eq $operator } NON_NUMERIC_OPERATORS;
+ my $numeric_field = $self->_chart_fields->{$field}->is_numeric;
+ my $numeric_value = ($value =~ NUMBER_REGEX) ? 1 : 0;
+ my $is_numeric = $numeric_operator && $numeric_field && $numeric_value;
+ if ($is_numeric) {
+ my $quoted = $value;
+ trick_taint($quoted);
+ return $quoted;
+ }
+ return Bugzilla->dbh->quote($value);
+}
+
+sub build_subselect {
+ my ($outer, $inner, $table, $cond) = @_;
+ return "$outer IN (SELECT $inner FROM $table WHERE $cond)";
+}
+
+# Used by anyexact to get the list of input values. This allows us to
+# support values with commas inside of them in the standard charts, and
+# still accept string values for the boolean charts (and split them on
+# commas).
+sub _all_values {
+ my ($self, $args, $split_on) = @_;
+ $split_on ||= qr/[\s,]+/;
+ my $dbh = Bugzilla->dbh;
+ my $all_values = $args->{all_values};
+
+ my @array;
+ if (ref $all_values eq 'ARRAY') {
+ @array = @$all_values;
+ }
+ else {
+ @array = split($split_on, $all_values);
+ @array = map { trim($_) } @array;
+ @array = grep { defined $_ and $_ ne '' } @array;
+ }
+
+ if ($args->{field} eq 'resolution') {
+ @array = map { $_ eq '---' ? '' : $_ } @array;
+ }
+
+ return @array;
+}
+
+# Support for "any/all/nowordssubstr" comparison type ("words as substrings")
+sub _substring_terms {
+ my ($self, $args) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # We don't have to (or want to) use _all_values, because we'd just
+ # split each term on spaces and commas anyway.
+ my @words = split(/[\s,]+/, $args->{value});
+ @words = grep { defined $_ and $_ ne '' } @words;
+ @words = map { $dbh->quote($_) } @words;
+ my @terms = map { $dbh->sql_iposition($_, $args->{full_field}) . " > 0" }
+ @words;
+ return @terms;
+}
+
+sub _word_terms {
+ my ($self, $args) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my @values = split(/[\s,]+/, $args->{value});
+ @values = grep { defined $_ and $_ ne '' } @values;
+ my @substring_terms = $self->_substring_terms($args);
+
+ my @terms;
+ my $start = $dbh->WORD_START;
+ my $end = $dbh->WORD_END;
+ foreach my $word (@values) {
+ my $regex = $start . quotemeta($word) . $end;
+ my $quoted = $dbh->quote($regex);
+ # We don't have to check the regexp, because we escaped it, so we're
+ # sure it's valid.
+ my $regex_term = $dbh->sql_regexp($args->{full_field}, $quoted,
+ 'no check');
+ # Regular expressions are slow--substring searches are faster.
+ # If we're searching for a word, we're also certain that the
+ # substring will appear in the value. So we limit first by
+ # substring and then by a regex that will match just words.
+ my $substring_term = shift @substring_terms;
+ push(@terms, "$substring_term AND $regex_term");
+ }
+
+ return @terms;
+}
+
+#####################################
+# "Special Parsing" Functions: Date #
+#####################################
+
+sub _timestamp_translate {
+ my ($self, $args) = @_;
+ my $value = $args->{value};
+ my $dbh = Bugzilla->dbh;
+
+ return if $value !~ /^(?:[\+\-]?\d+[hdwmy]s?|now)$/i;
+
+ # By default, the time is appended to the date, which we don't want
+ # for deadlines.
+ $value = SqlifyDate($value);
+ if ($args->{field} eq 'deadline') {
+ ($value) = split(/\s/, $value);
+ }
+ $args->{value} = $value;
+ $args->{quoted} = $dbh->quote($value);
+}
+
sub SqlifyDate {
my ($str) = @_;
- $str = "" if !defined $str;
+ my $fmt = "%Y-%m-%d %H:%M:%S";
+ $str = "" if (!defined $str || lc($str) eq 'now');
if ($str eq "") {
my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime(time());
return sprintf("%4d-%02d-%02d 00:00:00", $year+1900, $month+1, $mday);
}
-
- if ($str =~ /^(-|\+)?(\d+)([hHdDwWmMyY])$/) { # relative date
- my ($sign, $amount, $unit, $date) = ($1, $2, lc $3, time);
+ if ($str =~ /^(-|\+)?(\d+)([hdwmy])(s?)$/i) { # relative date
+ my ($sign, $amount, $unit, $startof, $date) = ($1, $2, lc $3, lc $4, time);
my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime($date);
if ($sign && $sign eq '+') { $amount = -$amount; }
+ $startof = 1 if $amount == 0;
if ($unit eq 'w') { # convert weeks to days
- $amount = 7*$amount + $wday;
+ $amount = 7*$amount;
+ $amount += $wday if $startof;
$unit = 'd';
}
if ($unit eq 'd') {
- $date -= $sec + 60*$min + 3600*$hour + 24*3600*$amount;
- return time2str("%Y-%m-%d %H:%M:%S", $date);
+ if ($startof) {
+ $fmt = "%Y-%m-%d 00:00:00";
+ $date -= $sec + 60*$min + 3600*$hour;
+ }
+ $date -= 24*3600*$amount;
+ return time2str($fmt, $date);
}
elsif ($unit eq 'y') {
- return sprintf("%4d-01-01 00:00:00", $year+1900-$amount);
+ if ($startof) {
+ return sprintf("%4d-01-01 00:00:00", $year+1900-$amount);
+ }
+ else {
+ return sprintf("%4d-%02d-%02d %02d:%02d:%02d",
+ $year+1900-$amount, $month+1, $mday, $hour, $min, $sec);
+ }
}
elsif ($unit eq 'm') {
$month -= $amount;
while ($month<0) { $year--; $month += 12; }
- return sprintf("%4d-%02d-01 00:00:00", $year+1900, $month+1);
+ if ($startof) {
+ return sprintf("%4d-%02d-01 00:00:00", $year+1900, $month+1);
+ }
+ else {
+ return sprintf("%4d-%02d-%02d %02d:%02d:%02d",
+ $year+1900, $month+1, $mday, $hour, $min, $sec);
+ }
}
elsif ($unit eq 'h') {
- # Special case 0h for 'beginning of this hour'
- if ($amount == 0) {
- $date -= $sec + 60*$min;
- } else {
- $date -= 3600*$amount;
- }
- return time2str("%Y-%m-%d %H:%M:%S", $date);
+ # Special case for 'beginning of an hour'
+ if ($startof) {
+ $fmt = "%Y-%m-%d %H:00:00";
+ }
+ $date -= 3600*$amount;
+ return time2str($fmt, $date);
}
return undef; # should not happen due to regexp at top
}
@@ -870,63 +1997,12 @@
if (!defined($date)) {
ThrowUserError("illegal_date", { date => $str });
}
- return time2str("%Y-%m-%d %H:%M:%S", $date);
+ return time2str($fmt, $date);
}
-sub build_subselect {
- my ($outer, $inner, $table, $cond) = @_;
- my $q = "SELECT $inner FROM $table WHERE $cond";
- #return "$outer IN ($q)";
- my $dbh = Bugzilla->dbh;
- my $list = $dbh->selectcol_arrayref($q);
- return "1=2" unless @$list; # Could use boolean type on dbs which support it
- return $dbh->sql_in($outer, $list);}
-
-sub GetByWordList {
- my ($field, $strs) = (@_);
- my @list;
- my $dbh = Bugzilla->dbh;
-
- foreach my $w (split(/[\s,]+/, $strs)) {
- my $word = $w;
- if ($word ne "") {
- $word =~ tr/A-Z/a-z/;
- $word = $dbh->quote('(^|[^a-z0-9])' . quotemeta($word) . '($|[^a-z0-9])');
- trick_taint($word);
- push(@list, $dbh->sql_regexp($field, $word));
- }
- }
-
- return \@list;
-}
-
-# Support for "any/all/nowordssubstr" comparison type ("words as substrings")
-sub GetByWordListSubstr {
- my ($field, $strs) = (@_);
- my @list;
- my $dbh = Bugzilla->dbh;
- my $sql_word;
-
- foreach my $word (split(/[\s,]+/, $strs)) {
- if ($word ne "") {
- $sql_word = $dbh->quote($word);
- trick_taint($sql_word);
- push(@list, $dbh->sql_iposition($sql_word, $field) . " > 0");
- }
- }
-
- return \@list;
-}
-
-sub getSQL {
- my $self = shift;
- return $self->{'sql'};
-}
-
-sub getDebugData {
- my $self = shift;
- return $self->{'debugdata'};
-}
+######################################
+# "Special Parsing" Functions: Users #
+######################################
sub pronoun {
my ($noun, $user) = (@_);
@@ -944,11 +2020,809 @@
return "bugs.assigned_to";
}
if ($noun eq "%qacontact%") {
- return "bugs.qa_contact";
+ return "COALESCE(bugs.qa_contact,0)";
}
return 0;
}
+sub _contact_pronoun {
+ my ($self, $args) = @_;
+ my $value = $args->{value};
+ my $user = $self->_user;
+
+ if ($value =~ /^\%group/) {
+ $self->_contact_exact_group($args);
+ }
+ elsif ($value =~ /^(%\w+%)$/) {
+ $args->{value} = pronoun($1, $user);
+ $args->{quoted} = $args->{value};
+ $args->{value_is_id} = 1;
+ }
+}
+
+sub _contact_exact_group {
+ my ($self, $args) = @_;
+ my ($value, $operator, $field, $chart_id, $joins) =
+ @$args{qw(value operator field chart_id joins)};
+ my $dbh = Bugzilla->dbh;
+ my $user = $self->_user;
+
+ $value =~ /\%group\.([^%]+)%/;
+ my $group = Bugzilla::Group->check({ name => $1, _error => 'invalid_group_name' });
+ $group->check_members_are_visible();
+ $user->in_group($group)
+ || ThrowUserError('invalid_group_name', {name => $group->name});
+
+ my $group_ids = Bugzilla::Group->flatten_group_membership($group->id);
+ my $table = "user_group_map_$chart_id";
+ my $join = {
+ table => 'user_group_map',
+ as => $table,
+ from => $field,
+ to => 'user_id',
+ extra => [$dbh->sql_in("$table.group_id", $group_ids),
+ "$table.isbless = 0"],
+ };
+ push(@$joins, $join);
+ if ($operator =~ /^not/) {
+ $args->{term} = "$table.group_id IS NULL";
+ }
+ else {
+ $args->{term} = "$table.group_id IS NOT NULL";
+ }
+}
+
+sub _cc_pronoun {
+ my ($self, $args) = @_;
+ my ($full_field, $value) = @$args{qw(full_field value)};
+ my $user = $self->_user;
+
+ if ($value =~ /\%group/) {
+ return $self->_cc_exact_group($args);
+ }
+ elsif ($value =~ /^(%\w+%)$/) {
+ $args->{value} = pronoun($1, $user);
+ $args->{quoted} = $args->{value};
+ $args->{value_is_id} = 1;
+ }
+}
+
+sub _cc_exact_group {
+ my ($self, $args) = @_;
+ my ($chart_id, $sequence, $joins, $operator, $value) =
+ @$args{qw(chart_id sequence joins operator value)};
+ my $user = $self->_user;
+ my $dbh = Bugzilla->dbh;
+
+ $value =~ m/%group\.([^%]+)%/;
+ my $group = Bugzilla::Group->check({ name => $1, _error => 'invalid_group_name' });
+ $group->check_members_are_visible();
+ $user->in_group($group)
+ || ThrowUserError('invalid_group_name', {name => $group->name});
+
+ my $all_groups = Bugzilla::Group->flatten_group_membership($group->id);
+
+ # This is for the email1, email2, email3 fields from query.cgi.
+ if ($chart_id eq "") {
+ $chart_id = "CC$$sequence";
+ $args->{sequence}++;
+ }
+
+ my $cc_table = "cc_$chart_id";
+ push(@$joins, { table => 'cc', as => $cc_table });
+ my $group_table = "user_group_map_$chart_id";
+ my $group_join = {
+ table => 'user_group_map',
+ as => $group_table,
+ from => "$cc_table.who",
+ to => 'user_id',
+ extra => [$dbh->sql_in("$group_table.group_id", $all_groups),
+ "$group_table.isbless = 0"],
+ };
+ push(@$joins, $group_join);
+
+ if ($operator =~ /^not/) {
+ $args->{term} = "$group_table.group_id IS NULL";
+ }
+ else {
+ $args->{term} = "$group_table.group_id IS NOT NULL";
+ }
+}
+
+# XXX This should probably be merged with cc_pronoun.
+sub _commenter_pronoun {
+ my ($self, $args) = @_;
+ my $value = $args->{value};
+ my $user = $self->_user;
+
+ if ($value =~ /^(%\w+%)$/) {
+ $args->{value} = pronoun($1, $user);
+ $args->{quoted} = $args->{value};
+ $args->{value_is_id} = 1;
+ }
+}
+
+#####################################################################
+# Search Functions
+#####################################################################
+
+sub _invalid_combination {
+ my ($self, $args) = @_;
+ my ($field, $operator) = @$args{qw(field operator)};
+ ThrowUserError('search_field_operator_invalid',
+ { field => $field, operator => $operator });
+}
+
+# For all the "user" fields--assigned_to, reporter, qa_contact,
+# cc, commenter, requestee, etc.
+sub _user_nonchanged {
+ my ($self, $args) = @_;
+ my ($field, $operator, $chart_id, $sequence, $joins) =
+ @$args{qw(field operator chart_id sequence joins)};
+
+ my $is_in_other_table;
+ if (my $join = USER_FIELDS->{$field}->{join}) {
+ $is_in_other_table = 1;
+ my $as = "${field}_$chart_id";
+ # Needed for setters.login_name and requestees.login_name.
+ # Otherwise when we try to join "profiles" below, we'd get
+ # something like "setters.login_name.login_name" in the "from".
+ $as =~ s/\./_/g;
+ # This helps implement the email1, email2, etc. parameters.
+ if ($chart_id =~ /default/) {
+ $as .= "_$sequence";
+ }
+ my $isprivate = USER_FIELDS->{$field}->{isprivate};
+ my $extra = ($isprivate and !$self->_user->is_insider)
+ ? ["$as.isprivate = 0"] : [];
+ # We want to copy $join so as not to modify USER_FIELDS.
+ push(@$joins, { %$join, as => $as, extra => $extra });
+ my $search_field = USER_FIELDS->{$field}->{field};
+ $args->{full_field} = "$as.$search_field";
+ }
+
+ my $is_nullable = USER_FIELDS->{$field}->{nullable};
+ my $null_alternate = "''";
+ # When using a pronoun, we use the userid, and we don't have to
+ # join the profiles table.
+ if ($args->{value_is_id}) {
+ $null_alternate = 0;
+ }
+ else {
+ my $as = "name_${field}_$chart_id";
+ # For fields with periods in their name.
+ $as =~ s/\./_/;
+ my $join = {
+ table => 'profiles',
+ as => $as,
+ from => $args->{full_field},
+ to => 'userid',
+ join => (!$is_in_other_table and !$is_nullable) ? 'INNER' : undef,
+ };
+ push(@$joins, $join);
+ $args->{full_field} = "$as.login_name";
+ }
+
+ # We COALESCE fields that can be NULL, to make "not"-style operators
+ # continue to work properly. For example, "qa_contact is not equal to bob"
+ # should also show bugs where the qa_contact is NULL. With COALESCE,
+ # it does.
+ if ($is_nullable) {
+ $args->{full_field} = "COALESCE($args->{full_field}, $null_alternate)";
+ }
+
+ # For fields whose values are stored in other tables, negation (NOT)
+ # only works properly if we put the condition into the JOIN instead
+ # of the WHERE.
+ if ($is_in_other_table) {
+ # Using the last join works properly whether we're searching based
+ # on userid or login_name.
+ my $last_join = $joins->[-1];
+
+ # For negative operators, the system we're using here
+ # only works properly if we reverse the operator and check IS NULL
+ # in the WHERE.
+ my $is_negative = $operator =~ /^no/ ? 1 : 0;
+ if ($is_negative) {
+ $args->{operator} = $self->_reverse_operator($operator);
+ }
+ $self->_do_operator_function($args);
+ push(@{ $last_join->{extra} }, $args->{term});
+
+ # For login_name searches, we only want a single join.
+ # So we create a subselect table out of our two joins. This makes
+ # negation (NOT) work properly for values that are in other
+ # tables.
+ if ($last_join->{table} eq 'profiles') {
+ pop @$joins;
+ $last_join->{join} = 'INNER';
+ my ($join_sql) = $self->_translate_join($last_join);
+ my $first_join = $joins->[-1];
+ my $as = $first_join->{as};
+ my $table = $first_join->{table};
+ my $columns = "bug_id";
+ $columns .= ",isprivate" if @{ $first_join->{extra} };
+ my $new_table = "SELECT $columns FROM $table AS $as $join_sql";
+ $first_join->{table} = "($new_table)";
+ # We always want to LEFT JOIN the generated table.
+ delete $first_join->{join};
+ # To support OR charts, we need multiple tables.
+ my $new_as = $first_join->{as} . "_$sequence";
+ $_ =~ s/\Q$as\E/$new_as/ foreach @{ $first_join->{extra} };
+ $first_join->{as} = $new_as;
+ $last_join = $first_join;
+ }
+
+ # If we're joining the first table (we're using a pronoun and
+ # searching by user id) then we need to check $other_table->{field}.
+ my $check_field = $last_join->{as} . '.bug_id';
+ if ($is_negative) {
+ $args->{term} = "$check_field IS NULL";
+ }
+ else {
+ $args->{term} = "$check_field IS NOT NULL";
+ }
+ }
+}
+
+# XXX This duplicates having Commenter as a search field.
+sub _long_desc_changedby {
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
+
+ my $table = "longdescs_$chart_id";
+ push(@$joins, { table => 'longdescs', as => $table });
+ my $user_id = login_to_id($value, THROW_ERROR);
+ $args->{term} = "$table.who = $user_id";
+}
+
+sub _long_desc_changedbefore_after {
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $value, $joins) =
+ @$args{qw(chart_id operator value joins)};
+ my $dbh = Bugzilla->dbh;
+
+ my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
+ my $table = "longdescs_$chart_id";
+ my $sql_date = $dbh->quote(SqlifyDate($value));
+ my $join = {
+ table => 'longdescs',
+ as => $table,
+ extra => ["$table.bug_when $sql_operator $sql_date"],
+ };
+ push(@$joins, $join);
+ $args->{term} = "$table.bug_when IS NOT NULL";
+}
+
+sub _content_matches {
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $fields, $operator, $value) =
+ @$args{qw(chart_id joins fields operator value)};
+ my $dbh = Bugzilla->dbh;
+
+ # "content" is an alias for columns containing text for which we
+ # can search a full-text index and retrieve results by relevance,
+ # currently just bug comments (and summaries to some degree).
+ # There's only one way to search a full-text index, so we only
+ # accept the "matches" operator, which is specific to full-text
+ # index searches.
+
+ # Add the fulltext table to the query so we can search on it.
+ my $table = "bugs_fulltext_$chart_id";
+ my $comments_col = "comments";
+ $comments_col = "comments_noprivate" unless $self->_user->is_insider;
+ push(@$joins, { table => 'bugs_fulltext', as => $table });
+
+ # Create search terms to add to the SELECT and WHERE clauses.
+ my ($term1, $rterm1) =
+ $dbh->sql_fulltext_search("$table.$comments_col", $value, 1);
+ my ($term2, $rterm2) =
+ $dbh->sql_fulltext_search("$table.short_desc", $value, 2);
+ $rterm1 = $term1 if !$rterm1;
+ $rterm2 = $term2 if !$rterm2;
+
+ # The term to use in the WHERE clause.
+ my $term = "$term1 OR $term2";
+ if ($operator =~ /not/i) {
+ $term = "NOT($term)";
+ }
+ $args->{term} = $term;
+
+ # In order to sort by relevance (in case the user requests it),
+ # we SELECT the relevance value so we can add it to the ORDER BY
+ # clause. Every time a new fulltext chart isadded, this adds more
+ # terms to the relevance sql.
+ #
+ # We build the relevance SQL by modifying the COLUMNS list directly,
+ # which is kind of a hack but works.
+ my $current = $self->COLUMNS->{'relevance'}->{name};
+ $current = $current ? "$current + " : '';
+ # For NOT searches, we just add 0 to the relevance.
+ my $select_term = $operator =~ /not/ ? 0 : "($current$rterm1 + $rterm2)";
+ $self->COLUMNS->{'relevance'}->{name} = $select_term;
+}
+
+sub _long_descs_count {
+ my ($self, $args) = @_;
+ my ($chart_id, $joins) = @$args{qw(chart_id joins)};
+ my $table = "longdescs_count_$chart_id";
+ my $extra = $self->_user->is_insider ? "" : "WHERE isprivate = 0";
+ my $join = {
+ table => "(SELECT bug_id, COUNT(*) AS num"
+ . " FROM longdescs $extra GROUP BY bug_id)",
+ as => $table,
+ };
+ push(@$joins, $join);
+ $args->{full_field} = "${table}.num";
+}
+
+sub _work_time_changedby {
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
+
+ my $table = "longdescs_$chart_id";
+ push(@$joins, { table => 'longdescs', as => $table });
+ my $user_id = login_to_id($value, THROW_ERROR);
+ $args->{term} = "$table.who = $user_id AND $table.work_time != 0";
+}
+
+sub _work_time_changedbefore_after {
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $value, $joins) =
+ @$args{qw(chart_id operator value joins)};
+ my $dbh = Bugzilla->dbh;
+
+ my $table = "longdescs_$chart_id";
+ my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
+ my $sql_date = $dbh->quote(SqlifyDate($value));
+ my $join = {
+ table => 'longdescs',
+ as => $table,
+ extra => ["$table.work_time != 0",
+ "$table.bug_when $sql_operator $sql_date"],
+ };
+ push(@$joins, $join);
+
+ $args->{term} = "$table.bug_when IS NOT NULL";
+}
+
+sub _work_time {
+ my ($self, $args) = @_;
+ $self->_add_extra_column('actual_time');
+ $args->{full_field} = $self->COLUMNS->{actual_time}->{name};
+}
+
+sub _percentage_complete {
+ my ($self, $args) = @_;
+
+ $args->{full_field} = $self->COLUMNS->{percentage_complete}->{name};
+
+ # We need actual_time in _select_columns, otherwise we can't use
+ # it in the expression for searching percentage_complete.
+ $self->_add_extra_column('actual_time');
+}
+
+sub _days_elapsed {
+ my ($self, $args) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $args->{full_field} = "(" . $dbh->sql_to_days('NOW()') . " - " .
+ $dbh->sql_to_days('bugs.delta_ts') . ")";
+}
+
+sub _component_nonchanged {
+ my ($self, $args) = @_;
+
+ $args->{full_field} = "components.name";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term} = build_subselect("bugs.component_id",
+ "components.id", "components", $args->{term});
+}
+
+sub _product_nonchanged {
+ my ($self, $args) = @_;
+
+ # Generate the restriction condition
+ $args->{full_field} = "products.name";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term} = build_subselect("bugs.product_id",
+ "products.id", "products", $term);
+}
+
+sub _classification_nonchanged {
+ my ($self, $args) = @_;
+ my $joins = $args->{joins};
+
+ # This joins the right tables for us.
+ $self->_add_extra_column('product');
+
+ # Generate the restriction condition
+ $args->{full_field} = "classifications.name";
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $args->{term} = build_subselect("map_product.classification_id",
+ "classifications.id", "classifications", $term);
+}
+
+sub _nullable {
+ my ($self, $args) = @_;
+ my $field = $args->{full_field};
+ $args->{full_field} = "COALESCE($field, '')";
+}
+
+sub _nullable_int {
+ my ($self, $args) = @_;
+ my $field = $args->{full_field};
+ $args->{full_field} = "COALESCE($field, 0)";
+}
+
+sub _nullable_datetime {
+ my ($self, $args) = @_;
+ my $field = $args->{full_field};
+ my $empty = Bugzilla->dbh->quote(EMPTY_DATETIME);
+ $args->{full_field} = "COALESCE($field, $empty)";
+}
+
+sub _deadline {
+ my ($self, $args) = @_;
+ my $field = $args->{full_field};
+ # This makes "equals" searches work on all DBs (even on MySQL, which
+ # has a bug: http://bugs.mysql.com/bug.php?id=60324).
+ $args->{full_field} = Bugzilla->dbh->sql_date_format($field, '%Y-%m-%d');
+ $self->_nullable_datetime($args);
+}
+
+sub _owner_idle_time_greater_less {
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $value, $operator) =
+ @$args{qw(chart_id joins value operator)};
+ my $dbh = Bugzilla->dbh;
+
+ my $table = "idle_$chart_id";
+ my $quoted = $dbh->quote(SqlifyDate($value));
+
+ my $ld_table = "comment_$table";
+ my $act_table = "activity_$table";
+ my $comments_join = {
+ table => 'longdescs',
+ as => $ld_table,
+ from => 'assigned_to',
+ to => 'who',
+ extra => ["$ld_table.bug_when > $quoted"],
+ };
+ my $activity_join = {
+ table => 'bugs_activity',
+ as => $act_table,
+ from => 'assigned_to',
+ to => 'who',
+ extra => ["$act_table.bug_when > $quoted"]
+ };
+
+ push(@$joins, $comments_join, $activity_join);
+
+ if ($operator =~ /greater/) {
+ $args->{term} =
+ "$ld_table.who IS NULL AND $act_table.who IS NULL";
+ } else {
+ $args->{term} =
+ "$ld_table.who IS NOT NULL OR $act_table.who IS NOT NULL";
+ }
+}
+
+sub _multiselect_negative {
+ my ($self, $args) = @_;
+ my ($field, $operator) = @$args{qw(field operator)};
+
+ $args->{operator} = $self->_reverse_operator($operator);
+ $args->{term} = $self->_multiselect_term($args, 1);
+}
+
+sub _multiselect_multiple {
+ my ($self, $args) = @_;
+ my ($chart_id, $field, $operator, $value)
+ = @$args{qw(chart_id field operator value)};
+ my $dbh = Bugzilla->dbh;
+
+ # We want things like "cf_multi_select=two+words" to still be
+ # considered a search for two separate words, unless we're using
+ # anyexact. (_all_values would consider that to be one "word" with a
+ # space in it, because it's not in the Boolean Charts).
+ my @words = $operator eq 'anyexact' ? $self->_all_values($args)
+ : split(/[\s,]+/, $value);
+
+ my @terms;
+ foreach my $word (@words) {
+ $args->{value} = $word;
+ $args->{quoted} = $dbh->quote($word);
+ push(@terms, $self->_multiselect_term($args));
+ }
+
+ # The spacing in the joins helps make the resulting SQL more readable.
+ if ($operator =~ /^any/) {
+ $args->{term} = join("\n OR ", @terms);
+ }
+ else {
+ $args->{term} = join("\n AND ", @terms);
+ }
+}
+
+sub _multiselect_nonchanged {
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator) =
+ @$args{qw(chart_id joins field operator)};
+ $args->{term} = $self->_multiselect_term($args)
+}
+
+sub _multiselect_table {
+ my ($self, $args) = @_;
+ my ($field, $chart_id) = @$args{qw(field chart_id)};
+ my $dbh = Bugzilla->dbh;
+
+ if ($field eq 'keywords') {
+ $args->{full_field} = 'keyworddefs.name';
+ return "keywords INNER JOIN keyworddefs".
+ " ON keywords.keywordid = keyworddefs.id";
+ }
+ elsif ($field eq 'tag') {
+ $args->{full_field} = 'tag.name';
+ return "bug_tag INNER JOIN tag ON bug_tag.tag_id = tag.id AND user_id = "
+ . ($self->_sharer_id || $self->_user->id);
+ }
+ elsif ($field eq 'bug_group') {
+ $args->{full_field} = 'groups.name';
+ return "bug_group_map INNER JOIN groups
+ ON bug_group_map.group_id = groups.id";
+ }
+ elsif ($field eq 'blocked' or $field eq 'dependson') {
+ my $select = $field eq 'blocked' ? 'dependson' : 'blocked';
+ $args->{_select_field} = $select;
+ $args->{full_field} = $field;
+ return "dependencies";
+ }
+ elsif ($field eq 'longdesc') {
+ $args->{_extra_where} = " AND isprivate = 0"
+ if !$self->_user->is_insider;
+ $args->{full_field} = 'thetext';
+ return "longdescs";
+ }
+ elsif ($field eq 'longdescs.isprivate') {
+ ThrowUserError('auth_failure', { action => 'search',
+ object => 'bug_fields',
+ field => 'longdescs.isprivate' })
+ if !$self->_user->is_insider;
+ $args->{full_field} = 'isprivate';
+ return "longdescs";
+ }
+ elsif ($field =~ /^attachments/) {
+ $args->{_extra_where} = " AND isprivate = 0"
+ if !$self->_user->is_insider;
+ $field =~ /^attachments\.(.+)$/;
+ $args->{full_field} = $1;
+ return "attachments";
+ }
+ elsif ($field eq 'attach_data.thedata') {
+ $args->{_extra_where} = " AND attachments.isprivate = 0"
+ if !$self->_user->is_insider;
+ return "attachments INNER JOIN attach_data "
+ . " ON attachments.attach_id = attach_data.id"
+ }
+ elsif ($field eq 'flagtypes.name') {
+ $args->{full_field} = $dbh->sql_string_concat("flagtypes.name",
+ "flags.status");
+ return "flags INNER JOIN flagtypes ON flags.type_id = flagtypes.id";
+ }
+ my $table = "bug_$field";
+ $args->{full_field} = "bug_$field.value";
+ return $table;
+}
+
+sub _multiselect_term {
+ my ($self, $args, $not) = @_;
+ my $table = $self->_multiselect_table($args);
+ $self->_do_operator_function($args);
+ my $term = $args->{term};
+ $term .= $args->{_extra_where} || '';
+ my $select = $args->{_select_field} || 'bug_id';
+ my $not_sql = $not ? "NOT " : '';
+ return "bugs.bug_id ${not_sql}IN (SELECT $select FROM $table WHERE $term)";
+}
+
+###############################
+# Standard Operator Functions #
+###############################
+
+sub _simple_operator {
+ my ($self, $args) = @_;
+ my ($full_field, $quoted, $operator) =
+ @$args{qw(full_field quoted operator)};
+ my $sql_operator = SIMPLE_OPERATORS->{$operator};
+ $args->{term} = "$full_field $sql_operator $quoted";
+}
+
+sub _casesubstring {
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
+ my $dbh = Bugzilla->dbh;
+
+ $args->{term} = $dbh->sql_position($quoted, $full_field) . " > 0";
+}
+
+sub _substring {
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
+ my $dbh = Bugzilla->dbh;
+
+ # XXX This should probably be changed to just use LIKE
+ $args->{term} = $dbh->sql_iposition($quoted, $full_field) . " > 0";
+}
+
+sub _notsubstring {
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
+ my $dbh = Bugzilla->dbh;
+
+ # XXX This should probably be changed to just use NOT LIKE
+ $args->{term} = $dbh->sql_iposition($quoted, $full_field) . " = 0";
+}
+
+sub _regexp {
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
+ my $dbh = Bugzilla->dbh;
+
+ $args->{term} = $dbh->sql_regexp($full_field, $quoted);
+}
+
+sub _notregexp {
+ my ($self, $args) = @_;
+ my ($full_field, $quoted) = @$args{qw(full_field quoted)};
+ my $dbh = Bugzilla->dbh;
+
+ $args->{term} = $dbh->sql_not_regexp($full_field, $quoted);
+}
+
+sub _anyexact {
+ my ($self, $args) = @_;
+ my ($field, $full_field) = @$args{qw(field full_field)};
+ my $dbh = Bugzilla->dbh;
+
+ my @list = $self->_all_values($args, ',');
+ @list = map { $self->_quote_unless_numeric($args, $_) } @list;
+
+ if (@list) {
+ $args->{term} = $dbh->sql_in($full_field, \@list);
+ }
+ else {
+ $args->{term} = '';
+ }
+}
+
+sub _anywordsubstr {
+ my ($self, $args) = @_;
+ my ($full_field, $value) = @$args{qw(full_field value)};
+
+ my @terms = $self->_substring_terms($args);
+ $args->{term} = join("\n\tOR ", @terms);
+}
+
+sub _allwordssubstr {
+ my ($self, $args) = @_;
+
+ my @terms = $self->_substring_terms($args);
+ $args->{term} = join("\n\tAND ", @terms);
+}
+
+sub _nowordssubstr {
+ my ($self, $args) = @_;
+ $self->_anywordsubstr($args);
+ my $term = $args->{term};
+ $args->{term} = "NOT($term)";
+}
+
+sub _anywords {
+ my ($self, $args) = @_;
+
+ my @terms = $self->_word_terms($args);
+ # Because _word_terms uses AND, we need to parenthesize its terms
+ # if there are more than one.
+ @terms = map("($_)", @terms) if scalar(@terms) > 1;
+ $args->{term} = join("\n\tOR ", @terms);
+}
+
+sub _allwords {
+ my ($self, $args) = @_;
+
+ my @terms = $self->_word_terms($args);
+ $args->{term} = join("\n\tAND ", @terms);
+}
+
+sub _nowords {
+ my ($self, $args) = @_;
+ $self->_anywords($args);
+ my $term = $args->{term};
+ $args->{term} = "NOT($term)";
+}
+
+sub _changedbefore_changedafter {
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $value) =
+ @$args{qw(chart_id joins field operator value)};
+ my $dbh = Bugzilla->dbh;
+
+ my $field_object = $self->_chart_fields->{$field}
+ || ThrowCodeError("invalid_field_name", { field => $field });
+
+ # Asking when creation_ts changed is just asking when the bug was created.
+ if ($field_object->name eq 'creation_ts') {
+ $args->{operator} =
+ $operator eq 'changedbefore' ? 'lessthaneq' : 'greaterthaneq';
+ return $self->_do_operator_function($args);
+ }
+
+ my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
+ my $field_id = $field_object->id;
+ # Charts on changed* fields need to be field-specific. Otherwise,
+ # OR chart rows make no sense if they contain multiple fields.
+ my $table = "act_${field_id}_$chart_id";
+
+ my $sql_date = $dbh->quote(SqlifyDate($value));
+ my $join = {
+ table => 'bugs_activity',
+ as => $table,
+ extra => ["$table.fieldid = $field_id",
+ "$table.bug_when $sql_operator $sql_date"],
+ };
+ push(@$joins, $join);
+ $args->{term} = "$table.bug_when IS NOT NULL";
+}
+
+sub _changedfrom_changedto {
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $quoted) =
+ @$args{qw(chart_id joins field operator quoted)};
+
+ my $column = ($operator =~ /from/) ? 'removed' : 'added';
+ my $field_object = $self->_chart_fields->{$field}
+ || ThrowCodeError("invalid_field_name", { field => $field });
+ my $field_id = $field_object->id;
+ my $table = "act_${field_id}_$chart_id";
+ my $join = {
+ table => 'bugs_activity',
+ as => $table,
+ extra => ["$table.fieldid = $field_id",
+ "$table.$column = $quoted"],
+ };
+ push(@$joins, $join);
+
+ $args->{term} = "$table.bug_when IS NOT NULL";
+}
+
+sub _changedby {
+ my ($self, $args) = @_;
+ my ($chart_id, $joins, $field, $operator, $value) =
+ @$args{qw(chart_id joins field operator value)};
+
+ my $field_object = $self->_chart_fields->{$field}
+ || ThrowCodeError("invalid_field_name", { field => $field });
+ my $field_id = $field_object->id;
+ my $table = "act_${field_id}_$chart_id";
+ my $user_id = login_to_id($value, THROW_ERROR);
+ my $join = {
+ table => 'bugs_activity',
+ as => $table,
+ extra => ["$table.fieldid = $field_id",
+ "$table.who = $user_id"],
+ };
+ push(@$joins, $join);
+ $args->{term} = "$table.bug_when IS NOT NULL";
+}
+
+######################
+# Public Subroutines #
+######################
+
# Validate that the query type is one we can deal with
sub IsValidQueryType
{
@@ -959,1110 +2833,37 @@
return 0;
}
-# BuildOrderBy - Private Subroutine
-# This function converts the input order to an "output" order,
-# suitable for concatenation to form an ORDER BY clause. Basically,
-# it just handles fields that have non-standard sort orders from
-# %specialorder.
-# Arguments:
-# $orderitem - A string. The next value to append to the ORDER BY clause,
-# in the format of an item in the 'order' parameter to
-# Bugzilla::Search.
-# $stringlist - A reference to the list of strings that will be join()'ed
-# to make ORDER BY. This is what the subroutine modifies.
-# $reverseorder - (Optional) A boolean. TRUE if we should reverse the order
-# of the field that we are given (from ASC to DESC or vice-versa).
-#
-# Explanation of $reverseorder
-# ----------------------------
-# The role of $reverseorder is to handle things like sorting by
-# "target_milestone DESC".
-# Let's say that we had a field "A" that normally translates to a sort
-# order of "B ASC, C DESC". If we sort by "A DESC", what we really then
-# mean is "B DESC, C ASC". So $reverseorder is only used if we call
-# BuildOrderBy recursively, to let it know that we're "reversing" the
-# order. That is, that we wanted "A DESC", not "A".
-sub BuildOrderBy {
- my ($special_order, $orderitem, $stringlist, $reverseorder) = (@_);
+# Splits out "asc|desc" from a sort order item.
+sub split_order_term {
+ my $fragment = shift;
+ $fragment =~ /^(.+?)(?:\s+(ASC|DESC))?$/i;
+ my ($column_name, $direction) = (lc($1), uc($2 || ''));
+ return wantarray ? ($column_name, $direction) : $column_name;
+}
- my @twopart = split(/\s+/, $orderitem);
- my $orderfield = $twopart[0];
- my $orderdirection = $twopart[1] || "";
+# Used to translate old SQL fragments from buglist.cgi's "order" argument
+# into our modern field IDs.
+sub translate_old_column {
+ my ($column) = @_;
+ # All old SQL fragments have a period in them somewhere.
+ return $column if $column !~ /\./;
- if ($reverseorder) {
- # If orderdirection is empty or ASC...
- if (!$orderdirection || $orderdirection =~ m/asc/i) {
- $orderdirection = "DESC";
- } else {
- # This has the minor side-effect of making any reversed invalid
- # direction into ASC.
- $orderdirection = "ASC";
- }
+ if ($column =~ /\bAS\s+(\w+)$/i) {
+ return $1;
}
-
- # Handle fields that have non-standard sort orders, from $specialorder.
- if ($special_order->{$orderfield}) {
- foreach my $subitem (@{$special_order->{$orderfield}}) {
- # DESC on a field with non-standard sort order means
- # "reverse the normal order for each field that we map to."
- BuildOrderBy($special_order, $subitem, $stringlist,
- $orderdirection =~ m/desc/i);
- }
- return;
- }
-
- push(@$stringlist, trim($orderfield . ' ' . $orderdirection));
-}
-
-#####################################################################
-# Search Functions
-#####################################################################
-
-sub _contact_exact_group {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $f, $t, $v, $term) =
- @func_args{qw(chartid supptables f t v term)};
- my $user = $self->{'user'};
-
- $$v =~ m/%group\\.([^%]+)%/;
- my $group = $1;
- my $groupid = Bugzilla::Group::ValidateGroupName( $group, ($user));
- $groupid || ThrowUserError('invalid_group_name',{name => $group});
- my @childgroups = @{$user->flatten_group_membership($groupid)};
- my $table = "user_group_map_$$chartid";
- push (@$supptables, "LEFT JOIN user_group_map AS $table " .
- "ON $table.user_id = bugs.$$f " .
- "AND $table.group_id IN(" .
- join(',', @childgroups) . ") " .
- "AND $table.isbless = 0 " .
- "AND $table.grant_type IN(" .
- GRANT_DIRECT . "," . GRANT_REGEXP . ")"
- );
- if ($$t =~ /^not/) {
- $$term = "$table.group_id IS NULL";
- } else {
- $$term = "$table.group_id IS NOT NULL";
- }
-}
-
-sub _contact_exact {
- my $self = shift;
- my %func_args = @_;
- my ($term, $f, $v) = @func_args{qw(term f v)};
- my $user = $self->{'user'};
-
- $$v =~ m/(%\\w+%)/;
- $$term = "bugs.$$f = " . pronoun($1, $user);
-}
-
-sub _contact_notequals {
- my $self = shift;
- my %func_args = @_;
- my ($term, $f, $v) = @func_args{qw(term f v)};
- my $user = $self->{'user'};
-
- $$v =~ m/(%\\w+%)/;
- $$term = "bugs.$$f <> " . pronoun($1, $user);
-}
-
-sub _assigned_to_reporter_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($f, $ff, $funcsbykey, $t, $term) =
- @func_args{qw(f ff funcsbykey t term)};
-
- my $real_f = $$f;
- $$f = "login_name";
- $$ff = "profiles.login_name";
- $$funcsbykey{",$$t"}($self, %func_args);
- $$term = "bugs.$real_f IN (SELECT userid FROM profiles WHERE $$term)";
-}
-
-sub _qa_contact_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($supptables, $f) =
- @func_args{qw(supptables f)};
-
- push(@$supptables, "LEFT JOIN profiles AS map_qa_contact " .
- "ON bugs.qa_contact = map_qa_contact.userid");
- $$f = "COALESCE(map_$$f.login_name,'')";
-}
-
-sub _cc_exact_group {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $sequence, $supptables, $t, $v, $term) =
- @func_args{qw(chartid sequence supptables t v term)};
- my $user = $self->{'user'};
-
- $$v =~ m/%group\\.([^%]+)%/;
- my $group = $1;
- my $groupid = Bugzilla::Group::ValidateGroupName( $group, ($user));
- $groupid || ThrowUserError('invalid_group_name',{name => $group});
- my @childgroups = @{$user->flatten_group_membership($groupid)};
- my $chartseq = $$chartid;
- if ($$chartid eq "") {
- $chartseq = "CC$$sequence";
- $$sequence++;
- }
- my $table = "user_group_map_$chartseq";
- push(@$supptables, "LEFT JOIN cc AS cc_$chartseq " .
- "ON bugs.bug_id = cc_$chartseq.bug_id");
- push(@$supptables, "LEFT JOIN user_group_map AS $table " .
- "ON $table.user_id = cc_$chartseq.who " .
- "AND $table.group_id IN(" .
- join(',', @childgroups) . ") " .
- "AND $table.isbless = 0 " .
- "AND $table.grant_type IN(" .
- GRANT_DIRECT . "," . GRANT_REGEXP . ")"
- );
- if ($$t =~ /^not/) {
- $$term = "$table.group_id IS NULL";
- } else {
- $$term = "$table.group_id IS NOT NULL";
- }
-}
-
-sub _cc_exact {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $sequence, $supptables, $term, $v) =
- @func_args{qw(chartid sequence supptables term v)};
- my $user = $self->{'user'};
-
- $$v =~ m/(%\\w+%)/;
- my $match = pronoun($1, $user);
- my $chartseq = $$chartid;
- if ($$chartid eq "") {
- $chartseq = "CC$$sequence";
- $$sequence++;
- }
- push(@$supptables, "LEFT JOIN cc AS cc_$chartseq " .
- "ON bugs.bug_id = cc_$chartseq.bug_id " .
- "AND cc_$chartseq.who = $match");
- $$term = "cc_$chartseq.who IS NOT NULL";
-}
-
-sub _cc_notequals {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $sequence, $supptables, $term, $v) =
- @func_args{qw(chartid sequence supptables term v)};
- my $user = $self->{'user'};
-
- $$v =~ m/(%\\w+%)/;
- my $match = pronoun($1, $user);
- my $chartseq = $$chartid;
- if ($$chartid eq "") {
- $chartseq = "CC$$sequence";
- $$sequence++;
- }
- push(@$supptables, "LEFT JOIN cc AS cc_$chartseq " .
- "ON bugs.bug_id = cc_$chartseq.bug_id " .
- "AND cc_$chartseq.who = $match");
- $$term = "cc_$chartseq.who IS NULL";
-}
-
-sub _cc_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $sequence, $f, $ff, $t, $funcsbykey, $supptables, $term, $v) =
- @func_args{qw(chartid sequence f ff t funcsbykey supptables term v)};
-
- my $chartseq = $$chartid;
- if ($$chartid eq "") {
- $chartseq = "CC$$sequence";
- $$sequence++;
- }
- $$f = "login_name";
- $$ff = "profiles.login_name";
- $$funcsbykey{",$$t"}($self, %func_args);
- push(@$supptables, "LEFT JOIN cc AS cc_$chartseq " .
- "ON bugs.bug_id = cc_$chartseq.bug_id " .
- "AND cc_$chartseq.who IN" .
- "(SELECT userid FROM profiles WHERE $$term)"
- );
- $$term = "cc_$chartseq.who IS NOT NULL";
-}
-
-sub _long_desc_changedby {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $term, $v) =
- @func_args{qw(chartid supptables term v)};
-
- my $table = "longdescs_$$chartid";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id");
- my $id = login_to_id($$v, THROW_ERROR);
- $$term = "$table.who = $id";
-}
-
-sub _long_desc_changedbefore_after {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $t, $v, $supptables, $term) =
- @func_args{qw(chartid t v supptables term)};
- my $dbh = Bugzilla->dbh;
-
- my $operator = ($$t =~ /before/) ? '<' : '>';
- my $table = "longdescs_$$chartid";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id " .
- "AND $table.bug_when $operator " .
- $dbh->quote(SqlifyDate($$v)) );
- $$term = "($table.bug_when IS NOT NULL)";
-}
-
-sub _content_matches {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $term, $groupby, $fields, $v) =
- @func_args{qw(chartid supptables term groupby fields v)};
- my $dbh = Bugzilla->dbh;
-
- # "content" is an alias for columns containing text for which we
- # can search a full-text index and retrieve results by relevance,
- # currently just bug comments (and summaries to some degree).
- # There's only one way to search a full-text index, so we only
- # accept the "matches" operator, which is specific to full-text
- # index searches.
-
- # Add the fulltext table to the query so we can search on it.
- my $table = "bugs_fulltext_$$chartid";
- my $comments_col = "comments";
- $comments_col = "comments_noprivate" unless $self->{'user'}->is_insider;
- push(@$supptables, "LEFT JOIN bugs_fulltext AS $table " .
- "ON bugs.bug_id = $table.bug_id");
-
- # Create search terms to add to the SELECT and WHERE clauses.
- my ($term1, $rterm1) = $dbh->sql_fulltext_search("$table.$comments_col",
- $$v, 1);
- my ($term2, $rterm2) = $dbh->sql_fulltext_search("$table.short_desc",
- $$v, 2);
- $rterm1 = $term1 if !$rterm1;
- $rterm2 = $term2 if !$rterm2;
-
- # The term to use in the WHERE clause.
- $$term = "$term1 > 0 OR $term2 > 0";
-
- # In order to sort by relevance (in case the user requests it),
- # we SELECT the relevance value and give it an alias so we can
- # add it to the SORT BY clause when we build it in buglist.cgi.
- my $select_term = "($rterm1 + $rterm2) AS relevance";
-
- # Users can specify to display the relevance field, in which case
- # it'll show up in the list of fields being selected, and we need
- # to replace that occurrence with our select term. Otherwise
- # we can just add the term to the list of fields being selected.
- if (grep($_ eq "relevance", @$fields)) {
- @$fields = map($_ eq "relevance" ? $select_term : $_ , @$fields);
- }
- else {
- push(@$fields, $select_term);
- }
-}
-
-sub _timestamp_compare {
- my $self = shift;
- my %func_args = @_;
- my ($v, $q) = @func_args{qw(v q)};
- my $dbh = Bugzilla->dbh;
-
- $$v = SqlifyDate($$v);
- $$q = $dbh->quote($$v);
-}
-
-sub _commenter_exact {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $sequence, $supptables, $term, $v) =
- @func_args{qw(chartid sequence supptables term v)};
- my $user = $self->{'user'};
-
- $$v =~ m/(%\\w+%)/;
- my $match = pronoun($1, $user);
- my $chartseq = $$chartid;
- if ($$chartid eq "") {
- $chartseq = "LD$$sequence";
- $$sequence++;
- }
- my $table = "longdescs_$chartseq";
- my $extra = $user->is_insider ? "" : "AND $table.isprivate < 1";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id $extra " .
- "AND $table.who IN ($match)");
- $$term = "$table.who IS NOT NULL";
-}
-
-sub _commenter {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $sequence, $supptables, $f, $ff, $t, $funcsbykey, $term) =
- @func_args{qw(chartid sequence supptables f ff t funcsbykey term)};
-
- my $chartseq = $$chartid;
- if ($$chartid eq "") {
- $chartseq = "LD$$sequence";
- $$sequence++;
- }
- my $table = "longdescs_$chartseq";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1";
- $$f = "login_name";
- $$ff = "profiles.login_name";
- $$funcsbykey{",$$t"}($self, %func_args);
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id $extra " .
- "AND $table.who IN" .
- "(SELECT userid FROM profiles WHERE $$term)"
- );
- $$term = "$table.who IS NOT NULL";
-}
-
-sub _long_desc {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $f) =
- @func_args{qw(chartid supptables f)};
-
- my $table = "longdescs_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id $extra");
- $$f = "$table.thetext";
-}
-
-sub _longdescs_isprivate {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $f) =
- @func_args{qw(chartid supptables f)};
-
- my $table = "longdescs_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate < 1";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id $extra");
- $$f = "$table.isprivate";
-}
-
-sub _work_time_changedby {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $v, $term) =
- @func_args{qw(chartid supptables v term)};
-
- my $table = "longdescs_$$chartid";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id");
- my $id = login_to_id($$v, THROW_ERROR);
- $$term = "(($table.who = $id";
- $$term .= ") AND ($table.work_time <> 0))";
-}
-
-sub _work_time_changedbefore_after {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $t, $v, $supptables, $term) =
- @func_args{qw(chartid t v supptables term)};
- my $dbh = Bugzilla->dbh;
-
- my $operator = ($$t =~ /before/) ? '<' : '>';
- my $table = "longdescs_$$chartid";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id " .
- "AND $table.work_time <> 0 " .
- "AND $table.bug_when $operator " .
- $dbh->quote(SqlifyDate($$v)) );
- $$term = "($table.bug_when IS NOT NULL)";
-}
-
-sub _work_time {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $f) =
- @func_args{qw(chartid supptables f)};
-
- my $table = "longdescs_$$chartid";
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id");
- $$f = "$table.work_time";
-}
-
-sub _percentage_complete {
- my $self = shift;
- my %func_args = @_;
- my ($t, $chartid, $supptables, $fields, $q, $v, $having, $groupby, $term) =
- @func_args{qw(t chartid supptables fields q v having groupby term)};
- my $dbh = Bugzilla->dbh;
-
- my $oper;
- if ($$t eq "equals") {
- $oper = "=";
- } elsif ($$t eq "greaterthan") {
- $oper = ">";
- } elsif ($$t eq "lessthan") {
- $oper = "<";
- } elsif ($$t eq "notequal") {
- $oper = "<>";
- } elsif ($$t eq "regexp") {
- # This is just a dummy to help catch bugs- $oper won't be used
- # since "regexp" is treated as a special case below. But
- # leaving $oper uninitialized seems risky...
- $oper = "sql_regexp";
- } elsif ($$t eq "notregexp") {
- # This is just a dummy to help catch bugs- $oper won't be used
- # since "notregexp" is treated as a special case below. But
- # leaving $oper uninitialized seems risky...
- $oper = "sql_not_regexp";
- } else {
- $oper = "noop";
- }
- if ($oper ne "noop") {
- my $table = "longdescs_$$chartid";
- if(lsearch($fields, "bugs.remaining_time") == -1) {
- push(@$fields, "bugs.remaining_time");
- }
- push(@$supptables, "LEFT JOIN longdescs AS $table " .
- "ON $table.bug_id = bugs.bug_id");
- my $expression = "(100 * ((SUM($table.work_time) *
- COUNT(DISTINCT $table.bug_when) /
- COUNT(bugs.bug_id)) /
- ((SUM($table.work_time) *
- COUNT(DISTINCT $table.bug_when) /
- COUNT(bugs.bug_id)) +
- bugs.remaining_time)))";
- $$q = $dbh->quote($$v);
- trick_taint($$q);
- if ($$t eq "regexp") {
- push(@$having, $dbh->sql_regexp($expression, $$q));
- } elsif ($$t eq "notregexp") {
- push(@$having, $dbh->sql_not_regexp($expression, $$q));
- } else {
- push(@$having, "$expression $oper " . $$q);
- }
- push(@$groupby, "bugs.remaining_time");
- }
- $$term = "0=0";
-}
-
-sub _bug_group_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($supptables, $chartid, $ff, $f, $t, $funcsbykey, $term) =
- @func_args{qw(supptables chartid ff f t funcsbykey term)};
-
- push(@$supptables,
- "LEFT JOIN bug_group_map AS bug_group_map_$$chartid " .
- "ON bugs.bug_id = bug_group_map_$$chartid.bug_id");
- $$ff = $$f = "groups_$$chartid.name";
- $$funcsbykey{",$$t"}($self, %func_args);
- push(@$supptables,
- "LEFT JOIN groups AS groups_$$chartid " .
- "ON groups_$$chartid.id = bug_group_map_$$chartid.group_id " .
- "AND $$term");
- $$term = "$$ff IS NOT NULL";
-}
-
-sub _attach_data_thedata_changed {
- my $self = shift;
- my %func_args = @_;
- my ($f) = @func_args{qw(f)};
-
- # Searches for attachment data's change must search
- # the creation timestamp of the attachment instead.
- $$f = "attachments.whocares";
-}
-
-sub _attach_data_thedata {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $f) =
- @func_args{qw(chartid supptables f)};
-
- my $atable = "attachments_$$chartid";
- my $dtable = "attachdata_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $atable.isprivate = 0";
- push(@$supptables, "INNER JOIN attachments AS $atable " .
- "ON bugs.bug_id = $atable.bug_id $extra");
- push(@$supptables, "INNER JOIN attach_data AS $dtable " .
- "ON $dtable.id = $atable.attach_id");
- $$f = "$dtable.thedata";
-}
-
-sub _attachments_submitter {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $f) =
- @func_args{qw(chartid supptables f)};
-
- my $atable = "map_attachment_submitter_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $atable.isprivate = 0";
- push(@$supptables, "INNER JOIN attachments AS $atable " .
- "ON bugs.bug_id = $atable.bug_id $extra");
- push(@$supptables, "LEFT JOIN profiles AS attachers_$$chartid " .
- "ON $atable.submitter_id = attachers_$$chartid.userid");
- $$f = "attachers_$$chartid.login_name";
-}
-
-sub _attachments {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $supptables, $f, $t, $v, $q) =
- @func_args{qw(chartid supptables f t v q)};
- my $dbh = Bugzilla->dbh;
-
- my $table = "attachments_$$chartid";
- my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
- push(@$supptables, "INNER JOIN attachments AS $table " .
- "ON bugs.bug_id = $table.bug_id $extra");
- $$f =~ m/^attachments\.(.*)$/;
- my $field = $1;
- if ($$t eq "changedby") {
- $$v = login_to_id($$v, THROW_ERROR);
- $$q = $dbh->quote($$v);
- $field = "submitter_id";
- $$t = "equals";
- } elsif ($$t eq "changedbefore") {
- $$v = SqlifyDate($$v);
- $$q = $dbh->quote($$v);
- $field = "creation_ts";
- $$t = "lessthan";
- } elsif ($$t eq "changedafter") {
- $$v = SqlifyDate($$v);
- $$q = $dbh->quote($$v);
- $field = "creation_ts";
- $$t = "greaterthan";
- }
- if ($field eq "ispatch" && $$v ne "0" && $$v ne "1") {
- ThrowUserError("illegal_attachment_is_patch");
- }
- if ($field eq "isobsolete" && $$v ne "0" && $$v ne "1") {
- ThrowUserError("illegal_is_obsolete");
- }
- $$f = "$table.$field";
-}
-
-sub _flagtypes_name {
- my $self = shift;
- my %func_args = @_;
- my ($t, $chartid, $supptables, $ff, $funcsbykey, $having, $term) =
- @func_args{qw(t chartid supptables ff funcsbykey having term)};
- my $dbh = Bugzilla->dbh;
-
- # Matches bugs by flag name/status.
- # Note that--for the purposes of querying--a flag comprises
- # its name plus its status (i.e. a flag named "review"
- # with a status of "+" can be found by searching for "review+").
-
- # Don't do anything if this condition is about changes to flags,
- # as the generic change condition processors can handle those.
- return if ($$t =~ m/^changed/);
-
- # Add the flags and flagtypes tables to the query. We do
- # a left join here so bugs without any flags still match
- # negative conditions (f.e. "flag isn't review+").
- my $flags = "flags_$$chartid";
- push(@$supptables, "LEFT JOIN flags AS $flags " .
- "ON bugs.bug_id = $flags.bug_id ");
- my $flagtypes = "flagtypes_$$chartid";
- push(@$supptables, "LEFT JOIN flagtypes AS $flagtypes " .
- "ON $flags.type_id = $flagtypes.id");
-
- # Generate the condition by running the operator-specific
- # function. Afterwards the condition resides in the global $term
- # variable.
- $$ff = $dbh->sql_string_concat("${flagtypes}.name",
- "$flags.status");
- $$funcsbykey{",$$t"}($self, %func_args);
-
- # If this is a negative condition (f.e. flag isn't "review+"),
- # we only want bugs where all flags match the condition, not
- # those where any flag matches, which needs special magic.
- # Instead of adding the condition to the WHERE clause, we select
- # the number of flags matching the condition and the total number
- # of flags on each bug, then compare them in a HAVING clause.
- # If the numbers are the same, all flags match the condition,
- # so this bug should be included.
- if ($$t =~ m/not/) {
- push(@$having,
- "SUM(CASE WHEN $$ff IS NOT NULL THEN 1 ELSE 0 END) = " .
- "SUM(CASE WHEN $$term THEN 1 ELSE 0 END)");
- $$term = "0=0";
- }
-}
-
-sub _requestees_login_name {
- my $self = shift;
- my %func_args = @_;
- my ($f, $chartid, $supptables) = @func_args{qw(f chartid supptables)};
-
- my $flags = "flags_$$chartid";
- push(@$supptables, "LEFT JOIN flags AS $flags " .
- "ON bugs.bug_id = $flags.bug_id ");
- push(@$supptables, "LEFT JOIN profiles AS requestees_$$chartid " .
- "ON $flags.requestee_id = requestees_$$chartid.userid");
- $$f = "requestees_$$chartid.login_name";
-}
-
-sub _setters_login_name {
- my $self = shift;
- my %func_args = @_;
- my ($f, $chartid, $supptables) = @func_args{qw(f chartid supptables)};
-
- my $flags = "flags_$$chartid";
- push(@$supptables, "LEFT JOIN flags AS $flags " .
- "ON bugs.bug_id = $flags.bug_id ");
- push(@$supptables, "LEFT JOIN profiles AS setters_$$chartid " .
- "ON $flags.setter_id = setters_$$chartid.userid");
- $$f = "setters_$$chartid.login_name";
-}
-
-sub _changedin_days_elapsed {
- my $self = shift;
- my %func_args = @_;
- my ($f) = @func_args{qw(f)};
- my $dbh = Bugzilla->dbh;
-
- $$f = "(" . $dbh->sql_to_days('NOW()') . " - " .
- $dbh->sql_to_days('bugs.delta_ts') . ")";
-}
-
-sub _component_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($f, $ff, $t, $funcsbykey, $term) =
- @func_args{qw(f ff t funcsbykey term)};
-
- $$f = $$ff = "components.name";
- $$funcsbykey{",$$t"}($self, %func_args);
- $$term = build_subselect("bugs.component_id",
- "components.id",
- "components",
- $$term);
-}
-sub _product_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($f, $ff, $t, $funcsbykey, $term) =
- @func_args{qw(f ff t funcsbykey term)};
-
- # Generate the restriction condition
- $$f = $$ff = "products.name";
- $$funcsbykey{",$$t"}($self, %func_args);
- $$term = build_subselect("bugs.product_id",
- "products.id",
- "products",
- $$term);
-}
-
-sub _classification_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $v, $ff, $f, $funcsbykey, $t, $supptables, $term) =
- @func_args{qw(chartid v ff f funcsbykey t supptables term)};
-
- # Generate the restriction condition
- push @$supptables, "INNER JOIN products AS map_products " .
- "ON bugs.product_id = map_products.id";
- $$f = $$ff = "classifications.name";
- $$funcsbykey{",$$t"}($self, %func_args);
- $$term = build_subselect("map_products.classification_id",
- "classifications.id",
- "classifications",
- $$term);
-}
-
-sub _keywords_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $v, $ff, $f, $t, $term, $supptables) =
- @func_args{qw(chartid v ff f t term supptables)};
-
- my @list;
- my $table = "keywords_$$chartid";
- foreach my $value (split(/[\s,]+/, $$v)) {
- if ($value eq '') {
- next;
- }
- my $keyword = new Bugzilla::Keyword({name => $value});
- if ($keyword) {
- push(@list, "$table.keywordid = " . $keyword->id);
- }
- else {
- ThrowUserError("unknown_keyword",
- { keyword => $$v });
- }
- }
- my $haveawordterm;
- if (@list) {
- $haveawordterm = "(" . join(' OR ', @list) . ")";
- if ($$t eq "anywords") {
- $$term = $haveawordterm;
- } elsif ($$t eq "allwords") {
- $self->_allwords;
- if ($$term && $haveawordterm) {
- $$term = "(($$term) AND $haveawordterm)";
- }
- }
- }
- if ($$term) {
- push(@$supptables, "LEFT JOIN keywords AS $table " .
- "ON $table.bug_id = bugs.bug_id");
- }
-}
-
-sub _dependson_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $ff, $f, $funcsbykey, $t, $term, $supptables) =
- @func_args{qw(chartid ff f funcsbykey t term supptables)};
-
- my $table = "dependson_" . $$chartid;
- $$ff = "$table.$$f";
- $$funcsbykey{",$$t"}($self, %func_args);
- push(@$supptables, "LEFT JOIN dependencies AS $table " .
- "ON $table.blocked = bugs.bug_id " .
- "AND ($$term)");
- $$term = "$$ff IS NOT NULL";
-}
-
-sub _blocked_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $ff, $f, $funcsbykey, $t, $term, $supptables) =
- @func_args{qw(chartid ff f funcsbykey t term supptables)};
-
- my $table = "blocked_" . $$chartid;
- $$ff = "$table.$$f";
- $$funcsbykey{",$$t"}($self, %func_args);
- push(@$supptables, "LEFT JOIN dependencies AS $table " .
- "ON $table.dependson = bugs.bug_id " .
- "AND ($$term)");
- $$term = "$$ff IS NOT NULL";
-}
-
-sub _alias_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $funcsbykey, $t, $term) =
- @func_args{qw(ff funcsbykey t term)};
-
- $$ff = "COALESCE(bugs.alias, '')";
-
- $$funcsbykey{",$$t"}($self, %func_args);
-}
-
-sub _owner_idle_time_greater_less {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $v, $supptables, $t, $wherepart, $term) =
- @func_args{qw(chartid v supptables t wherepart term)};
- my $dbh = Bugzilla->dbh;
-
- my $table = "idle_" . $$chartid;
- $$v =~ /^(\d+)\s*([hHdDwWmMyY])?$/;
- my $quantity = $1;
- my $unit = lc $2;
- my $unitinterval = 'DAY';
- if ($unit eq 'h') {
- $unitinterval = 'HOUR';
- } elsif ($unit eq 'w') {
- $unitinterval = ' * 7 DAY';
- } elsif ($unit eq 'm') {
- $unitinterval = 'MONTH';
- } elsif ($unit eq 'y') {
- $unitinterval = 'YEAR';
- }
- my $cutoff = "NOW() - " .
- $dbh->sql_interval($quantity, $unitinterval);
- my $assigned_fieldid = get_field_id('assigned_to');
- push(@$supptables, "LEFT JOIN longdescs AS comment_$table " .
- "ON comment_$table.who = bugs.assigned_to " .
- "AND comment_$table.bug_id = bugs.bug_id " .
- "AND comment_$table.bug_when > $cutoff");
- push(@$supptables, "LEFT JOIN bugs_activity AS activity_$table " .
- "ON (activity_$table.who = bugs.assigned_to " .
- "OR activity_$table.fieldid = $assigned_fieldid) " .
- "AND activity_$table.bug_id = bugs.bug_id " .
- "AND activity_$table.bug_when > $cutoff");
- if ($$t =~ /greater/) {
- push(@$wherepart, "(comment_$table.who IS NULL " .
- "AND activity_$table.who IS NULL)");
- } else {
- push(@$wherepart, "(comment_$table.who IS NOT NULL " .
- "OR activity_$table.who IS NOT NULL)");
- }
- $$term = "0=0";
-}
-
-sub _multiselect_negative {
- my $self = shift;
- my %func_args = @_;
- my ($f, $ff, $t, $funcsbykey, $term) = @func_args{qw(f ff t funcsbykey term)};
-
- my %map = (
- notequals => 'equals',
- notregexp => 'regexp',
- notsubstring => 'substring',
- nowords => 'anywords',
- nowordssubstr => 'anywordssubstr',
- );
-
- my $table = "bug_$$f";
- $$ff = "$table.value";
-
- $$funcsbykey{",".$map{$$t}}($self, %func_args);
- $$term = "bugs.bug_id NOT IN (SELECT bug_id FROM $table WHERE $$term)";
-}
-
-sub _multiselect_multiple {
- my $self = shift;
- my %func_args = @_;
- my ($f, $ff, $t, $v, $funcsbykey, $term) = @func_args{qw(f ff t v funcsbykey term)};
-
- my @terms;
- my $table = "bug_$$f";
- $$ff = "$table.value";
-
- foreach my $word (split(/[\s,]+/, $$v)) {
- $$v = $word;
- $$funcsbykey{",".$$t}($self, %func_args);
- push(@terms, "bugs.bug_id IN
- (SELECT bug_id FROM $table WHERE $$term)");
+ # product, component, classification, assigned_to, qa_contact, reporter
+ elsif ($column =~ /map_(\w+?)s?\.(login_)?name/i) {
+ return $1;
}
- if ($$t eq 'anyexact') {
- $$term = "(" . join(" OR ", @terms) . ")";
+ # If it doesn't match the regexps above, check to see if the old
+ # SQL fragment matches the SQL of an existing column
+ foreach my $key (%{ COLUMNS() }) {
+ next unless exists COLUMNS->{$key}->{name};
+ return $key if COLUMNS->{$key}->{name} eq $column;
}
- else {
- $$term = "(" . join(" AND ", @terms) . ")";
- }
-}
-sub _multiselect_nonchanged {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $f, $ff, $t, $funcsbykey, $supptables) =
- @func_args{qw(chartid f ff t funcsbykey supptables)};
-
- my $table = $$f."_".$$chartid;
- $$ff = "$table.value";
-
- $$funcsbykey{",$$t"}($self, %func_args);
- push(@$supptables, "LEFT JOIN bug_$$f AS $table " .
- "ON $table.bug_id = bugs.bug_id ");
-}
-
-sub _equals {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
-
- $$term = "$$ff = $$q";
-}
-
-sub _notequals {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
-
- $$term = "$$ff != $$q";
-}
-
-sub _casesubstring {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
- my $dbh = Bugzilla->dbh;
-
- $$term = $dbh->sql_position($$q, $$ff) . " > 0";
-}
-
-sub _substring {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
- my $dbh = Bugzilla->dbh;
-
- $$term = $dbh->sql_iposition($$q, $$ff) . " > 0";
-}
-
-sub _notsubstring {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
- my $dbh = Bugzilla->dbh;
-
- $$term = $dbh->sql_iposition($$q, $$ff) . " = 0";
-}
-
-sub _regexp {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
- my $dbh = Bugzilla->dbh;
-
- $$term = $dbh->sql_regexp($$ff, $$q);
-}
-
-sub _notregexp {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
- my $dbh = Bugzilla->dbh;
-
- $$term = $dbh->sql_not_regexp($$ff, $$q);
-}
-
-sub _lessthan {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
-
- $$term = "$$ff < $$q";
-}
-
-sub _greaterthan {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $q, $term) = @func_args{qw(ff q term)};
-
- $$term = "$$ff > $$q";
-}
-
-sub _anyexact {
- my $self = shift;
- my %func_args = @_;
- my ($f, $ff, $v, $q, $term) = @func_args{qw(f ff v q term)};
- my $dbh = Bugzilla->dbh;
-
- my @list;
- foreach my $w (split(/,/, $$v)) {
- if ($w eq "---" && $$f =~ /resolution/) {
- $w = "";
- }
- $$q = $dbh->quote($w);
- trick_taint($$q);
- push(@list, $$q);
- }
- if (@list) {
- $$term = $dbh->sql_in($$ff, \@list);
- }
-}
-
-sub _anywordsubstr {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
-
- $$term = join(" OR ", @{GetByWordListSubstr($$ff, $$v)});
-}
-
-sub _allwordssubstr {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
-
- $$term = join(" AND ", @{GetByWordListSubstr($$ff, $$v)});
-}
-
-sub _nowordssubstr {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
-
- my @list = @{GetByWordListSubstr($$ff, $$v)};
- if (@list) {
- $$term = "NOT (" . join(" OR ", @list) . ")";
- }
-}
-
-sub _anywords {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
-
- $$term = join(" OR ", @{GetByWordList($$ff, $$v)});
-}
-
-sub _allwords {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
-
- $$term = join(" AND ", @{GetByWordList($$ff, $$v)});
-}
-
-sub _nowords {
- my $self = shift;
- my %func_args = @_;
- my ($ff, $v, $term) = @func_args{qw(ff v term)};
-
- my @list = @{GetByWordList($$ff, $$v)};
- if (@list) {
- $$term = "NOT (" . join(" OR ", @list) . ")";
- }
-}
-
-sub _changedbefore_changedafter {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $f, $ff, $t, $v, $chartfields, $supptables, $term) =
- @func_args{qw(chartid f ff t v chartfields supptables term)};
- my $dbh = Bugzilla->dbh;
-
- my $operator = ($$t =~ /before/) ? '<' : '>';
- my $table = "act_$$chartid";
- my $fieldid = $$chartfields{$$f};
- if (!$fieldid) {
- ThrowCodeError("invalid_field_name", {field => $$f});
- }
- push(@$supptables, "LEFT JOIN bugs_activity AS $table " .
- "ON $table.bug_id = bugs.bug_id " .
- "AND $table.fieldid = $fieldid " .
- "AND $table.bug_when $operator " .
- $dbh->quote(SqlifyDate($$v)) );
- $$term = "($table.bug_when IS NOT NULL)";
-}
-
-sub _changedfrom_changedto {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $chartfields, $f, $t, $v, $q, $supptables, $term) =
- @func_args{qw(chartid chartfields f t v q supptables term)};
-
- my $operator = ($$t =~ /from/) ? 'removed' : 'added';
- my $table = "act_$$chartid";
- my $fieldid = $$chartfields{$$f};
- if (!$fieldid) {
- ThrowCodeError("invalid_field_name", {field => $$f});
- }
- push(@$supptables, "LEFT JOIN bugs_activity AS $table " .
- "ON $table.bug_id = bugs.bug_id " .
- "AND $table.fieldid = $fieldid " .
- "AND $table.$operator = $$q");
- $$term = "($table.bug_when IS NOT NULL)";
-}
-
-sub _changedby {
- my $self = shift;
- my %func_args = @_;
- my ($chartid, $chartfields, $f, $v, $supptables, $term) =
- @func_args{qw(chartid chartfields f v supptables term)};
-
- my $table = "act_$$chartid";
- my $fieldid = $$chartfields{$$f};
- if (!$fieldid) {
- ThrowCodeError("invalid_field_name", {field => $$f});
- }
- my $id = login_to_id($$v, THROW_ERROR);
- push(@$supptables, "LEFT JOIN bugs_activity AS $table " .
- "ON $table.bug_id = bugs.bug_id " .
- "AND $table.fieldid = $fieldid " .
- "AND $table.who = $id");
- $$term = "($table.bug_when IS NOT NULL)";
+ return $column;
}
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Search/Clause.pm b/Websites/bugs.webkit.org/Bugzilla/Search/Clause.pm
new file mode 100644
index 0000000..a068ce5
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Search/Clause.pm
@@ -0,0 +1,138 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is BugzillaSource, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2011 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Search::Clause;
+use strict;
+
+use Bugzilla::Error;
+use Bugzilla::Search::Condition qw(condition);
+use Bugzilla::Util qw(trick_taint);
+
+sub new {
+ my ($class, $joiner) = @_;
+ if ($joiner and $joiner ne 'OR' and $joiner ne 'AND') {
+ ThrowCodeError('search_invalid_joiner', { joiner => $joiner });
+ }
+ # This will go into SQL directly so needs to be untainted.
+ trick_taint($joiner) if $joiner;
+ bless { joiner => $joiner || 'AND' }, $class;
+}
+
+sub children {
+ my ($self) = @_;
+ $self->{children} ||= [];
+ return $self->{children};
+}
+
+sub joiner { return $_[0]->{joiner} }
+
+sub has_translated_conditions {
+ my ($self) = @_;
+ my $children = $self->children;
+ return 1 if grep { $_->isa('Bugzilla::Search::Condition')
+ && $_->translated } @$children;
+ foreach my $child (@$children) {
+ next if $child->isa('Bugzilla::Search::Condition');
+ return 1 if $child->has_translated_conditions;
+ }
+ return 0;
+}
+
+sub add {
+ my $self = shift;
+ my $children = $self->children;
+ if (@_ == 3) {
+ push(@$children, condition(@_));
+ return;
+ }
+
+ my ($child) = @_;
+ return if !defined $child;
+ $child->isa(__PACKAGE__) || $child->isa('Bugzilla::Search::Condition')
+ || die 'child not the right type: ' . $child;
+ push(@{ $self->children }, $child);
+}
+
+sub negate {
+ my ($self, $value) = @_;
+ if (@_ == 2) {
+ $self->{negate} = $value ? 1 : 0;
+ }
+ return $self->{negate};
+}
+
+sub walk_conditions {
+ my ($self, $callback) = @_;
+ foreach my $child (@{ $self->children }) {
+ if ($child->isa('Bugzilla::Search::Condition')) {
+ $callback->($child);
+ }
+ else {
+ $child->walk_conditions($callback);
+ }
+ }
+}
+
+sub as_string {
+ my ($self) = @_;
+ my @strings;
+ foreach my $child (@{ $self->children }) {
+ next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
+ next if $child->isa('Bugzilla::Search::Condition')
+ && !$child->translated;
+
+ my $string = $child->as_string;
+ if ($self->joiner eq 'AND') {
+ $string = "( $string )" if $string =~ /OR/;
+ }
+ else {
+ $string = "( $string )" if $string =~ /AND/;
+ }
+ push(@strings, $string);
+ }
+
+ my $sql = join(' ' . $self->joiner . ' ', @strings);
+ $sql = "NOT( $sql )" if $sql && $self->negate;
+ return $sql;
+}
+
+# Search.pm converts URL parameters to Clause objects. This helps do the
+# reverse.
+sub as_params {
+ my ($self) = @_;
+ my @params;
+ foreach my $child (@{ $self->children }) {
+ if ($child->isa(__PACKAGE__)) {
+ my %open_paren = (f => 'OP', n => scalar $child->negate,
+ j => $child->joiner);
+ push(@params, \%open_paren);
+ push(@params, $child->as_params);
+ my %close_paren = (f => 'CP');
+ push(@params, \%close_paren);
+ }
+ else {
+ push(@params, $child->as_params);
+ }
+ }
+ return @params;
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Search/Condition.pm b/Websites/bugs.webkit.org/Bugzilla/Search/Condition.pm
new file mode 100644
index 0000000..2268da1
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Search/Condition.pm
@@ -0,0 +1,82 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is BugzillaSource, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2011 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Search::Condition;
+use strict;
+use base qw(Exporter);
+our @EXPORT_OK = qw(condition);
+
+sub new {
+ my ($class, $params) = @_;
+ my %self = %$params;
+ bless \%self, $class;
+ return \%self;
+}
+
+sub field { return $_[0]->{field} }
+sub operator { return $_[0]->{operator} }
+sub value { return $_[0]->{value} }
+
+sub fov {
+ my ($self) = @_;
+ return ($self->field, $self->operator, $self->value);
+}
+
+sub translated {
+ my ($self, $params) = @_;
+ if (@_ == 2) {
+ $self->{translated} = $params;
+ }
+ return $self->{translated};
+}
+
+sub as_string {
+ my ($self) = @_;
+ my $term = $self->translated->{term};
+ $term = "NOT( $term )" if $term && $self->negate;
+ return $term;
+}
+
+sub as_params {
+ my ($self) = @_;
+ return { f => $self->field, o => $self->operator, v => $self->value,
+ n => scalar $self->negate };
+}
+
+sub negate {
+ my ($self, $value) = @_;
+ if (@_ == 2) {
+ $self->{negate} = $value ? 1 : 0;
+ }
+ return $self->{negate};
+}
+
+###########################
+# Convenience Subroutines #
+###########################
+
+sub condition {
+ my ($field, $operator, $value) = @_;
+ return __PACKAGE__->new({ field => $field, operator => $operator,
+ value => $value });
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Search/Quicksearch.pm b/Websites/bugs.webkit.org/Bugzilla/Search/Quicksearch.pm
index 04216b8..7424f83 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Search/Quicksearch.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Search/Quicksearch.pm
@@ -30,73 +30,93 @@
use Bugzilla::Field;
use Bugzilla::Util;
+use List::Util qw(min max);
+use List::MoreUtils qw(firstidx);
+use Text::ParseWords qw(parse_line);
+
use base qw(Exporter);
@Bugzilla::Search::Quicksearch::EXPORT = qw(quicksearch);
-# Word renamings
+# Custom mappings for some fields.
use constant MAPPINGS => {
- # Status, Resolution, Platform, OS, Priority, Severity
- "status" => "bug_status",
- "resolution" => "resolution", # no change
- "platform" => "rep_platform",
- "os" => "op_sys",
- "opsys" => "op_sys",
- "priority" => "priority", # no change
- "pri" => "priority",
- "severity" => "bug_severity",
- "sev" => "bug_severity",
- # People: AssignedTo, Reporter, QA Contact, CC, Added comment (?)
- "owner" => "assigned_to", # deprecated since bug 76507
- "assignee" => "assigned_to",
- "assignedto" => "assigned_to",
- "reporter" => "reporter", # no change
- "rep" => "reporter",
- "qa" => "qa_contact",
- "qacontact" => "qa_contact",
- "cc" => "cc", # no change
- # Product, Version, Component, Target Milestone
- "product" => "product", # no change
- "prod" => "product",
- "version" => "version", # no change
- "ver" => "version",
- "component" => "component", # no change
- "comp" => "component",
- "milestone" => "target_milestone",
- "target" => "target_milestone",
- "targetmilestone" => "target_milestone",
- # Summary, Description, URL, Status whiteboard, Keywords
- "summary" => "short_desc",
- "shortdesc" => "short_desc",
- "desc" => "longdesc",
- "description" => "longdesc",
- #"comment" => "longdesc", # ???
- # reserve "comment" for "added comment" email search?
- "longdesc" => "longdesc",
- "url" => "bug_file_loc",
- "whiteboard" => "status_whiteboard",
- "statuswhiteboard" => "status_whiteboard",
- "sw" => "status_whiteboard",
- "keywords" => "keywords", # no change
- "kw" => "keywords",
- "group" => "bug_group",
- "flag" => "flagtypes.name",
- "requestee" => "requestees.login_name",
- "req" => "requestees.login_name",
- "setter" => "setters.login_name",
- "set" => "setters.login_name",
- # Attachments
- "attachment" => "attachments.description",
- "attachmentdesc" => "attachments.description",
- "attachdesc" => "attachments.description",
- "attachmentdata" => "attach_data.thedata",
- "attachdata" => "attach_data.thedata",
- "attachmentmimetype" => "attachments.mimetype",
- "attachmimetype" => "attachments.mimetype"
+ # Status, Resolution, Platform, OS, Priority, Severity
+ "status" => "bug_status",
+ "platform" => "rep_platform",
+ "os" => "op_sys",
+ "severity" => "bug_severity",
+
+ # People: AssignedTo, Reporter, QA Contact, CC, etc.
+ "assignee" => "assigned_to",
+ "owner" => "assigned_to",
+
+ # Product, Version, Component, Target Milestone
+ "milestone" => "target_milestone",
+
+ # Summary, Description, URL, Status whiteboard, Keywords
+ "summary" => "short_desc",
+ "description" => "longdesc",
+ "comment" => "longdesc",
+ "url" => "bug_file_loc",
+ "whiteboard" => "status_whiteboard",
+ "sw" => "status_whiteboard",
+ "kw" => "keywords",
+ "group" => "bug_group",
+
+ # Flags
+ "flag" => "flagtypes.name",
+ "requestee" => "requestees.login_name",
+ "setter" => "setters.login_name",
+
+ # Attachments
+ "attachment" => "attachments.description",
+ "attachmentdesc" => "attachments.description",
+ "attachdesc" => "attachments.description",
+ "attachmentdata" => "attach_data.thedata",
+ "attachdata" => "attach_data.thedata",
+ "attachmentmimetype" => "attachments.mimetype",
+ "attachmimetype" => "attachments.mimetype"
+};
+
+sub FIELD_MAP {
+ my $cache = Bugzilla->request_cache;
+ return $cache->{quicksearch_fields} if $cache->{quicksearch_fields};
+
+ # Get all the fields whose names don't contain periods. (Fields that
+ # contain periods are always handled in MAPPINGS.)
+ my @db_fields = grep { $_->name !~ /\./ }
+ @{ Bugzilla->fields({ obsolete => 0 }) };
+ my %full_map = (%{ MAPPINGS() }, map { $_->name => $_->name } @db_fields);
+
+ # Eliminate the fields that start with bug_ or rep_, because those are
+ # handled by the MAPPINGS instead, and we don't want too many names
+ # for them. (Also, otherwise "rep" doesn't match "reporter".)
+ #
+ # Remove "status_whiteboard" because we have "whiteboard" for it in
+ # the mappings, and otherwise "stat" can't match "status".
+ #
+ # Also, don't allow searching the _accessible stuff via quicksearch
+ # (both because it's unnecessary and because otherwise
+ # "reporter_accessible" and "reporter" both match "rep".
+ delete @full_map{qw(rep_platform bug_status bug_file_loc bug_group
+ bug_severity bug_status
+ status_whiteboard
+ cclist_accessible reporter_accessible)};
+
+ Bugzilla::Hook::process('quicksearch_map', {'map' => \%full_map} );
+
+ $cache->{quicksearch_fields} = \%full_map;
+
+ return $cache->{quicksearch_fields};
+}
+
+# Certain fields, when specified like "field:value" get an operator other
+# than "substring"
+use constant FIELD_OPERATOR => {
+ content => 'matches',
+ owner_idle_time => 'greaterthan',
};
# We might want to put this into localconfig or somewhere
-use constant PLATFORMS => ('pc', 'sun', 'macintosh', 'mac');
-use constant OPSYSTEMS => ('windows', 'win', 'linux');
use constant PRODUCT_EXCEPTIONS => (
'row', # [Browser]
# ^^^
@@ -109,12 +129,11 @@
);
# Quicksearch-wide globals for boolean charts.
-our ($chart, $and, $or);
+our ($chart, $and, $or, $fulltext, $bug_status_set);
sub quicksearch {
my ($searchstring) = (@_);
my $cgi = Bugzilla->cgi;
- my $urlbase = correct_urlbase();
$chart = 0;
$and = 0;
@@ -125,281 +144,107 @@
ThrowUserError('buglist_parameters_required') unless ($searchstring);
if ($searchstring =~ m/^[0-9,\s]*$/) {
- # Bug number(s) only.
-
- # Allow separation by comma or whitespace.
- $searchstring =~ s/[,\s]+/,/g;
-
- if (index($searchstring, ',') < $[) {
- # Single bug number; shortcut to show_bug.cgi.
- print $cgi->redirect(-uri => "${urlbase}show_bug.cgi?id=$searchstring");
- exit;
- }
- else {
- # List of bug numbers.
- $cgi->param('bug_id', $searchstring);
- $cgi->param('order', 'bugs.bug_id');
- $cgi->param('bugidtype', 'include');
- }
+ _bug_numbers_only($searchstring);
}
else {
- # It's not just a bug number or a list of bug numbers.
- # Maybe it's an alias?
- if ($searchstring =~ /^([^,\s]+)$/) {
- if (Bugzilla->dbh->selectrow_array(q{SELECT COUNT(*)
- FROM bugs
- WHERE alias = ?},
- undef,
- $1)) {
- print $cgi->redirect(-uri => "${urlbase}show_bug.cgi?id=$1");
- exit;
+ _handle_alias($searchstring);
+
+ # Retain backslashes and quotes, to know which strings are quoted,
+ # and which ones are not.
+ my @words = parse_line('\s+', 1, $searchstring);
+ # If parse_line() returns no data, this means strings are badly quoted.
+ # Rather than trying to guess what the user wanted to do, we throw an error.
+ scalar(@words)
+ || ThrowUserError('quicksearch_unbalanced_quotes', {string => $searchstring});
+
+ # A query cannot start with AND or OR, nor can it end with AND, OR or NOT.
+ ThrowUserError('quicksearch_invalid_query')
+ if ($words[0] =~ /^(?:AND|OR)$/ || $words[$#words] =~ /^(?:AND|OR|NOT)$/);
+
+ my (@qswords, @or_group);
+ while (scalar @words) {
+ my $word = shift @words;
+ # AND is the default word separator, similar to a whitespace,
+ # but |a AND OR b| is not a valid combination.
+ if ($word eq 'AND') {
+ ThrowUserError('quicksearch_invalid_query', {operators => ['AND', 'OR']})
+ if $words[0] eq 'OR';
}
- }
-
- # It's no alias either, so it's a more complex query.
- my $legal_statuses = get_legal_field_values('bug_status');
- my $legal_resolutions = get_legal_field_values('resolution');
-
- # Globally translate " AND ", " OR ", " NOT " to space, pipe, dash.
- $searchstring =~ s/\s+AND\s+/ /g;
- $searchstring =~ s/\s+OR\s+/|/g;
- $searchstring =~ s/\s+NOT\s+/ -/g;
-
- my @words = splitString($searchstring);
- my $searchComments =
- $#words < Bugzilla->params->{'quicksearch_comment_cutoff'};
- my @openStates = BUG_STATE_OPEN;
- my @closedStates;
- my @unknownFields;
- my (%states, %resolutions);
-
- foreach (@$legal_statuses) {
- push(@closedStates, $_) unless is_open_state($_);
- }
- foreach (@openStates) { $states{$_} = 1 }
- if ($words[0] eq 'ALL') {
- foreach (@$legal_statuses) { $states{$_} = 1 }
- shift @words;
- }
- elsif ($words[0] eq 'OPEN') {
- shift @words;
- }
- elsif ($words[0] =~ /^\+[A-Z]+(,[A-Z]+)*$/) {
- # e.g. +DUP,FIX
- if (matchPrefixes(\%states,
- \%resolutions,
- [split(/,/, substr($words[0], 1))],
- \@closedStates,
- $legal_resolutions)) {
- shift @words;
- # Allowing additional resolutions means we need to keep
- # the "no resolution" resolution.
- $resolutions{'---'} = 1;
+ # |a OR AND b| is not a valid combination.
+ # |a OR OR b| is equivalent to |a OR b| and so is harmless.
+ elsif ($word eq 'OR') {
+ ThrowUserError('quicksearch_invalid_query', {operators => ['OR', 'AND']})
+ if $words[0] eq 'AND';
+ }
+ # NOT negates the following word.
+ # |NOT AND| and |NOT OR| are not valid combinations.
+ # |NOT NOT| is fine but has no effect as they cancel themselves.
+ elsif ($word eq 'NOT') {
+ $word = shift @words;
+ next if $word eq 'NOT';
+ if ($word eq 'AND' || $word eq 'OR') {
+ ThrowUserError('quicksearch_invalid_query', {operators => ['NOT', $word]});
+ }
+ unshift(@words, "-$word");
}
else {
- # Carry on if no match found.
+ # OR groups words together, as OR has higher precedence than AND.
+ push(@or_group, $word);
+ # If the next word is not OR, then we are not in a OR group,
+ # or we are leaving it.
+ if (!defined $words[0] || $words[0] ne 'OR') {
+ push(@qswords, join('|', @or_group));
+ @or_group = ();
+ }
}
}
- elsif ($words[0] =~ /^[A-Z]+(,[A-Z]+)*$/) {
- # e.g. NEW,ASSI,REOP,FIX
- undef %states;
- if (matchPrefixes(\%states,
- \%resolutions,
- [split(/,/, $words[0])],
- $legal_statuses,
- $legal_resolutions)) {
- shift @words;
- }
- else {
- # Carry on if no match found
- foreach (@openStates) { $states{$_} = 1 }
- }
- }
- else {
- # Default: search for unresolved bugs only.
- # Put custom code here if you would like to change this behaviour.
- }
- # If we have wanted resolutions, allow closed states
- if (keys(%resolutions)) {
- foreach (@closedStates) { $states{$_} = 1 }
- }
+ _handle_status_and_resolution($qswords[0]);
+ shift(@qswords) if $bug_status_set;
- $cgi->param('bug_status', keys(%states));
- $cgi->param('resolution', keys(%resolutions));
+ my (@unknownFields, %ambiguous_fields);
+ $fulltext = Bugzilla->user->setting('quicksearch_fulltext') eq 'on' ? 1 : 0;
# Loop over all main-level QuickSearch words.
- foreach my $qsword (@words) {
- my $negate = substr($qsword, 0, 1) eq '-';
- if ($negate) {
- $qsword = substr($qsword, 1);
- }
+ foreach my $qsword (@qswords) {
+ my @or_operand = parse_line('\|', 1, $qsword);
+ foreach my $term (@or_operand) {
+ my $negate = substr($term, 0, 1) eq '-';
+ if ($negate) {
+ $term = substr($term, 1);
+ }
- my $firstChar = substr($qsword, 0, 1);
- my $baseWord = substr($qsword, 1);
- my @subWords = split(/[\|,]/, $baseWord);
- if ($firstChar eq '+') {
- foreach (@subWords) {
- addChart('short_desc', 'substring', $qsword, $negate);
- }
- }
- elsif ($firstChar eq '#') {
- addChart('short_desc', 'anywords', $baseWord, $negate);
- if ($searchComments) {
- addChart('longdesc', 'anywords', $baseWord, $negate);
- }
- }
- elsif ($firstChar eq ':') {
- foreach (@subWords) {
- addChart('product', 'substring', $_, $negate);
- addChart('component', 'substring', $_, $negate);
- }
- }
- elsif ($firstChar eq '@') {
- foreach (@subWords) {
- addChart('assigned_to', 'substring', $_, $negate);
- }
- }
- elsif ($firstChar eq '[') {
- addChart('short_desc', 'substring', $baseWord, $negate);
- addChart('status_whiteboard', 'substring', $baseWord, $negate);
- }
- elsif ($firstChar eq '!') {
- addChart('keywords', 'anywords', $baseWord, $negate);
+ next if _handle_special_first_chars($term, $negate);
+ next if _handle_field_names($term, $negate, \@unknownFields,
+ \%ambiguous_fields);
- }
- else { # No special first char
-
- # Split by '|' to get all operands for a boolean OR.
- foreach my $or_operand (split(/\|/, $qsword)) {
- if ($or_operand =~ /^votes:([0-9]+)$/) {
- # votes:xx ("at least xx votes")
- addChart('votes', 'greaterthan', $1 - 1, $negate);
+ # Having ruled out the special cases, we may now split
+ # by comma, which is another legal boolean OR indicator.
+ # Remove quotes from quoted words, if any.
+ @words = parse_line(',', 0, $term);
+ foreach my $word (@words) {
+ if (!_special_field_syntax($word, $negate)) {
+ _default_quicksearch_word($word, $negate);
}
- elsif ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
- # Flag and requestee shortcut
- addChart('flagtypes.name', 'substring', $1, $negate);
- $chart++; $and = $or = 0; # Next chart for boolean AND
- addChart('requestees.login_name', 'substring', $2, $negate);
- }
- elsif ($or_operand =~ /^([^:]+):([^:]+)$/) {
- # generic field1,field2,field3:value1,value2 notation
- my @fields = split(/,/, $1);
- my @values = split(/,/, $2);
- foreach my $field (@fields) {
- # Skip and record any unknown fields
- if (!defined(MAPPINGS->{$field})) {
- push(@unknownFields, $field);
- next;
- }
- $field = MAPPINGS->{$field};
- foreach (@values) {
- addChart($field, 'substring', $_, $negate);
- }
- }
-
- }
- else {
-
- # Having ruled out the special cases, we may now split
- # by comma, which is another legal boolean OR indicator.
- foreach my $word (split(/,/, $or_operand)) {
- # Platform and operating system
- if (grep({lc($word) eq $_} PLATFORMS)
- || grep({lc($word) eq $_} OPSYSTEMS)) {
- addChart('rep_platform', 'substring',
- $word, $negate);
- addChart('op_sys', 'substring',
- $word, $negate);
- }
- # Priority
- elsif ($word =~ m/^[pP]([1-5](-[1-5])?)$/) {
- addChart('priority', 'regexp',
- "[$1]", $negate);
- }
- # Severity
- elsif (grep({lc($word) eq substr($_, 0, 3)}
- @{get_legal_field_values('bug_severity')})) {
- addChart('bug_severity', 'substring',
- $word, $negate);
- }
- # Votes (votes>xx)
- elsif ($word =~ m/^votes>([0-9]+)$/) {
- addChart('votes', 'greaterthan',
- $1, $negate);
- }
- # Votes (votes>=xx, votes=>xx)
- elsif ($word =~ m/^votes(>=|=>)([0-9]+)$/) {
- addChart('votes', 'greaterthan',
- $2-1, $negate);
-
- }
- else { # Default QuickSearch word
-
- if (!grep({lc($word) eq $_}
- PRODUCT_EXCEPTIONS) &&
- length($word)>2
- ) {
- addChart('product', 'substring',
- $word, $negate);
- }
- if (!grep({lc($word) eq $_}
- COMPONENT_EXCEPTIONS) &&
- length($word)>2
- ) {
- addChart('component', 'substring',
- $word, $negate);
- }
- if (grep({lc($word) eq lc($_)}
- map($_->name, Bugzilla::Keyword->get_all))) {
- addChart('keywords', 'substring',
- $word, $negate);
- if (length($word)>2) {
- addChart('short_desc', 'substring',
- $word, $negate);
- addChart('status_whiteboard',
- 'substring',
- $word, $negate);
- }
-
- }
- else {
-
- addChart('short_desc', 'substring',
- $word, $negate);
- addChart('status_whiteboard', 'substring',
- $word, $negate);
- }
- if ($searchComments) {
- addChart('longdesc', 'substring',
- $word, $negate);
- }
- }
- # URL field (for IP addrs, host.names,
- # scheme://urls)
- if ($word =~ m/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/
- || $word =~ /^[A-Za-z]+(\.[A-Za-z]+)+/
- || $word =~ /:[\\\/][\\\/]/
- || $word =~ /localhost/
- || $word =~ /mailto[:]?/
- # || $word =~ /[A-Za-z]+[:][0-9]+/ #host:port
- ) {
- addChart('bug_file_loc', 'substring',
- $word, $negate);
- }
- } # foreach my $word (split(/,/, $qsword))
- } # votes and generic field detection
- } # foreach (split(/\|/, $_))
- } # "switch" $firstChar
+ _handle_urls($word, $negate);
+ }
+ }
$chart++;
$and = 0;
$or = 0;
- } # foreach (@words)
+ }
+
+ # If there is no mention of a bug status, we restrict the query
+ # to open bugs by default.
+ unless ($bug_status_set) {
+ $cgi->param('bug_status', BUG_STATE_OPEN);
+ }
# Inform user about any unknown fields
- if (scalar(@unknownFields)) {
+ if (scalar(@unknownFields) || scalar(keys %ambiguous_fields)) {
ThrowUserError("quicksearch_unknown_field",
- { fields => \@unknownFields });
+ { unknown => \@unknownFields,
+ ambiguous => \%ambiguous_fields });
}
# Make sure we have some query terms left
@@ -411,6 +256,7 @@
my $modified_query_string = $cgi->canonicalise_query(@params_to_strip);
if ($cgi->param('load')) {
+ my $urlbase = correct_urlbase();
# Param 'load' asks us to display the query in the advanced search form.
print $cgi->redirect(-uri => "${urlbase}query.cgi?format=advanced&"
. $modified_query_string);
@@ -423,50 +269,305 @@
return $modified_query_string;
}
+##########################
+# Parts of quicksearch() #
+##########################
+
+sub _bug_numbers_only {
+ my $searchstring = shift;
+ my $cgi = Bugzilla->cgi;
+ # Allow separation by comma or whitespace.
+ $searchstring =~ s/[,\s]+/,/g;
+
+ if ($searchstring !~ /,/) {
+ # Single bug number; shortcut to show_bug.cgi.
+ print $cgi->redirect(
+ -uri => correct_urlbase() . "show_bug.cgi?id=$searchstring");
+ exit;
+ }
+ else {
+ # List of bug numbers.
+ $cgi->param('bug_id', $searchstring);
+ $cgi->param('order', 'bugs.bug_id');
+ $cgi->param('bug_id_type', 'anyexact');
+ }
+}
+
+sub _handle_alias {
+ my $searchstring = shift;
+ if ($searchstring =~ /^([^,\s]+)$/) {
+ my $alias = $1;
+ # We use this direct SQL because we want quicksearch to be VERY fast.
+ my $is_alias = Bugzilla->dbh->selectrow_array(
+ q{SELECT 1 FROM bugs WHERE alias = ?}, undef, $alias);
+ if ($is_alias) {
+ $alias = url_quote($alias);
+ print Bugzilla->cgi->redirect(
+ -uri => correct_urlbase() . "show_bug.cgi?id=$alias");
+ exit;
+ }
+ }
+}
+
+sub _handle_status_and_resolution {
+ my $word = shift;
+ my $legal_statuses = get_legal_field_values('bug_status');
+ my (%states, %resolutions);
+ $bug_status_set = 1;
+
+ if ($word eq 'OPEN') {
+ $states{$_} = 1 foreach BUG_STATE_OPEN;
+ }
+ # If we want all bugs, then there is nothing to do.
+ elsif ($word ne 'ALL'
+ && !matchPrefixes(\%states, \%resolutions, $word, $legal_statuses))
+ {
+ $bug_status_set = 0;
+ }
+
+ # If we have wanted resolutions, allow closed states
+ if (keys(%resolutions)) {
+ foreach my $status (@$legal_statuses) {
+ $states{$status} = 1 unless is_open_state($status);
+ }
+ }
+
+ Bugzilla->cgi->param('bug_status', keys(%states));
+ Bugzilla->cgi->param('resolution', keys(%resolutions));
+}
+
+
+sub _handle_special_first_chars {
+ my ($qsword, $negate) = @_;
+
+ my $firstChar = substr($qsword, 0, 1);
+ my $baseWord = substr($qsword, 1);
+ my @subWords = split(/,/, $baseWord);
+
+ if ($firstChar eq '#') {
+ addChart('short_desc', 'substring', $baseWord, $negate);
+ addChart('content', 'matches', _matches_phrase($baseWord), $negate) if $fulltext;
+ return 1;
+ }
+ if ($firstChar eq ':') {
+ foreach (@subWords) {
+ addChart('product', 'substring', $_, $negate);
+ addChart('component', 'substring', $_, $negate);
+ }
+ return 1;
+ }
+ if ($firstChar eq '@') {
+ addChart('assigned_to', 'substring', $_, $negate) foreach (@subWords);
+ return 1;
+ }
+ if ($firstChar eq '[') {
+ addChart('short_desc', 'substring', $baseWord, $negate);
+ addChart('status_whiteboard', 'substring', $baseWord, $negate);
+ return 1;
+ }
+ if ($firstChar eq '!') {
+ addChart('keywords', 'anywords', $baseWord, $negate);
+ return 1;
+ }
+ return 0;
+}
+
+sub _handle_field_names {
+ my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_;
+
+ # Flag and requestee shortcut
+ if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
+ addChart('flagtypes.name', 'substring', $1, $negate);
+ $chart++; $and = $or = 0; # Next chart for boolean AND
+ addChart('requestees.login_name', 'substring', $2, $negate);
+ return 1;
+ }
+
+ # Generic field1,field2,field3:value1,value2 notation.
+ # We have to correctly ignore commas and colons in quotes.
+ my @field_values = parse_line(':', 1, $or_operand);
+ if (scalar @field_values == 2) {
+ my @fields = parse_line(',', 1, $field_values[0]);
+ my @values = parse_line(',', 1, $field_values[1]);
+ foreach my $field (@fields) {
+ my $translated = _translate_field_name($field);
+ # Skip and record any unknown fields
+ if (!defined $translated) {
+ push(@$unknownFields, $field);
+ }
+ # If we got back an array, that means the substring is
+ # ambiguous and could match more than field name
+ elsif (ref $translated) {
+ $ambiguous_fields->{$field} = $translated;
+ }
+ else {
+ if ($translated eq 'bug_status' || $translated eq 'resolution') {
+ $bug_status_set = 1;
+ }
+ foreach my $value (@values) {
+ my $operator = FIELD_OPERATOR->{$translated} || 'substring';
+ # If the string was quoted to protect some special
+ # characters such as commas and colons, we need
+ # to remove quotes.
+ if ($value =~ /^(["'])(.+)\1$/) {
+ $value = $2;
+ $value =~ s/\\(["'])/$1/g;
+ }
+ addChart($translated, $operator, $value, $negate);
+ }
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+sub _translate_field_name {
+ my $field = shift;
+ $field = lc($field);
+ my $field_map = FIELD_MAP;
+
+ # If the field exactly matches a mapping, just return right now.
+ return $field_map->{$field} if exists $field_map->{$field};
+
+ # Check if we match, as a starting substring, exactly one field.
+ my @field_names = keys %$field_map;
+ my @matches = grep { $_ =~ /^\Q$field\E/ } @field_names;
+ # Eliminate duplicates that are actually the same field
+ # (otherwise "assi" matches both "assignee" and "assigned_to", and
+ # the lines below fail when they shouldn't.)
+ my %match_unique = map { $field_map->{$_} => $_ } @matches;
+ @matches = values %match_unique;
+
+ if (scalar(@matches) == 1) {
+ return $field_map->{$matches[0]};
+ }
+ elsif (scalar(@matches) > 1) {
+ return \@matches;
+ }
+
+ # Check if we match exactly one custom field, ignoring the cf_ on the
+ # custom fields (to allow people to type things like "build" for
+ # "cf_build").
+ my %cfless;
+ foreach my $name (@field_names) {
+ my $no_cf = $name;
+ if ($no_cf =~ s/^cf_//) {
+ if ($field eq $no_cf) {
+ return $field_map->{$name};
+ }
+ $cfless{$no_cf} = $name;
+ }
+ }
+
+ # See if we match exactly one substring of any of the cf_-less fields.
+ my @cfless_matches = grep { $_ =~ /^\Q$field\E/ } (keys %cfless);
+
+ if (scalar(@cfless_matches) == 1) {
+ my $match = $cfless_matches[0];
+ my $actual_field = $cfless{$match};
+ return $field_map->{$actual_field};
+ }
+ elsif (scalar(@matches) > 1) {
+ return \@matches;
+ }
+
+ return undef;
+}
+
+sub _special_field_syntax {
+ my ($word, $negate) = @_;
+
+ # P1-5 Syntax
+ if ($word =~ m/^P(\d+)(?:-(\d+))?$/i) {
+ my ($p_start, $p_end) = ($1, $2);
+ my $legal_priorities = get_legal_field_values('priority');
+
+ # If Pn exists explicitly, use it.
+ my $start = firstidx { $_ eq "P$p_start" } @$legal_priorities;
+ my $end;
+ $end = firstidx { $_ eq "P$p_end" } @$legal_priorities if defined $p_end;
+
+ # If Pn doesn't exist explicitly, then we mean the nth priority.
+ if ($start == -1) {
+ $start = max(0, $p_start - 1);
+ }
+ my $prios = $legal_priorities->[$start];
+
+ if (defined $end) {
+ # If Pn doesn't exist explicitly, then we mean the nth priority.
+ if ($end == -1) {
+ $end = min(scalar(@$legal_priorities), $p_end) - 1;
+ $end = max(0, $end); # Just in case the user typed P0.
+ }
+ ($start, $end) = ($end, $start) if $end < $start;
+ $prios = join(',', @$legal_priorities[$start..$end])
+ }
+
+ addChart('priority', 'anyexact', $prios, $negate);
+ return 1;
+ }
+ return 0;
+}
+
+sub _default_quicksearch_word {
+ my ($word, $negate) = @_;
+
+ if (!grep { lc($word) eq $_ } PRODUCT_EXCEPTIONS and length($word) > 2) {
+ addChart('product', 'substring', $word, $negate);
+ }
+
+ if (!grep { lc($word) eq $_ } COMPONENT_EXCEPTIONS and length($word) > 2) {
+ addChart('component', 'substring', $word, $negate);
+ }
+
+ my @legal_keywords = map($_->name, Bugzilla::Keyword->get_all);
+ if (grep { lc($word) eq lc($_) } @legal_keywords) {
+ addChart('keywords', 'substring', $word, $negate);
+ }
+
+ addChart('alias', 'substring', $word, $negate);
+ addChart('short_desc', 'substring', $word, $negate);
+ addChart('status_whiteboard', 'substring', $word, $negate);
+ addChart('content', 'matches', _matches_phrase($word), $negate) if $fulltext;
+}
+
+sub _handle_urls {
+ my ($word, $negate) = @_;
+ # URL field (for IP addrs, host.names,
+ # scheme://urls)
+ if ($word =~ m/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/
+ || $word =~ /^[A-Za-z]+(\.[A-Za-z]+)+/
+ || $word =~ /:[\\\/][\\\/]/
+ || $word =~ /localhost/
+ || $word =~ /mailto[:]?/)
+ # || $word =~ /[A-Za-z]+[:][0-9]+/ #host:port
+ {
+ addChart('bug_file_loc', 'substring', $word, $negate);
+ }
+}
+
###########################################################################
# Helpers
###########################################################################
-# Split string on whitespace, retaining quoted strings as one
-sub splitString {
- my $string = shift;
- my @quoteparts;
- my @parts;
- my $i = 0;
-
- # Now split on quote sign; be tolerant about unclosed quotes
- @quoteparts = split(/"/, $string);
- foreach my $part (@quoteparts) {
- # After every odd quote, quote special chars
- $part = url_quote($part) if $i++ % 2;
- }
- # Join again
- $string = join('"', @quoteparts);
-
- # Now split on unescaped whitespace
- @parts = split(/\s+/, $string);
- foreach (@parts) {
- # Protect plus signs from becoming a blank.
- # If "+" appears as the first character, leave it alone
- # as it has a special meaning. Strings which start with
- # "+" must be quoted.
- s/(?<!^)\+/%2B/g;
- # Remove quotes
- s/"//g;
- }
- return @parts;
+# Quote and escape a phrase appropriately for a "content matches" search.
+sub _matches_phrase {
+ my ($phrase) = @_;
+ $phrase =~ s/"/\\"/g;
+ return "\"$phrase\"";
}
# Expand found prefixes to states or resolutions
sub matchPrefixes {
- my $hr_states = shift;
- my $hr_resolutions = shift;
- my $ar_prefixes = shift;
- my $ar_check_states = shift;
- my $ar_check_resolutions = shift;
+ my ($hr_states, $hr_resolutions, $word, $ar_check_states) = @_;
+ return unless $word =~ /^[A-Z_]+(,[A-Z_]+)*$/;
+
+ my @ar_prefixes = split(/,/, $word);
+ my $ar_check_resolutions = get_legal_field_values('resolution');
my $foundMatch = 0;
- foreach my $prefix (@$ar_prefixes) {
+ foreach my $prefix (@ar_prefixes) {
foreach (@$ar_check_states) {
if (/^$prefix/) {
$$hr_states{$_} = 1;
@@ -487,19 +588,10 @@
sub negateComparisonType {
my $comparisonType = shift;
- if ($comparisonType eq 'substring') {
- return 'notsubstring';
- }
- elsif ($comparisonType eq 'anywords') {
+ if ($comparisonType eq 'anywords') {
return 'nowords';
}
- elsif ($comparisonType eq 'regexp') {
- return 'notregexp';
- }
- else {
- # Don't know how to negate that
- ThrowCodeError('unknown_comparison_type');
- }
+ return "not$comparisonType";
}
# Add a boolean chart
@@ -524,7 +616,7 @@
my $cgi = Bugzilla->cgi;
$cgi->param("field$expr", $field);
$cgi->param("type$expr", $type);
- $cgi->param("value$expr", url_decode($value));
+ $cgi->param("value$expr", $value);
}
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Search/Recent.pm b/Websites/bugs.webkit.org/Bugzilla/Search/Recent.pm
new file mode 100644
index 0000000..5f04b18
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Search/Recent.pm
@@ -0,0 +1,172 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Search::Recent;
+use strict;
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+#############
+# Constants #
+#############
+
+use constant DB_TABLE => 'profile_search';
+use constant LIST_ORDER => 'id DESC';
+# Do not track buglists viewed by users.
+use constant AUDIT_CREATES => 0;
+use constant AUDIT_UPDATES => 0;
+use constant AUDIT_REMOVES => 0;
+
+use constant DB_COLUMNS => qw(
+ id
+ user_id
+ bug_list
+ list_order
+);
+
+use constant VALIDATORS => {
+ user_id => \&_check_user_id,
+ bug_list => \&_check_bug_list,
+ list_order => \&_check_list_order,
+};
+
+use constant UPDATE_COLUMNS => qw(bug_list list_order);
+
+###################
+# DB Manipulation #
+###################
+
+sub create {
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ my $search = $class->SUPER::create(@_);
+ my $user_id = $search->user_id;
+
+ # Enforce there only being SAVE_NUM_SEARCHES per user.
+ my $min_id = $dbh->selectrow_array(
+ 'SELECT id FROM profile_search WHERE user_id = ? ORDER BY id DESC '
+ . $dbh->sql_limit(1, SAVE_NUM_SEARCHES), undef, $user_id);
+ if ($min_id) {
+ $dbh->do('DELETE FROM profile_search WHERE user_id = ? AND id <= ?',
+ undef, ($user_id, $min_id));
+ }
+ $dbh->bz_commit_transaction();
+ return $search;
+}
+
+sub create_placeholder {
+ my $class = shift;
+ return $class->create({ user_id => Bugzilla->user->id,
+ bug_list => '' });
+}
+
+###############
+# Constructor #
+###############
+
+sub check {
+ my $class = shift;
+ my $search = $class->SUPER::check(@_);
+ my $user = Bugzilla->user;
+ if ($search->user_id != $user->id) {
+ ThrowUserError('object_does_not_exist', { id => $search->id });
+ }
+ return $search;
+}
+
+sub check_quietly {
+ my $class = shift;
+ my $error_mode = Bugzilla->error_mode;
+ Bugzilla->error_mode(ERROR_MODE_DIE);
+ my $search = eval { $class->check(@_) };
+ Bugzilla->error_mode($error_mode);
+ return $search;
+}
+
+sub new_from_cookie {
+ my ($invocant, $bug_ids) = @_;
+ my $class = ref($invocant) || $invocant;
+
+ my $search = { id => 'cookie',
+ user_id => Bugzilla->user->id,
+ bug_list => join(',', @$bug_ids) };
+
+ bless $search, $class;
+ return $search;
+}
+
+####################
+# Simple Accessors #
+####################
+
+sub bug_list { return [split(',', $_[0]->{'bug_list'})]; }
+sub list_order { return $_[0]->{'list_order'}; }
+sub user_id { return $_[0]->{'user_id'}; }
+
+############
+# Mutators #
+############
+
+sub set_bug_list { $_[0]->set('bug_list', $_[1]); }
+sub set_list_order { $_[0]->set('list_order', $_[1]); }
+
+##############
+# Validators #
+##############
+
+sub _check_user_id {
+ my ($invocant, $id) = @_;
+ require Bugzilla::User;
+ return Bugzilla::User->check({ id => $id })->id;
+}
+
+sub _check_bug_list {
+ my ($invocant, $list) = @_;
+
+ my @bug_ids = ref($list) ? @$list : split(',', $list || '');
+ detaint_natural($_) foreach @bug_ids;
+ return join(',', @bug_ids);
+}
+
+sub _check_list_order { defined $_[1] ? trim($_[1]) : '' }
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Search::Recent - A search recently run by a logged-in user.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Search::Recent;
+
+
+=head1 DESCRIPTION
+
+This is an implementation of L<Bugzilla::Object>, and so has all the
+same methods available as L<Bugzilla::Object>, in addition to what is
+documented below.
diff --git a/Websites/bugs.webkit.org/Bugzilla/Search/Saved.pm b/Websites/bugs.webkit.org/Bugzilla/Search/Saved.pm
index c832224..fc773fc 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Search/Saved.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Search/Saved.pm
@@ -32,30 +32,89 @@
use Bugzilla::User;
use Bugzilla::Util;
+use Scalar::Util qw(blessed);
+
#############
# Constants #
#############
use constant DB_TABLE => 'namedqueries';
+# Do not track buglists saved by users.
+use constant AUDIT_CREATES => 0;
+use constant AUDIT_UPDATES => 0;
+use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
id
userid
name
query
- query_type
);
-use constant REQUIRED_CREATE_FIELDS => qw(name query);
-
use constant VALIDATORS => {
name => \&_check_name,
query => \&_check_query,
- query_type => \&_check_query_type,
link_in_footer => \&_check_link_in_footer,
};
-use constant UPDATE_COLUMNS => qw(name query query_type);
+use constant UPDATE_COLUMNS => qw(name query);
+
+###############
+# Constructor #
+###############
+
+sub new {
+ my $class = shift;
+ my $param = shift;
+ my $dbh = Bugzilla->dbh;
+
+ my $user;
+ if (ref $param) {
+ $user = $param->{user} || Bugzilla->user;
+ my $name = $param->{name};
+ if (!defined $name) {
+ ThrowCodeError('bad_arg',
+ {argument => 'name',
+ function => "${class}::new"});
+ }
+ my $condition = 'userid = ? AND name = ?';
+ my $user_id = blessed $user ? $user->id : $user;
+ detaint_natural($user_id)
+ || ThrowCodeError('param_must_be_numeric',
+ {function => $class . '::_init', param => 'user'});
+ my @values = ($user_id, $name);
+ $param = { condition => $condition, values => \@values };
+ }
+
+ unshift @_, $param;
+ my $self = $class->SUPER::new(@_);
+ if ($self) {
+ $self->{user} = $user if blessed $user;
+
+ # Some DBs (read: Oracle) incorrectly mark the query string as UTF-8
+ # when it's coming out of the database, even though it has no UTF-8
+ # characters in it, which prevents Bugzilla::CGI from later reading
+ # it correctly.
+ utf8::downgrade($self->{query}) if utf8::is_utf8($self->{query});
+ }
+ return $self;
+}
+
+sub check {
+ my $class = shift;
+ my $search = $class->SUPER::check(@_);
+ my $user = Bugzilla->user;
+ return $search if $search->user->id == $user->id;
+
+ if (!$search->shared_with_group
+ or !$user->in_group($search->shared_with_group))
+ {
+ ThrowUserError('missing_query', { queryname => $search->name,
+ sharer_id => $search->user->id });
+ }
+
+ return $search;
+}
##############
# Validators #
@@ -84,12 +143,6 @@
return $cgi->query_string;
}
-sub _check_query_type {
- my ($invocant, $type) = @_;
- # Right now the only query type is LIST_OF_BUGS.
- return $type ? LIST_OF_BUGS : QUERY_LIST;
-}
-
#########################
# Database Manipulation #
#########################
@@ -117,6 +170,40 @@
return $obj;
}
+sub rename_field_value {
+ my ($class, $field, $old_value, $new_value) = @_;
+
+ my $old = url_quote($old_value);
+ my $new = url_quote($new_value);
+ my $old_sql = $old;
+ $old_sql =~ s/([_\%])/\\$1/g;
+
+ my $table = $class->DB_TABLE;
+ my $id_field = $class->ID_FIELD;
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+
+ my %queries = @{ $dbh->selectcol_arrayref(
+ "SELECT $id_field, query FROM $table WHERE query LIKE ?",
+ {Columns=>[1,2]}, "\%$old_sql\%") };
+ foreach my $id (keys %queries) {
+ my $query = $queries{$id};
+ $query =~ s/\b$field=\Q$old\E\b/$field=$new/gi;
+ # Fix boolean charts.
+ while ($query =~ /\bfield(\d+-\d+-\d+)=\Q$field\E\b/gi) {
+ my $chart_id = $1;
+ # Note that this won't handle lists or substrings inside of
+ # boolean charts. Users will have to fix those themselves.
+ $query =~ s/\bvalue\Q$chart_id\E=\Q$old\E\b/value$chart_id=$new/i;
+ }
+ $dbh->do("UPDATE $table SET query = ? WHERE $id_field = ?",
+ undef, $query, $id);
+ }
+
+ $dbh->bz_commit_transaction();
+}
+
sub preload {
my ($searches) = @_;
my $dbh = Bugzilla->dbh;
@@ -210,8 +297,7 @@
# Simple Accessors #
####################
-sub bug_ids_only { return ($_[0]->{'query_type'} == LIST_OF_BUGS) ? 1 : 0; }
-sub url { return $_[0]->{'query'}; }
+sub url { return $_[0]->{'query'}; }
sub user {
my ($self) = @_;
@@ -226,7 +312,6 @@
sub set_name { $_[0]->set('name', $_[1]); }
sub set_url { $_[0]->set('query', $_[1]); }
-sub set_query_type { $_[0]->set('query_type', $_[1]); }
1;
@@ -264,7 +349,8 @@
=item C<new>
-Does not accept a bare C<name> argument. Instead, accepts only an id.
+Takes either an id, or the named parameters C<user> and C<name>.
+C<user> can be either a L<Bugzilla::User> object or a numeric user id.
See also: L<Bugzilla::Object/new>.
@@ -297,9 +383,9 @@
I<current user> (not the owner of the search, but the person actually
using Bugzilla right now).
-=item C<bug_ids_only>
+=item C<type>
-True if the search contains only a list of Bug IDs.
+The numeric id of the type of search this is (from L<Bugzilla::Constants>).
=item C<shared_with_group>
diff --git a/Websites/bugs.webkit.org/Bugzilla/Series.pm b/Websites/bugs.webkit.org/Bugzilla/Series.pm
index 95d0a8f..7168bcb 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Series.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Series.pm
@@ -33,7 +33,11 @@
use Bugzilla::Error;
use Bugzilla::Util;
-use Bugzilla::User;
+
+# This is a hack so that we can re-use the rename_field_value
+# code from Bugzilla::Search::Saved.
+use constant DB_TABLE => 'series';
+use constant ID_FIELD => 'series_id';
sub new {
my $invocant = shift;
@@ -69,7 +73,8 @@
elsif ($arg_count >= 6 && $arg_count <= 8) {
# We've been given a load of parameters to create a new Series from.
# Currently, undef is always passed as the first parameter; this allows
- # you to call writeToDatabase() unconditionally.
+ # you to call writeToDatabase() unconditionally.
+ # XXX - You cannot set category_id and subcategory_id from here.
$self->initFromParameters(@_);
}
else {
@@ -80,34 +85,30 @@
}
sub initFromDatabase {
- my $self = shift;
- my $series_id = shift;
-
+ my ($self, $series_id) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
detaint_natural($series_id)
|| ThrowCodeError("invalid_series_id", { 'series_id' => $series_id });
-
- my $dbh = Bugzilla->dbh;
+
+ my $grouplist = $user->groups_as_string;
+
my @series = $dbh->selectrow_array("SELECT series.series_id, cc1.name, " .
"cc2.name, series.name, series.creator, series.frequency, " .
- "series.query, series.is_public " .
+ "series.query, series.is_public, series.category, series.subcategory " .
"FROM series " .
- "LEFT JOIN series_categories AS cc1 " .
+ "INNER JOIN series_categories AS cc1 " .
" ON series.category = cc1.id " .
- "LEFT JOIN series_categories AS cc2 " .
+ "INNER JOIN series_categories AS cc2 " .
" ON series.subcategory = cc2.id " .
"LEFT JOIN category_group_map AS cgm " .
" ON series.category = cgm.category_id " .
- "LEFT JOIN user_group_map AS ugm " .
- " ON cgm.group_id = ugm.group_id " .
- " AND ugm.user_id = " . Bugzilla->user->id .
- " AND isbless = 0 " .
- "WHERE series.series_id = $series_id AND " .
- "(is_public = 1 OR creator = " . Bugzilla->user->id . " OR " .
- "(ugm.group_id IS NOT NULL)) " .
- $dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
- 'series.name, series.creator, series.frequency, ' .
- 'series.query, series.is_public'));
-
+ " AND cgm.group_id NOT IN($grouplist) " .
+ "WHERE series.series_id = ? " .
+ " AND (creator = ? OR (is_public = 1 AND cgm.category_id IS NULL))",
+ undef, ($series_id, $user->id));
+
if (@series) {
$self->initFromParameters(@series);
return $self;
@@ -122,8 +123,9 @@
my $self = shift;
($self->{'series_id'}, $self->{'category'}, $self->{'subcategory'},
- $self->{'name'}, $self->{'creator'}, $self->{'frequency'},
- $self->{'query'}, $self->{'public'}) = @_;
+ $self->{'name'}, $self->{'creator_id'}, $self->{'frequency'},
+ $self->{'query'}, $self->{'public'}, $self->{'category_id'},
+ $self->{'subcategory_id'}) = @_;
# If the first parameter is undefined, check if this series already
# exists and update it series_id accordingly
@@ -152,7 +154,7 @@
$self->{'name'} = $cgi->param('name')
|| ThrowUserError("missing_name");
- $self->{'creator'} = Bugzilla->user->id;
+ $self->{'creator_id'} = Bugzilla->user->id;
$self->{'frequency'} = $cgi->param('frequency');
detaint_natural($self->{'frequency'})
@@ -203,7 +205,7 @@
$dbh->do("INSERT INTO series (creator, category, subcategory, " .
"name, frequency, query, is_public) VALUES " .
"(?, ?, ?, ?, ?, ?, ?)", undef,
- $self->{'creator'}, $category_id, $subcategory_id, $self->{'name'},
+ $self->{'creator_id'}, $category_id, $subcategory_id, $self->{'name'},
$self->{'frequency'}, $self->{'query'}, $self->{'public'});
# Retrieve series_id
@@ -258,4 +260,27 @@
return $category_id;
}
+##########
+# Methods
+##########
+sub id { return $_[0]->{'series_id'}; }
+sub name { return $_[0]->{'name'}; }
+
+sub creator {
+ my $self = shift;
+
+ if (!$self->{creator} && $self->{creator_id}) {
+ require Bugzilla::User;
+ $self->{creator} = new Bugzilla::User($self->{creator_id});
+ }
+ return $self->{creator};
+}
+
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->do('DELETE FROM series WHERE series_id = ?', undef, $self->id);
+}
+
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Status.pm b/Websites/bugs.webkit.org/Bugzilla/Status.pm
index f8b7733..ffef600 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Status.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Status.pm
@@ -22,43 +22,115 @@
package Bugzilla::Status;
-use base qw(Bugzilla::Object Exporter);
-@Bugzilla::Status::EXPORT = qw(BUG_STATE_OPEN is_open_state closed_bug_statuses);
+use Bugzilla::Error;
+# This subclasses Bugzilla::Field::Choice instead of implementing
+# ChoiceInterface, because a bug status literally is a special type
+# of Field::Choice, not just an object that happens to have the same
+# methods.
+use base qw(Bugzilla::Field::Choice Exporter);
+@Bugzilla::Status::EXPORT = qw(
+ BUG_STATE_OPEN
+ SPECIAL_STATUS_WORKFLOW_ACTIONS
+
+ is_open_state
+ closed_bug_statuses
+);
################################
##### Initialization #####
################################
-use constant DB_TABLE => 'bug_status';
-
-use constant DB_COLUMNS => qw(
- id
- value
- sortkey
- isactive
- is_open
+use constant SPECIAL_STATUS_WORKFLOW_ACTIONS => qw(
+ none
+ duplicate
+ change_resolution
+ clearresolution
);
-use constant NAME_FIELD => 'value';
-use constant LIST_ORDER => 'sortkey, value';
+use constant DB_TABLE => 'bug_status';
+
+# This has all the standard Bugzilla::Field::Choice columns plus "is_open"
+sub DB_COLUMNS {
+ return ($_[0]->SUPER::DB_COLUMNS, 'is_open');
+}
+
+sub VALIDATORS {
+ my $invocant = shift;
+ my $validators = $invocant->SUPER::VALIDATORS;
+ $validators->{is_open} = \&Bugzilla::Object::check_boolean;
+ $validators->{value} = \&_check_value;
+ return $validators;
+}
+
+#########################
+# Database Manipulation #
+#########################
+
+sub create {
+ my $class = shift;
+ my $self = $class->SUPER::create(@_);
+ delete Bugzilla->request_cache->{status_bug_state_open};
+ add_missing_bug_status_transitions();
+ return $self;
+}
+
+sub remove_from_db {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $id = $self->id;
+ $dbh->bz_start_transaction();
+ $self->SUPER::remove_from_db();
+ $dbh->do('DELETE FROM status_workflow
+ WHERE old_status = ? OR new_status = ?',
+ undef, $id, $id);
+ $dbh->bz_commit_transaction();
+ delete Bugzilla->request_cache->{status_bug_state_open};
+}
###############################
##### Accessors ####
###############################
-sub name { return $_[0]->{'value'}; }
-sub sortkey { return $_[0]->{'sortkey'}; }
sub is_active { return $_[0]->{'isactive'}; }
sub is_open { return $_[0]->{'is_open'}; }
+sub is_static {
+ my $self = shift;
+ if ($self->name eq 'UNCONFIRMED'
+ || $self->name eq Bugzilla->params->{'duplicate_or_move_bug_status'})
+ {
+ return 1;
+ }
+ return 0;
+}
+
+##############
+# Validators #
+##############
+
+sub _check_value {
+ my $invocant = shift;
+ my $value = $invocant->SUPER::_check_value(@_);
+
+ if (grep { lc($value) eq lc($_) } SPECIAL_STATUS_WORKFLOW_ACTIONS) {
+ ThrowUserError('fieldvalue_reserved_word',
+ { field => $invocant->field, value => $value });
+ }
+ return $value;
+}
+
+
###############################
##### Methods ####
###############################
sub BUG_STATE_OPEN {
- # XXX - We should cache this list.
my $dbh = Bugzilla->dbh;
- return @{$dbh->selectcol_arrayref('SELECT value FROM bug_status WHERE is_open = 1')};
+ my $cache = Bugzilla->request_cache;
+ $cache->{status_bug_state_open} ||=
+ $dbh->selectcol_arrayref('SELECT value FROM bug_status
+ WHERE is_open = 1');
+ return @{ $cache->{status_bug_state_open} };
}
# Tells you whether or not the argument is a valid "open" state.
@@ -107,28 +179,6 @@
return $self->{'can_change_to'};
}
-sub can_change_from {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- if (!defined $self->{'can_change_from'}) {
- my $old_status_ids = $dbh->selectcol_arrayref('SELECT old_status
- FROM status_workflow
- INNER JOIN bug_status
- ON id = old_status
- WHERE isactive = 1
- AND new_status = ?
- AND old_status IS NOT NULL',
- undef, $self->id);
-
- # Allow the bug status to remain unchanged.
- push(@$old_status_ids, $self->id);
- $self->{'can_change_from'} = Bugzilla::Status->new_from_list($old_status_ids);
- }
-
- return $self->{'can_change_from'};
-}
-
sub comment_required_on_change_from {
my ($self, $old_status) = @_;
my ($cond, $values) = $self->_status_condition($old_status);
@@ -191,7 +241,7 @@
use Bugzilla::Status;
- my $bug_status = new Bugzilla::Status({name => 'ASSIGNED'});
+ my $bug_status = new Bugzilla::Status({ name => 'IN_PROGRESS' });
my $bug_status = new Bugzilla::Status(4);
my @closed_bug_statuses = closed_bug_statuses();
@@ -231,17 +281,6 @@
Returns: A list of Bugzilla::Status objects.
-=item C<can_change_from>
-
- Description: Returns the list of active statuses a bug can be changed from
- given the new bug status. If the bug status is available on
- bug creation, this method doesn't return this information.
- You have to call C<can_change_to> instead.
-
- Params: none.
-
- Returns: A list of Bugzilla::Status objects.
-
=item C<comment_required_on_change_from>
=over
diff --git a/Websites/bugs.webkit.org/Bugzilla/Template.pm b/Websites/bugs.webkit.org/Bugzilla/Template.pm
index 6a8a717..48b871b 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Template.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Template.pm
@@ -34,48 +34,42 @@
use strict;
+use Bugzilla::Bug;
use Bugzilla::Constants;
+use Bugzilla::Hook;
use Bugzilla::Install::Requirements;
-use Bugzilla::Install::Util qw(install_string template_include_path include_languages);
+use Bugzilla::Install::Util qw(install_string template_include_path
+ include_languages);
+use Bugzilla::Keyword;
use Bugzilla::Util;
use Bugzilla::User;
use Bugzilla::Error;
+use Bugzilla::Search;
use Bugzilla::Status;
use Bugzilla::Token;
-use Bugzilla::Template::Parser;
use Cwd qw(abs_path);
use MIME::Base64;
-# for time2str - replace by TT Date plugin??
use Date::Format ();
-use File::Basename qw(dirname);
+use File::Basename qw(basename dirname);
use File::Find;
use File::Path qw(rmtree mkpath);
use File::Spec;
use IO::Dir;
+use List::MoreUtils qw(firstidx);
+use Scalar::Util qw(blessed);
use base qw(Template);
-# As per the Template::Base documentation, the _init() method is being called
-# by the new() constructor. We take advantage of this in order to plug our
-# UTF-8-aware Parser object in neatly after the original _init() method has
-# happened, in particular, after having set up the constants namespace.
-# See bug 413121 for details.
-sub _init {
- my $self = shift;
- my $config = $_[0];
+use constant FORMAT_TRIPLE => '%19s|%-28s|%-28s';
+use constant FORMAT_3_SIZE => [19,28,28];
+use constant FORMAT_DOUBLE => '%19s %-55s';
+use constant FORMAT_2_SIZE => [19,55];
- $self->SUPER::_init(@_) || return undef;
-
- $self->{PARSER} = $config->{PARSER}
- = new Bugzilla::Template::Parser($config);
-
- # Now we need to re-create the default Service object, making it aware
- # of our Parser object.
- $self->{SERVICE} = $config->{SERVICE}
- = Template::Config->service($config);
-
- return $self;
+# Pseudo-constant.
+sub SAFE_URL_REGEXP {
+ my $safe_protocols = join('|', SAFE_PROTOCOLS);
+ return qr/($safe_protocols):[^\s<>\"]+[\w\/]/i;
}
# Convert the constants in the Bugzilla::Constants module into a hash we can
@@ -103,12 +97,11 @@
# settings of the user and of the available languages
# If no Accept-Language is present it uses the defined default
# Templates may also be found in the extensions/ tree
-sub getTemplateIncludePath {
+sub _include_path {
+ my $lang = shift || '';
my $cache = Bugzilla->request_cache;
- my $lang = $cache->{'language'} || '';
- $cache->{"template_include_path_$lang"} ||= template_include_path({
- use_languages => Bugzilla->languages,
- only_language => $lang });
+ $cache->{"template_include_path_$lang"} ||=
+ template_include_path({ language => $lang });
return $cache->{"template_include_path_$lang"};
}
@@ -161,7 +154,7 @@
# If you want to modify this routine, read the comments carefully
sub quoteUrls {
- my ($text, $curr_bugid) = (@_);
+ my ($text, $bug, $comment) = (@_);
return $text unless $text;
# We use /g for speed, but uris can have other things inside them
@@ -189,22 +182,38 @@
my $count = 0;
my $tmp;
+ my @hook_regexes;
+ Bugzilla::Hook::process('bug_format_comment',
+ { text => \$text, bug => $bug, regexes => \@hook_regexes,
+ comment => $comment });
+
+ foreach my $re (@hook_regexes) {
+ my ($match, $replace) = @$re{qw(match replace)};
+ if (ref($replace) eq 'CODE') {
+ $text =~ s/$match/($things[$count++] = $replace->({matches => [
+ $1, $2, $3, $4,
+ $5, $6, $7, $8,
+ $9, $10]}))
+ && ("\0\0" . ($count-1) . "\0\0")/egx;
+ }
+ else {
+ $text =~ s/$match/($things[$count++] = $replace)
+ && ("\0\0" . ($count-1) . "\0\0")/egx;
+ }
+ }
+
# Provide tooltips for full bug links (Bug 74355)
my $urlbase_re = '(' . join('|',
map { qr/$_/ } grep($_, Bugzilla->params->{'urlbase'},
Bugzilla->params->{'sslbase'})) . ')';
$text =~ s~\b(${urlbase_re}\Qshow_bug.cgi?id=\E([0-9]+)(\#c([0-9]+))?)\b
- ~($things[$count++] = get_bug_link($3, $1, $5)) &&
+ ~($things[$count++] = get_bug_link($3, $1, { comment_num => $5 })) &&
("\0\0" . ($count-1) . "\0\0")
~egox;
# non-mailto protocols
- my $safe_protocols = join('|', SAFE_PROTOCOLS);
- my $protocol_re = qr/($safe_protocols)/i;
-
- $text =~ s~\b(${protocol_re}: # The protocol:
- [^\s<>\"]+ # Any non-whitespace
- [\w\/]) # so that we end in \w or /
+ my $safe_protocols = SAFE_URL_REGEXP();
+ $text =~ s~\b($safe_protocols)
~($tmp = html_quote($1)) &&
($things[$count++] = "<a href=\"$tmp\">$tmp</a>") &&
("\0\0" . ($count-1) . "\0\0")
@@ -221,34 +230,30 @@
# mailto:
# Use |<nothing> so that $1 is defined regardless
- $text =~ s~\b(mailto:|)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b
+ # @ is the encoded '@' character.
+ $text =~ s~\b(mailto:|)?([\w\.\-\+\=]+&\#64;[\w\-]+(?:\.[\w\-]+)+)\b
~<a href=\"mailto:$2\">$1$2</a>~igx;
- # attachment links - handle both cases separately for simplicity
- $text =~ s~((?:^Created\ an\ |\b)attachment\s*\(id=(\d+)\)(\s\[edit\])?)
- ~($things[$count++] = get_attachment_link($2, $1)) &&
- ("\0\0" . ($count-1) . "\0\0")
- ~egmx;
-
- $text =~ s~\b(attachment\s*\#?\s*(\d+))
+ # attachment links
+ $text =~ s~\b(attachment\s*\#?\s*(\d+)(?:\s+\[details\])?)
~($things[$count++] = get_attachment_link($2, $1)) &&
("\0\0" . ($count-1) . "\0\0")
~egmxi;
# Current bug ID this comment belongs to
- my $current_bugurl = $curr_bugid ? "show_bug.cgi?id=$curr_bugid" : "";
+ my $current_bugurl = $bug ? ("show_bug.cgi?id=" . $bug->id) : "";
# This handles bug a, comment b type stuff. Because we're using /g
# we have to do this in one pattern, and so this is semi-messy.
# Also, we can't use $bug_re?$comment_re? because that will match the
# empty string
- my $bug_word = get_text('term', { term => 'bug' });
+ my $bug_word = template_var('terms')->{bug};
my $bug_re = qr/\Q$bug_word\E\s*\#?\s*(\d+)/i;
my $comment_re = qr/comment\s*\#?\s*(\d+)/i;
$text =~ s~\b($bug_re(?:\s*,?\s*$comment_re)?|$comment_re)
~ # We have several choices. $1 here is the link, and $2-4 are set
# depending on which part matched
- (defined($2) ? get_bug_link($2,$1,$3) :
+ (defined($2) ? get_bug_link($2, $1, { comment_num => $3 }) :
"<a href=\"$current_bugurl#c$4\">$1</a>")
~egox;
@@ -260,8 +265,10 @@
~get_bug_link($1, $1)
~egmx;
- # Now remove the encoding hacks
- $text =~ s/\0\0(\d+)\0\0/$things[$1]/eg;
+ # Now remove the encoding hacks in reverse order
+ for (my $i = $#things; $i >= 0; $i--) {
+ $text =~ s/\0\0($i)\0\0/$things[$i]/eg;
+ }
$text =~ s/$chr1\0/\0/g;
return $text;
@@ -272,21 +279,15 @@
my ($attachid, $link_text) = @_;
my $dbh = Bugzilla->dbh;
- detaint_natural($attachid)
- || die "get_attachment_link() called with non-integer attachment number";
+ my $attachment = new Bugzilla::Attachment($attachid);
- my ($bugid, $isobsolete, $desc) =
- $dbh->selectrow_array('SELECT bug_id, isobsolete, description
- FROM attachments WHERE attach_id = ?',
- undef, $attachid);
-
- if ($bugid) {
+ if ($attachment) {
my $title = "";
my $className = "";
- if (Bugzilla->user->can_see_bug($bugid)) {
- $title = $desc;
+ if (Bugzilla->user->can_see_bug($attachment->bug_id)) {
+ $title = $attachment->description;
}
- if ($isobsolete) {
+ if ($attachment->isobsolete) {
$className = "bz_obsolete";
}
# Prevent code injection in the title.
@@ -294,9 +295,17 @@
$link_text =~ s/ \[details\]$//;
my $linkval = "attachment.cgi?id=$attachid";
+
+ # If the attachment is a patch, try to link to the diff rather
+ # than the text, by default.
+ my $patchlink = "";
+ if ($attachment->ispatch and Bugzilla->feature('patch_viewer')) {
+ $patchlink = '&action=diff';
+ }
+
# Whitespace matters here because these links are in <pre> tags.
return qq|<span class="$className">|
- . qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>|
+ . qq|<a href="${linkval}${patchlink}" name="attach_${attachid}" title="$title">$link_text</a>|
. qq| <a href="${linkval}&action=edit" title="$title">[details]</a>|
. qq|</span>|;
}
@@ -313,50 +322,172 @@
# comment in the bug
sub get_bug_link {
- my ($bug_num, $link_text, $comment_num) = @_;
+ my ($bug, $link_text, $options) = @_;
+ $options ||= {};
my $dbh = Bugzilla->dbh;
- if (!defined($bug_num) || ($bug_num eq "")) {
- return "<missing bug number>";
+ if (defined $bug) {
+ $bug = blessed($bug) ? $bug : new Bugzilla::Bug($bug);
+ return $link_text if $bug->{error};
}
- my $quote_bug_num = html_quote($bug_num);
- detaint_natural($bug_num) || return "<invalid bug number: $quote_bug_num>";
- my ($bug_state, $bug_res, $bug_desc) =
- $dbh->selectrow_array('SELECT bugs.bug_status, resolution, short_desc
- FROM bugs WHERE bugs.bug_id = ?',
- undef, $bug_num);
+ my $template = Bugzilla->template_inner;
+ my $linkified;
+ $template->process('bug/link.html.tmpl',
+ { bug => $bug, link_text => $link_text, %$options }, \$linkified);
+ return $linkified;
+}
- if ($bug_state) {
- # Initialize these variables to be "" so that we don't get warnings
- # if we don't change them below (which is highly likely).
- my ($pre, $title, $post) = ("", "", "");
-
- $title = get_text('get_status', {status => $bug_state});
- if ($bug_state eq 'UNCONFIRMED') {
- $pre = "<i>";
- $post = "</i>";
- }
- elsif (!is_open_state($bug_state)) {
- $pre = '<span class="bz_closed">';
- $title .= ' ' . get_text('get_resolution', {resolution => $bug_res});
- $post = '</span>';
- }
- if (Bugzilla->user->can_see_bug($bug_num)) {
- $title .= " - $bug_desc";
- }
- # Prevent code injection in the title.
- $title = html_quote(clean_text($title));
-
- my $linkval = "show_bug.cgi?id=$bug_num";
- if (defined $comment_num) {
- $linkval .= "#c$comment_num";
- }
- return qq{$pre<a href="$linkval" title="$title">$link_text</a>$post};
+# We use this instead of format because format doesn't deal well with
+# multi-byte languages.
+sub multiline_sprintf {
+ my ($format, $args, $sizes) = @_;
+ my @parts;
+ my @my_sizes = @$sizes; # Copy this so we don't modify the input array.
+ foreach my $string (@$args) {
+ my $size = shift @my_sizes;
+ my @pieces = split("\n", wrap_hard($string, $size));
+ push(@parts, \@pieces);
}
- else {
- return qq{$link_text};
+
+ my $formatted;
+ while (1) {
+ # Get the first item of each part.
+ my @line = map { shift @$_ } @parts;
+ # If they're all undef, we're done.
+ last if !grep { defined $_ } @line;
+ # Make any single undef item into ''
+ @line = map { defined $_ ? $_ : '' } @line;
+ # And append a formatted line
+ $formatted .= sprintf($format, @line);
+ # Remove trailing spaces, or they become lots of =20's in
+ # quoted-printable emails.
+ $formatted =~ s/\s+$//;
+ $formatted .= "\n";
}
+ return $formatted;
+}
+
+#####################
+# Header Generation #
+#####################
+
+# Returns the last modification time of a file, as an integer number of
+# seconds since the epoch.
+sub _mtime { return (stat($_[0]))[9] }
+
+sub mtime_filter {
+ my ($file_url, $mtime) = @_;
+ # This environment var is set in the .htaccess if we have mod_headers
+ # and mod_expires installed, to make sure that JS and CSS with "?"
+ # after them will still be cached by clients.
+ return $file_url if !$ENV{BZ_CACHE_CONTROL};
+ if (!$mtime) {
+ my $cgi_path = bz_locations()->{'cgi_path'};
+ my $file_path = "$cgi_path/$file_url";
+ $mtime = _mtime($file_path);
+ }
+ return "$file_url?$mtime";
+}
+
+# Set up the skin CSS cascade:
+#
+# 1. YUI CSS
+# 2. Standard Bugzilla stylesheet set (persistent)
+# 3. Standard Bugzilla stylesheet set (selectable)
+# 4. All third-party "skin" stylesheet sets (selectable)
+# 5. Page-specific styles
+# 6. Custom Bugzilla stylesheet set (persistent)
+#
+# "Selectable" skin file sets may be either preferred or alternate.
+# Exactly one is preferred, determined by the "skin" user preference.
+sub css_files {
+ my ($style_urls, $yui, $yui_css) = @_;
+
+ # global.css goes on every page, and so does IE-fixes.css.
+ my @requested_css = ('skins/standard/global.css', @$style_urls,
+ 'skins/standard/IE-fixes.css');
+
+ my @yui_required_css;
+ foreach my $yui_name (@$yui) {
+ next if !$yui_css->{$yui_name};
+ push(@yui_required_css, "js/yui/assets/skins/sam/$yui_name.css");
+ }
+ unshift(@requested_css, @yui_required_css);
+
+ my @css_sets = map { _css_link_set($_) } @requested_css;
+
+ my %by_type = (standard => [], alternate => {}, skin => [], custom => []);
+ foreach my $set (@css_sets) {
+ foreach my $key (keys %$set) {
+ if ($key eq 'alternate') {
+ foreach my $alternate_skin (keys %{ $set->{alternate} }) {
+ my $files = $by_type{alternate}->{$alternate_skin} ||= [];
+ push(@$files, $set->{alternate}->{$alternate_skin});
+ }
+ }
+ else {
+ push(@{ $by_type{$key} }, $set->{$key});
+ }
+ }
+ }
+
+ return \%by_type;
+}
+
+sub _css_link_set {
+ my ($file_name) = @_;
+
+ my %set = (standard => mtime_filter($file_name));
+
+ # We use (^|/) to allow Extensions to use the skins system if they
+ # want.
+ if ($file_name !~ m{(^|/)skins/standard/}) {
+ return \%set;
+ }
+
+ my $skin_user_prefs = Bugzilla->user->settings->{skin};
+ my $cgi_path = bz_locations()->{'cgi_path'};
+ # If the DB is not accessible, user settings are not available.
+ my $all_skins = $skin_user_prefs ? $skin_user_prefs->legal_values : [];
+ my %skin_urls;
+ foreach my $option (@$all_skins) {
+ next if $option eq 'standard';
+ my $skin_file_name = $file_name;
+ $skin_file_name =~ s{(^|/)skins/standard/}{skins/contrib/$option/};
+ if (my $mtime = _mtime("$cgi_path/$skin_file_name")) {
+ $skin_urls{$option} = mtime_filter($skin_file_name, $mtime);
+ }
+ }
+ $set{alternate} = \%skin_urls;
+
+ my $skin = $skin_user_prefs->{'value'};
+ if ($skin ne 'standard' and defined $set{alternate}->{$skin}) {
+ $set{skin} = delete $set{alternate}->{$skin};
+ }
+
+ my $custom_file_name = $file_name;
+ $custom_file_name =~ s{(^|/)skins/standard/}{skins/custom/};
+ if (my $custom_mtime = _mtime("$cgi_path/$custom_file_name")) {
+ $set{custom} = mtime_filter($custom_file_name, $custom_mtime);
+ }
+
+ return \%set;
+}
+
+# YUI dependency resolution
+sub yui_resolve_deps {
+ my ($yui, $yui_deps) = @_;
+
+ my @yui_resolved;
+ foreach my $yui_name (@$yui) {
+ my $deps = $yui_deps->{$yui_name} || [];
+ foreach my $dep (reverse @$deps) {
+ push(@yui_resolved, $dep) if !grep { $_ eq $dep } @yui_resolved;
+ }
+ push(@yui_resolved, $yui_name) if !grep { $_ eq $yui_name } @yui_resolved;
+ }
+ return \@yui_resolved;
}
###############################################################################
@@ -372,20 +503,31 @@
# to template variables.
use Template::Stash;
+# Allow keys to start with an underscore or a dot.
+$Template::Stash::PRIVATE = undef;
+
# Add "contains***" methods to list variables that search for one or more
# items in a list and return boolean values representing whether or not
# one/all/any item(s) were found.
$Template::Stash::LIST_OPS->{ contains } =
sub {
my ($list, $item) = @_;
- return grep($_ eq $item, @$list);
+ if (ref $item && $item->isa('Bugzilla::Object')) {
+ return grep($_->id == $item->id, @$list);
+ } else {
+ return grep($_ eq $item, @$list);
+ }
};
$Template::Stash::LIST_OPS->{ containsany } =
sub {
my ($list, $items) = @_;
foreach my $item (@$items) {
- return 1 if grep($_ eq $item, @$list);
+ if (ref $item && $item->isa('Bugzilla::Object')) {
+ return 1 if grep($_->id == $item->id, @$list);
+ } else {
+ return 1 if grep($_ eq $item, @$list);
+ }
}
return 0;
};
@@ -404,14 +546,6 @@
return $_[0];
};
-# Add a "substr" method to the Template Toolkit's "scalar" object
-# that returns a substring of a string.
-$Template::Stash::SCALAR_OPS->{ substr } =
- sub {
- my ($scalar, $offset, $length) = @_;
- return substr($scalar, $offset, $length);
- };
-
# Add a "truncate" method to the Template Toolkit's "scalar" object
# that truncates a string to a certain length.
$Template::Stash::SCALAR_OPS->{ truncate } =
@@ -431,6 +565,17 @@
###############################################################################
+sub process {
+ my $self = shift;
+ # All of this current_langs stuff allows template_inner to correctly
+ # determine what-language Template object it should instantiate.
+ my $current_langs = Bugzilla->request_cache->{template_current_lang} ||= [];
+ unshift(@$current_langs, $self->context->{bz_language});
+ my $retval = $self->SUPER::process(@_);
+ shift @$current_langs;
+ return $retval;
+}
+
# Construct the Template object
# Note that all of the failure cases here can't use templateable errors,
@@ -440,19 +585,13 @@
my $class = shift;
my %opts = @_;
- # checksetup.pl will call us once for any template/lang directory.
- # We need a possibility to reset the cache, so that no files from
- # the previous language pollute the action.
- if ($opts{'clean_cache'}) {
- delete Bugzilla->request_cache->{template_include_path_};
- }
+ # IMPORTANT - If you make any FILTER changes here, make sure to
+ # make them in t/004.template.t also, if required.
- # IMPORTANT - If you make any configuration changes here, make sure to
- # make them in t/004.template.t and checksetup.pl.
-
- return $class->new({
+ my $config = {
# Colon-separated list of directories containing templates.
- INCLUDE_PATH => [\&getTemplateIncludePath],
+ INCLUDE_PATH => $opts{'include_path'}
+ || _include_path($opts{'language'}),
# Remove white-space before template directives (PRE_CHOMP) and at the
# beginning and end of templates and template blocks (TRIM) for better
@@ -461,10 +600,20 @@
PRE_CHOMP => 1,
TRIM => 1,
- COMPILE_DIR => bz_locations()->{'datadir'} . "/template",
+ # Bugzilla::Template::Plugin::Hook uses the absolute (in mod_perl)
+ # or relative (in mod_cgi) paths of hook files to explicitly compile
+ # a specific file. Also, these paths may be absolute at any time
+ # if a packager has modified bz_locations() to contain absolute
+ # paths.
+ ABSOLUTE => 1,
+ RELATIVE => $ENV{MOD_PERL} ? 0 : 1,
+
+ COMPILE_DIR => bz_locations()->{'template_cache'},
# Initialize templates (f.e. by loading plugins like Hook).
- PRE_PROCESS => "global/initialize.none.tmpl",
+ PRE_PROCESS => ["global/initialize.none.tmpl"],
+
+ ENCODING => Bugzilla->params->{'utf8'} ? 'UTF-8' : undef,
# Functions for processing text within templates in various ways.
# IMPORTANT! When adding a filter here that does not override a
@@ -508,6 +657,8 @@
$var =~ s/\n/\\n/g;
$var =~ s/\r/\\r/g;
$var =~ s/\@/\\x40/g; # anti-spam for email addresses
+ $var =~ s/</\\x3c/g;
+ $var =~ s/>/\\x3e/g;
return $var;
},
@@ -523,6 +674,7 @@
# See bugs 4928, 22983 and 32000 for more details
html_linebreak => sub {
my ($var) = @_;
+ $var = html_quote($var);
$var =~ s/\r\n/\
/g;
$var =~ s/\n\r/\
/g;
$var =~ s/\r/\
/g;
@@ -540,31 +692,28 @@
xml => \&Bugzilla::Util::xml_quote ,
- # This filter escapes characters in a variable or value string for
- # use in a query string. It escapes all characters NOT in the
- # regex set: [a-zA-Z0-9_\-.]. The 'uri' filter should be used for
- # a full URL that may have characters that need encoding.
- url_quote => \&Bugzilla::Util::url_quote ,
-
# This filter is similar to url_quote but used a \ instead of a %
# as prefix. In addition it replaces a ' ' by a '_'.
css_class_quote => \&Bugzilla::Util::css_class_quote ,
+ # Removes control characters and trims extra whitespace.
+ clean_text => \&Bugzilla::Util::clean_text ,
+
quoteUrls => [ sub {
- my ($context, $bug) = @_;
+ my ($context, $bug, $comment) = @_;
return sub {
my $text = shift;
- return quoteUrls($text, $bug);
+ return quoteUrls($text, $bug, $comment);
};
},
1
],
bug_link => [ sub {
- my ($context, $bug) = @_;
+ my ($context, $bug, $options) = @_;
return sub {
my $text = shift;
- return get_bug_link($bug, $text);
+ return get_bug_link($bug, $text, $options);
};
},
1
@@ -613,44 +762,24 @@
},
# Format a time for display (more info in Bugzilla::Util)
- time => \&Bugzilla::Util::format_time,
+ time => [ sub {
+ my ($context, $format, $timezone) = @_;
+ return sub {
+ my $time = shift;
+ return format_time($time, $format, $timezone);
+ };
+ },
+ 1
+ ],
- # Bug 120030: Override html filter to obscure the '@' in user
- # visible strings.
- # Bug 319331: Handle BiDi disruptions.
- html => sub {
- my ($var) = Template::Filters::html_filter(@_);
- # Obscure '@'.
- $var =~ s/\@/\@/g;
- if (Bugzilla->params->{'utf8'}) {
- # Remove the following characters because they're
- # influencing BiDi:
- # --------------------------------------------------------
- # |Code |Name |UTF-8 representation|
- # |------|--------------------------|--------------------|
- # |U+202a|Left-To-Right Embedding |0xe2 0x80 0xaa |
- # |U+202b|Right-To-Left Embedding |0xe2 0x80 0xab |
- # |U+202c|Pop Directional Formatting|0xe2 0x80 0xac |
- # |U+202d|Left-To-Right Override |0xe2 0x80 0xad |
- # |U+202e|Right-To-Left Override |0xe2 0x80 0xae |
- # --------------------------------------------------------
- #
- # The following are characters influencing BiDi, too, but
- # they can be spared from filtering because they don't
- # influence more than one character right or left:
- # --------------------------------------------------------
- # |Code |Name |UTF-8 representation|
- # |------|--------------------------|--------------------|
- # |U+200e|Left-To-Right Mark |0xe2 0x80 0x8e |
- # |U+200f|Right-To-Left Mark |0xe2 0x80 0x8f |
- # --------------------------------------------------------
- $var =~ s/[\x{202a}-\x{202e}]//g;
- }
- return $var;
- },
+ html => \&Bugzilla::Util::html_quote,
html_light => \&Bugzilla::Util::html_light_quote,
+ email => \&Bugzilla::Util::email_filter,
+
+ mtime => \&mtime_filter,
+
# iCalendar contentline filter
ics => [ sub {
my ($context, @args) = @_;
@@ -689,6 +818,20 @@
$var =~ s/\>/>/g;
$var =~ s/\"/\"/g;
$var =~ s/\&/\&/g;
+ # Now remove extra whitespace...
+ my $collapse_filter = $Template::Filters::FILTERS->{collapse};
+ $var = $collapse_filter->($var);
+ # And if we're not in the WebService, wrap the message.
+ # (Wrapping the message in the WebService is unnecessary
+ # and causes awkward things like \n's appearing in error
+ # messages in JSON-RPC.)
+ unless (Bugzilla->usage_mode == USAGE_MODE_JSON
+ or Bugzilla->usage_mode == USAGE_MODE_XMLRPC)
+ {
+ $var = wrap_comment($var, 72);
+ }
+ $var =~ s/\ / /g;
+
return $var;
},
@@ -717,24 +860,36 @@
# Function to create date strings
'time2str' => \&Date::Format::time2str,
+ # Fixed size column formatting for bugmail.
+ 'format_columns' => sub {
+ my $cols = shift;
+ my $format = ($cols == 3) ? FORMAT_TRIPLE : FORMAT_DOUBLE;
+ my $col_size = ($cols == 3) ? FORMAT_3_SIZE : FORMAT_2_SIZE;
+ return multiline_sprintf($format, \@_, $col_size);
+ },
+
# Generic linear search function
- 'lsearch' => \&Bugzilla::Util::lsearch,
+ 'lsearch' => sub {
+ my ($array, $item) = @_;
+ return firstidx { $_ eq $item } @$array;
+ },
# Currently logged in user, if any
# If an sudo session is in progress, this is the user we're faking
'user' => sub { return Bugzilla->user; },
+
+ # Currenly active language
+ # XXX Eventually this should probably be replaced with something
+ # like Bugzilla->language.
+ 'current_language' => sub {
+ my ($language) = include_languages();
+ return $language;
+ },
# If an sudo session is in progress, this is the user who
# started the session.
'sudoer' => sub { return Bugzilla->sudoer; },
- # SendBugMail - sends mail about a bug, using Bugzilla::BugMail.pm
- 'SendBugMail' => sub {
- my ($id, $mailrecipients) = (@_);
- require Bugzilla::BugMail;
- Bugzilla::BugMail::Send($id, $mailrecipients);
- },
-
# Allow templates to access the "corect" URLBase value
'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
@@ -746,78 +901,136 @@
return $docs_urlbase;
},
+ # Check whether the URL is safe.
+ 'is_safe_url' => sub {
+ my $url = shift;
+ return 0 unless $url;
+
+ my $safe_url_regexp = SAFE_URL_REGEXP();
+ return 1 if $url =~ /^$safe_url_regexp$/;
+ # Pointing to a local file with no colon in its name is fine.
+ return 1 if $url =~ /^[^\s<>\":]+[\w\/]$/i;
+ # If we come here, then we cannot guarantee it's safe.
+ return 0;
+ },
+
# Allow templates to generate a token themselves.
'issue_hash_token' => \&Bugzilla::Token::issue_hash_token,
+ # A way for all templates to get at Field data, cached.
+ 'bug_fields' => sub {
+ my $cache = Bugzilla->request_cache;
+ $cache->{template_bug_fields} ||=
+ Bugzilla->fields({ by_name => 1 });
+ return $cache->{template_bug_fields};
+ },
+
+ 'css_files' => \&css_files,
+ yui_resolve_deps => \&yui_resolve_deps,
+
+ # Whether or not keywords are enabled, in this Bugzilla.
+ 'use_keywords' => sub { return Bugzilla::Keyword->any_exist; },
+
+ # All the keywords.
+ 'all_keywords' => sub { return Bugzilla::Keyword->get_all(); },
+
+ 'feature_enabled' => sub { return Bugzilla->feature(@_); },
+
+ # field_descs can be somewhat slow to generate, so we generate
+ # it only once per-language no matter how many times
+ # $template->process() is called.
+ 'field_descs' => sub { return template_var('field_descs') },
+
+ # Calling bug/field-help.none.tmpl once per label is very
+ # expensive, so we generate it once per-language.
+ 'help_html' => sub { return template_var('help_html') },
+
+ # This way we don't have to load field-descs.none.tmpl in
+ # many templates.
+ 'display_value' => \&Bugzilla::Util::display_value,
+
+ 'install_string' => \&Bugzilla::Install::Util::install_string,
+
+ 'report_columns' => \&Bugzilla::Search::REPORT_COLUMNS,
+
# These don't work as normal constants.
DB_MODULE => \&Bugzilla::Constants::DB_MODULE,
REQUIRED_MODULES =>
\&Bugzilla::Install::Requirements::REQUIRED_MODULES,
OPTIONAL_MODULES => sub {
my @optional = @{OPTIONAL_MODULES()};
- @optional = sort {$a->{feature} cmp $b->{feature}}
- @optional;
+ foreach my $item (@optional) {
+ my @features;
+ foreach my $feat_id (@{ $item->{feature} }) {
+ push(@features, install_string("feature_$feat_id"));
+ }
+ $item->{feature} = \@features;
+ }
return \@optional;
},
+ 'default_authorizer' => new Bugzilla::Auth(),
},
+ };
- }) || die("Template creation failed: " . $class->error());
+ local $Template::Config::CONTEXT = 'Bugzilla::Template::Context';
+
+ Bugzilla::Hook::process('template_before_create', { config => $config });
+ my $template = $class->new($config)
+ || die("Template creation failed: " . $class->error());
+
+ # Pass on our current language to any template hooks or inner templates
+ # called by this Template object.
+ $template->context->{bz_language} = $opts{language} || '';
+
+ return $template;
}
# Used as part of the two subroutines below.
-our (%_templates_to_precompile, $_current_path);
-
+our %_templates_to_precompile;
sub precompile_templates {
my ($output) = @_;
# Remove the compiled templates.
+ my $cache_dir = bz_locations()->{'template_cache'};
my $datadir = bz_locations()->{'datadir'};
- if (-e "$datadir/template") {
+ if (-e $cache_dir) {
print install_string('template_removing_dir') . "\n" if $output;
- # XXX This frequently fails if the webserver made the files, because
- # then the webserver owns the directories. We could fix that by
- # doing a chmod/chown on all the directories here.
- rmtree("$datadir/template");
+ # This frequently fails if the webserver made the files, because
+ # then the webserver owns the directories.
+ rmtree($cache_dir);
- # Check that the directory was really removed
- if(-e "$datadir/template") {
- print "\n\n";
- print "The directory '$datadir/template' could not be removed.\n";
- print "Please remove it manually and rerun checksetup.pl.\n\n";
- exit;
+ # Check that the directory was really removed, and if not, move it
+ # into data/deleteme/.
+ if (-e $cache_dir) {
+ my $deleteme = "$datadir/deleteme";
+
+ print STDERR "\n\n",
+ install_string('template_removal_failed',
+ { deleteme => $deleteme,
+ template_cache => $cache_dir }), "\n\n";
+ mkpath($deleteme);
+ my $random = generate_random_password();
+ rename($cache_dir, "$deleteme/$random")
+ or die "move failed: $!";
}
}
print install_string('template_precompile') if $output;
- my $templatedir = bz_locations()->{'templatedir'};
- # Don't hang on templates which use the CGI library
- eval("use CGI qw(-no_debug)");
-
- my $dir_reader = new IO::Dir($templatedir) || die "$templatedir: $!";
- my @language_dirs = grep { /^[a-z-]+$/i } $dir_reader->read;
- $dir_reader->close;
+ # Pre-compile all available languages.
+ my $paths = template_include_path({ language => Bugzilla->languages });
- foreach my $dir (@language_dirs) {
- next if ($dir eq 'CVS');
- -d "$templatedir/$dir/default" || -d "$templatedir/$dir/custom"
- || next;
- local $ENV{'HTTP_ACCEPT_LANGUAGE'} = $dir;
- my $template = Bugzilla::Template->create(clean_cache => 1);
+ foreach my $dir (@$paths) {
+ my $template = Bugzilla::Template->create(include_path => [$dir]);
- # Precompile all the templates found in all the directories.
%_templates_to_precompile = ();
- foreach my $subdir (qw(custom extension default), bz_locations()->{'project'}) {
- next unless $subdir; # If 'project' is empty.
- $_current_path = File::Spec->catdir($templatedir, $dir, $subdir);
- next unless -d $_current_path;
- # Traverse the template hierarchy.
- find({ wanted => \&_precompile_push, no_chdir => 1 }, $_current_path);
- }
+ # Traverse the template hierarchy.
+ find({ wanted => \&_precompile_push, no_chdir => 1 }, $dir);
# The sort isn't totally necessary, but it makes debugging easier
# by making the templates always be compiled in the same order.
foreach my $file (sort keys %_templates_to_precompile) {
+ $file =~ s{^\Q$dir\E/}{};
# Compile the template but throw away the result. This has the side-
# effect of writing the compiled version to disk.
$template->context->template($file);
@@ -827,28 +1040,17 @@
# Under mod_perl, we look for templates using the absolute path of the
# template directory, which causes Template Toolkit to look for their
# *compiled* versions using the full absolute path under the data/template
- # directory. (Like data/template/var/www/html/mod_perl/.) To avoid
+ # directory. (Like data/template/var/www/html/bugzilla/.) To avoid
# re-compiling templates under mod_perl, we symlink to the
# already-compiled templates. This doesn't work on Windows.
if (!ON_WINDOWS) {
- my $abs_root = dirname(abs_path($templatedir));
- my $todir = "$datadir/template$abs_root";
- mkpath($todir);
- # We use abs2rel so that the symlink will look like
- # "../../../../template" which works, while just
- # "data/template/template/" doesn't work.
- my $fromdir = File::Spec->abs2rel("$datadir/template/template", $todir);
- # We eval for systems that can't symlink at all, where "symlink"
- # throws a fatal error.
- eval { symlink($fromdir, "$todir/template")
- or warn "Failed to symlink from $fromdir to $todir: $!" };
+ # We do these separately in case they're in different locations.
+ _do_template_symlink(bz_locations()->{'templatedir'});
+ _do_template_symlink(bz_locations()->{'extensionsdir'});
}
# If anything created a Template object before now, clear it out.
delete Bugzilla->request_cache->{template};
- # This is the single variable used to precompile templates,
- # which needs to be cleared as well.
- delete Bugzilla->request_cache->{template_include_path_};
print install_string('done') . "\n" if $output;
}
@@ -859,11 +1061,40 @@
return if (-d $name);
return if ($name =~ /\/CVS\//);
return if ($name !~ /\.tmpl$/);
-
- $name =~ s/\Q$_current_path\E\///;
$_templates_to_precompile{$name} = 1;
}
+# Helper for precompile_templates
+sub _do_template_symlink {
+ my $dir_to_symlink = shift;
+
+ my $abs_path = abs_path($dir_to_symlink);
+
+ # If $dir_to_symlink is already an absolute path (as might happen
+ # with packagers who set $libpath to an absolute path), then we don't
+ # need to do this symlink.
+ return if ($abs_path eq $dir_to_symlink);
+
+ my $abs_root = dirname($abs_path);
+ my $dir_name = basename($abs_path);
+ my $cache_dir = bz_locations()->{'template_cache'};
+ my $container = "$cache_dir$abs_root";
+ mkpath($container);
+ my $target = "$cache_dir/$dir_name";
+ # Check if the directory exists, because if there are no extensions,
+ # there won't be an "data/template/extensions" directory to link to.
+ if (-d $target) {
+ # We use abs2rel so that the symlink will look like
+ # "../../../../template" which works, while just
+ # "data/template/template/" doesn't work.
+ my $relative_target = File::Spec->abs2rel($target, $container);
+
+ my $link_name = "$container/$dir_name";
+ symlink($relative_target, $link_name)
+ or warn "Could not make $link_name a symlink to $relative_target: $!";
+ }
+}
+
1;
__END__
diff --git a/Websites/bugs.webkit.org/Bugzilla/Template/Context.pm b/Websites/bugs.webkit.org/Bugzilla/Template/Context.pm
new file mode 100644
index 0000000..7923603
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Template/Context.pm
@@ -0,0 +1,104 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is ITA Software.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This exists to implement the template-before_process hook.
+package Bugzilla::Template::Context;
+use strict;
+use base qw(Template::Context);
+
+use Bugzilla::Hook;
+use Scalar::Util qw(blessed);
+
+sub process {
+ my $self = shift;
+ # We don't want to run the template_before_process hook for
+ # template hooks (but we do want it to run if a hook calls
+ # PROCESS inside itself). The problem is that the {component}->{name} of
+ # hooks is unreliable--sometimes it starts with ./ and it's the
+ # full path to the hook template, and sometimes it's just the relative
+ # name (like hook/global/field-descs-end.none.tmpl). Also, calling
+ # template_before_process for hook templates doesn't seem too useful,
+ # because that's already part of the extension and they should be able
+ # to modify their hook if they want (or just modify the variables in the
+ # calling template).
+ if (not delete $self->{bz_in_hook}) {
+ $self->{bz_in_process} = 1;
+ }
+ my $result = $self->SUPER::process(@_);
+ delete $self->{bz_in_process};
+ return $result;
+}
+
+# This method is called by Template-Toolkit exactly once per template or
+# block (look at a compiled template) so this is an ideal place for us to
+# modify the variables before a template or block runs.
+#
+# We don't do it during Context::process because at that time
+# our stash hasn't been set correctly--the parameters we were passed
+# in the PROCESS or INCLUDE directive haven't been set, and if we're
+# in an INCLUDE, the stash is not yet localized during process().
+sub stash {
+ my $self = shift;
+ my $stash = $self->SUPER::stash(@_);
+
+ my $name = $stash->{component}->{name};
+ my $pre_process = $self->config->{PRE_PROCESS};
+
+ # Checking bz_in_process tells us that we were indeed called as part of a
+ # Context::process, and not at some other point.
+ #
+ # Checking $name makes sure that we're processing a file, and not just a
+ # block, by checking that the name has a period in it. We don't allow
+ # blocks because their names are too unreliable--an extension could have
+ # a block with the same name, or multiple files could have a same-named
+ # block, and then your extension would malfunction.
+ #
+ # We also make sure that we don't run, ever, during the PRE_PROCESS
+ # templates, because if somebody calls Throw*Error globally inside of
+ # template_before_process, that causes an infinite recursion into
+ # the PRE_PROCESS templates (because Bugzilla, while inside
+ # global/intialize.none.tmpl, loads the template again to create the
+ # template object for Throw*Error).
+ #
+ # Checking Bugzilla::Hook::in prevents infinite recursion on this hook.
+ if ($self->{bz_in_process} and $name =~ /\./
+ and !grep($_ eq $name, @$pre_process)
+ and !Bugzilla::Hook::in('template_before_process'))
+ {
+ Bugzilla::Hook::process("template_before_process",
+ { vars => $stash, context => $self,
+ file => $name });
+ }
+
+ # This prevents other calls to stash() that might somehow happen
+ # later in the file from also triggering the hook.
+ delete $self->{bz_in_process};
+
+ return $stash;
+}
+
+# We need a DESTROY sub for the same reason that Bugzilla::CGI does.
+sub DESTROY {
+ my $self = shift;
+ $self->SUPER::DESTROY(@_);
+};
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/Template/Parser.pm b/Websites/bugs.webkit.org/Bugzilla/Template/Parser.pm
deleted file mode 100644
index 0965e07..0000000
--- a/Websites/bugs.webkit.org/Bugzilla/Template/Parser.pm
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Bug Tracking System.
-#
-# The Initial Developer of the Original Code is Marc Schumann.
-# Portions created by Marc Schumann are Copyright (C) 2008 Marc Schumann.
-# All Rights Reserved.
-#
-# Contributor(s): Marc Schumann <wurblzap@gmail.com>
-
-
-package Bugzilla::Template::Parser;
-
-use strict;
-
-use base qw(Template::Parser);
-
-sub parse {
- my ($self, $text, @params) = @_;
- if (Bugzilla->params->{'utf8'}) {
- utf8::is_utf8($text) || utf8::decode($text);
- }
- return $self->SUPER::parse($text, @params);
-}
-
-1;
-
-__END__
-
-=head1 NAME
-
-Bugzilla::Template::Parser - Wrapper around the Template Toolkit
-C<Template::Parser> object
-
-=head1 DESCRIPTION
-
-This wrapper makes the Template Toolkit aware of UTF-8 templates.
-
-=head1 SUBROUTINES
-
-=over
-
-=item C<parse($options)>
-
-Description: Parses template text using Template::Parser::parse(),
-converting the text to UTF-8 encoding before, if necessary.
-
-Params: C<$text> - Text to pass to Template::Parser::parse().
-
-Returns: Parsed text as returned by Template::Parser::parse().
-
-=back
-
-=head1 SEE ALSO
-
-L<Template>
diff --git a/Websites/bugs.webkit.org/Bugzilla/Template/Plugin/Hook.pm b/Websites/bugs.webkit.org/Bugzilla/Template/Plugin/Hook.pm
index 99ece08..f243481 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Template/Plugin/Hook.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Template/Plugin/Hook.pm
@@ -20,91 +20,88 @@
# Contributor(s): Myk Melez <myk@mozilla.org>
# Zach Lipton <zach@zachlipton.com>
# Elliotte Martin <everythingsolved.com>
-#
+# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Template::Plugin::Hook;
-
use strict;
-
-use Bugzilla::Constants;
-use Bugzilla::Install::Util qw(include_languages);
-use Bugzilla::Template;
-use Bugzilla::Util;
-use Bugzilla::Error;
-use File::Spec;
-
use base qw(Template::Plugin);
-sub load {
- my ($class, $context) = @_;
- return $class;
-}
+use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(template_include_path);
+use Bugzilla::Util;
+use Bugzilla::Error;
+
+use File::Spec;
sub new {
my ($class, $context) = @_;
return bless { _CONTEXT => $context }, $class;
}
+sub _context { return $_[0]->{_CONTEXT} }
+
sub process {
my ($self, $hook_name, $template) = @_;
- $template ||= $self->{_CONTEXT}->stash->{component}->{name};
-
- my @hooks;
+ my $context = $self->_context();
+ $template ||= $context->stash->{component}->{name};
# sanity check:
if (!$template =~ /[\w\.\/\-_\\]+/) {
- ThrowCodeError('template_invalid', { name => $template});
+ ThrowCodeError('template_invalid', { name => $template });
}
- # also get extension hook files that live in extensions/:
- # parse out the parts of the template name
- my ($vol, $subpath, $filename) = File::Spec->splitpath($template);
- $subpath = $subpath || '';
- $filename =~ m/(.*)\.(.*)\.tmpl/;
- my $templatename = $1;
+ my (undef, $path, $filename) = File::Spec->splitpath($template);
+ $path ||= '';
+ $filename =~ m/(.+)\.(.+)\.tmpl$/;
+ my $template_name = $1;
my $type = $2;
- # munge the filename to create the extension hook filename:
- my $extensiontemplate = $subpath.'/'.$templatename.'-'.$hook_name.'.'.$type.'.tmpl';
- my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
- my @usedlanguages = include_languages({use_languages => Bugzilla->languages});
- foreach my $extension (@extensions) {
- next if -e "$extension/disabled";
- foreach my $language (@usedlanguages) {
- my $file = $extension.'/template/'.$language.'/'.$extensiontemplate;
+
+ # Hooks are named like this:
+ my $extension_template = "$path$template_name-$hook_name.$type.tmpl";
+
+ # Get the hooks out of the cache if they exist. Otherwise, read them
+ # from the disk.
+ my $cache = Bugzilla->request_cache->{template_plugin_hook_cache} ||= {};
+ my $lang = $context->{bz_language} || '';
+ $cache->{"${lang}__$extension_template"}
+ ||= $self->_get_hooks($extension_template);
+
+ # process() accepts an arrayref of templates, so we just pass the whole
+ # arrayref.
+ $context->{bz_in_hook} = 1; # See Bugzilla::Template::Context
+ return $context->process($cache->{"${lang}__$extension_template"});
+}
+
+sub _get_hooks {
+ my ($self, $extension_template) = @_;
+
+ my $template_sets = $self->_template_hook_include_path();
+ my @hooks;
+ foreach my $dir_set (@$template_sets) {
+ foreach my $template_dir (@$dir_set) {
+ my $file = "$template_dir/hook/$extension_template";
if (-e $file) {
- # tt is stubborn and won't take a template file not in its
- # include path, so we open a filehandle and give it to process()
- # so the hook gets invoked:
- open (my $fh, $file);
- push(@hooks, $fh);
+ my $template = $self->_context->template($file);
+ push(@hooks, $template);
+ # Don't run the hook for more than one language.
+ last;
}
}
}
- my $paths = $self->{_CONTEXT}->{LOAD_TEMPLATES}->[0]->paths;
-
- # we keep this too since you can still put hook templates in
- # template/en/custom/hook
- foreach my $path (@$paths) {
- my @files = glob("$path/hook/$template/$hook_name/*.tmpl");
+ return \@hooks;
+}
- # Have to remove the templates path (INCLUDE_PATH) from the
- # file path since the template processor auto-adds it back.
- @files = map($_ =~ /^$path\/(.*)$/ ? $1 : {}, @files);
-
- # Add found files to the list of hooks, but removing duplicates,
- # which can happen when there are identical hooks or duplicate
- # directories in the INCLUDE_PATH (the latter probably being a TT bug).
- foreach my $file (@files) {
- push(@hooks, $file) unless grep($file eq $_, @hooks);
- }
- }
-
- my $output;
- foreach my $hook (@hooks) {
- $output .= $self->{_CONTEXT}->process($hook);
- }
- return $output;
+sub _template_hook_include_path {
+ my $self = shift;
+ my $cache = Bugzilla->request_cache;
+ my $language = $self->_context->{bz_language} || '';
+ my $cache_key = "template_plugin_hook_include_path_$language";
+ $cache->{$cache_key} ||= template_include_path({
+ language => $language,
+ hook => 1,
+ });
+ return $cache->{$cache_key};
}
1;
@@ -165,8 +162,4 @@
L<Template::Plugin>
-L<http://www.bugzilla.org/docs/tip/html/customization.html>
-
-L<http://bugzilla.mozilla.org/show_bug.cgi?id=229658>
-
-L<http://bugzilla.mozilla.org/show_bug.cgi?id=298341>
+L<http://wiki.mozilla.org/Bugzilla:Writing_Extensions>
diff --git a/Websites/bugs.webkit.org/Bugzilla/Token.pm b/Websites/bugs.webkit.org/Bugzilla/Token.pm
index a8862bd..2bb68e7 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Token.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Token.pm
@@ -63,20 +63,21 @@
# But to prevent using this way to mailbomb an email address, make sure
# the last request is at least 10 minutes old before sending a new email.
- my $pending_requests =
- $dbh->selectrow_array('SELECT COUNT(*)
- FROM tokens
- WHERE tokentype = ?
- AND ' . $dbh->sql_istrcmp('eventdata', '?') . '
- AND issuedate > NOW() - ' . $dbh->sql_interval(10, 'MINUTE'),
- undef, ('account', $login_name));
+ my $pending_requests = $dbh->selectrow_array(
+ 'SELECT COUNT(*)
+ FROM tokens
+ WHERE tokentype = ?
+ AND ' . $dbh->sql_istrcmp('eventdata', '?') . '
+ AND issuedate > '
+ . $dbh->sql_date_math('NOW()', '-', 10, 'MINUTE'),
+ undef, ('account', $login_name));
ThrowUserError('too_soon_for_new_token', {'type' => 'account'}) if $pending_requests;
my ($token, $token_ts) = _create_token(undef, 'account', $login_name);
$vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
- $vars->{'token_ts'} = $token_ts;
+ $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
$vars->{'token'} = $token;
my $message;
@@ -91,8 +92,9 @@
}
sub IssueEmailChangeToken {
- my ($user, $old_email, $new_email) = @_;
+ my ($user, $new_email) = @_;
my $email_suffix = Bugzilla->params->{'emailsuffix'};
+ my $old_email = $user->login;
my ($token, $token_ts) = _create_token($user->id, 'emailold', $old_email . ":" . $new_email);
@@ -100,15 +102,12 @@
# Mail the user the token along with instructions for using it.
- my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+ my $template = Bugzilla->template_inner($user->setting('lang'));
my $vars = {};
$vars->{'oldemailaddress'} = $old_email . $email_suffix;
$vars->{'newemailaddress'} = $new_email . $email_suffix;
-
- $vars->{'max_token_age'} = MAX_TOKEN_AGE;
- $vars->{'token_ts'} = $token_ts;
-
+ $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
$vars->{'token'} = $token;
$vars->{'emailaddress'} = $old_email . $email_suffix;
@@ -125,7 +124,6 @@
$template->process("account/email/change-new.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
- Bugzilla->template_inner("");
MessageToMTA($message);
}
@@ -135,33 +133,33 @@
my $user = shift;
my $dbh = Bugzilla->dbh;
- my $too_soon =
- $dbh->selectrow_array('SELECT 1 FROM tokens
- WHERE userid = ?
- AND tokentype = ?
- AND issuedate > NOW() - ' .
- $dbh->sql_interval(10, 'MINUTE'),
- undef, ($user->id, 'password'));
+ my $too_soon = $dbh->selectrow_array(
+ 'SELECT 1 FROM tokens
+ WHERE userid = ? AND tokentype = ?
+ AND issuedate > '
+ . $dbh->sql_date_math('NOW()', '-', 10, 'MINUTE'),
+ undef, ($user->id, 'password'));
ThrowUserError('too_soon_for_new_token', {'type' => 'password'}) if $too_soon;
- my ($token, $token_ts) = _create_token($user->id, 'password', $::ENV{'REMOTE_ADDR'});
+ my ($token, $token_ts) = _create_token($user->id, 'password', remote_ip());
# Mail the user the token along with instructions for using it.
- my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+ my $template = Bugzilla->template_inner($user->setting('lang'));
my $vars = {};
$vars->{'token'} = $token;
$vars->{'emailaddress'} = $user->email;
- $vars->{'max_token_age'} = MAX_TOKEN_AGE;
- $vars->{'token_ts'} = $token_ts;
+ $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
+ # The user is not logged in (else he wouldn't request a new password).
+ # So we have to pass this information to the template.
+ $vars->{'timezone'} = $user->timezone;
my $message = "";
$template->process("account/password/forgotten-password.txt.tmpl",
$vars, \$message)
|| ThrowTemplateError($template->error());
- Bugzilla->template_inner("");
MessageToMTA($message);
}
@@ -178,9 +176,14 @@
$data ||= [];
$time ||= time();
+ # For the user ID, use the actual ID if the user is logged in.
+ # Otherwise, use the remote IP, in case this is for something
+ # such as creating an account or logging in.
+ my $user_id = Bugzilla->user->id || remote_ip();
+
# The concatenated string is of the form
- # token creation time + site-wide secret + user ID + data
- my @args = ($time, Bugzilla->localconfig->{'site_wide_secret'}, Bugzilla->user->id, @$data);
+ # token creation time + site-wide secret + user ID (either ID or remote IP) + data
+ my @args = ($time, Bugzilla->localconfig->{'site_wide_secret'}, $user_id, @$data);
my $token = join('*', @args);
# Wide characters cause md5_hex() to die.
@@ -248,7 +251,7 @@
$column ||= "token";
my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare("SELECT userid FROM $table WHERE $column = ?");
+ my $sth = $dbh->prepare("SELECT 1 FROM $table WHERE $column = ?");
while ($duplicate) {
++$tries;
@@ -284,21 +287,23 @@
my $user = new Bugzilla::User($userid);
$vars->{'emailaddress'} = $userid ? $user->email : $eventdata;
- $vars->{'remoteaddress'} = $::ENV{'REMOTE_ADDR'};
+ $vars->{'remoteaddress'} = remote_ip();
$vars->{'token'} = $token;
$vars->{'tokentype'} = $tokentype;
$vars->{'issuedate'} = $issuedate;
+ # The user is probably not logged in.
+ # So we have to pass this information to the template.
+ $vars->{'timezone'} = $user->timezone;
$vars->{'eventdata'} = $eventdata;
$vars->{'cancelaction'} = $cancelaction;
# Notify the user via email about the cancellation.
- my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+ my $template = Bugzilla->template_inner($user->setting('lang'));
my $message;
$template->process("account/cancel-token.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
- Bugzilla->template_inner("");
MessageToMTA($message);
# Delete the token from the database.
@@ -448,7 +453,7 @@
use Bugzilla::Token;
Bugzilla::Token::issue_new_user_account_token($login_name);
- Bugzilla::Token::IssueEmailChangeToken($user, $old_email, $new_email);
+ Bugzilla::Token::IssueEmailChangeToken($user, $new_email);
Bugzilla::Token::IssuePasswordToken($user);
Bugzilla::Token::DeletePasswordTokens($user_id, $reason);
Bugzilla::Token::Cancel($token, $cancelaction, $vars);
@@ -479,7 +484,7 @@
Returns: Nothing. It throws an error if the same user made the same
request in the last few minutes.
-=item C<sub IssueEmailChangeToken($user, $old_email, $new_email)>
+=item C<sub IssueEmailChangeToken($user, $new_email)>
Description: Sends two distinct tokens per email to the old and new email
addresses to confirm the email address change for the given
@@ -487,7 +492,6 @@
Params: $user - User object of the user requesting a new
email address.
- $old_email - The current (old) email address of the user.
$new_email - The new email address of the user.
Returns: Nothing.
diff --git a/Websites/bugs.webkit.org/Bugzilla/Update.pm b/Websites/bugs.webkit.org/Bugzilla/Update.pm
index d3f7805..c9942a4 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Update.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Update.pm
@@ -20,45 +20,33 @@
use Bugzilla::Constants;
-use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
-use constant LOCAL_FILE => "/bugzilla-update.xml"; # Relative to datadir.
use constant TIME_INTERVAL => 86400; # Default is one day, in seconds.
use constant TIMEOUT => 5; # Number of seconds before timeout.
# Look for new releases and notify logged in administrators about them.
sub get_notifications {
+ return if !Bugzilla->feature('updates');
return if (Bugzilla->params->{'upgrade_notification'} eq 'disabled');
- # If the XML::Twig module is missing, we won't be able to parse
- # the XML file. So there is no need to go further.
- eval("require XML::Twig");
- return if $@;
-
- my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
+ my $local_file = bz_locations()->{'datadir'} . '/' . LOCAL_FILE;
# Update the local XML file if this one doesn't exist or if
# the last modification time (stat[9]) is older than TIME_INTERVAL.
if (!-e $local_file || (time() - (stat($local_file))[9] > TIME_INTERVAL)) {
- # Are we sure we didn't try to refresh this file already
- # but we failed because we cannot modify its timestamp?
- my $can_alter = (-e $local_file) ? utime(undef, undef, $local_file) : 1;
- if ($can_alter) {
- unlink $local_file; # Make sure the old copy is away.
- my $error = _synchronize_data();
- # If an error is returned, leave now.
- return $error if $error;
- }
- else {
- return {'error' => 'no_update', 'xml_file' => $local_file};
- }
+ unlink $local_file; # Make sure the old copy is away.
+ return { 'error' => 'no_update' } if (-e $local_file);
+
+ my $error = _synchronize_data();
+ # If an error is returned, leave now.
+ return $error if $error;
}
# If we cannot access the local XML file, ignore it.
- return {'error' => 'no_access', 'xml_file' => $local_file} unless (-r $local_file);
+ return { 'error' => 'no_access' } unless (-r $local_file);
my $twig = XML::Twig->new();
$twig->safe_parsefile($local_file);
# If the XML file is invalid, return.
- return {'error' => 'corrupted', 'xml_file' => $local_file} if $@;
+ return { 'error' => 'corrupted' } if $@;
my $root = $twig->root;
my @releases;
@@ -128,10 +116,7 @@
}
sub _synchronize_data {
- eval("require LWP::UserAgent");
- return {'error' => 'missing_package', 'package' => 'LWP::UserAgent'} if $@;
-
- my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
+ my $local_file = bz_locations()->{'datadir'} . '/' . LOCAL_FILE;
my $ua = LWP::UserAgent->new();
$ua->timeout(TIMEOUT);
@@ -145,7 +130,7 @@
else {
$ua->env_proxy;
}
- $ua->mirror(REMOTE_FILE, $local_file);
+ my $response = eval { $ua->mirror(REMOTE_FILE, $local_file) };
# $ua->mirror() forces the modification time of the local XML file
# to match the modification time of the remote one.
@@ -156,11 +141,14 @@
# Try to alter its last modification time.
my $can_alter = utime(undef, undef, $local_file);
# This error should never happen.
- $can_alter || return {'error' => 'no_update', 'xml_file' => $local_file};
+ $can_alter || return { 'error' => 'no_update' };
+ }
+ elsif ($response && $response->is_error) {
+ # We have been unable to download the file.
+ return { 'error' => 'cannot_download', 'reason' => $response->status_line };
}
else {
- # We have been unable to download the file.
- return {'error' => 'cannot_download', 'xml_file' => $local_file};
+ return { 'error' => 'no_write', 'reason' => $@ };
}
# Everything went well.
diff --git a/Websites/bugs.webkit.org/Bugzilla/User.pm b/Websites/bugs.webkit.org/Bugzilla/User.pm
index 41284e5..391e416 100644
--- a/Websites/bugs.webkit.org/Bugzilla/User.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/User.pm
@@ -44,10 +44,19 @@
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Constants;
+use Bugzilla::Search::Recent;
use Bugzilla::User::Setting;
use Bugzilla::Product;
use Bugzilla::Classification;
use Bugzilla::Field;
+use Bugzilla::Group;
+
+use DateTime::TimeZone;
+use List::Util qw(max);
+use Scalar::Util qw(blessed);
+use Storable qw(dclone);
+use URI;
+use URI::QueryParam;
use base qw(Bugzilla::Object Exporter);
@Bugzilla::User::EXPORT = qw(is_available_username
@@ -73,6 +82,7 @@
'showmybugslink' => 0,
'disabledtext' => '',
'disable_mail' => 0,
+ 'is_enabled' => 1,
};
use constant DB_TABLE => 'profiles';
@@ -88,19 +98,21 @@
'profiles.mybugslink AS showmybugslink',
'profiles.disabledtext',
'profiles.disable_mail',
+ 'profiles.extern_id',
+ 'profiles.is_enabled',
);
use constant NAME_FIELD => 'login_name';
use constant ID_FIELD => 'userid';
use constant LIST_ORDER => NAME_FIELD;
-use constant REQUIRED_CREATE_FIELDS => qw(login_name cryptpassword);
-
use constant VALIDATORS => {
cryptpassword => \&_check_password,
disable_mail => \&_check_disable_mail,
disabledtext => \&_check_disabledtext,
login_name => \&check_login_name_for_creation,
realname => \&_check_realname,
+ extern_id => \&_check_extern_id,
+ is_enabled => \&_check_is_enabled,
};
sub UPDATE_COLUMNS {
@@ -110,11 +122,19 @@
disabledtext
login_name
realname
+ extern_id
+ is_enabled
);
push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
return @cols;
};
+use constant VALIDATOR_DEPENDENCIES => {
+ is_enabled => ['disabledtext'],
+};
+
+use constant EXTRA_REQUIRED_FIELDS => qw(is_enabled);
+
################################################################################
# Functions
################################################################################
@@ -128,9 +148,27 @@
bless ($user, $class);
return $user unless $param;
+ if (ref($param) eq 'HASH') {
+ if (defined $param->{extern_id}) {
+ $param = { condition => 'extern_id = ?' , values => [$param->{extern_id}] };
+ $_[0] = $param;
+ }
+ }
return $class->SUPER::new(@_);
}
+sub super_user {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my ($param) = @_;
+
+ my $user = dclone(DEFAULT_USER);
+ $user->{groups} = [Bugzilla::Group->get_all];
+ $user->{bless_groups} = [Bugzilla::Group->get_all];
+ bless $user, $class;
+ return $user;
+}
+
sub update {
my $self = shift;
my $changes = $self->SUPER::update(@_);
@@ -150,7 +188,7 @@
# XXX Can update profiles_activity here as soon as it understands
# field names like login_name.
-
+
return $changes;
}
@@ -161,6 +199,22 @@
sub _check_disable_mail { return $_[1] ? 1 : 0; }
sub _check_disabledtext { return trim($_[1]) || ''; }
+# Check whether the extern_id is unique.
+sub _check_extern_id {
+ my ($invocant, $extern_id) = @_;
+ $extern_id = trim($extern_id);
+ return undef unless defined($extern_id) && $extern_id ne "";
+ if (!ref($invocant) || $invocant->extern_id ne $extern_id) {
+ my $existing_login = $invocant->new({ extern_id => $extern_id });
+ if ($existing_login) {
+ ThrowUserError( 'extern_id_exists',
+ { extern_id => $extern_id,
+ existing_login_name => $existing_login->login });
+ }
+ }
+ return $extern_id;
+}
+
# This is public since createaccount.cgi needs to use it before issuing
# a token for account creation.
sub check_login_name_for_creation {
@@ -194,12 +248,22 @@
sub _check_realname { return trim($_[1]) || ''; }
+sub _check_is_enabled {
+ my ($invocant, $is_enabled, undef, $params) = @_;
+ # is_enabled is set automatically on creation depending on whether
+ # disabledtext is empty (enabled) or not empty (disabled).
+ # When updating the user, is_enabled is set by calling set_disabledtext().
+ # Any value passed into this validator is ignored.
+ my $disabledtext = ref($invocant) ? $invocant->disabledtext : $params->{disabledtext};
+ return $disabledtext ? 0 : 1;
+}
+
################################################################################
# Mutators
################################################################################
-sub set_disabledtext { $_[0]->set('disabledtext', $_[1]); }
sub set_disable_mail { $_[0]->set('disable_mail', $_[1]); }
+sub set_extern_id { $_[0]->set('extern_id', $_[1]); }
sub set_login {
my ($self, $login) = @_;
@@ -216,6 +280,10 @@
sub set_password { $_[0]->set('cryptpassword', $_[1]); }
+sub set_disabledtext {
+ $_[0]->set('disabledtext', $_[1]);
+ $_[0]->set('is_enabled', $_[1] ? 0 : 1);
+}
################################################################################
# Methods
@@ -224,12 +292,22 @@
# Accessors for user attributes
sub name { $_[0]->{realname}; }
sub login { $_[0]->{login_name}; }
+sub extern_id { $_[0]->{extern_id}; }
sub email { $_[0]->login . Bugzilla->params->{'emailsuffix'}; }
sub disabledtext { $_[0]->{'disabledtext'}; }
-sub is_disabled { $_[0]->disabledtext ? 1 : 0; }
+sub is_enabled { $_[0]->{'is_enabled'} ? 1 : 0; }
sub showmybugslink { $_[0]->{showmybugslink}; }
sub email_disabled { $_[0]->{disable_mail}; }
sub email_enabled { !($_[0]->{disable_mail}); }
+sub cryptpassword {
+ my $self = shift;
+ # We don't store it because we never want it in the object (we
+ # don't want to accidentally dump even the hash somewhere).
+ my ($pw) = Bugzilla->dbh->selectrow_array(
+ 'SELECT cryptpassword FROM profiles WHERE userid = ?',
+ undef, $self->id);
+ return $pw;
+}
sub set_authorizer {
my ($self, $authorizer) = @_;
@@ -308,7 +386,7 @@
ON ngm.namedquery_id = lif.namedquery_id
WHERE lif.user_id = ?
AND lif.namedquery_id NOT IN ($query_id_string)
- AND ngm.group_id IN (" . $self->groups_as_string . ")",
+ AND " . $self->groups_in_sql,
undef, $self->id);
require Bugzilla::Search::Saved;
$self->{queries_subscribed} =
@@ -327,7 +405,7 @@
my $avail_query_ids = Bugzilla->dbh->selectcol_arrayref(
'SELECT namedquery_id FROM namedquery_group_map
- WHERE group_id IN (' . $self->groups_as_string . ")
+ WHERE ' . $self->groups_in_sql . "
AND namedquery_id NOT IN ($query_id_string)");
require Bugzilla::Search::Saved;
$self->{queries_available} =
@@ -335,6 +413,168 @@
return $self->{queries_available};
}
+sub tags {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{tags}) {
+ # We must use LEFT JOIN instead of INNER JOIN as we may be
+ # in the process of inserting a new tag to some bugs,
+ # in which case there are no bugs with this tag yet.
+ $self->{tags} = $dbh->selectall_hashref(
+ 'SELECT name, id, COUNT(bug_id) AS bug_count
+ FROM tag
+ LEFT JOIN bug_tag ON bug_tag.tag_id = tag.id
+ WHERE user_id = ? ' . $dbh->sql_group_by('id', 'name'),
+ 'name', undef, $self->id);
+ }
+ return $self->{tags};
+}
+
+##########################
+# Saved Recent Bug Lists #
+##########################
+
+sub recent_searches {
+ my $self = shift;
+ $self->{recent_searches} ||=
+ Bugzilla::Search::Recent->match({ user_id => $self->id });
+ return $self->{recent_searches};
+}
+
+sub recent_search_containing {
+ my ($self, $bug_id) = @_;
+ my $searches = $self->recent_searches;
+
+ foreach my $search (@$searches) {
+ return $search if grep($_ == $bug_id, @{ $search->bug_list });
+ }
+
+ return undef;
+}
+
+sub recent_search_for {
+ my ($self, $bug) = @_;
+ my $params = Bugzilla->input_params;
+ my $cgi = Bugzilla->cgi;
+
+ if ($self->id) {
+ # First see if there's a list_id parameter in the query string.
+ my $list_id = $params->{list_id};
+ if (!$list_id) {
+ # If not, check for "list_id" in the query string of the referer.
+ my $referer = $cgi->referer;
+ if ($referer) {
+ my $uri = URI->new($referer);
+ if ($uri->path =~ /buglist\.cgi$/) {
+ $list_id = $uri->query_param('list_id')
+ || $uri->query_param('regetlastlist');
+ }
+ }
+ }
+
+ if ($list_id && $list_id ne 'cookie') {
+ # If we got a bad list_id (either some other user's or an expired
+ # one) don't crash, just don't return that list.
+ my $search = Bugzilla::Search::Recent->check_quietly(
+ { id => $list_id });
+ return $search if $search;
+ }
+
+ # If there's no list_id, see if the current bug's id is contained
+ # in any of the user's saved lists.
+ my $search = $self->recent_search_containing($bug->id);
+ return $search if $search;
+ }
+
+ # Finally (or always, if we're logged out), if there's a BUGLIST cookie
+ # and the selected bug is in the list, then return the cookie as a fake
+ # Search::Recent object.
+ if (my $list = $cgi->cookie('BUGLIST')) {
+ # Also split on colons, which was used as a separator in old cookies.
+ my @bug_ids = split(/[:-]/, $list);
+ if (grep { $_ == $bug->id } @bug_ids) {
+ my $search = Bugzilla::Search::Recent->new_from_cookie(\@bug_ids);
+ return $search;
+ }
+ }
+
+ return undef;
+}
+
+sub save_last_search {
+ my ($self, $params) = @_;
+ my ($bug_ids, $order, $vars, $list_id) =
+ @$params{qw(bugs order vars list_id)};
+
+ my $cgi = Bugzilla->cgi;
+ if ($order) {
+ $cgi->send_cookie(-name => 'LASTORDER',
+ -value => $order,
+ -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
+ }
+
+ return if !@$bug_ids;
+
+ my $search;
+ if ($self->id) {
+ on_main_db {
+ if ($list_id) {
+ $search = Bugzilla::Search::Recent->check_quietly({ id => $list_id });
+ }
+
+ if ($search) {
+ if (join(',', @{$search->bug_list}) ne join(',', @$bug_ids)) {
+ $search->set_bug_list($bug_ids);
+ }
+ if (!$search->list_order || $order ne $search->list_order) {
+ $search->set_list_order($order);
+ }
+ $search->update();
+ }
+ else {
+ # If we already have an existing search with a totally
+ # identical bug list, then don't create a new one. This
+ # prevents people from writing over their whole
+ # recent-search list by just refreshing a saved search
+ # (which doesn't have list_id in the header) over and over.
+ my $list_string = join(',', @$bug_ids);
+ my $existing_search = Bugzilla::Search::Recent->match({
+ user_id => $self->id, bug_list => $list_string });
+
+ if (!scalar(@$existing_search)) {
+ $search = Bugzilla::Search::Recent->create({
+ user_id => $self->id,
+ bug_list => $bug_ids,
+ list_order => $order });
+ }
+ else {
+ $search = $existing_search->[0];
+ }
+ }
+ };
+ delete $self->{recent_searches};
+ }
+ # Logged-out users use a cookie to store a single last search. We don't
+ # override that cookie with the logged-in user's latest search, because
+ # if they did one search while logged out and another while logged in,
+ # they may still want to navigate through the search they made while
+ # logged out.
+ else {
+ my $bug_list = join('-', @$bug_ids);
+ if (length($bug_list) < 4000) {
+ $cgi->send_cookie(-name => 'BUGLIST',
+ -value => $bug_list,
+ -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
+ }
+ else {
+ $cgi->remove_cookie('BUGLIST');
+ $vars->{'toolong'} = 1;
+ }
+ }
+ return $search;
+}
+
sub settings {
my ($self) = @_;
@@ -352,6 +592,27 @@
return $self->{'settings'};
}
+sub setting {
+ my ($self, $name) = @_;
+ return $self->settings->{$name}->{'value'};
+}
+
+sub timezone {
+ my $self = shift;
+
+ if (!defined $self->{timezone}) {
+ my $tz = $self->setting('timezone');
+ if ($tz eq 'local') {
+ # The user wants the local timezone of the server.
+ $self->{timezone} = Bugzilla->local_timezone;
+ }
+ else {
+ $self->{timezone} = DateTime::TimeZone->new(name => $tz);
+ }
+ }
+ return $self->{timezone};
+}
+
sub flush_queries_cache {
my $self = shift;
@@ -364,75 +625,80 @@
my $self = shift;
return $self->{groups} if defined $self->{groups};
- return {} unless $self->id;
+ return [] unless $self->id;
my $dbh = Bugzilla->dbh;
- my $groups = $dbh->selectcol_arrayref(q{SELECT DISTINCT groups.name, group_id
- FROM groups, user_group_map
- WHERE groups.id=user_group_map.group_id
- AND user_id=?
- AND isbless=0},
- { Columns=>[1,2] },
- $self->id);
+ my $groups_to_check = $dbh->selectcol_arrayref(
+ q{SELECT DISTINCT group_id
+ FROM user_group_map
+ WHERE user_id = ? AND isbless = 0}, undef, $self->id);
- # The above gives us an arrayref [name, id, name, id, ...]
- # Convert that into a hashref
- my %groups = @$groups;
- my @groupidstocheck = values(%groups);
- my %groupidschecked = ();
my $rows = $dbh->selectall_arrayref(
- "SELECT DISTINCT groups.name, groups.id, member_id
- FROM group_group_map
- INNER JOIN groups
- ON groups.id = grantor_id
- WHERE grant_type = " . GROUP_MEMBERSHIP);
- my %group_names = ();
- my %group_membership = ();
+ "SELECT DISTINCT grantor_id, member_id
+ FROM group_group_map
+ WHERE grant_type = " . GROUP_MEMBERSHIP);
+
+ my %group_membership;
foreach my $row (@$rows) {
- my ($member_name, $grantor_id, $member_id) = @$row;
- # Just save the group names
- $group_names{$grantor_id} = $member_name;
-
- # And group membership
- push (@{$group_membership{$member_id}}, $grantor_id);
+ my ($grantor_id, $member_id) = @$row;
+ push (@{ $group_membership{$member_id} }, $grantor_id);
}
# Let's walk the groups hierarchy tree (using FIFO)
# On the first iteration it's pre-filled with direct groups
# membership. Later on, each group can add its own members into the
# FIFO. Circular dependencies are eliminated by checking
- # $groupidschecked{$member_id} hash values.
+ # $checked_groups{$member_id} hash values.
# As a result, %groups will have all the groups we are the member of.
- while ($#groupidstocheck >= 0) {
+ my %checked_groups;
+ my %groups;
+ while (scalar(@$groups_to_check) > 0) {
# Pop the head group from FIFO
- my $member_id = shift @groupidstocheck;
+ my $member_id = shift @$groups_to_check;
# Skip the group if we have already checked it
- if (!$groupidschecked{$member_id}) {
+ if (!$checked_groups{$member_id}) {
# Mark group as checked
- $groupidschecked{$member_id} = 1;
+ $checked_groups{$member_id} = 1;
# Add all its members to the FIFO check list
# %group_membership contains arrays of group members
# for all groups. Accessible by group number.
- foreach my $newgroupid (@{$group_membership{$member_id}}) {
- push @groupidstocheck, $newgroupid
- if (!$groupidschecked{$newgroupid});
- }
- # Note on if clause: we could have group in %groups from 1st
- # query and do not have it in second one
- $groups{$group_names{$member_id}} = $member_id
- if $group_names{$member_id} && $member_id;
+ my $members = $group_membership{$member_id};
+ my @new_to_check = grep(!$checked_groups{$_}, @$members);
+ push(@$groups_to_check, @new_to_check);
+
+ $groups{$member_id} = 1;
}
}
- $self->{groups} = \%groups;
+
+ $self->{groups} = Bugzilla::Group->new_from_list([keys %groups]);
return $self->{groups};
}
+# It turns out that calling ->id on objects a few hundred thousand
+# times is pretty slow. (It showed up as a significant time contributor
+# when profiling xt/search.t.) So we cache the group ids separately from
+# groups for functions that need the group ids.
+sub _group_ids {
+ my ($self) = @_;
+ $self->{group_ids} ||= [map { $_->id } @{ $self->groups }];
+ return $self->{group_ids};
+}
+
sub groups_as_string {
my $self = shift;
- return (join(',',values(%{$self->groups})) || '-1');
+ my $ids = $self->_group_ids;
+ return scalar(@$ids) ? join(',', @$ids) : '-1';
+}
+
+sub groups_in_sql {
+ my ($self, $field) = @_;
+ $field ||= 'group_id';
+ my $ids = $self->_group_ids;
+ $ids = [-1] if !scalar @$ids;
+ return Bugzilla->dbh->sql_in($field, $ids);
}
sub bless_groups {
@@ -441,54 +707,46 @@
return $self->{'bless_groups'} if defined $self->{'bless_groups'};
return [] unless $self->id;
- my $dbh = Bugzilla->dbh;
- my $query;
- my $connector;
- my @bindValues;
-
if ($self->in_group('editusers')) {
# Users having editusers permissions may bless all groups.
- $query = 'SELECT DISTINCT id, name, description FROM groups';
- $connector = 'WHERE';
+ $self->{'bless_groups'} = [Bugzilla::Group->get_all];
+ return $self->{'bless_groups'};
}
- else {
- # Get all groups for the user where:
- # + They have direct bless privileges
- # + They are a member of a group that inherits bless privs.
- $query = q{
- SELECT DISTINCT groups.id, groups.name, groups.description
- FROM groups, user_group_map, group_group_map AS ggm
- WHERE user_group_map.user_id = ?
- AND ((user_group_map.isbless = 1
- AND groups.id=user_group_map.group_id)
- OR (groups.id = ggm.grantor_id
- AND ggm.grant_type = ?
- AND ggm.member_id IN(} .
- $self->groups_as_string .
- q{)))};
- $connector = 'AND';
- @bindValues = ($self->id, GROUP_BLESS);
- }
+
+ my $dbh = Bugzilla->dbh;
+
+ # Get all groups for the user where:
+ # + They have direct bless privileges
+ # + They are a member of a group that inherits bless privs.
+ my @group_ids = map {$_->id} @{ $self->groups };
+ @group_ids = (-1) if !@group_ids;
+ my $query =
+ 'SELECT DISTINCT groups.id
+ FROM groups, user_group_map, group_group_map AS ggm
+ WHERE user_group_map.user_id = ?
+ AND ( (user_group_map.isbless = 1
+ AND groups.id=user_group_map.group_id)
+ OR (groups.id = ggm.grantor_id
+ AND ggm.grant_type = ' . GROUP_BLESS . '
+ AND ' . $dbh->sql_in('ggm.member_id', \@group_ids)
+ . ') )';
# If visibilitygroups are used, restrict the set of groups.
- if (!$self->in_group('editusers')
- && Bugzilla->params->{'usevisibilitygroups'})
- {
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ return [] if !$self->visible_groups_as_string;
# Users need to see a group in order to bless it.
- my $visibleGroups = join(', ', @{$self->visible_groups_direct()})
- || return $self->{'bless_groups'} = [];
- $query .= " $connector id in ($visibleGroups)";
+ $query .= " AND "
+ . $dbh->sql_in('groups.id', $self->visible_groups_inherited);
}
- $query .= ' ORDER BY name';
-
- return $self->{'bless_groups'} =
- $dbh->selectall_arrayref($query, {'Slice' => {}}, @bindValues);
+ my $ids = $dbh->selectcol_arrayref($query, undef, $self->id);
+ return $self->{'bless_groups'} = Bugzilla::Group->new_from_list($ids);
}
sub in_group {
my ($self, $group, $product_id) = @_;
- if (exists $self->groups->{$group}) {
+ $group = $group->name if blessed $group;
+ if (scalar grep($_->name eq $group, @{ $self->groups })) {
return 1;
}
elsif ($product_id && detaint_natural($product_id)) {
@@ -503,7 +761,7 @@
FROM group_control_map
WHERE product_id = ?
AND $group != 0
- AND group_id IN (" . $self->groups_as_string . ") " .
+ AND " . $self->groups_in_sql . ' ' .
$dbh->sql_limit(1),
undef, $product_id);
@@ -517,8 +775,7 @@
sub in_group_id {
my ($self, $id) = @_;
- my %j = reverse(%{$self->groups});
- return exists $j{$id} ? 1 : 0;
+ return grep($_->id == $id, @{ $self->groups }) ? 1 : 0;
}
sub get_products_by_permission {
@@ -530,14 +787,15 @@
"SELECT DISTINCT product_id
FROM group_control_map
WHERE $group != 0
- AND group_id IN(" . $self->groups_as_string . ")");
+ AND " . $self->groups_in_sql);
# No need to go further if the user has no "special" privs.
return [] unless scalar(@$product_ids);
+ my %product_map = map { $_ => 1 } @$product_ids;
# We will restrict the list to products the user can see.
my $selectable_products = $self->get_selectable_products;
- my @products = grep {lsearch($product_ids, $_->id) > -1} @$selectable_products;
+ my @products = grep { $product_map{$_->id} } @$selectable_products;
return \@products;
}
@@ -580,55 +838,80 @@
}
sub can_see_bug {
- my ($self, $bugid) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($self, $bug_id) = @_;
+ return @{ $self->visible_bugs([$bug_id]) } ? 1 : 0;
+}
- #if WEBKIT_CHANGES
- # FIXME: disable memoization since it results in stale handle
- my $sth;
- #my $sth = $self->{sthCanSeeBug};
- #endif WEBKIT_CHANGES
+sub visible_bugs {
+ my ($self, $bugs) = @_;
+ # Allow users to pass in Bug objects and bug ids both.
+ my @bug_ids = map { blessed $_ ? $_->id : $_ } @$bugs;
- my $userid = $self->id;
- # Get fields from bug, presence of user on cclist, and determine if
- # the user is missing any groups required by the bug. The prepared query
- # is cached because this may be called for every row in buglists or
- # every bug in a dependency list.
- unless ($sth) {
- $sth = $dbh->prepare("SELECT 1, reporter, assigned_to, qa_contact,
- reporter_accessible, cclist_accessible,
- COUNT(cc.who), COUNT(bug_group_map.bug_id)
- FROM bugs
- LEFT JOIN cc
- ON cc.bug_id = bugs.bug_id
- AND cc.who = $userid
- LEFT JOIN bug_group_map
- ON bugs.bug_id = bug_group_map.bug_id
- AND bug_group_map.group_ID NOT IN(" .
- $self->groups_as_string .
- ") WHERE bugs.bug_id = ?
- AND creation_ts IS NOT NULL " .
- $dbh->sql_group_by('bugs.bug_id', 'reporter, ' .
- 'assigned_to, qa_contact, reporter_accessible, ' .
- 'cclist_accessible'));
+ # We only check the visibility of bugs that we haven't
+ # checked yet.
+ # Bugzilla::Bug->update automatically removes updated bugs
+ # from the cache to force them to be checked again.
+ my $visible_cache = $self->{_visible_bugs_cache} ||= {};
+ my @check_ids = grep(!exists $visible_cache->{$_}, @bug_ids);
+
+ if (@check_ids) {
+ my $dbh = Bugzilla->dbh;
+ my $user_id = $self->id;
+ my $sth;
+ # Speed up the can_see_bug case.
+ if (scalar(@check_ids) == 1) {
+ $sth = $self->{_sth_one_visible_bug};
+ }
+ $sth ||= $dbh->prepare(
+ # This checks for groups that the bug is in that the user
+ # *isn't* in. Then, in the Perl code below, we check if
+ # the user can otherwise access the bug (for example, by being
+ # the assignee or QA Contact).
+ #
+ # The DISTINCT exists because the bug could be in *several*
+ # groups that the user isn't in, but they will all return the
+ # same result for bug_group_map.bug_id (so DISTINCT filters
+ # out duplicate rows).
+ "SELECT DISTINCT bugs.bug_id, reporter, assigned_to, qa_contact,
+ reporter_accessible, cclist_accessible, cc.who,
+ bug_group_map.bug_id
+ FROM bugs
+ LEFT JOIN cc
+ ON cc.bug_id = bugs.bug_id
+ AND cc.who = $user_id
+ LEFT JOIN bug_group_map
+ ON bugs.bug_id = bug_group_map.bug_id
+ AND bug_group_map.group_id NOT IN ("
+ . $self->groups_as_string . ')
+ WHERE bugs.bug_id IN (' . join(',', ('?') x @check_ids) . ')
+ AND creation_ts IS NOT NULL ');
+ if (scalar(@check_ids) == 1) {
+ $self->{_sth_one_visible_bug} = $sth;
+ }
+
+ $sth->execute(@check_ids);
+ my $use_qa_contact = Bugzilla->params->{'useqacontact'};
+ while (my $row = $sth->fetchrow_arrayref) {
+ my ($bug_id, $reporter, $owner, $qacontact, $reporter_access,
+ $cclist_access, $isoncclist, $missinggroup) = @$row;
+ $visible_cache->{$bug_id} ||=
+ ((($reporter == $user_id) && $reporter_access)
+ || ($use_qa_contact
+ && $qacontact && ($qacontact == $user_id))
+ || ($owner == $user_id)
+ || ($isoncclist && $cclist_access)
+ || !$missinggroup) ? 1 : 0;
+ }
}
- $sth->execute($bugid);
- my ($ready, $reporter, $owner, $qacontact, $reporter_access, $cclist_access,
- $isoncclist, $missinggroup) = $sth->fetchrow_array();
- $sth->finish;
- #if WEBKIT_CHANGES
- # FIXME: disable memoization since it results in stale handle
- #$self->{sthCanSeeBug} = $sth;
- #endif WEBKIT_CHANGES
+ return [grep { $visible_cache->{blessed $_ ? $_->id : $_} } @$bugs];
+}
- return ($ready
- && ((($reporter == $userid) && $reporter_access)
- || (Bugzilla->params->{'useqacontact'}
- && $qacontact && ($qacontact == $userid))
- || ($owner == $userid)
- || ($isoncclist && $cclist_access)
- || (!$missinggroup)));
+sub clear_product_cache {
+ my $self = shift;
+ delete $self->{enterable_products};
+ delete $self->{selectable_products};
+ delete $self->{selectable_classifications};
}
sub can_see_product {
@@ -646,13 +929,9 @@
my $query = "SELECT id " .
" FROM products " .
"LEFT JOIN group_control_map " .
- " ON group_control_map.product_id = products.id ";
- if (Bugzilla->params->{'useentrygroupdefault'}) {
- $query .= " AND group_control_map.entry != 0 ";
- } else {
- $query .= " AND group_control_map.membercontrol = " . CONTROLMAPMANDATORY;
- }
- $query .= " AND group_id NOT IN(" . $self->groups_as_string . ") " .
+ "ON group_control_map.product_id = products.id " .
+ " AND group_control_map.membercontrol = " . CONTROLMAPMANDATORY .
+ " AND group_id NOT IN(" . $self->groups_as_string . ") " .
" WHERE group_id IS NULL " .
"ORDER BY name";
@@ -671,58 +950,69 @@
sub get_selectable_classifications {
my ($self) = @_;
- if (defined $self->{selectable_classifications}) {
- return $self->{selectable_classifications};
- }
+ if (!defined $self->{selectable_classifications}) {
+ my $products = $self->get_selectable_products;
+ my %class_ids = map { $_->classification_id => 1 } @$products;
- my $products = $self->get_selectable_products;
-
- my $class;
- foreach my $product (@$products) {
- $class->{$product->classification_id} ||=
- new Bugzilla::Classification($product->classification_id);
+ $self->{selectable_classifications} = Bugzilla::Classification->new_from_list([keys %class_ids]);
}
- my @sorted_class = sort {$a->sortkey <=> $b->sortkey
- || lc($a->name) cmp lc($b->name)} (values %$class);
- $self->{selectable_classifications} = \@sorted_class;
return $self->{selectable_classifications};
}
sub can_enter_product {
- my ($self, $product_name, $warn) = @_;
+ my ($self, $input, $warn) = @_;
my $dbh = Bugzilla->dbh;
+ $warn ||= 0;
- if (!defined($product_name)) {
+ $input = trim($input) if !ref $input;
+ if (!defined $input or $input eq '') {
+ return unless $warn == THROW_ERROR;
+ ThrowUserError('object_not_specified',
+ { class => 'Bugzilla::Product' });
+ }
+
+ if (!scalar @{ $self->get_enterable_products }) {
return unless $warn == THROW_ERROR;
ThrowUserError('no_products');
}
- my $product = new Bugzilla::Product({name => $product_name});
+ my $product = blessed($input) ? $input
+ : new Bugzilla::Product({ name => $input });
my $can_enter =
- $product && grep($_->name eq $product->name, @{$self->get_enterable_products});
+ $product && grep($_->name eq $product->name,
+ @{ $self->get_enterable_products });
- return 1 if $can_enter;
+ return $product if $can_enter;
return 0 unless $warn == THROW_ERROR;
# Check why access was denied. These checks are slow,
# but that's fine, because they only happen if we fail.
+ # We don't just use $product->name for error messages, because if it
+ # changes case from $input, then that's a clue that the product does
+ # exist but is hidden.
+ my $name = blessed($input) ? $input->name : $input;
+
# The product could not exist or you could be denied...
if (!$product || !$product->user_has_access($self)) {
- ThrowUserError('entry_access_denied', {product => $product_name});
+ ThrowUserError('entry_access_denied', { product => $name });
}
# It could be closed for bug entry...
- elsif ($product->disallow_new) {
- ThrowUserError('product_disabled', {product => $product});
+ elsif (!$product->is_active) {
+ ThrowUserError('product_disabled', { product => $product });
}
# It could have no components...
- elsif (!@{$product->components}) {
- ThrowUserError('missing_component', {product => $product});
+ elsif (!@{$product->components}
+ || !grep { $_->is_active } @{$product->components})
+ {
+ ThrowUserError('missing_component', { product => $product });
}
# It could have no versions...
- elsif (!@{$product->versions}) {
- ThrowUserError ('missing_version', {product => $product});
+ elsif (!@{$product->versions}
+ || !grep { $_->is_active } @{$product->versions})
+ {
+ ThrowUserError ('missing_version', { product => $product });
}
die "can_enter_product reached an unreachable location.";
@@ -737,31 +1027,40 @@
}
# All products which the user has "Entry" access to.
- my @enterable_ids =@{$dbh->selectcol_arrayref(
+ my $enterable_ids = $dbh->selectcol_arrayref(
'SELECT products.id FROM products
LEFT JOIN group_control_map
ON group_control_map.product_id = products.id
AND group_control_map.entry != 0
AND group_id NOT IN (' . $self->groups_as_string . ')
WHERE group_id IS NULL
- AND products.disallownew = 0') || []};
+ AND products.isactive = 1');
- if (@enterable_ids) {
+ if (scalar @$enterable_ids) {
# And all of these products must have at least one component
# and one version.
- @enterable_ids = @{$dbh->selectcol_arrayref(
- 'SELECT DISTINCT products.id FROM products
- INNER JOIN components ON components.product_id = products.id
- INNER JOIN versions ON versions.product_id = products.id
- WHERE products.id IN (' . (join(',', @enterable_ids)) .
- ')') || []};
+ $enterable_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT products.id FROM products
+ WHERE ' . $dbh->sql_in('products.id', $enterable_ids) .
+ ' AND products.id IN (SELECT DISTINCT components.product_id
+ FROM components
+ WHERE components.isactive = 1)
+ AND products.id IN (SELECT DISTINCT versions.product_id
+ FROM versions
+ WHERE versions.isactive = 1)');
}
$self->{enterable_products} =
- Bugzilla::Product->new_from_list(\@enterable_ids);
+ Bugzilla::Product->new_from_list($enterable_ids);
return $self->{enterable_products};
}
+sub can_access_product {
+ my ($self, $product) = @_;
+ my $product_name = blessed($product) ? $product->name : $product;
+ return scalar(grep {$_->name eq $product_name} @{$self->get_accessible_products});
+}
+
sub get_accessible_products {
my $self = shift;
@@ -777,7 +1076,7 @@
my ($self, $product_name) = @_;
# First make sure the product name is valid.
- my $product = Bugzilla::Product::check_product($product_name);
+ my $product = Bugzilla::Product->check($product_name);
($self->in_group('editcomponents', $product->id)
&& $self->can_see_product($product->name))
@@ -787,6 +1086,49 @@
return $product;
}
+sub check_can_admin_flagtype {
+ my ($self, $flagtype_id) = @_;
+
+ my $flagtype = Bugzilla::FlagType->check({ id => $flagtype_id });
+ my $can_fully_edit = 1;
+
+ if (!$self->in_group('editcomponents')) {
+ my $products = $self->get_products_by_permission('editcomponents');
+ # You need editcomponents privs for at least one product to have
+ # a chance to edit the flagtype.
+ scalar(@$products)
+ || ThrowUserError('auth_failure', {group => 'editcomponents',
+ action => 'edit',
+ object => 'flagtypes'});
+ my $can_admin = 0;
+ my $i = $flagtype->inclusions_as_hash;
+ my $e = $flagtype->exclusions_as_hash;
+
+ # If there is at least one product for which the user doesn't have
+ # editcomponents privs, then don't allow him to do everything with
+ # this flagtype, independently of whether this product is in the
+ # exclusion list or not.
+ my %product_ids;
+ map { $product_ids{$_->id} = 1 } @$products;
+ $can_fully_edit = 0 if grep { !$product_ids{$_} } keys %$i;
+
+ unless ($e->{0}->{0}) {
+ foreach my $product (@$products) {
+ my $id = $product->id;
+ next if $e->{$id}->{0};
+ # If we are here, the product has not been explicitly excluded.
+ # Check whether it's explicitly included, or at least one of
+ # its components.
+ $can_admin = ($i->{0}->{0} || $i->{$id}->{0}
+ || scalar(grep { !$e->{$id}->{$_} } keys %{$i->{$id}}));
+ last if $can_admin;
+ }
+ }
+ $can_admin || ThrowUserError('flag_type_not_editable', { flagtype => $flagtype });
+ }
+ return wantarray ? ($flagtype, $can_fully_edit) : $flagtype;
+}
+
sub can_request_flag {
my ($self, $flag_type) = @_;
@@ -827,7 +1169,7 @@
return $self->{visible_groups_inherited} if defined $self->{visible_groups_inherited};
return [] unless $self->id;
my @visgroups = @{$self->visible_groups_direct};
- @visgroups = @{$self->flatten_group_membership(@visgroups)};
+ @visgroups = @{Bugzilla::Group->flatten_group_membership(@visgroups)};
$self->{visible_groups_inherited} = \@visgroups;
return $self->{visible_groups_inherited};
}
@@ -844,10 +1186,9 @@
my $sth;
if (Bugzilla->params->{'usevisibilitygroups'}) {
- my $glist = join(',',(-1,values(%{$self->groups})));
$sth = $dbh->prepare("SELECT DISTINCT grantor_id
FROM group_group_map
- WHERE member_id IN($glist)
+ WHERE " . $self->groups_in_sql('member_id') . "
AND grant_type=" . GROUP_VISIBLE);
}
else {
@@ -889,7 +1230,7 @@
}
}
else {
- @queryshare_groups = values(%{$self->groups});
+ @queryshare_groups = @{ $self->_group_ids };
}
}
@@ -945,11 +1286,14 @@
return $self->{'product_resp'} if defined $self->{'product_resp'};
return [] unless $self->id;
- my $list = $dbh->selectall_arrayref('SELECT product_id, id
+ my $list = $dbh->selectall_arrayref('SELECT components.product_id, components.id
FROM components
- WHERE initialowner = ?
- OR initialqacontact = ?',
- {Slice => {}}, ($self->id, $self->id));
+ LEFT JOIN component_cc
+ ON components.id = component_cc.component_id
+ WHERE components.initialowner = ?
+ OR components.initialqacontact = ?
+ OR component_cc.user_id = ?',
+ {Slice => {}}, ($self->id, $self->id, $self->id));
unless ($list) {
$self->{'product_resp'} = [];
@@ -981,36 +1325,12 @@
if (!scalar(@_)) {
# If we're called without an argument, just return
# whether or not we can bless at all.
- return scalar(@{$self->bless_groups}) ? 1 : 0;
+ return scalar(@{ $self->bless_groups }) ? 1 : 0;
}
# Otherwise, we're checking a specific group
my $group_id = shift;
- return (grep {$$_{'id'} eq $group_id} (@{$self->bless_groups})) ? 1 : 0;
-}
-
-sub flatten_group_membership {
- my ($self, @groups) = @_;
-
- my $dbh = Bugzilla->dbh;
- my $sth;
- my @groupidstocheck = @groups;
- my %groupidschecked = ();
- $sth = $dbh->prepare("SELECT member_id FROM group_group_map
- WHERE grantor_id = ?
- AND grant_type = " . GROUP_MEMBERSHIP);
- while (my $node = shift @groupidstocheck) {
- $sth->execute($node);
- my $member;
- while (($member) = $sth->fetchrow_array) {
- if (!$groupidschecked{$member}) {
- $groupidschecked{$member} = 1;
- push @groupidstocheck, $member;
- push @groups, $member unless grep $_ == $member, @groups;
- }
- }
- }
- return \@groups;
+ return grep($_->id == $group_id, @{ $self->bless_groups }) ? 1 : 0;
}
sub match {
@@ -1025,6 +1345,8 @@
my $user = Bugzilla->user;
my $dbh = Bugzilla->dbh;
+ $str = trim($str);
+
my @users = ();
return \@users if $str =~ /^\s*$/;
@@ -1036,14 +1358,11 @@
# first try wildcards
my $wildstr = $str;
- if ($wildstr =~ s/\*/\%/g # don't do wildcards if no '*' in the string
- # or if we only want exact matches
- && Bugzilla->params->{'usermatchmode'} ne 'off')
- {
-
+ # Do not do wildcards if there is no '*' in the string.
+ if ($wildstr =~ s/\*/\%/g && $user->id) {
# Build the query.
trick_taint($wildstr);
- my $query = "SELECT DISTINCT login_name FROM profiles ";
+ my $query = "SELECT DISTINCT userid FROM profiles ";
if (Bugzilla->params->{'usevisibilitygroups'}) {
$query .= "INNER JOIN user_group_map
ON user_group_map.user_id = profiles.userid ";
@@ -1056,16 +1375,13 @@
"AND group_id IN(" .
join(', ', (-1, @{$user->visible_groups_inherited})) . ") ";
}
- $query .= " AND disabledtext = '' " if $exclude_disabled;
- $query .= " ORDER BY login_name ";
+ $query .= " AND is_enabled = 1 " if $exclude_disabled;
$query .= $dbh->sql_limit($limit) if $limit;
# Execute the query, retrieve the results, and make them into
# User objects.
- my $user_logins = $dbh->selectcol_arrayref($query, undef, ($wildstr, $wildstr));
- foreach my $login_name (@$user_logins) {
- push(@users, new Bugzilla::User({ name => $login_name }));
- }
+ my $user_ids = $dbh->selectcol_arrayref($query, undef, ($wildstr, $wildstr));
+ @users = @{Bugzilla::User->new_from_list($user_ids)};
}
else { # try an exact match
# Exact matches don't care if a user is disabled.
@@ -1078,89 +1394,39 @@
}
# then try substring search
- if ((scalar(@users) == 0)
- && (Bugzilla->params->{'usermatchmode'} eq 'search')
- && (length($str) >= 3))
- {
- $str = lc($str);
+ if (!scalar(@users) && length($str) >= 3 && $user->id) {
trick_taint($str);
- my $query = "SELECT DISTINCT login_name FROM profiles ";
+ my $query = "SELECT DISTINCT userid FROM profiles ";
if (Bugzilla->params->{'usevisibilitygroups'}) {
$query .= "INNER JOIN user_group_map
ON user_group_map.user_id = profiles.userid ";
}
$query .= " WHERE (" .
- $dbh->sql_position('?', 'LOWER(login_name)') . " > 0" . " OR " .
- $dbh->sql_position('?', 'LOWER(realname)') . " > 0) ";
+ $dbh->sql_iposition('?', 'login_name') . " > 0" . " OR " .
+ $dbh->sql_iposition('?', 'realname') . " > 0) ";
if (Bugzilla->params->{'usevisibilitygroups'}) {
$query .= " AND isbless = 0" .
" AND group_id IN(" .
join(', ', (-1, @{$user->visible_groups_inherited})) . ") ";
}
- $query .= " AND disabledtext = '' " if $exclude_disabled;
- $query .= " ORDER BY login_name ";
+ $query .= " AND is_enabled = 1 " if $exclude_disabled;
$query .= $dbh->sql_limit($limit) if $limit;
-
- my $user_logins = $dbh->selectcol_arrayref($query, undef, ($str, $str));
- foreach my $login_name (@$user_logins) {
- push(@users, new Bugzilla::User({ name => $login_name }));
- }
+ my $user_ids = $dbh->selectcol_arrayref($query, undef, ($str, $str));
+ @users = @{Bugzilla::User->new_from_list($user_ids)};
}
return \@users;
}
-# match_field() is a CGI wrapper for the match() function.
-#
-# Here's what it does:
-#
-# 1. Accepts a list of fields along with whether they may take multiple values
-# 2. Takes the values of those fields from the first parameter, a $cgi object
-# and passes them to match()
-# 3. Checks the results of the match and displays confirmation or failure
-# messages as appropriate.
-#
-# The confirmation screen functions the same way as verify-new-product and
-# confirm-duplicate, by rolling all of the state information into a
-# form which is passed back, but in this case the searched fields are
-# replaced with the search results.
-#
-# The act of displaying the confirmation or failure messages means it must
-# throw a template and terminate. When confirmation is sent, all of the
-# searchable fields have been replaced by exact fields and the calling script
-# is executed as normal.
-#
-# You also have the choice of *never* displaying the confirmation screen.
-# In this case, match_field will return one of the three USER_MATCH
-# constants described in the POD docs. To make match_field behave this
-# way, pass in MATCH_SKIP_CONFIRM as the third argument.
-#
-# match_field must be called early in a script, before anything external is
-# done with the form data.
-#
-# In order to do a simple match without dealing with templates, confirmation,
-# or globals, simply calling Bugzilla::User::match instead will be
-# sufficient.
-
-# How to call it:
-#
-# Bugzilla::User::match_field($cgi, {
-# 'field_name' => { 'type' => fieldtype },
-# 'field_name2' => { 'type' => fieldtype },
-# [...]
-# });
-#
-# fieldtype can be either 'single' or 'multi'.
-#
-
sub match_field {
- my $cgi = shift; # CGI object to look up fields in
my $fields = shift; # arguments as a hash
+ my $data = shift || Bugzilla->input_params; # hash to look up fields in
my $behavior = shift || 0; # A constant that tells us how to act
my $matches = {}; # the values sent to the template
my $matchsuccess = 1; # did the match fail?
my $need_confirm = 0; # whether to display confirmation screen
my $match_multiple = 0; # whether we ever matched more than one user
+ my @non_conclusive_fields; # fields which don't have a unique user.
my $params = Bugzilla->params;
@@ -1178,7 +1444,8 @@
$expanded_fields->{$field_pattern} = $fields->{$field_pattern};
}
else {
- my @field_names = grep(/$field_pattern/, $cgi->param());
+ my @field_names = grep(/$field_pattern/, keys %$data);
+
foreach my $field_name (@field_names) {
$expanded_fields->{$field_name} =
{ type => $fields->{$field_pattern}->{'type'} };
@@ -1204,7 +1471,7 @@
# No need to look for a valid requestee if the flag(type)
# has been deleted (may occur in race conditions).
delete $expanded_fields->{$field_name};
- $cgi->delete($field_name);
+ delete $data->{$field_name};
}
}
}
@@ -1212,54 +1479,34 @@
}
$fields = $expanded_fields;
- for my $field (keys %{$fields}) {
+ foreach my $field (keys %{$fields}) {
+ next unless defined $data->{$field};
- # Tolerate fields that do not exist.
- #
- # This is so that fields like qa_contact can be specified in the code
- # and it won't break if the CGI object does not know about them.
- #
- # It has the side-effect that if a bad field name is passed it will be
- # quietly ignored rather than raising a code error.
-
- next if !defined $cgi->param($field);
-
- # We need to move the query to $raw_field, where it will be split up,
- # modified by the search, and put back into the CGI environment
- # incrementally.
-
- my $raw_field = join(" ", $cgi->param($field));
-
- # When we add back in values later, it matters that we delete
- # the field here, and not set it to '', so that we will add
- # things to an empty list, and not to a list containing one
- # empty string.
- # If the field accepts only one match (type eq "single") and
- # no match or more than one match is found for this field,
- # we will set it back to '' so that the field remains defined
- # outside this function (it was if we came here; else we would
- # have returned earlier above).
- # If the field accepts several matches (type eq "multi") and no match
- # is found, we leave this field undefined (= empty array).
- $cgi->delete($field);
-
- my @queries = ();
+ #Concatenate login names, so that we have a common way to handle them.
+ my $raw_field;
+ if (ref $data->{$field}) {
+ $raw_field = join(",", @{$data->{$field}});
+ }
+ else {
+ $raw_field = $data->{$field};
+ }
+ $raw_field = clean_text($raw_field || '');
# Now we either split $raw_field by spaces/commas and put the list
# into @queries, or in the case of fields which only accept single
# entries, we simply use the verbatim text.
-
- $raw_field =~ s/^\s+|\s+$//sg; # trim leading/trailing space
-
- # single field
+ my @queries;
if ($fields->{$field}->{'type'} eq 'single') {
- @queries = ($raw_field) unless $raw_field =~ /^\s*$/;
-
- # multi-field
+ @queries = ($raw_field);
+ # We will repopulate it later if a match is found, else it must
+ # be set to an empty string so that the field remains defined.
+ $data->{$field} = '';
}
elsif ($fields->{$field}->{'type'} eq 'multi') {
- @queries = split(/[\s,]+/, $raw_field);
-
+ @queries = split(/[,;]+/, $raw_field);
+ # We will repopulate it later if a match is found, else it must
+ # be undefined.
+ delete $data->{$field};
}
else {
# bad argument
@@ -1269,40 +1516,32 @@
});
}
+ # Tolerate fields that do not exist (in case you specify
+ # e.g. the QA contact, and it's currently not in use).
+ next unless (defined $raw_field && $raw_field ne '');
+
my $limit = 0;
if ($params->{'maxusermatches'}) {
$limit = $params->{'maxusermatches'} + 1;
}
+ my @logins;
for my $query (@queries) {
-
+ $query = trim($query);
my $users = match(
$query, # match string
$limit, # match limit
1 # exclude_disabled
);
- # skip confirmation for exact matches
- if ((scalar(@{$users}) == 1)
- && (lc(@{$users}[0]->login) eq lc($query)))
-
- {
- $cgi->append(-name=>$field,
- -values=>[@{$users}[0]->login]);
-
- next;
- }
-
- $matches->{$field}->{$query}->{'users'} = $users;
- $matches->{$field}->{$query}->{'status'} = 'success';
-
# here is where it checks for multiple matches
-
if (scalar(@{$users}) == 1) { # exactly one match
+ push(@logins, @{$users}[0]->login);
- $cgi->append(-name=>$field,
- -values=>[@{$users}[0]->login]);
+ # skip confirmation for exact matches
+ next if (lc(@{$users}[0]->login) eq lc($query));
+ $matches->{$field}->{$query}->{'status'} = 'success';
$need_confirm = 1 if $params->{'confirmuniqueusermatch'};
}
@@ -1310,6 +1549,7 @@
&& ($params->{'maxusermatches'} != 1)) {
$need_confirm = 1;
$match_multiple = 1;
+ push(@non_conclusive_fields, $field);
if (($params->{'maxusermatches'})
&& (scalar(@{$users}) > $params->{'maxusermatches'}))
@@ -1317,23 +1557,31 @@
$matches->{$field}->{$query}->{'status'} = 'trunc';
pop @{$users}; # take the last one out
}
+ else {
+ $matches->{$field}->{$query}->{'status'} = 'success';
+ }
}
else {
# everything else fails
$matchsuccess = 0; # fail
+ push(@non_conclusive_fields, $field);
$matches->{$field}->{$query}->{'status'} = 'fail';
$need_confirm = 1; # confirmation screen shows failures
}
+
+ $matches->{$field}->{$query}->{'users'} = $users;
}
- # Above, we deleted the field before adding matches. If no match
- # or more than one match has been found for a field expecting only
- # one match (type eq "single"), we set it back to '' so
- # that the caller of this function can still check whether this
+
+ # If no match or more than one match has been found for a field
+ # expecting only one match (type eq "single"), we set it back to ''
+ # so that the caller of this function can still check whether this
# field was defined or not (and it was if we came here).
- if (!defined $cgi->param($field)
- && $fields->{$field}->{'type'} eq 'single') {
- $cgi->param($field, '');
+ if ($fields->{$field}->{'type'} eq 'single') {
+ $data->{$field} = $logins[0] || '';
+ }
+ elsif (scalar @logins) {
+ $data->{$field} = \@logins;
}
}
@@ -1349,55 +1597,55 @@
}
# Skip confirmation if we were told to, or if we don't need to confirm.
- return $retval if ($behavior == MATCH_SKIP_CONFIRM || !$need_confirm);
+ if ($behavior == MATCH_SKIP_CONFIRM || !$need_confirm) {
+ return wantarray ? ($retval, \@non_conclusive_fields) : $retval;
+ }
my $template = Bugzilla->template;
+ my $cgi = Bugzilla->cgi;
my $vars = {};
- $vars->{'script'} = Bugzilla->cgi->url(-relative => 1); # for self-referencing URLs
+ $vars->{'script'} = $cgi->url(-relative => 1); # for self-referencing URLs
$vars->{'fields'} = $fields; # fields being matched
$vars->{'matches'} = $matches; # matches that were made
$vars->{'matchsuccess'} = $matchsuccess; # continue or fail
$vars->{'matchmultiple'} = $match_multiple;
- print Bugzilla->cgi->header();
+ print $cgi->header();
$template->process("global/confirm-user-match.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
-
exit;
}
-# Changes in some fields automatically trigger events. The 'field names' are
-# from the fielddefs table. We really should be using proper field names
-# throughout.
+# Changes in some fields automatically trigger events. The field names are
+# from the fielddefs table.
our %names_to_events = (
- 'Resolution' => EVT_OPENED_CLOSED,
- 'Keywords' => EVT_KEYWORD,
- 'CC' => EVT_CC,
- 'Severity' => EVT_PROJ_MANAGEMENT,
- 'Priority' => EVT_PROJ_MANAGEMENT,
- 'Status' => EVT_PROJ_MANAGEMENT,
- 'Target Milestone' => EVT_PROJ_MANAGEMENT,
- 'Attachment description' => EVT_ATTACHMENT_DATA,
- 'Attachment mime type' => EVT_ATTACHMENT_DATA,
- 'Attachment is patch' => EVT_ATTACHMENT_DATA,
- 'Depends on' => EVT_DEPEND_BLOCK,
- 'Blocks' => EVT_DEPEND_BLOCK);
+ 'resolution' => EVT_OPENED_CLOSED,
+ 'keywords' => EVT_KEYWORD,
+ 'cc' => EVT_CC,
+ 'bug_severity' => EVT_PROJ_MANAGEMENT,
+ 'priority' => EVT_PROJ_MANAGEMENT,
+ 'bug_status' => EVT_PROJ_MANAGEMENT,
+ 'target_milestone' => EVT_PROJ_MANAGEMENT,
+ 'attachments.description' => EVT_ATTACHMENT_DATA,
+ 'attachments.mimetype' => EVT_ATTACHMENT_DATA,
+ 'attachments.ispatch' => EVT_ATTACHMENT_DATA,
+ 'dependson' => EVT_DEPEND_BLOCK,
+ 'blocked' => EVT_DEPEND_BLOCK);
# Returns true if the user wants mail for a given bug change.
# Note: the "+" signs before the constants suppress bareword quoting.
sub wants_bug_mail {
my $self = shift;
- my ($bug_id, $relationship, $fieldDiffs, $commentField, $dependencyText,
- $changer, $bug_is_new) = @_;
+ my ($bug, $relationship, $fieldDiffs, $comments, $dep_mail, $changer) = @_;
# Make a list of the events which have happened during this bug change,
# from the point of view of this user.
my %events;
- foreach my $ref (@$fieldDiffs) {
- my ($who, $whoname, $fieldName, $when, $old, $new) = @$ref;
+ foreach my $change (@$fieldDiffs) {
+ my $fieldName = $change->{field_name};
# A change to any of the above fields sets the corresponding event
if (defined($names_to_events{$fieldName})) {
$events{$names_to_events{$fieldName}} = 1;
@@ -1409,16 +1657,16 @@
# If the user is in a particular role and the value of that role
# changed, we need the ADDED_REMOVED event.
- if (($fieldName eq "AssignedTo" && $relationship == REL_ASSIGNEE) ||
- ($fieldName eq "QAContact" && $relationship == REL_QA))
+ if (($fieldName eq "assigned_to" && $relationship == REL_ASSIGNEE) ||
+ ($fieldName eq "qa_contact" && $relationship == REL_QA))
{
$events{+EVT_ADDED_REMOVED} = 1;
}
- if ($fieldName eq "CC") {
+ if ($fieldName eq "cc") {
my $login = $self->login;
- my $inold = ($old =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
- my $innew = ($new =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
+ my $inold = ($change->{old} =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
+ my $innew = ($change->{new} =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
if ($inold != $innew)
{
$events{+EVT_ADDED_REMOVED} = 1;
@@ -1426,26 +1674,30 @@
}
}
- # You role is new if the bug itself is.
- # Only makes sense for the assignee, QA contact and the CC list.
- if ($bug_is_new
- && ($relationship == REL_ASSIGNEE
+ if (!$bug->lastdiffed) {
+ # Notify about new bugs.
+ $events{+EVT_BUG_CREATED} = 1;
+
+ # You role is new if the bug itself is.
+ # Only makes sense for the assignee, QA contact and the CC list.
+ if ($relationship == REL_ASSIGNEE
|| $relationship == REL_QA
- || $relationship == REL_CC))
- {
- $events{+EVT_ADDED_REMOVED} = 1;
+ || $relationship == REL_CC)
+ {
+ $events{+EVT_ADDED_REMOVED} = 1;
+ }
}
- if ($commentField =~ /Created an attachment \(/) {
+ if (grep { $_->type == CMT_ATTACHMENT_CREATED } @$comments) {
$events{+EVT_ATTACHMENT} = 1;
}
- elsif ($commentField ne '') {
+ elsif (defined($$comments[0])) {
$events{+EVT_COMMENT} = 1;
}
# Dependent changed bugmails must have an event to ensure the bugmail is
# emailed.
- if ($dependencyText ne '') {
+ if ($dep_mail) {
$events{+EVT_DEPEND_BLOCK} = 1;
}
@@ -1458,23 +1710,12 @@
#
# We do them separately because if _any_ of them are set, we don't want
# the mail.
- if ($wants_mail && $changer && ($self->login eq $changer)) {
+ if ($wants_mail && $changer && ($self->id == $changer->id)) {
$wants_mail &= $self->wants_mail([EVT_CHANGED_BY_ME], $relationship);
}
- if ($wants_mail) {
- my $dbh = Bugzilla->dbh;
- # We don't create a Bug object from the bug_id here because we only
- # need one piece of information, and doing so (as of 2004-11-23) slows
- # down bugmail sending by a factor of 2. If Bug creation was more
- # lazy, this might not be so bad.
- my $bug_status = $dbh->selectrow_array('SELECT bug_status
- FROM bugs WHERE bug_id = ?',
- undef, $bug_id);
-
- if ($bug_status eq "UNCONFIRMED") {
- $wants_mail &= $self->wants_mail([EVT_UNCONFIRMED], $relationship);
- }
+ if ($wants_mail && $bug->bug_status eq 'UNCONFIRMED') {
+ $wants_mail &= $self->wants_mail([EVT_UNCONFIRMED], $relationship);
}
return $wants_mail;
@@ -1502,29 +1743,37 @@
# Skip DB query if relationship is explicit
return 1 if $relationship == REL_GLOBAL_WATCHER;
- my $dbh = Bugzilla->dbh;
-
- my $wants_mail =
- $dbh->selectrow_array('SELECT 1
- FROM email_setting
- WHERE user_id = ?
- AND relationship = ?
- AND event IN (' . join(',', @$events) . ') ' .
- $dbh->sql_limit(1),
- undef, ($self->id, $relationship));
-
- return defined($wants_mail) ? 1 : 0;
+ my $wants_mail = grep { $self->mail_settings->{$relationship}{$_} } @$events;
+ return $wants_mail ? 1 : 0;
}
-sub is_mover {
+sub mail_settings {
my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!defined $self->{'is_mover'}) {
- my @movers = map { trim($_) } split(',', Bugzilla->params->{'movers'});
- $self->{'is_mover'} = ($self->id
- && lsearch(\@movers, $self->login) != -1);
+ if (!defined $self->{'mail_settings'}) {
+ my $data =
+ $dbh->selectall_arrayref('SELECT relationship, event FROM email_setting
+ WHERE user_id = ?', undef, $self->id);
+ my %mail;
+ # The hash is of the form $mail{$relationship}{$event} = 1.
+ $mail{$_->[0]}{$_->[1]} = 1 foreach @$data;
+
+ $self->{'mail_settings'} = \%mail;
}
- return $self->{'is_mover'};
+ return $self->{'mail_settings'};
+}
+
+sub has_audit_entries {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!exists $self->{'has_audit_entries'}) {
+ $self->{'has_audit_entries'} =
+ $dbh->selectrow_array('SELECT 1 FROM audit_log WHERE user_id = ? ' .
+ $dbh->sql_limit(1), undef, $self->id);
+ }
+ return $self->{'has_audit_entries'};
}
sub is_insider {
@@ -1542,12 +1791,23 @@
my $self = shift;
if (!defined $self->{'is_global_watcher'}) {
- my @watchers = split(/[,\s]+/, Bugzilla->params->{'globalwatchers'});
+ my @watchers = split(/[,;]+/, Bugzilla->params->{'globalwatchers'});
$self->{'is_global_watcher'} = scalar(grep { $_ eq $self->login } @watchers) ? 1 : 0;
}
return $self->{'is_global_watcher'};
}
+sub is_timetracker {
+ my $self = shift;
+
+ if (!defined $self->{'is_timetracker'}) {
+ my $tt_group = Bugzilla->params->{'timetrackinggroup'};
+ $self->{'is_timetracker'} =
+ ($tt_group && $self->in_group($tt_group)) ? 1 : 0;
+ }
+ return $self->{'is_timetracker'};
+}
+
sub get_userlist {
my $self = shift;
@@ -1567,7 +1827,7 @@
"AND group_id IN(" .
join(', ', (-1, @{$self->visible_groups_inherited})) . ")";
}
- $query .= " WHERE disabledtext = '' ";
+ $query .= " WHERE is_enabled = 1 ";
$query .= $dbh->sql_group_by('userid', 'login_name, realname');
my $sth = $dbh->prepare($query);
@@ -1597,7 +1857,9 @@
my $user = $class->SUPER::create(@_);
# Turn on all email for the new user
- foreach my $rel (RELATIONSHIPS) {
+ require Bugzilla::BugMail;
+ my %relationships = Bugzilla::BugMail::relationships();
+ foreach my $rel (keys %relationships) {
foreach my $event (POS_EVENTS, NEG_EVENTS) {
# These "exceptions" define the default email preferences.
#
@@ -1635,6 +1897,55 @@
return $user;
}
+###########################
+# Account Lockout Methods #
+###########################
+
+sub account_is_locked_out {
+ my $self = shift;
+ my $login_failures = scalar @{ $self->account_ip_login_failures };
+ return $login_failures >= MAX_LOGIN_ATTEMPTS ? 1 : 0;
+}
+
+sub note_login_failure {
+ my $self = shift;
+ my $ip_addr = remote_ip();
+ trick_taint($ip_addr);
+ Bugzilla->dbh->do("INSERT INTO login_failure (user_id, ip_addr, login_time)
+ VALUES (?, ?, LOCALTIMESTAMP(0))",
+ undef, $self->id, $ip_addr);
+ delete $self->{account_ip_login_failures};
+}
+
+sub clear_login_failures {
+ my $self = shift;
+ my $ip_addr = remote_ip();
+ trick_taint($ip_addr);
+ Bugzilla->dbh->do(
+ 'DELETE FROM login_failure WHERE user_id = ? AND ip_addr = ?',
+ undef, $self->id, $ip_addr);
+ delete $self->{account_ip_login_failures};
+}
+
+sub account_ip_login_failures {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $time = $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-',
+ LOGIN_LOCKOUT_INTERVAL, 'MINUTE');
+ my $ip_addr = remote_ip();
+ trick_taint($ip_addr);
+ $self->{account_ip_login_failures} ||= Bugzilla->dbh->selectall_arrayref(
+ "SELECT login_time, ip_addr, user_id FROM login_failure
+ WHERE user_id = ? AND login_time > $time
+ AND ip_addr = ?
+ ORDER BY login_time", {Slice => {}}, $self->id, $ip_addr);
+ return $self->{account_ip_login_failures};
+}
+
+###############
+# Subroutines #
+###############
+
sub is_available_username {
my ($username, $old_username) = @_;
@@ -1660,7 +1971,7 @@
$dbh->sql_position(q{':'}, 'eventdata') . "- 1)) = ?)
OR (tokentype = 'emailnew'
AND SUBSTRING(eventdata, (" .
- $dbh->sql_position(q{':'}, 'eventdata') . "+ 1)) = ?)",
+ $dbh->sql_position(q{':'}, 'eventdata') . "+ 1), LENGTH(eventdata)) = ?)",
undef, ($username, $username));
if ($eventdata) {
@@ -1674,15 +1985,57 @@
return 1;
}
+sub check_account_creation_enabled {
+ my $self = shift;
+
+ # If we're using e.g. LDAP for login, then we can't create a new account.
+ $self->authorizer->user_can_create_account
+ || ThrowUserError('auth_cant_create_account');
+
+ Bugzilla->params->{'createemailregexp'}
+ || ThrowUserError('account_creation_disabled');
+}
+
+sub check_and_send_account_creation_confirmation {
+ my ($self, $login) = @_;
+
+ $login = $self->check_login_name_for_creation($login);
+ my $creation_regexp = Bugzilla->params->{'createemailregexp'};
+
+ if ($login !~ /$creation_regexp/i) {
+ ThrowUserError('account_creation_restricted');
+ }
+
+ # Create and send a token for this new account.
+ require Bugzilla::Token;
+ Bugzilla::Token::issue_new_user_account_token($login);
+}
+
+# This is used in a few performance-critical areas where we don't want to
+# do check() and pull all the user data from the database.
sub login_to_id {
my ($login, $throw_error) = @_;
my $dbh = Bugzilla->dbh;
- # No need to validate $login -- it will be used by the following SELECT
- # statement only, so it's safe to simply trick_taint.
- trick_taint($login);
- my $user_id = $dbh->selectrow_array("SELECT userid FROM profiles WHERE " .
- $dbh->sql_istrcmp('login_name', '?'),
- undef, $login);
+ my $cache = Bugzilla->request_cache->{user_login_to_id} ||= {};
+
+ # We cache lookups because this function showed up as taking up a
+ # significant amount of time in profiles of xt/search.t. However,
+ # for users that don't exist, we re-do the check every time, because
+ # otherwise we break is_available_username.
+ my $user_id;
+ if (defined $cache->{$login}) {
+ $user_id = $cache->{$login};
+ }
+ else {
+ # No need to validate $login -- it will be used by the following SELECT
+ # statement only, so it's safe to simply trick_taint.
+ trick_taint($login);
+ $user_id = $dbh->selectrow_array(
+ "SELECT userid FROM profiles
+ WHERE " . $dbh->sql_istrcmp('login_name', '?'), undef, $login);
+ $cache->{$login} = $user_id;
+ }
+
if ($user_id) {
return $user_id;
} elsif ($throw_error) {
@@ -1708,11 +2061,22 @@
if (length($password) < USER_PASSWORD_MIN_LENGTH) {
ThrowUserError('password_too_short');
- } elsif (length($password) > USER_PASSWORD_MAX_LENGTH) {
- ThrowUserError('password_too_long');
} elsif ((defined $matchpassword) && ($password ne $matchpassword)) {
ThrowUserError('passwords_dont_match');
}
+
+ my $complexity_level = Bugzilla->params->{password_complexity};
+ if ($complexity_level eq 'letters_numbers_specialchars') {
+ ThrowUserError('password_not_complex')
+ if ($password !~ /\w/ || $password !~ /\d/ || $password !~ /[[:punct:]]/);
+ } elsif ($complexity_level eq 'letters_numbers') {
+ ThrowUserError('password_not_complex')
+ if ($password !~ /[[:lower:]]/ || $password !~ /[[:upper:]]/ || $password !~ /\d/);
+ } elsif ($complexity_level eq 'mixed_letters') {
+ ThrowUserError('password_not_complex')
+ if ($password !~ /[[:lower:]]/ || $password !~ /[[:upper:]]/);
+ }
+
# Having done these checks makes us consider the password untainted.
trick_taint($_[0]);
return 1;
@@ -1784,6 +2148,18 @@
=head1 METHODS
+=head2 Constructors
+
+=over
+
+=item C<super_user>
+
+Returns a user who is in all groups, but who does not really exist in the
+database. Used for non-web scripts like L<checksetup> that need to make
+database changes and so on.
+
+=back
+
=head2 Saved and Shared Queries
=over
@@ -1816,6 +2192,34 @@
An arrayref of group ids. The user can share their own queries with these
groups.
+=item C<tags>
+
+Returns a hashref with tag IDs as key, and a hashref with tag 'id',
+'name' and 'bug_count' as value.
+
+=back
+
+=head2 Account Lockout
+
+=over
+
+=item C<account_is_locked_out>
+
+Returns C<1> if the account has failed to log in too many times recently,
+and thus is locked out for a period of time. Returns C<0> otherwise.
+
+=item C<account_ip_login_failures>
+
+Returns an arrayref of hashrefs, that contains information about the recent
+times that this account has failed to log in from the current remote IP.
+The hashes contain C<ip_addr>, C<login_time>, and C<user_id>.
+
+=item C<note_login_failure>
+
+This notes that this account has failed to log in, and stores the fact
+in the database. The storing happens immediately, it does not wait for
+you to call C<update>.
+
=back
=head2 Other Methods
@@ -1886,12 +2290,19 @@
is_default - a boolean to indicate whether the user has chosen to make
a preference for themself or use the site default.
+=item C<setting(name)>
+
+Returns the value for the specified setting.
+
+=item C<timezone>
+
+Returns the timezone used to display dates and times to the user,
+as a DateTime::TimeZone object.
+
=item C<groups>
-Returns a hashref of group names for groups the user is a member of. The keys
-are the names of the groups, whilst the values are the respective group ids.
-(This is so that a set of all groupids for groups the user is in can be
-obtained by C<values(%{$user-E<gt>groups})>.)
+Returns an arrayref of L<Bugzilla::Group> objects representing
+groups that this user is a member of.
=item C<groups_as_string>
@@ -1899,6 +2310,13 @@
the user is not a member of any groups, returns "-1". This is most often used
within an SQL IN() function.
+=item C<groups_in_sql>
+
+This returns an C<IN> clause for SQL, containing either all of the groups
+the user is in, or C<-1> if the user is in no groups. This takes one
+argument--the name of the SQL field that should be on the left-hand-side
+of the C<IN> statement, which defaults to C<group_id> if not specified.
+
=item C<in_group($group_name, $product_id)>
Determines whether or not a user is in the given group by name.
@@ -1911,12 +2329,11 @@
=item C<bless_groups>
-Returns an arrayref of hashes of C<groups> entries, where the keys of each hash
-are the names of C<id>, C<name> and C<description> columns of the C<groups>
-table.
+Returns an arrayref of L<Bugzilla::Group> objects.
+
The arrayref consists of the groups the user can bless, taking into account
that having editusers permissions means that you can bless all groups, and
-that you need to be aware of a group in order to bless a group.
+that you need to be able to see a group in order to bless it.
=item C<get_products_by_permission($group)>
@@ -1951,6 +2368,12 @@
user may be placed into different groups, based on a new email regexp. This
method should be called in such a case to force reresolution of these groups.
+=item C<clear_product_cache>
+
+Clears the stored values for L</get_selectable_products>,
+L</get_enterable_products>, etc. so that their data will be read from
+the database again. Used mostly by L<Bugzilla::Product>.
+
=item C<get_selectable_products>
Description: Returns all products the user is allowed to access. This list
@@ -1998,6 +2421,21 @@
Returns: an array of product objects.
+=item C<can_access_product($product)>
+
+Returns 1 if the user can search or enter bugs into the specified product
+(either a L<Bugzilla::Product> or a product name), and 0 if the user should
+not be aware of the existence of the product.
+
+=item C<get_accessible_products>
+
+ Description: Returns an array of product objects the user can search
+ or enter bugs against.
+
+ Params: none
+
+ Returns: an array of product objects.
+
=item C<check_can_admin_product($product_name)>
Description: Checks whether the user is allowed to administrate the product.
@@ -2006,6 +2444,21 @@
Returns: On success, a product object. On failure, an error is thrown.
+=item C<check_can_admin_flagtype($flagtype_id)>
+
+ Description: Checks whether the user is allowed to edit properties of the flag type.
+ If the flag type is also used by some products for which the user
+ hasn't editcomponents privs, then the user is only allowed to edit
+ the inclusion and exclusion lists for products he can administrate.
+
+ Params: $flagtype_id - a flag type ID.
+
+ Returns: On success, a flag type object. On failure, an error is thrown.
+ In list context, a boolean indicating whether the user can edit
+ all properties of the flag type is also returned. The boolean
+ is false if the user can only edit the inclusion and exclusions
+ lists.
+
=item C<can_request_flag($flag_type)>
Description: Checks whether the user can request flags of the given type.
@@ -2030,14 +2483,6 @@
containing the login, identity and visibility. Users that are not visible to this
user will have 'visible' set to zero.
-=item C<flatten_group_membership>
-
-Accepts a list of groups and returns a list of all the groups whose members
-inherit membership in any group on the list. So, we can determine if a user
-is in any of the groups input to flatten_group_membership by querying the
-user_group_map for any user with DIRECT or REGEXP membership IN() the list
-of groups returned.
-
=item C<direct_group_membership>
Returns a reference to an array of group objects. Groups the user belong to
@@ -2082,12 +2527,6 @@
more general than C<wants_bug_mail>, allowing you to check e.g. permissions
for flag mail.
-=item C<is_mover>
-
-Returns true if the user is in the list of users allowed to move bugs
-to another database. Note that this method doesn't check whether bug
-moving is enabled.
-
=item C<is_insider>
Returns true if the user can access private comments and attachments,
@@ -2128,6 +2567,17 @@
Takes a username as its only argument. Throws an error if there is no
user with that username. Returns a C<Bugzilla::User> object.
+=item C<check_account_creation_enabled>
+
+Checks that users can create new user accounts, and throws an error
+if user creation is disabled.
+
+=item C<check_and_send_account_creation_confirmation($login)>
+
+If the user request for a new account passes validation checks, an email
+is sent to this user for confirmation. Otherwise an error is thrown
+indicating why the request has been rejected.
+
=item C<is_available_username>
Returns a boolean indicating whether or not the supplied username is
@@ -2172,6 +2622,49 @@
If a second password is passed in, this function also verifies that
the two passwords match.
+=item C<match_field($data, $fields, $behavior)>
+
+=over
+
+=item B<Description>
+
+Wrapper for the C<match()> function.
+
+=item B<Params>
+
+=over
+
+=item C<$fields> - A hashref with field names as keys and a hash as values.
+Each hash is of the form { 'type' => 'single|multi' }, which specifies
+whether the field can take a single login name only or several.
+
+=item C<$data> (optional) - A hashref with field names as keys and field values
+as values. If undefined, C<Bugzilla-E<gt>input_params> is used.
+
+=item C<$behavior> (optional) - If set to C<MATCH_SKIP_CONFIRM>, no confirmation
+screen is displayed. In that case, the fields which don't match a unique user
+are left undefined. If not set, a confirmation screen is displayed if at
+least one field doesn't match any login name or match more than one.
+
+=back
+
+=item B<Returns>
+
+If the third parameter is set to C<MATCH_SKIP_CONFIRM>, the function returns
+either C<USER_MATCH_SUCCESS> if all fields can be set unambiguously,
+C<USER_MATCH_FAILED> if at least one field doesn't match any user account,
+or C<USER_MATCH_MULTIPLE> if some fields match more than one user account.
+
+If the third parameter is not set, then if all fields could be set
+unambiguously, nothing is returned, else a confirmation page is displayed.
+
+=item B<Note>
+
+This function must be called early in a script, before anything external
+is done with the data.
+
+=back
+
=back
=head1 SEE ALSO
diff --git a/Websites/bugs.webkit.org/Bugzilla/User/Setting.pm b/Websites/bugs.webkit.org/Bugzilla/User/Setting.pm
index 7a6c72f..78e64c9 100644
--- a/Websites/bugs.webkit.org/Bugzilla/User/Setting.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/User/Setting.pm
@@ -125,7 +125,8 @@
###############################
sub add_setting {
- my ($name, $values, $default_value, $subclass, $force_check) = @_;
+ my ($name, $values, $default_value, $subclass, $force_check,
+ $silently) = @_;
my $dbh = Bugzilla->dbh;
my $exists = _setting_exists($name);
@@ -146,7 +147,7 @@
undef, $name);
}
}
- else {
+ elsif (!$silently) {
print get_text('install_setting_new', { name => $name }) . "\n";
}
$dbh->do(q{INSERT INTO setting (name, default_value, is_enabled, subclass)
diff --git a/Websites/bugs.webkit.org/Bugzilla/User/Setting/Timezone.pm b/Websites/bugs.webkit.org/Bugzilla/User/Setting/Timezone.pm
new file mode 100644
index 0000000..27a90e3
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/User/Setting/Timezone.pm
@@ -0,0 +1,72 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (c) 2008 Frédéric Buclin.
+# All rights reserved.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+package Bugzilla::User::Setting::Timezone;
+
+use strict;
+
+use DateTime::TimeZone;
+
+use base qw(Bugzilla::User::Setting);
+
+use Bugzilla::Constants;
+
+sub legal_values {
+ my ($self) = @_;
+
+ return $self->{'legal_values'} if defined $self->{'legal_values'};
+
+ my @timezones = DateTime::TimeZone->all_names;
+ # Remove old formats, such as CST6CDT, EST, EST5EDT.
+ @timezones = grep { $_ =~ m#.+/.+#} @timezones;
+ # Append 'local' to the list, which will use the timezone
+ # given by the server.
+ push(@timezones, 'local');
+ push(@timezones, 'UTC');
+
+ return $self->{'legal_values'} = \@timezones;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::User::Setting::Timezone - Object for a user preference setting for desired timezone
+
+=head1 DESCRIPTION
+
+Timezone.pm extends Bugzilla::User::Setting and implements a class specialized for
+setting the desired timezone.
+
+=head1 METHODS
+
+=over
+
+=item C<legal_values()>
+
+Description: Returns all legal timezones
+
+Params: none
+
+Returns: A reference to an array containing the names of all legal timezones
+
+=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/Util.pm b/Websites/bugs.webkit.org/Bugzilla/Util.pm
index 0d0c970..f43c9af 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Util.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Util.pm
@@ -31,36 +31,36 @@
use strict;
use base qw(Exporter);
-@Bugzilla::Util::EXPORT = qw(is_tainted trick_taint detaint_natural
+@Bugzilla::Util::EXPORT = qw(trick_taint detaint_natural
detaint_signed
html_quote url_quote xml_quote
- css_class_quote html_light_quote url_decode
- i_am_cgi get_netaddr correct_urlbase
- lsearch ssl_require_redirect use_attachbase
- diff_arrays diff_strings
+ css_class_quote html_light_quote
+ i_am_cgi correct_urlbase remote_ip validate_ip
+ do_ssl_redirect_if_required use_attachbase
+ diff_arrays on_main_db
trim wrap_hard wrap_comment find_wrap_point
- format_time format_time_decimal validate_date
- validate_time
+ format_time validate_date validate_time datetime_from
file_mod_time is_7bit_clean
bz_crypt generate_random_password
validate_email_syntax clean_text
- get_text disable_utf8);
+ get_text template_var disable_utf8
+ detect_encoding);
use Bugzilla::Constants;
+use Bugzilla::RNG qw(irand);
use Date::Parse;
use Date::Format;
+use DateTime;
+use DateTime::TimeZone;
+use Digest;
+use Email::Address;
+use List::Util qw(first);
+use Scalar::Util qw(tainted blessed);
+use Template::Filters;
use Text::Wrap;
-
-# This is from the perlsec page, slightly modified to remove a warning
-# From that page:
-# This function makes use of the fact that the presence of
-# tainted data anywhere within an expression renders the
-# entire expression tainted.
-# Don't ask me how it works...
-sub is_tainted {
- return not eval { my $foo = join('',@_), kill 0; 1; };
-}
+use Encode qw(encode decode resolve_alias);
+use Encode::Guess;
sub trick_taint {
require Carp;
@@ -72,26 +72,48 @@
sub detaint_natural {
my $match = $_[0] =~ /^(\d+)$/;
- $_[0] = $match ? $1 : undef;
+ $_[0] = $match ? int($1) : undef;
return (defined($_[0]));
}
sub detaint_signed {
my $match = $_[0] =~ /^([-+]?\d+)$/;
- $_[0] = $match ? $1 : undef;
- # Remove any leading plus sign.
- if (defined($_[0]) && $_[0] =~ /^\+(\d+)$/) {
- $_[0] = $1;
- }
+ # The "int()" call removes any leading plus sign.
+ $_[0] = $match ? int($1) : undef;
return (defined($_[0]));
}
+# Bug 120030: Override html filter to obscure the '@' in user
+# visible strings.
+# Bug 319331: Handle BiDi disruptions.
sub html_quote {
- my ($var) = (@_);
- $var =~ s/\&/\&/g;
- $var =~ s/</\</g;
- $var =~ s/>/\>/g;
- $var =~ s/\"/\"/g;
+ my ($var) = Template::Filters::html_filter(@_);
+ # Obscure '@'.
+ $var =~ s/\@/\@/g;
+ if (Bugzilla->params->{'utf8'}) {
+ # Remove the following characters because they're
+ # influencing BiDi:
+ # --------------------------------------------------------
+ # |Code |Name |UTF-8 representation|
+ # |------|--------------------------|--------------------|
+ # |U+202a|Left-To-Right Embedding |0xe2 0x80 0xaa |
+ # |U+202b|Right-To-Left Embedding |0xe2 0x80 0xab |
+ # |U+202c|Pop Directional Formatting|0xe2 0x80 0xac |
+ # |U+202d|Left-To-Right Override |0xe2 0x80 0xad |
+ # |U+202e|Right-To-Left Override |0xe2 0x80 0xae |
+ # --------------------------------------------------------
+ #
+ # The following are characters influencing BiDi, too, but
+ # they can be spared from filtering because they don't
+ # influence more than one character right or left:
+ # --------------------------------------------------------
+ # |Code |Name |UTF-8 representation|
+ # |------|--------------------------|--------------------|
+ # |U+200e|Left-To-Right Mark |0xe2 0x80 0x8e |
+ # |U+200f|Right-To-Left Mark |0xe2 0x80 0x8f |
+ # --------------------------------------------------------
+ $var =~ s/[\x{202a}-\x{202e}]//g;
+ }
return $var;
}
@@ -103,12 +125,7 @@
dfn samp kbd big small sub sup tt dd dt dl ul li ol
fieldset legend);
- # Are HTML::Scrubber and HTML::Parser installed?
- eval { require HTML::Scrubber;
- require HTML::Parser;
- };
-
- if ($@) { # Package(s) not installed.
+ if (!Bugzilla->feature('html_desc')) {
my $safe = join('|', @allow);
my $chr = chr(1);
@@ -123,7 +140,7 @@
$text =~ s#$chr($safe)$chr#<$1>#go;
return $text;
}
- else { # Packages installed.
+ else {
# We can be less restrictive. We can accept elements with attributes.
push(@allow, qw(a blockquote q span));
@@ -176,6 +193,20 @@
}
}
+sub email_filter {
+ my ($toencode) = @_;
+ if (!Bugzilla->user->id) {
+ my @emails = Email::Address->parse($toencode);
+ if (scalar @emails) {
+ my @hosts = map { quotemeta($_->host) } @emails;
+ my $hosts_re = join('|', @hosts);
+ $toencode =~ s/\@(?:$hosts_re)//g;
+ return $toencode;
+ }
+ }
+ return $toencode;
+}
+
# This originally came from CGI.pm, by Lincoln D. Stein
sub url_quote {
my ($toencode) = (@_);
@@ -187,7 +218,7 @@
sub css_class_quote {
my ($toencode) = (@_);
- $toencode =~ s/ /_/g;
+ $toencode =~ s#[ /]#_#g;
$toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("&#x%x;",ord($1))/eg;
return $toencode;
}
@@ -212,77 +243,143 @@
return $var;
}
-# This function must not be relied upon to return a valid string to pass to
-# the DB or the user in UTF-8 situations. The only thing you can rely upon
-# it for is that if you url_decode a string, it will url_encode back to the
-# exact same thing.
-sub url_decode {
- my ($todecode) = (@_);
- $todecode =~ tr/+/ /; # pluses become spaces
- $todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
- return $todecode;
-}
-
sub i_am_cgi {
# I use SERVER_SOFTWARE because it's required to be
# defined for all requests in the CGI spec.
return exists $ENV{'SERVER_SOFTWARE'} ? 1 : 0;
}
-sub ssl_require_redirect {
- my $method = shift;
+# This exists as a separate function from Bugzilla::CGI::redirect_to_https
+# because we don't want to create a CGI object during XML-RPC calls
+# (doing so can mess up XML-RPC).
+sub do_ssl_redirect_if_required {
+ return if !i_am_cgi();
+ return if !Bugzilla->params->{'ssl_redirect'};
- # If currently not in a protected SSL
- # connection, determine if a redirection is
- # needed based on value in Bugzilla->params->{ssl}.
- # If we are already in a protected connection or
- # sslbase is not set then no action is required.
- if (uc($ENV{'HTTPS'}) ne 'ON'
- && $ENV{'SERVER_PORT'} != 443
- && Bugzilla->params->{'sslbase'} ne '')
- {
- # System is configured to never require SSL
- # so no redirection is needed.
- return 0
- if Bugzilla->params->{'ssl'} eq 'never';
-
- # System is configured to always require a SSL
- # connection so we need to redirect.
- return 1
- if Bugzilla->params->{'ssl'} eq 'always';
-
- # System is configured such that if we are inside
- # of an authenticated session, then we need to make
- # sure that all of the connections are over SSL. Non
- # authenticated sessions SSL is not mandatory.
- # For XMLRPC requests, if the method is User.login
- # then we always want the connection to be over SSL
- # if the system is configured for authenticated
- # sessions since the user's username and password
- # will be passed before the user is logged in.
- return 1
- if Bugzilla->params->{'ssl'} eq 'authenticated sessions'
- && (Bugzilla->user->id
- || (defined $method && $method eq 'User.login'));
- }
-
- return 0;
+ my $sslbase = Bugzilla->params->{'sslbase'};
+
+ # If we're already running under SSL, never redirect.
+ return if uc($ENV{HTTPS} || '') eq 'ON';
+ # Never redirect if there isn't an sslbase.
+ return if !$sslbase;
+ Bugzilla->cgi->redirect_to_https();
}
sub correct_urlbase {
- my $ssl = Bugzilla->params->{'ssl'};
- return Bugzilla->params->{'urlbase'} if $ssl eq 'never';
-
+ my $ssl = Bugzilla->params->{'ssl_redirect'};
+ my $urlbase = Bugzilla->params->{'urlbase'};
my $sslbase = Bugzilla->params->{'sslbase'};
- if ($sslbase) {
- return $sslbase if $ssl eq 'always';
- # Authenticated Sessions
- return $sslbase if Bugzilla->user->id;
+
+ if (!$sslbase) {
+ return $urlbase;
+ }
+ elsif ($ssl) {
+ return $sslbase;
+ }
+ else {
+ # Return what the user currently uses.
+ return (uc($ENV{HTTPS} || '') eq 'ON') ? $sslbase : $urlbase;
+ }
+}
+
+sub remote_ip {
+ my $ip = $ENV{'REMOTE_ADDR'} || '127.0.0.1';
+ my @proxies = split(/[\s,]+/, Bugzilla->params->{'inbound_proxies'});
+
+ # If the IP address is one of our trusted proxies, then we look at
+ # the X-Forwarded-For header to determine the real remote IP address.
+ if ($ENV{'HTTP_X_FORWARDED_FOR'} && first { $_ eq $ip } @proxies) {
+ my @ips = split(/[\s,]+/, $ENV{'HTTP_X_FORWARDED_FOR'});
+ # This header can contain several IP addresses. We want the
+ # IP address of the machine which connected to our proxies as
+ # all other IP addresses may be fake or internal ones.
+ # Note that this may block a whole external proxy, but we have
+ # no way to determine if this proxy is malicious or trustable.
+ foreach my $remote_ip (reverse @ips) {
+ if (!first { $_ eq $remote_ip } @proxies) {
+ # Keep the original IP address if the remote IP is invalid.
+ $ip = validate_ip($remote_ip) || $ip;
+ last;
+ }
+ }
+ }
+ return $ip;
+}
+
+sub validate_ip {
+ my $ip = shift;
+ return is_ipv4($ip) || is_ipv6($ip);
+}
+
+# Copied from Data::Validate::IP::is_ipv4().
+sub is_ipv4 {
+ my $ip = shift;
+ return unless defined $ip;
+
+ my @octets = $ip =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
+ return unless scalar(@octets) == 4;
+
+ foreach my $octet (@octets) {
+ return unless ($octet >= 0 && $octet <= 255 && $octet !~ /^0\d{1,2}$/);
}
- # Set to "authenticated sessions" but nobody's logged in, or
- # sslbase isn't set.
- return Bugzilla->params->{'urlbase'};
+ # The IP address is valid and can now be detainted.
+ return join('.', @octets);
+}
+
+# Copied from Data::Validate::IP::is_ipv6().
+sub is_ipv6 {
+ my $ip = shift;
+ return unless defined $ip;
+
+ # If there is a :: then there must be only one :: and the length
+ # can be variable. Without it, the length must be 8 groups.
+ my @chunks = split(':', $ip);
+
+ # Need to check if the last chunk is an IPv4 address, if it is we
+ # pop it off and exempt it from the normal IPv6 checking and stick
+ # it back on at the end. If there is only one chunk and it's an IPv4
+ # address, then it isn't an IPv6 address.
+ my $ipv4;
+ my $expected_chunks = 8;
+ if (@chunks > 1 && is_ipv4($chunks[$#chunks])) {
+ $ipv4 = pop(@chunks);
+ $expected_chunks--;
+ }
+
+ my $empty = 0;
+ # Workaround to handle trailing :: being valid.
+ if ($ip =~ /[0-9a-f]{1,4}::$/) {
+ $empty++;
+ # Single trailing ':' is invalid.
+ } elsif ($ip =~ /:$/) {
+ return;
+ }
+
+ foreach my $chunk (@chunks) {
+ return unless $chunk =~ /^[0-9a-f]{0,4}$/i;
+ $empty++ if $chunk eq '';
+ }
+ # More than one :: block is bad, but if it starts with :: it will
+ # look like two, so we need an exception.
+ if ($empty == 2 && $ip =~ /^::/) {
+ # This is ok
+ } elsif ($empty > 1) {
+ return;
+ }
+
+ push(@chunks, $ipv4) if $ipv4;
+ # Need 8 chunks, or we need an empty section that could be filled
+ # to represent the missing '0' sections.
+ return unless (@chunks == $expected_chunks || @chunks < $expected_chunks && $empty);
+
+ my $ipv6 = join(':', @chunks);
+ # The IP address is valid and can now be detainted.
+ trick_taint($ipv6);
+
+ # Need to handle the exception of trailing :: being valid.
+ return "${ipv6}::" if $ip =~ /::$/;
+ return $ipv6;
}
sub use_attachbase {
@@ -292,38 +389,41 @@
&& $attachbase ne Bugzilla->params->{'sslbase'}) ? 1 : 0;
}
-sub lsearch {
- my ($list,$item) = (@_);
- my $count = 0;
- foreach my $i (@$list) {
- if ($i eq $item) {
- return $count;
- }
- $count++;
- }
- return -1;
-}
-
sub diff_arrays {
- my ($old_ref, $new_ref) = @_;
+ my ($old_ref, $new_ref, $attrib) = @_;
+ $attrib ||= 'name';
+ my (%counts, %pos);
+ # We are going to alter the old array.
my @old = @$old_ref;
- my @new = @$new_ref;
+ my $i = 0;
- # For each pair of (old, new) entries:
- # If they're equal, set them to empty. When done, @old contains entries
- # that were removed; @new contains ones that got added.
- foreach my $oldv (@old) {
- foreach my $newv (@new) {
- next if ($newv eq '');
- if ($oldv eq $newv) {
- $newv = $oldv = '';
- }
+ # $counts{foo}-- means old, $counts{foo}++ means new.
+ # If $counts{foo} becomes positive, then we are adding new items,
+ # else we simply cancel one old existing item. Remaining items
+ # in the old list have been removed.
+ foreach (@old) {
+ next unless defined $_;
+ my $value = blessed($_) ? $_->$attrib : $_;
+ $counts{$value}--;
+ push @{$pos{$value}}, $i++;
+ }
+ my @added;
+ foreach (@$new_ref) {
+ next unless defined $_;
+ my $value = blessed($_) ? $_->$attrib : $_;
+ if (++$counts{$value} > 0) {
+ # Ignore empty strings, but objects having an empty string
+ # as attribute are fine.
+ push(@added, $_) unless ($value eq '' && !blessed($_));
+ }
+ else {
+ my $old_pos = shift @{$pos{$value}};
+ $old[$old_pos] = undef;
}
}
-
- my @removed = grep { $_ ne '' } @old;
- my @added = grep { $_ ne '' } @new;
+ # Ignore canceled items as well as empty strings.
+ my @removed = grep { defined $_ && $_ ne '' } @old;
return (\@removed, \@added);
}
@@ -336,30 +436,15 @@
return $str;
}
-sub diff_strings {
- my ($oldstr, $newstr) = @_;
-
- # Split the old and new strings into arrays containing their values.
- $oldstr =~ s/[\s,]+/ /g;
- $newstr =~ s/[\s,]+/ /g;
- my @old = split(" ", $oldstr);
- my @new = split(" ", $newstr);
-
- my ($rem, $add) = diff_arrays(\@old, \@new);
-
- my $removed = join (", ", @$rem);
- my $added = join (", ", @$add);
-
- return ($removed, $added);
-}
-
sub wrap_comment {
my ($comment, $cols) = @_;
my $wrappedcomment = "";
# Use 'local', as recommended by Text::Wrap's perldoc.
+#if WEBKIT_CHANGES
local $Text::Wrap::columns = $cols || COMMENT_COLS_WRAP;
# Make words that are longer than COMMENT_COLS_WRAP not wrap.
+#endif // WEBKIT_CHANGES
local $Text::Wrap::huge = 'overflow';
# Don't mess with tabs.
local $Text::Wrap::unexpand = 0;
@@ -414,56 +499,80 @@
}
sub format_time {
- my ($date, $format) = @_;
+ my ($date, $format, $timezone) = @_;
- # If $format is undefined, try to guess the correct date format.
- my $show_timezone;
- if (!defined($format)) {
- if ($date =~ m/^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/) {
+ # If $format is not set, try to guess the correct date format.
+ if (!$format) {
+ if (!ref $date
+ && $date =~ /^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/)
+ {
my $sec = $7;
if (defined $sec) {
- $format = "%Y-%m-%d %T";
+ $format = "%Y-%m-%d %T %Z";
} else {
- $format = "%Y-%m-%d %R";
+ $format = "%Y-%m-%d %R %Z";
}
} else {
- # Default date format. See Date::Format for other formats available.
- $format = "%Y-%m-%d %R";
+ # Default date format. See DateTime for other formats available.
+ $format = "%Y-%m-%d %R %Z";
}
- # By default, we want the timezone to be displayed.
- $show_timezone = 1;
- }
- else {
- # Search for %Z or %z, meaning we want the timezone to be displayed.
- # Till bug 182238 gets fixed, we assume Bugzilla->params->{'timezone'}
- # is used.
- $show_timezone = ($format =~ s/\s?%Z$//i);
}
- # str2time($date) is undefined if $date has an invalid date format.
- my $time = str2time($date);
-
- if (defined $time) {
- $date = time2str($format, $time);
- $date .= " " . Bugzilla->params->{'timezone'} if $show_timezone;
- }
- else {
- # Don't let invalid (time) strings to be passed to templates!
- $date = '';
- }
+ my $dt = ref $date ? $date : datetime_from($date, $timezone);
+ $date = defined $dt ? $dt->strftime($format) : '';
return trim($date);
}
-sub format_time_decimal {
- my ($time) = (@_);
+sub datetime_from {
+ my ($date, $timezone) = @_;
- my $newtime = sprintf("%.2f", $time);
+ # In the database, this is the "0" date.
+ return undef if $date =~ /^0000/;
- if ($newtime =~ /0\Z/) {
- $newtime = sprintf("%.1f", $time);
+ # strptime($date) returns an empty array if $date has an invalid
+ # date format.
+ my @time = strptime($date);
+
+ unless (scalar @time) {
+ # If an unknown timezone is passed (such as MSK, for Moskow),
+ # strptime() is unable to parse the date. We try again, but we first
+ # remove the timezone.
+ $date =~ s/\s+\S+$//;
+ @time = strptime($date);
}
- return $newtime;
+ return undef if !@time;
+
+ # strptime() counts years from 1900, and months from 0 (January).
+ # We have to fix both values.
+ my %args = (
+ year => $time[5] + 1900,
+ month => $time[4] + 1,
+ day => $time[3],
+ hour => $time[2],
+ minute => $time[1],
+ # DateTime doesn't like fractional seconds.
+ # Also, sometimes seconds are undef.
+ second => defined($time[0]) ? int($time[0]) : undef,
+ # If a timezone was specified, use it. Otherwise, use the
+ # local timezone.
+ time_zone => Bugzilla->local_timezone->offset_as_string($time[6])
+ || Bugzilla->local_timezone,
+ );
+
+ # If something wasn't specified in the date, it's best to just not
+ # pass it to DateTime at all. (This is important for doing datetime_from
+ # on the deadline field, which is usually just a date with no time.)
+ foreach my $arg (keys %args) {
+ delete $args{$arg} if !defined $args{$arg};
+ }
+
+ my $dt = new DateTime(\%args);
+
+ # Now display the date using the given timezone,
+ # or the user's timezone if none is given.
+ $dt->set_time_zone($timezone || Bugzilla->user->timezone);
+ return $dt;
}
sub file_mod_time {
@@ -475,33 +584,56 @@
}
sub bz_crypt {
- my ($password) = @_;
+ my ($password, $salt) = @_;
- # The list of characters that can appear in a salt. Salts and hashes
- # are both encoded as a sequence of characters from a set containing
- # 64 characters, each one of which represents 6 bits of the salt/hash.
- # The encoding is similar to BASE64, the difference being that the
- # BASE64 plus sign (+) is replaced with a forward slash (/).
- my @saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/');
-
- # Generate the salt. We use an 8 character (48 bit) salt for maximum
- # security on systems whose crypt uses MD5. Systems with older
- # versions of crypt will just use the first two characters of the salt.
- my $salt = '';
- for ( my $i=0 ; $i < 8 ; ++$i ) {
- $salt .= $saltchars[rand(64)];
+ my $algorithm;
+ if (!defined $salt) {
+ # If you don't use a salt, then people can create tables of
+ # hashes that map to particular passwords, and then break your
+ # hashing very easily if they have a large-enough table of common
+ # (or even uncommon) passwords. So we generate a unique salt for
+ # each password in the database, and then just prepend it to
+ # the hash.
+ $salt = generate_random_password(PASSWORD_SALT_LENGTH);
+ $algorithm = PASSWORD_DIGEST_ALGORITHM;
}
- # Wide characters cause crypt to die
- if (Bugzilla->params->{'utf8'}) {
- utf8::encode($password) if utf8::is_utf8($password);
+ # We append the algorithm used to the string. This is good because then
+ # we can change the algorithm being used, in the future, without
+ # disrupting the validation of existing passwords. Also, this tells
+ # us if a password is using the old "crypt" method of hashing passwords,
+ # because the algorithm will be missing from the string.
+ if ($salt =~ /{([^}]+)}$/) {
+ $algorithm = $1;
}
+
+ my $crypted_password;
+ if (!$algorithm) {
+ # Wide characters cause crypt to die
+ if (Bugzilla->params->{'utf8'}) {
+ utf8::encode($password) if utf8::is_utf8($password);
+ }
- # Crypt the password.
- my $cryptedpassword = crypt($password, $salt);
+ # Crypt the password.
+ $crypted_password = crypt($password, $salt);
+
+ # HACK: Perl has bug where returned crypted password is considered
+ # tainted. See http://rt.perl.org/rt3/Public/Bug/Display.html?id=59998
+ unless(tainted($password) || tainted($salt)) {
+ trick_taint($crypted_password);
+ }
+ }
+ else {
+ my $hasher = Digest->new($algorithm);
+ # We only want to use the first characters of the salt, no
+ # matter how long of a salt we may have been passed.
+ $salt = substr($salt, 0, PASSWORD_SALT_LENGTH);
+ $hasher->add($password, $salt);
+ $crypted_password = $salt . $hasher->b64digest . "{$algorithm}";
+ }
# Return the crypted password.
- return $cryptedpassword;
+ return $crypted_password;
}
# If you want to understand the security of strings generated by this
@@ -512,54 +644,13 @@
# strength of the string in bits.
sub generate_random_password {
my $size = shift || 10; # default to 10 chars if nothing specified
- my $rand;
- if (eval { require Math::Random::Secure; 1; }) {
- $rand = \&Math::Random::Secure::irand;
- }
- else {
- # For details on why this block works the way it does, see bug 619594.
- # (Note that we don't do this if Math::Random::Secure is installed,
- # because we don't need to.)
- my $counter = 0;
- $rand = sub {
- # If we regenerate the seed every 5 characters, our seed is roughly
- # as strong (in terms of bit size) as our randomly-generated
- # string itself.
- _do_srand() if ($counter % 5) == 0;
- $counter++;
- return int(rand $_[0]);
- };
- }
- return join("", map{ ('0'..'9','a'..'z','A'..'Z')[$rand->(62)] }
- (1..$size));
-}
-
-sub _do_srand {
- # On Windows, calling srand over and over in the same process produces
- # very bad results. We need a stronger seed.
- if (ON_WINDOWS) {
- require Win32;
- # GuidGen generates random data via Windows's CryptGenRandom
- # interface, which is documented as being cryptographically secure.
- my $guid = Win32::GuidGen();
- # GUIDs look like:
- # {09531CF1-D0C7-4860-840C-1C8C8735E2AD}
- $guid =~ s/[-{}]+//g;
- # Get a 32-bit integer using the first eight hex digits.
- my $seed = hex(substr($guid, 0, 8));
- srand($seed);
- return;
- }
-
- # On *nix-like platforms, this uses /dev/urandom, so the seed changes
- # enough on every invocation.
- srand();
+ return join("", map{ ('0'..'9','a'..'z','A'..'Z')[irand 62] } (1..$size));
}
sub validate_email_syntax {
my ($addr) = @_;
my $match = Bugzilla->params->{'emailregexp'};
- my $ret = ($addr =~ /$match/ && $addr !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/);
+ my $ret = ($addr =~ /$match/ && $addr !~ /[\\\(\)<>&,;:"\[\] \t\r\n\P{ASCII}]/);
if ($ret) {
# We assume these checks to suffice to consider the address untainted.
trick_taint($_[0]);
@@ -604,44 +695,64 @@
}
sub clean_text {
- my ($dtext) = shift;
- $dtext =~ s/[\x00-\x1F\x7F]+/ /g; # change control characters into a space
+ my $dtext = shift;
+ if ($dtext) {
+ # change control characters into a space
+ $dtext =~ s/[\x00-\x1F\x7F]+/ /g;
+ }
return trim($dtext);
}
+sub on_main_db (&) {
+ my $code = shift;
+ my $original_dbh = Bugzilla->dbh;
+ Bugzilla->request_cache->{dbh} = Bugzilla->dbh_main;
+ $code->();
+ Bugzilla->request_cache->{dbh} = $original_dbh;
+}
+
sub get_text {
my ($name, $vars) = @_;
my $template = Bugzilla->template_inner;
$vars ||= {};
$vars->{'message'} = $name;
my $message;
- $template->process('global/message.txt.tmpl', $vars, \$message)
- || ThrowTemplateError($template->error());
+ if (!$template->process('global/message.txt.tmpl', $vars, \$message)) {
+ require Bugzilla::Error;
+ Bugzilla::Error::ThrowTemplateError($template->error());
+ }
# Remove the indenting that exists in messages.html.tmpl.
$message =~ s/^ //gm;
return $message;
}
-
-sub get_netaddr {
- my $ipaddr = shift;
-
- # Check for a valid IPv4 addr which we know how to parse
- if (!$ipaddr || $ipaddr !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
- return undef;
+sub template_var {
+ my $name = shift;
+ my $cache = Bugzilla->request_cache->{util_template_var} ||= {};
+ my $template = Bugzilla->template_inner;
+ my $lang = $template->context->{bz_language};
+ return $cache->{$lang}->{$name} if defined $cache->{$lang};
+ my %vars;
+ # Note: If we suddenly start needing a lot of template_var variables,
+ # they should move into their own template, not field-descs.
+ my $result = $template->process('global/field-descs.none.tmpl',
+ { vars => \%vars, in_template_var => 1 });
+ # Bugzilla::Error can't be "use"d in Bugzilla::Util.
+ if (!$result) {
+ require Bugzilla::Error;
+ Bugzilla::Error::ThrowTemplateError($template->error);
}
+ $cache->{$lang} = \%vars;
+ return $vars{$name};
+}
- my $addr = unpack("N", pack("CCCC", split(/\./, $ipaddr)));
-
- my $maskbits = Bugzilla->params->{'loginnetmask'};
-
- # Make Bugzilla ignore the IP address if loginnetmask is set to 0
- return "0.0.0.0" if ($maskbits == 0);
-
- $addr >>= (32-$maskbits);
-
- $addr <<= (32-$maskbits);
- return join(".", unpack("CCCC", pack("N", $addr)));
+sub display_value {
+ my ($field, $value) = @_;
+ my $value_descs = template_var('value_descs');
+ if (defined $value_descs->{$field}->{$value}) {
+ return $value_descs->{$field}->{$value};
+ }
+ return $value;
}
sub disable_utf8 {
@@ -650,6 +761,63 @@
}
}
+use constant UTF8_ACCIDENTAL => qw(shiftjis big5-eten euc-kr euc-jp);
+
+sub detect_encoding {
+ my $data = shift;
+
+ if (!Bugzilla->feature('detect_charset')) {
+ require Bugzilla::Error;
+ Bugzilla::Error::ThrowCodeError('feature_disabled',
+ { feature => 'detect_charset' });
+ }
+
+ require Encode::Detect::Detector;
+ import Encode::Detect::Detector 'detect';
+
+ my $encoding = detect($data);
+ $encoding = resolve_alias($encoding) if $encoding;
+
+ # Encode::Detect is bad at detecting certain charsets, but Encode::Guess
+ # is better at them. Here's the details:
+
+ # shiftjis, big5-eten, euc-kr, and euc-jp: (Encode::Detect
+ # tends to accidentally mis-detect UTF-8 strings as being
+ # these encodings.)
+ if ($encoding && grep($_ eq $encoding, UTF8_ACCIDENTAL)) {
+ $encoding = undef;
+ my $decoder = guess_encoding($data, UTF8_ACCIDENTAL);
+ $encoding = $decoder->name if ref $decoder;
+ }
+
+ # Encode::Detect sometimes mis-detects various ISO encodings as iso-8859-8,
+ # but Encode::Guess can usually tell which one it is.
+ if ($encoding && $encoding eq 'iso-8859-8') {
+ my $decoded_as = _guess_iso($data, 'iso-8859-8',
+ # These are ordered this way because it gives the most
+ # accurate results.
+ qw(iso-8859-7 iso-8859-2));
+ $encoding = $decoded_as if $decoded_as;
+ }
+
+ return $encoding;
+}
+
+# A helper for detect_encoding.
+sub _guess_iso {
+ my ($data, $versus, @isos) = (shift, shift, shift);
+
+ my $encoding;
+ foreach my $iso (@isos) {
+ my $decoder = guess_encoding($data, ($iso, $versus));
+ if (ref $decoder) {
+ $encoding = $decoder->name if ref $decoder;
+ last;
+ }
+ }
+ return $encoding;
+}
+
1;
__END__
@@ -663,7 +831,6 @@
use Bugzilla::Util;
# Functions for dealing with variable tainting
- $rv = is_tainted($var);
trick_taint($var);
detaint_natural($var);
detaint_signed($var);
@@ -672,28 +839,22 @@
html_quote($var);
url_quote($var);
xml_quote($var);
-
- # Functions for decoding
- $rv = url_decode($var);
+ email_filter($var);
# Functions that tell you about your environment
my $is_cgi = i_am_cgi();
- my $net_addr = get_netaddr($ip_addr);
my $urlbase = correct_urlbase();
- # Functions for searching
- $loc = lsearch(\@arr, $val);
-
# Data manipulation
($removed, $added) = diff_arrays(\@old, \@new);
# Functions for manipulating strings
$val = trim(" abc ");
- ($removed, $added) = diff_strings($old, $new);
$wrapped = wrap_comment($comment);
# Functions for formatting time
format_time($time);
+ datetime_from($time, $timezone);
# Functions for dealing with files
$time = file_mod_time($filename);
@@ -706,6 +867,11 @@
validate_email_syntax($email);
validate_date($date);
+ # DB-related functions
+ on_main_db {
+ ... code here ...
+ };
+
=head1 DESCRIPTION
This package contains various utility functions which do not belong anywhere
@@ -727,10 +893,6 @@
=over 4
-=item C<is_tainted>
-
-Determines whether a particular variable is tainted
-
=item C<trick_taint($val)>
Tricks perl into untainting a particular variable.
@@ -765,8 +927,9 @@
=item C<html_quote($val)>
-Returns a value quoted for use in HTML, with &, E<lt>, E<gt>, and E<34> being
-replaced with their appropriate HTML entities.
+Returns a value quoted for use in HTML, with &, E<lt>, E<gt>, E<34> and @ being
+replaced with their appropriate HTML entities. Also, Unicode BiDi controls are
+deleted.
=item C<html_light_quote($val)>
@@ -781,7 +944,7 @@
=item C<css_class_quote($val)>
Quotes characters so that they may be used as CSS class names. Spaces
-are replaced by underscores.
+and forward slashes are replaced by underscores.
=item C<xml_quote($val)>
@@ -789,9 +952,11 @@
is kept separate from html_quote partly for compatibility with previous code
(for ') and partly for future handling of non-ASCII characters.
-=item C<url_decode($val)>
+=item C<email_filter>
-Converts the %xx encoding from the given URL back to its original form.
+Removes the hostname from email addresses in the string, if the user
+currently viewing Bugzilla is logged out. If the user is logged-in,
+this filter just returns the input string.
=back
@@ -807,17 +972,21 @@
server. For example, it would return false if the caller is running
in a command-line script.
-=item C<get_netaddr($ipaddr)>
-
-Given an IP address, this returns the associated network address, using
-C<Bugzilla->params->{'loginnetmask'}> as the netmask. This can be used
-to obtain data in order to restrict weak authentication methods (such as
-cookies) to only some addresses.
-
=item C<correct_urlbase()>
Returns either the C<sslbase> or C<urlbase> parameter, depending on the
-current setting for the C<ssl> parameter.
+current setting for the C<ssl_redirect> parameter.
+
+=item C<remote_ip()>
+
+Returns the IP address of the remote client. If Bugzilla is behind
+a trusted proxy, it will get the remote IP address by looking at the
+X-Forwarded-For header.
+
+=item C<validate_ip($ip)>
+
+Returns the sanitized IP address if it is a valid IPv4 or IPv6 address,
+else returns undef.
=item C<use_attachbase()>
@@ -826,21 +995,6 @@
=back
-=head2 Searching
-
-Functions for searching within a set of values.
-
-=over 4
-
-=item C<lsearch($list, $item)>
-
-Returns the position of C<$item> in C<$list>. C<$list> must be a list
-reference.
-
-If the item is not in the list, returns -1.
-
-=back
-
=head2 Data Manipulation
=over 4
@@ -868,14 +1022,6 @@
Removes any leading or trailing whitespace from a string. This routine does not
modify the existing string.
-=item C<diff_strings($oldstr, $newstr)>
-
-Takes two strings containing a list of comma- or space-separated items
-and returns what items were removed from or added to the new one,
-compared to the old one. Returns a list, where the first entry is a scalar
-containing removed items, and the second entry is a scalar containing added
-items.
-
=item C<wrap_hard($string, $size)>
Wraps a string, so that a line is I<never> longer than C<$size>.
@@ -906,6 +1052,12 @@
Disable utf8 on STDOUT (and display raw data instead).
+=item C<detect_encoding($str)>
+
+Guesses what encoding a given data is encoded in, returning the canonical name
+of the detected encoding (which may be different from the MIME charset
+specification).
+
=item C<clean_text($str)>
Returns the parameter "cleaned" by exchanging non-printable characters with spaces.
Specifically characters (ASCII 0 through 31) and (ASCII 127) will become ASCII 32 (Space).
@@ -938,6 +1090,14 @@
=back
+
+=item C<template_var>
+
+This is a method of getting the value of a variable from a template in
+Perl code. The available variables are in the C<global/field-descs.none.tmpl>
+template. Just pass in the name of the variable that you want the value of.
+
+
=back
=head2 Formatting Time
@@ -946,20 +1106,22 @@
=item C<format_time($time)>
-Takes a time, converts it to the desired format and appends the timezone
-as defined in editparams.cgi, if desired. This routine will be expanded
-in the future to adjust for user preferences regarding what timezone to
-display times in.
+Takes a time and converts it to the desired format and timezone.
+If no format is given, the routine guesses the correct one and returns
+an empty array if it cannot. If no timezone is given, the user's timezone
+is used, as defined in his preferences.
This routine is mainly called from templates to filter dates, see
-"FILTER time" in Templates.pm. In this case, $format is undefined and
-the routine has to "guess" the date format that was passed to $dbh->sql_date_format().
+"FILTER time" in L<Bugzilla::Template>.
+=item C<datetime_from($time, $timezone)>
-=item C<format_time_decimal($time)>
+Returns a DateTime object given a date string. If the string is not in some
+valid date format that C<strptime> understands, we return C<undef>.
-Returns a number with 2 digit precision, unless the last digit is a 0. Then it
-returns only 1 digit precision.
+You can optionally specify a timezone for the returned date. If not
+specified, defaults to the currently-logged-in user's timezone, or
+the Bugzilla server's local timezone if there isn't a logged-in user.
=back
@@ -979,12 +1141,14 @@
=over 4
-=item C<bz_crypt($password)>
+=item C<bz_crypt($password, $salt)>
-Takes a string and returns a C<crypt>ed value for it, using a random salt.
+Takes a string and returns a hashed (encrypted) value for it, using a
+random salt. An optional salt string may also be passed in.
-Please always use this function instead of the built-in perl "crypt"
-when initially encrypting a password.
+Please always use this function instead of the built-in perl C<crypt>
+function, when checking or setting a password. Bugzilla does not use
+C<crypt>.
=begin undocumented
@@ -1020,3 +1184,20 @@
the check is successful, else returns 0.
=back
+
+=head2 Database
+
+=over
+
+=item C<on_main_db>
+
+Runs a block of code always on the main DB. Useful for when you're inside
+a subroutine and need to do some writes to the database, but don't know
+if Bugzilla is currently using the shadowdb or not. Used like:
+
+ on_main_db {
+ my $dbh = Bugzilla->dbh;
+ $dbh->do("INSERT ...");
+ }
+
+=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/Version.pm b/Websites/bugs.webkit.org/Bugzilla/Version.pm
index a2ef6b0..7f53add 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Version.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/Version.pm
@@ -14,6 +14,7 @@
#
# Contributor(s): Tiago R. Mello <timello@async.com.br>
# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
use strict;
@@ -25,6 +26,8 @@
use Bugzilla::Util;
use Bugzilla::Error;
+use Scalar::Util qw(blessed);
+
################################
##### Initialization #####
################################
@@ -32,17 +35,40 @@
use constant DEFAULT_VERSION => 'unspecified';
use constant DB_TABLE => 'versions';
+use constant NAME_FIELD => 'value';
+# This is "id" because it has to be filled in and id is probably the fastest.
+# We do a custom sort in new_from_list below.
+use constant LIST_ORDER => 'id';
use constant DB_COLUMNS => qw(
id
value
product_id
+ isactive
);
-use constant NAME_FIELD => 'value';
-# This is "id" because it has to be filled in and id is probably the fastest.
-# We do a custom sort in new_from_list below.
-use constant LIST_ORDER => 'id';
+use constant REQUIRED_FIELD_MAP => {
+ product_id => 'product',
+};
+
+use constant UPDATE_COLUMNS => qw(
+ value
+ isactive
+);
+
+use constant VALIDATORS => {
+ product => \&_check_product,
+ value => \&_check_value,
+ isactive => \&Bugzilla::Object::check_boolean,
+};
+
+use constant VALIDATOR_DEPENDENCIES => {
+ value => ['product'],
+};
+
+################################
+# Methods
+################################
sub new {
my $class = shift;
@@ -79,6 +105,14 @@
return [sort { vers_cmp(lc($a->name), lc($b->name)) } @$list];
}
+sub run_create_validators {
+ my $class = shift;
+ my $params = $class->SUPER::run_create_validators(@_);
+ my $product = delete $params->{product};
+ $params->{product_id} = $product->id;
+ return $params;
+}
+
sub bug_count {
my $self = shift;
my $dbh = Bugzilla->dbh;
@@ -92,6 +126,19 @@
return $self->{'bug_count'};
}
+sub update {
+ my $self = shift;
+ my ($changes, $old_self) = $self->SUPER::update(@_);
+
+ if (exists $changes->{value}) {
+ my $dbh = Bugzilla->dbh;
+ $dbh->do('UPDATE bugs SET version = ?
+ WHERE version = ? AND product_id = ?',
+ undef, ($self->name, $old_self->name, $self->product_id));
+ }
+ return $changes;
+}
+
sub remove_from_db {
my $self = shift;
my $dbh = Bugzilla->dbh;
@@ -101,78 +148,53 @@
if ($self->bug_count) {
ThrowUserError("version_has_bugs", { nb => $self->bug_count });
}
-
- $dbh->do(q{DELETE FROM versions WHERE product_id = ? AND value = ?},
- undef, ($self->product_id, $self->name));
-}
-
-sub update {
- my $self = shift;
- my ($name, $product) = @_;
- my $dbh = Bugzilla->dbh;
-
- $name || ThrowUserError('version_not_specified');
-
- # Remove unprintable characters
- $name = clean_text($name);
-
- return 0 if ($name eq $self->name);
- my $version = new Bugzilla::Version({ product => $product, name => $name });
-
- if ($version) {
- ThrowUserError('version_already_exists',
- {'name' => $version->name,
- 'product' => $product->name});
- }
-
- trick_taint($name);
- $dbh->do("UPDATE bugs SET version = ?
- WHERE version = ? AND product_id = ?", undef,
- ($name, $self->name, $self->product_id));
-
- $dbh->do("UPDATE versions SET value = ?
- WHERE product_id = ? AND value = ?", undef,
- ($name, $self->product_id, $self->name));
-
- $self->{'value'} = $name;
-
- return 1;
+ $self->SUPER::remove_from_db();
}
###############################
##### Accessors ####
###############################
-sub name { return $_[0]->{'value'}; }
sub product_id { return $_[0]->{'product_id'}; }
+sub is_active { return $_[0]->{'isactive'}; }
-###############################
-##### Subroutines ###
-###############################
+sub product {
+ my $self = shift;
-sub create {
- my ($name, $product) = @_;
- my $dbh = Bugzilla->dbh;
+ require Bugzilla::Product;
+ $self->{'product'} ||= new Bugzilla::Product($self->product_id);
+ return $self->{'product'};
+}
- # Cleanups and validity checks
+################################
+# Validators
+################################
+
+sub set_name { $_[0]->set('value', $_[1]); }
+sub set_is_active { $_[0]->set('isactive', $_[1]); }
+
+sub _check_value {
+ my ($invocant, $name, undef, $params) = @_;
+ my $product = blessed($invocant) ? $invocant->product : $params->{product};
+
+ $name = trim($name);
$name || ThrowUserError('version_blank_name');
-
# Remove unprintable characters
$name = clean_text($name);
my $version = new Bugzilla::Version({ product => $product, name => $name });
- if ($version) {
- ThrowUserError('version_already_exists',
- {'name' => $version->name,
- 'product' => $product->name});
+ if ($version && (!ref $invocant || $version->id != $invocant->id)) {
+ ThrowUserError('version_already_exists', { name => $version->name,
+ product => $product->name });
}
+ return $name;
+}
- # Add the new version
- trick_taint($name);
- $dbh->do(q{INSERT INTO versions (value, product_id)
- VALUES (?, ?)}, undef, ($name, $product->id));
-
- return new Bugzilla::Version($dbh->bz_last_key('versions', 'id'));
+sub _check_product {
+ my ($invocant, $product) = @_;
+ $product || ThrowCodeError('param_required',
+ { function => "$invocant->create", param => 'product' });
+ return Bugzilla->user->check_can_admin_product($product->name);
}
1;
@@ -187,37 +209,33 @@
use Bugzilla::Version;
- my $version = new Bugzilla::Version(1, 'version_value');
+ my $version = new Bugzilla::Version({ name => $name, product => $product });
+ my $value = $version->name;
my $product_id = $version->product_id;
- my $value = $version->value;
+ my $product = $version->product;
+
+ my $version = Bugzilla::Version->create(
+ { value => $name, product => $product });
+
+ $version->set_name($new_name);
+ $version->update();
$version->remove_from_db;
- my $updated = $version->update($version_name, $product);
-
- my $version = $hash_ref->{'version_value'};
-
- my $version = Bugzilla::Version::create($version_name, $product);
-
=head1 DESCRIPTION
-Version.pm represents a Product Version object.
+Version.pm represents a Product Version object. It is an implementation
+of L<Bugzilla::Object>, and thus provides all methods that
+L<Bugzilla::Object> provides.
+
+The methods that are specific to C<Bugzilla::Version> are listed
+below.
=head1 METHODS
=over
-=item C<new($product_id, $value)>
-
- Description: The constructor is used to load an existing version
- by passing a product id and a version value.
-
- Params: $product_id - Integer with a product id.
- $value - String with a version value.
-
- Returns: A Bugzilla::Version object.
-
=item C<bug_count()>
Description: Returns the total of bugs that belong to the version.
@@ -226,38 +244,6 @@
Returns: Integer with the number of bugs.
-=item C<remove_from_db()>
-
- Description: Removes the version from the database.
-
- Params: none.
-
- Retruns: none.
-
-=item C<update($name, $product)>
-
- Description: Update the value of the version.
-
- Params: $name - String with the new version value.
- $product - Bugzilla::Product object the version belongs to.
-
- Returns: An integer - 1 if the version has been updated, else 0.
-
-=back
-
-=head1 SUBROUTINES
-
-=over
-
-=item C<create($version_name, $product)>
-
- Description: Create a new version for the given product.
-
- Params: $version_name - String with a version value.
- $product - A Bugzilla::Product object.
-
- Returns: A Bugzilla::Version object.
-
=back
=cut
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService.pm b/Websites/bugs.webkit.org/Bugzilla/WebService.pm
index 0e42924..1667076 100644
--- a/Websites/bugs.webkit.org/Bugzilla/WebService.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService.pm
@@ -15,130 +15,31 @@
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
+# This is the base class for $self in WebService method calls. For the
+# actual RPC server, see Bugzilla::WebService::Server and its subclasses.
package Bugzilla::WebService;
-
use strict;
-use Bugzilla::WebService::Constants;
-use Bugzilla::Util;
-use Date::Parse;
+use Bugzilla::WebService::Server;
-sub fail_unimplemented {
- my $this = shift;
-
- die SOAP::Fault
- ->faultcode(ERROR_UNIMPLEMENTED)
- ->faultstring('Service Unimplemented');
-}
-
-sub datetime_format {
- my ($self, $date_string) = @_;
-
- my $time = str2time($date_string);
- my ($sec, $min, $hour, $mday, $mon, $year) = localtime $time;
- # This format string was stolen from SOAP::Utils->format_datetime,
- # which doesn't work but which has almost the right format string.
- my $iso_datetime = sprintf('%d%02d%02dT%02d:%02d:%02d',
- $year + 1900, $mon + 1, $mday, $hour, $min, $sec);
- return $iso_datetime;
-}
-
-sub handle_login {
- my ($classes, $action, $uri, $method) = @_;
-
- my $class = $classes->{$uri};
- eval "require $class";
-
- return if $class->login_exempt($method);
- Bugzilla->login();
-
- # Even though we check for the need to redirect in
- # Bugzilla->login() we check here again since Bugzilla->login()
- # does not know what the current XMLRPC method is. Therefore
- # ssl_require_redirect in Bugzilla->login() will have returned
- # false if system was configured to redirect for authenticated
- # sessions and the user was not yet logged in.
- # So here we pass in the method name to ssl_require_redirect so
- # it can then check for the extra case where the method equals
- # User.login, which we would then need to redirect if not
- # over a secure connection.
- my $full_method = $uri . "." . $method;
- Bugzilla->cgi->require_https(Bugzilla->params->{'sslbase'})
- if ssl_require_redirect($full_method);
-
- return;
-}
+# Used by the JSON-RPC server to convert incoming date fields apprpriately.
+use constant DATE_FIELDS => {};
+# Used by the JSON-RPC server to convert incoming base64 fields appropriately.
+use constant BASE64_FIELDS => {};
# For some methods, we shouldn't call Bugzilla->login before we call them
use constant LOGIN_EXEMPT => { };
+# Used to allow methods to be called in the JSON-RPC WebService via GET.
+# Methods that can modify data MUST not be listed here.
+use constant READ_ONLY => ();
+
sub login_exempt {
my ($class, $method) = @_;
-
return $class->LOGIN_EXEMPT->{$method};
}
1;
-package Bugzilla::WebService::XMLRPC::Transport::HTTP::CGI;
-use strict;
-eval { require XMLRPC::Transport::HTTP; };
-our @ISA = qw(XMLRPC::Transport::HTTP::CGI);
-
-sub initialize {
- my $self = shift;
- my %retval = $self->SUPER::initialize(@_);
- $retval{'serializer'} = Bugzilla::WebService::XMLRPC::Serializer->new;
- return %retval;
-}
-
-sub make_response {
- my $self = shift;
-
- $self->SUPER::make_response(@_);
-
- # XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
- # its cookies in Bugzilla::CGI, so we need to copy them over.
- foreach (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
- $self->response->headers->push_header('Set-Cookie', $_);
- }
-}
-
-1;
-
-# This package exists to fix a UTF-8 bug in SOAP::Lite.
-# See http://rt.cpan.org/Public/Bug/Display.html?id=32952.
-package Bugzilla::WebService::XMLRPC::Serializer;
-use strict;
-# We can't use "use base" because XMLRPC::Serializer doesn't return
-# a true value.
-eval { require XMLRPC::Lite; };
-our @ISA = qw(XMLRPC::Serializer);
-
-sub new {
- my $class = shift;
- my $self = $class->SUPER::new(@_);
- # This fixes UTF-8.
- $self->{'_typelookup'}->{'base64'} =
- [10, sub { !utf8::is_utf8($_[0]) && $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/},
- 'as_base64'];
- # This makes arrays work right even though we're a subclass.
- # (See http://rt.cpan.org//Ticket/Display.html?id=34514)
- $self->{'_encodingStyle'} = '';
- return $self;
-}
-
-sub as_string {
- my $self = shift;
- my ($value) = @_;
- # Something weird happens with XML::Parser when we have upper-ASCII
- # characters encoded as UTF-8, and this fixes it.
- utf8::encode($value) if utf8::is_utf8($value)
- && $value =~ /^[\x00-\xff]+$/;
- return $self->SUPER::as_string($value);
-}
-
-1;
-
__END__
=head1 NAME
@@ -150,87 +51,131 @@
This is the standard API for external programs that want to interact
with Bugzilla. It provides various methods in various modules.
-Currently the only method of accessing the API is via XML-RPC. The XML-RPC
-standard is described here: L<http://www.xmlrpc.com/spec>
-
-The endpoint for Bugzilla WebServices is the C<xmlrpc.cgi> script in
-your Bugzilla installation. For example, if your Bugzilla is at
-C<bugzilla.yourdomain.com>, then your XML-RPC client would access the
-API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi>
+You can interact with this API via
+L<XML-RPC|Bugzilla::WebService::Server::XMLRPC> or
+L<JSON-RPC|Bugzilla::WebService::Server::JSONRPC>.
=head1 CALLING METHODS
-Methods are called in the normal XML-RPC fashion. Bugzilla does not currently
-implement any extensions to the standard method of XML-RPC method calling.
-
Methods are grouped into "packages", like C<Bug> for
L<Bugzilla::WebService::Bug>. So, for example,
-L<Bugzilla::WebService::Bug/get>, is called as C<Bug.get> in XML-RPC.
+L<Bugzilla::WebService::Bug/get>, is called as C<Bug.get>.
=head1 PARAMETERS
-In addition to the standard parameter types like C<int>, C<string>, etc.,
-XML-RPC has two data structures, a C<< <struct> >> and an C<< <array> >>.
+The Bugzilla API takes the following various types of parameters:
-=head2 Structs
+=over
-In Perl, we call a C<< <struct> >> a "hash" or a "hashref". You may see
-us refer to it that way in the API documentation.
+=item C<int>
-In example code, you will see the characters C<{> and C<}> used to represent
-the beginning and end of structs.
+Integer. May be null.
-For example, here's a struct in XML-RPC:
+=item C<double>
- <struct>
- <member>
- <name>fruit</name>
- <value><string>oranges</string></value>
- </member>
- <member>
- <name>vegetable</name>
- <value><string>lettuce</string></value>
- </member>
- </struct>
+A floating-point number. May be null.
-In our example code in these API docs, that would look like:
+=item C<string>
- { fruit => 'oranges', vegetable => 'lettuce' }
+A string. May be null.
-=head2 Arrays
+=item C<dateTime>
+
+A date/time. Represented differently in different interfaces to this API.
+May be null.
+
+=item C<boolean>
+
+True or false.
+
+=item C<base64>
+
+A base64-encoded string. This is the only way to transfer
+binary data via the WebService.
+
+=item C<array>
+
+An array. There may be mixed types in an array.
In example code, you will see the characters C<[> and C<]> used to
represent the beginning and end of arrays.
-For example, here's an array in XML-RPC:
-
- <array>
- <data>
- <value><i4>1</i4></value>
- <value><i4>2</i4></value>
- <value><i4>3</i4></value>
- </data>
- </array>
-
-In our example code in these API docs, that would look like:
+In our example code in these API docs, an array that contains the numbers
+1, 2, and 3 would look like:
[1, 2, 3]
+=item C<struct>
+
+A mapping of keys to values. Called a "hash", "dict", or "map" in some
+other programming languages. We sometimes call this a "hash" in the API
+documentation.
+
+The keys are strings, and the values can be any type.
+
+In example code, you will see the characters C<{> and C<}> used to represent
+the beginning and end of structs.
+
+For example, a struct with an "fruit" key whose value is "oranges",
+and a "vegetable" key whose value is "lettuce" would look like:
+
+ { fruit => 'oranges', vegetable => 'lettuce' }
+
+=back
+
=head2 How Bugzilla WebService Methods Take Parameters
-B<All> Bugzilla WebServices functions take their parameters in
-a C<< <struct> >>. Another way of saying this would be: All functions
-take a single argument, a C<< <struct> >> that contains all parameters.
-The names of the parameters listed in the API docs for each function are
-the C<name> element for the struct C<member>s.
+B<All> Bugzilla WebService functions use I<named> parameters.
+The individual C<Bugzilla::WebService::Server> modules explain
+how this is implemented for those frontends.
=head1 LOGGING IN
+There are various ways to log in:
+
+=over
+
+=item C<User.login>
+
You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla
user. This issues standard HTTP cookies that you must then use in future
-calls, so your XML-RPC client must be capable of receiving and transmitting
+calls, so your client must be capable of receiving and transmitting
cookies.
+=item C<Bugzilla_login> and C<Bugzilla_password>
+
+B<Added in Bugzilla 3.6>
+
+You can specify C<Bugzilla_login> and C<Bugzilla_password> as arguments
+to any WebService method, and you will be logged in as that user if your
+credentials are correct. Here are the arguments you can specify to any
+WebService method to perform a login:
+
+=over
+
+=item C<Bugzilla_login> (string) - A user's login name.
+
+=item C<Bugzilla_password> (string) - That user's password.
+
+=item C<Bugzilla_restrictlogin> (boolean) - Optional. If true,
+then your login will only be valid for your IP address.
+
+=item C<Bugzilla_rememberlogin> (boolean) - Optional. If true,
+then the cookie sent back to you with the method response will
+not expire.
+
+=back
+
+The C<Bugzilla_restrictlogin> and C<Bugzilla_rememberlogin> options
+are only used when you have also specified C<Bugzilla_login> and
+C<Bugzilla_password>.
+
+Note that Bugzilla will return HTTP cookies along with the method
+response when you use these arguments (just like the C<User.login> method
+above).
+
+=back
+
=head1 STABLE, EXPERIMENTAL, and UNSTABLE
Methods are marked B<STABLE> if you can expect their parameters and
@@ -251,18 +196,17 @@
=head1 ERRORS
-If a particular webservice call fails, it will throw a standard XML-RPC
-error. There will be a numeric error code, and then the description
-field will contain descriptive text of the error. Each error that Bugzilla
-can throw has a specific code that will not change between versions of
-Bugzilla.
+If a particular webservice call fails, it will throw an error in the
+appropriate format for the frontend that you are using. For all frontends,
+there is at least a numeric error code and descriptive text for the error.
The various errors that functions can throw are specified by the
documentation of those functions.
-If your code needs to know what error Bugzilla threw, use the numeric
-code. Don't try to parse the description, because that may change
-from version to version of Bugzilla.
+Each error that Bugzilla can throw has a specific numeric code that will
+not change between versions of Bugzilla. If your code needs to know what
+error Bugzilla threw, use the numeric code. Don't try to parse the
+description, because that may change from version to version of Bugzilla.
Note that if you display the error to the user in an HTML program, make
sure that you properly escape the error, as it will not be HTML-escaped.
@@ -285,3 +229,93 @@
Sometimes a function will throw an error that doesn't have a specific
error code. In this case, the code will be C<-32000> if it's a "fatal"
error, and C<32000> if it's a "transient" error.
+
+=head1 COMMON PARAMETERS
+
+Many Webservice methods take similar arguments. Instead of re-writing
+the documentation for each method, we document the parameters here, once,
+and then refer back to this documentation from the individual methods
+where these parameters are used.
+
+=head2 Limiting What Fields Are Returned
+
+Many WebService methods return an array of structs with various
+fields in the structs. (For example, L<Bugzilla::WebService::Bug/get>
+returns a list of C<bugs> that have fields like C<id>, C<summary>,
+C<creation_time>, etc.)
+
+These parameters allow you to limit what fields are present in
+the structs, to possibly improve performance or save some bandwidth.
+
+=over
+
+=item C<include_fields>
+
+C<array> An array of strings, representing the (case-sensitive) names of
+fields in the return value. Only the fields specified in this hash will
+be returned, the rest will not be included.
+
+If you specify an empty array, then this function will return empty
+hashes.
+
+Invalid field names are ignored.
+
+Example:
+
+ User.get( ids => [1], include_fields => ['id', 'name'] )
+
+would return something like:
+
+ { users => [{ id => 1, name => 'user@domain.com' }] }
+
+=item C<exclude_fields>
+
+C<array> An array of strings, representing the (case-sensitive) names of
+fields in the return value. The fields specified will not be included in
+the returned hashes.
+
+If you specify all the fields, then this function will return empty
+hashes.
+
+Invalid field names are ignored.
+
+Specifying fields here overrides C<include_fields>, so if you specify a
+field in both, it will be excluded, not included.
+
+Example:
+
+ User.get( ids => [1], exclude_fields => ['name'] )
+
+would return something like:
+
+ { users => [{ id => 1, real_name => 'John Smith' }] }
+
+=back
+
+=head1 SEE ALSO
+
+=head2 Server Types
+
+=over
+
+=item L<Bugzilla::WebService::Server::XMLRPC>
+
+=item L<Bugzilla::WebService::Server::JSONRPC>
+
+=back
+
+=head2 WebService Methods
+
+=over
+
+=item L<Bugzilla::WebService::Bug>
+
+=item L<Bugzilla::WebService::Bugzilla>
+
+=item L<Bugzilla::WebService::Group>
+
+=item L<Bugzilla::WebService::Product>
+
+=item L<Bugzilla::WebService::User>
+
+=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/Bug.pm b/Websites/bugs.webkit.org/Bugzilla/WebService/Bug.pm
index 5df7c7d..781e8b9 100644
--- a/Websites/bugs.webkit.org/Bugzilla/WebService/Bug.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/Bug.pm
@@ -16,93 +16,379 @@
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Mads Bondo Dydensborg <mbd@dbc.dk>
# Tsahi Asher <tsahi_75@yahoo.com>
+# Noura Elhawary <nelhawar@redhat.com>
+# Frank Becker <Frank@Frank-Becker.de>
+# Dave Lawrence <dkl@redhat.com>
package Bugzilla::WebService::Bug;
use strict;
use base qw(Bugzilla::WebService);
-import SOAP::Data qw(type);
+use Bugzilla::Comment;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Field;
use Bugzilla::WebService::Constants;
+use Bugzilla::WebService::Util qw(filter filter_wants validate);
use Bugzilla::Bug;
use Bugzilla::BugMail;
-use Bugzilla::Util qw(trim);
+use Bugzilla::Util qw(trick_taint trim);
+use Bugzilla::Version;
+use Bugzilla::Milestone;
+use Bugzilla::Status;
+use Bugzilla::Token qw(issue_hash_token);
#############
# Constants #
#############
-# This maps the names of internal Bugzilla bug fields to things that would
-# make sense to somebody who's not intimately familiar with the inner workings
-# of Bugzilla. (These are the field names that the WebService uses.)
-use constant FIELD_MAP => {
- status => 'bug_status',
- severity => 'bug_severity',
- description => 'comment',
- summary => 'short_desc',
- platform => 'rep_platform',
+use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
+
+use constant DATE_FIELDS => {
+ comments => ['new_since'],
+ search => ['last_change_time', 'creation_time'],
};
-use constant GLOBAL_SELECT_FIELDS => qw(
- bug_severity
- bug_status
- op_sys
- priority
- rep_platform
- resolution
-);
+use constant BASE64_FIELDS => {
+ add_attachment => ['data'],
+};
-use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
+use constant READ_ONLY => qw(
+ attachments
+ comments
+ fields
+ get
+ history
+ legal_values
+ search
+);
######################################################
# Add aliases here for old method name compatibility #
######################################################
-BEGIN { *get_bugs = \&get }
+BEGIN {
+ # In 3.0, get was called get_bugs
+ *get_bugs = \&get;
+ # Before 3.4rc1, "history" was get_history.
+ *get_history = \&history;
+}
###########
# Methods #
###########
-sub get {
+sub fields {
+ my ($self, $params) = validate(@_, 'ids', 'names');
+
+ my @fields;
+ if (defined $params->{ids}) {
+ my $ids = $params->{ids};
+ foreach my $id (@$ids) {
+ my $loop_field = Bugzilla::Field->check({ id => $id });
+ push(@fields, $loop_field);
+ }
+ }
+
+ if (defined $params->{names}) {
+ my $names = $params->{names};
+ foreach my $field_name (@$names) {
+ my $loop_field = Bugzilla::Field->check($field_name);
+ # Don't push in duplicate fields if we also asked for this field
+ # in "ids".
+ if (!grep($_->id == $loop_field->id, @fields)) {
+ push(@fields, $loop_field);
+ }
+ }
+ }
+
+ if (!defined $params->{ids} and !defined $params->{names}) {
+ @fields = @{ Bugzilla->fields({ obsolete => 0 }) };
+ }
+
+ my @fields_out;
+ foreach my $field (@fields) {
+ my $visibility_field = $field->visibility_field
+ ? $field->visibility_field->name : undef;
+ my $vis_values = $field->visibility_values;
+ my $value_field = $field->value_field
+ ? $field->value_field->name : undef;
+
+ my (@values, $has_values);
+ if ( ($field->is_select and $field->name ne 'product')
+ or grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS))
+ {
+ $has_values = 1;
+ @values = @{ $self->_legal_field_values({ field => $field }) };
+ }
+
+ if (grep($_ eq $field->name, PRODUCT_SPECIFIC_FIELDS)) {
+ $value_field = 'product';
+ }
+
+ my %field_data = (
+ id => $self->type('int', $field->id),
+ type => $self->type('int', $field->type),
+ is_custom => $self->type('boolean', $field->custom),
+ name => $self->type('string', $field->name),
+ display_name => $self->type('string', $field->description),
+ is_mandatory => $self->type('boolean', $field->is_mandatory),
+ is_on_bug_entry => $self->type('boolean', $field->enter_bug),
+ visibility_field => $self->type('string', $visibility_field),
+ visibility_values =>
+ [ map { $self->type('string', $_->name) } @$vis_values ],
+ );
+ if ($has_values) {
+ $field_data{value_field} = $self->type('string', $value_field);
+ $field_data{values} = \@values;
+ };
+ push(@fields_out, filter $params, \%field_data);
+ }
+
+ return { fields => \@fields_out };
+}
+
+sub _legal_field_values {
my ($self, $params) = @_;
+ my $field = $params->{field};
+ my $field_name = $field->name;
+ my $user = Bugzilla->user;
+
+ my @result;
+ if (grep($_ eq $field_name, PRODUCT_SPECIFIC_FIELDS)) {
+ my @list;
+ if ($field_name eq 'version') {
+ @list = Bugzilla::Version->get_all;
+ }
+ elsif ($field_name eq 'component') {
+ @list = Bugzilla::Component->get_all;
+ }
+ else {
+ @list = Bugzilla::Milestone->get_all;
+ }
+
+ foreach my $value (@list) {
+ my $sortkey = $field_name eq 'target_milestone'
+ ? $value->sortkey : 0;
+ # XXX This is very slow for large numbers of values.
+ my $product_name = $value->product->name;
+ if ($user->can_see_product($product_name)) {
+ push(@result, {
+ name => $self->type('string', $value->name),
+ sort_key => $self->type('int', $sortkey),
+ sortkey => $self->type('int', $sortkey), # deprecated
+ visibility_values => [$self->type('string', $product_name)],
+ });
+ }
+ }
+ }
+
+ elsif ($field_name eq 'bug_status') {
+ my @status_all = Bugzilla::Status->get_all;
+ foreach my $status (@status_all) {
+ my @can_change_to;
+ foreach my $change_to (@{ $status->can_change_to }) {
+ # There's no need to note that a status can transition
+ # to itself.
+ next if $change_to->id == $status->id;
+ my %change_to_hash = (
+ name => $self->type('string', $change_to->name),
+ comment_required => $self->type('boolean',
+ $change_to->comment_required_on_change_from($status)),
+ );
+ push(@can_change_to, \%change_to_hash);
+ }
+
+ push (@result, {
+ name => $self->type('string', $status->name),
+ is_open => $self->type('boolean', $status->is_open),
+ sort_key => $self->type('int', $status->sortkey),
+ sortkey => $self->type('int', $status->sortkey), # deprecated
+ can_change_to => \@can_change_to,
+ visibility_values => [],
+ });
+ }
+ }
+
+ else {
+ my @values = Bugzilla::Field::Choice->type($field)->get_all();
+ foreach my $value (@values) {
+ my $vis_val = $value->visibility_value;
+ push(@result, {
+ name => $self->type('string', $value->name),
+ sort_key => $self->type('int' , $value->sortkey),
+ sortkey => $self->type('int' , $value->sortkey), # deprecated
+ visibility_values => [
+ defined $vis_val ? $self->type('string', $vis_val->name)
+ : ()
+ ],
+ });
+ }
+ }
+
+ return \@result;
+}
+
+sub comments {
+ my ($self, $params) = validate(@_, 'ids', 'comment_ids');
+
+ if (!(defined $params->{ids} || defined $params->{comment_ids})) {
+ ThrowCodeError('params_required',
+ { function => 'Bug.comments',
+ params => ['ids', 'comment_ids'] });
+ }
+
+ my $bug_ids = $params->{ids} || [];
+ my $comment_ids = $params->{comment_ids} || [];
+
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ my %bugs;
+ foreach my $bug_id (@$bug_ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
+ # We want the API to always return comments in the same order.
+
+ my $comments = $bug->comments({ order => 'oldest_to_newest',
+ after => $params->{new_since} });
+ my @result;
+ foreach my $comment (@$comments) {
+ next if $comment->is_private && !$user->is_insider;
+ push(@result, $self->_translate_comment($comment, $params));
+ }
+ $bugs{$bug->id}{'comments'} = \@result;
+ }
+
+ my %comments;
+ if (scalar @$comment_ids) {
+ my @ids = map { trim($_) } @$comment_ids;
+ my $comment_data = Bugzilla::Comment->new_from_list(\@ids);
+
+ # See if we were passed any invalid comment ids.
+ my %got_ids = map { $_->id => 1 } @$comment_data;
+ foreach my $comment_id (@ids) {
+ if (!$got_ids{$comment_id}) {
+ ThrowUserError('comment_id_invalid', { id => $comment_id });
+ }
+ }
+
+ # Now make sure that we can see all the associated bugs.
+ my %got_bug_ids = map { $_->bug_id => 1 } @$comment_data;
+ Bugzilla::Bug->check($_) foreach (keys %got_bug_ids);
+
+ foreach my $comment (@$comment_data) {
+ if ($comment->is_private && !$user->is_insider) {
+ ThrowUserError('comment_is_private', { id => $comment->id });
+ }
+ $comments{$comment->id} =
+ $self->_translate_comment($comment, $params);
+ }
+ }
+
+ return { bugs => \%bugs, comments => \%comments };
+}
+
+# Helper for Bug.comments
+sub _translate_comment {
+ my ($self, $comment, $filters) = @_;
+ my $attach_id = $comment->is_about_attachment ? $comment->extra_data
+ : undef;
+ return filter $filters, {
+ id => $self->type('int', $comment->id),
+ bug_id => $self->type('int', $comment->bug_id),
+ creator => $self->type('string', $comment->author->login),
+ author => $self->type('string', $comment->author->login),
+ time => $self->type('dateTime', $comment->creation_ts),
+ is_private => $self->type('boolean', $comment->is_private),
+ text => $self->type('string', $comment->body_full),
+ attachment_id => $self->type('int', $attach_id),
+ };
+}
+
+sub get {
+ my ($self, $params) = validate(@_, 'ids');
+
+ my $ids = $params->{ids};
+ defined $ids || ThrowCodeError('param_required', { param => 'ids' });
+
+ my @bugs;
+ my @faults;
+ foreach my $bug_id (@$ids) {
+ my $bug;
+ if ($params->{permissive}) {
+ eval { $bug = Bugzilla::Bug->check($bug_id); };
+ if ($@) {
+ push(@faults, {id => $bug_id,
+ faultString => $@->faultstring,
+ faultCode => $@->faultcode,
+ }
+ );
+ undef $@;
+ next;
+ }
+ }
+ else {
+ $bug = Bugzilla::Bug->check($bug_id);
+ }
+ push(@bugs, $self->_bug_to_hash($bug, $params));
+ }
+
+ return { bugs => \@bugs, faults => \@faults };
+}
+
+# this is a function that gets bug activity for list of bug ids
+# it can be called as the following:
+# $call = $rpc->call( 'Bug.history', { ids => [1,2] });
+sub history {
+ my ($self, $params) = validate(@_, 'ids');
+
my $ids = $params->{ids};
defined $ids || ThrowCodeError('param_required', { param => 'ids' });
my @return;
+
foreach my $bug_id (@$ids) {
- ValidateBugID($bug_id);
- my $bug = new Bugzilla::Bug($bug_id);
-
- # Timetracking fields are deleted if the user doesn't belong to
- # the corresponding group.
- unless (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
- delete $bug->{'estimated_time'};
- delete $bug->{'remaining_time'};
- delete $bug->{'deadline'};
- }
- # This is done in this fashion in order to produce a stable API.
- # The internals of Bugzilla::Bug are not stable enough to just
- # return them directly.
- my $creation_ts = $self->datetime_format($bug->creation_ts);
- my $delta_ts = $self->datetime_format($bug->delta_ts);
my %item;
- $item{'creation_time'} = type('dateTime')->value($creation_ts);
- $item{'last_change_time'} = type('dateTime')->value($delta_ts);
- $item{'internals'} = $bug;
- $item{'id'} = type('int')->value($bug->bug_id);
- $item{'summary'} = type('string')->value($bug->short_desc);
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bug_id = $bug->id;
+ $item{id} = $self->type('int', $bug_id);
+ my ($activity) = Bugzilla::Bug::GetBugActivity($bug_id);
+
+ my @history;
+ foreach my $changeset (@$activity) {
+ my %bug_history;
+ $bug_history{when} = $self->type('dateTime', $changeset->{when});
+ $bug_history{who} = $self->type('string', $changeset->{who});
+ $bug_history{changes} = [];
+ foreach my $change (@{ $changeset->{changes} }) {
+ my $attach_id = delete $change->{attachid};
+ if ($attach_id) {
+ $change->{attachment_id} = $self->type('int', $attach_id);
+ }
+ $change->{removed} = $self->type('string', $change->{removed});
+ $change->{added} = $self->type('string', $change->{added});
+ $change->{field_name} = $self->type('string',
+ delete $change->{fieldname});
+ push (@{$bug_history{changes}}, $change);
+ }
+
+ push (@history, \%bug_history);
+ }
+
+ $item{history} = \@history;
+
+ # alias is returned in case users passes a mixture of ids and aliases
+ # then they get to know which bug activity relates to which value
+ # they passed
if (Bugzilla->params->{'usebugaliases'}) {
- $item{'alias'} = type('string')->value($bug->alias);
+ $item{alias} = $self->type('string', $bug->alias);
}
else {
# For API reasons, we always want the value to appear, we just
# don't want it to have a value if aliases are turned off.
- $item{'alias'} = undef;
+ $item{alias} = undef;
}
push(@return, \%item);
@@ -111,39 +397,195 @@
return { bugs => \@return };
}
+sub search {
+ my ($self, $params) = @_;
+
+ if ( defined($params->{offset}) and !defined($params->{limit}) ) {
+ ThrowCodeError('param_required',
+ { param => 'limit', function => 'Bug.search()' });
+ }
+
+ $params = Bugzilla::Bug::map_fields($params);
+ delete $params->{WHERE};
+
+ unless (Bugzilla->user->is_timetracker) {
+ delete $params->{$_} foreach qw(estimated_time remaining_time deadline);
+ }
+
+ # Do special search types for certain fields.
+ if ( my $bug_when = delete $params->{delta_ts} ) {
+ $params->{WHERE}->{'delta_ts >= ?'} = $bug_when;
+ }
+ if (my $when = delete $params->{creation_ts}) {
+ $params->{WHERE}->{'creation_ts >= ?'} = $when;
+ }
+ if (my $summary = delete $params->{short_desc}) {
+ my @strings = ref $summary ? @$summary : ($summary);
+ my @likes = ("short_desc LIKE ?") x @strings;
+ my $clause = join(' OR ', @likes);
+ $params->{WHERE}->{"($clause)"} = [map { "\%$_\%" } @strings];
+ }
+ if (my $whiteboard = delete $params->{status_whiteboard}) {
+ my @strings = ref $whiteboard ? @$whiteboard : ($whiteboard);
+ my @likes = ("status_whiteboard LIKE ?") x @strings;
+ my $clause = join(' OR ', @likes);
+ $params->{WHERE}->{"($clause)"} = [map { "\%$_\%" } @strings];
+ }
+
+ # We want include_fields and exclude_fields to be passed to
+ # _bug_to_hash but not to Bugzilla::Bug->match so we copy the
+ # params and delete those before passing to Bugzilla::Bug->match.
+ my %match_params = %{ $params };
+ delete $match_params{'include_fields'};
+ delete $match_params{'exclude_fields'};
+
+ my $bugs = Bugzilla::Bug->match(\%match_params);
+ my $visible = Bugzilla->user->visible_bugs($bugs);
+ my @hashes = map { $self->_bug_to_hash($_, $params) } @$visible;
+ return { bugs => \@hashes };
+}
+
+sub possible_duplicates {
+ my ($self, $params) = validate(@_, 'product');
+ my $user = Bugzilla->user;
+
+ # Undo the array-ification that validate() does, for "summary".
+ $params->{summary} || ThrowCodeError('param_required',
+ { function => 'Bug.possible_duplicates', param => 'summary' });
+
+ my @products;
+ foreach my $name (@{ $params->{'product'} || [] }) {
+ my $object = $user->can_enter_product($name, THROW_ERROR);
+ push(@products, $object);
+ }
+
+ my $possible_dupes = Bugzilla::Bug->possible_duplicates(
+ { summary => $params->{summary}, products => \@products,
+ limit => $params->{limit} });
+ my @hashes = map { $self->_bug_to_hash($_, $params) } @$possible_dupes;
+ return { bugs => \@hashes };
+}
+
+sub update {
+ my ($self, $params) = validate(@_, 'ids');
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
+
+ # We skip certain fields because their set_ methods actually use
+ # the external names instead of the internal names.
+ $params = Bugzilla::Bug::map_fields($params,
+ { summary => 1, platform => 1, severity => 1, url => 1 });
+
+ my $ids = delete $params->{ids};
+ defined $ids || ThrowCodeError('param_required', { param => 'ids' });
+
+ my @bugs = map { Bugzilla::Bug->check($_) } @$ids;
+
+ my %values = %$params;
+ $values{other_bugs} = \@bugs;
+
+ if (exists $values{comment} and exists $values{comment}{comment}) {
+ $values{comment}{body} = delete $values{comment}{comment};
+ }
+
+ # Prevent bugs that could be triggered by specifying fields that
+ # have valid "set_" functions in Bugzilla::Bug, but shouldn't be
+ # called using those field names.
+ delete $values{dependencies};
+ delete $values{flags};
+
+ foreach my $bug (@bugs) {
+ if (!$user->can_edit_product($bug->product_obj->id) ) {
+ ThrowUserError("product_edit_denied",
+ { product => $bug->product });
+ }
+
+ $bug->set_all(\%values);
+ }
+
+ my %all_changes;
+ $dbh->bz_start_transaction();
+ foreach my $bug (@bugs) {
+ $all_changes{$bug->id} = $bug->update();
+ }
+ $dbh->bz_commit_transaction();
+
+ foreach my $bug (@bugs) {
+ $bug->send_changes($all_changes{$bug->id});
+ }
+
+ my %api_name = reverse %{ Bugzilla::Bug::FIELD_MAP() };
+ # This doesn't normally belong in FIELD_MAP, but we do want to translate
+ # "bug_group" back into "groups".
+ $api_name{'bug_group'} = 'groups';
+
+ my @result;
+ foreach my $bug (@bugs) {
+ my %hash = (
+ id => $self->type('int', $bug->id),
+ last_change_time => $self->type('dateTime', $bug->delta_ts),
+ changes => {},
+ );
+
+ # alias is returned in case users pass a mixture of ids and aliases,
+ # so that they can know which set of changes relates to which value
+ # they passed.
+ if (Bugzilla->params->{'usebugaliases'}) {
+ $hash{alias} = $self->type('string', $bug->alias);
+ }
+ else {
+ # For API reasons, we always want the alias field to appear, we
+ # just don't want it to have a value if aliases are turned off.
+ $hash{alias} = $self->type('string', '');
+ }
+
+ my %changes = %{ $all_changes{$bug->id} };
+ foreach my $field (keys %changes) {
+ my $change = $changes{$field};
+ my $api_field = $api_name{$field} || $field;
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things like Deadline that can become
+ # empty.
+ $change->[0] = '' if !defined $change->[0];
+ $change->[1] = '' if !defined $change->[1];
+ $hash{changes}->{$api_field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
+ }
+
+ push(@result, \%hash);
+ }
+
+ return { bugs => \@result };
+}
sub create {
my ($self, $params) = @_;
-
Bugzilla->login(LOGIN_REQUIRED);
-
- my %field_values;
- foreach my $field (keys %$params) {
- my $field_name = FIELD_MAP->{$field} || $field;
- $field_values{$field_name} = $params->{$field};
- }
-
- # WebService users can't set the creation date of a bug.
- delete $field_values{'creation_ts'};
-
- my $bug = Bugzilla::Bug->create(\%field_values);
-
- Bugzilla::BugMail::Send($bug->bug_id, { changer => $bug->reporter->login });
-
- return { id => type('int')->value($bug->bug_id) };
+ $params = Bugzilla::Bug::map_fields($params);
+ my $bug = Bugzilla::Bug->create($params);
+ Bugzilla::BugMail::Send($bug->bug_id, { changer => $bug->reporter });
+ return { id => $self->type('int', $bug->bug_id) };
}
sub legal_values {
my ($self, $params) = @_;
- my $field = FIELD_MAP->{$params->{field}} || $params->{field};
- my @custom_select = Bugzilla->get_fields(
- {custom => 1, type => [FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT]});
- # We only want field names.
- @custom_select = map {$_->name} @custom_select;
+ defined $params->{field}
+ or ThrowCodeError('param_required', { param => 'field' });
+
+ my $field = Bugzilla::Bug::FIELD_MAP->{$params->{field}}
+ || $params->{field};
+
+ my @global_selects =
+ @{ Bugzilla->fields({ is_select => 1, is_abnormal => 0 }) };
my $values;
- if (grep($_ eq $field, GLOBAL_SELECT_FIELDS, @custom_select)) {
+ if (grep($_->name eq $field, @global_selects)) {
+ # The field is a valid one.
+ trick_taint($field);
$values = get_legal_field_values($field);
}
elsif (grep($_ eq $field, PRODUCT_SPECIFIC_FIELDS)) {
@@ -151,7 +593,7 @@
defined $id || ThrowCodeError('param_required',
{ function => 'Bug.legal_values', param => 'product_id' });
grep($_->id eq $id, @{Bugzilla->user->get_accessible_products})
- || ThrowUserError('product_access_denied', { product => $id });
+ || ThrowUserError('product_access_denied', { id => $id });
my $product = new Bugzilla::Product($id);
my @objects;
@@ -173,12 +615,61 @@
my @result;
foreach my $val (@$values) {
- push(@result, type('string')->value($val));
+ push(@result, $self->type('string', $val));
}
return { values => \@result };
}
+sub add_attachment {
+ my ($self, $params) = validate(@_, 'ids');
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ defined $params->{ids}
+ || ThrowCodeError('param_required', { param => 'ids' });
+ defined $params->{data}
+ || ThrowCodeError('param_required', { param => 'data' });
+
+ my @bugs = map { Bugzilla::Bug->check($_) } @{ $params->{ids} };
+ foreach my $bug (@bugs) {
+ Bugzilla->user->can_edit_product($bug->product_id)
+ || ThrowUserError("product_edit_denied", {product => $bug->product});
+ }
+
+ my @created;
+ $dbh->bz_start_transaction();
+ my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ foreach my $bug (@bugs) {
+ my $attachment = Bugzilla::Attachment->create({
+ bug => $bug,
+ creation_ts => $timestamp,
+ data => $params->{data},
+ description => $params->{summary},
+ filename => $params->{file_name},
+ mimetype => $params->{content_type},
+ ispatch => $params->{is_patch},
+ isprivate => $params->{is_private},
+ });
+ my $comment = $params->{comment} || '';
+ $attachment->bug->add_comment($comment,
+ { isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_CREATED,
+ extra_data => $attachment->id });
+ push(@created, $attachment);
+ }
+ $_->bug->update($timestamp) foreach @created;
+ $dbh->bz_commit_transaction();
+
+ $_->send_changes() foreach @bugs;
+
+ my %attachments = map { $_->id => $self->_attachment_to_hash($_, $params) }
+ @created;
+
+ return { attachments => \%attachments };
+}
+
sub add_comment {
my ($self, $params) = @_;
@@ -187,26 +678,290 @@
# Check parameters
defined $params->{id}
- || ThrowCodeError('param_required', { param => 'id' });
- ValidateBugID($params->{id});
-
+ || ThrowCodeError('param_required', { param => 'id' });
my $comment = $params->{comment};
(defined $comment && trim($comment) ne '')
|| ThrowCodeError('param_required', { param => 'comment' });
- my $bug = new Bugzilla::Bug($params->{id});
+ my $bug = Bugzilla::Bug->check($params->{id});
Bugzilla->user->can_edit_product($bug->product_id)
|| ThrowUserError("product_edit_denied", {product => $bug->product});
-
+
+ # Backwards-compatibility for versions before 3.6
+ if (defined $params->{private}) {
+ $params->{is_private} = delete $params->{private};
+ }
# Append comment
- $bug->add_comment($comment, { isprivate => $params->{private},
+ $bug->add_comment($comment, { isprivate => $params->{is_private},
work_time => $params->{work_time} });
+
+ # Capture the call to bug->update (which creates the new comment) in
+ # a transaction so we're sure to get the correct comment_id.
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+
$bug->update();
+ my $new_comment_id = $dbh->bz_last_key('longdescs', 'comment_id');
+
+ $dbh->bz_commit_transaction();
+
# Send mail.
- Bugzilla::BugMail::Send($bug->bug_id, { changer => Bugzilla->user->login });
- return undef;
+ Bugzilla::BugMail::Send($bug->bug_id, { changer => Bugzilla->user });
+
+ return { id => $self->type('int', $new_comment_id) };
+}
+
+sub update_see_also {
+ my ($self, $params) = @_;
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ # Check parameters
+ $params->{ids}
+ || ThrowCodeError('param_required', { param => 'id' });
+ my ($add, $remove) = @$params{qw(add remove)};
+ ($add || $remove)
+ or ThrowCodeError('params_required', { params => ['add', 'remove'] });
+
+ my @bugs;
+ foreach my $id (@{ $params->{ids} }) {
+ my $bug = Bugzilla::Bug->check($id);
+ $user->can_edit_product($bug->product_id)
+ || ThrowUserError("product_edit_denied",
+ { product => $bug->product });
+ push(@bugs, $bug);
+ if ($remove) {
+ $bug->remove_see_also($_) foreach @$remove;
+ }
+ if ($add) {
+ $bug->add_see_also($_) foreach @$add;
+ }
+ }
+
+ my %changes;
+ foreach my $bug (@bugs) {
+ my $change = $bug->update();
+ if (my $see_also = $change->{see_also}) {
+ $changes{$bug->id}->{see_also} = {
+ removed => [split(', ', $see_also->[0])],
+ added => [split(', ', $see_also->[1])],
+ };
+ }
+ else {
+ # We still want a changes entry, for API consistency.
+ $changes{$bug->id}->{see_also} = { added => [], removed => [] };
+ }
+
+ Bugzilla::BugMail::Send($bug->id, { changer => $user });
+ }
+
+ return { changes => \%changes };
+}
+
+sub attachments {
+ my ($self, $params) = validate(@_, 'ids', 'attachment_ids');
+
+ if (!(defined $params->{ids}
+ or defined $params->{attachment_ids}))
+ {
+ ThrowCodeError('param_required',
+ { function => 'Bug.attachments',
+ params => ['ids', 'attachment_ids'] });
+ }
+
+ my $ids = $params->{ids} || [];
+ my $attach_ids = $params->{attachment_ids} || [];
+
+ my %bugs;
+ foreach my $bug_id (@$ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bugs{$bug->id} = [];
+ foreach my $attach (@{$bug->attachments}) {
+ push @{$bugs{$bug->id}},
+ $self->_attachment_to_hash($attach, $params);
+ }
+ }
+
+ my %attachments;
+ foreach my $attach (@{Bugzilla::Attachment->new_from_list($attach_ids)}) {
+ Bugzilla::Bug->check($attach->bug_id);
+ if ($attach->isprivate && !Bugzilla->user->is_insider) {
+ ThrowUserError('auth_failure', {action => 'access',
+ object => 'attachment',
+ attach_id => $attach->id});
+ }
+ $attachments{$attach->id} =
+ $self->_attachment_to_hash($attach, $params);
+ }
+
+ return { bugs => \%bugs, attachments => \%attachments };
+}
+
+##############################
+# Private Helper Subroutines #
+##############################
+
+# A helper for get() and search(). This is done in this fashion in order
+# to produce a stable API and to explicitly type return values.
+# The internals of Bugzilla::Bug are not stable enough to just
+# return them directly.
+
+sub _bug_to_hash {
+ my ($self, $bug, $params) = @_;
+
+ # All the basic bug attributes are here, in alphabetical order.
+ # A bug attribute is "basic" if it doesn't require an additional
+ # database call to get the info.
+ my %item = (
+ alias => $self->type('string', $bug->alias),
+ classification => $self->type('string', $bug->classification),
+ component => $self->type('string', $bug->component),
+ creation_time => $self->type('dateTime', $bug->creation_ts),
+ id => $self->type('int', $bug->bug_id),
+ is_confirmed => $self->type('boolean', $bug->everconfirmed),
+ last_change_time => $self->type('dateTime', $bug->delta_ts),
+ op_sys => $self->type('string', $bug->op_sys),
+ platform => $self->type('string', $bug->rep_platform),
+ priority => $self->type('string', $bug->priority),
+ product => $self->type('string', $bug->product),
+ resolution => $self->type('string', $bug->resolution),
+ severity => $self->type('string', $bug->bug_severity),
+ status => $self->type('string', $bug->bug_status),
+ summary => $self->type('string', $bug->short_desc),
+ target_milestone => $self->type('string', $bug->target_milestone),
+ url => $self->type('string', $bug->bug_file_loc),
+ version => $self->type('string', $bug->version),
+ whiteboard => $self->type('string', $bug->status_whiteboard),
+ );
+
+
+ # First we handle any fields that require extra SQL calls.
+ # We don't do the SQL calls at all if the filter would just
+ # eliminate them anyway.
+ if (filter_wants $params, 'assigned_to') {
+ $item{'assigned_to'} = $self->type('string', $bug->assigned_to->login);
+ }
+ if (filter_wants $params, 'blocks') {
+ my @blocks = map { $self->type('int', $_) } @{ $bug->blocked };
+ $item{'blocks'} = \@blocks;
+ }
+ if (filter_wants $params, 'cc') {
+ my @cc = map { $self->type('string', $_) } @{ $bug->cc || [] };
+ $item{'cc'} = \@cc;
+ }
+ if (filter_wants $params, 'creator') {
+ $item{'creator'} = $self->type('string', $bug->reporter->login);
+ }
+ if (filter_wants $params, 'depends_on') {
+ my @depends_on = map { $self->type('int', $_) } @{ $bug->dependson };
+ $item{'depends_on'} = \@depends_on;
+ }
+ if (filter_wants $params, 'dupe_of') {
+ $item{'dupe_of'} = $self->type('int', $bug->dup_id);
+ }
+ if (filter_wants $params, 'groups') {
+ my @groups = map { $self->type('string', $_->name) }
+ @{ $bug->groups_in };
+ $item{'groups'} = \@groups;
+ }
+ if (filter_wants $params, 'is_open') {
+ $item{'is_open'} = $self->type('boolean', $bug->status->is_open);
+ }
+ if (filter_wants $params, 'keywords') {
+ my @keywords = map { $self->type('string', $_->name) }
+ @{ $bug->keyword_objects };
+ $item{'keywords'} = \@keywords;
+ }
+ if (filter_wants $params, 'qa_contact') {
+ my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : '';
+ $item{'qa_contact'} = $self->type('string', $qa_login);
+ }
+ if (filter_wants $params, 'see_also') {
+ my @see_also = map { $self->type('string', $_->name) }
+ @{ $bug->see_also };
+ $item{'see_also'} = \@see_also;
+ }
+
+ # And now custom fields
+ my @custom_fields = Bugzilla->active_custom_fields;
+ foreach my $field (@custom_fields) {
+ my $name = $field->name;
+ next if !filter_wants $params, $name;
+ if ($field->type == FIELD_TYPE_BUG_ID) {
+ $item{$name} = $self->type('int', $bug->$name);
+ }
+ elsif ($field->type == FIELD_TYPE_DATETIME) {
+ $item{$name} = $self->type('dateTime', $bug->$name);
+ }
+ elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ my @values = map { $self->type('string', $_) } @{ $bug->$name };
+ $item{$name} = \@values;
+ }
+ else {
+ $item{$name} = $self->type('string', $bug->$name);
+ }
+ }
+
+ # Timetracking fields are only sent if the user can see them.
+ if (Bugzilla->user->is_timetracker) {
+ $item{'estimated_time'} = $self->type('double', $bug->estimated_time);
+ $item{'remaining_time'} = $self->type('double', $bug->remaining_time);
+ # No need to format $bug->deadline specially, because Bugzilla::Bug
+ # already does it for us.
+ $item{'deadline'} = $self->type('string', $bug->deadline);
+ }
+
+ if (Bugzilla->user->id) {
+ my $token = issue_hash_token([$bug->id, $bug->delta_ts]);
+ $item{'update_token'} = $self->type('string', $token);
+ }
+
+ # The "accessible" bits go here because they have long names and it
+ # makes the code look nicer to separate them out.
+ $item{'is_cc_accessible'} = $self->type('boolean',
+ $bug->cclist_accessible);
+ $item{'is_creator_accessible'} = $self->type('boolean',
+ $bug->reporter_accessible);
+
+ return filter $params, \%item;
+}
+
+sub _attachment_to_hash {
+ my ($self, $attach, $filters) = @_;
+
+ # Skipping attachment flags for now.
+ delete $attach->{flags};
+
+ my $item = filter $filters, {
+ creation_time => $self->type('dateTime', $attach->attached),
+ last_change_time => $self->type('dateTime', $attach->modification_time),
+ id => $self->type('int', $attach->id),
+ bug_id => $self->type('int', $attach->bug_id),
+ file_name => $self->type('string', $attach->filename),
+ summary => $self->type('string', $attach->description),
+ description => $self->type('string', $attach->description),
+ content_type => $self->type('string', $attach->contenttype),
+ is_private => $self->type('int', $attach->isprivate),
+ is_obsolete => $self->type('int', $attach->isobsolete),
+ is_patch => $self->type('int', $attach->ispatch),
+ };
+
+ # creator/attacher require an extra lookup, so we only send them if
+ # the filter wants them.
+ foreach my $field (qw(creator attacher)) {
+ if (filter_wants $filters, $field) {
+ $item->{$field} = $self->type('string', $attach->attacher->login);
+ }
+ }
+
+ if (filter_wants $filters, 'data') {
+ $item->{'data'} = $self->type('base64', $attach->data);
+ }
+
+ return $item;
}
1;
@@ -228,13 +983,214 @@
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
-=head2 Utility Functions
+=head1 Utility Functions
+
+=head2 fields
+
+B<UNSTABLE>
=over
-=item C<legal_values>
+=item B<Description>
-B<EXPERIMENTAL>
+Get information about valid bug fields, including the lists of legal values
+for each field.
+
+=item B<Params>
+
+You can pass either field ids or field names.
+
+B<Note>: If neither C<ids> nor C<names> is specified, then all
+non-obsolete fields will be returned.
+
+In addition to the parameters below, this method also accepts the
+standard L<include_fields|Bugzilla::WebService/include_fields> and
+L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
+=over
+
+=item C<ids> (array) - An array of integer field ids.
+
+=item C<names> (array) - An array of strings representing field names.
+
+=back
+
+=item B<Returns>
+
+A hash containing a single element, C<fields>. This is an array of hashes,
+containing the following keys:
+
+=over
+
+=item C<id>
+
+C<int> An integer id uniquely identifying this field in this installation only.
+
+=item C<type>
+
+C<int> The number of the fieldtype. The following values are defined:
+
+=over
+
+=item C<0> Unknown
+
+=item C<1> Free Text
+
+=item C<2> Drop Down
+
+=item C<3> Multiple-Selection Box
+
+=item C<4> Large Text Box
+
+=item C<5> Date/Time
+
+=item C<6> Bug Id
+
+=item C<7> Bug URLs ("See Also")
+
+=back
+
+=item C<is_custom>
+
+C<boolean> True when this is a custom field, false otherwise.
+
+=item C<name>
+
+C<string> The internal name of this field. This is a unique identifier for
+this field. If this is not a custom field, then this name will be the same
+across all Bugzilla installations.
+
+=item C<display_name>
+
+C<string> The name of the field, as it is shown in the user interface.
+
+=item C<is_mandatory>
+
+C<boolean> True if the field must have a value when filing new bugs.
+Also, mandatory fields cannot have their value cleared when updating
+bugs.
+
+=item C<is_on_bug_entry>
+
+C<boolean> For custom fields, this is true if the field is shown when you
+enter a new bug. For standard fields, this is currently always false,
+even if the field shows up when entering a bug. (To know whether or not
+a standard field is valid on bug entry, see L</create>.)
+
+=item C<visibility_field>
+
+C<string> The name of a field that controls the visibility of this field
+in the user interface. This field only appears in the user interface when
+the named field is equal to one of the values in C<visibility_values>.
+Can be null.
+
+=item C<visibility_values>
+
+C<array> of C<string>s This field is only shown when C<visibility_field>
+matches one of these values. When C<visibility_field> is null,
+then this is an empty array.
+
+=item C<value_field>
+
+C<string> The name of the field that controls whether or not particular
+values of the field are shown in the user interface. Can be null.
+
+=item C<values>
+
+This is an array of hashes, representing the legal values for
+select-type (drop-down and multiple-selection) fields. This is also
+populated for the C<component>, C<version>, and C<target_milestone>
+fields, but not for the C<product> field (you must use
+L<Product.get_accessible_products|Bugzilla::WebService::Product/get_accessible_products>
+for that.
+
+For fields that aren't select-type fields, this will simply be an empty
+array.
+
+Each hash has the following keys:
+
+=over
+
+=item C<name>
+
+C<string> The actual value--this is what you would specify for this
+field in L</create>, etc.
+
+=item C<sort_key>
+
+C<int> Values, when displayed in a list, are sorted first by this integer
+and then secondly by their name.
+
+=item C<sortkey>
+
+B<DEPRECATED> - Use C<sort_key> instead.
+
+=item C<visibility_values>
+
+If C<value_field> is defined for this field, then this value is only shown
+if the C<value_field> is set to one of the values listed in this array.
+Note that for per-product fields, C<value_field> is set to C<'product'>
+and C<visibility_values> will reflect which product(s) this value appears in.
+
+=item C<is_open>
+
+C<boolean> For C<bug_status> values, determines whether this status
+specifies that the bug is "open" (true) or "closed" (false). This item
+is only included for the C<bug_status> field.
+
+=item C<can_change_to>
+
+For C<bug_status> values, this is an array of hashes that determines which
+statuses you can transition to from this status. (This item is only included
+for the C<bug_status> field.)
+
+Each hash contains the following items:
+
+=over
+
+=item C<name>
+
+the name of the new status
+
+=item C<comment_required>
+
+this C<boolean> True if a comment is required when you change a bug into
+this status using this transition.
+
+=back
+
+=back
+
+=back
+
+=item B<Errors>
+
+=over
+
+=item 51 (Invalid Field Name or Id)
+
+You specified an invalid field name or id.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.6>.
+
+=item The C<is_mandatory> return value was added in Bugzilla B<4.0>.
+
+=item C<sortkey> was renamed to C<sort_key> in Bugzilla B<4.2>.
+
+=back
+
+=back
+
+
+=head2 legal_values
+
+B<DEPRECATED> - Use L</fields> instead.
=over
@@ -276,16 +1232,322 @@
=back
+=head1 Bug Information
-=back
+=head2 attachments
-=head2 Bug Information
+B<EXPERIMENTAL>
=over
-=item C<get>
+=item B<Description>
-B<EXPERIMENTAL>
+It allows you to get data about attachments, given a list of bugs
+and/or attachment ids.
+
+B<Note>: Private attachments will only be returned if you are in the
+insidergroup or if you are the submitter of the attachment.
+
+=item B<Params>
+
+B<Note>: At least one of C<ids> or C<attachment_ids> is required.
+
+=over
+
+=item C<ids>
+
+See the description of the C<ids> parameter in the L</get> method.
+
+=item C<attachment_ids>
+
+C<array> An array of integer attachment ids.
+
+=back
+
+Also accepts the L<include_fields|Bugzilla::WebService/include_fields>,
+and L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
+=item B<Returns>
+
+A hash containing two elements: C<bugs> and C<attachments>. The return
+value looks like this:
+
+ {
+ bugs => {
+ 1345 => [
+ { (attachment) },
+ { (attachment) }
+ ],
+ 9874 => [
+ { (attachment) },
+ { (attachment) }
+ ],
+ },
+
+ attachments => {
+ 234 => { (attachment) },
+ 123 => { (attachment) },
+ }
+ }
+
+The attachments of any bugs that you specified in the C<ids> argument in
+input are returned in C<bugs> on output. C<bugs> is a hash that has integer
+bug IDs for keys and the values are arrayrefs that contain hashes as attachments.
+(Fields for attachments are described below.)
+
+For any attachments that you specified directly in C<attachment_ids>, they
+are returned in C<attachments> on output. This is a hash where the attachment
+ids point directly to hashes describing the individual attachment.
+
+The fields for each attachment (where it says C<(attachment)> in the
+diagram above) are:
+
+=over
+
+=item C<data>
+
+C<base64> The raw data of the attachment, encoded as Base64.
+
+=item C<creation_time>
+
+C<dateTime> The time the attachment was created.
+
+=item C<last_change_time>
+
+C<dateTime> The last time the attachment was modified.
+
+=item C<id>
+
+C<int> The numeric id of the attachment.
+
+=item C<bug_id>
+
+C<int> The numeric id of the bug that the attachment is attached to.
+
+=item C<file_name>
+
+C<string> The file name of the attachment.
+
+=item C<summary>
+
+C<string> A short string describing the attachment.
+
+Also returned as C<description>, for backwards-compatibility with older
+Bugzillas. (However, this backwards-compatibility will go away in Bugzilla
+5.0.)
+
+=item C<content_type>
+
+C<string> The MIME type of the attachment.
+
+=item C<is_private>
+
+C<boolean> True if the attachment is private (only visible to a certain
+group called the "insidergroup"), False otherwise.
+
+=item C<is_obsolete>
+
+C<boolean> True if the attachment is obsolete, False otherwise.
+
+=item C<is_patch>
+
+C<boolean> True if the attachment is a patch, False otherwise.
+
+=item C<creator>
+
+C<string> The login name of the user that created the attachment.
+
+Also returned as C<attacher>, for backwards-compatibility with older
+Bugzillas. (However, this backwards-compatibility will go away in Bugzilla
+5.0.)
+
+=back
+
+=item B<Errors>
+
+This method can throw all the same errors as L</get>. In addition,
+it can also throw the following error:
+
+=over
+
+=item 304 (Auth Failure, Attachment is Private)
+
+You specified the id of a private attachment in the C<attachment_ids>
+argument, and you are not in the "insider group" that can see
+private attachments.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.6>.
+
+=item In Bugzilla B<4.0>, the C<attacher> return value was renamed to
+C<creator>.
+
+=item In Bugzilla B<4.0>, the C<description> return value was renamed to
+C<summary>.
+
+=item The C<data> return value was added in Bugzilla B<4.0>.
+
+=item In Bugzilla B<4.2>, the C<is_url> return value was removed
+(this attribute no longer exists for attachments).
+
+=back
+
+=back
+
+
+=head2 comments
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+This allows you to get data about comments, given a list of bugs
+and/or comment ids.
+
+=item B<Params>
+
+B<Note>: At least one of C<ids> or C<comment_ids> is required.
+
+In addition to the parameters below, this method also accepts the
+standard L<include_fields|Bugzilla::WebService/include_fields> and
+L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
+=over
+
+=item C<ids>
+
+C<array> An array that can contain both bug IDs and bug aliases.
+All of the comments (that are visible to you) will be returned for the
+specified bugs.
+
+=item C<comment_ids>
+
+C<array> An array of integer comment_ids. These comments will be
+returned individually, separate from any other comments in their
+respective bugs.
+
+=item C<new_since>
+
+C<dateTime> If specified, the method will only return comments I<newer>
+than this time. This only affects comments returned from the C<ids>
+argument. You will always be returned all comments you request in the
+C<comment_ids> argument, even if they are older than this date.
+
+=back
+
+=item B<Returns>
+
+Two items are returned:
+
+=over
+
+=item C<bugs>
+
+This is used for bugs specified in C<ids>. This is a hash,
+where the keys are the numeric ids of the bugs, and the value is
+a hash with a single key, C<comments>, which is an array of comments.
+(The format of comments is described below.)
+
+Note that any individual bug will only be returned once, so if you
+specify an id multiple times in C<ids>, it will still only be
+returned once.
+
+=item C<comments>
+
+Each individual comment requested in C<comment_ids> is returned here,
+in a hash where the numeric comment id is the key, and the value
+is the comment. (The format of comments is described below.)
+
+=back
+
+A "comment" as described above is a hash that contains the following
+keys:
+
+=over
+
+=item id
+
+C<int> The globally unique ID for the comment.
+
+=item bug_id
+
+C<int> The ID of the bug that this comment is on.
+
+=item attachment_id
+
+C<int> If the comment was made on an attachment, this will be the
+ID of that attachment. Otherwise it will be null.
+
+=item text
+
+C<string> The actual text of the comment.
+
+=item creator
+
+C<string> The login name of the comment's author.
+
+Also returned as C<author>, for backwards-compatibility with older
+Bugzillas. (However, this backwards-compatibility will go away in Bugzilla
+5.0.)
+
+=item time
+
+C<dateTime> The time (in Bugzilla's timezone) that the comment was added.
+
+=item is_private
+
+C<boolean> True if this comment is private (only visible to a certain
+group called the "insidergroup"), False otherwise.
+
+=back
+
+=item B<Errors>
+
+This method can throw all the same errors as L</get>. In addition,
+it can also throw the following errors:
+
+=over
+
+=item 110 (Comment Is Private)
+
+You specified the id of a private comment in the C<comment_ids>
+argument, and you are not in the "insider group" that can see
+private comments.
+
+=item 111 (Invalid Comment ID)
+
+You specified an id in the C<comment_ids> argument that is invalid--either
+you specified something that wasn't a number, or there is no comment with
+that id.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=item C<attachment_id> was added to the return value in Bugzilla B<3.6>.
+
+=item In Bugzilla B<4.0>, the C<author> return value was renamed to
+C<creator>.
+
+=back
+
+=back
+
+
+=head2 get
+
+B<STABLE>
=over
@@ -297,6 +1559,10 @@
=item B<Params>
+In addition to the parameters below, this method also accepts the
+standard L<include_fields|Bugzilla::WebService/include_fields> and
+L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
=over
=item C<ids>
@@ -312,12 +1578,225 @@
case you will be told that you have specified an invalid bug_id if you
try to specify an alias. (It will be error 100.)
+=item C<permissive> B<EXPERIMENTAL>
+
+C<boolean> Normally, if you request any inaccessible or invalid bug ids,
+Bug.get will throw an error. If this parameter is True, instead of throwing an
+error we return an array of hashes with a C<id>, C<faultString> and C<faultCode>
+for each bug that fails, and return normal information for the other bugs that
+were accessible.
+
=back
=item B<Returns>
-A hash containing a single element, C<bugs>. This is an array of hashes.
-Each hash contains the following items:
+Two items are returned:
+
+=over
+
+=item C<bugs>
+
+An array of hashes that contains information about the bugs with
+the valid ids. Each hash contains the following items:
+
+=over
+
+=item C<alias>
+
+C<string> The unique alias of this bug.
+
+=item C<assigned_to>
+
+C<string> The login name of the user to whom the bug is assigned.
+
+=item C<blocks>
+
+C<array> of C<int>s. The ids of bugs that are "blocked" by this bug.
+
+=item C<cc>
+
+C<array> of C<string>s. The login names of users on the CC list of this
+bug.
+
+=item C<classification>
+
+C<string> The name of the current classification the bug is in.
+
+=item C<component>
+
+C<string> The name of the current component of this bug.
+
+=item C<creation_time>
+
+C<dateTime> When the bug was created.
+
+=item C<creator>
+
+C<string> The login name of the person who filed this bug (the reporter).
+
+=item C<deadline>
+
+C<string> The day that this bug is due to be completed, in the format
+C<YYYY-MM-DD>.
+
+If you are not in the time-tracking group, this field will not be included
+in the return value.
+
+=item C<depends_on>
+
+C<array> of C<int>s. The ids of bugs that this bug "depends on".
+
+=item C<dupe_of>
+
+C<int> The bug ID of the bug that this bug is a duplicate of. If this bug
+isn't a duplicate of any bug, this will be null.
+
+=item C<estimated_time>
+
+C<double> The number of hours that it was estimated that this bug would
+take.
+
+If you are not in the time-tracking group, this field will not be included
+in the return value.
+
+=item C<groups>
+
+C<array> of C<string>s. The names of all the groups that this bug is in.
+
+=item C<id>
+
+C<int> The unique numeric id of this bug.
+
+=item C<is_cc_accessible>
+
+C<boolean> If true, this bug can be accessed by members of the CC list,
+even if they are not in the groups the bug is restricted to.
+
+=item C<is_confirmed>
+
+C<boolean> True if the bug has been confirmed. Usually this means that
+the bug has at some point been moved out of the C<UNCONFIRMED> status
+and into another open status.
+
+=item C<is_open>
+
+C<boolean> True if this bug is open, false if it is closed.
+
+=item C<is_creator_accessible>
+
+C<boolean> If true, this bug can be accessed by the creator (reporter)
+of the bug, even if he or she is not a member of the groups the bug
+is restricted to.
+
+=item C<keywords>
+
+C<array> of C<string>s. Each keyword that is on this bug.
+
+=item C<last_change_time>
+
+C<dateTime> When the bug was last changed.
+
+=item C<op_sys>
+
+C<string> The name of the operating system that the bug was filed against.
+
+=item C<platform>
+
+C<string> The name of the platform (hardware) that the bug was filed against.
+
+=item C<priority>
+
+C<string> The priority of the bug.
+
+=item C<product>
+
+C<string> The name of the product this bug is in.
+
+=item C<qa_contact>
+
+C<string> The login name of the current QA Contact on the bug.
+
+=item C<remaining_time>
+
+C<double> The number of hours of work remaining until work on this bug
+is complete.
+
+If you are not in the time-tracking group, this field will not be included
+in the return value.
+
+=item C<resolution>
+
+C<string> The current resolution of the bug, or an empty string if the bug
+is open.
+
+=item C<see_also>
+
+B<UNSTABLE>
+
+C<array> of C<string>s. The URLs in the See Also field on the bug.
+
+=item C<severity>
+
+C<string> The current severity of the bug.
+
+=item C<status>
+
+C<string> The current status of the bug.
+
+=item C<summary>
+
+C<string> The summary of this bug.
+
+=item C<target_milestone>
+
+C<string> The milestone that this bug is supposed to be fixed by, or for
+closed bugs, the milestone that it was fixed for.
+
+=item C<update_token>
+
+C<string> The token that you would have to pass to the F<process_bug.cgi>
+page in order to update this bug. This changes every time the bug is
+updated.
+
+This field is not returned to logged-out users.
+
+=item C<url>
+
+B<UNSTABLE>
+
+C<string> A URL that demonstrates the problem described in
+the bug, or is somehow related to the bug report.
+
+=item C<version>
+
+C<string> The version the bug was reported against.
+
+=item C<whiteboard>
+
+C<string> The value of the "status whiteboard" field on the bug.
+
+=item I<custom fields>
+
+Every custom field in this installation will also be included in the
+return value. Most fields are returned as C<string>s. However, some
+field types have different return values:
+
+=over
+
+=item Bug ID Fields - C<int>
+
+=item Multiple-Selection Fields - C<array> of C<string>s.
+
+=item Date/Time Fields - C<dateTime>
+
+=back
+
+=back
+
+=item C<faults> B<EXPERIMENTAL>
+
+An array of hashes that contains invalid bug ids with error messages
+returned for them. Each hash contains the following items:
=over
@@ -325,28 +1804,19 @@
C<int> The numeric bug_id of this bug.
-=item alias
+=item faultString
-C<string> The alias of this bug. If there is no alias or aliases are
-disabled in this Bugzilla, this will be an empty string.
+c<string> This will only be returned for invalid bugs if the C<permissive>
+argument was set when calling Bug.get, and it is an error indicating that
+the bug id was invalid.
-=item summary
+=item faultCode
-C<string> The summary of this bug.
+c<int> This will only be returned for invalid bugs if the C<permissive>
+argument was set when calling Bug.get, and it is the error code for the
+invalid bug error.
-=item creation_time
-
-C<dateTime> When the bug was created.
-
-=item last_change_time
-
-C<dateTime> When the bug was last changed.
-
-=item internals B<UNSTABLE>
-
-A hash. The internals of a L<Bugzilla::Bug> object. This is extremely
-unstable, and you should only rely on this if you absolutely have to. The
-structure of the hash may even change between point releases of Bugzilla.
+=back
=back
@@ -369,16 +1839,359 @@
=back
-=back
-
-=back
-
-
-=head2 Bug Creation and Modification
+=item B<History>
=over
-=item C<create> B<EXPERIMENTAL>
+=item C<permissive> argument added to this method's params in Bugzilla B<3.4>.
+
+=item The following properties were added to this method's return values
+in Bugzilla B<3.4>:
+
+=over
+
+=item For C<bugs>
+
+=over
+
+=item assigned_to
+
+=item component
+
+=item dupe_of
+
+=item is_open
+
+=item priority
+
+=item product
+
+=item resolution
+
+=item severity
+
+=item status
+
+=back
+
+=item C<faults>
+
+=back
+
+=item In Bugzilla B<4.0>, the following items were added to the C<bugs>
+return value: C<blocks>, C<cc>, C<classification>, C<creator>,
+C<deadline>, C<depends_on>, C<estimated_time>, C<is_cc_accessible>,
+C<is_confirmed>, C<is_creator_accessible>, C<groups>, C<keywords>,
+C<op_sys>, C<platform>, C<qa_contact>, C<remaining_time>, C<see_also>,
+C<target_milestone>, C<update_token>, C<url>, C<version>, C<whiteboard>,
+and all custom fields.
+
+=back
+
+
+=back
+
+=head2 history
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Gets the history of changes for particular bugs in the database.
+
+=item B<Params>
+
+=over
+
+=item C<ids>
+
+An array of numbers and strings.
+
+If an element in the array is entirely numeric, it represents a bug_id
+from the Bugzilla database to fetch. If it contains any non-numeric
+characters, it is considered to be a bug alias instead, and the data bug
+with that alias will be loaded.
+
+Note that it's possible for aliases to be disabled in Bugzilla, in which
+case you will be told that you have specified an invalid bug_id if you
+try to specify an alias. (It will be error 100.)
+
+=back
+
+=item B<Returns>
+
+A hash containing a single element, C<bugs>. This is an array of hashes,
+containing the following keys:
+
+=over
+
+=item id
+
+C<int> The numeric id of the bug.
+
+=item alias
+
+C<string> The alias of this bug. If there is no alias or aliases are
+disabled in this Bugzilla, this will be undef.
+
+=item history
+
+C<array> An array of hashes, each hash having the following keys:
+
+=over
+
+=item when
+
+C<dateTime> The date the bug activity/change happened.
+
+=item who
+
+C<string> The login name of the user who performed the bug change.
+
+=item changes
+
+C<array> An array of hashes which contain all the changes that happened
+to the bug at this time (as specified by C<when>). Each hash contains
+the following items:
+
+=over
+
+=item field_name
+
+C<string> The name of the bug field that has changed.
+
+=item removed
+
+C<string> The previous value of the bug field which has been deleted
+by the change.
+
+=item added
+
+C<string> The new value of the bug field which has been added by the change.
+
+=item attachment_id
+
+C<int> The id of the attachment that was changed. This only appears if
+the change was to an attachment, otherwise C<attachment_id> will not be
+present in this hash.
+
+=back
+
+=back
+
+=back
+
+=item B<Errors>
+
+The same as L</get>.
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=back
+
+=back
+
+
+=head2 search
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Allows you to search for bugs based on particular criteria.
+
+=item B<Params>
+
+Unless otherwise specified in the description of a parameter, bugs are
+returned if they match I<exactly> the criteria you specify in these
+parameters. That is, we don't match against substrings--if a bug is in
+the "Widgets" product and you ask for bugs in the "Widg" product, you
+won't get anything.
+
+Criteria are joined in a logical AND. That is, you will be returned
+bugs that match I<all> of the criteria, not bugs that match I<any> of
+the criteria.
+
+Each parameter can be either the type it says, or an array of the types
+it says. If you pass an array, it means "Give me bugs with I<any> of
+these values." For example, if you wanted bugs that were in either
+the "Foo" or "Bar" products, you'd pass:
+
+ product => ['Foo', 'Bar']
+
+Some Bugzillas may treat your arguments case-sensitively, depending
+on what database system they are using. Most commonly, though, Bugzilla is
+not case-sensitive with the arguments passed (because MySQL is the
+most-common database to use with Bugzilla, and MySQL is not case sensitive).
+
+=over
+
+=item C<alias>
+
+C<string> The unique alias for this bug. Note that you can search
+by alias even if the alias field is disabled in this Bugzilla, but
+it's likely that there won't be any aliases set on bugs, in that case.
+
+=item C<assigned_to>
+
+C<string> The login name of a user that a bug is assigned to.
+
+=item C<component>
+
+C<string> The name of the Component that the bug is in. Note that
+if there are multiple Components with the same name, and you search
+for that name, bugs in I<all> those Components will be returned. If you
+don't want this, be sure to also specify the C<product> argument.
+
+=item C<creation_time>
+
+C<dateTime> Searches for bugs that were created at this time or later.
+May not be an array.
+
+=item C<creator>
+
+C<string> The login name of the user who created the bug.
+
+You can also pass this argument with the name C<reporter>, for
+backwards compatibility with older Bugzillas.
+
+=item C<id>
+
+C<int> The numeric id of the bug.
+
+=item C<last_change_time>
+
+C<dateTime> Searches for bugs that were modified at this time or later.
+May not be an array.
+
+=item C<limit>
+
+C<int> Limit the number of results returned to C<int> records.
+
+=item C<offset>
+
+C<int> Used in conjunction with the C<limit> argument, C<offset> defines
+the starting position for the search. For example, given a search that
+would return 100 bugs, setting C<limit> to 10 and C<offset> to 10 would return
+bugs 11 through 20 from the set of 100.
+
+=item C<op_sys>
+
+C<string> The "Operating System" field of a bug.
+
+=item C<platform>
+
+C<string> The Platform (sometimes called "Hardware") field of a bug.
+
+=item C<priority>
+
+C<string> The Priority field on a bug.
+
+=item C<product>
+
+C<string> The name of the Product that the bug is in.
+
+=item C<resolution>
+
+C<string> The current resolution--only set if a bug is closed. You can
+find open bugs by searching for bugs with an empty resolution.
+
+=item C<severity>
+
+C<string> The Severity field on a bug.
+
+=item C<status>
+
+C<string> The current status of a bug (not including its resolution,
+if it has one, which is a separate field above).
+
+=item C<summary>
+
+C<string> Searches for substrings in the single-line Summary field on
+bugs. If you specify an array, then bugs whose summaries match I<any> of the
+passed substrings will be returned.
+
+Note that unlike searching in the Bugzilla UI, substrings are not split
+on spaces. So searching for C<foo bar> will match "This is a foo bar"
+but not "This foo is a bar". C<['foo', 'bar']>, would, however, match
+the second item.
+
+=item C<target_milestone>
+
+C<string> The Target Milestone field of a bug. Note that even if this
+Bugzilla does not have the Target Milestone field enabled, you can
+still search for bugs by Target Milestone. However, it is likely that
+in that case, most bugs will not have a Target Milestone set (it
+defaults to "---" when the field isn't enabled).
+
+=item C<qa_contact>
+
+C<string> The login name of the bug's QA Contact. Note that even if
+this Bugzilla does not have the QA Contact field enabled, you can
+still search for bugs by QA Contact (though it is likely that no bug
+will have a QA Contact set, if the field is disabled).
+
+=item C<url>
+
+C<string> The "URL" field of a bug.
+
+=item C<version>
+
+C<string> The Version field of a bug.
+
+=item C<whiteboard>
+
+C<string> Search the "Status Whiteboard" field on bugs for a substring.
+Works the same as the C<summary> field described above, but searches the
+Status Whiteboard field.
+
+=back
+
+=item B<Returns>
+
+The same as L</get>.
+
+Note that you will only be returned information about bugs that you
+can see. Bugs that you can't see will be entirely excluded from the
+results. So, if you want to see private bugs, you will have to first
+log in and I<then> call this method.
+
+=item B<Errors>
+
+Currently, this function doesn't throw any special errors (other than
+the ones that all webservice functions can throw). If you specify
+an invalid value for a particular field, you just won't get any results
+for that value.
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=item Searching by C<votes> was removed in Bugzilla B<4.0>.
+
+=item The C<reporter> input parameter was renamed to C<creator>
+in Bugzilla B<4.0>.
+
+=back
+
+=back
+
+
+=head1 Bug Creation and Modification
+
+=head2 create
+
+B<STABLE>
=over
@@ -450,6 +2263,17 @@
=item C<cc> (array) - An array of usernames to CC on this bug.
+=item C<comment_is_private> (boolean) - If set to true, the description
+is private, otherwise it is assumed to be public.
+
+=item C<groups> (array) - An array of group names to put this
+bug into. You can see valid group names on the Permissions
+tab of the Preferences screen, or, if you are an administrator,
+in the Groups control panel.
+If you don't specify this argument, then the bug will be added into
+all the groups that are set as being "Default" for this product. (If
+you want to avoid that, you should specify C<groups> as an empty array.)
+
=item C<qa_contact> (username) - If this installation has QA Contacts
enabled, you can set the QA Contact here if you don't want to use
the component's default QA Contact.
@@ -476,7 +2300,8 @@
=item 51 (Invalid Object)
-The component you specified is not valid for this Product.
+You specified a field value that is invalid. The error message will have
+more details.
=item 103 (Invalid Alias)
@@ -501,6 +2326,16 @@
You didn't specify a summary for the bug.
+=item 116 (Dependency Loop)
+
+You specified values in the C<blocks> or C<depends_on> fields
+that would cause a circular dependency between bugs.
+
+=item 120 (Group Restriction Denied)
+
+You tried to restrict the bug to a group which does not exist, or which
+you cannot use with this product.
+
=item 504 (Invalid User)
Either the QA Contact, Assignee, or CC lists have some invalid user
@@ -515,13 +2350,136 @@
=item Before B<3.0.4>, parameters marked as B<Defaulted> were actually
B<Required>, due to a bug in Bugzilla.
+=item The C<groups> argument was added in Bugzilla B<4.0>. Before
+Bugzilla 4.0, bugs were only added into Mandatory groups by this
+method. Since Bugzilla B<4.0.2>, passing an illegal group name will
+throw an error. In Bugzilla 4.0 and 4.0.1, illegal group names were
+silently ignored.
+
+=item The C<comment_is_private> argument was added in Bugzilla B<4.0>.
+Before Bugzilla 4.0, you had to use the undocumented C<commentprivacy>
+argument.
+
+=item Error 116 was added in Bugzilla B<4.0>. Before that, dependency
+loop errors had a generic code of C<32000>.
+
=back
=back
-=item C<add_comment>
-B<EXPERIMENTAL>
+=head2 add_attachment
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+This allows you to add an attachment to a bug in Bugzilla.
+
+=item B<Params>
+
+=over
+
+=item C<ids>
+
+B<Required> C<array> An array of ints and/or strings--the ids
+or aliases of bugs that you want to add this attachment to.
+The same attachment and comment will be added to all
+these bugs.
+
+=item C<data>
+
+B<Required> C<base64> The content of the attachment.
+
+=item C<file_name>
+
+B<Required> C<string> The "file name" that will be displayed
+in the UI for this attachment.
+
+=item C<summary>
+
+B<Required> C<string> A short string describing the
+attachment.
+
+=item C<content_type>
+
+B<Required> C<string> The MIME type of the attachment, like
+C<text/plain> or C<image/png>.
+
+=item C<comment>
+
+C<string> A comment to add along with this attachment.
+
+=item C<is_patch>
+
+C<boolean> True if Bugzilla should treat this attachment as a patch.
+If you specify this, you do not need to specify a C<content_type>.
+The C<content_type> of the attachment will be forced to C<text/plain>.
+
+Defaults to False if not specified.
+
+=item C<is_private>
+
+C<boolean> True if the attachment should be private (restricted
+to the "insidergroup"), False if the attachment should be public.
+
+Defaults to False if not specified.
+
+=back
+
+=item B<Returns>
+
+A single item C<attachments>, which contains the created
+attachments in the same format as the C<attachments> return
+value from L</attachments>.
+
+=item B<Errors>
+
+This method can throw all the same errors as L</get>, plus:
+
+=over
+
+=item 600 (Attachment Too Large)
+
+You tried to attach a file that was larger than Bugzilla will accept.
+
+=item 601 (Invalid MIME Type)
+
+You specified a C<content_type> argument that was blank, not a valid
+MIME type, or not a MIME type that Bugzilla accepts for attachments.
+
+=item 603 (File Name Not Specified)
+
+You did not specify a valid for the C<file_name> argument.
+
+=item 604 (Summary Required)
+
+You did not specify a value for the C<summary> argument.
+
+=item 606 (Empty Data)
+
+You set the "data" field to an empty string.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<4.0>.
+
+=item The C<is_url> parameter was removed in Bugzilla B<4.2>.
+
+=back
+
+=back
+
+
+=head2 add_comment
+
+B<STABLE>
=over
@@ -533,15 +2491,15 @@
=over
-=item C<id> (int) B<Required> - The id or alias of the bug to append a
+=item C<id> (int or string) B<Required> - The id or alias of the bug to append a
comment to.
=item C<comment> (string) B<Required> - The comment to append to the bug.
If this is empty or all whitespace, an error will be thrown saying that
you did not set the C<comment> parameter.
-=item C<private> (boolean) - If set to true, the comment is private, otherwise
-it is assumed to be public.
+=item C<is_private> (boolean) - If set to true, the comment is private,
+otherwise it is assumed to be public.
=item C<work_time> (double) - Adds this many hours to the "Hours Worked"
on the bug. If you are not in the time tracking group, this value will
@@ -550,10 +2508,19 @@
=back
+=item B<Returns>
+
+A hash with one element, C<id> whose value is the id of the newly-created comment.
+
=item B<Errors>
=over
+=item 54 (Hours Worked Too Large)
+
+You specified a C<work_time> larger than the maximum allowed value of
+C<99999.99>.
+
=item 100 (Invalid Bug Alias)
If you specified an alias and either: (a) the Bugzilla you're querying
@@ -563,10 +2530,19 @@
The id you specified doesn't exist in the database.
-=item 108 (Bug Edit Denied)
+=item 109 (Bug Edit Denied)
You did not have the necessary rights to edit the bug.
+=item 113 (Can't Make Private Comments)
+
+You tried to add a private comment, but don't have the necessary rights.
+
+=item 114 (Comment Too Long)
+
+You tried to add a comment longer than the maximum allowed length
+(65,535 characters).
+
=back
=item B<History>
@@ -575,9 +2551,592 @@
=item Added in Bugzilla B<3.2>.
+=item Modified to return the new comment's id in Bugzilla B<3.4>
+
+=item Modified to throw an error if you try to add a private comment
+but can't, in Bugzilla B<3.4>.
+
+=item Before Bugzilla B<3.6>, the C<is_private> argument was called
+C<private>, and you can still call it C<private> for backwards-compatibility
+purposes if you wish.
+
+=item Before Bugzilla B<3.6>, error 54 and error 114 had a generic error
+code of 32000.
+
=back
=back
+=head2 update
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Allows you to update the fields of a bug. Automatically sends emails
+out about the changes.
+
+=item B<Params>
+
+=over
+
+=item C<ids>
+
+Array of C<int>s or C<string>s. The ids or aliases of the bugs that
+you want to modify.
+
+=back
+
+B<Note>: All following fields specify the values you want to set on the
+bugs you are updating.
+
+=over
+
+=item C<alias>
+
+(string) The alias of the bug. You can only set this if you are modifying
+a single bug. If there is more than one bug specified in C<ids>, passing in
+a value for C<alias> will cause an error to be thrown.
+
+=item C<assigned_to>
+
+C<string> The full login name of the user this bug is assigned to.
+
+=item C<blocks>
+
+=item C<depends_on>
+
+C<hash> These specify the bugs that this bug blocks or depends on,
+respectively. To set these, you should pass a hash as the value. The hash
+may contain the following fields:
+
+=over
+
+=item C<add> An array of C<int>s. Bug ids to add to this field.
+
+=item C<remove> An array of C<int>s. Bug ids to remove from this field.
+If the bug ids are not already in the field, they will be ignored.
+
+=item C<set> An array of C<int>s. An exact set of bug ids to set this
+field to, overriding the current value. If you specify C<set>, then C<add>
+and C<remove> will be ignored.
+
+=back
+
+=item C<cc>
+
+C<hash> The users on the cc list. To modify this field, pass a hash, which
+may have the following fields:
+
+=over
+
+=item C<add> Array of C<string>s. User names to add to the CC list.
+They must be full user names, and an error will be thrown if you pass
+in an invalid user name.
+
+=item C<remove> Array of C<string>s. User names to remove from the CC
+list. They must be full user names, and an error will be thrown if you
+pass in an invalid user name.
+
+=back
+
+=item C<is_cc_accessible>
+
+C<boolean> Whether or not users in the CC list are allowed to access
+the bug, even if they aren't in a group that can normally access the bug.
+
+=item C<comment>
+
+C<hash>. A comment on the change. The hash may contain the following fields:
+
+=over
+
+=item C<body> C<string> The actual text of the comment.
+B<Note>: For compatibility with the parameters to L</add_comment>,
+you can also call this field C<comment>, if you want.
+
+=item C<is_private> C<boolean> Whether the comment is private or not.
+If you try to make a comment private and you don't have the permission
+to, an error will be thrown.
+
+=back
+
+=item C<comment_is_private>
+
+C<hash> This is how you update the privacy of comments that are already
+on a bug. This is a hash, where the keys are the C<int> id of comments (not
+their count on a bug, like #1, #2, #3, but their globally-unique id,
+as returned by L</comments>) and the value is a C<boolean> which specifies
+whether that comment should become private (C<true>) or public (C<false>).
+
+The comment ids must be valid for the bug being updated. Thus, it is not
+practical to use this while updating multiple bugs at once, as a single
+comment id will never be valid on multiple bugs.
+
+=item C<component>
+
+C<string> The Component the bug is in.
+
+=item C<deadline>
+
+C<string> The Deadline field--a date specifying when the bug must
+be completed by, in the format C<YYYY-MM-DD>.
+
+=item C<dupe_of>
+
+C<int> The bug that this bug is a duplicate of. If you want to mark
+a bug as a duplicate, the safest thing to do is to set this value
+and I<not> set the C<status> or C<resolution> fields. They will
+automatically be set by Bugzilla to the appropriate values for
+duplicate bugs.
+
+=item C<estimated_time>
+
+C<double> The total estimate of time required to fix the bug, in hours.
+This is the I<total> estimate, not the amount of time remaining to fix it.
+
+=item C<groups>
+
+C<hash> The groups a bug is in. To modify this field, pass a hash, which
+may have the following fields:
+
+=over
+
+=item C<add> Array of C<string>s. The names of groups to add. Passing
+in an invalid group name or a group that you cannot add to this bug will
+cause an error to be thrown.
+
+=item C<remove> Array of C<string>s. The names of groups to remove. Passing
+in an invalid group name or a group that you cannot remove from this bug
+will cause an error to be thrown.
+
+=back
+
+=item C<keywords>
+
+C<hash> Keywords on the bug. To modify this field, pass a hash, which
+may have the following fields:
+
+=over
+
+=item C<add> An array of C<strings>s. The names of keywords to add to
+the field on the bug. Passing something that isn't a valid keyword name
+will cause an error to be thrown.
+
+=item C<remove> An array of C<string>s. The names of keywords to remove
+from the field on the bug. Passing something that isn't a valid keyword
+name will cause an error to be thrown.
+
+=item C<set> An array of C<strings>s. An exact set of keywords to set the
+field to, on the bug. Passing something that isn't a valid keyword name
+will cause an error to be thrown. Specifying C<set> overrides C<add> and
+C<remove>.
+
+=back
+
+=item C<op_sys>
+
+C<string> The Operating System ("OS") field on the bug.
+
+=item C<platform>
+
+C<string> The Platform or "Hardware" field on the bug.
+
+=item C<priority>
+
+C<string> The Priority field on the bug.
+
+=item C<product>
+
+C<string> The name of the product that the bug is in. If you change
+this, you will probably also want to change C<target_milestone>,
+C<version>, and C<component>, since those have different legal
+values in every product.
+
+If you cannot change the C<target_milestone> field, it will be reset to
+the default for the product, when you move a bug to a new product.
+
+You may also wish to add or remove groups, as which groups are
+valid on a bug depends on the product. Groups that are not valid
+in the new product will be automatically removed, and groups which
+are mandatory in the new product will be automaticaly added, but no
+other automatic group changes will be done.
+
+Note that users can only move a bug into a product if they would
+normally have permission to file new bugs in that product.
+
+=item C<qa_contact>
+
+C<string> The full login name of the bug's QA Contact.
+
+=item C<is_creator_accessible>
+
+C<boolean> Whether or not the bug's reporter is allowed to access
+the bug, even if he or she isn't in a group that can normally access
+the bug.
+
+=item C<remaining_time>
+
+C<double> How much work time is remaining to fix the bug, in hours.
+If you set C<work_time> but don't explicitly set C<remaining_time>,
+then the C<work_time> will be deducted from the bug's C<remaining_time>.
+
+=item C<reset_assigned_to>
+
+C<boolean> If true, the C<assigned_to> field will be reset to the
+default for the component that the bug is in. (If you have set the
+component at the same time as using this, then the component used
+will be the new component, not the old one.)
+
+=item C<reset_qa_contact>
+
+C<boolean> If true, the C<qa_contact> field will be reset to the
+default for the component that the bug is in. (If you have set the
+component at the same time as using this, then the component used
+will be the new component, not the old one.)
+
+=item C<resolution>
+
+C<string> The current resolution. May only be set if you are closing
+a bug or if you are modifying an already-closed bug. Attempting to set
+the resolution to I<any> value (even an empty or null string) on an
+open bug will cause an error to be thrown.
+
+If you change the C<status> field to an open status, the resolution
+field will automatically be cleared, so you don't have to clear it
+manually.
+
+=item C<see_also>
+
+C<hash> The See Also field on a bug, specifying URLs to bugs in other
+bug trackers. To modify this field, pass a hash, which may have the
+following fields:
+
+=over
+
+=item C<add> An array of C<strings>s. URLs to add to the field.
+Each URL must be a valid URL to a bug-tracker, or an error will
+be thrown.
+
+=item C<remove> An array of C<string>s. URLs to remove from the field.
+Invalid URLs will be ignored.
+
+=back
+
+=item C<severity>
+
+C<string> The Severity field of a bug.
+
+=item C<status>
+
+C<string> The status you want to change the bug to. Note that if
+a bug is changing from open to closed, you should also specify
+a C<resolution>.
+
+=item C<summary>
+
+C<string> The Summary field of the bug.
+
+=item C<target_milestone>
+
+C<string> The bug's Target Milestone.
+
+=item C<url>
+
+C<string> The "URL" field of a bug.
+
+=item C<version>
+
+C<string> The bug's Version field.
+
+=item C<whiteboard>
+
+C<string> The Status Whiteboard field of a bug.
+
+=item C<work_time>
+
+C<double> The number of hours worked on this bug as part of this change.
+If you set C<work_time> but don't explicitly set C<remaining_time>,
+then the C<work_time> will be deducted from the bug's C<remaining_time>.
+
+=back
+
+You can also set the value of any custom field by passing its name as
+a parameter, and the value to set the field to. For multiple-selection
+fields, the value should be an array of strings.
+
+=item B<Returns>
+
+A C<hash> with a single field, "bugs". This points to an array of hashes
+with the following fields:
+
+=over
+
+=item C<id>
+
+C<int> The id of the bug that was updated.
+
+=item C<alias>
+
+C<string> The alias of the bug that was updated, if aliases are enabled and
+this bug has an alias.
+
+=item C<last_change_time>
+
+C<dateTime> The exact time that this update was done at, for this bug.
+If no update was done (that is, no fields had their values changed and
+no comment was added) then this will instead be the last time the bug
+was updated.
+
+=item C<changes>
+
+C<hash> The changes that were actually done on this bug. The keys are
+the names of the fields that were changed, and the values are a hash
+with two keys:
+
+=over
+
+=item C<added> (C<string>) The values that were added to this field,
+possibly a comma-and-space-separated list if multiple values were added.
+
+=item C<removed> (C<string>) The values that were removed from this
+field, possibly a comma-and-space-separated list if multiple values were
+removed.
+
+=back
+
+=back
+
+Here's an example of what a return value might look like:
+
+ {
+ bugs => [
+ {
+ id => 123,
+ alias => 'foo',
+ last_change_time => '2010-01-01T12:34:56',
+ changes => {
+ status => {
+ removed => 'NEW',
+ added => 'ASSIGNED'
+ },
+ keywords => {
+ removed => 'bar',
+ added => 'qux, quo, qui',
+ }
+ },
+ }
+ ]
+ }
+
+Currently, some fields are not tracked in changes: C<comment>,
+C<comment_is_private>, and C<work_time>. This means that they will not
+show up in the return value even if they were successfully updated.
+This may change in a future version of Bugzilla.
+
+=item B<Errors>
+
+This function can throw all of the errors that L</get>, L</create>,
+and L</add_comment> can throw, plus:
+
+=over
+
+=item 50 (Empty Field)
+
+You tried to set some field to be empty, but that field cannot be empty.
+The error message will have more details.
+
+=item 52 (Input Not A Number)
+
+You tried to set a numeric field to a value that wasn't numeric.
+
+=item 54 (Number Too Large)
+
+You tried to set a numeric field to a value larger than that field can
+accept.
+
+=item 55 (Number Too Small)
+
+You tried to set a negative value in a numeric field that does not accept
+negative values.
+
+=item 56 (Bad Date/Time)
+
+You specified an invalid date or time in a date/time field (such as
+the C<deadline> field or a custom date/time field).
+
+=item 112 (See Also Invalid)
+
+You attempted to add an invalid value to the C<see_also> field.
+
+=item 115 (Permission Denied)
+
+You don't have permission to change a particular field to a particular value.
+The error message will have more detail.
+
+=item 116 (Dependency Loop)
+
+You specified a value in the C<blocks> or C<depends_on> fields that causes
+a dependency loop.
+
+=item 117 (Invalid Comment ID)
+
+You specified a comment id in C<comment_is_private> that isn't on this bug.
+
+=item 118 (Duplicate Loop)
+
+You specified a value for C<dupe_of> that causes an infinite loop of
+duplicates.
+
+=item 119 (dupe_of Required)
+
+You changed the resolution to C<DUPLICATE> but did not specify a value
+for the C<dupe_of> field.
+
+=item 120 (Group Add/Remove Denied)
+
+You tried to add or remove a group that you don't have permission to modify
+for this bug, or you tried to add a group that isn't valid in this product.
+
+=item 121 (Resolution Required)
+
+You tried to set the C<status> field to a closed status, but you didn't
+specify a resolution.
+
+=item 122 (Resolution On Open Status)
+
+This bug has an open status, but you specified a value for the C<resolution>
+field.
+
+=item 123 (Invalid Status Transition)
+
+You tried to change from one status to another, but the status workflow
+rules don't allow that change.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<4.0>.
+
+=back
+
+=back
+
+
+=head2 update_see_also
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Adds or removes URLs for the "See Also" field on bugs. These URLs must
+point to some valid bug in some Bugzilla installation or in Launchpad.
+
+=item B<Params>
+
+=over
+
+=item C<ids>
+
+Array of C<int>s or C<string>s. The ids or aliases of bugs that you want
+to modify.
+
+=item C<add>
+
+Array of C<string>s. URLs to Bugzilla bugs. These URLs will be added to
+the See Also field. They must be valid URLs to C<show_bug.cgi> in a
+Bugzilla installation or to a bug filed at launchpad.net.
+
+If the URLs don't start with C<http://> or C<https://>, it will be assumed
+that C<http://> should be added to the beginning of the string.
+
+It is safe to specify URLs that are already in the "See Also" field on
+a bug--they will just be silently ignored.
+
+=item C<remove>
+
+Array of C<string>s. These URLs will be removed from the See Also field.
+You must specify the full URL that you want removed. However, matching
+is done case-insensitively, so you don't have to specify the URL in
+exact case, if you don't want to.
+
+If you specify a URL that is not in the See Also field of a particular bug,
+it will just be silently ignored. Invaild URLs are currently silently ignored,
+though this may change in some future version of Bugzilla.
+
+=back
+
+NOTE: If you specify the same URL in both C<add> and C<remove>, it will
+be I<added>. (That is, C<add> overrides C<remove>.)
+
+=item B<Returns>
+
+C<changes>, a hash where the keys are numeric bug ids and the contents
+are a hash with one key, C<see_also>. C<see_also> points to a hash, which
+contains two keys, C<added> and C<removed>. These are arrays of strings,
+representing the actual changes that were made to the bug.
+
+Here's a diagram of what the return value looks like for updating
+bug ids 1 and 2:
+
+ {
+ changes => {
+ 1 => {
+ see_also => {
+ added => (an array of bug URLs),
+ removed => (an array of bug URLs),
+ }
+ },
+ 2 => {
+ see_also => {
+ added => (an array of bug URLs),
+ removed => (an array of bug URLs),
+ }
+ }
+ }
+ }
+
+This return value allows you to tell what this method actually did. It is in
+this format to be compatible with the return value of a future C<Bug.update>
+method.
+
+=item B<Errors>
+
+This method can throw all of the errors that L</get> throws, plus:
+
+=over
+
+=item 109 (Bug Edit Denied)
+
+You did not have the necessary rights to edit the bug.
+
+=item 112 (Invalid Bug URL)
+
+One of the URLs you provided did not look like a valid bug URL.
+
+=item 115 (See Also Edit Denied)
+
+You did not have the necessary rights to edit the See Also field for
+this bug.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=item Before Bugzilla B<3.6>, error 115 had a generic error code of 32000.
+
+=back
+
=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/Bugzilla.pm b/Websites/bugs.webkit.org/Bugzilla/WebService/Bugzilla.pm
index 53e0d7d..efc8223 100644
--- a/Websites/bugs.webkit.org/Bugzilla/WebService/Bugzilla.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/Bugzilla.pm
@@ -21,10 +21,9 @@
use strict;
use base qw(Bugzilla::WebService);
use Bugzilla::Constants;
-use Bugzilla::Hook;
-import SOAP::Data qw(type);
+use Bugzilla::Util qw(datetime_from);
-use Time::Zone;
+use DateTime;
# Basic info that is needed before logins
use constant LOGIN_EXEMPT => {
@@ -32,27 +31,54 @@
version => 1,
};
+use constant READ_ONLY => qw(
+ extensions
+ timezone
+ time
+ version
+);
+
sub version {
- return { version => type('string')->value(BUGZILLA_VERSION) };
+ my $self = shift;
+ return { version => $self->type('string', BUGZILLA_VERSION) };
}
sub extensions {
- my $extensions = Bugzilla::Hook::enabled_plugins();
- foreach my $name (keys %$extensions) {
- my $info = $extensions->{$name};
- foreach my $data (keys %$info)
- {
- $extensions->{$name}->{$data} = type('string')->value($info->{$data});
- }
+ my $self = shift;
+
+ my %retval;
+ foreach my $extension (@{ Bugzilla->extensions }) {
+ my $version = $extension->VERSION || 0;
+ my $name = $extension->NAME;
+ $retval{$name}->{version} = $self->type('string', $version);
}
- return { extensions => $extensions };
+ return { extensions => \%retval };
}
sub timezone {
- my $offset = tz_offset();
- $offset = (($offset / 60) / 60) * 100;
- $offset = sprintf('%+05d', $offset);
- return { timezone => type('string')->value($offset) };
+ my $self = shift;
+ # All Webservices return times in UTC; Use UTC here for backwards compat.
+ return { timezone => $self->type('string', "+0000") };
+}
+
+sub time {
+ my ($self) = @_;
+ # All Webservices return times in UTC; Use UTC here for backwards compat.
+ # Hardcode values where appropriate
+ my $dbh = Bugzilla->dbh;
+
+ my $db_time = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $db_time = datetime_from($db_time, 'UTC');
+ my $now_utc = DateTime->now();
+
+ return {
+ db_time => $self->type('dateTime', $db_time),
+ web_time => $self->type('dateTime', $now_utc),
+ web_time_utc => $self->type('dateTime', $now_utc),
+ tz_name => $self->type('string', 'UTC'),
+ tz_offset => $self->type('string', '+0000'),
+ tz_short_name => $self->type('string', 'UTC'),
+ };
}
1;
@@ -72,9 +98,7 @@
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
-=over
-
-=item C<version>
+=head2 version
B<STABLE>
@@ -95,7 +119,7 @@
=back
-=item C<extensions>
+=head2 extensions
B<EXPERIMENTAL>
@@ -110,10 +134,21 @@
=item B<Returns>
-A hash with a single item, C<extesions>. This points to a hash. I<That> hash
-contains the names of extensions as keys, and information about the extension
-as values. One of the values that must be returned is the 'version' of the
-extension
+A hash with a single item, C<extensions>. This points to a hash. I<That> hash
+contains the names of extensions as keys, and the values are a hash.
+That hash contains a single key C<version>, which is the version of the
+extension, or C<0> if the extension hasn't defined a version.
+
+The return value looks something like this:
+
+ extensions => {
+ Example => {
+ version => '3.6',
+ },
+ BmpConvert => {
+ version => '1.0',
+ },
+ }
=item B<History>
@@ -121,21 +156,24 @@
=item Added in Bugzilla B<3.2>.
+=item As of Bugzilla B<3.6>, the names of extensions are canonical names
+that the extensions define themselves. Before 3.6, the names of the
+extensions depended on the directory they were in on the Bugzilla server.
+
=back
=back
-=item C<timezone>
+=head2 timezone
-B<STABLE>
+B<DEPRECATED> This method may be removed in a future version of Bugzilla.
+Use L</time> instead.
=over
=item B<Description>
-Returns the timezone of the server Bugzilla is running on. This is
-important because all dates/times that the webservice interface
-returns will be in this timezone.
+Returns the timezone that Bugzilla expects dates and times in.
=item B<Params> (none)
@@ -144,6 +182,89 @@
A hash with a single item, C<timezone>, that is the timezone offset as a
string in (+/-)XXXX (RFC 2822) format.
+=item B<History>
+
+=over
+
+=item As of Bugzilla B<3.6>, the timezone returned is always C<+0000>
+(the UTC timezone).
+
+=back
+
+=back
+
+
+=head2 time
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+Gets information about what time the Bugzilla server thinks it is, and
+what timezone it's running in.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+A struct with the following items:
+
+=over
+
+=item C<db_time>
+
+C<dateTime> The current time in UTC, according to the Bugzilla
+I<database server>.
+
+Note that Bugzilla assumes that the database and the webserver are running
+in the same time zone. However, if the web server and the database server
+aren't synchronized for some reason, I<this> is the time that you should
+rely on for doing searches and other input to the WebService.
+
+=item C<web_time>
+
+C<dateTime> This is the current time in UTC, according to Bugzilla's
+I<web server>.
+
+This might be different by a second from C<db_time> since this comes from
+a different source. If it's any more different than a second, then there is
+likely some problem with this Bugzilla instance. In this case you should
+rely on the C<db_time>, not the C<web_time>.
+
+=item C<web_time_utc>
+
+Identical to C<web_time>. (Exists only for backwards-compatibility with
+versions of Bugzilla before 3.6.)
+
+=item C<tz_name>
+
+C<string> The literal string C<UTC>. (Exists only for backwards-compatibility
+with versions of Bugzilla before 3.6.)
+
+=item C<tz_short_name>
+
+C<string> The literal string C<UTC>. (Exists only for backwards-compatibility
+with versions of Bugzilla before 3.6.)
+
+=item C<tz_offset>
+
+C<string> The literal string C<+0000>. (Exists only for backwards-compatibility
+with versions of Bugzilla before 3.6.)
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=item As of Bugzilla B<3.6>, this method returns all data as though the server
+were in the UTC timezone, instead of returning information in the server's
+local timezone.
+
=back
=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/Constants.pm b/Websites/bugs.webkit.org/Bugzilla/WebService/Constants.pm
index 078d76e..59aab9b 100644
--- a/Websites/bugs.webkit.org/Bugzilla/WebService/Constants.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/Constants.pm
@@ -20,13 +20,13 @@
use strict;
use base qw(Exporter);
-@Bugzilla::WebService::Constants::EXPORT = qw(
+our @EXPORT = qw(
WS_ERROR_CODE
ERROR_UNKNOWN_FATAL
ERROR_UNKNOWN_TRANSIENT
+ XMLRPC_CONTENT_TYPE_WHITELIST
- ERROR_AUTH_NODATA
- ERROR_UNIMPLEMENTED
+ WS_DISPATCH
);
# This maps the error names in global/*-error.html.tmpl to numbers.
@@ -48,12 +48,19 @@
# comment that it was retired. Also, if an error changes its name, you'll
# have to fix it here.
use constant WS_ERROR_CODE => {
- # Generic Bugzilla::Object errors are 50-99.
- object_name_not_specified => 50,
+ # Generic errors (Bugzilla::Object and others) are 50-99.
+ object_not_specified => 50,
+ reassign_to_empty => 50,
param_required => 50,
+ params_required => 50,
+ undefined_field => 50,
object_does_not_exist => 51,
- # Error 52 exists only in later releases.
+ param_must_be_numeric => 52,
+ number_not_numeric => 52,
param_invalid => 53,
+ number_too_large => 54,
+ number_too_small => 55,
+ illegal_date => 56,
# Bug errors usually occupy the 100-200 range.
improper_bug_id_field_value => 100,
bug_id_does_not_exist => 101,
@@ -64,6 +71,7 @@
alias_in_use => 103,
alias_is_numeric => 103,
alias_has_comma_or_space => 103,
+ multiple_alias_not_allowed => 103,
# Misc. bug field errors
illegal_field => 104,
freetext_too_long => 104,
@@ -81,32 +89,120 @@
invalid_field_name => 108,
# Not authorized to edit the bug
product_edit_denied => 109,
+ # Comment-related errors
+ comment_is_private => 110,
+ comment_id_invalid => 111,
+ comment_too_long => 114,
+ comment_invalid_isprivate => 117,
+ # See Also errors
+ bug_url_invalid => 112,
+ bug_url_too_long => 112,
+ # Insidergroup Errors
+ user_not_insider => 113,
+ # Note: 114 is above in the Comment-related section.
+ # Bug update errors
+ illegal_change => 115,
+ # Dependency errors
+ dependency_loop_single => 116,
+ dependency_loop_multi => 116,
+ # Note: 117 is above in the Comment-related section.
+ # Dup errors
+ dupe_loop_detected => 118,
+ dupe_id_required => 119,
+ # Bug-related group errors
+ group_invalid_removal => 120,
+ group_restriction_not_allowed => 120,
+ # Status/Resolution errors
+ missing_resolution => 121,
+ resolution_not_allowed => 122,
+ illegal_bug_status_transition => 123,
# Authentication errors are usually 300-400.
invalid_username_or_password => 300,
account_disabled => 301,
auth_invalid_email => 302,
extern_id_conflict => -303,
+ auth_failure => 304,
+ password_current_too_short => 305,
+
+ # Except, historically, AUTH_NODATA, which is 410.
+ login_required => 410,
# User errors are 500-600.
account_exists => 500,
illegal_email_address => 501,
+ auth_cant_create_account => 501,
account_creation_disabled => 501,
account_creation_restricted => 501,
password_too_short => 502,
- password_too_long => 503,
+ # Error 503 password_too_long no longer exists.
invalid_username => 504,
# This is from strict_isolation, but it also basically means
# "invalid user."
invalid_user_group => 504,
+ user_access_by_id_denied => 505,
+ user_access_by_match_denied => 505,
+
+ # Attachment errors are 600-700.
+ file_too_large => 600,
+ invalid_content_type => 601,
+ # Error 602 attachment_illegal_url no longer exists.
+ file_not_specified => 603,
+ missing_attachment_description => 604,
+ # Error 605 attachment_url_disabled no longer exists.
+ zero_length_file => 606,
+
+ # Product erros are 700-800
+ product_blank_name => 700,
+ product_name_too_long => 701,
+ product_name_already_in_use => 702,
+ product_name_diff_in_case => 702,
+ product_must_have_description => 703,
+ product_must_have_version => 704,
+ product_must_define_defaultmilestone => 705,
+
+ # Group errors are 800-900
+ empty_group_name => 800,
+ group_exists => 801,
+ empty_group_description => 802,
+ invalid_regexp => 803,
+
+ # Errors thrown by the WebService itself. The ones that are negative
+ # conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
+ xmlrpc_invalid_value => -32600,
+ unknown_method => -32601,
+ json_rpc_post_only => 32610,
+ json_rpc_invalid_callback => 32611,
+ xmlrpc_illegal_content_type => 32612,
+ json_rpc_illegal_content_type => 32613,
};
# These are the fallback defaults for errors not in ERROR_CODE.
use constant ERROR_UNKNOWN_FATAL => -32000;
use constant ERROR_UNKNOWN_TRANSIENT => 32000;
-use constant ERROR_AUTH_NODATA => 410;
-use constant ERROR_UNIMPLEMENTED => 910;
use constant ERROR_GENERAL => 999;
+use constant XMLRPC_CONTENT_TYPE_WHITELIST => qw(
+ text/xml
+ application/xml
+);
+
+sub WS_DISPATCH {
+ # We "require" here instead of "use" above to avoid a dependency loop.
+ require Bugzilla::Hook;
+ my %hook_dispatch;
+ Bugzilla::Hook::process('webservice', { dispatch => \%hook_dispatch });
+
+ my $dispatch = {
+ 'Bugzilla' => 'Bugzilla::WebService::Bugzilla',
+ 'Bug' => 'Bugzilla::WebService::Bug',
+ 'User' => 'Bugzilla::WebService::User',
+ 'Product' => 'Bugzilla::WebService::Product',
+ 'Group' => 'Bugzilla::WebService::Group',
+ %hook_dispatch
+ };
+ return $dispatch;
+};
+
1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/Group.pm b/Websites/bugs.webkit.org/Bugzilla/WebService/Group.pm
new file mode 100644
index 0000000..65feb7a
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/Group.pm
@@ -0,0 +1,141 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s):
+# Carole Pryfer <carole.pryfer@dgfip.finances.gouv.fr>
+
+package Bugzilla::WebService::Group;
+
+use strict;
+use base qw(Bugzilla::WebService);
+use Bugzilla::Constants;
+use Bugzilla::Error;
+
+sub create {
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->user->in_group('creategroups')
+ || ThrowUserError("auth_failure", { group => "creategroups",
+ action => "add",
+ object => "group"});
+ # Create group
+ my $group = Bugzilla::Group->create({
+ name => $params->{name},
+ description => $params->{description},
+ userregexp => $params->{user_regexp},
+ isactive => $params->{is_active},
+ isbuggroup => 1,
+ icon_url => $params->{icon_url}
+ });
+ return { id => $self->type('int', $group->id) };
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Webservice::Group - The API for creating, changing, and getting
+information about Groups.
+
+=head1 DESCRIPTION
+
+This part of the Bugzilla API allows you to create Groups and
+get information about them.
+
+=head1 METHODS
+
+See L<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
+
+=head1 Group Creation
+
+=head2 create
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+This allows you to create a new group in Bugzilla.
+
+=item B<Params>
+
+Some params must be set, or an error will be thrown. These params are
+marked B<Required>.
+
+=over
+
+=item C<name>
+
+B<Required> C<string> A short name for this group. Must be unique. This
+is not usually displayed in the user interface, except in a few places.
+
+=item C<description>
+
+B<Required> C<string> A human-readable name for this group. Should be
+relatively short. This is what will normally appear in the UI as the
+name of the group.
+
+=item C<user_regexp>
+
+C<string> A regular expression. Any user whose Bugzilla username matches
+this regular expression will automatically be granted membership in this group.
+
+=item C<is_active>
+
+C<boolean> C<True> if new group can be used for bugs, C<False> if this
+is a group that will only contain users and no bugs will be restricted
+to it.
+
+=item C<icon_url>
+
+C<string> A URL pointing to a small icon used to identify the group.
+This icon will show up next to users' names in various parts of Bugzilla
+if they are in this group.
+
+=back
+
+=item B<Returns>
+
+A hash with one element, C<id>. This is the id of the newly-created group.
+
+=item B<Errors>
+
+=over
+
+=item 800 (Empty Group Name)
+
+You must specify a value for the C<name> field.
+
+=item 801 (Group Exists)
+
+There is already another group with the same C<name>.
+
+=item 802 (Group Missing Description)
+
+You must specify a value for the C<description> field.
+
+=item 803 (Group Regexp Invalid)
+
+You specified an invalid regular expression in the C<user_regexp> field.
+
+=back
+
+=back
+
+=cut
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/Product.pm b/Websites/bugs.webkit.org/Bugzilla/WebService/Product.pm
index cd86296..3cd0d0a 100644
--- a/Websites/bugs.webkit.org/Bugzilla/WebService/Product.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/Product.pm
@@ -14,6 +14,7 @@
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Mads Bondo Dydensborg <mbd@dbc.dk>
+# Byron Jones <glob@mozilla.com>
package Bugzilla::WebService::Product;
@@ -21,7 +22,22 @@
use base qw(Bugzilla::WebService);
use Bugzilla::Product;
use Bugzilla::User;
-import SOAP::Data qw(type);
+use Bugzilla::Error;
+use Bugzilla::Constants;
+use Bugzilla::WebService::Constants;
+use Bugzilla::WebService::Util qw(validate filter filter_wants);
+
+use constant READ_ONLY => qw(
+ get
+ get_accessible_products
+ get_enterable_products
+ get_selectable_products
+);
+
+use constant FIELD_MAP => {
+ has_unconfirmed => 'allows_unconfirmed',
+ is_open => 'isactive',
+};
##################################################
# Add aliases here for method name compatibility #
@@ -44,34 +60,153 @@
return {ids => [map {$_->id} @{Bugzilla->user->get_accessible_products}]};
}
-# Get a list of actual products, based on list of ids
+# Get a list of actual products, based on list of ids or names
sub get {
- my ($self, $params) = @_;
+ my ($self, $params) = validate(@_, 'ids', 'names');
# Only products that are in the users accessible products,
# can be allowed to be returned
my $accessible_products = Bugzilla->user->get_accessible_products;
- # Create a hash with the ids the user wants
- my %ids = map { $_ => 1 } @{$params->{ids}};
-
- # Return the intersection of this, by grepping the ids from
- # accessible products.
- my @requested_accessible = grep { $ids{$_->id} } @$accessible_products;
+ my @requested_accessible;
+
+ if (defined $params->{ids}) {
+ # Create a hash with the ids the user wants
+ my %ids = map { $_ => 1 } @{$params->{ids}};
+
+ # Return the intersection of this, by grepping the ids from
+ # accessible products.
+ push(@requested_accessible,
+ grep { $ids{$_->id} } @$accessible_products);
+ }
+
+ if (defined $params->{names}) {
+ # Create a hash with the names the user wants
+ my %names = map { lc($_) => 1 } @{$params->{names}};
+
+ # Return the intersection of this, by grepping the names from
+ # accessible products, union'ed with products found by ID to
+ # avoid duplicates
+ foreach my $product (grep { $names{lc $_->name} }
+ @$accessible_products) {
+ next if grep { $_->id == $product->id }
+ @requested_accessible;
+ push @requested_accessible, $product;
+ }
+ }
# Now create a result entry for each.
- my @products =
- map {{
- internals => $_,
- id => type('int')->value($_->id),
- name => type('string')->value($_->name),
- description => type('string')->value($_->description),
- }
- } @requested_accessible;
-
+ my @products = map { $self->_product_to_hash($params, $_) }
+ @requested_accessible;
return { products => \@products };
}
+sub create {
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+ Bugzilla->user->in_group('editcomponents')
+ || ThrowUserError("auth_failure", { group => "editcomponents",
+ action => "add",
+ object => "products"});
+ # Create product
+ my $args = {
+ name => $params->{name},
+ description => $params->{description},
+ version => $params->{version},
+ defaultmilestone => $params->{default_milestone},
+ # create_series has no default value.
+ create_series => defined $params->{create_series} ?
+ $params->{create_series} : 1
+ };
+ foreach my $field (qw(has_unconfirmed is_open classification)) {
+ if (defined $params->{$field}) {
+ my $name = FIELD_MAP->{$field} || $field;
+ $args->{$name} = $params->{$field};
+ }
+ }
+ my $product = Bugzilla::Product->create($args);
+ return { id => $self->type('int', $product->id) };
+}
+
+sub _product_to_hash {
+ my ($self, $params, $product) = @_;
+
+ my $field_data = {
+ id => $self->type('int', $product->id),
+ name => $self->type('string', $product->name),
+ description => $self->type('string', $product->description),
+ is_active => $self->type('boolean', $product->is_active),
+ default_milestone => $self->type('string', $product->default_milestone),
+ has_unconfirmed => $self->type('boolean', $product->allows_unconfirmed),
+ classification => $self->type('string', $product->classification->name),
+ };
+ if (filter_wants($params, 'components')) {
+ $field_data->{components} = [map {
+ $self->_component_to_hash($_)
+ } @{$product->components}];
+ }
+ if (filter_wants($params, 'versions')) {
+ $field_data->{versions} = [map {
+ $self->_version_to_hash($_)
+ } @{$product->versions}];
+ }
+ if (filter_wants($params, 'milestones')) {
+ $field_data->{milestones} = [map {
+ $self->_milestone_to_hash($_)
+ } @{$product->milestones}];
+ }
+ return filter($params, $field_data);
+}
+
+sub _component_to_hash {
+ my ($self, $component) = @_;
+ return {
+ id =>
+ $self->type('int', $component->id),
+ name =>
+ $self->type('string', $component->name),
+ description =>
+ $self->type('string' , $component->description),
+ default_assigned_to =>
+ $self->type('string' , $component->default_assignee->login),
+ default_qa_contact =>
+ $self->type('string' , $component->default_qa_contact->login),
+ sort_key => # sort_key is returned to match Bug.fields
+ 0,
+ is_active =>
+ $self->type('boolean', $component->is_active),
+ };
+}
+
+sub _version_to_hash {
+ my ($self, $version) = @_;
+ return {
+ id =>
+ $self->type('int', $version->id),
+ name =>
+ $self->type('string', $version->name),
+ sort_key => # sort_key is returened to match Bug.fields
+ 0,
+ is_active =>
+ $self->type('boolean', $version->is_active),
+ };
+}
+
+sub _milestone_to_hash {
+ my ($self, $milestone) = @_;
+ return {
+ id =>
+ $self->type('int', $milestone->id),
+ name =>
+ $self->type('string', $milestone->name),
+ sort_key =>
+ $self->type('int', $milestone->sortkey),
+ is_active =>
+ $self->type('boolean', $milestone->is_active),
+ };
+}
+
1;
__END__
@@ -90,11 +225,9 @@
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
-=head2 List Products
+=head1 List Products
-=over
-
-=item C<get_selectable_products>
+=head2 get_selectable_products
B<EXPERIMENTAL>
@@ -115,7 +248,7 @@
=back
-=item C<get_enterable_products>
+=head2 get_enterable_products
B<EXPERIMENTAL>
@@ -137,7 +270,7 @@
=back
-=item C<get_accessible_products>
+=head2 get_accessible_products
B<UNSTABLE>
@@ -159,7 +292,7 @@
=back
-=item C<get>
+=head2 get
B<EXPERIMENTAL>
@@ -173,17 +306,113 @@
=item B<Params>
-A hash containing one item, C<ids>, that is an array of product ids.
+In addition to the parameters below, this method also accepts the
+standard L<include_fields|Bugzilla::WebService/include_fields> and
+L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
+=over
+
+=item C<ids>
+
+An array of product ids
+
+=item C<names>
+
+An array of product names
+
+=back
=item B<Returns>
A hash containing one item, C<products>, that is an array of
hashes. Each hash describes a product, and has the following items:
-C<id>, C<name>, C<description>, and C<internals>. The C<id> item is
-the id of the product. The C<name> item is the name of the
-product. The C<description> is the description of the
-product. Finally, the C<internals> is an internal representation of
-the product.
+
+=over
+
+=item C<id>
+
+C<int> An integer id uniquely identifying the product in this installation only.
+
+=item C<name>
+
+C<string> The name of the product. This is a unique identifier for the
+product.
+
+=item C<description>
+
+C<string> A description of the product, which may contain HTML.
+
+=item C<is_active>
+
+C<boolean> A boolean indicating if the product is active.
+
+=item C<default_milestone>
+
+C<string> The name of the default milestone for the product.
+
+=item C<has_unconfirmed>
+
+C<boolean> Indicates whether the UNCONFIRMED bug status is available
+for this product.
+
+=item C<classification>
+
+C<string> The classification name for the product.
+
+=item C<components>
+
+C<array> An array of hashes, where each hash describes a component, and has the
+following items:
+
+=over
+
+=item C<id>
+
+C<int> An integer id uniquely identifying the component in this installation
+only.
+
+=item C<name>
+
+C<string> The name of the component. This is a unique identifier for this
+component.
+
+=item C<description>
+
+C<string> A description of the component, which may contain HTML.
+
+=item C<default_assigned_to>
+
+C<string> The login name of the user to whom new bugs will be assigned by
+default.
+
+=item C<default_qa_contact>
+
+C<string> The login name of the user who will be set as the QA Contact for
+new bugs by default.
+
+=item C<sort_key>
+
+C<int> Components, when displayed in a list, are sorted first by this integer
+and then secondly by their name.
+
+=item C<is_active>
+
+C<boolean> A boolean indicating if the component is active. Inactive
+components are not enabled for new bugs.
+
+=back
+
+=item C<versions>
+
+C<array> An array of hashes, where each hash describes a version, and has the
+following items: C<name>, C<sort_key> and C<is_active>.
+
+=item C<milestones>
+
+C<array> An array of hashes, where each hash describes a milestone, and has the
+following items: C<name>, C<sort_key> and C<is_active>.
+
+=back
Note, that if the user tries to access a product that is not in the
list of accessible products for the user, or a product that does not
@@ -192,7 +421,112 @@
=item B<Errors> (none)
+=item B<History>
+
+=over
+
+=item In Bugzilla B<4.2>, C<names> was added as an input parameter.
+
+=item In Bugzilla B<4.2>, C<classification>, C<components>, C<versions>,
+C<milestones>, C<default_milestone> and C<has_unconfirmed> were added to
+the fields returned by C<get> as a replacement for C<internals>, which has
+been removed.
+
=back
=back
+=head1 Product Creation
+
+=head2 create
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+This allows you to create a new product in Bugzilla.
+
+=item B<Params>
+
+Some params must be set, or an error will be thrown. These params are
+marked B<Required>.
+
+=over
+
+=item C<name>
+
+B<Required> C<string> The name of this product. Must be globally unique
+within Bugzilla.
+
+=item C<description>
+
+B<Required> C<string> A description for this product. Allows some simple HTML.
+
+=item C<version>
+
+B<Required> C<string> The default version for this product.
+
+=item C<has_unconfirmed>
+
+C<boolean> Allow the UNCONFIRMED status to be set on bugs in this product.
+Default: true.
+
+=item C<classification>
+
+C<string> The name of the Classification which contains this product.
+
+=item C<default_milestone>
+
+C<string> The default milestone for this product. Default: '---'.
+
+=item C<is_open>
+
+C<boolean> True if the product is currently allowing bugs to be entered
+into it. Default: true.
+
+=item C<create_series>
+
+C<boolean> True if you want series for New Charts to be created for this
+new product. Default: true.
+
+=back
+
+=item B<Returns>
+
+A hash with one element, id. This is the id of the newly-filed product.
+
+=item B<Errors>
+
+=over
+
+=item 51 (Classification does not exist)
+
+You must specify an existing classification name.
+
+=item 700 (Product blank name)
+
+You must specify a non-blank name for this product.
+
+=item 701 (Product name too long)
+
+The name specified for this product was longer than the maximum
+allowed length.
+
+=item 702 (Product name already exists)
+
+You specified the name of a product that already exists.
+(Product names must be globally unique in Bugzilla.)
+
+=item 703 (Product must have description)
+
+You must specify a description for this product.
+
+=item 704 (Product must have version)
+
+You must specify a version for this product.
+
+=back
+
+=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/README b/Websites/bugs.webkit.org/Bugzilla/WebService/README
new file mode 100644
index 0000000..bbe3209
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/README
@@ -0,0 +1,18 @@
+The class structure of these files is a little strange, and this README
+explains it.
+
+Our goal is to make JSON::RPC and XMLRPC::Lite both work with the same code.
+(That is, we want to have one WebService API, and have two frontends for it.)
+
+The problem is that these both pass different things for $self to WebService
+methods.
+
+When XMLRPC::Lite calls a method, $self is the name of the *class* the
+method is in. For example, if we call Bugzilla.version(), the first argument
+is Bugzilla::WebService::Bugzilla. So in order to have $self
+(our first argument) act correctly in XML-RPC, we make all WebService
+classes use base qw(Bugzilla::WebService).
+
+When JSON::RPC calls a method, $self is the JSON-RPC *server object*. In other
+words, it's an instance of Bugzilla::WebService::Server::JSONRPC. So we have
+Bugzilla::WebService::Server::JSONRPC inherit from Bugzilla::WebService.
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/Server.pm b/Websites/bugs.webkit.org/Bugzilla/WebService/Server.pm
new file mode 100644
index 0000000..4e03152
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/Server.pm
@@ -0,0 +1,63 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::WebService::Server;
+use strict;
+
+use Bugzilla::Error;
+use Bugzilla::Util qw(datetime_from);
+
+use Scalar::Util qw(blessed);
+
+sub handle_login {
+ my ($self, $class, $method, $full_method) = @_;
+ eval "require $class";
+ ThrowCodeError('unknown_method', {method => $full_method}) if $@;
+ return if ($class->login_exempt($method)
+ and !defined Bugzilla->input_params->{Bugzilla_login});
+ Bugzilla->login();
+}
+
+sub datetime_format_inbound {
+ my ($self, $time) = @_;
+
+ my $converted = datetime_from($time, Bugzilla->local_timezone);
+ if (!defined $converted) {
+ ThrowUserError('illegal_date', { date => $time });
+ }
+ $time = $converted->ymd() . ' ' . $converted->hms();
+ return $time
+}
+
+sub datetime_format_outbound {
+ my ($self, $date) = @_;
+
+ return undef if (!defined $date or $date eq '');
+
+ my $time = $date;
+ if (blessed($date)) {
+ # We expect this to mean we were sent a datetime object
+ $time->set_time_zone('UTC');
+ } else {
+ # We always send our time in UTC, for consistency.
+ # passed in value is likely a string, create a datetime object
+ $time = datetime_from($date, 'UTC');
+ }
+ return $time->iso8601();
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/Server/JSONRPC.pm b/Websites/bugs.webkit.org/Bugzilla/WebService/Server/JSONRPC.pm
new file mode 100644
index 0000000..cec1c29
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/Server/JSONRPC.pm
@@ -0,0 +1,582 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla JSON Webservices Interface.
+#
+# The Initial Developer of the Original Code is the San Jose State
+# University Foundation. Portions created by the Initial Developer
+# are Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::WebService::Server::JSONRPC;
+
+use strict;
+use Bugzilla::WebService::Server;
+BEGIN {
+ our @ISA = qw(Bugzilla::WebService::Server);
+
+ if (eval { require JSON::RPC::Server::CGI }) {
+ unshift(@ISA, 'JSON::RPC::Server::CGI');
+ }
+ else {
+ require JSON::RPC::Legacy::Server::CGI;
+ unshift(@ISA, 'JSON::RPC::Legacy::Server::CGI');
+ }
+}
+
+use Bugzilla::Error;
+use Bugzilla::WebService::Constants;
+use Bugzilla::WebService::Util qw(taint_data);
+use Bugzilla::Util qw(correct_urlbase trim disable_utf8);
+
+use HTTP::Message;
+use MIME::Base64 qw(decode_base64 encode_base64);
+
+#####################################
+# Public JSON::RPC Method Overrides #
+#####################################
+
+sub new {
+ my $class = shift;
+ my $self = $class->SUPER::new(@_);
+ Bugzilla->_json_server($self);
+ $self->dispatch(WS_DISPATCH);
+ $self->return_die_message(1);
+ return $self;
+}
+
+sub create_json_coder {
+ my $self = shift;
+ my $json = $self->SUPER::create_json_coder(@_);
+ $json->allow_blessed(1);
+ $json->convert_blessed(1);
+ # This may seem a little backwards, but what this really means is
+ # "don't convert our utf8 into byte strings, just leave it as a
+ # utf8 string."
+ $json->utf8(0) if Bugzilla->params->{'utf8'};
+ return $json;
+}
+
+# Override the JSON::RPC method to return our CGI object instead of theirs.
+sub cgi { return Bugzilla->cgi; }
+
+sub response_header {
+ my $self = shift;
+ # The HTTP body needs to be bytes (not a utf8 string) for recent
+ # versions of HTTP::Message, but JSON::RPC::Server doesn't handle this
+ # properly. $_[1] is the HTTP body content we're going to be sending.
+ if (utf8::is_utf8($_[1])) {
+ utf8::encode($_[1]);
+ # Since we're going to just be sending raw bytes, we need to
+ # set STDOUT to not expect utf8.
+ disable_utf8();
+ }
+ return $self->SUPER::response_header(@_);
+}
+
+sub response {
+ my ($self, $response) = @_;
+
+ # Implement JSONP.
+ if (my $callback = $self->_bz_callback) {
+ my $content = $response->content;
+ $response->content("$callback($content)");
+
+ }
+
+ # Use $cgi->header properly instead of just printing text directly.
+ # This fixes various problems, including sending Bugzilla's cookies
+ # properly.
+ my $headers = $response->headers;
+ my @header_args;
+ foreach my $name ($headers->header_field_names) {
+ my @values = $headers->header($name);
+ $name =~ s/-/_/g;
+ foreach my $value (@values) {
+ push(@header_args, "-$name", $value);
+ }
+ }
+ my $cgi = $self->cgi;
+ print $cgi->header(-status => $response->code, @header_args);
+ print $response->content;
+}
+
+# The JSON-RPC 1.1 GET specification is not so great--you can't specify
+# data structures as parameters. However, the JSON-RPC 2.0 "JSON-RPC over
+# HTTP" spec is excellent, so we are using that for GET requests, instead.
+# Spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http
+#
+# The one exception is that we don't require the "params" argument to be
+# Base64 encoded, because that is ridiculous and obnoxious for JavaScript
+# clients.
+sub retrieve_json_from_get {
+ my $self = shift;
+ my $cgi = $self->cgi;
+
+ my %input;
+
+ # Both version and id must be set before any errors are thrown.
+ if ($cgi->param('version')) {
+ $self->version(scalar $cgi->param('version'));
+ $input{version} = $cgi->param('version');
+ }
+ else {
+ $self->version('1.0');
+ }
+
+ # The JSON-RPC 2.0 spec says that any request that omits an id doesn't
+ # want a response. However, in an HTTP GET situation, it's stupid to
+ # expect all clients to specify some id parameter just to get a response,
+ # so we don't require it.
+ my $id;
+ if (defined $cgi->param('id')) {
+ $id = $cgi->param('id');
+ }
+ # However, JSON::RPC does require that an id exist in most cases, in
+ # order to throw proper errors. We use the installation's urlbase as
+ # the id, in this case.
+ else {
+ $id = correct_urlbase();
+ }
+ # Setting _bz_request_id here is required in case we throw errors early,
+ # before _handle.
+ $self->{_bz_request_id} = $input{id} = $id;
+
+ # _bz_callback can throw an error, so we have to set it here, after we're
+ # ready to throw errors.
+ $self->_bz_callback(scalar $cgi->param('callback'));
+
+ if (!$cgi->param('method')) {
+ ThrowUserError('json_rpc_get_method_required');
+ }
+ $input{method} = $cgi->param('method');
+
+ my $params;
+ if (defined $cgi->param('params')) {
+ local $@;
+ $params = eval {
+ $self->json->decode(scalar $cgi->param('params'))
+ };
+ if ($@) {
+ ThrowUserError('json_rpc_invalid_params',
+ { params => scalar $cgi->param('params'),
+ err_msg => $@ });
+ }
+ }
+ elsif (!$self->version or $self->version ne '1.1') {
+ $params = [];
+ }
+ else {
+ $params = {};
+ }
+
+ $input{params} = $params;
+
+ my $json = $self->json->encode(\%input);
+ return $json;
+}
+
+#######################################
+# Bugzilla::WebService Implementation #
+#######################################
+
+sub type {
+ my ($self, $type, $value) = @_;
+
+ # This is the only type that does something special with undef.
+ if ($type eq 'boolean') {
+ return $value ? JSON::true : JSON::false;
+ }
+
+ return JSON::null if !defined $value;
+
+ my $retval = $value;
+
+ if ($type eq 'int') {
+ $retval = int($value);
+ }
+ if ($type eq 'double') {
+ $retval = 0.0 + $value;
+ }
+ elsif ($type eq 'string') {
+ # Forces string context, so that JSON will make it a string.
+ $retval = "$value";
+ }
+ elsif ($type eq 'dateTime') {
+ # ISO-8601 "YYYYMMDDTHH:MM:SS" with a literal T
+ $retval = $self->datetime_format_outbound($value);
+ }
+ elsif ($type eq 'base64') {
+ utf8::encode($value) if utf8::is_utf8($value);
+ $retval = encode_base64($value, '');
+ }
+
+ return $retval;
+}
+
+sub datetime_format_outbound {
+ my $self = shift;
+ # YUI expects ISO8601 in UTC time; including TZ specifier
+ return $self->SUPER::datetime_format_outbound(@_) . 'Z';
+}
+
+sub handle_login {
+ my $self = shift;
+
+ # If we're being called using GET, we don't allow cookie-based or Env
+ # login, because GET requests can be done cross-domain, and we don't
+ # want private data showing up on another site unless the user
+ # explicitly gives that site their username and password. (This is
+ # particularly important for JSONP, which would allow a remote site
+ # to use private data without the user's knowledge, unless we had this
+ # protection in place.)
+ if ($self->request->method ne 'POST') {
+ # XXX There's no particularly good way for us to get a parameter
+ # to Bugzilla->login at this point, so we pass this information
+ # around using request_cache, which is a bit of a hack. The
+ # implementation of it is in Bugzilla::Auth::Login::Stack.
+ Bugzilla->request_cache->{auth_no_automatic_login} = 1;
+ }
+
+ my $path = $self->path_info;
+ my $class = $self->{dispatch_path}->{$path};
+ my $full_method = $self->_bz_method_name;
+ $full_method =~ /^\S+\.(\S+)/;
+ my $method = $1;
+ $self->SUPER::handle_login($class, $method, $full_method);
+}
+
+######################################
+# Private JSON::RPC Method Overrides #
+######################################
+
+# Store the ID of the current call, because Bugzilla::Error will need it.
+sub _handle {
+ my $self = shift;
+ my ($obj) = @_;
+ $self->{_bz_request_id} = $obj->{id};
+ return $self->SUPER::_handle(@_);
+}
+
+# Make all error messages returned by JSON::RPC go into the 100000
+# range, and bring down all our errors into the normal range.
+sub _error {
+ my ($self, $id, $code) = (shift, shift, shift);
+ # All JSON::RPC errors are less than 1000.
+ if ($code < 1000) {
+ $code += 100000;
+ }
+ # Bugzilla::Error adds 100,000 to all *our* errors, so
+ # we know they came from us.
+ elsif ($code > 100000) {
+ $code -= 100000;
+ }
+
+ # We can't just set $_[1] because it's not always settable,
+ # in JSON::RPC::Server.
+ unshift(@_, $id, $code);
+ my $json = $self->SUPER::_error(@_);
+
+ # We want to always send the JSON-RPC 1.1 error format, although
+ # If we're not in JSON-RPC 1.1, we don't need the silly "name" parameter.
+ if (!$self->version or $self->version ne '1.1') {
+ my $object = $self->json->decode($json);
+ my $message = $object->{error};
+ # Just assure that future versions of JSON::RPC don't change the
+ # JSON-RPC 1.0 error format.
+ if (!ref $message) {
+ $object->{error} = {
+ code => $code,
+ message => $message,
+ };
+ $json = $self->json->encode($object);
+ }
+ }
+ return $json;
+}
+
+# This handles dispatching our calls to the appropriate class based on
+# the name of the method.
+sub _find_procedure {
+ my $self = shift;
+
+ my $method = shift;
+ $self->{_bz_method_name} = $method;
+
+ # This tricks SUPER::_find_procedure into finding the right class.
+ $method =~ /^(\S+)\.(\S+)$/;
+ $self->path_info($1);
+ unshift(@_, $2);
+
+ return $self->SUPER::_find_procedure(@_);
+}
+
+# This is a hacky way to do something right before methods are called.
+# This is the last thing that JSON::RPC::Server::_handle calls right before
+# the method is actually called.
+sub _argument_type_check {
+ my $self = shift;
+ my $params = $self->SUPER::_argument_type_check(@_);
+
+ # JSON-RPC 1.0 requires all parameters to be passed as an array, so
+ # we just pull out the first item and assume it's an object.
+ my $params_is_array;
+ if (ref $params eq 'ARRAY') {
+ $params = $params->[0];
+ $params_is_array = 1;
+ }
+
+ taint_data($params);
+
+ # Now, convert dateTime fields on input.
+ $self->_bz_method_name =~ /^(\S+)\.(\S+)$/;
+ my ($class, $method) = ($1, $2);
+ my $pkg = $self->{dispatch_path}->{$class};
+ my @date_fields = @{ $pkg->DATE_FIELDS->{$method} || [] };
+ foreach my $field (@date_fields) {
+ if (defined $params->{$field}) {
+ my $value = $params->{$field};
+ if (ref $value eq 'ARRAY') {
+ $params->{$field} =
+ [ map { $self->datetime_format_inbound($_) } @$value ];
+ }
+ else {
+ $params->{$field} = $self->datetime_format_inbound($value);
+ }
+ }
+ }
+ my @base64_fields = @{ $pkg->BASE64_FIELDS->{$method} || [] };
+ foreach my $field (@base64_fields) {
+ if (defined $params->{$field}) {
+ $params->{$field} = decode_base64($params->{$field});
+ }
+ }
+
+ Bugzilla->input_params($params);
+
+ if ($self->request->method eq 'POST') {
+ # CSRF is possible via XMLHttpRequest when the Content-Type header
+ # is not application/json (for example: text/plain or
+ # application/x-www-form-urlencoded).
+ # application/json is the single official MIME type, per RFC 4627.
+ my $content_type = $self->cgi->content_type;
+ # The charset can be appended to the content type, so we use a regexp.
+ if ($content_type !~ m{^application/json(-rpc)?(;.*)?$}i) {
+ ThrowUserError('json_rpc_illegal_content_type',
+ { content_type => $content_type });
+ }
+ }
+ else {
+ # When being called using GET, we don't allow calling
+ # methods that can change data. This protects us against cross-site
+ # request forgeries.
+ if (!grep($_ eq $method, $pkg->READ_ONLY)) {
+ ThrowUserError('json_rpc_post_only',
+ { method => $self->_bz_method_name });
+ }
+ }
+
+ # This is the best time to do login checks.
+ $self->handle_login();
+
+ # Bugzilla::WebService packages call internal methods like
+ # $self->_some_private_method. So we have to inherit from
+ # that class as well as this Server class.
+ my $new_class = ref($self) . '::' . $pkg;
+ my $isa_string = 'our @ISA = qw(' . ref($self) . " $pkg)";
+ eval "package $new_class;$isa_string;";
+ bless $self, $new_class;
+
+ if ($params_is_array) {
+ $params = [$params];
+ }
+
+ return $params;
+}
+
+##########################
+# Private Custom Methods #
+##########################
+
+# _bz_method_name is stored by _find_procedure for later use.
+sub _bz_method_name {
+ return $_[0]->{_bz_method_name};
+}
+
+sub _bz_callback {
+ my ($self, $value) = @_;
+ if (defined $value) {
+ $value = trim($value);
+ # We don't use \w because we don't want to allow Unicode here.
+ if ($value !~ /^[A-Za-z0-9_\.\[\]]+$/) {
+ ThrowUserError('json_rpc_invalid_callback', { callback => $value });
+ }
+ $self->{_bz_callback} = $value;
+ # JSONP needs to be parsed by a JS parser, not by a JSON parser.
+ $self->content_type('text/javascript');
+ }
+ return $self->{_bz_callback};
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::WebService::Server::JSONRPC - The JSON-RPC Interface to Bugzilla
+
+=head1 DESCRIPTION
+
+This documentation describes things about the Bugzilla WebService that
+are specific to JSON-RPC. For a general overview of the Bugzilla WebServices,
+see L<Bugzilla::WebService>.
+
+Please note that I<everything> about this JSON-RPC interface is
+B<EXPERIMENTAL>. If you want a fully stable API, please use the
+C<Bugzilla::WebService::Server::XMLRPC|XML-RPC> interface.
+
+=head1 JSON-RPC
+
+Bugzilla supports both JSON-RPC 1.0 and 1.1. We recommend that you use
+JSON-RPC 1.0 instead of 1.1, though, because 1.1 is deprecated.
+
+At some point in the future, Bugzilla may also support JSON-RPC 2.0.
+
+The JSON-RPC standards are described at L<http://json-rpc.org/>.
+
+=head1 CONNECTING
+
+The endpoint for the JSON-RPC interface is the C<jsonrpc.cgi> script in
+your Bugzilla installation. For example, if your Bugzilla is at
+C<bugzilla.yourdomain.com>, then your JSON-RPC client would access the
+API via: C<http://bugzilla.yourdomain.com/jsonrpc.cgi>
+
+=head2 Connecting via GET
+
+The most powerful way to access the JSON-RPC interface is by HTTP POST.
+However, for convenience, you can also access certain methods by using GET
+(a normal webpage load). Methods that modify the database or cause some
+action to happen in Bugzilla cannot be called over GET. Only methods that
+simply return data can be used over GET.
+
+For security reasons, when you connect over GET, cookie authentication
+is not accepted. If you want to authenticate using GET, you have to
+use the C<Bugzilla_login> and C<Bugzilla_password> method described at
+L<Bugzilla::WebService/LOGGING IN>.
+
+To connect over GET, simply send the values that you'd normally send for
+each JSON-RPC argument as URL parameters, with the C<params> item being
+a JSON string.
+
+The simplest example is a call to C<Bugzilla.time>:
+
+ jsonrpc.cgi?method=Bugzilla.time
+
+Here's a call to C<User.get>, with several parameters:
+
+ jsonrpc.cgi?method=User.get¶ms=[ { "ids": [1,2], "names": ["user@domain.com"] } ]
+
+Although in reality you would url-encode the C<params> argument, so it would
+look more like this:
+
+ jsonrpc.cgi?method=User.get¶ms=%5B+%7B+%22ids%22%3A+%5B1%2C2%5D%2C+%22names%22%3A+%5B%22user%40domain.com%22%5D+%7D+%5D
+
+You can also specify C<version> as a URL parameter, if you want to specify
+what version of the JSON-RPC protocol you're using, and C<id> as a URL
+parameter if you want there to be a specific C<id> value in the returned
+JSON-RPC response.
+
+=head2 JSONP
+
+When calling the JSON-RPC WebService over GET, you can use the "JSONP"
+method of doing cross-domain requests, if you want to access the WebService
+directly on a web page from another site. JSONP is described at
+L<http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/>.
+
+To use JSONP with Bugzilla's JSON-RPC WebService, simply specify a
+C<callback> parameter to jsonrpc.cgi when using it via GET as described above.
+For example, here's some HTML you could use to get the data from
+C<Bugzilla.time> on a remote website, using JSONP:
+
+ <script type="text/javascript"
+ src="http://bugzilla.example.com/jsonrpc.cgi?method=Bugzilla.time&callback=foo">
+
+That would call the C<Bugzilla.time> method and pass its value to a function
+called C<foo> as the only argument. All the other URL parameters (such as
+C<params>, for passing in arguments to methods) that can be passed to
+C<jsonrpc.cgi> during GET requests are also available, of course. The above
+is just the simplest possible example.
+
+The values returned when using JSONP are identical to the values returned
+when not using JSONP, so you will also get error messages if there is an
+error.
+
+The C<callback> URL parameter may only contain letters, numbers, periods, and
+the underscore (C<_>) character. Including any other characters will cause
+Bugzilla to throw an error. (This error will be a normal JSON-RPC response,
+not JSONP.)
+
+=head1 PARAMETERS
+
+For JSON-RPC 1.0, the very first parameter should be an object containing
+the named parameters. For example, if you were passing two named parameters,
+one called C<foo> and the other called C<bar>, the C<params> element of
+your JSON-RPC call would look like:
+
+ "params": [{ "foo": 1, "bar": "something" }]
+
+For JSON-RPC 1.1, you can pass parameters either in the above fashion
+or using the standard named-parameters mechanism of JSON-RPC 1.1.
+
+C<dateTime> fields are strings in the standard ISO-8601 format:
+C<YYYY-MM-DDTHH:MM:SSZ>, where C<T> and C<Z> are a literal T and Z,
+respectively. The "Z" means that all times are in UTC timezone--times are
+always returned in UTC, and should be passed in as UTC. (Note: The JSON-RPC
+interface currently also accepts non-UTC times for any values passed in, if
+they include a time-zone specifier that follows the ISO-8601 standard, instead
+of "Z" at the end. This behavior is expected to continue into the future, but
+to be fully safe for forward-compatibility with all future versions of
+Bugzilla, it is safest to pass in all times as UTC with the "Z" timezone
+specifier.)
+
+C<base64> fields are strings that have been base64 encoded. Note that
+although normal base64 encoding includes newlines to break up the data,
+newlines within a string are not valid JSON, so you should not insert
+newlines into your base64-encoded string.
+
+All other types are standard JSON types.
+
+=head1 ERRORS
+
+JSON-RPC 1.0 and JSON-RPC 1.1 both return an C<error> element when they
+throw an error. In Bugzilla, the error contents look like:
+
+ { message: 'Some message here', code: 123 }
+
+So, for example, in JSON-RPC 1.0, an error response would look like:
+
+ {
+ result: null,
+ error: { message: 'Some message here', code: 123 },
+ id: 1
+ }
+
+Every error has a "code", as described in L<Bugzilla::WebService/ERRORS>.
+Errors with a numeric C<code> higher than 100000 are errors thrown by
+the JSON-RPC library that Bugzilla uses, not by Bugzilla.
+
+=head1 SEE ALSO
+
+L<Bugzilla::WebService>
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/Server/XMLRPC.pm b/Websites/bugs.webkit.org/Bugzilla/WebService/Server/XMLRPC.pm
new file mode 100644
index 0000000..025fb8f
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/Server/XMLRPC.pm
@@ -0,0 +1,376 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Rosie Clarkson <rosie.clarkson@planningportal.gov.uk>
+#
+# Portions © Crown copyright 2009 - Rosie Clarkson (development@planningportal.gov.uk) for the Planning Portal
+
+package Bugzilla::WebService::Server::XMLRPC;
+
+use strict;
+use XMLRPC::Transport::HTTP;
+use Bugzilla::WebService::Server;
+if ($ENV{MOD_PERL}) {
+ our @ISA = qw(XMLRPC::Transport::HTTP::Apache Bugzilla::WebService::Server);
+} else {
+ our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
+}
+
+use Bugzilla::WebService::Constants;
+
+# Allow WebService methods to call XMLRPC::Lite's type method directly
+BEGIN {
+ *Bugzilla::WebService::type = sub {
+ my ($self, $type, $value) = @_;
+ if ($type eq 'dateTime') {
+ # This is the XML-RPC implementation, see the README in Bugzilla/WebService/.
+ # Our "base" implementation is in Bugzilla::WebService::Server.
+ $value = Bugzilla::WebService::Server->datetime_format_outbound($value);
+ $value =~ s/-//g;
+ }
+ return XMLRPC::Data->type($type)->value($value);
+ };
+}
+
+sub initialize {
+ my $self = shift;
+ my %retval = $self->SUPER::initialize(@_);
+ $retval{'serializer'} = Bugzilla::XMLRPC::Serializer->new;
+ $retval{'deserializer'} = Bugzilla::XMLRPC::Deserializer->new;
+ $retval{'dispatch_with'} = WS_DISPATCH;
+ return %retval;
+}
+
+sub make_response {
+ my $self = shift;
+
+ $self->SUPER::make_response(@_);
+
+ # XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
+ # its cookies in Bugzilla::CGI, so we need to copy them over.
+ foreach (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
+ $self->response->headers->push_header('Set-Cookie', $_);
+ }
+}
+
+sub handle_login {
+ my ($self, $classes, $action, $uri, $method) = @_;
+ my $class = $classes->{$uri};
+ my $full_method = $uri . "." . $method;
+ $self->SUPER::handle_login($class, $method, $full_method);
+ return;
+}
+
+1;
+
+# This exists to validate input parameters (which XMLRPC::Lite doesn't do)
+# and also, in some cases, to more-usefully decode them.
+package Bugzilla::XMLRPC::Deserializer;
+use strict;
+# We can't use "use base" because XMLRPC::Serializer doesn't return
+# a true value.
+use XMLRPC::Lite;
+our @ISA = qw(XMLRPC::Deserializer);
+
+use Bugzilla::Error;
+use Bugzilla::WebService::Constants qw(XMLRPC_CONTENT_TYPE_WHITELIST);
+use Scalar::Util qw(tainted);
+
+sub deserialize {
+ my $self = shift;
+
+ # Only allow certain content types to protect against CSRF attacks
+ my $content_type = lc($ENV{'CONTENT_TYPE'});
+ # Remove charset, etc, if provided
+ $content_type =~ s/^([^;]+);.*/$1/;
+ if (!grep($_ eq $content_type, XMLRPC_CONTENT_TYPE_WHITELIST)) {
+ ThrowUserError('xmlrpc_illegal_content_type',
+ { content_type => $ENV{'CONTENT_TYPE'} });
+ }
+
+ my ($xml) = @_;
+ my $som = $self->SUPER::deserialize(@_);
+ if (tainted($xml)) {
+ $som->{_bz_do_taint} = 1;
+ }
+ bless $som, 'Bugzilla::XMLRPC::SOM';
+ my $params = $som->paramsin;
+ # This allows positional parameters for Testopia.
+ $params = {} if ref $params ne 'HASH';
+ Bugzilla->input_params($params);
+ return $som;
+}
+
+# Some method arguments need to be converted in some way, when they are input.
+sub decode_value {
+ my $self = shift;
+ my ($type) = @{ $_[0] };
+ my $value = $self->SUPER::decode_value(@_);
+
+ # We only validate/convert certain types here.
+ return $value if $type !~ /^(?:int|i4|boolean|double|dateTime\.iso8601)$/;
+
+ # Though the XML-RPC standard doesn't allow an empty <int>,
+ # <double>,or <dateTime.iso8601>, we do, and we just say
+ # "that's undef".
+ if (grep($type eq $_, qw(int double dateTime))) {
+ return undef if $value eq '';
+ }
+
+ my $validator = $self->_validation_subs->{$type};
+ if (!$validator->($value)) {
+ ThrowUserError('xmlrpc_invalid_value',
+ { type => $type, value => $value });
+ }
+
+ # We convert dateTimes to a DB-friendly date format.
+ if ($type eq 'dateTime.iso8601') {
+ if ($value !~ /T.*[\-+Z]/i) {
+ # The caller did not specify a timezone, so we assume UTC.
+ # pass 'Z' specifier to datetime_from to force it
+ $value = $value . 'Z';
+ }
+ $value = Bugzilla::WebService::Server::XMLRPC->datetime_format_inbound($value);
+ }
+
+ return $value;
+}
+
+sub _validation_subs {
+ my $self = shift;
+ return $self->{_validation_subs} if $self->{_validation_subs};
+ # The only place that XMLRPC::Lite stores any sort of validation
+ # regex is in XMLRPC::Serializer. We want to re-use those regexes here.
+ my $lookup = Bugzilla::XMLRPC::Serializer->new->typelookup;
+
+ # $lookup is a hash whose values are arrayrefs, and whose keys are the
+ # names of types. The second item of each arrayref is a subroutine
+ # that will do our validation for us.
+ my %validators = map { $_ => $lookup->{$_}->[1] } (keys %$lookup);
+ # Add a boolean validator
+ $validators{'boolean'} = sub {$_[0] =~ /^[01]$/};
+ # Some types have multiple names, or have a different name in
+ # XMLRPC::Serializer than their standard XML-RPC name.
+ $validators{'dateTime.iso8601'} = $validators{'dateTime'};
+ $validators{'i4'} = $validators{'int'};
+
+ $self->{_validation_subs} = \%validators;
+ return \%validators;
+}
+
+1;
+
+package Bugzilla::XMLRPC::SOM;
+use strict;
+use XMLRPC::Lite;
+our @ISA = qw(XMLRPC::SOM);
+use Bugzilla::WebService::Util qw(taint_data);
+
+sub paramsin {
+ my $self = shift;
+ if (!$self->{bz_params_in}) {
+ my @params = $self->SUPER::paramsin(@_);
+ if ($self->{_bz_do_taint}) {
+ taint_data(@params);
+ }
+ $self->{bz_params_in} = \@params;
+ }
+ my $params = $self->{bz_params_in};
+ return wantarray ? @$params : $params->[0];
+}
+
+1;
+
+# This package exists to fix a UTF-8 bug in SOAP::Lite.
+# See http://rt.cpan.org/Public/Bug/Display.html?id=32952.
+package Bugzilla::XMLRPC::Serializer;
+use Scalar::Util qw(blessed);
+use strict;
+# We can't use "use base" because XMLRPC::Serializer doesn't return
+# a true value.
+use XMLRPC::Lite;
+our @ISA = qw(XMLRPC::Serializer);
+
+sub new {
+ my $class = shift;
+ my $self = $class->SUPER::new(@_);
+ # This fixes UTF-8.
+ $self->{'_typelookup'}->{'base64'} =
+ [10, sub { !utf8::is_utf8($_[0]) && $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/},
+ 'as_base64'];
+ # This makes arrays work right even though we're a subclass.
+ # (See http://rt.cpan.org//Ticket/Display.html?id=34514)
+ $self->{'_encodingStyle'} = '';
+ return $self;
+}
+
+# Here the XMLRPC::Serializer is extended to use the XMLRPC nil extension.
+sub encode_object {
+ my $self = shift;
+ my @encoded = $self->SUPER::encode_object(@_);
+
+ return $encoded[0]->[0] eq 'nil'
+ ? ['value', {}, [@encoded]]
+ : @encoded;
+}
+
+# Removes undefined values so they do not produce invalid XMLRPC.
+sub envelope {
+ my $self = shift;
+ my ($type, $method, $data) = @_;
+ # If the type isn't a successful response we don't want to change the values.
+ if ($type eq 'response'){
+ $data = _strip_undefs($data);
+ }
+ return $self->SUPER::envelope($type, $method, $data);
+}
+
+# In an XMLRPC response we have to handle hashes of arrays, hashes, scalars,
+# Bugzilla objects (reftype = 'HASH') and XMLRPC::Data objects.
+# The whole XMLRPC::Data object must be removed if its value key is undefined
+# so it cannot be recursed like the other hash type objects.
+sub _strip_undefs {
+ my ($initial) = @_;
+ if (ref $initial eq "HASH" || (blessed $initial && $initial->isa("HASH"))) {
+ while (my ($key, $value) = each(%$initial)) {
+ if ( !defined $value
+ || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
+ {
+ # If the value is undefined remove it from the hash.
+ delete $initial->{$key};
+ }
+ else {
+ $initial->{$key} = _strip_undefs($value);
+ }
+ }
+ }
+ if (ref $initial eq "ARRAY" || (blessed $initial && $initial->isa("ARRAY"))) {
+ for (my $count = 0; $count < scalar @{$initial}; $count++) {
+ my $value = $initial->[$count];
+ if ( !defined $value
+ || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
+ {
+ # If the value is undefined remove it from the array.
+ splice(@$initial, $count, 1);
+ $count--;
+ }
+ else {
+ $initial->[$count] = _strip_undefs($value);
+ }
+ }
+ }
+ return $initial;
+}
+
+sub BEGIN {
+ no strict 'refs';
+ for my $type (qw(double i4 int dateTime)) {
+ my $method = 'as_' . $type;
+ *$method = sub {
+ my ($self, $value) = @_;
+ if (!defined($value)) {
+ return as_nil();
+ }
+ else {
+ my $super_method = "SUPER::$method";
+ return $self->$super_method($value);
+ }
+ }
+ }
+}
+
+sub as_nil {
+ return ['nil', {}];
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::WebService::Server::XMLRPC - The XML-RPC Interface to Bugzilla
+
+=head1 DESCRIPTION
+
+This documentation describes things about the Bugzilla WebService that
+are specific to XML-RPC. For a general overview of the Bugzilla WebServices,
+see L<Bugzilla::WebService>.
+
+=head1 XML-RPC
+
+The XML-RPC standard is described here: L<http://www.xmlrpc.com/spec>
+
+=head1 CONNECTING
+
+The endpoint for the XML-RPC interface is the C<xmlrpc.cgi> script in
+your Bugzilla installation. For example, if your Bugzilla is at
+C<bugzilla.yourdomain.com>, then your XML-RPC client would access the
+API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi>
+
+=head1 PARAMETERS
+
+C<dateTime> fields are the standard C<dateTime.iso8601> XML-RPC field. They
+should be in C<YYYY-MM-DDTHH:MM:SS> format (where C<T> is a literal T). As
+of Bugzilla B<3.6>, Bugzilla always expects C<dateTime> fields to be in the
+UTC timezone, and all returned C<dateTime> values are in the UTC timezone.
+
+All other fields are standard XML-RPC types.
+
+=head2 How XML-RPC WebService Methods Take Parameters
+
+All functions take a single argument, a C<< <struct> >> that contains all parameters.
+The names of the parameters listed in the API docs for each function are the
+C<< <name> >> element for the struct C<< <member> >>s.
+
+=head1 EXTENSIONS TO THE XML-RPC STANDARD
+
+=head2 Undefined Values
+
+Normally, XML-RPC does not allow empty values for C<int>, C<double>, or
+C<dateTime.iso8601> fields. Bugzilla does--it treats empty values as
+C<undef> (called C<NULL> or C<None> in some programming languages).
+
+Bugzilla accepts a timezone specifier at the end of C<dateTime.iso8601>
+fields that are specified as method arguments. The format of the timezone
+specifier is specified in the ISO-8601 standard. If no timezone specifier
+is included, the passed-in time is assumed to be in the UTC timezone.
+Bugzilla will never output a timezone specifier on returned data, because
+doing so would violate the XML-RPC specification. All returned times are in
+the UTC timezone.
+
+Bugzilla also accepts an element called C<< <nil> >>, as specified by the
+XML-RPC extension here: L<http://ontosys.com/xml-rpc/extensions.php>, which
+is always considered to be C<undef>, no matter what it contains.
+
+Bugzilla does not use C<< <nil> >> values in returned data, because currently
+most clients do not support C<< <nil> >>. Instead, any fields with C<undef>
+values will be stripped from the response completely. Therefore
+B<the client must handle the fact that some expected fields may not be
+returned>.
+
+=begin private
+
+nil is implemented by XMLRPC::Lite, in XMLRPC::Deserializer::decode_value
+in the CPAN SVN since 14th Dec 2008
+L<http://rt.cpan.org/Public/Bug/Display.html?id=20569> and in Fedora's
+perl-SOAP-Lite package in versions 0.68-1 and above.
+
+=end private
+
+=head1 SEE ALSO
+
+L<Bugzilla::WebService>
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/User.pm b/Websites/bugs.webkit.org/Bugzilla/WebService/User.pm
index 5446d80..f8704a9 100644
--- a/Websites/bugs.webkit.org/Bugzilla/WebService/User.pm
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/User.pm
@@ -15,20 +15,20 @@
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Mads Bondo Dydensborg <mbd@dbc.dk>
+# Noura Elhawary <nelhawar@redhat.com>
package Bugzilla::WebService::User;
use strict;
use base qw(Bugzilla::WebService);
-import SOAP::Data qw(type);
-
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
+use Bugzilla::Group;
use Bugzilla::User;
use Bugzilla::Util qw(trim);
-use Bugzilla::Token;
+use Bugzilla::WebService::Util qw(filter validate);
# Don't need auth to login
use constant LOGIN_EXEMPT => {
@@ -36,6 +36,10 @@
offer_account_by_email => 1,
};
+use constant READ_ONLY => qw(
+ get
+);
+
##############
# User Login #
##############
@@ -61,13 +65,13 @@
}
# Make sure the CGI user info class works if necessary.
- my $cgi = Bugzilla->cgi;
- $cgi->param('Bugzilla_login', $params->{login});
- $cgi->param('Bugzilla_password', $params->{password});
- $cgi->param('Bugzilla_remember', $remember);
+ my $input_params = Bugzilla->input_params;
+ $input_params->{'Bugzilla_login'} = $params->{login};
+ $input_params->{'Bugzilla_password'} = $params->{password};
+ $input_params->{'Bugzilla_remember'} = $remember;
- Bugzilla->login;
- return { id => type('int')->value(Bugzilla->user->id) };
+ Bugzilla->login();
+ return { id => $self->type('int', Bugzilla->user->id) };
}
sub logout {
@@ -86,19 +90,8 @@
my $email = trim($params->{email})
|| ThrowCodeError('param_required', { param => 'email' });
- my $createexp = Bugzilla->params->{'createemailregexp'};
- if (!$createexp) {
- ThrowUserError("account_creation_disabled");
- }
- elsif ($email !~ /$createexp/) {
- ThrowUserError("account_creation_restricted");
- }
-
- $email = Bugzilla::User->check_login_name_for_creation($email);
-
- # Create and send a token for this new account.
- Bugzilla::Token::issue_new_user_account_token($email);
-
+ Bugzilla->user->check_account_creation_enabled;
+ Bugzilla->user->check_and_send_account_creation_confirmation($email);
return undef;
}
@@ -122,7 +115,142 @@
cryptpassword => $password
});
- return { id => type('int')->value($user->id) };
+ return { id => $self->type('int', $user->id) };
+}
+
+
+# function to return user information by passing either user ids or
+# login names or both together:
+# $call = $rpc->call( 'User.get', { ids => [1,2,3],
+# names => ['testusera@redhat.com', 'testuserb@redhat.com'] });
+sub get {
+ my ($self, $params) = validate(@_, 'names', 'ids');
+
+ defined($params->{names}) || defined($params->{ids})
+ || defined($params->{match})
+ || ThrowCodeError('params_required',
+ { function => 'User.get', params => ['ids', 'names', 'match'] });
+
+ my @user_objects;
+ @user_objects = map { Bugzilla::User->check($_) } @{ $params->{names} }
+ if $params->{names};
+
+ # start filtering to remove duplicate user ids
+ my %unique_users = map { $_->id => $_ } @user_objects;
+ @user_objects = values %unique_users;
+
+ my @users;
+
+ # If the user is not logged in: Return an error if they passed any user ids.
+ # Otherwise, return a limited amount of information based on login names.
+ if (!Bugzilla->user->id){
+ if ($params->{ids}){
+ ThrowUserError("user_access_by_id_denied");
+ }
+ if ($params->{match}) {
+ ThrowUserError('user_access_by_match_denied');
+ }
+ my $in_group = $self->_filter_users_by_group(
+ \@user_objects, $params);
+ @users = map {filter $params, {
+ id => $self->type('int', $_->id),
+ real_name => $self->type('string', $_->name),
+ name => $self->type('string', $_->login),
+ }} @$in_group;
+
+ return { users => \@users };
+ }
+
+ my $obj_by_ids;
+ $obj_by_ids = Bugzilla::User->new_from_list($params->{ids}) if $params->{ids};
+
+ # obj_by_ids are only visible to the user if he can see
+ # the otheruser, for non visible otheruser throw an error
+ foreach my $obj (@$obj_by_ids) {
+ if (Bugzilla->user->can_see_user($obj)){
+ if (!$unique_users{$obj->id}) {
+ push (@user_objects, $obj);
+ $unique_users{$obj->id} = $obj;
+ }
+ }
+ else {
+ ThrowUserError('auth_failure', {reason => "not_visible",
+ action => "access",
+ object => "user",
+ userid => $obj->id});
+ }
+ }
+
+ # User Matching
+ my $limit;
+ if ($params->{'maxusermatches'}) {
+ $limit = $params->{'maxusermatches'} + 1;
+ }
+ my $exclude_disabled = $params->{'include_disabled'} ? 0 : 1;
+ foreach my $match_string (@{ $params->{'match'} || [] }) {
+ my $matched = Bugzilla::User::match($match_string, $limit, $exclude_disabled);
+ foreach my $user (@$matched) {
+ if (!$unique_users{$user->id}) {
+ push(@user_objects, $user);
+ $unique_users{$user->id} = $user;
+ }
+ }
+ }
+
+ my $in_group = $self->_filter_users_by_group(
+ \@user_objects, $params);
+ if (Bugzilla->user->in_group('editusers')) {
+ @users =
+ map {filter $params, {
+ id => $self->type('int', $_->id),
+ real_name => $self->type('string', $_->name),
+ name => $self->type('string', $_->login),
+ email => $self->type('string', $_->email),
+ can_login => $self->type('boolean', $_->is_enabled ? 1 : 0),
+ email_enabled => $self->type('boolean', $_->email_enabled),
+ login_denied_text => $self->type('string', $_->disabledtext),
+ }} @$in_group;
+
+ }
+ else {
+ @users =
+ map {filter $params, {
+ id => $self->type('int', $_->id),
+ real_name => $self->type('string', $_->name),
+ name => $self->type('string', $_->login),
+ email => $self->type('string', $_->email),
+ can_login => $self->type('boolean', $_->is_enabled ? 1 : 0),
+ }} @$in_group;
+ }
+
+ return { users => \@users };
+}
+
+sub _filter_users_by_group {
+ my ($self, $users, $params) = @_;
+ my ($group_ids, $group_names) = @$params{qw(group_ids groups)};
+
+ # If no groups are specified, we return all users.
+ return $users if (!$group_ids and !$group_names);
+
+ my @groups = map { Bugzilla::Group->check({ id => $_ }) }
+ @{ $group_ids || [] };
+ my @name_groups = map { Bugzilla::Group->check($_) }
+ @{ $group_names || [] };
+ push(@groups, @name_groups);
+
+
+ my @in_group = grep { $self->_user_in_any_group($_, \@groups) }
+ @$users;
+ return \@in_group;
+}
+
+sub _user_in_any_group {
+ my ($self, $user, $groups) = @_;
+ foreach my $group (@$groups) {
+ return 1 if $user->in_group($group);
+ }
+ return 0;
}
1;
@@ -143,11 +271,9 @@
See L<Bugzilla::WebService> for a description of how parameters are passed,
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
-=head2 Logging In and Out
+=head1 Logging In and Out
-=over
-
-=item C<login>
+=head2 login
B<STABLE>
@@ -197,6 +323,11 @@
The account has been disabled. A reason may be specified with the
error.
+=item 305 (New Password Required)
+
+The current password is correct, but the user is asked to change
+his password.
+
=item 50 (Param Required)
A login or password parameter was not provided.
@@ -205,7 +336,7 @@
=back
-=item C<logout>
+=head2 logout
B<STABLE>
@@ -223,13 +354,9 @@
=back
-=back
+=head1 Account Creation
-=head2 Account Creation
-
-=over
-
-=item C<offer_account_by_email>
+=head2 offer_account_by_email
B<STABLE>
@@ -257,22 +384,22 @@
=over
-=item 500 (Illegal Email Address)
+=item 500 (Account Already Exists)
+
+An account with that email address already exists in Bugzilla.
+
+=item 501 (Illegal Email Address)
This Bugzilla does not allow you to create accounts with the format of
email address you specified. Account creation may be entirely disabled.
-=item 501 (Account Already Exists)
-
-An account with that email address already exists in Bugzilla.
-
=back
=back
-=item C<create>
+=head2 create
-B<EXPERIMENTAL>
+B<STABLE>
=over
@@ -321,10 +448,158 @@
The password specified is too short. (Usually, this means the
password is under three characters.)
-=item 503 (Password Too Long)
+=back
-The password specified is too long. (Usually, this means the
-password is over ten characters.)
+=item B<History>
+
+=over
+
+=item Error 503 (Password Too Long) removed in Bugzilla B<3.6>.
+
+=back
+
+=back
+
+=head1 User Info
+
+=head2 get
+
+B<STABLE>
+
+=over
+
+=item B<Description>
+
+Gets information about user accounts in Bugzilla.
+
+=item B<Params>
+
+B<Note>: At least one of C<ids>, C<names>, or C<match> must be specified.
+
+B<Note>: Users will not be returned more than once, so even if a user
+is matched by more than one argument, only one user will be returned.
+
+In addition to the parameters below, this method also accepts the
+standard L<include_fields|Bugzilla::WebService/include_fields> and
+L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
+
+=over
+
+=item C<ids> (array)
+
+An array of integers, representing user ids.
+
+Logged-out users cannot pass this parameter to this function. If they try,
+they will get an error. Logged-in users will get an error if they specify
+the id of a user they cannot see.
+
+=item C<names> (array)
+
+An array of login names (strings).
+
+=item C<match> (array)
+
+An array of strings. This works just like "user matching" in
+Bugzilla itself. Users will be returned whose real name or login name
+contains any one of the specified strings. Users that you cannot see will
+not be included in the returned list.
+
+Some Bugzilla installations have user-matching turned off, in which
+case you will only be returned exact matches.
+
+Most installations have a limit on how many matches are returned for
+each string, which defaults to 1000 but can be changed by the Bugzilla
+administrator.
+
+Logged-out users cannot use this argument, and an error will be thrown
+if they try. (This is to make it harder for spammers to harvest email
+addresses from Bugzilla, and also to enforce the user visibility
+restrictions that are implemented on some Bugzillas.)
+
+=item C<group_ids> (array)
+
+=item C<groups> (array)
+
+C<group_ids> is an array of numeric ids for groups that a user can be in.
+C<groups> is an array of names of groups that a user can be in.
+If these are specified, they limit the return value to users who are
+in I<any> of the groups specified.
+
+=item C<include_disabled> (boolean)
+
+By default, when using the C<match> parameter, disabled users are excluded
+from the returned results unless their full username is identical to the
+match string. Setting C<include_disabled> to C<true> will include disabled
+users in the returned results even if their username doesn't fully match
+the input string.
+
+=back
+
+=item B<Returns>
+
+A hash containing one item, C<users>, that is an array of
+hashes. Each hash describes a user, and has the following items:
+
+=over
+
+=item id
+
+C<int> The unique integer ID that Bugzilla uses to represent this user.
+Even if the user's login name changes, this will not change.
+
+=item real_name
+
+C<string> The actual name of the user. May be blank.
+
+=item email
+
+C<string> The email address of the user.
+
+=item name
+
+C<string> The login name of the user. Note that in some situations this is
+different than their email.
+
+=item can_login
+
+C<boolean> A boolean value to indicate if the user can login into bugzilla.
+
+=item email_enabled
+
+C<boolean> A boolean value to indicate if bug-related mail will be sent
+to the user or not.
+
+=item login_denied_text
+
+C<string> A text field that holds the reason for disabling a user from logging
+into bugzilla, if empty then the user account is enabled. Otherwise it is
+disabled/closed.
+
+B<Note>: If you are not logged in to Bugzilla when you call this function, you
+will only be returned the C<id>, C<name>, and C<real_name> items. If you are
+logged in and not in editusers group, you will only be returned the C<id>, C<name>,
+C<real_name>, C<email>, and C<can_login> items.
+
+=back
+
+=item B<Errors>
+
+=over
+
+=item 51 (Bad Login Name or Group Name)
+
+You passed an invalid login name in the "names" array or a bad
+group name/id in the C<groups>/C<group_ids> arguments.
+
+=item 304 (Authorization Required)
+
+You are logged in, but you are not authorized to see one of the users you
+wanted to get information about by user id.
+
+=item 505 (User Access By Id or User-Matching Denied)
+
+Logged-out users cannot use the "ids" or "match" arguments to this
+function.
=back
@@ -334,7 +609,10 @@
=item Added in Bugzilla B<3.4>.
-=back
+=item C<group_ids> and C<groups> were added in Bugzilla B<4.0>.
+
+=item C<include_disabled> added in Bugzilla B<4.0>. Default behavior
+for C<match> has changed to only returning enabled accounts.
=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/WebService/Util.pm b/Websites/bugs.webkit.org/Bugzilla/WebService/Util.pm
new file mode 100644
index 0000000..adb7fb4
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/WebService/Util.pm
@@ -0,0 +1,149 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::WebService::Util;
+use strict;
+use base qw(Exporter);
+
+# We have to "require", not "use" this, because otherwise it tries to
+# use features of Test::More during import().
+require Test::Taint;
+
+our @EXPORT_OK = qw(
+ filter
+ filter_wants
+ taint_data
+ validate
+);
+
+sub filter ($$) {
+ my ($params, $hash) = @_;
+ my %newhash = %$hash;
+
+ foreach my $key (keys %$hash) {
+ delete $newhash{$key} if !filter_wants($params, $key);
+ }
+
+ return \%newhash;
+}
+
+sub filter_wants ($$) {
+ my ($params, $field) = @_;
+ my %include = map { $_ => 1 } @{ $params->{'include_fields'} || [] };
+ my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] };
+
+ if (defined $params->{include_fields}) {
+ return 0 if !$include{$field};
+ }
+ if (defined $params->{exclude_fields}) {
+ return 0 if $exclude{$field};
+ }
+
+ return 1;
+}
+
+sub taint_data {
+ my @params = @_;
+ return if !@params;
+ # Though this is a private function, it hasn't changed since 2004 and
+ # should be safe to use, and prevents us from having to write it ourselves
+ # or require another module to do it.
+ Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params);
+ Test::Taint::taint_deeply(\@params);
+}
+
+sub _delete_bad_keys {
+ foreach my $item (@_) {
+ next if ref $item ne 'HASH';
+ foreach my $key (keys %$item) {
+ # Making something a hash key always untaints it, in Perl.
+ # However, we need to validate our argument names in some way.
+ # We know that all hash keys passed in to the WebService will
+ # match \w+, so we delete any key that doesn't match that.
+ if ($key !~ /^\w+$/) {
+ delete $item->{$key};
+ }
+ }
+ }
+ return @_;
+}
+
+sub validate {
+ my ($self, $params, @keys) = @_;
+
+ # If $params is defined but not a reference, then we weren't
+ # sent any parameters at all, and we're getting @keys where
+ # $params should be.
+ return ($self, undef) if (defined $params and !ref $params);
+
+ # If @keys is not empty then we convert any named
+ # parameters that have scalar values to arrayrefs
+ # that match.
+ foreach my $key (@keys) {
+ if (exists $params->{$key}) {
+ $params->{$key} = ref $params->{$key}
+ ? $params->{$key}
+ : [ $params->{$key} ];
+ }
+ }
+
+ return ($self, $params);
+}
+
+__END__
+
+=head1 NAME
+
+Bugzilla::WebService::Util - Utility functions used inside of the WebService
+code. These are B<not> functions that can be called via the WebService.
+
+=head1 DESCRIPTION
+
+This is somewhat like L<Bugzilla::Util>, but these functions are only used
+internally in the WebService code.
+
+=head1 SYNOPSIS
+
+ filter({ include_fields => ['id', 'name'],
+ exclude_fields => ['name'] }, $hash);
+ my $wants = filter_wants $params, 'field_name';
+ validate(@_, 'ids');
+
+=head1 METHODS
+
+=head2 filter
+
+This helps implement the C<include_fields> and C<exclude_fields> arguments
+of WebService methods. Given a hash (the second argument to this subroutine),
+this will remove any keys that are I<not> in C<include_fields> and then remove
+any keys that I<are> in C<exclude_fields>.
+
+=head2 filter_wants
+
+Returns C<1> if a filter would preserve the specified field when passing
+a hash to L</filter>, C<0> otherwise.
+
+=head2 validate
+
+This helps in the validation of parameters passed into the WebSerice
+methods. Currently it converts listed parameters into an array reference
+if the client only passed a single scalar value. It modifies the parameters
+hash in place so other parameters should be unaltered.
diff --git a/Websites/bugs.webkit.org/Bugzilla/Whine.pm b/Websites/bugs.webkit.org/Bugzilla/Whine.pm
new file mode 100644
index 0000000..73b0802
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Whine.pm
@@ -0,0 +1,132 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Eric Black.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# Eric Black. All Rights Reserved.
+#
+# Contributor(s): Eric Black <black.eric@gmail.com>
+
+use strict;
+
+package Bugzilla::Whine;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Util;
+use Bugzilla::Whine::Schedule;
+use Bugzilla::Whine::Query;
+
+#############
+# Constants #
+#############
+
+use constant DB_TABLE => 'whine_events';
+
+use constant DB_COLUMNS => qw(
+ id
+ owner_userid
+ subject
+ body
+ mailifnobugs
+);
+
+use constant LIST_ORDER => 'id';
+
+####################
+# Simple Accessors #
+####################
+sub subject { return $_[0]->{'subject'}; }
+sub body { return $_[0]->{'body'}; }
+sub mail_if_no_bugs { return $_[0]->{'mailifnobugs'}; }
+
+sub user {
+ my ($self) = @_;
+ return $self->{user} if defined $self->{user};
+ $self->{user} = new Bugzilla::User($self->{'owner_userid'});
+ return $self->{user};
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Whine - A Whine event
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Whine;
+
+ my $event = new Bugzilla::Whine($event_id);
+
+ my $subject = $event->subject;
+ my $body = $event->body;
+ my $mailifnobugs = $event->mail_if_no_bugs;
+ my $user = $event->user;
+
+=head1 DESCRIPTION
+
+This module exists to represent a whine event that has been
+saved to the database.
+
+This is an implementation of L<Bugzilla::Object>, and so has all the
+same methods available as L<Bugzilla::Object>, in addition to what is
+documented below.
+
+=head1 METHODS
+
+=head2 Constructors
+
+=over
+
+=item C<new>
+
+Does not accept a bare C<name> argument. Instead, accepts only an id.
+
+See also: L<Bugzilla::Object/new>.
+
+=back
+
+
+=head2 Accessors
+
+These return data about the object, without modifying the object.
+
+=over
+
+=item C<subject>
+
+Returns the subject of the whine event.
+
+=item C<body>
+
+Returns the body of the whine event.
+
+=item C<mail_if_no_bugs>
+
+Returns a numeric 1(C<true>) or 0(C<false>) to represent whether this
+whine event object is supposed to be mailed even if there are no bugs
+returned by the query.
+
+=item C<user>
+
+Returns the L<Bugzilla::User> object for the owner of the L<Bugzilla::Whine>
+event.
+
+=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/Whine/Query.pm b/Websites/bugs.webkit.org/Bugzilla/Whine/Query.pm
new file mode 100644
index 0000000..1121575
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Whine/Query.pm
@@ -0,0 +1,136 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Eric Black.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# Eric Black. All Rights Reserved.
+#
+# Contributor(s): Eric Black <black.eric@gmail.com>
+
+package Bugzilla::Whine::Query;
+
+use strict;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Constants;
+use Bugzilla::Search::Saved;
+
+#############
+# Constants #
+#############
+
+use constant DB_TABLE => 'whine_queries';
+
+use constant DB_COLUMNS => qw(
+ id
+ eventid
+ query_name
+ sortkey
+ onemailperbug
+ title
+);
+
+use constant NAME_FIELD => 'id';
+use constant LIST_ORDER => 'sortkey';
+
+####################
+# Simple Accessors #
+####################
+sub eventid { return $_[0]->{'eventid'}; }
+sub sortkey { return $_[0]->{'sortkey'}; }
+sub one_email_per_bug { return $_[0]->{'onemailperbug'}; }
+sub title { return $_[0]->{'title'}; }
+sub name { return $_[0]->{'query_name'}; }
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Whine::Query - A query object used by L<Bugzilla::Whine>.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Whine::Query;
+
+ my $query = new Bugzilla::Whine::Query($id);
+
+ my $event_id = $query->eventid;
+ my $id = $query->id;
+ my $query_name = $query->name;
+ my $sortkey = $query->sortkey;
+ my $one_email_per_bug = $query->one_email_per_bug;
+ my $title = $query->title;
+
+=head1 DESCRIPTION
+
+This module exists to represent a query for a L<Bugzilla::Whine::Event>.
+Each event, which are groups of schedules and queries based on how the
+user configured the event, may have zero or more queries associated
+with it. Additionally, the queries are selected from the user's saved
+searches, or L<Bugzilla::Search::Saved> object with a matching C<name>
+attribute for the user.
+
+This is an implementation of L<Bugzilla::Object>, and so has all the
+same methods available as L<Bugzilla::Object>, in addition to what is
+documented below.
+
+=head1 METHODS
+
+=head2 Constructors
+
+=over
+
+=item C<new>
+
+Does not accept a bare C<name> argument. Instead, accepts only an id.
+
+See also: L<Bugzilla::Object/new>.
+
+=back
+
+
+=head2 Accessors
+
+These return data about the object, without modifying the object.
+
+=over
+
+=item C<event_id>
+
+The L<Bugzilla::Whine::Event> object id for this object.
+
+=item C<name>
+
+The L<Bugzilla::Search::Saved> query object name for this object.
+
+=item C<sortkey>
+
+The relational sorting key as compared with other L<Bugzilla::Whine::Query>
+objects.
+
+=item C<one_email_per_bug>
+
+Returns a numeric 1(C<true>) or 0(C<false>) to represent whether this
+L<Bugzilla::Whine::Query> object is supposed to be mailed as a list of
+bugs or one email per bug.
+
+=item C<title>
+
+The title of this object as it appears in the user forms and emails.
+
+=back
diff --git a/Websites/bugs.webkit.org/Bugzilla/Whine/Schedule.pm b/Websites/bugs.webkit.org/Bugzilla/Whine/Schedule.pm
new file mode 100644
index 0000000..6314885
--- /dev/null
+++ b/Websites/bugs.webkit.org/Bugzilla/Whine/Schedule.pm
@@ -0,0 +1,170 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Eric Black.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# Eric Black. All Rights Reserved.
+#
+# Contributor(s): Eric Black <black.eric@gmail.com>
+
+use strict;
+
+package Bugzilla::Whine::Schedule;
+
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Constants;
+
+#############
+# Constants #
+#############
+
+use constant DB_TABLE => 'whine_schedules';
+
+use constant DB_COLUMNS => qw(
+ id
+ eventid
+ run_day
+ run_time
+ run_next
+ mailto
+ mailto_type
+);
+
+use constant UPDATE_COLUMNS => qw(
+ eventid
+ run_day
+ run_time
+ run_next
+ mailto
+ mailto_type
+);
+use constant NAME_FIELD => 'id';
+use constant LIST_ORDER => 'id';
+
+####################
+# Simple Accessors #
+####################
+sub eventid { return $_[0]->{'eventid'}; }
+sub run_day { return $_[0]->{'run_day'}; }
+sub run_time { return $_[0]->{'run_time'}; }
+sub mailto_is_group { return $_[0]->{'mailto_type'}; }
+
+sub mailto {
+ my $self = shift;
+
+ return $self->{mailto_object} if exists $self->{mailto_object};
+ my $id = $self->{'mailto'};
+
+ if ($self->mailto_is_group) {
+ $self->{mailto_object} = Bugzilla::Group->new($id);
+ } else {
+ $self->{mailto_object} = Bugzilla::User->new($id);
+ }
+ return $self->{mailto_object};
+}
+
+sub mailto_users {
+ my $self = shift;
+ return $self->{mailto_users} if exists $self->{mailto_users};
+ my $object = $self->mailto;
+
+ if ($self->mailto_is_group) {
+ $self->{mailto_users} = $object->members_non_inherited if $object->is_active;
+ } else {
+ $self->{mailto_users} = $object;
+ }
+ return $self->{mailto_users};
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Whine::Schedule - A schedule object used by L<Bugzilla::Whine>.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Whine::Schedule;
+
+ my $schedule = new Bugzilla::Whine::Schedule($schedule_id);
+
+ my $event_id = $schedule->eventid;
+ my $run_day = $schedule->run_day;
+ my $run_time = $schedule->run_time;
+ my $is_group = $schedule->mailto_is_group;
+ my $object = $schedule->mailto;
+ my $array_ref = $schedule->mailto_users;
+
+=head1 DESCRIPTION
+
+This module exists to represent a L<Bugzilla::Whine> event schedule.
+
+This is an implementation of L<Bugzilla::Object>, and so has all the
+same methods available as L<Bugzilla::Object>, in addition to what is
+documented below.
+
+=head1 METHODS
+
+=head2 Constructors
+
+=over
+
+=item C<new>
+
+Does not accept a bare C<name> argument. Instead, accepts only an id.
+
+See also: L<Bugzilla::Object/new>.
+
+=back
+
+
+=head2 Accessors
+
+These return data about the object, without modifying the object.
+
+=over
+
+=item C<event_id>
+
+The L<Bugzilla::Whine> event object id for this object.
+
+=item C<run_day>
+
+The day or day pattern that a L<Bugzilla::Whine> event is scheduled to run.
+
+=item C<run_time>
+
+The time or time pattern that a L<Bugzilla::Whine> event is scheduled to run.
+
+=item C<mailto_is_group>
+
+Returns a numeric 1 (C<group>) or 0 (C<user>) to represent whether
+L</mailto> is a group or user.
+
+=item C<mailto>
+
+This is either a L<Bugzilla::User> or L<Bugzilla::Group> object to represent
+the user or group this scheduled event is set to be mailed to.
+
+=item C<mailto_users>
+
+Returns an array reference of L<Bugzilla::User>s. This is derived from the
+L<Bugzilla::Group> stored in L</mailto> if L</mailto_is_group> is true and
+the group is still active, otherwise it will contain a single array element
+for the L<Bugzilla::User> in L</mailto>.
+
+=back
diff --git a/Websites/bugs.webkit.org/QUICKSTART b/Websites/bugs.webkit.org/QUICKSTART
deleted file mode 100644
index 943773a..0000000
--- a/Websites/bugs.webkit.org/QUICKSTART
+++ /dev/null
@@ -1,84 +0,0 @@
-Bugzilla Quick Start Guide
-==========================
-(or, how to get Bugzilla up and running in 10 steps)
-Christian Reis <kiko@async.com.br>
-
-This express installation guide is for "normal" Bugzilla installations,
-which means a Linux or Unix system on which Apache, Perl, MySQL or PostgreSQL
-and a Sendmail compatible MTA are available. For other configurations, please
-see Section 4 of the Bugzilla Guide in the docs/ directory.
-
-1. Decide from which URL and directory under your webserver root you
- will be serving the Bugzilla webpages.
-
-2. Unpack the distribution into the chosen directory (there is no copying or
- installation involved).
-
-3. Run ./checksetup.pl, look for unsolved requirements, and install them.
- You can run checksetup as many times as necessary to check if
- everything required has been installed.
-
- These will usually include assorted Perl modules, MySQL or PostgreSQL,
- and a MTA.
-
- After a successful dependency check, checksetup should complain that
- localconfig needs to be edited.
-
-4. Edit the localconfig file, in particular the $webservergroup and
- $db_* variables. In particular, $db_name and $db_user will define
- your database setup in step 5.
-
-5. Using the name you provided as $db_name above, create a MySQL database
- for Bugzilla. You should also create a user permission for the name
- supplied as $db_user with read/write access to that database.
-
- If you are not familiar with MySQL permissions, it's a good idea to
- use the mysql_setpermission script that is installed with the MySQL
- distribution, and be sure to read Bugzilla Security - MySQL section
- in the Bugzilla Guide or PostgreSQL documentation.
-
-6. Run checksetup.pl once more; if all goes well, it should set up the
- Bugzilla database for you. If not, return to step 5.
-
- checksetup.pl should ask you, this time, for the administrator's
- email address and password. These will be used for the initial
- Bugzilla administrator account.
-
-7. Configure Apache (or install and configure, if you don't have it up
- yet) to point to the Bugzilla directory. You should enable and
- activate mod_cgi, and add the configuration entries
-
- Options +ExecCGI
- AllowOverride Limit
- DirectoryIndex index.cgi
-
- to your Bugzilla <Directory> block. You may also need
-
- AddHandler cgi-script .cgi
-
- if you don't have that in your Apache configuration file yet.
-
-8. Visit the URL you chose for Bugzilla. Your browser should display the
- default Bugzilla home page. You should then log in as the
- administrator by following the "Log in" link and supplying the
- account information you provided in step 6.
-
-9. Scroll to the bottom of the page after logging in, and select
- "Parameters". Set up the relevant parameters for your local setup.
-
- See section 4.2 of the Bugzilla Guide for a in-depth description of
- some of the configuration parameters available.
-
-10. That's it. If anything unexpected comes up:
-
- - read the error message carefully,
- - backtrack through the steps above,
- - check the official installation guide, which is section 4 in the
- Bugzilla Guide, included in the docs/ directory in various
- formats.
-
-Support and installation questions should be directed to the
-mozilla-webtools@mozilla.org mailing list -- don't write to the
-developer mailing list: your post *will* be ignored if you do.
-
-Further support information is at http://www.bugzilla.org/support/
diff --git a/Websites/bugs.webkit.org/README b/Websites/bugs.webkit.org/README
index d29229c..041aebc 100644
--- a/Websites/bugs.webkit.org/README
+++ b/Websites/bugs.webkit.org/README
@@ -1,19 +1,92 @@
-* This README is no longer used to house installation instructions. Instead,
-it contains pointers to where you may find the information you need.
+What is Bugzilla?
+=================
+Bugzilla is a free bug-tracking system that is developed by an active
+community of volunteers in the Mozilla community. You can install and
+use it without having to pay any license fee.
-* A quick installation guide is provided in the QUICKSTART file.
+Minimum requirements
+====================
+It can be installed on Windows, Mac OS X, Linux and other Unix flavors.
+Bugzilla is written in Perl, meaning that Perl must be installed on your system.
+You will also need a web server as well as a DB server (see below).
-* Complete installation instructions are found in docs/, with a
-variety of document types available. Please refer to these documents
-when installing, configuring, and maintaining your Bugzilla
-installation. A helpful starting point is docs/txt/Bugzilla-Guide.txt,
-or with a web browser at docs/html/index.html.
+Installation & Upgrading
+========================
+The documentation to install, upgrade, configure and use Bugzilla can be found
+in different formats:
+* docs/en/html/Bugzilla-Guide.html (HTML version)
+* docs/en/txt/Bugzilla-Guide.txt (text version)
+* docs/en/pdf/Bugzilla-Guide.pdf (PDF version)
-* Release notes for people upgrading to a new version of Bugzilla are
-available at docs/rel_notes.txt.
+If the documentation is missing, you can get it online by visiting
+http://www.bugzilla.org/docs/ from where you can select the documentation
+corresponding to the Bugzilla version you are installing.
-* If you wish to contribute to the documentation, please read docs/README.docs.
+Bugzilla Quick Start Guide
+==========================
+(or, how to get Bugzilla up and running in 10 steps)
+Christian Reis <kiko@async.com.br>
-* The Bugzilla web site is at "http://www.bugzilla.org/". This site will
-contain the latest Bugzilla information, including how to report bugs and how
-to get help with Bugzilla.
+This express installation guide is for "normal" Bugzilla installations,
+which means a Linux or Unix system on which Apache, Perl, MySQL or PostgreSQL
+and a Sendmail compatible MTA are available. For other configurations, please
+see the "Installing Bugzilla" section of the Bugzilla Guide in the docs/ directory.
+
+1. Decide from which URL and directory under your webserver root you
+ will be serving the Bugzilla webpages.
+
+2. Unpack the distribution into the chosen directory (there is no copying or
+ installation involved).
+
+3. Run ./checksetup.pl, look for unsolved requirements, and install them.
+ You can run checksetup as many times as necessary to check if
+ everything required has been installed.
+
+ These will usually include assorted Perl modules, MySQL or PostgreSQL,
+ and a MTA.
+
+ After a successful dependency check, checksetup should complain that
+ localconfig needs to be edited.
+
+4. Edit the localconfig file, in particular the $webservergroup and
+ $db_* variables. In particular, $db_name and $db_user will define
+ your database setup in step 5.
+
+5. Create a user permission for the name supplied as $db_user with
+ read/write access to the database whose name is given by $db_name.
+
+ If you are not familiar with MySQL permissions, it's a good idea to
+ use the mysql_setpermission script that is installed with the MySQL
+ distribution, and be sure to read Bugzilla Security - MySQL section
+ in the Bugzilla Guide or PostgreSQL documentation.
+
+6. Run checksetup.pl once more; if all goes well, it should set up the
+ Bugzilla database for you. If not, return to step 5.
+
+ checksetup.pl should ask you, this time, for the administrator's
+ email address and password. These will be used for the initial
+ Bugzilla administrator account.
+
+7. Configure Apache (or install and configure, if you don't have it up
+ yet) to point to the Bugzilla directory. You can choose between
+ mod_cgi and mod_perl. The Bugzilla documentation has detailed information
+ for both modes.
+
+8. Visit the URL you chose for Bugzilla. Your browser should display the
+ default Bugzilla home page. You should then log in as the
+ administrator by following the "Log in" link and supplying the
+ account information you provided in step 6.
+
+9. Visit the "Parameters" page, as suggested by the page displayed to you.
+ Set up the relevant parameters for your local setup.
+
+10. That's it. If anything unexpected comes up:
+
+ - read the error message carefully,
+ - backtrack through the steps above,
+ - check the official installation guide.
+
+Support and installation questions should be directed to the
+support-bugzilla@lists.mozilla.org mailing list.
+
+Further support information is at http://www.bugzilla.org/support/
diff --git a/Websites/bugs.webkit.org/UPGRADING b/Websites/bugs.webkit.org/UPGRADING
deleted file mode 100644
index fb5e99f..0000000
--- a/Websites/bugs.webkit.org/UPGRADING
+++ /dev/null
@@ -1,3 +0,0 @@
-Please consult The Bugzilla Guide for instructions on how to upgrade
-Bugzilla from an older version. The Guide can be found with this
-distribution, in docs/html, docs/txt, and docs/sgml.
diff --git a/Websites/bugs.webkit.org/UPGRADING-pre-2.8 b/Websites/bugs.webkit.org/UPGRADING-pre-2.8
deleted file mode 100644
index fd35b7b..0000000
--- a/Websites/bugs.webkit.org/UPGRADING-pre-2.8
+++ /dev/null
@@ -1,412 +0,0 @@
-This file contains only important changes made to Bugzilla before release
-2.8. If you are upgrading from version older than 2.8, please read this file.
-If you are upgrading from 2.8 or newer, please read the Installation and
-Upgrade instructions in The Bugzilla Guide, found with this distribution in
-docs/html, docs/txt, and docs/sgml.
-
-Please note that the period in our version numbers is a place separator, not
-a decimal point. The 14 in version 2.14 is newer than the 8 in 2.8, for
-example. You should only be using this file if you have a single digit
-after the period in the version 2.x Bugzilla you are upgrading from.
-
-For a complete list of what changes, use Bonsai
-(http://cvs-mirror.mozilla.org/webtools/bonsai/cvsqueryform.cgi) to
-query the CVS tree. For example,
-
- http://cvs-mirror.mozilla.org/webtools/bonsai/cvsquery.cgi?module=all&branch=HEAD&branchtype=match&dir=mozilla%2Fwebtools%2Fbugzilla&file=&filetype=match&who=&whotype=match&sortby=Date&hours=2&date=week&mindate=&maxdate=&cvsroot=%2Fcvsroot
-
-will tell you what has been changed in the last week.
-
-
-10/12/99 The CHANGES file is now obsolete! There is a new file called
-checksetup.pl. You should get in the habit of running that file every time
-you update your installation of Bugzilla. That file will be constantly
-updated to automatically update your installation to match any code changes.
-If you're curious as to what is going on, changes are commented in that file,
-at the end.
-
-Many thanks to Holger Schurig <holgerschurig@nikocity.de> for writing this
-script!
-
-
-
-10/11/99 Restructured voting database to add a cached value in each
-bug recording how many total votes that bug has. While I'm at it, I
-removed the unused "area" field from the bugs database. It is
-distressing to realize that the bugs table has reached the maximum
-number of indices allowed by MySQL (16), which may make future
-enhancements awkward.
-
-You must feed the following to MySQL:
-
- alter table bugs drop column area;
- alter table bugs add column votes mediumint not null, add index (votes);
-
-You then *must* delete the data/versioncache file when you make this
-change, as it contains references to the "area" field. Deleting it is safe,
-bugzilla will correctly regenerate it.
-
-If you have been using the voting feature at all, then you will then
-need to update the voting cache. You can do this by visiting the
-sanitycheck.cgi page, and taking it up on its offer to rebuild the
-votes stuff.
-
-
-10/7/99 Added voting ability. You must run the new script
-"makevotestable.sh". You must also feed the following to mysql:
-
- alter table products add column votesperuser smallint not null;
-
-
-
-9/15/99 Apparently, newer alphas of MySQL won't allow you to have
-"when" as a column name. So, I have had to rename a column in the
-bugs_activity table. You must feed the below to mysql or you won't
-work at all.
-
- alter table bugs_activity change column when bug_when datetime not null;
-
-
-8/16/99 Added "OpenVMS" to the list of OS's. Feed this to mysql:
-
- alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "BeOS", "OpenVMS", "other") not null;
-
-6/22/99 Added an entry to the attachments table to record who the submitter
-was. Nothing uses this yet, but it still should be recorded.
-
- alter table attachments add column submitter_id mediumint not null;
-
-You should also run this script to populate the new field:
-
-#!/usr/bin/perl -w
-use diagnostics;
-use strict;
-require "globals.pl";
-$|=1;
-ConnectToDatabase();
-SendSQL("select bug_id, attach_id from attachments order by bug_id");
-my @list;
-while (MoreSQLData()) {
- my @row = FetchSQLData();
- push(@list, \@row);
-}
-foreach my $ref (@list) {
- my ($bug, $attach) = (@$ref);
- SendSQL("select long_desc from bugs where bug_id = $bug");
- my $comment = FetchOneColumn() . "Created an attachment (id=$attach)";
-
- if ($comment =~ m@-* Additional Comments From ([^ ]*)[- 0-9/:]*\nCreated an attachment \(id=$attach\)@) {
- print "Found $1\n";
- SendSQL("select userid from profiles where login_name=" .
- SqlQuote($1));
- my $userid = FetchOneColumn();
- if (defined $userid && $userid > 0) {
- SendSQL("update attachments set submitter_id=$userid where attach_id = $attach");
- }
- } else {
- print "Bug $bug can't find comment for attachment $attach\n";
- }
-}
-
-
-
-
-
-
-6/14/99 Added "BeOS" to the list of OS's. Feed this to mysql:
-
- alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "BeOS", "other") not null;
-
-
-5/27/99 Added support for dependency information. You must run the new
-"makedependenciestable.sh" script. You can turn off dependencies with the new
-"usedependencies" param, but it defaults to being on. Also, read very
-carefully the description for the new "webdotbase" param; you will almost
-certainly need to tweak it.
-
-
-5/24/99 Added "Mac System 8.6" and "Neutrino" to the list of OS's.
-Feed this to mysql:
-
- alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "other") not null;
-
-
-5/12/99 Added a pref to control how much email you get. This needs a new
-column in the profiles table, so feed the following to mysql:
-
- alter table profiles add column emailnotification enum("ExcludeSelfChanges", "CConly", "All") not null default "ExcludeSelfChanges";
-
-5/5/99 Added the ability to search by creation date. To make this perform
-well, you ought to do the following:
-
- alter table bugs change column creation_ts creation_ts datetime not null, add index (creation_ts);
-
-
-4/30/99 Added a new severity, "blocker". To get this into your running
-Bugzilla, do the following:
-
- alter table bugs change column bug_severity bug_severity enum("blocker", "critical", "major", "normal", "minor", "trivial", "enhancement") not null;
-
-
-4/22/99 There was a bug where the long descriptions of bugs had a variety of
-newline characters at the end, depending on the operating system of the browser
-that submitted the text. This bug has been fixed, so that no further changes
-like that will happen. But to fix problems that have already crept into your
-database, you can run the following perl script (which is slow and ugly, but
-does work:)
-#!/usr/bin/perl -w
-use diagnostics;
-use strict;
-require "globals.pl";
-$|=1;
-ConnectToDatabase();
-SendSQL("select bug_id from bugs order by bug_id");
-my @list;
-while (MoreSQLData()) {
- push(@list, FetchOneColumn());
-}
-foreach my $id (@list) {
- if ($id % 50 == 0) {
- print "\n$id ";
- }
- SendSQL("select long_desc from bugs where bug_id = $id");
- my $comment = FetchOneColumn();
- my $orig = $comment;
- $comment =~ s/\r\n/\n/g; # Get rid of windows-style line endings.
- $comment =~ s/\r/\n/g; # Get rid of mac-style line endings.
- if ($comment ne $orig) {
- SendSQL("update bugs set long_desc = " . SqlQuote($comment) .
- " where bug_id = $id");
- print ".";
- } else {
- print "-";
- }
-}
-
-
-
-4/8/99 Added ability to store patches with bugs. This requires a new table
-to store the data, so you will need to run the "makeattachmenttable.sh" script.
-
-3/25/99 Unfortunately, the HTML::FromText CPAN module had too many bugs, and
-so I had to roll my own. We no longer use the HTML::FromText CPAN module.
-
-3/24/99 (This entry has been removed. It used to say that we required the
-HTML::FromText CPAN module, but that's no longer true.)
-
-3/22/99 Added the ability to query by fields which have changed within a date
-range. To make this perform a bit better, we need a new index:
-
- alter table bugs_activity add index (field);
-
-3/10/99 Added 'groups' stuff, where we have different group bits that we can
-put on a person or on a bug. Some of the group bits control access to bugzilla
-features. And a person can't access a bug unless he has every group bit set
-that is also set on the bug. See the comments in makegroupstable.sh for a bit
-more info.
-
-The 'maintainer' param is now used only as an email address for people to send
-complaints to. The groups table is what is now used to determine permissions.
-
-You will need to run the new script "makegroupstable.sh". And then you need to
-feed the following lines to MySQL (replace XXX with the login name of the
-maintainer, the person you wish to be all-powerful).
-
- alter table bugs add column groupset bigint not null;
- alter table profiles add column groupset bigint not null;
- update profiles set groupset=0x7fffffffffffffff where login_name = XXX;
-
-
-
-3/8/99 Added params to control how priorities are set in a new bug. You can
-now choose whether to let submitters of new bugs choose a priority, or whether
-they should just accept the default priority (which is now no longer hardcoded
-to "P2", but is instead a param.) The default value of the params will cause
-the same behavior as before.
-
-3/3/99 Added a "disallownew" field to the products table. If non-zero, then
-don't let people file new bugs against this product. (This is for when a
-product is retired, but you want to keep the bug reports around for posterity.)
-Feed this to MySQL:
-
- alter table products add column disallownew tinyint not null;
-
-
-2/8/99 Added FreeBSD to the list of OS's. Feed this to MySQL:
-
- alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "OS/2", "other") not null;
-
-
-2/4/99 Added a new column "description" to the components table, and added
-links to a new page which will use this to describe the components of a
-given product. Feed this to MySQL:
-
- alter table components add column description mediumtext not null;
-
-
-2/3/99 Added a new column "initialqacontact" to the components table that gives
-an initial QA contact field. It may be empty if you wish the initial qa
-contact to be empty. If you're not using the QA contact field, you don't need
-to add this column, but you might as well be safe and add it anyway:
-
- alter table components add column initialqacontact tinytext not null;
-
-
-2/2/99 Added a new column "milestoneurl" to the products table that gives a URL
-which is to describe the currently defined milestones for a product. If you
-don't use target milestone, you might be able to get away without adding this
-column, but you might as well be safe and add it anyway:
-
- alter table products add column milestoneurl tinytext not null;
-
-
-1/29/99 Whoops; had a misspelled op_sys. It was "Mac System 7.1.6"; it should
-be "Mac System 7.6.1". It turns out I had no bugs with this value set, so I
-could just do the below simple command. If you have bugs with this value, you
-may need to do something more complicated.
-
- alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "OSF/1", "Solaris", "SunOS", "OS/2", "other") not null;
-
-
-
-1/20/99 Added new fields: Target Milestone, QA Contact, and Status Whiteboard.
-These fields are all optional in the UI; there are parameters to turn them on.
-However, whether or not you use them, the fields need to be in the DB. There
-is some code that needs them, even if you don't.
-
-To update your DB to have these fields, send the following to MySQL:
-
- alter table bugs add column target_milestone varchar(20) not null,
- add column qa_contact mediumint not null,
- add column status_whiteboard mediumtext not null,
- add index (target_milestone), add index (qa_contact);
-
-
-
-1/18/99 You can now query by CC. To make this perform reasonably, the CC table
-needs some indices. The following MySQL does the necessary stuff:
-
- alter table cc add index (bug_id), add index (who);
-
-
-1/15/99 The op_sys field can now be queried by (and more easily tweaked).
-To make this perform reasonably, it needs an index. The following MySQL
-command will create the necessary index:
-
- alter table bugs add index (op_sys);
-
-
-12/2/98 The op_sys and rep_platform fields have been tweaked. op_sys
-is now an enum, rather than having the legal values all hard-coded in
-perl. rep_platform now no longer allows a value of "X-Windows".
-
-Here's how I ported to the new world. This ought to work for you too.
-Actually, it's probably overkill. I had a lot of illegal values for op_sys
-in my tables, from importing bugs from strange places. If you haven't done
-anything funky, then much of the below will be a no-op.
-
-First, send the following commands to MySQL to make sure all your values for
-rep_platform and op_sys are legal in the new world..
-
- update bugs set rep_platform="Sun" where rep_platform="X-Windows" and op_sys like "Solaris%";
- update bugs set rep_platform="SGI" where rep_platform="X-Windows" and op_sys = "IRIX";
- update bugs set rep_platform="SGI" where rep_platform="X-Windows" and op_sys = "HP-UX";
- update bugs set rep_platform="DEC" where rep_platform="X-Windows" and op_sys = "OSF/1";
- update bugs set rep_platform="PC" where rep_platform="X-Windows" and op_sys = "Linux";
- update bugs set rep_platform="other" where rep_platform="X-Windows";
- update bugs set rep_platform="other" where rep_platform="";
- update bugs set op_sys="Mac System 7" where op_sys="System 7";
- update bugs set op_sys="Mac System 7.5" where op_sys="System 7.5";
- update bugs set op_sys="Mac System 8.0" where op_sys="8.0";
- update bugs set op_sys="OSF/1" where op_sys="Digital Unix 4.0";
- update bugs set op_sys="IRIX" where op_sys like "IRIX %";
- update bugs set op_sys="HP-UX" where op_sys like "HP-UX %";
- update bugs set op_sys="Windows NT" where op_sys like "NT %";
- update bugs set op_sys="OSF/1" where op_sys like "OSF/1 %";
- update bugs set op_sys="Solaris" where op_sys like "Solaris %";
- update bugs set op_sys="SunOS" where op_sys like "SunOS%";
- update bugs set op_sys="other" where op_sys = "Motif";
- update bugs set op_sys="other" where op_sys = "Other";
-
-Next, send the following commands to make sure you now have only legal
-entries in your table. If either of the queries do not come up empty, then
-you have to do more stuff like the above.
-
- select bug_id,op_sys,rep_platform from bugs where rep_platform not regexp "^(All|DEC|HP|Macintosh|PC|SGI|Sun|X-Windows|Other)$";
- select bug_id,op_sys,rep_platform from bugs where op_sys not regexp "^(All|Windows 3.1|Windows 95|Windows 98|Windows NT|Mac System 7|Mac System 7.5|Mac System 7.1.6|Mac System 8.0|AIX|BSDI|HP-UX|IRIX|Linux|OSF/1|Solaris|SunOS|other)$";
-
-Finally, once that's all clear, alter the table to make enforce the new legal
-entries:
-
- alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.1.6", "Mac System 8.0", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "OSF/1", "Solaris", "SunOS", "other") not null, change column rep_platform rep_platform enum("All", "DEC", "HP", "Macintosh", "PC", "SGI", "Sun", "Other");
-
-
-
-
-
-11/20/98 Added searching of CC field. To better support this, added
-some indexes to the CC table. You probably want to execute the following
-mysql commands:
-
- alter table cc add index (bug_id);
- alter table cc add index (who);
-
-
-10/27/98 security check for legal products in place. bug charts are not
-available as an option if collectstats.pl has never been run. all products
-get daily stats collected now. README updated: Chart::Base is listed as
-a requirement, instructions for using collectstats.pl included as
-an optional step. also got silly and added optional quips to bug
-reports.
-
-10/17/98 modified README installation instructions slightly.
-
-10/7/98 Added a new table called "products". Right now, this is used
-only to have a description for each product, and that description is
-only used when initially adding a new bug. Anyway, you *must* create
-the new table (which you can do by running the new makeproducttable.sh
-script). If you just leave it empty, things will work much as they
-did before, or you can add descriptions for some or all of your
-products.
-
-
-9/15/98 Everything has been ported to Perl. NO MORE TCL. This
-transition should be relatively painless, except for the "params"
-file. This is the file that contains parameters you've set up on the
-editparams.cgi page. Before changing to Perl, this was a tcl-syntax
-file, stored in the same directory as the code; after the change to
-Perl, it becomes a perl-syntax file, stored in a subdirectory named
-"data". See the README file for more details on what version of Perl
-you need.
-
-So, if updating from an older version of Bugzilla, you will need to
-edit data/param, change the email address listed for
-$::param{'maintainer'}, and then go revisit the editparams.cgi page
-and reset all the parameters to your taste. Fortunately, your old
-params file will still be around, and so you ought to be able to
-cut&paste important bits from there.
-
-Also, note that the "whineatnews" script has changed name (it now has
-an extension of .pl instead of .tcl), so you'll need to change your
-cron job.
-
-And the "comments" file has been moved to the data directory. Just do
-"cat comments >> data/comments" to restore any old comments that may
-have been lost.
-
-
-
-9/2/98 Changed the way password validation works. We now keep a
-crypt'd version of the password in the database, and check against
-that. (This is silly, because we're also keeping the plaintext
-version there, but I have plans...) Stop passing the plaintext
-password around as a cookie; instead, we have a cookie that references
-a record in a new database table, logincookies.
-
-IMPORTANT: if updating from an older version of Bugzilla, you must run
-the following commands to keep things working:
-
- ./makelogincookiestable.sh
- echo "alter table profiles add column cryptpassword varchar(64);" | mysql bugs
- echo "update profiles set cryptpassword = encrypt(password,substring(rand(),3, 4));" | mysql bugs
-
diff --git a/Websites/bugs.webkit.org/attachment.cgi b/Websites/bugs.webkit.org/attachment.cgi
index 3bf89222..466f096 100755
--- a/Websites/bugs.webkit.org/attachment.cgi
+++ b/Websites/bugs.webkit.org/attachment.cgi
@@ -39,6 +39,7 @@
use lib qw(. lib);
use Bugzilla;
+use Bugzilla::BugMail;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Flag;
@@ -52,6 +53,7 @@
use Bugzilla::Token;
use Bugzilla::Keyword;
+use Encode qw(encode find_encoding);
#if WEBKIT_CHANGES
use Apache2::SubProcess ();
use Apache2::RequestUtil ();
@@ -76,25 +78,20 @@
# Determine whether to use the action specified by the user or the default.
my $action = $cgi->param('action') || 'view';
+my $format = $cgi->param('format') || '';
# You must use the appropriate urlbase/sslbase param when doing anything
-# but viewing an attachment.
-if ($action ne 'view') {
- my $urlbase = Bugzilla->params->{'urlbase'};
- my $sslbase = Bugzilla->params->{'sslbase'};
- my $path_regexp = $sslbase ? qr/^(\Q$urlbase\E|\Q$sslbase\E)/ : qr/^\Q$urlbase\E/;
- if (use_attachbase() && $cgi->self_url !~ /$path_regexp/) {
+# but viewing an attachment, or a raw diff.
+if ($action ne 'view'
+ && (($action !~ /^(?:interdiff|diff)$/) || $format ne 'raw'))
+{
+ do_ssl_redirect_if_required();
+ if ($cgi->url_is_attachment_base) {
$cgi->redirect_to_urlbase;
}
Bugzilla->login();
}
-# Determine if PatchReader is installed
-eval {
- require PatchReader;
- $vars->{'patchviewerinstalled'} = 1;
-};
-
# When viewing an attachment, do not request credentials if we are on
# the alternate host. Let view() decide when to call Bugzilla->login.
if ($action eq "view")
@@ -157,7 +154,7 @@
}
else
{
- ThrowCodeError("unknown_action", { action => $action });
+ ThrowUserError('unknown_action', {action => $action});
}
exit;
@@ -197,11 +194,12 @@
# non-natural, so use the original value from $cgi in our exception
# message here.
detaint_natural($attach_id)
- || ThrowUserError("invalid_attach_id", { attach_id => $cgi->param($param) });
+ || ThrowUserError("invalid_attach_id",
+ { attach_id => scalar $cgi->param($param) });
# Make sure the attachment exists in the database.
- my $attachment = Bugzilla::Attachment->get($attach_id)
- || ThrowUserError("invalid_attach_id", { attach_id => $attach_id });
+ my $attachment = new Bugzilla::Attachment($attach_id)
+ || ThrowUserError("invalid_attach_id", { attach_id => $attach_id });
return $attachment if ($dont_validate_access || check_can_access($attachment));
}
@@ -212,10 +210,13 @@
my $user = Bugzilla->user;
# Make sure the user is authorized to access this attachment's bug.
- ValidateBugID($attachment->bug_id);
- if ($attachment->isprivate && $user->id != $attachment->attacher->id && !$user->is_insider) {
+ Bugzilla::Bug->check($attachment->bug_id);
+ if ($attachment->isprivate && $user->id != $attachment->attacher->id
+ && !$user->is_insider)
+ {
ThrowUserError('auth_failure', {action => 'access',
- object => 'attachment'});
+ object => 'attachment',
+ attach_id => $attachment->id});
}
return 1;
}
@@ -235,12 +236,10 @@
# Validates format of a diff/interdiff. Takes a list as an parameter, which
# defines the valid format values. Will throw an error if the format is not
# in the list. Returns either the user selected or default format.
-sub validateFormat
-{
+sub validateFormat {
# receives a list of legal formats; first item is a default
my $format = $cgi->param('format') || $_[0];
- if ( lsearch(\@_, $format) == -1)
- {
+ if (not grep($_ eq $format, @_)) {
ThrowUserError("invalid_format", { format => $format, formats => \@_ });
}
@@ -260,63 +259,51 @@
return $context;
}
-sub validateCanChangeBug
-{
- my ($bugid) = @_;
- my $dbh = Bugzilla->dbh;
- my ($productid) = $dbh->selectrow_array(
- "SELECT product_id
- FROM bugs
- WHERE bug_id = ?", undef, $bugid);
+# Gets the attachment object(s) generated by validateID, while ensuring
+# attachbase and token authentication is used when required.
+sub get_attachment {
+ my @field_names = @_ ? @_ : qw(id);
- Bugzilla->user->can_edit_product($productid)
- || ThrowUserError("illegal_attachment_edit_bug",
- { bug_id => $bugid });
-}
-
-################################################################################
-# Functions
-################################################################################
-
-# Display an attachment.
-sub view {
- my $attachment;
+ my %attachments;
if (use_attachbase()) {
- $attachment = validateID(undef, 1);
- # Replace %bugid% by the ID of the bug the attachment belongs to, if present.
- my $attachbase = Bugzilla->params->{'attachment_base'};
- my $bug_id = $attachment->bug_id;
- $attachbase =~ s/%bugid%/$bug_id/;
- my $path = 'attachment.cgi?id=' . $attachment->id;
+ # Load each attachment, and ensure they are all from the same bug
+ my $bug_id = 0;
+ foreach my $field_name (@field_names) {
+ my $attachment = validateID($field_name, 1);
+ if (!$bug_id) {
+ $bug_id = $attachment->bug_id;
+ } elsif ($attachment->bug_id != $bug_id) {
+ ThrowUserError('attachment_bug_id_mismatch');
+ }
+ $attachments{$field_name} = $attachment;
+ }
+ my @args = map { $_ . '=' . $attachments{$_}->id } @field_names;
+ my $cgi_params = $cgi->canonicalise_query(@field_names, 't',
+ 'Bugzilla_login', 'Bugzilla_password');
+ push(@args, $cgi_params) if $cgi_params;
+ my $path = 'attachment.cgi?' . join('&', @args);
# Make sure the attachment is served from the correct server.
- if ($cgi->self_url !~ /^\Q$attachbase\E/) {
- # We couldn't call Bugzilla->login earlier as we first had to make sure
- # we were not going to request credentials on the alternate host.
- Bugzilla->login();
- if (attachmentIsPublic($attachment)) {
- # No need for a token; redirect to attachment base.
- print $cgi->redirect(-location => $attachbase . $path);
- exit;
- } else {
- # Make sure the user can view the attachment.
- check_can_access($attachment);
- # Create a token and redirect.
- my $token = url_quote(issue_session_token($attachment->id));
- print $cgi->redirect(-location => $attachbase . "$path&t=$token");
- exit;
- }
- } else {
+ if ($cgi->url_is_attachment_base($bug_id)) {
# No need to validate the token for public attachments. We cannot request
# credentials as we are on the alternate host.
- if (!attachmentIsPublic($attachment)) {
+ if (!all_attachments_are_public(\%attachments)) {
my $token = $cgi->param('t');
- my ($userid, undef, $token_attach_id) = Bugzilla::Token::GetTokenData($token);
- unless ($userid
- && detaint_natural($token_attach_id)
- && ($token_attach_id == $attachment->id))
- {
+ my ($userid, undef, $token_data) = Bugzilla::Token::GetTokenData($token);
+ my %token_data = unpack_token_data($token_data);
+ my $valid_token = 1;
+ foreach my $field_name (@field_names) {
+ my $token_id = $token_data{$field_name};
+ if (!$token_id
+ || !detaint_natural($token_id)
+ || $attachments{$field_name}->id != $token_id)
+ {
+ $valid_token = 0;
+ last;
+ }
+ }
+ unless ($userid && $valid_token) {
# Not a valid token.
print $cgi->redirect('-location' => correct_urlbase() . $path);
exit;
@@ -327,24 +314,91 @@
delete_token($token);
}
}
+ elsif ($cgi->url_is_attachment_base) {
+ # If we come here, this means that each bug has its own host
+ # for attachments, and that we are trying to view one attachment
+ # using another bug's host. That's not desired.
+ $cgi->redirect_to_urlbase;
+ }
+ else {
+ # We couldn't call Bugzilla->login earlier as we first had to
+ # make sure we were not going to request credentials on the
+ # alternate host.
+ Bugzilla->login();
+ my $attachbase = Bugzilla->params->{'attachment_base'};
+ # Replace %bugid% by the ID of the bug the attachment
+ # belongs to, if present.
+ $attachbase =~ s/\%bugid\%/$bug_id/;
+ if (all_attachments_are_public(\%attachments)) {
+ # No need for a token; redirect to attachment base.
+ print $cgi->redirect(-location => $attachbase . $path);
+ exit;
+ } else {
+ # Make sure the user can view the attachment.
+ foreach my $field_name (@field_names) {
+ check_can_access($attachments{$field_name});
+ }
+ # Create a token and redirect.
+ my $token = url_quote(issue_session_token(pack_token_data(\%attachments)));
+ print $cgi->redirect(-location => $attachbase . "$path&t=$token");
+ exit;
+ }
+ }
} else {
+ do_ssl_redirect_if_required();
# No alternate host is used. Request credentials if required.
Bugzilla->login();
- $attachment = validateID();
+ foreach my $field_name (@field_names) {
+ $attachments{$field_name} = validateID($field_name);
+ }
}
+ return wantarray
+ ? map { $attachments{$_} } @field_names
+ : $attachments{$field_names[0]};
+}
+
+sub all_attachments_are_public {
+ my $attachments = shift;
+ foreach my $field_name (keys %$attachments) {
+ if (!attachmentIsPublic($attachments->{$field_name})) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+sub pack_token_data {
+ my $attachments = shift;
+ return join(' ', map { $_ . '=' . $attachments->{$_}->id } keys %$attachments);
+}
+
+sub unpack_token_data {
+ my @token_data = split(/ /, shift || '');
+ my %data;
+ foreach my $token (@token_data) {
+ my ($field_name, $attach_id) = split('=', $token);
+ $data{$field_name} = $attach_id;
+ }
+ return %data;
+}
+
+################################################################################
+# Functions
+################################################################################
+
+# Display an attachment.
+sub view {
+ my $attachment = get_attachment();
+
# At this point, Bugzilla->login has been called if it had to.
my $contenttype = $attachment->contenttype;
my $filename = $attachment->filename;
# Bug 111522: allow overriding content-type manually in the posted form
# params.
- if (defined $cgi->param('content_type'))
- {
- $cgi->param('contenttypemethod', 'manual');
- $cgi->param('contenttypeentry', $cgi->param('content_type'));
- Bugzilla::Attachment->validate_content_type(THROW_ERROR);
- $contenttype = $cgi->param('content_type');
+ if (defined $cgi->param('content_type')) {
+ $contenttype = $attachment->_check_content_type($cgi->param('content_type'));
}
# Return the appropriate HTTP response headers.
@@ -355,20 +409,45 @@
$filename =~ s/\\/\\\\/g; # escape backslashes
$filename =~ s/"/\\"/g; # escape quotes
+ # Avoid line wrapping done by Encode, which we don't need for HTTP
+ # headers. See discussion in bug 328628 for details.
+ local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 10000;
+ $filename = encode('MIME-Q', $filename);
+
my $disposition = Bugzilla->params->{'allow_attachment_display'} ? 'inline' : 'attachment';
+ # Don't send a charset header with attachments--they might not be UTF-8.
+ # However, we do allow people to explicitly specify a charset if they
+ # want.
+ if ($contenttype !~ /\bcharset=/i) {
+ # In order to prevent Apache from adding a charset, we have to send a
+ # charset that's a single space.
+ $cgi->charset(' ');
+ if (Bugzilla->feature('detect_charset') && $contenttype =~ /^text\//) {
+ my $encoding = detect_encoding($attachment->data);
+ if ($encoding) {
+ $cgi->charset(find_encoding($encoding)->mime_name);
+ }
+ }
+ }
print $cgi->header(-type=>"$contenttype; name=\"$filename\"",
-content_disposition=> "$disposition; filename=\"$filename\"",
- -content_length => $attachment->datasize);
+ -content_length => $attachment->datasize,
+ -x_content_type_options => "nosniff");
disable_utf8();
print $attachment->data;
}
sub interdiff {
# Retrieve and validate parameters
- my $old_attachment = validateID('oldid');
- my $new_attachment = validateID('newid');
my $format = validateFormat('html', 'raw');
+ my($old_attachment, $new_attachment);
+ if ($format eq 'raw') {
+ ($old_attachment, $new_attachment) = get_attachment('oldid', 'newid');
+ } else {
+ $old_attachment = validateID('oldid');
+ $new_attachment = validateID('newid');
+ }
my $context = validateContext();
Bugzilla::Attachment::PatchReader::process_interdiff(
@@ -411,8 +490,8 @@
sub diff {
# Retrieve and validate parameters
- my $attachment = validateID();
my $format = validateFormat('html', 'raw');
+ my $attachment = $format eq 'raw' ? get_attachment() : validateID();
my $context = validateContext();
# If it is not a patch, view normally.
@@ -428,11 +507,17 @@
# HTML page.
sub viewall {
# Retrieve and validate parameters
- my $bugid = $cgi->param('bugid');
- ValidateBugID($bugid);
- my $bug = new Bugzilla::Bug($bugid);
+ my $bug = Bugzilla::Bug->check(scalar $cgi->param('bugid'));
+ my $bugid = $bug->id;
my $attachments = Bugzilla::Attachment->get_attachments_by_bug($bugid);
+ # Ignore deleted attachments.
+ @$attachments = grep { $_->datasize } @$attachments;
+
+ if ($cgi->param('hide_obsolete')) {
+ @$attachments = grep { !$_->isobsolete } @$attachments;
+ $vars->{'hide_obsolete'} = 1;
+ }
# Define the variables and functions that will be passed to the UI template.
$vars->{'bug'} = $bug;
@@ -448,13 +533,12 @@
# Display a form for entering a new attachment.
sub enter {
# Retrieve and validate parameters
- my $bugid = $cgi->param('bugid');
- ValidateBugID($bugid);
- validateCanChangeBug($bugid);
+ my $bug = Bugzilla::Bug->check(scalar $cgi->param('bugid'));
+ my $bugid = $bug->id;
+ Bugzilla::Attachment->_check_bug($bug);
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
- my $bug = new Bugzilla::Bug($bugid, $user->id);
# Retrieve the attachments the user can edit from the database and write
# them into an array of hashes where each hash represents one attachment.
my $canEdit = "";
@@ -467,14 +551,15 @@
# Define the variables and functions that will be passed to the UI template.
$vars->{'bug'} = $bug;
- $vars->{'attachments'} = Bugzilla::Attachment->get_list($attach_ids);
+ $vars->{'attachments'} = Bugzilla::Attachment->new_from_list($attach_ids);
my $flag_types = Bugzilla::FlagType::match({'target_type' => 'attachment',
'product_id' => $bug->product_id,
'component_id' => $bug->component_id});
$vars->{'flag_types'} = $flag_types;
- $vars->{'any_flags_requesteeble'} = grep($_->is_requesteeble, @$flag_types);
- $vars->{'token'} = issue_session_token('createattachment:');
+ $vars->{'any_flags_requesteeble'} =
+ grep { $_->is_requestable && $_->is_requesteeble } @$flag_types;
+ $vars->{'token'} = issue_session_token('create_attachment');
print $cgi->header();
@@ -491,46 +576,57 @@
$dbh->bz_start_transaction;
# Retrieve and validate parameters
- my $bugid = $cgi->param('bugid');
- ValidateBugID($bugid);
- validateCanChangeBug($bugid);
- my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()");
+ my $bug = Bugzilla::Bug->check(scalar $cgi->param('bugid'));
+ my $bugid = $bug->id;
+ my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
# Detect if the user already used the same form to submit an attachment
my $token = trim($cgi->param('token'));
- if ($token) {
- my ($creator_id, $date, $old_attach_id) = Bugzilla::Token::GetTokenData($token);
- unless ($creator_id
- && ($creator_id == $user->id)
- && ($old_attach_id =~ "^createattachment:"))
- {
- # The token is invalid.
- ThrowUserError('token_does_not_exist');
- }
-
- $old_attach_id =~ s/^createattachment://;
-
- if ($old_attach_id) {
- $vars->{'bugid'} = $bugid;
- $vars->{'attachid'} = $old_attach_id;
- print $cgi->header();
- $template->process("attachment/cancel-create-dupe.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
+ check_token_data($token, 'create_attachment', 'index.cgi');
+
+ # Check attachments the user tries to mark as obsolete.
+ my @obsolete_attachments;
+ if ($cgi->param('obsolete')) {
+ my @obsolete = $cgi->param('obsolete');
+ @obsolete_attachments = Bugzilla::Attachment->validate_obsolete($bug, \@obsolete);
}
- my $bug = new Bugzilla::Bug($bugid);
- my $attachment =
- Bugzilla::Attachment->insert_attachment_for_bug(THROW_ERROR, $bug, $user,
- $timestamp, $vars);
+ # Must be called before create() as it may alter $cgi->param('ispatch').
+ my $content_type = Bugzilla::Attachment::get_content_type();
+
+ # Get the filehandle of the attachment.
+ my $data_fh = $cgi->upload('data');
+
+ my $attachment = Bugzilla::Attachment->create(
+ {bug => $bug,
+ creation_ts => $timestamp,
+ data => scalar $cgi->param('attach_text') || $data_fh,
+ description => scalar $cgi->param('description'),
+ filename => $cgi->param('attach_text') ? "file_$bugid.txt" : scalar $cgi->upload('data'),
+ ispatch => scalar $cgi->param('ispatch'),
+ isprivate => scalar $cgi->param('isprivate'),
+ mimetype => $content_type,
+ });
+
+ # Delete the token used to create this attachment.
+ delete_token($token);
+
+ foreach my $obsolete_attachment (@obsolete_attachments) {
+ $obsolete_attachment->set_is_obsolete(1);
+ $obsolete_attachment->update($timestamp);
+ }
+
+ my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi(
+ $bug, $attachment, $vars, SKIP_REQUESTEE_ON_ERROR);
+ $attachment->set_flags($flags, $new_flags);
+ $attachment->update($timestamp);
# Insert a comment about the new attachment into the database.
- my $comment = "Created an attachment (id=" . $attachment->id . ")\n" .
- $attachment->description . "\n";
- $comment .= ("\n" . $cgi->param('comment')) if defined $cgi->param('comment');
-
- $bug->add_comment($comment, { isprivate => $attachment->isprivate });
+ my $comment = $cgi->param('comment');
+ $comment = '' unless defined $comment;
+ $bug->add_comment($comment, { isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_CREATED,
+ extra_data => $attachment->id });
# Assign the bug to the user, if they are allowed to take it
my $owner = "";
@@ -540,9 +636,10 @@
($bug_status) = grep {$_->name eq $bug_status} @{$bug->status->can_change_to};
if ($bug_status && $bug_status->is_open
- && ($bug_status->name ne 'UNCONFIRMED' || $bug->product_obj->votes_to_confirm))
+ && ($bug_status->name ne 'UNCONFIRMED'
+ || $bug->product_obj->allows_unconfirmed))
{
- $bug->set_status($bug_status->name);
+ $bug->set_bug_status($bug_status->name);
$bug->clear_resolution();
}
# Make sure the person we are taking the bug from gets mail.
@@ -551,24 +648,18 @@
}
$bug->update($timestamp);
- if ($token) {
- trick_taint($token);
- $dbh->do('UPDATE tokens SET eventdata = ? WHERE token = ?', undef,
- ("createattachment:" . $attachment->id, $token));
- }
-
$dbh->bz_commit_transaction;
# Define the variables and functions that will be passed to the UI template.
- $vars->{'mailrecipients'} = { 'changer' => $user->login,
- 'owner' => $owner };
$vars->{'attachment'} = $attachment;
# We cannot reuse the $bug object as delta_ts has eventually been updated
# since the object was created.
$vars->{'bugs'} = [new Bugzilla::Bug($bugid)];
$vars->{'header_done'} = 1;
$vars->{'contenttypemethod'} = $cgi->param('contenttypemethod');
- $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
+
+ my $recipients = { 'changer' => $user, 'owner' => $owner };
+ $vars->{'sent_bugmail'} = Bugzilla::BugMail::Send($bugid, $recipients);
print $cgi->header();
# Generate and return the UI (HTML page) from the appropriate template.
@@ -587,32 +678,19 @@
#endif // WEBKIT_CHANGES
my $attachment = validateID();
- my $dbh = Bugzilla->dbh;
- # Retrieve a list of attachments for this bug as well as a summary of the bug
- # to use in a navigation bar across the top of the screen.
my $bugattachments =
Bugzilla::Attachment->get_attachments_by_bug($attachment->bug_id);
# We only want attachment IDs.
@$bugattachments = map { $_->id } @$bugattachments;
- my ($bugsummary, $product_id, $component_id) =
- $dbh->selectrow_array('SELECT short_desc, product_id, component_id
- FROM bugs
- WHERE bug_id = ?', undef, $attachment->bug_id);
-
- # Get a list of flag types that can be set for this attachment.
- my $flag_types = Bugzilla::FlagType::match({ 'target_type' => 'attachment' ,
- 'product_id' => $product_id ,
- 'component_id' => $component_id });
- foreach my $flag_type (@$flag_types) {
- $flag_type->{'flags'} = Bugzilla::Flag->match({ 'type_id' => $flag_type->id,
- 'attach_id' => $attachment->id });
- }
- $vars->{'flag_types'} = $flag_types;
- $vars->{'any_flags_requesteeble'} = grep($_->is_requesteeble, @$flag_types);
+ my $any_flags_requesteeble =
+ grep { $_->is_requestable && $_->is_requesteeble } @{$attachment->flag_types};
+ # Useful in case a flagtype is no longer requestable but a requestee
+ # has been set before we turned off that bit.
+ $any_flags_requesteeble ||= grep { $_->requestee_id } @{$attachment->flags};
+ $vars->{'any_flags_requesteeble'} = $any_flags_requesteeble;
$vars->{'attachment'} = $attachment;
- $vars->{'bugsummary'} = $bugsummary;
$vars->{'attachments'} = $bugattachments;
#if WEBKIT_CHANGES
@@ -630,48 +708,53 @@
|| ThrowTemplateError($template->error());
}
-# Updates an attachment record. Users with "editbugs" privileges, (or the
-# original attachment's submitter) can edit the attachment's description,
-# content type, ispatch and isobsolete flags, and statuses, and they can
-# also submit a comment that appears in the bug.
+# Updates an attachment record. Only users with "editbugs" privileges,
+# (or the original attachment's submitter) can edit the attachment.
# Users cannot edit the content of the attachment itself.
sub update {
my $user = Bugzilla->user;
my $dbh = Bugzilla->dbh;
+ # Start a transaction in preparation for updating the attachment.
+ $dbh->bz_start_transaction();
+
# Retrieve and validate parameters
my $attachment = validateID();
- my $bug = new Bugzilla::Bug($attachment->bug_id);
- $attachment->validate_can_edit($bug->product_id);
- validateCanChangeBug($bug->id);
- Bugzilla::Attachment->validate_description(THROW_ERROR);
- Bugzilla::Attachment->validate_is_patch(THROW_ERROR);
- Bugzilla::Attachment->validate_content_type(THROW_ERROR) unless $cgi->param('ispatch');
- $cgi->param('isobsolete', $cgi->param('isobsolete') ? 1 : 0);
- $cgi->param('isprivate', $cgi->param('isprivate') ? 1 : 0);
+ my $bug = $attachment->bug;
+ $attachment->_check_bug;
+ my $can_edit = $attachment->validate_can_edit($bug->product_id);
- # Now make sure the attachment has not been edited since we loaded the page.
- if (defined $cgi->param('delta_ts')
- && $cgi->param('delta_ts') ne $attachment->modification_time)
- {
- ($vars->{'operations'}) =
- Bugzilla::Bug::GetBugActivity($bug->id, $attachment->id, $cgi->param('delta_ts'));
+ if ($can_edit) {
+ $attachment->set_description(scalar $cgi->param('description'));
+ $attachment->set_is_patch(scalar $cgi->param('ispatch'));
+ $attachment->set_content_type(scalar $cgi->param('contenttypeentry'));
+ $attachment->set_is_obsolete(scalar $cgi->param('isobsolete'));
+ $attachment->set_is_private(scalar $cgi->param('isprivate'));
+ $attachment->set_filename(scalar $cgi->param('filename'));
- # The token contains the old modification_time. We need a new one.
- $cgi->param('token', issue_hash_token([$attachment->id, $attachment->modification_time]));
+ # Now make sure the attachment has not been edited since we loaded the page.
+ if (defined $cgi->param('delta_ts')
+ && $cgi->param('delta_ts') ne $attachment->modification_time)
+ {
+ ($vars->{'operations'}) =
+ Bugzilla::Bug::GetBugActivity($bug->id, $attachment->id, $cgi->param('delta_ts'));
- # If the modification date changed but there is no entry in
- # the activity table, this means someone commented only.
- # In this case, there is no reason to midair.
- if (scalar(@{$vars->{'operations'}})) {
- $cgi->param('delta_ts', $attachment->modification_time);
- $vars->{'attachment'} = $attachment;
+ # The token contains the old modification_time. We need a new one.
+ $cgi->param('token', issue_hash_token([$attachment->id, $attachment->modification_time]));
- print $cgi->header();
- # Warn the user about the mid-air collision and ask them what to do.
- $template->process("attachment/midair.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ # If the modification date changed but there is no entry in
+ # the activity table, this means someone commented only.
+ # In this case, there is no reason to midair.
+ if (scalar(@{$vars->{'operations'}})) {
+ $cgi->param('delta_ts', $attachment->modification_time);
+ $vars->{'attachment'} = $attachment;
+
+ print $cgi->header();
+ # Warn the user about the mid-air collision and ask them what to do.
+ $template->process("attachment/midair.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
}
}
@@ -680,128 +763,49 @@
my $token = $cgi->param('token');
check_hash_token($token, [$attachment->id, $attachment->modification_time]);
- # If the submitter of the attachment is not in the insidergroup,
- # be sure that he cannot overwrite the private bit.
- # This check must be done before calling Bugzilla::Flag*::validate(),
- # because they will look at the private bit when checking permissions.
- # XXX - This is a ugly hack. Ideally, we shouldn't have to look at the
- # old private bit twice (first here, and then below again), but this is
- # the less risky change.
- unless ($user->is_insider) {
- $cgi->param('isprivate', $attachment->isprivate);
- }
-
# If the user submitted a comment while editing the attachment,
# add the comment to the bug. Do this after having validated isprivate!
- if ($cgi->param('comment')) {
- # Prepend a string to the comment to let users know that the comment came
- # from the "edit attachment" screen.
- my $comment = "(From update of attachment " . $attachment->id . ")\n" .
- $cgi->param('comment');
-
- $bug->add_comment($comment, { isprivate => $cgi->param('isprivate') });
+ my $comment = $cgi->param('comment');
+ if (defined $comment && trim($comment) ne '') {
+ $bug->add_comment($comment, { isprivate => $attachment->isprivate,
+ type => CMT_ATTACHMENT_UPDATED,
+ extra_data => $attachment->id });
}
- # 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.
- Bugzilla::User::match_field($cgi, {
- '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' }
- });
- Bugzilla::Flag::validate($bug->id, $attachment->id);
+ if ($can_edit) {
+ my ($flags, $new_flags) =
+ Bugzilla::Flag->extract_flags_from_cgi($bug, $attachment, $vars);
+ $attachment->set_flags($flags, $new_flags);
+ }
- # Start a transaction in preparation for updating the attachment.
- $dbh->bz_start_transaction();
+ # Figure out when the changes were made.
+ my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- # Quote the description and content type for use in the SQL UPDATE statement.
- my $description = $cgi->param('description');
- my $contenttype = $cgi->param('contenttype');
- my $filename = $cgi->param('filename');
- # we can detaint this way thanks to placeholders
- trick_taint($description);
- trick_taint($contenttype);
- trick_taint($filename);
+ if ($can_edit) {
+ my $changes = $attachment->update($timestamp);
+ # If there are changes, we updated delta_ts in the DB. We have to
+ # reflect this change in the bug object.
+ $bug->{delta_ts} = $timestamp if scalar(keys %$changes);
+ }
- # Figure out when the changes were made.
- my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
-
- # Update flags. We have to do this before committing changes
- # to attachments so that we can delete pending requests if the user
- # is obsoleting this attachment without deleting any requests
- # the user submits at the same time.
- Bugzilla::Flag->process($bug, $attachment, $timestamp, $vars);
+ # Commit the comment, if any.
+ $bug->update($timestamp);
- # Update the attachment record in the database.
- $dbh->do("UPDATE attachments
- SET description = ?,
- mimetype = ?,
- filename = ?,
- ispatch = ?,
- isobsolete = ?,
- isprivate = ?,
- modification_time = ?
- WHERE attach_id = ?",
- undef, ($description, $contenttype, $filename,
- $cgi->param('ispatch'), $cgi->param('isobsolete'),
- $cgi->param('isprivate'), $timestamp, $attachment->id));
+ # Commit the transaction now that we are finished updating the database.
+ $dbh->bz_commit_transaction();
- my $updated_attachment = Bugzilla::Attachment->get($attachment->id);
- # 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 (?, ?, ?, ?, ?, ?, ?)');
+ # Define the variables and functions that will be passed to the UI template.
+ $vars->{'attachment'} = $attachment;
+ $vars->{'bugs'} = [$bug];
+ $vars->{'header_done'} = 1;
+ $vars->{'sent_bugmail'} =
+ Bugzilla::BugMail::Send($bug->id, { 'changer' => $user });
- if ($attachment->description ne $updated_attachment->description) {
- my $fieldid = get_field_id('attachments.description');
- $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
- $attachment->description, $updated_attachment->description);
- }
- if ($attachment->contenttype ne $updated_attachment->contenttype) {
- my $fieldid = get_field_id('attachments.mimetype');
- $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
- $attachment->contenttype, $updated_attachment->contenttype);
- }
- if ($attachment->filename ne $updated_attachment->filename) {
- my $fieldid = get_field_id('attachments.filename');
- $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
- $attachment->filename, $updated_attachment->filename);
- }
- if ($attachment->ispatch != $updated_attachment->ispatch) {
- my $fieldid = get_field_id('attachments.ispatch');
- $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
- $attachment->ispatch, $updated_attachment->ispatch);
- }
- if ($attachment->isobsolete != $updated_attachment->isobsolete) {
- my $fieldid = get_field_id('attachments.isobsolete');
- $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
- $attachment->isobsolete, $updated_attachment->isobsolete);
- }
- if ($attachment->isprivate != $updated_attachment->isprivate) {
- my $fieldid = get_field_id('attachments.isprivate');
- $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
- $attachment->isprivate, $updated_attachment->isprivate);
- }
-
- # Commit the transaction now that we are finished updating the database.
- $dbh->bz_commit_transaction();
+ print $cgi->header();
- # Commit the comment, if any.
- $bug->update();
-
- # Define the variables and functions that will be passed to the UI template.
- $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
- $vars->{'attachment'} = $attachment;
- # We cannot reuse the $bug object as delta_ts has eventually been updated
- # since the object was created.
- $vars->{'bugs'} = [new Bugzilla::Bug($bug->id)];
- $vars->{'header_done'} = 1;
- $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
-
- print $cgi->header();
-
- # Generate and return the UI (HTML page) from the appropriate template.
- $template->process("attachment/updated.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("attachment/updated.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
}
# Only administrators can delete attachments.
@@ -821,7 +825,7 @@
# Make sure the administrator is allowed to edit this attachment.
my $attachment = validateID();
- validateCanChangeBug($attachment->bug_id);
+ Bugzilla::Attachment->_check_bug($attachment->bug);
$attachment->datasize || ThrowUserError('attachment_removed');
@@ -831,7 +835,7 @@
my ($creator_id, $date, $event) = Bugzilla::Token::GetTokenData($token);
unless ($creator_id
&& ($creator_id == $user->id)
- && ($event eq 'attachment' . $attachment->id))
+ && ($event eq 'delete_attachment' . $attachment->id))
{
# The token is invalid.
ThrowUserError('token_does_not_exist');
@@ -844,7 +848,6 @@
$vars->{'attachment'} = $attachment;
$vars->{'date'} = $date;
$vars->{'reason'} = clean_text($cgi->param('reason') || '');
- $vars->{'mailrecipients'} = { 'changer' => $user->login };
$template->process("attachment/delete_reason.txt.tmpl", $vars, \$msg)
|| ThrowTemplateError($template->error());
@@ -867,14 +870,16 @@
# Required to display the bug the deleted attachment belongs to.
$vars->{'bugs'} = [$bug];
$vars->{'header_done'} = 1;
- $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
+
+ $vars->{'sent_bugmail'} =
+ Bugzilla::BugMail::Send($bug->id, { 'changer' => $user });
$template->process("attachment/updated.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
else {
# Create a token.
- $token = issue_session_token('attachment' . $attachment->id);
+ $token = issue_session_token('delete_attachment' . $attachment->id);
$vars->{'a'} = $attachment;
$vars->{'token'} = $token;
diff --git a/Websites/bugs.webkit.org/buglist.cgi b/Websites/bugs.webkit.org/buglist.cgi
index e8b40fc..0cf4215 100755
--- a/Websites/bugs.webkit.org/buglist.cgi
+++ b/Websites/bugs.webkit.org/buglist.cgi
@@ -40,6 +40,7 @@
use Bugzilla::Util;
use Bugzilla::Search;
use Bugzilla::Search::Quicksearch;
+use Bugzilla::Search::Recent;
use Bugzilla::Search::Saved;
use Bugzilla::User;
use Bugzilla::Bug;
@@ -60,13 +61,15 @@
# We have to check the login here to get the correct footer if an error is
# thrown and to prevent a logged out user to use QuickSearch if 'requirelogin'
# is turned 'on'.
-Bugzilla->login();
+my $user = Bugzilla->login();
if (length($buffer) == 0) {
print $cgi->header(-refresh=> '10; URL=query.cgi');
ThrowUserError("buglist_parameters_required");
}
+$cgi->redirect_search_url();
+
# Determine whether this is a quicksearch query.
my $searchstring = $cgi->param('quicksearch');
if (defined($searchstring)) {
@@ -79,7 +82,7 @@
# If configured to not allow empty words, reject empty searches from the
# Find a Specific Bug search form, including words being a single or
# several consecutive whitespaces only.
-if (!Bugzilla->params->{'specific_search_allow_empty_words'}
+if (!Bugzilla->params->{'search_allow_no_criteria'}
&& defined($cgi->param('content')) && $cgi->param('content') =~ /^\s*$/)
{
ThrowUserError("buglist_parameters_required");
@@ -109,16 +112,6 @@
$cgi->param('ctype', "atom");
}
-# The js ctype presents a security risk; a malicious site could use it
-# to gather information about secure bugs. So, we only allow public bugs to be
-# retrieved with this format.
-#
-# Note that if and when this call clears cookies or has other persistent
-# effects, we'll need to do this another way instead.
-if ((defined $cgi->param('ctype')) && ($cgi->param('ctype') eq "js")) {
- Bugzilla->logout_request();
-}
-
# An agent is a program that automatically downloads and extracts data
# on its user's behalf. If this request comes from an agent, we turn off
# various aspects of bug list functionality so agent requests succeed
@@ -154,35 +147,32 @@
|| $cgi->param('serverpush');
my $order = $cgi->param('order') || "";
-my $order_from_cookie = 0; # True if $order set using the LASTORDER cookie
# The params object to use for the actual query itself
my $params;
# If the user is retrieving the last bug list they looked at, hack the buffer
# storing the query string so that it looks like a query retrieving those bugs.
-if (defined $cgi->param('regetlastlist')) {
- $cgi->cookie('BUGLIST') || ThrowUserError("missing_cookie");
+if (my $last_list = $cgi->param('regetlastlist')) {
+ my $bug_ids;
- $order = "reuse last sort" unless $order;
- my $bug_id = $cgi->cookie('BUGLIST');
- $bug_id =~ s/:/,/g;
+ # Logged-out users use the old cookie method for storing the last search.
+ if (!$user->id or $last_list eq 'cookie') {
+ $bug_ids = $cgi->cookie('BUGLIST') or ThrowUserError("missing_cookie");
+ $bug_ids =~ s/[:-]/,/g;
+ $order ||= "reuse last sort";
+ }
+ # But logged in users store the last X searches in the DB so they can
+ # have multiple bug lists available.
+ else {
+ my $last_search = Bugzilla::Search::Recent->check(
+ { id => $last_list });
+ $bug_ids = join(',', @{ $last_search->bug_list });
+ $order ||= $last_search->list_order;
+ }
# set up the params for this new query
- $params = new Bugzilla::CGI({
- bug_id => $bug_id,
- order => $order,
- });
-}
-
-if ($buffer =~ /&cmd-/) {
- my $url = "query.cgi?$buffer#chart";
- print $cgi->redirect(-location => $url);
- # Generate and return the UI (HTML page) from the appropriate template.
- $vars->{'message'} = "buglist_adding_field";
- $vars->{'url'} = $url;
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $params = new Bugzilla::CGI({ bug_id => $bug_ids, order => $order });
+ $params->param('list_id', $last_list);
}
# Figure out whether or not the user is doing a fulltext search. If not,
@@ -202,82 +192,35 @@
# Utilities
################################################################################
-local our @weekday= qw( Sun Mon Tue Wed Thu Fri Sat );
sub DiffDate {
my ($datestr) = @_;
my $date = str2time($datestr);
my $age = time() - $date;
- my ($s,$m,$h,$d,$mo,$y,$wd)= localtime $date;
+
if( $age < 18*60*60 ) {
- $date = sprintf "%02d:%02d:%02d", $h,$m,$s;
+ $date = format_time($datestr, '%H:%M:%S');
} elsif( $age < 6*24*60*60 ) {
- $date = sprintf "%s %02d:%02d", $weekday[$wd],$h,$m;
+ $date = format_time($datestr, '%a %H:%M');
} else {
- $date = sprintf "%04d-%02d-%02d", 1900+$y,$mo+1,$d;
+ $date = format_time($datestr, '%Y-%m-%d');
}
return $date;
}
sub LookupNamedQuery {
- my ($name, $sharer_id, $query_type, $throw_error) = @_;
- my $user = Bugzilla->login(LOGIN_REQUIRED);
- my $dbh = Bugzilla->dbh;
- my $owner_id;
- $throw_error = 1 unless defined $throw_error;
+ my ($name, $sharer_id) = @_;
- # $name and $sharer_id are safe -- we only use them below in SELECT
- # placeholders and then in error messages (which are always HTML-filtered).
- $name || ThrowUserError("query_name_missing");
- trick_taint($name);
- if ($sharer_id) {
- $owner_id = $sharer_id;
- detaint_natural($owner_id);
- $owner_id || ThrowUserError('illegal_user_id', {'userid' => $sharer_id});
- }
- else {
- $owner_id = $user->id;
- }
+ Bugzilla->login(LOGIN_REQUIRED);
- my @args = ($owner_id, $name);
- my $extra = '';
- # If $query_type is defined, then we restrict our search.
- if (defined $query_type) {
- $extra = ' AND query_type = ? ';
- detaint_natural($query_type);
- push(@args, $query_type);
- }
- my ($id, $result) = $dbh->selectrow_array("SELECT id, query
- FROM namedqueries
- WHERE userid = ? AND name = ?
- $extra",
- undef, @args);
+ my $query = Bugzilla::Search::Saved->check(
+ { user => $sharer_id, name => $name });
- # Some DBs (read: Oracle) incorrectly mark this string as UTF-8
- # even though it has no UTF-8 characters in it, which prevents
- # Bugzilla::CGI from later reading it correctly.
- utf8::downgrade($result) if utf8::is_utf8($result);
+ $query->url
+ || ThrowUserError("buglist_parameters_required");
- if (!defined($result)) {
- return 0 unless $throw_error;
- ThrowUserError("missing_query", {'queryname' => $name,
- 'sharer_id' => $sharer_id});
- }
-
- if ($sharer_id) {
- my $group = $dbh->selectrow_array('SELECT group_id
- FROM namedquery_group_map
- WHERE namedquery_id = ?',
- undef, $id);
- if (!grep {$_ == $group} values(%{$user->groups()})) {
- ThrowUserError("missing_query", {'queryname' => $name,
- 'sharer_id' => $sharer_id});
- }
- }
-
- $result
- || ThrowUserError("buglist_parameters_required", {'queryname' => $name});
-
- return wantarray ? ($result, $id) : $result;
+ # Detaint $sharer_id.
+ $sharer_id = $query->user->id if $sharer_id;
+ return wantarray ? ($query->url, $query->id, $sharer_id) : $query->url;
}
# Inserts a Named Query (a "Saved Search") into the database, or
@@ -293,15 +236,13 @@
# empty, or we will throw a UserError.
# link_in_footer (optional) - 1 if the Named Query should be
# displayed in the user's footer, 0 otherwise.
-# query_type (optional) - 1 if the Named Query contains a list of
-# bug IDs only, 0 otherwise (default).
#
# All parameters are validated before passing them into the database.
#
# Returns: A boolean true value if the query existed in the database
# before, and we updated it. A boolean false value otherwise.
sub InsertNamedQuery {
- my ($query_name, $query, $link_in_footer, $query_type) = @_;
+ my ($query_name, $query, $link_in_footer) = @_;
my $dbh = Bugzilla->dbh;
$query_name = trim($query_name);
@@ -310,13 +251,11 @@
if ($query_obj) {
$query_obj->set_name($query_name);
$query_obj->set_url($query);
- $query_obj->set_query_type($query_type);
$query_obj->update();
} else {
Bugzilla::Search::Saved->create({
name => $query_name,
query => $query,
- query_type => $query_type,
link_in_footer => $link_in_footer
});
}
@@ -398,14 +337,15 @@
# Command Execution
################################################################################
-$cgi->param('cmdtype', "") if !defined $cgi->param('cmdtype');
-$cgi->param('remaction', "") if !defined $cgi->param('remaction');
+my $cmdtype = $cgi->param('cmdtype') || '';
+my $remaction = $cgi->param('remaction') || '';
+my $sharer_id;
# Backwards-compatibility - the old interface had cmdtype="runnamed" to run
# a named command, and we can't break this because it's in bookmarks.
-if ($cgi->param('cmdtype') eq "runnamed") {
- $cgi->param('cmdtype', "dorem");
- $cgi->param('remaction', "run");
+if ($cmdtype eq "runnamed") {
+ $cmdtype = "dorem";
+ $remaction = "run";
}
# Now we're going to be running, so ensure that the params object is set up,
@@ -423,7 +363,7 @@
my @time = localtime(time());
my $date = sprintf "%04d-%02d-%02d", 1900+$time[5],$time[4]+1,$time[3];
my $filename = "bugs-$date.$format->{extension}";
-if ($cgi->param('cmdtype') eq "dorem" && $cgi->param('remaction') =~ /^run/) {
+if ($cmdtype eq "dorem" && $remaction =~ /^run/) {
$filename = $cgi->param('namedcmd') . "-$date.$format->{extension}";
# Remove white-space from the filename so the user cannot tamper
# with the HTTP headers.
@@ -433,11 +373,12 @@
$filename =~ s/"/\\"/g; # escape quotes
# Take appropriate action based on user's request.
-if ($cgi->param('cmdtype') eq "dorem") {
- if ($cgi->param('remaction') eq "run") {
+if ($cmdtype eq "dorem") {
+ if ($remaction eq "run") {
my $query_id;
- ($buffer, $query_id) = LookupNamedQuery(scalar $cgi->param("namedcmd"),
- scalar $cgi->param('sharer_id'));
+ ($buffer, $query_id, $sharer_id) =
+ LookupNamedQuery(scalar $cgi->param("namedcmd"),
+ scalar $cgi->param('sharer_id'));
# If this is the user's own query, remember information about it
# so that it can be modified easily.
$vars->{'searchname'} = $cgi->param('namedcmd');
@@ -450,15 +391,15 @@
$order = $params->param('order') || $order;
}
- elsif ($cgi->param('remaction') eq "runseries") {
+ elsif ($remaction eq "runseries") {
$buffer = LookupSeries(scalar $cgi->param("series_id"));
$vars->{'searchname'} = $cgi->param('namedcmd');
$vars->{'searchtype'} = "series";
$params = new Bugzilla::CGI($buffer);
$order = $params->param('order') || $order;
}
- elsif ($cgi->param('remaction') eq "forget") {
- my $user = Bugzilla->login(LOGIN_REQUIRED);
+ elsif ($remaction eq "forget") {
+ $user = Bugzilla->login(LOGIN_REQUIRED);
# Copy the name into a variable, so that we can trick_taint it for
# the DB. We know it's safe, because we're using placeholders in
# the SQL, and the SQL is only a DELETE.
@@ -485,14 +426,10 @@
}
# If we are here, then we can safely remove the saved search
- my ($query_id) = $dbh->selectrow_array('SELECT id FROM namedqueries
- WHERE userid = ?
- AND name = ?',
- undef, ($user->id, $qname));
- if (!$query_id) {
- # The user has no query of this name. Play along.
- }
- else {
+ my $query_id;
+ ($buffer, $query_id) = LookupNamedQuery(scalar $cgi->param("namedcmd"),
+ $user->id);
+ if ($query_id) {
# Make sure the user really wants to delete his saved search.
my $token = $cgi->param('token');
check_hash_token($token, [$query_id, $qname]);
@@ -515,103 +452,70 @@
# Generate and return the UI (HTML page) from the appropriate template.
$vars->{'message'} = "buglist_query_gone";
$vars->{'namedcmd'} = $qname;
- $vars->{'url'} = "query.cgi";
+ $vars->{'url'} = "buglist.cgi?newquery=" . url_quote($buffer) . "&cmdtype=doit&remtype=asnamed&newqueryname=" . url_quote($qname);
$template->process("global/message.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
}
-elsif (($cgi->param('cmdtype') eq "doit") && defined $cgi->param('remtype')) {
+elsif (($cmdtype eq "doit") && defined $cgi->param('remtype')) {
if ($cgi->param('remtype') eq "asdefault") {
- my $user = Bugzilla->login(LOGIN_REQUIRED);
+ $user = Bugzilla->login(LOGIN_REQUIRED);
InsertNamedQuery(DEFAULT_QUERY_NAME, $buffer);
$vars->{'message'} = "buglist_new_default_query";
}
elsif ($cgi->param('remtype') eq "asnamed") {
- my $user = Bugzilla->login(LOGIN_REQUIRED);
+ $user = Bugzilla->login(LOGIN_REQUIRED);
my $query_name = $cgi->param('newqueryname');
my $new_query = $cgi->param('newquery');
- my $query_type = QUERY_LIST;
- # If list_of_bugs is true, we are adding/removing individual bugs
- # to a saved search. We get the existing list of bug IDs (if any)
- # and add/remove the passed ones.
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['savedsearch']);
+ # If list_of_bugs is true, we are adding/removing tags to/from
+ # individual bugs.
if ($cgi->param('list_of_bugs')) {
- # We add or remove bugs based on the action choosen.
+ # We add/remove tags based on the action choosen.
my $action = trim($cgi->param('action') || '');
$action =~ /^(add|remove)$/
- || ThrowCodeError('unknown_action', {'action' => $action});
+ || ThrowUserError('unknown_action', {action => $action});
- # If we are removing bugs, then we must have an existing
- # saved search selected.
- if ($action eq 'remove') {
- $query_name && ThrowUserError('no_bugs_to_remove');
- }
+ my $method = "${action}_tag";
- my %bug_ids;
- my $is_new_name = 0;
- if ($query_name) {
- my ($query, $query_id) =
- LookupNamedQuery($query_name, undef, QUERY_LIST, !THROW_ERROR);
- # Make sure this name is not already in use by a normal saved search.
- if ($query) {
- ThrowUserError('query_name_exists', {name => $query_name,
- query_id => $query_id});
- }
- $is_new_name = 1;
- }
# If no new tag name has been given, use the selected one.
- $query_name ||= $cgi->param('oldqueryname');
+ $query_name ||= $cgi->param('oldqueryname')
+ or ThrowUserError('no_tag_to_edit', {action => $action});
- # Don't throw an error if it's a new tag name: if the tag already
- # exists, add/remove bugs to it, else create it. But if we are
- # considering an existing tag, then it has to exist and we throw
- # an error if it doesn't (hence the usage of !$is_new_name).
- if (my $old_query = LookupNamedQuery($query_name, undef, LIST_OF_BUGS, !$is_new_name)) {
- # We get the encoded query. We need to decode it.
- my $old_cgi = new Bugzilla::CGI($old_query);
- foreach my $bug_id (split /[\s,]+/, scalar $old_cgi->param('bug_id')) {
- $bug_ids{$bug_id} = 1 if detaint_natural($bug_id);
- }
- }
-
- my $keep_bug = ($action eq 'add') ? 1 : 0;
- my $changes = 0;
+ my @buglist;
+ # Validate all bug IDs before editing tags in any of them.
foreach my $bug_id (split(/[\s,]+/, $cgi->param('bug_ids'))) {
next unless $bug_id;
- ValidateBugID($bug_id);
- $bug_ids{$bug_id} = $keep_bug;
- $changes = 1;
+ push(@buglist, Bugzilla::Bug->check($bug_id));
}
- ThrowUserError('no_bug_ids',
- {'action' => $action,
- 'tag' => $query_name})
- unless $changes;
- # Only keep bug IDs we want to add/keep. Disregard deleted ones.
- my @bug_ids = grep { $bug_ids{$_} == 1 } keys %bug_ids;
- # If the list is now empty, we could as well delete it completely.
- ThrowUserError('no_bugs_in_list', {'tag' => $query_name})
- unless scalar(@bug_ids);
+ foreach my $bug (@buglist) {
+ $bug->$method($query_name);
+ }
- $new_query = "bug_id=" . join(',', sort {$a <=> $b} @bug_ids);
- $query_type = LIST_OF_BUGS;
- }
- my $tofooter = 1;
- my $existed_before = InsertNamedQuery($query_name, $new_query,
- $tofooter, $query_type);
- if ($existed_before) {
- $vars->{'message'} = "buglist_updated_named_query";
+ $vars->{'message'} = 'tag_updated';
+ $vars->{'action'} = $action;
+ $vars->{'tag'} = $query_name;
+ $vars->{'buglist'} = [map { $_->id } @buglist];
}
else {
- $vars->{'message'} = "buglist_new_named_query";
+ my $existed_before = InsertNamedQuery($query_name, $new_query, 1);
+ if ($existed_before) {
+ $vars->{'message'} = "buglist_updated_named_query";
+ }
+ else {
+ $vars->{'message'} = "buglist_new_named_query";
+ }
+
+ # Make sure to invalidate any cached query data, so that the footer is
+ # correctly displayed
+ $user->flush_queries_cache();
+
+ $vars->{'queryname'} = $query_name;
}
- # Make sure to invalidate any cached query data, so that the footer is
- # correctly displayed
- $user->flush_queries_cache();
-
- $vars->{'queryname'} = $query_name;
-
print $cgi->header();
$template->process("global/message.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
@@ -631,83 +535,7 @@
# Column Definition
################################################################################
-# Define the columns that can be selected in a query and/or displayed in a bug
-# list. Column records include the following fields:
-#
-# 1. ID: a unique identifier by which the column is referred in code;
-#
-# 2. Name: The name of the column in the database (may also be an expression
-# that returns the value of the column);
-#
-# 3. Title: The title of the column as displayed to users.
-#
-# Note: There are a few hacks in the code that deviate from these definitions.
-# In particular, when the list is sorted by the "votes" field the word
-# "DESC" is added to the end of the field to sort in descending order,
-# and the redundant short_desc column is removed when the client
-# requests "all" columns.
-# Note: For column names using aliasing (SQL "<field> AS <alias>"), the column
-# ID needs to be identical to the field ID for list ordering to work.
-
-local our $columns = {};
-sub DefineColumn {
- my ($id, $name, $title) = @_;
- $columns->{$id} = { 'name' => $name , 'title' => $title };
-}
-
-# Column: ID Name Title
-DefineColumn("bug_id" , "bugs.bug_id" , "ID" );
-DefineColumn("alias" , "bugs.alias" , "Alias" );
-DefineColumn("opendate" , "bugs.creation_ts" , "Opened" );
-DefineColumn("changeddate" , "bugs.delta_ts" , "Changed" );
-DefineColumn("bug_severity" , "bugs.bug_severity" , "Severity" );
-DefineColumn("priority" , "bugs.priority" , "Priority" );
-DefineColumn("rep_platform" , "bugs.rep_platform" , "Hardware" );
-DefineColumn("assigned_to" , "map_assigned_to.login_name" , "Assignee" );
-DefineColumn("reporter" , "map_reporter.login_name" , "Reporter" );
-DefineColumn("qa_contact" , "map_qa_contact.login_name" , "QA Contact" );
-if ($format->{'extension'} eq 'html') {
- DefineColumn("assigned_to_realname", "CASE WHEN map_assigned_to.realname = '' THEN map_assigned_to.login_name ELSE map_assigned_to.realname END AS assigned_to_realname", "Assignee" );
- DefineColumn("reporter_realname" , "CASE WHEN map_reporter.realname = '' THEN map_reporter.login_name ELSE map_reporter.realname END AS reporter_realname" , "Reporter" );
- DefineColumn("qa_contact_realname" , "CASE WHEN map_qa_contact.realname = '' THEN map_qa_contact.login_name ELSE map_qa_contact.realname END AS qa_contact_realname" , "QA Contact");
-} else {
- DefineColumn("assigned_to_realname", "map_assigned_to.realname AS assigned_to_realname", "Assignee" );
- DefineColumn("reporter_realname" , "map_reporter.realname AS reporter_realname" , "Reporter" );
- DefineColumn("qa_contact_realname" , "map_qa_contact.realname AS qa_contact_realname" , "QA Contact");
-}
-DefineColumn("bug_status" , "bugs.bug_status" , "Status" );
-DefineColumn("resolution" , "bugs.resolution" , "Resolution" );
-DefineColumn("short_short_desc" , "bugs.short_desc" , "Summary" );
-DefineColumn("short_desc" , "bugs.short_desc" , "Summary" );
-DefineColumn("status_whiteboard" , "bugs.status_whiteboard" , "Whiteboard" );
-DefineColumn("component" , "map_components.name" , "Component" );
-DefineColumn("product" , "map_products.name" , "Product" );
-DefineColumn("classification" , "map_classifications.name" , "Classification" );
-DefineColumn("version" , "bugs.version" , "Version" );
-DefineColumn("op_sys" , "bugs.op_sys" , "OS" );
-DefineColumn("target_milestone" , "bugs.target_milestone" , "Target Milestone" );
-DefineColumn("votes" , "bugs.votes" , "Votes" );
-DefineColumn("keywords" , "bugs.keywords" , "Keywords" );
-DefineColumn("estimated_time" , "bugs.estimated_time" , "Estimated Hours" );
-DefineColumn("remaining_time" , "bugs.remaining_time" , "Remaining Hours" );
-DefineColumn("actual_time" , "(SUM(ldtime.work_time)*COUNT(DISTINCT ldtime.bug_when)/COUNT(bugs.bug_id)) AS actual_time", "Actual Hours");
-DefineColumn("percentage_complete",
- "(CASE WHEN (SUM(ldtime.work_time)*COUNT(DISTINCT ldtime.bug_when)/COUNT(bugs.bug_id)) " .
- " + bugs.remaining_time = 0.0 " .
- "THEN 0.0 " .
- "ELSE 100*((SUM(ldtime.work_time)*COUNT(DISTINCT ldtime.bug_when)/COUNT(bugs.bug_id)) " .
- " /((SUM(ldtime.work_time)*COUNT(DISTINCT ldtime.bug_when)/COUNT(bugs.bug_id)) + bugs.remaining_time)) " .
- "END) AS percentage_complete" , "% Complete");
-DefineColumn("relevance" , "relevance" , "Relevance" );
-DefineColumn("deadline" , $dbh->sql_date_format('bugs.deadline', '%Y-%m-%d') . " AS deadline", "Deadline");
-
-foreach my $field (Bugzilla->active_custom_fields) {
- # Multi-select fields are not (yet) supported in buglists.
- next if $field->type == FIELD_TYPE_MULTI_SELECT;
- DefineColumn($field->name, 'bugs.' . $field->name, $field->description);
-}
-
-Bugzilla::Hook::process("buglist-columns", {'columns' => $columns} );
+my $columns = Bugzilla::Search::COLUMNS;
################################################################################
# Display Column Determination
@@ -754,21 +582,9 @@
# and are hard-coded into the display templates.
@displaycolumns = grep($_ ne 'bug_id', @displaycolumns);
-# Add the votes column to the list of columns to be displayed
-# in the bug list if the user is searching for bugs with a certain
-# number of votes and the votes column is not already on the list.
-
-# Some versions of perl will taint 'votes' if this is done as a single
-# statement, because the votes param is tainted at this point
-my $votes = $params->param('votes');
-$votes ||= "";
-if (trim($votes) && !grep($_ eq 'votes', @displaycolumns)) {
- push(@displaycolumns, 'votes');
-}
-
# Remove the timetracking columns if they are not a part of the group
# (happens if a user had access to time tracking and it was revoked/disabled)
-if (!Bugzilla->user->in_group(Bugzilla->params->{"timetrackinggroup"})) {
+if (!Bugzilla->user->is_timetracker) {
@displaycolumns = grep($_ ne 'estimated_time', @displaycolumns);
@displaycolumns = grep($_ ne 'remaining_time', @displaycolumns);
@displaycolumns = grep($_ ne 'actual_time', @displaycolumns);
@@ -792,27 +608,35 @@
# Severity, priority, resolution and status are required for buglist
# CSS classes.
my @selectcolumns = ("bug_id", "bug_severity", "priority", "bug_status",
- "resolution");
-
-# if using classification, we also need to look in product.classification_id
-if (Bugzilla->params->{"useclassification"}) {
- push (@selectcolumns,"product");
-}
+ "resolution", "product");
# remaining and actual_time are required for percentage_complete calculation:
-if (lsearch(\@displaycolumns, "percentage_complete") >= 0) {
+if (grep { $_ eq "percentage_complete" } @displaycolumns) {
push (@selectcolumns, "remaining_time");
push (@selectcolumns, "actual_time");
}
-# Display columns are selected because otherwise we could not display them.
-push (@selectcolumns, @displaycolumns);
+# Make sure that the login_name version of a field is always also
+# requested if the realname version is requested, so that we can
+# display the login name when the realname is empty.
+my @realname_fields = grep(/_realname$/, @displaycolumns);
+foreach my $item (@realname_fields) {
+ my $login_field = $item;
+ $login_field =~ s/_realname$//;
+ if (!grep($_ eq $login_field, @selectcolumns)) {
+ push(@selectcolumns, $login_field);
+ }
+}
-# If the user is editing multiple bugs, we also make sure to select the product
-# and status because the values of those fields determine what options the user
+# Display columns are selected because otherwise we could not display them.
+foreach my $col (@displaycolumns) {
+ push (@selectcolumns, $col) if !grep($_ eq $col, @selectcolumns);
+}
+
+# If the user is editing multiple bugs, we also make sure to select the
+# status, because the values of that field determines what options the user
# has for modifying the bugs.
if ($dotweak) {
- push(@selectcolumns, "product") if !grep($_ eq 'product', @selectcolumns);
push(@selectcolumns, "bug_status") if !grep($_ eq 'bug_status', @selectcolumns);
}
@@ -829,9 +653,11 @@
'short_desc',
'opendate',
'changeddate',
+ 'reporter',
'reporter_realname',
'priority',
'bug_severity',
+ 'assigned_to',
'assigned_to_realname',
'bug_status',
'product',
@@ -846,17 +672,6 @@
}
################################################################################
-# Query Generation
-################################################################################
-
-# Convert the list of columns being selected into a list of column names.
-my @selectnames = map($columns->{$_}->{'name'}, @selectcolumns);
-
-# Remove columns with no names, such as percentage_complete
-# (or a removed *_time column due to permissions)
-@selectnames = grep($_ ne '', @selectnames);
-
-################################################################################
# Sort Order Determination
################################################################################
@@ -871,59 +686,58 @@
# Cookies from early versions of Specific Search included this text,
# which is now invalid.
$order =~ s/ LIMIT 200//;
-
- $order_from_cookie = 1;
}
else {
$order = ''; # Remove possible "reuse" identifier as unnecessary
}
}
-my $db_order = ""; # Modified version of $order for use with SQL query
if ($order) {
# Convert the value of the "order" form field into a list of columns
# by which to sort the results.
ORDER: for ($order) {
/^Bug Number$/ && do {
- $order = "bugs.bug_id";
+ $order = "bug_id";
last ORDER;
};
/^Importance$/ && do {
- $order = "bugs.priority, bugs.bug_severity";
+ $order = "priority,bug_severity";
last ORDER;
};
/^Assignee$/ && do {
- $order = "map_assigned_to.login_name, bugs.bug_status, bugs.priority, bugs.bug_id";
+ $order = "assigned_to,bug_status,priority,bug_id";
last ORDER;
};
/^Last Changed$/ && do {
- $order = "bugs.delta_ts, bugs.bug_status, bugs.priority, map_assigned_to.login_name, bugs.bug_id";
+ $order = "changeddate,bug_status,priority,assigned_to,bug_id";
last ORDER;
};
do {
- my @order;
- my @columnnames = map($columns->{lc($_)}->{'name'}, keys(%$columns));
+ my (@order, @invalid_fragments);
+
# A custom list of columns. Make sure each column is valid.
foreach my $fragment (split(/,/, $order)) {
$fragment = trim($fragment);
next unless $fragment;
- # Accept an order fragment matching a column name, with
- # asc|desc optionally following (to specify the direction)
- if (grep($fragment =~ /^\Q$_\E(\s+(asc|desc))?$/, @columnnames, keys(%$columns))) {
- next if $fragment =~ /\brelevance\b/ && !$fulltext;
- push(@order, $fragment);
+ my ($column_name, $direction) = split_order_term($fragment);
+ $column_name = translate_old_column($column_name);
+
+ # Special handlings for certain columns
+ next if $column_name eq 'relevance' && !$fulltext;
+
+ if (exists $columns->{$column_name}) {
+ $direction = " $direction" if $direction;
+ push(@order, "$column_name$direction");
}
else {
- my $vars = { fragment => $fragment };
- if ($order_from_cookie) {
- $cgi->remove_cookie('LASTORDER');
- ThrowCodeError("invalid_column_name_cookie", $vars);
- }
- else {
- ThrowCodeError("invalid_column_name_form", $vars);
- }
+ push(@invalid_fragments, $fragment);
}
}
+ if (scalar @invalid_fragments) {
+ $vars->{'message'} = 'invalid_column_name';
+ $vars->{'invalid_fragments'} = \@invalid_fragments;
+ }
+
$order = join(",", @order);
# Now that we have checked that all columns in the order are valid,
# detaint the order string.
@@ -934,73 +748,34 @@
if (!$order) {
# DEFAULT
- $order = "bugs.bug_status, bugs.priority, map_assigned_to.login_name, bugs.bug_id";
+ $order = "bug_status,priority,assigned_to,bug_id";
}
-# Make sure ORDER BY columns are included in the field list.
-foreach my $fragment (split(/,/, $order)) {
- $fragment = trim($fragment);
- if (!grep($fragment =~ /^\Q$_\E(\s+(asc|desc))?$/, @selectnames)) {
- # Add order columns to selectnames
- # The fragment has already been validated
- $fragment =~ s/\s+(asc|desc)$//;
+my @orderstrings = split(/,\s*/, $order);
- # While newer fragments contain IDs for aliased columns, older
- # LASTORDER cookies (or bookmarks) may contain full names.
- # Convert them to an ID here.
- if ($fragment =~ / AS (\w+)/) {
- $fragment = $1;
- }
-
- $fragment =~ tr/a-zA-Z\.0-9\-_//cd;
-
- # If the order fragment is an ID, we need its corresponding name
- # to be in the field list.
- if (exists($columns->{$fragment})) {
- $fragment = $columns->{$fragment}->{'name'};
- }
-
- push @selectnames, $fragment;
- }
+if ($fulltext and grep { /^relevance/ } @orderstrings) {
+ $vars->{'message'} = 'buglist_sorted_by_relevance'
}
-$db_order = $order; # Copy $order into $db_order for use with SQL query
-
-# If we are sorting by votes, sort in descending order if no explicit
-# sort order was given
-$db_order =~ s/bugs.votes\s*(,|$)/bugs.votes desc$1/i;
-
-# the 'actual_time' field is defined as an aggregate function, but
-# for order we just need the column name 'actual_time'
-my $aggregate_search = quotemeta($columns->{'actual_time'}->{'name'});
-$db_order =~ s/$aggregate_search/actual_time/g;
-
-# the 'percentage_complete' field is defined as an aggregate too
-$aggregate_search = quotemeta($columns->{'percentage_complete'}->{'name'});
-$db_order =~ s/$aggregate_search/percentage_complete/g;
-
-# Now put $db_order into a format that Bugzilla::Search can use.
-# (We create $db_order as a string first because that's the way
-# we did it before Bugzilla::Search took an "order" argument.)
-my @orderstrings = split(/,\s*/, $db_order);
+# In the HTML interface, by default, we limit the returned results,
+# which speeds up quite a few searches where people are really only looking
+# for the top results.
+if ($format->{'extension'} eq 'html' && !defined $params->param('limit')) {
+ $params->param('limit', Bugzilla->params->{'default_search_limit'});
+ $vars->{'default_limited'} = 1;
+}
# Generate the basic SQL query that will be used to generate the bug list.
-my $search = new Bugzilla::Search('fields' => \@selectnames,
- 'params' => $params,
- 'order' => \@orderstrings);
-my $query = $search->getSQL();
+my $search = new Bugzilla::Search('fields' => \@selectcolumns,
+ 'params' => scalar $params->Vars,
+ 'order' => \@orderstrings,
+ 'sharer' => $sharer_id);
+my $query = $search->sql;
+$vars->{'search_description'} = $search->search_description;
-if (defined $cgi->param('limit')) {
- my $limit = $cgi->param('limit');
- if (detaint_natural($limit)) {
- $query .= " " . $dbh->sql_limit($limit);
- }
-}
-elsif ($fulltext) {
- $query .= " " . $dbh->sql_limit(FULLTEXT_BUGLIST_LIMIT);
- $vars->{'message'} = 'buglist_sorted_by_relevance' if ($cgi->param('order') =~ /^relevance/);
-}
-
+# We don't want saved searches and other buglist things to save
+# our default limit.
+$params->delete('limit') if $vars->{'default_limited'};
################################################################################
# Query Execution
@@ -1012,7 +787,13 @@
) {
$vars->{'debug'} = 1;
$vars->{'query'} = $query;
- $vars->{'debugdata'} = $search->getDebugData();
+ # Explains are limited to admins because you could use them to figure
+ # out how many hidden bugs are in a particular product (by doing
+ # searches and looking at the number of rows the explain says it's
+ # examining).
+ if (Bugzilla->user->in_group('admin')) {
+ $vars->{'query_explain'} = $dbh->bz_explain($query);
+ }
}
# Time to use server push to display an interim message to the user until
@@ -1058,6 +839,22 @@
# Retrieve the query results one row at a time and write the data into a list
# of Perl records.
+# If we're doing time tracking, then keep totals for all bugs.
+my $percentage_complete = grep($_ eq 'percentage_complete', @displaycolumns);
+my $estimated_time = grep($_ eq 'estimated_time', @displaycolumns);
+my $remaining_time = grep($_ eq 'remaining_time', @displaycolumns)
+ || $percentage_complete;
+my $actual_time = grep($_ eq 'actual_time', @displaycolumns)
+ || $percentage_complete;
+
+my $time_info = { 'estimated_time' => 0,
+ 'remaining_time' => 0,
+ 'actual_time' => 0,
+ 'percentage_complete' => 0,
+ 'time_present' => ($estimated_time || $remaining_time ||
+ $actual_time || $percentage_complete),
+ };
+
my $bugowners = {};
my $bugproducts = {};
my $bugstatuses = {};
@@ -1080,16 +877,12 @@
$bug->{'changeddate'} =~
s/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/$1-$2-$3 $4:$5:$6/;
- # Put in the change date as a time, so that the template date plugin
- # can format the date in any way needed by the template. ICS and Atom
- # have specific, and different, date and time formatting.
- $bug->{'changedtime'} = str2time($bug->{'changeddate'}, Bugzilla->params->{'timezone'});
- $bug->{'changeddate'} = DiffDate($bug->{'changeddate'});
+ $bug->{'changedtime'} = $bug->{'changeddate'}; # for iCalendar and Atom
+ $bug->{'changeddate'} = DiffDate($bug->{'changeddate'});
}
if ($bug->{'opendate'}) {
- # Put in the open date as a time for the template date plugin.
- $bug->{'opentime'} = str2time($bug->{'opendate'}, Bugzilla->params->{'timezone'});
+ $bug->{'opentime'} = $bug->{'opendate'}; # for iCalendar
$bug->{'opendate'} = DiffDate($bug->{'opendate'});
}
@@ -1105,6 +898,11 @@
# Add id to list for checking for bug privacy later
push(@bugidlist, $bug->{'bug_id'});
+
+ # Compute time tracking info.
+ $time_info->{'estimated_time'} += $bug->{'estimated_time'} if ($estimated_time);
+ $time_info->{'remaining_time'} += $bug->{'remaining_time'} if ($remaining_time);
+ $time_info->{'actual_time'} += $bug->{'actual_time'} if ($actual_time);
}
# Check for bug privacy and set $bug->{'secure_mode'} to 'implied' or 'manual'
@@ -1137,6 +935,15 @@
}
}
+# Compute percentage complete without rounding.
+my $sum = $time_info->{'actual_time'}+$time_info->{'remaining_time'};
+if ($sum > 0) {
+ $time_info->{'percentage_complete'} = 100*$time_info->{'actual_time'}/$sum;
+}
+else { # remaining_time <= 0
+ $time_info->{'percentage_complete'} = 0
+}
+
################################################################################
# Template Variable Definition
################################################################################
@@ -1152,16 +959,20 @@
$vars->{'openstates'} = [BUG_STATE_OPEN];
$vars->{'closedstates'} = [map {$_->name} closed_bug_statuses()];
-# The list of query fields in URL query string format, used when creating
-# URLs to the same query results page with different parameters (such as
-# a different sort order or when taking some action on the set of query
-# results). To get this string, we call the Bugzilla::CGI::canoncalise_query
-# function with a list of elements to be removed from the URL.
-$vars->{'urlquerypart'} = $params->canonicalise_query('order',
- 'cmdtype',
- 'query_based_on');
+# The iCal file needs priorities ordered from 1 to 9 (highest to lowest)
+# If there are more than 9 values, just make all the lower ones 9
+if ($format->{'extension'} eq 'ics') {
+ my $n = 1;
+ $vars->{'ics_priorities'} = {};
+ my $priorities = get_legal_field_values('priority');
+ foreach my $p (@$priorities) {
+ $vars->{'ics_priorities'}->{$p} = ($n > 9) ? 9 : $n++;
+ }
+}
+
$vars->{'order'} = $order;
$vars->{'caneditbugs'} = 1;
+$vars->{'time_info'} = $time_info;
if (!Bugzilla->user->in_group('editbugs')) {
foreach my $product (keys %$bugproducts) {
@@ -1186,7 +997,26 @@
$vars->{'splitheader'} = $cgi->cookie('SPLITHEADER') ? 1 : 0;
$vars->{'quip'} = GetQuip();
-$vars->{'currenttime'} = time();
+$vars->{'currenttime'} = localtime(time());
+
+# See if there's only one product in all the results (or only one product
+# that we searched for), which allows us to provide more helpful links.
+my @products = keys %$bugproducts;
+my $one_product;
+if (scalar(@products) == 1) {
+ $one_product = new Bugzilla::Product({ name => $products[0] });
+}
+# This is used in the "Zarroo Boogs" case.
+elsif (my @product_input = $cgi->param('product')) {
+ if (scalar(@product_input) == 1 and $product_input[0] ne '') {
+ $one_product = new Bugzilla::Product({ name => $cgi->param('product') });
+ }
+}
+# We only want the template to use it if the user can actually
+# enter bugs against it.
+if ($one_product && Bugzilla->user->can_enter_product($one_product)) {
+ $vars->{'one_product'} = $one_product;
+}
# The following variables are used when the user is making changes to multiple bugs.
if ($dotweak && scalar @bugs) {
@@ -1197,7 +1027,6 @@
object => 'multiple_bugs'});
}
$vars->{'dotweak'} = 1;
- $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
# issue_session_token needs to write to the master DB.
Bugzilla->switch_to_main_db();
@@ -1211,8 +1040,6 @@
$vars->{'severities'} = get_legal_field_values('bug_severity');
$vars->{'resolutions'} = get_legal_field_values('resolution');
- $vars->{'unconfirmedstate'} = 'UNCONFIRMED';
-
# Convert bug statuses to their ID.
my @bug_statuses = map {$dbh->quote($_)} keys %$bugstatuses;
my $bug_status_ids =
@@ -1242,19 +1069,19 @@
$vars->{'new_bug_statuses'} = Bugzilla::Status->new_from_list($bug_status_ids);
# The groups the user belongs to and which are editable for the given buglist.
- my @products = keys %$bugproducts;
$vars->{'groups'} = GetGroups(\@products);
# If all bugs being changed are in the same product, the user can change
# their version and component, so generate a list of products, a list of
# versions for the product (if there is only one product on the list of
# products), and a list of components for the product.
- if (scalar(@products) == 1) {
- my $product = new Bugzilla::Product({name => $products[0]});
- $vars->{'versions'} = [map($_->name ,@{$product->versions})];
- $vars->{'components'} = [map($_->name, @{$product->components})];
- $vars->{'targetmilestones'} = [map($_->name, @{$product->milestones})]
- if Bugzilla->params->{'usetargetmilestone'};
+ if ($one_product) {
+ $vars->{'versions'} = [map($_->name, grep($_->is_active, @{ $one_product->versions }))];
+ $vars->{'components'} = [map($_->name, grep($_->is_active, @{ $one_product->components }))];
+ if (Bugzilla->params->{'usetargetmilestone'}) {
+ $vars->{'targetmilestones'} = [map($_->name, grep($_->is_active,
+ @{ $one_product->milestones }))];
+ }
}
}
@@ -1262,6 +1089,9 @@
# the "Remember search as" field.
$vars->{'defaultsavename'} = $cgi->param('query_based_on');
+# If we did a quick search then redisplay the previously entered search
+# string in the text field.
+$vars->{'quicksearch'} = $searchstring;
################################################################################
# HTTP Header Generation
@@ -1273,36 +1103,28 @@
my $disposition = "inline";
if ($format->{'extension'} eq "html" && !$agent) {
- if ($order) {
- $cgi->send_cookie(-name => 'LASTORDER',
- -value => $order,
- -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
- }
- my $bugids = join(":", @bugidlist);
- # See also Bug 111999
- if (length($bugids) == 0) {
- $cgi->remove_cookie('BUGLIST');
- }
- elsif (length($bugids) < 4000) {
- $cgi->send_cookie(-name => 'BUGLIST',
- -value => $bugids,
- -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
- }
- else {
- $cgi->remove_cookie('BUGLIST');
- $vars->{'toolong'} = 1;
- }
-
+ my $list_id = $cgi->param('list_id') || $cgi->param('regetlastlist');
+ my $search = $user->save_last_search(
+ { bugs => \@bugidlist, order => $order, vars => $vars, list_id => $list_id });
+ $cgi->param('list_id', $search->id) if $search;
$contenttype = "text/html";
}
else {
$contenttype = $format->{'ctype'};
}
+# Set 'urlquerypart' once the buglist ID is known.
+$vars->{'urlquerypart'} = $params->canonicalise_query('order', 'cmdtype',
+ 'query_based_on');
+
if ($format->{'extension'} eq "csv") {
# We set CSV files to be downloaded, as they are designed for importing
# into other programs.
$disposition = "attachment";
+
+ # If the user clicked the CSV link in the search results,
+ # They should get the Field Description, not the column name in the db
+ $vars->{'human'} = $cgi->param('human');
}
# Suggest a name for the bug list if the user wants to save it as a file.
diff --git a/Websites/bugs.webkit.org/bugzilla.dtd b/Websites/bugs.webkit.org/bugzilla.dtd
index c0f9ff4..61d7969 100644
--- a/Websites/bugs.webkit.org/bugzilla.dtd
+++ b/Websites/bugs.webkit.org/bugzilla.dtd
@@ -1,13 +1,19 @@
<!ELEMENT bugzilla (bug+)>
<!ATTLIST bugzilla
- version CDATA #REQUIRED
- urlbase CDATA #REQUIRED
- maintainer CDATA #REQUIRED
- exporter CDATA #IMPLIED
+ version CDATA #REQUIRED
+ urlbase CDATA #REQUIRED
+ maintainer CDATA #REQUIRED
+ exporter CDATA #IMPLIED
>
-<!ELEMENT bug (bug_id, (alias?, creation_ts, short_desc, delta_ts, reporter_accessible, cclist_accessible, classification_id, classification, product, component, version, rep_platform, op_sys, bug_status, resolution?, dup_id?, bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity, target_milestone?, dependson*, blocked*, votes?, everconfirmed, reporter, assigned_to, qa_contact?, cc*, (estimated_time, remaining_time, actual_time, deadline)?, group*, flag*, long_desc*, attachment*)?)>
+<!ELEMENT bug (bug_id, (alias?, creation_ts, short_desc, delta_ts, reporter_accessible,
+ cclist_accessible, classification_id, classification, product, component,
+ version, rep_platform, op_sys, bug_status, resolution?, dup_id?, see_also*,
+ bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity,
+ target_milestone?, dependson*, blocked*, everconfirmed, reporter, assigned_to,
+ cc*, (estimated_time, remaining_time, actual_time, deadline?)?, qa_contact?,
+ votes?, token?, group*, flag*, long_desc*, attachment*)?)>
<!ATTLIST bug
- error (NotFound | NotPermitted | InvalidBugId) #IMPLIED
+ error (NotFound | NotPermitted | InvalidBugId) #IMPLIED
>
<!ELEMENT bug_id (#PCDATA)>
<!ELEMENT alias (#PCDATA)>
@@ -23,13 +29,22 @@
<!ELEMENT version (#PCDATA)>
<!ELEMENT rep_platform (#PCDATA)>
<!ELEMENT assigned_to (#PCDATA)>
+<!ATTLIST assigned_to
+ name CDATA #REQUIRED
+>
<!ELEMENT delta_ts (#PCDATA)>
<!ELEMENT component (#PCDATA)>
<!ELEMENT reporter (#PCDATA)>
+<!ATTLIST reporter
+ name CDATA #REQUIRED
+>
<!ELEMENT target_milestone (#PCDATA)>
<!ELEMENT bug_severity (#PCDATA)>
<!ELEMENT creation_ts (#PCDATA)>
<!ELEMENT qa_contact (#PCDATA)>
+<!ATTLIST qa_contact
+ name CDATA #REQUIRED
+>
<!ELEMENT status_whiteboard (#PCDATA)>
<!ELEMENT op_sys (#PCDATA)>
<!ELEMENT resolution (#PCDATA)>
@@ -39,29 +54,38 @@
<!ELEMENT keywords (#PCDATA)>
<!ELEMENT dependson (#PCDATA)>
<!ELEMENT blocked (#PCDATA)>
-<!ELEMENT votes (#PCDATA)>
<!ELEMENT everconfirmed (#PCDATA)>
<!ELEMENT cc (#PCDATA)>
+<!ELEMENT see_also (#PCDATA)>
+<!ELEMENT votes (#PCDATA)>
+<!ELEMENT token (#PCDATA)>
<!ELEMENT group (#PCDATA)>
+<!ATTLIST group
+ id CDATA #REQUIRED
+>
<!ELEMENT estimated_time (#PCDATA)>
<!ELEMENT remaining_time (#PCDATA)>
<!ELEMENT actual_time (#PCDATA)>
<!ELEMENT deadline (#PCDATA)>
-<!ELEMENT long_desc (who, bug_when, work_time?, thetext)>
+<!ELEMENT long_desc (commentid, attachid?, who, bug_when, work_time?, thetext)>
<!ATTLIST long_desc
- encoding (base64) #IMPLIED
- isprivate (0|1) #IMPLIED
- >
+ isprivate (0|1) #REQUIRED
+>
+<!ELEMENT commentid (#PCDATA)>
<!ELEMENT who (#PCDATA)>
+<!ATTLIST who
+ name CDATA #REQUIRED
+>
<!ELEMENT bug_when (#PCDATA)>
<!ELEMENT work_time (#PCDATA)>
<!ELEMENT thetext (#PCDATA)>
-<!ELEMENT attachment (attachid, date, desc, filename?, type?, size?, data?, flag*)>
+<!ELEMENT attachment (attachid, date, delta_ts, desc, filename, type, size, attacher, token?, data?, flag*)>
<!ATTLIST attachment
- isobsolete (0|1) #IMPLIED
- ispatch (0|1) #IMPLIED
- isprivate (0|1) #IMPLIED
+ isobsolete (0|1) #REQUIRED
+ ispatch (0|1) #REQUIRED
+ isprivate (0|1) #REQUIRED
>
+<!ELEMENT attacher (#PCDATA)>
<!ELEMENT attachid (#PCDATA)>
<!ELEMENT date (#PCDATA)>
<!ELEMENT desc (#PCDATA)>
@@ -75,7 +99,9 @@
<!ELEMENT flag EMPTY>
<!ATTLIST flag
name CDATA #REQUIRED
+ id CDATA #REQUIRED
+ type_id CDATA #REQUIRED
status CDATA #REQUIRED
- setter CDATA #IMPLIED
+ setter CDATA #REQUIRED
requestee CDATA #IMPLIED
>
diff --git a/Websites/bugs.webkit.org/chart.cgi b/Websites/bugs.webkit.org/chart.cgi
index 1d1193d..9e59dde 100755
--- a/Websites/bugs.webkit.org/chart.cgi
+++ b/Websites/bugs.webkit.org/chart.cgi
@@ -20,6 +20,7 @@
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
# Lance Larsh <lance.larsh@oracle.com>
+# Frédéric Buclin <LpSolit@gmail.com>
# Glossary:
# series: An individual, defined set of data plotted over time.
@@ -47,11 +48,13 @@
use Bugzilla;
use Bugzilla::Constants;
+use Bugzilla::CGI;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Chart;
use Bugzilla::Series;
use Bugzilla::User;
+use Bugzilla::Token;
# For most scripts we don't make $cgi and $template global variables. But
# when preparing Bugzilla for mod_perl, this script used these
@@ -60,12 +63,19 @@
local our $cgi = Bugzilla->cgi;
local our $template = Bugzilla->template;
local our $vars = {};
+my $dbh = Bugzilla->dbh;
+
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+if (!Bugzilla->feature('new_charts')) {
+ ThrowCodeError('feature_disabled', { feature => 'new_charts' });
+}
# Go back to query.cgi if we are adding a boolean chart parameter.
if (grep(/^cmd-/, $cgi->param())) {
my $params = $cgi->canonicalise_query("format", "ctype", "action");
- print "Location: query.cgi?format=" . $cgi->param('query_format') .
- ($params ? "&$params" : "") . "\n\n";
+ print $cgi->redirect("query.cgi?format=" . $cgi->param('query_format') .
+ ($params ? "&$params" : ""));
exit;
}
@@ -88,19 +98,17 @@
# Go to buglist.cgi if we are doing a search.
if ($action eq "search") {
my $params = $cgi->canonicalise_query("format", "ctype", "action");
- print "Location: buglist.cgi" . ($params ? "?$params" : "") . "\n\n";
+ print $cgi->redirect("buglist.cgi" . ($params ? "?$params" : ""));
exit;
}
-my $user = Bugzilla->login(LOGIN_REQUIRED);
-
-Bugzilla->user->in_group(Bugzilla->params->{"chartgroup"})
+$user->in_group(Bugzilla->params->{"chartgroup"})
|| ThrowUserError("auth_failure", {group => Bugzilla->params->{"chartgroup"},
action => "use",
object => "charts"});
# Only admins may create public queries
-Bugzilla->user->in_group('admin') || $cgi->delete('public');
+$user->in_group('admin') || $cgi->delete('public');
# All these actions relate to chart construction.
if ($action =~ /^(assemble|add|remove|sum|subscribe|unsubscribe)$/) {
@@ -138,37 +146,31 @@
}
elsif ($action eq "create") {
assertCanCreate($cgi);
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['create-series']);
my $series = new Bugzilla::Series($cgi);
- if (!$series->existsInDatabase()) {
- $series->writeToDatabase();
- $vars->{'message'} = "series_created";
- }
- else {
- ThrowUserError("series_already_exists", {'series' => $series});
- }
+ ThrowUserError("series_already_exists", {'series' => $series})
+ if $series->existsInDatabase;
+ $series->writeToDatabase();
+ $vars->{'message'} = "series_created";
$vars->{'series'} = $series;
- print $cgi->header();
- $template->process("global/message.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
+ my $chart = new Bugzilla::Chart($cgi);
+ view($chart);
}
elsif ($action eq "edit") {
- detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
- assertCanEdit($series_id);
-
- my $series = new Bugzilla::Series($series_id);
-
+ my $series = assertCanEdit($series_id);
edit($series);
}
elsif ($action eq "alter") {
- # This is the "commit" action for editing a series
- detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
- assertCanEdit($series_id);
-
- my $series = new Bugzilla::Series($cgi);
+ my $series = assertCanEdit($series_id);
+ my $token = $cgi->param('token');
+ check_hash_token($token, [$series->id, $series->name]);
+ # XXX - This should be replaced by $series->set_foo() methods.
+ $series = new Bugzilla::Series($cgi);
# We need to check if there is _another_ series in the database with
# our (potentially new) name. So we call existsInDatabase() to see if
@@ -186,8 +188,50 @@
edit($series);
}
+elsif ($action eq "confirm-delete") {
+ $vars->{'series'} = assertCanEdit($series_id);
+
+ print $cgi->header();
+ $template->process("reports/delete-series.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+elsif ($action eq "delete") {
+ my $series = assertCanEdit($series_id);
+ my $token = $cgi->param('token');
+ check_hash_token($token, [$series->id, $series->name]);
+
+ $dbh->bz_start_transaction();
+
+ $series->remove_from_db();
+ # Remove (sub)categories which no longer have any series.
+ foreach my $cat (qw(category subcategory)) {
+ my $is_used = $dbh->selectrow_array("SELECT COUNT(*) FROM series WHERE $cat = ?",
+ undef, $series->{"${cat}_id"});
+ if (!$is_used) {
+ $dbh->do('DELETE FROM series_categories WHERE id = ?',
+ undef, $series->{"${cat}_id"});
+ }
+ }
+ $dbh->bz_commit_transaction();
+
+ $vars->{'message'} = "series_deleted";
+ $vars->{'series'} = $series;
+ view();
+}
+elsif ($action eq "convert_search") {
+ my $saved_search = $cgi->param('series_from_search') || '';
+ my ($query) = grep { $_->name eq $saved_search } @{ $user->queries };
+ my $url = '';
+ if ($query) {
+ my $params = new Bugzilla::CGI($query->edit_link);
+ # These two parameters conflict with the one below.
+ $url = $params->canonicalise_query('format', 'query_format');
+ $url = '&' . html_quote($url);
+ }
+ print $cgi->redirect(-location => correct_urlbase() . "query.cgi?format=create-series$url");
+}
else {
- ThrowCodeError("unknown_action");
+ ThrowUserError('unknown_action', {action => $action});
}
exit;
@@ -208,30 +252,31 @@
# Check if the user is the owner of series_id or is an admin.
sub assertCanEdit {
- my ($series_id) = @_;
+ my $series_id = shift;
my $user = Bugzilla->user;
- return if $user->in_group('admin');
+ my $series = new Bugzilla::Series($series_id)
+ || ThrowCodeError('invalid_series_id');
- my $dbh = Bugzilla->dbh;
- my $iscreator = $dbh->selectrow_array("SELECT CASE WHEN creator = ? " .
- "THEN 1 ELSE 0 END FROM series " .
- "WHERE series_id = ?", undef,
- $user->id, $series_id);
- $iscreator || ThrowUserError("illegal_series_edit");
+ if (!$user->in_group('admin') && $series->{creator_id} != $user->id) {
+ ThrowUserError('illegal_series_edit');
+ }
+
+ return $series;
}
# Check if the user is permitted to create this series with these parameters.
sub assertCanCreate {
my ($cgi) = shift;
-
- Bugzilla->user->in_group("editbugs") || ThrowUserError("illegal_series_creation");
+ my $user = Bugzilla->user;
+
+ $user->in_group("editbugs") || ThrowUserError("illegal_series_creation");
# Check permission for frequency
my $min_freq = 7;
- if ($cgi->param('frequency') < $min_freq && !Bugzilla->user->in_group("admin")) {
+ if ($cgi->param('frequency') < $min_freq && !$user->in_group("admin")) {
ThrowUserError("illegal_frequency", { 'minimum' => $min_freq });
- }
+ }
}
sub validateWidthAndHeight {
@@ -261,7 +306,6 @@
my $series = shift;
$vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
- $vars->{'creator'} = new Bugzilla::User($series->{'creator'});
$vars->{'default'} = $series;
print $cgi->header();
@@ -294,7 +338,7 @@
# We create a Chart object so we can validate the parameters
my $chart = new Bugzilla::Chart($cgi);
- $vars->{'time'} = time();
+ $vars->{'time'} = localtime(time());
$vars->{'imagebase'} = $cgi->canonicalise_query(
"action", "action-wrap", "ctype", "format", "width", "height");
diff --git a/Websites/bugs.webkit.org/checksetup.pl b/Websites/bugs.webkit.org/checksetup.pl
index 2809c94..d9f2afa 100755
--- a/Websites/bugs.webkit.org/checksetup.pl
+++ b/Websites/bugs.webkit.org/checksetup.pl
@@ -53,15 +53,19 @@
use lib qw(. lib);
use Bugzilla::Constants;
use Bugzilla::Install::Requirements;
-use Bugzilla::Install::Util qw(install_string get_version_and_os get_console_locale);
+use Bugzilla::Install::Util qw(install_string get_version_and_os
+ init_console success);
######################################################################
# Live Code
######################################################################
+# Do not run checksetup.pl from the web browser.
+Bugzilla::Install::Util::no_checksetup_from_cgi() if $ENV{'SERVER_SOFTWARE'};
+
# When we're running at the command line, we need to pick the right
# language before ever displaying any string.
-$ENV{'HTTP_ACCEPT_LANGUAGE'} ||= get_console_locale();
+init_console();
my %switch;
GetOptions(\%switch, 'help|h|?', 'check-modules', 'no-templates|t',
@@ -95,10 +99,11 @@
# then instead of our nice normal checksetup message, the user would
# get a cryptic perl error about the missing module.
-# We need $::ENV{'PATH'} to remain defined.
-my $env = $::ENV{'PATH'};
require Bugzilla;
-$::ENV{'PATH'} = $env;
+require Bugzilla::User;
+
+require Bugzilla::Util;
+import Bugzilla::Util qw(get_text);
require Bugzilla::Config;
import Bugzilla::Config qw(:admin);
@@ -115,7 +120,6 @@
require Bugzilla::Field;
require Bugzilla::Install;
-Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
Bugzilla->installation_mode(INSTALLATION_MODE_NON_INTERACTIVE) if $answers_file;
Bugzilla->installation_answers($answers_file);
@@ -193,6 +197,7 @@
###########################################################################
Bugzilla::Install::DB::update_table_definitions(\%old_params);
+Bugzilla::Install::init_workflow();
###########################################################################
# Bugzilla uses --GROUPS-- to assign various rights to its users.
@@ -200,6 +205,9 @@
Bugzilla::Install::update_system_groups();
+# "Log In" as the fake superuser who can do everything.
+Bugzilla->set_user(Bugzilla::User->super_user);
+
###########################################################################
# Create --SETTINGS-- users can adjust
###########################################################################
@@ -217,12 +225,12 @@
if $switch{'reset-password'};
###########################################################################
-# Create default Product and Classification
+# Create default Product
###########################################################################
Bugzilla::Install::create_default_product();
-Bugzilla::Hook::process('install-before_final_checks', {'silent' => $silent });
+Bugzilla::Hook::process('install_before_final_checks', { silent => $silent });
###########################################################################
# Final checks
@@ -231,9 +239,12 @@
# Check if the default parameter for urlbase is still set, and if so, give
# notification that they should go and visit editparams.cgi
if (Bugzilla->params->{'urlbase'} eq '') {
- print "\n" . Bugzilla::Install::get_text('install_urlbase_default') . "\n"
+ print "\n" . get_text('install_urlbase_default') . "\n"
unless $silent;
}
+if (!$silent) {
+ success(get_text('install_success'));
+}
__END__
@@ -406,6 +417,10 @@
The code for this is in L<Bugzilla::Install::DB/update_table_definitions>.
+This includes creating the default Classification (using
+L<Bugzilla::Install/create_default_classification>) and setting up all
+the foreign keys for all tables, using L<Bugzilla::DB/bz_setup_foreign_keys>.
+
=item 14
Creates the system groups--the ones like C<editbugs>, C<admin>, and so on.
@@ -426,7 +441,7 @@
=item 17
-Creates the default Classification, Product, and Component, using
+Creates the default Product and Component, using
L<Bugzilla::Install/create_default_product>.
=back
diff --git a/Websites/bugs.webkit.org/colchange.cgi b/Websites/bugs.webkit.org/colchange.cgi
index 476ce99..80edb8a 100755
--- a/Websites/bugs.webkit.org/colchange.cgi
+++ b/Websites/bugs.webkit.org/colchange.cgi
@@ -21,9 +21,9 @@
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Gervase Markham <gerv@gerv.net>
# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Pascal Held <paheld@gmail.com>
use strict;
-
use lib qw(. lib);
use Bugzilla;
@@ -33,7 +33,25 @@
use Bugzilla::Search::Saved;
use Bugzilla::Error;
use Bugzilla::User;
-use Bugzilla::Keyword;
+use Bugzilla::Token;
+
+use Storable qw(dclone);
+
+# Maps parameters that control columns to the names of columns.
+use constant COLUMN_PARAMS => {
+ 'useclassification' => ['classification'],
+ 'usebugaliases' => ['alias'],
+ 'usetargetmilestone' => ['target_milestone'],
+ 'useqacontact' => ['qa_contact', 'qa_contact_realname'],
+ 'usestatuswhiteboard' => ['status_whiteboard'],
+};
+
+# We only show these columns if an object of this type exists in the
+# database.
+use constant COLUMN_CLASSES => {
+ 'Bugzilla::Flag' => 'flagtypes.name',
+ 'Bugzilla::Keyword' => 'keywords',
+};
Bugzilla->login();
@@ -41,71 +59,60 @@
my $template = Bugzilla->template;
my $vars = {};
-# The master list not only says what fields are possible, but what order
-# they get displayed in.
-my @masterlist = ("opendate", "changeddate", "bug_severity", "priority",
- "rep_platform", "assigned_to", "assigned_to_realname",
- "reporter", "reporter_realname", "bug_status",
- "resolution");
+my $columns = dclone(Bugzilla::Search::COLUMNS);
-if (Bugzilla->params->{"useclassification"}) {
- push(@masterlist, "classification");
+# You can't manually select "relevance" as a column you want to see.
+delete $columns->{'relevance'};
+
+foreach my $param (keys %{ COLUMN_PARAMS() }) {
+ next if Bugzilla->params->{$param};
+ foreach my $column (@{ COLUMN_PARAMS->{$param} }) {
+ delete $columns->{$column};
+ }
}
-push(@masterlist, ("product", "component", "version", "op_sys"));
-
-if (Bugzilla->params->{"usevotes"}) {
- push (@masterlist, "votes");
-}
-if (Bugzilla->params->{"usebugaliases"}) {
- unshift(@masterlist, "alias");
-}
-if (Bugzilla->params->{"usetargetmilestone"}) {
- push(@masterlist, "target_milestone");
-}
-if (Bugzilla->params->{"useqacontact"}) {
- push(@masterlist, "qa_contact");
- push(@masterlist, "qa_contact_realname");
-}
-if (Bugzilla->params->{"usestatuswhiteboard"}) {
- push(@masterlist, "status_whiteboard");
-}
-if (Bugzilla::Keyword::keyword_count()) {
- push(@masterlist, "keywords");
+foreach my $class (keys %{ COLUMN_CLASSES() }) {
+ eval("use $class; 1;") || die $@;
+ my $column = COLUMN_CLASSES->{$class};
+ delete $columns->{$column} if !$class->any_exist;
}
-if (Bugzilla->user->in_group(Bugzilla->params->{"timetrackinggroup"})) {
- push(@masterlist, ("estimated_time", "remaining_time", "actual_time",
- "percentage_complete", "deadline"));
+if (!Bugzilla->user->is_timetracker) {
+ foreach my $column (TIMETRACKING_FIELDS) {
+ delete $columns->{$column};
+ }
}
-push(@masterlist, ("short_desc", "short_short_desc"));
-
-my @custom_fields = grep { $_->type != FIELD_TYPE_MULTI_SELECT }
- Bugzilla->active_custom_fields;
-push(@masterlist, map { $_->name } @custom_fields);
-
-Bugzilla::Hook::process("colchange-columns", {'columns' => \@masterlist} );
-
-$vars->{'masterlist'} = \@masterlist;
+$vars->{'columns'} = $columns;
my @collist;
if (defined $cgi->param('rememberedquery')) {
+ my $search;
+ if (defined $cgi->param('saved_search')) {
+ $search = new Bugzilla::Search::Saved($cgi->param('saved_search'));
+ }
+
+ my $token = $cgi->param('token');
+ if ($search) {
+ check_hash_token($token, [$search->id, $search->name]);
+ }
+ else {
+ check_hash_token($token, ['default-list']);
+ }
+
my $splitheader = 0;
if (defined $cgi->param('resetit')) {
@collist = DEFAULT_COLUMN_LIST;
} else {
- foreach my $i (@masterlist) {
- if (defined $cgi->param("column_$i")) {
- push @collist, $i;
- }
+ if (defined $cgi->param("selected_columns")) {
+ @collist = grep { exists $columns->{$_} }
+ $cgi->param("selected_columns");
}
if (defined $cgi->param('splitheader')) {
$splitheader = $cgi->param('splitheader')? 1: 0;
}
}
my $list = join(" ", @collist);
- my $urlbase = Bugzilla->params->{"urlbase"};
if ($list) {
# Only set the cookie if this is not a saved search.
@@ -130,11 +137,6 @@
$vars->{'message'} = "change_columns";
- my $search;
- if (defined $cgi->param('saved_search')) {
- $search = new Bugzilla::Search::Saved($cgi->param('saved_search'));
- }
-
if ($cgi->param('save_columns_for_search')
&& defined $search && $search->user->id == Bugzilla->user->id)
{
@@ -142,19 +144,20 @@
$params->param('columnlist', join(",", @collist));
$search->set_url($params->query_string());
$search->update();
- $vars->{'redirect_url'} = "buglist.cgi?".$cgi->param('rememberedquery');
- }
- else {
- my $params = new Bugzilla::CGI($cgi->param('rememberedquery'));
- $params->param('columnlist', join(",", @collist));
- $vars->{'redirect_url'} = "buglist.cgi?".$params->query_string();
}
+ my $params = new Bugzilla::CGI($cgi->param('rememberedquery'));
+ $params->param('columnlist', join(",", @collist));
+ $vars->{'redirect_url'} = "buglist.cgi?".$params->query_string();
- # If we're running on Microsoft IIS, using cgi->redirect discards
- # the Set-Cookie lines -- workaround is to use the old-fashioned
- # redirection mechanism. See bug 214466 for details.
- if ($ENV{'SERVER_SOFTWARE'} =~ /Microsoft-IIS/
+
+ # If we're running on Microsoft IIS, $cgi->redirect discards
+ # the Set-Cookie lines. In mod_perl, $cgi->redirect with cookies
+ # causes the page to be rendered as text/plain.
+ # Workaround is to use the old-fashioned redirection mechanism.
+ # See bug 214466 and bug 376044 for details.
+ if ($ENV{'MOD_PERL'}
+ || $ENV{'SERVER_SOFTWARE'} =~ /Microsoft-IIS/
|| $ENV{'SERVER_SOFTWARE'} =~ /Sun ONE Web/)
{
print $cgi->header(-type => "text/html",
@@ -162,6 +165,7 @@
}
else {
print $cgi->redirect($vars->{'redirect_url'});
+ exit;
}
$template->process("global/message.html.tmpl", $vars)
@@ -169,7 +173,9 @@
exit;
}
-if (defined $cgi->cookie('COLUMNLIST')) {
+if (defined $cgi->param('columnlist')) {
+ @collist = split(/[ ,]+/, $cgi->param('columnlist'));
+} elsif (defined $cgi->cookie('COLUMNLIST')) {
@collist = split(/ /, $cgi->cookie('COLUMNLIST'));
} else {
@collist = DEFAULT_COLUMN_LIST;
@@ -185,16 +191,8 @@
my $searches = Bugzilla->user->queries;
my ($search) = grep($_->name eq $cgi->param('query_based_on'), @$searches);
- # Only allow users to edit their own queries.
- if ($search && $search->user->id == Bugzilla->user->id) {
+ if ($search) {
$vars->{'saved_search'} = $search;
- $vars->{'buffer'} = "cmdtype=runnamed&namedcmd=". url_quote($search->name);
-
- my $params = new Bugzilla::CGI($search->url);
- if ($params->param('columnlist')) {
- my @collist = split(',', $params->param('columnlist'));
- $vars->{'collist'} = \@collist if scalar (@collist);
- }
}
}
diff --git a/Websites/bugs.webkit.org/collectstats.pl b/Websites/bugs.webkit.org/collectstats.pl
index 0fda413..c27d4c2 100755
--- a/Websites/bugs.webkit.org/collectstats.pl
+++ b/Websites/bugs.webkit.org/collectstats.pl
@@ -25,18 +25,14 @@
# Jean-Sebastien Guay <jean_seb@hybride.com>
# Frédéric Buclin <LpSolit@gmail.com>
-# Run me out of cron at midnight to collect Bugzilla statistics.
-#
-# To run new charts for a specific date, pass it in on the command line in
-# ISO (2004-08-14) format.
-
-use AnyDBM_File;
use strict;
-use IO::Handle;
-use Cwd;
-
use lib qw(. lib);
+use Getopt::Long qw(:config bundling);
+use Pod::Usage;
+use List::Util qw(first);
+use Cwd;
+
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
@@ -45,38 +41,32 @@
use Bugzilla::User;
use Bugzilla::Product;
use Bugzilla::Field;
+use Bugzilla::Install::Filesystem qw(fix_dir_permissions);
+
+my %switch;
+GetOptions(\%switch, 'help|h', 'regenerate');
+
+# Print the help message if that switch was selected.
+pod2usage({-verbose => 1, -exitval => 1}) if $switch{'help'};
# Turn off output buffering (probably needed when displaying output feedback
# in the regenerate mode).
$| = 1;
+my $datadir = bz_locations()->{'datadir'};
+my $graphsdir = bz_locations()->{'graphsdir'};
+
# Tidy up after graphing module
my $cwd = Cwd::getcwd();
-if (chdir("graphs")) {
+if (chdir($graphsdir)) {
unlink <./*.gif>;
unlink <./*.png>;
# chdir("..") doesn't work if graphs is a symlink, see bug 429378
chdir($cwd);
}
-# This is a pure command line script.
-Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
-
my $dbh = Bugzilla->switch_to_shadow_db();
-
-# To recreate the daily statistics, run "collectstats.pl --regenerate" .
-my $regenerate = 0;
-if ($#ARGV >= 0 && $ARGV[0] eq "--regenerate") {
- shift(@ARGV);
- $regenerate = 1;
-}
-
-my $datadir = bz_locations()->{'datadir'};
-
-my @myproducts = map {$_->name} Bugzilla::Product->get_all;
-unshift(@myproducts, "-All-");
-
# As we can now customize statuses and resolutions, looking at the current list
# of legal values only is not enough as some now removed statuses and resolutions
# may have existed in the past, or have been renamed. We want them all.
@@ -114,71 +104,74 @@
# Exclude "" from the resolution list.
@resolutions = grep {$_} @resolutions;
+# --regenerate was taking an enormous amount of time to query everything
+# per bug, per day. Instead, we now just get all the data out of the DB
+# at once and stuff it into some data structures.
+my (%bug_status, %bug_resolution, %removed);
+if ($switch{'regenerate'}) {
+ %bug_resolution = @{ $dbh->selectcol_arrayref(
+ 'SELECT bug_id, resolution FROM bugs', {Columns=>[1,2]}) };
+ %bug_status = @{ $dbh->selectcol_arrayref(
+ 'SELECT bug_id, bug_status FROM bugs', {Columns=>[1,2]}) };
+
+ my $removed_sth = $dbh->prepare(
+ q{SELECT bugs_activity.bug_id, bugs_activity.removed,}
+ . $dbh->sql_to_days('bugs_activity.bug_when')
+ . q{ FROM bugs_activity
+ WHERE bugs_activity.fieldid = ?
+ ORDER BY bugs_activity.bug_when});
+
+ %removed = (bug_status => {}, resolution => {});
+ foreach my $field (qw(bug_status resolution)) {
+ my $field_id = Bugzilla::Field->check($field)->id;
+ my $rows = $dbh->selectall_arrayref($removed_sth, undef, $field_id);
+ my $hash = $removed{$field};
+ foreach my $row (@$rows) {
+ my ($bug_id, $removed, $when) = @$row;
+ $hash->{$bug_id} ||= [];
+ push(@{ $hash->{$bug_id} }, { when => int($when),
+ removed => $removed });
+ }
+ }
+}
+
my $tstart = time;
+
+my @myproducts = Bugzilla::Product->get_all;
+unshift(@myproducts, "-All-");
+
+my $dir = "$datadir/mining";
+if (!-d $dir) {
+ mkdir $dir or die "mkdir $dir failed: $!";
+ fix_dir_permissions($dir);
+}
+
foreach (@myproducts) {
- my $dir = "$datadir/mining";
-
- &check_data_dir ($dir);
-
- if ($regenerate) {
- ®enerate_stats($dir, $_);
+ if ($switch{'regenerate'}) {
+ regenerate_stats($dir, $_, \%bug_resolution, \%bug_status, \%removed);
} else {
&collect_stats($dir, $_);
}
}
+# Fix permissions for all files in mining/.
+fix_dir_permissions($dir);
+
my $tend = time;
# Uncomment the following line for performance testing.
#print "Total time taken " . delta_time($tstart, $tend) . "\n";
-&calculate_dupes();
-
CollectSeriesData();
-{
- local $ENV{'GATEWAY_INTERFACE'} = 'cmdline';
- local $ENV{'REQUEST_METHOD'} = 'GET';
- local $ENV{'QUERY_STRING'} = 'ctype=rdf';
-
- my $perl = $^X;
- trick_taint($perl);
-
- # Generate a static RDF file containing the default view of the duplicates data.
- open(CGI, "$perl -T duplicates.cgi |")
- || die "can't fork duplicates.cgi: $!";
- open(RDF, ">$datadir/duplicates.tmp")
- || die "can't write to $datadir/duplicates.tmp: $!";
- my $headers_done = 0;
- while (<CGI>) {
- print RDF if $headers_done;
- $headers_done = 1 if $_ eq "\r\n";
- }
- close CGI;
- close RDF;
-}
-if (-s "$datadir/duplicates.tmp") {
- rename("$datadir/duplicates.rdf", "$datadir/duplicates-old.rdf");
- rename("$datadir/duplicates.tmp", "$datadir/duplicates.rdf");
-}
-
-sub check_data_dir {
- my $dir = shift;
-
- if (! -d $dir) {
- mkdir $dir, 0755;
- chmod 0755, $dir;
- }
-}
-
sub collect_stats {
my $dir = shift;
my $product = shift;
my $when = localtime (time);
my $dbh = Bugzilla->dbh;
-
my $product_id;
- if ($product ne '-All-') {
- my $prod = Bugzilla::Product::check_product($product);
- $product_id = $prod->id;
+
+ if (ref $product) {
+ $product_id = $product->id;
+ $product = $product->name;
}
# NB: Need to mangle the product for the filename, but use the real
@@ -202,6 +195,10 @@
|| ThrowCodeError('chart_file_open_fail', {'filename' => $file});
}
+ if (Bugzilla->params->{'utf8'}) {
+ binmode DATA, ':utf8';
+ }
+
# Now collect current data.
my @row = (today());
my $status_sql = q{SELECT COUNT(*) FROM bugs WHERE bug_status = ?};
@@ -250,7 +247,6 @@
}
print DATA (join '|', @row) . "\n";
close DATA;
- chmod 0644, $file;
}
sub get_old_data {
@@ -259,6 +255,10 @@
open(DATA, '<', $file)
|| ThrowCodeError('chart_file_open_fail', {'filename' => $file});
+ if (Bugzilla->params->{'utf8'}) {
+ binmode DATA, ':utf8';
+ }
+
my @data;
my @columns;
my $recreate = 0;
@@ -295,85 +295,9 @@
return @data;
}
-sub calculate_dupes {
- my $dbh = Bugzilla->dbh;
- my $rows = $dbh->selectall_arrayref("SELECT dupe_of, dupe FROM duplicates");
-
- my %dupes;
- my %count;
- my $key;
- my $changed = 1;
-
- my $today = &today_dash;
-
- # Save % count here in a date-named file
- # so we can read it back in to do changed counters
- # First, delete it if it exists, so we don't add to the contents of an old file
- my $datadir = bz_locations()->{'datadir'};
-
- if (my @files = <$datadir/duplicates/dupes$today*>) {
- map { trick_taint($_) } @files;
- unlink @files;
- }
-
- dbmopen(%count, "$datadir/duplicates/dupes$today", 0644) || die "Can't open DBM dupes file: $!";
-
- # Create a hash with key "a bug number", value "bug which that bug is a
- # direct dupe of" - straight from the duplicates table.
- foreach my $row (@$rows) {
- my ($dupe_of, $dupe) = @$row;
- $dupes{$dupe} = $dupe_of;
- }
-
- # Total up the number of bugs which are dupes of a given bug
- # count will then have key = "bug number",
- # value = "number of immediate dupes of that bug".
- foreach $key (keys(%dupes))
- {
- my $dupe_of = $dupes{$key};
-
- if (!defined($count{$dupe_of})) {
- $count{$dupe_of} = 0;
- }
-
- $count{$dupe_of}++;
- }
-
- # Now we collapse the dupe tree by iterating over %count until
- # there is no further change.
- while ($changed == 1)
- {
- $changed = 0;
- foreach $key (keys(%count)) {
- # if this bug is actually itself a dupe, and has a count...
- if (defined($dupes{$key}) && $count{$key} > 0) {
- # add that count onto the bug it is a dupe of,
- # and zero the count; the check is to avoid
- # loops
- if ($count{$dupes{$key}} != 0) {
- $count{$dupes{$key}} += $count{$key};
- $count{$key} = 0;
- $changed = 1;
- }
- }
- }
- }
-
- # Remove the values for which the count is zero
- foreach $key (keys(%count))
- {
- if ($count{$key} == 0) {
- delete $count{$key};
- }
- }
-
- dbmclose(%count);
-}
-
# This regenerates all statistics from the database.
sub regenerate_stats {
- my $dir = shift;
- my $product = shift;
+ my ($dir, $product, $bug_resolution, $bug_status, $removed) = @_;
my $dbh = Bugzilla->dbh;
my $when = localtime(time());
@@ -381,12 +305,13 @@
# NB: Need to mangle the product for the filename, but use the real
# product name in the query
+ if (ref $product) {
+ $product = $product->name;
+ }
my $file_product = $product;
$file_product =~ s/\//-/gs;
my $file = join '/', $dir, $file_product;
- my @bugs;
-
my $and_product = "";
my $from_product = "";
@@ -402,7 +327,7 @@
# database was created, and the end date from the current day.
# If there were no bugs in the search, return early.
my $query = q{SELECT } .
- $dbh->sql_to_days('creation_ts') . q{ AS start_day, } .
+ $dbh->sql_to_days('creation_ts') . q{ AS start_day, } .
$dbh->sql_to_days('current_date') . q{ AS end_day, } .
$dbh->sql_to_days("'1970-01-01'") .
qq{ FROM bugs $from_product
@@ -416,7 +341,6 @@
}
if (open DATA, ">$file") {
- DATA->autoflush(1);
my $fields = join('|', ('DATE', @statuses, @resolutions));
print DATA <<FIN;
# Bugzilla Daily Bug Stats
@@ -429,6 +353,7 @@
FIN
# For each day, generate a line of statistics.
my $total_days = $end - $start;
+ my @bugs;
for (my $day = $start + 1; $day <= $end; $day++) {
# Some output feedback
my $percent_done = ($day - $start - 1) * 100 / $total_days;
@@ -445,56 +370,25 @@
$and_product . q{ ORDER BY bug_id};
my $bug_ids = $dbh->selectcol_arrayref($query, undef, @values);
-
push(@bugs, @$bug_ids);
- # For each bug that existed on that day, determine its status
- # at the beginning of the day. If there were no status
- # changes on or after that day, the status was the same as it
- # is today, which can be found in the bugs table. Otherwise,
- # the status was equal to the first "previous value" entry in
- # the bugs_activity table for that bug made on or after that
- # day.
my %bugcount;
foreach (@statuses) { $bugcount{$_} = 0; }
foreach (@resolutions) { $bugcount{$_} = 0; }
# Get information on bug states and resolutions.
- $query = qq{SELECT bugs_activity.removed
- FROM bugs_activity
- INNER JOIN fielddefs
- ON bugs_activity.fieldid = fielddefs.id
- WHERE fielddefs.name = ?
- AND bugs_activity.bug_id = ?
- AND bugs_activity.bug_when >= } .
- $dbh->sql_from_days($day) .
- " ORDER BY bugs_activity.bug_when " .
- $dbh->sql_limit(1);
-
- my $sth_bug = $dbh->prepare($query);
- my $sth_status = $dbh->prepare(q{SELECT bug_status
- FROM bugs
- WHERE bug_id = ?});
-
- my $sth_reso = $dbh->prepare(q{SELECT resolution
- FROM bugs
- WHERE bug_id = ?});
-
for my $bug (@bugs) {
- my $status = $dbh->selectrow_array($sth_bug, undef,
- 'bug_status', $bug);
- unless ($status) {
- $status = $dbh->selectrow_array($sth_status, undef, $bug);
- }
+ my $status = _get_value(
+ $removed->{'bug_status'}->{$bug},
+ $bug_status, $day, $bug);
if (defined $bugcount{$status}) {
$bugcount{$status}++;
}
- my $resolution = $dbh->selectrow_array($sth_bug, undef,
- 'resolution', $bug);
- unless ($resolution) {
- $resolution = $dbh->selectrow_array($sth_reso, undef, $bug);
- }
-
+
+ my $resolution = _get_value(
+ $removed->{'resolution'}->{$bug},
+ $bug_resolution, $day, $bug);
+
if (defined $bugcount{$resolution}) {
$bugcount{$resolution}++;
}
@@ -508,17 +402,34 @@
foreach (@resolutions) { print DATA "|$bugcount{$_}"; }
print DATA "\n";
}
-
+
# Finish up output feedback for this product.
my $tend = time;
print "\rRegenerating $product \[100.0\%] - " .
delta_time($tstart, $tend) . "\n";
-
+
close DATA;
- chmod 0640, $file;
}
}
+# A helper for --regenerate.
+# For each bug that exists on a day, we determine its status/resolution
+# at the beginning of the day. If there were no status/resolution
+# changes on or after that day, the status was the same as it
+# is today (the "current" value). Otherwise, the status was equal to the
+# first "previous value" entry in the bugs_activity table for that
+# bug made on or after that day.
+sub _get_value {
+ my ($removed, $current, $day, $bug) = @_;
+
+ # Get the first change that's on or after this day.
+ my $item = first { $_->{when} >= $day } @{ $removed || [] };
+
+ # If there's no change on or after this day, then we just return the
+ # current value.
+ return $item ? $item->{removed} : $current->{$bug};
+}
+
sub today {
my ($dom, $mon, $year) = (localtime(time))[3, 4, 5];
return sprintf "%04d%02d%02d", 1900 + $year, ++$mon, $dom;
@@ -555,7 +466,7 @@
# (days_since_epoch + series_id) % frequency = 0. So they'll run every
# <frequency> days, but the start date depends on the series_id.
my $days_since_epoch = int(time() / (60 * 60 * 24));
- my $today = $ARGV[0] || today_dash();
+ my $today = today_dash();
# We save a copy of the main $dbh and then switch to the shadow and get
# that one too. Remember, these may be the same.
@@ -589,10 +500,11 @@
# Do not die if Search->new() detects invalid data, such as an obsolete
# login name or a renamed product or component, etc.
eval {
- my $search = new Bugzilla::Search('params' => $cgi,
- 'fields' => ["bugs.bug_id"],
+ my $search = new Bugzilla::Search('params' => scalar $cgi->Vars,
+ 'fields' => ["bug_id"],
+ 'allow_unlimited' => 1,
'user' => $user);
- my $sql = $search->getSQL();
+ my $sql = $search->sql;
$data = $shadow_dbh->selectall_arrayref($sql);
};
@@ -607,3 +519,39 @@
}
}
+__END__
+
+=head1 NAME
+
+collectstats.pl - Collect data about Bugzilla bugs.
+
+=head1 SYNOPSIS
+
+ ./collectstats.pl [--regenerate] [--help]
+
+Collects data about bugs to be used in Old and New Charts.
+
+=head1 OPTIONS
+
+=over
+
+=item B<--help>
+
+Print this help page.
+
+=item B<--regenerate>
+
+Recreate all the data about bugs, from day 1. This option is only relevant
+for Old Charts, and has no effect for New Charts.
+This option will overwrite all existing collected data and can take a huge
+amount of time. You normally don't need to use this option (do not use it
+in a cron job).
+
+=back
+
+=head1 DESCRIPTION
+
+This script collects data about all bugs for Old Charts, triaged by product
+and by bug status and resolution. It also collects data for New Charts, based
+on existing series. For New Charts, data is only collected once a series is
+defined; this script cannot recreate data prior to this date.
diff --git a/Websites/bugs.webkit.org/config.cgi b/Websites/bugs.webkit.org/config.cgi
index 3353967..0aa4506 100755
--- a/Websites/bugs.webkit.org/config.cgi
+++ b/Websites/bugs.webkit.org/config.cgi
@@ -20,6 +20,7 @@
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Myk Melez <myk@mozilla.org>
+# Frank Becker <Frank@Frank-Becker.de>
################################################################################
# Script Initialization
@@ -34,9 +35,12 @@
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Keyword;
+use Bugzilla::Product;
use Bugzilla::Status;
use Bugzilla::Field;
+use Digest::MD5 qw(md5_base64);
+
my $user = Bugzilla->login(LOGIN_OPTIONAL);
my $cgi = Bugzilla->cgi;
@@ -46,6 +50,9 @@
display_data();
}
+# Get data from the shadow DB as they don't change very often.
+Bugzilla->switch_to_shadow_db;
+
# Pass a bunch of Bugzilla configuration to the templates.
my $vars = {};
$vars->{'priority'} = get_legal_field_values('priority');
@@ -56,14 +63,13 @@
$vars->{'resolution'} = get_legal_field_values('resolution');
$vars->{'status'} = get_legal_field_values('bug_status');
$vars->{'custom_fields'} =
- [ grep {$_->type == FIELD_TYPE_SINGLE_SELECT || $_->type == FIELD_TYPE_MULTI_SELECT}
- Bugzilla->active_custom_fields ];
+ [ grep {$_->is_select} Bugzilla->active_custom_fields ];
# Include a list of product objects.
if ($cgi->param('product')) {
my @products = $cgi->param('product');
foreach my $product_name (@products) {
- # We don't use check_product because config.cgi outputs mostly
+ # We don't use check() because config.cgi outputs mostly
# in XML and JS and we don't want to display an HTML error
# instead of that.
my $product = new Bugzilla::Product({ name => $product_name });
@@ -75,6 +81,18 @@
$vars->{'products'} = $user->get_selectable_products;
}
+# We set the 2nd argument to 1 to also preload flag types.
+Bugzilla::Product::preload($vars->{'products'}, 1);
+
+# Allow consumers to specify whether or not they want flag data.
+if (defined $cgi->param('flags')) {
+ $vars->{'show_flags'} = $cgi->param('flags');
+}
+else {
+ # We default to sending flag data.
+ $vars->{'show_flags'} = 1;
+}
+
# Create separate lists of open versus resolved statuses. This should really
# be made part of the configuration.
my @open_status;
@@ -89,7 +107,7 @@
# Generate a list of fields that can be queried.
my @fields = @{Bugzilla::Field->match({obsolete => 0})};
# Exclude fields the user cannot query.
-if (!Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
+if (!Bugzilla->user->is_timetracker) {
@fields = grep { $_->name !~ /^(estimated_time|remaining_time|work_time|percentage_complete|deadline)$/ } @fields;
}
$vars->{'field'} = \@fields;
@@ -108,11 +126,41 @@
my $format = $template->get_format("config", scalar($cgi->param('format')),
scalar($cgi->param('ctype')) || "js");
- # Return HTTP headers.
- print "Content-Type: $format->{'ctype'}\n\n";
-
- # Generate the configuration file and return it to the user.
- $template->process($format->{'template'}, $vars)
+ # Generate the configuration data.
+ my $output;
+ $template->process($format->{'template'}, $vars, \$output)
|| ThrowTemplateError($template->error());
+
+ # Wide characters cause md5_base64() to die.
+ my $digest_data = $output;
+ utf8::encode($digest_data) if utf8::is_utf8($digest_data);
+ my $digest = md5_base64($digest_data);
+
+ # ETag support.
+ my $if_none_match = $cgi->http('If-None-Match') || "";
+ my $found304;
+ my @if_none = split(/[\s,]+/, $if_none_match);
+ foreach my $if_none (@if_none) {
+ # remove quotes from begin and end of the string
+ $if_none =~ s/^\"//g;
+ $if_none =~ s/\"$//g;
+ if ($if_none eq $digest or $if_none eq '*') {
+ # leave the loop after the first match
+ $found304 = $if_none;
+ last;
+ }
+ }
+
+ if ($found304) {
+ print $cgi->header(-type => 'text/html',
+ -ETag => $found304,
+ -status => '304 Not Modified');
+ }
+ else {
+ # Return HTTP headers.
+ print $cgi->header (-ETag => $digest,
+ -type => $format->{'ctype'});
+ print $output;
+ }
exit;
}
diff --git a/Websites/bugs.webkit.org/contrib/README b/Websites/bugs.webkit.org/contrib/README
index 32a5b64..f4e40e4 100644
--- a/Websites/bugs.webkit.org/contrib/README
+++ b/Websites/bugs.webkit.org/contrib/README
@@ -8,19 +8,13 @@
This directory includes:
-bugzilla_ldapsync.rb -- Script that can be run via Cron that queries an LDAP
- server for e-mail addresses to add Bugzilla users
- for. Will optionally disable Bugzilla users with
- no matching LDAP record. Contributed by Thomas
- Stromberg <thomas+bugzilla@stromberg.org>.
-
bugzilla-submit/ -- A standalone bug submission program.
bzdbcopy.pl -- A script to copy data from an installation running
on one DB platform to an installation running on
another DB platform.
-bz_webservice_demo.p -- An example script that demonstrates how to talk to
+bz_webservice_demo.pl -- An example script that demonstrates how to talk to
Bugzilla via XMLRPC.
cmdline/ -- Various commands for querying your Bugzilla
@@ -30,13 +24,6 @@
from a given directory. The log is useful when
changes need to be backed out.
- gnatsparse/ -- A Python script used to import a GNATS database
- into Bugzilla.
-
- gnats2bz.pl -- A Perl script to help import bugs from a GNATS
- database into a Bugzilla database. Contributed by
- Tom Schutter <tom@platte.com>.
-
jb2bz.py -- Script to import bugs from JitterBug to Bugzilla.
merge-users.pl -- Script to merge two user accounts. The activities
@@ -68,6 +55,3 @@
missing users to Bugzilla. Can disable/update
non-existing/changed information. Contributed by
Andreas Höfler <andreas.hoefler@bearingpoint.com>.
-
- yp_nomail.sh -- Script that can be run via Cron that regularly updates
- the nomail file for terminated employees.
diff --git a/Websites/bugs.webkit.org/contrib/bugzilla-queue.rhel b/Websites/bugs.webkit.org/contrib/bugzilla-queue.rhel
new file mode 100755
index 0000000..3e00cce
--- /dev/null
+++ b/Websites/bugs.webkit.org/contrib/bugzilla-queue.rhel
@@ -0,0 +1,109 @@
+#!/bin/bash
+#
+# bugzilla-queue This starts, stops, and restarts the Bugzilla jobqueue.pl
+# daemon, which manages sending queued mail and possibly
+# other queued tasks in the future.
+#
+# chkconfig: 345 85 15
+# description: Bugzilla queue runner
+#
+### BEGIN INIT INFO
+# Provides: bugzilla-queue
+# Required-Start: $local_fs $syslog MTA mysqld
+# Required-Stop: $local_fs $syslog MTA mysqld
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 6
+# Short-Description: Start and stop the Bugzilla queue runner.
+# Description: The Bugzilla queue runner (jobqueue.pl) sends any mail
+# that Bugzilla has queued to be sent in the background. If you
+# have enabled the use_mailer_queue parameter in Bugzilla, you
+# must run this daemon.
+### END INIT INFO
+
+NAME=`basename $0`
+
+#################
+# Configuration #
+#################
+
+# This should be the path to your Bugzilla
+BUGZILLA=/var/www/html/bugzilla
+# Who owns the Bugzilla directory and files?
+USER=root
+
+# If you want to pass any options to the daemon (like -d for debugging)
+# specify it here.
+OPTIONS=""
+
+# You can also override the configuration by creating a
+# /etc/sysconfig/bugzilla-queue file so that you don't
+# have to edit this script.
+if [ -r /etc/sysconfig/$NAME ]; then
+ . /etc/sysconfig/$NAME
+fi
+
+##########
+# Script #
+##########
+
+RETVAL=0
+BIN=$BUGZILLA/jobqueue.pl
+PIDFILE=/var/run/$NAME.pid
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+usage ()
+{
+ echo "Usage: service $NAME {start|stop|status|restart|condrestart}"
+ RETVAL=1
+}
+
+
+start ()
+{
+ if [ -f "$PIDFILE" ]; then
+ checkpid `cat $PIDFILE` && return 0
+ fi
+ echo -n "Starting $NAME: "
+ touch $PIDFILE
+ chown $USER $PIDFILE
+ daemon --user=$USER \
+ "$BIN ${OPTIONS} -p '$PIDFILE' -n $NAME start > /dev/null"
+ ret=$?
+ [ $ret -eq "0" ] && touch /var/lock/subsys/$NAME
+ echo
+ return $ret
+}
+
+stop ()
+{
+ [ -f /var/lock/subsys/$NAME ] || return 0
+ echo -n "Killing $NAME: "
+ killproc $NAME
+ echo
+ rm -f /var/lock/subsys/$NAME
+}
+
+restart ()
+{
+ stop
+ start
+}
+
+condrestart ()
+{
+ [ -e /var/lock/subsys/$NAME ] && restart || return 0
+}
+
+
+case "$1" in
+ start) start; RETVAL=$? ;;
+ stop) stop; RETVAL=$? ;;
+ status) $BIN -p $PIDFILE -n $NAME check; RETVAL=$?;;
+ restart) restart; RETVAL=$? ;;
+ condrestart) condrestart; RETVAL=$? ;;
+ *) usage ; RETVAL=2 ;;
+esac
+
+exit $RETVAL
diff --git a/Websites/bugs.webkit.org/contrib/bugzilla-queue.suse b/Websites/bugs.webkit.org/contrib/bugzilla-queue.suse
new file mode 100755
index 0000000..3563020
--- /dev/null
+++ b/Websites/bugs.webkit.org/contrib/bugzilla-queue.suse
@@ -0,0 +1,174 @@
+#!/bin/bash
+#
+# bugzilla-queue This starts, stops, and restarts the Bugzilla jobqueue.pl
+# daemon, which manages sending queued mail and possibly
+# other queued tasks in the future.
+#
+# chkconfig: 345 85 15
+# description: Bugzilla queue runner
+#
+### BEGIN INIT INFO
+# Provides: bugzilla-queue
+# Required-Start: $local_fs $syslog
+# Required-Stop: $local_fs $syslog
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 6
+# Short-Description: Start and stop the Bugzilla queue runner.
+# Description: The Bugzilla queue runner (jobqueue.pl) sends any mail
+# that Bugzilla has queued to be sent in the background. If you
+# have enabled the use_mailer_queue parameter in Bugzilla, you
+# must run this daemon.
+### END INIT INFO
+
+NAME=`basename $0`
+
+#################
+# Configuration #
+#################
+
+# This should be the path to your Bugzilla
+BUGZILLA=/var/www/html/bugzilla
+# Who owns the Bugzilla directory and files?
+USER=root
+
+# If you want to pass any options to the daemon (like -d for debugging)
+# specify it here.
+OPTIONS=""
+
+# You can also override the configuration by creating a
+# /etc/sysconfig/bugzilla-queue file so that you don't
+# have to edit this script.
+if [ -r /etc/sysconfig/$NAME ]; then
+ . /etc/sysconfig/$NAME
+fi
+
+##########
+# Script #
+##########
+
+BIN=$BUGZILLA/jobqueue.pl
+if [ ! -x $BIN ]; then
+ echo "$BIN not installed"
+ if [ "$1" = "stop" ]; then
+ exit 0
+ else
+ exit 5
+ fi
+fi
+
+# Source LSB function library.
+. /lib/lsb/init-functions
+
+# Reset status of this service.
+rc_reset
+
+# Return values for all commands but status:
+# 0 - success
+# 1 - generic or unspecified error
+# 2 - invalid or excess argument(s)
+# 3 - unimplemented feature (e.g. "reload")
+# 4 - user had insufficient privileges
+# 5 - program is not installed
+# 6 - program is not configured
+# 7 - program is not running
+# 8--199 - reserved (8--99 LSB, 100--149 distrib, 150--199 appl)
+#
+# Note that starting an already running service, stopping
+# or restarting a not-running service as well as the restart
+# with force-reload (in case signaling is not supported) are
+# considered a success.
+
+case "$1" in
+ start)
+ echo -n "Starting $NAME "
+ # Start daemon with startproc(8). If this fails the return value
+ # is set appropriately by startproc.
+ start_daemon -u $USER $BIN ${OPTIONS} start
+
+ # Remember status and be verbose
+ rc_status -v
+ ;;
+
+ stop)
+ echo -n "Shutting down $NAME "
+ # Stop daemon with killproc(8) and if this fails killproc sets the
+ # return value according to LSB.
+ killproc -TERM $BIN
+
+ # Remember status and be verbose
+ rc_status -v
+ ;;
+
+ status)
+ echo -n "Checking for service $NAME "
+ # Check status with checkproc(8), if process is running checkproc
+ # will return with exit status 0.
+
+ # Return value is slightly different for the status command:
+ # 0 - service up and running
+ # 1 - service dead, but /var/run/ pid file exists
+ # 2 - service dead, but /var/lock/ lock file exists
+ # 3 - service not running (unused)
+ # 4 - service status unknown :-(
+ # 5--199 reserved (5--99 LSB, 100--149 distro, 150--199 appl.)
+
+ # NOTE: checkproc returns LSB compliant status values.
+ checkproc $BIN
+
+ # NOTE: rc_status knows that we called this init script with
+ # "status" option and adapts its messages accordingly.
+ rc_status -v
+
+ # Run jobqueue's own check function too.
+ $BIN check
+ ;;
+
+ restart)
+ # Stop the service and regardless of whether it was running or not,
+ # start it again.
+ $0 stop
+ $0 start
+
+ # Remember status and be quiet.
+ rc_status
+ ;;
+
+ try-restart|condrestart)
+ # Do a restart only if the service was active before.
+ # NOTE: try-restart is now part of LSB (as of 1.9). RH has a
+ # similar command named condrestart.
+ if [ "$1" = "condrestart" ]; then
+ echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}"
+ fi
+ $0 status
+ if [ $? -eq 0 ]; then
+ $0 restart
+ else
+ rc_reset # Not running is not a failure.
+ fi
+
+ # Remember status and be quiet
+ rc_status
+ ;;
+
+ force-reload)
+ # The jobqueue.pl daemon does not support SIGHUP for reload. Just
+ # restart the service if it is running.
+ echo -n "Reload service $NAME "
+
+ $0 try-restart
+ rc_status
+ ;;
+
+ reload)
+ # The jobqueue.pl daemon does not support SIGHUP for reload.
+ rc_failed 3
+ rc_status -v
+ ;;
+
+ *)
+ echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
+ exit 1
+esac
+
+rc_exit
diff --git a/Websites/bugs.webkit.org/contrib/bugzilla-submit/bugdata.txt b/Websites/bugs.webkit.org/contrib/bugzilla-submit/bugdata.txt
old mode 100644
new mode 100755
diff --git a/Websites/bugs.webkit.org/contrib/bugzilla-submit/bugzilla-submit b/Websites/bugs.webkit.org/contrib/bugzilla-submit/bugzilla-submit
index 47c94b2..d98e7de 100755
--- a/Websites/bugs.webkit.org/contrib/bugzilla-submit/bugzilla-submit
+++ b/Websites/bugs.webkit.org/contrib/bugzilla-submit/bugzilla-submit
@@ -152,13 +152,13 @@
if 'rep_platform' not in data:
data['rep_platform'] = 'PC'
if 'bug_status' not in data:
- data['bug_status'] = 'NEW'
+ data['bug_status'] = 'CONFIRMED'
if 'bug_severity' not in data:
data['bug_severity'] = 'normal'
if 'bug_file_loc' not in data:
data['bug_file_loc'] = 'http://' # Yes, Bugzilla needs this
if 'priority' not in data:
- data['priority'] = 'P2'
+ data['priority'] = 'Normal'
def validate_fields(data):
# Fields for validation
diff --git a/Websites/bugs.webkit.org/contrib/bugzilla-submit/bugzilla-submit.xml b/Websites/bugs.webkit.org/contrib/bugzilla-submit/bugzilla-submit.xml
old mode 100644
new mode 100755
diff --git a/Websites/bugs.webkit.org/contrib/bugzilla_ldapsync.rb b/Websites/bugs.webkit.org/contrib/bugzilla_ldapsync.rb
deleted file mode 100755
index 1635301..0000000
--- a/Websites/bugs.webkit.org/contrib/bugzilla_ldapsync.rb
+++ /dev/null
@@ -1,185 +0,0 @@
-#!/usr/local/bin/ruby
-
-# Queries an LDAP server for all email addresses (tested against Exchange 5.5),
-# and makes nice bugzilla user entries out of them. Also disables Bugzilla users
-# that are not found in LDAP.
-
-# $Id$
-
-require 'ldap'
-require 'dbi'
-require 'getoptlong'
-
-opts = GetoptLong.new(
- ['--dbname', '-d', GetoptLong::OPTIONAL_ARGUMENT],
- ['--dbpassword', '-p', GetoptLong::OPTIONAL_ARGUMENT],
- ['--dbuser', '-u', GetoptLong::OPTIONAL_ARGUMENT],
- ['--dbpassfile', '-P', GetoptLong::OPTIONAL_ARGUMENT],
- ['--ldaphost', '-h', GetoptLong::REQUIRED_ARGUMENT],
- ['--ldapbase', '-b', GetoptLong::OPTIONAL_ARGUMENT],
- ['--ldapquery', '-q', GetoptLong::OPTIONAL_ARGUMENT],
- ['--maildomain', '-m', GetoptLong::OPTIONAL_ARGUMENT],
- ['--noremove', '-n', GetoptLong::OPTIONAL_ARGUMENT],
- ['--defaultpass', '-D', GetoptLong::OPTIONAL_ARGUMENT],
- ['--checkmode', '-c', GetoptLong::OPTIONAL_ARGUMENT]
-)
-
-
-# in hash to make it easy
-optHash = Hash.new
-opts.each do |opt, arg|
- optHash[opt]=arg
-end
-
-# grab password from file if it's an option
-if optHash['--dbpassfile']
- dbPassword=File.open(optHash['--dbpassfile'], 'r').readlines[0].chomp!
-else
- dbPassword=optHash['--dbpassword'] || nil
-end
-
-# make bad assumptions.
-dbName = optHash['--dbname'] || 'bugzilla'
-dbUser = optHash['--dbuser'] || 'bugzilla'
-ldapHost = optHash['--ldaphost'] || 'ldap'
-ldapBase = optHash['--ldapbase'] || ''
-mailDomain = optHash['--maildomain'] || `domainname`.chomp!
-ldapQuery = optHash['--ldapquery'] || "(&(objectclass=person)(rfc822Mailbox=*@#{mailDomain}))"
-checkMode = optHash['--checkmode'] || nil
-noRemove = optHash['--noremove'] || nil
-defaultPass = optHash['--defaultpass'] || 'bugzilla'
-
-if (! dbPassword)
- puts "bugzilla_ldapsync v1.3 (c) 2003 Thomas Stromberg <thomas+bugzilla@stromberg.org>"
- puts ""
- puts " -d | --dbname name of MySQL database [#{dbName}]"
- puts " -u | --dbuser username for MySQL database [#{dbUser}]"
- puts " -p | --dbpassword password for MySQL user [#{dbPassword}]"
- puts " -P | --dbpassfile filename containing password for MySQL user"
- puts " -h | --ldaphost hostname for LDAP server [#{ldapHost}]"
- puts " -b | --ldapbase Base of LDAP query, for instance, o=Bugzilla.com"
- puts " -q | --ldapquery LDAP query, uses maildomain [#{ldapQuery}]"
- puts " -m | --maildomain e-mail domain to use records from"
- puts " -n | --noremove do not remove Bugzilla users that are not in LDAP"
- puts " -c | --checkmode checkmode, does not perform any SQL changes"
- puts " -D | --defaultpass default password for new users [#{defaultPass}]"
- puts
- puts "example:"
- puts
- puts " bugzilla_ldapsync.rb -c -u taskzilla -P /tmp/test -d taskzilla -h bhncmail -m \"bowebellhowell.com\""
- exit
-end
-
-
-if (checkMode)
- puts '(checkmode enabled, no SQL writes will actually happen)'
- puts "ldapquery is #{ldapQuery}"
- puts
-end
-
-
-bugzillaUsers = Hash.new
-ldapUsers = Hash.new
-encPassword = defaultPass.crypt('xx')
-sqlNewUser = "INSERT INTO profiles VALUES ('', ?, '#{encPassword}', ?, '', 1, NULL, '0000-00-00 00:00:00');"
-
-# presumes that the MySQL database is local.
-dbh = DBI.connect("DBI:Mysql:#{dbName}", dbUser, dbPassword)
-
-# select all e-mail addresses where there is no disabledtext defined. Only valid users, please!
-dbh.select_all('select login_name, realname, disabledtext from profiles') { |row|
- login = row[0].downcase
- bugzillaUsers[login] = Hash.new
- bugzillaUsers[login]['desc'] = row[1]
- bugzillaUsers[login]['disabled'] = row[2]
- #puts "bugzilla has #{login} - \"#{bugzillaUsers[login]['desc']}\" (#{bugzillaUsers[login]['disabled']})"
-}
-
-
-LDAP::Conn.new(ldapHost, 389).bind{|conn|
- sub = nil
- # perform the query, but only get the e-mail address, location, and name returned to us.
- conn.search(ldapBase, LDAP::LDAP_SCOPE_SUBTREE, ldapQuery,
- ['rfc822Mailbox', 'physicalDeliveryOfficeName', 'cn']) { |entry|
-
- # Get the users first (primary) e-mail address, but I only want what's before the @ sign.
- entry.vals("rfc822Mailbox")[0] =~ /([\w\.-]+)\@/
- email = $1
-
- # We put the officename in the users description, and nothing otherwise.
- if entry.vals("physicalDeliveryOfficeName")
- location = entry.vals("physicalDeliveryOfficeName")[0]
- else
- location = ''
- end
-
- # for whatever reason, we get blank entries. Do some double checking here.
- if (email && (email.length > 4) && (location !~ /Generic/) && (entry.vals("cn")))
- if (location.length > 2)
- desc = entry.vals("cn")[0] + " (" + location + ")"
- else
- desc = entry.vals("cn")[0]
- end
-
- # take care of the whitespace.
- desc.sub!("\s+$", "")
- desc.sub!("^\s+", "")
-
- # dumb hack. should be properly escaped, and apostrophes should never ever ever be in email.
- email.sub!("\'", "%")
- email.sub!('%', "\'")
- email=email.downcase
- ldapUsers[email.downcase] = Hash.new
- ldapUsers[email.downcase]['desc'] = desc
- ldapUsers[email.downcase]['disabled'] = nil
- #puts "ldap has #{email} - #{ldapUsers[email.downcase]['desc']}"
- end
- }
-}
-
-# This is the loop that takes the users that we found in Bugzilla originally, and
-# checks to see if they are still in the LDAP server. If they are not, away they go!
-
-ldapUsers.each_key { |user|
- # user does not exist at all.
- #puts "checking ldap user #{user}"
- if (! bugzillaUsers[user])
- puts "+ Adding #{user} - #{ldapUsers[user]['desc']}"
-
- if (! checkMode)
- dbh.do(sqlNewUser, user, ldapUsers[user]['desc'])
- end
-
- # short-circuit now.
- next
- end
-
- if (bugzillaUsers[user]['desc'] != ldapUsers[user]['desc'])
- puts "* Changing #{user} from \"#{bugzillaUsers[user]['desc']}\" to \"#{ldapUsers[user]['desc']}\""
- if (! checkMode)
- # not efficient.
- dbh.do("UPDATE profiles SET realname = ? WHERE login_name = ?", ldapUsers[user]['desc'], user)
- end
- end
-
- if (bugzillaUsers[user]['disabled'].length > 0)
- puts "+ Enabling #{user} (was \"#{bugzillaUsers[user]['disabled']}\")"
- if (! checkMode)
- dbh.do("UPDATE profiles SET disabledtext = NULL WHERE login_name=\"#{user}\"")
- end
- end
-}
-
-if (! noRemove)
- bugzillaUsers.each_key { |user|
- if ((bugzillaUsers[user]['disabled'].length < 1) && (! ldapUsers[user]))
- puts "- Disabling #{user} (#{bugzillaUsers[user]['disabled']})"
-
- if (! checkMode)
- dbh.do("UPDATE profiles SET disabledtext = \'auto-disabled by ldap sync\' WHERE login_name=\"#{user}\"")
- end
- end
- }
-end
-dbh.disconnect
-
diff --git a/Websites/bugs.webkit.org/contrib/bz_webservice_demo.pl b/Websites/bugs.webkit.org/contrib/bz_webservice_demo.pl
index 8afe940..ee32c77 100755
--- a/Websites/bugs.webkit.org/contrib/bz_webservice_demo.pl
+++ b/Websites/bugs.webkit.org/contrib/bz_webservice_demo.pl
@@ -210,7 +210,7 @@
_die_on_fault($soapresult);
my $extensions = $soapresult->result()->{extensions};
foreach my $extensionname (keys(%$extensions)) {
- print "Extensionn '$extensionname' information\n";
+ print "Extension '$extensionname' information\n";
my $extension = $extensions->{$extensionname};
foreach my $data (keys(%$extension)) {
print ' ' . $data . ' => ' . $extension->{$data} . "\n";
@@ -390,7 +390,7 @@
version => "unspecified",
description => "This is a description of the bug... hohoho",
op_sys => "All",
- platform => "All",
+ platform => "All",
priority => "P4",
severity => "normal"
};
diff --git a/Websites/bugs.webkit.org/contrib/bzdbcopy.pl b/Websites/bugs.webkit.org/contrib/bzdbcopy.pl
index 4eb5a8c..08e2564 100755
--- a/Websites/bugs.webkit.org/contrib/bzdbcopy.pl
+++ b/Websites/bugs.webkit.org/contrib/bzdbcopy.pl
@@ -48,15 +48,18 @@
# MAIN SCRIPT
#####################################################################
-Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
-
print "Connecting to the '" . SOURCE_DB_NAME . "' source database on "
. SOURCE_DB_TYPE . "...\n";
-my $source_db = Bugzilla::DB::_connect(SOURCE_DB_TYPE, SOURCE_DB_HOST,
- SOURCE_DB_NAME, undef, undef, SOURCE_DB_USER, SOURCE_DB_PASSWORD);
+my $source_db = Bugzilla::DB::_connect({
+ db_driver => SOURCE_DB_TYPE,
+ db_host => SOURCE_DB_HOST,
+ db_name => SOURCE_DB_NAME,
+ db_user => SOURCE_DB_USER,
+ db_pass => SOURCE_DB_PASSWORD,
+});
# Don't read entire tables into memory.
if (SOURCE_DB_TYPE eq 'Mysql') {
- $source_db->{'mysql_use_result'}=1;
+ $source_db->{'mysql_use_result'} = 1;
# MySQL cannot have two queries running at the same time. Ensure the schema
# is loaded from the database so bz_column_info will not execute a query
@@ -65,19 +68,22 @@
print "Connecting to the '" . TARGET_DB_NAME . "' target database on "
. TARGET_DB_TYPE . "...\n";
-my $target_db = Bugzilla::DB::_connect(TARGET_DB_TYPE, TARGET_DB_HOST,
- TARGET_DB_NAME, undef, undef, TARGET_DB_USER, TARGET_DB_PASSWORD);
+my $target_db = Bugzilla::DB::_connect({
+ db_driver => TARGET_DB_TYPE,
+ db_host => TARGET_DB_HOST,
+ db_name => TARGET_DB_NAME,
+ db_user => TARGET_DB_USER,
+ db_pass => TARGET_DB_PASSWORD,
+});
my $ident_char = $target_db->get_info( 29 ); # SQL_IDENTIFIER_QUOTE_CHAR
# We use the table list from the target DB, because if somebody
# has customized their source DB, we still want the script to work,
# and it may otherwise fail in that situation (that is, the tables
# may not exist in the target DB).
-my @table_list = $target_db->bz_table_list_real();
-
-# We don't want to copy over the bz_schema table's contents.
-my $bz_schema_location = lsearch(\@table_list, 'bz_schema');
-splice(@table_list, $bz_schema_location, 1) if $bz_schema_location > 0;
+#
+# We don't want to copy over the bz_schema table's contents, though.
+my @table_list = grep { $_ ne 'bz_schema' } $target_db->bz_table_list_real();
# Instead of figuring out some fancy algorithm to insert data in the right
# order and not break FK integrity, we just drop them all.
@@ -195,8 +201,7 @@
# PostgreSQL doesn't like it when you insert values into
# a serial field; it doesn't increment the counter
# automatically.
- $target_db->do("SELECT pg_catalog.setval
- ('${table}_${column}_seq', $max_val, false)");
+ $target_db->bz_set_next_serial_value($table, $column);
}
elsif ($target_db->isa('Bugzilla::DB::Oracle')) {
# Oracle increments the counter on every insert, and *always*
diff --git a/Websites/bugs.webkit.org/contrib/cmdline/query.conf b/Websites/bugs.webkit.org/contrib/cmdline/query.conf
old mode 100644
new mode 100755
diff --git a/Websites/bugs.webkit.org/contrib/console.pl b/Websites/bugs.webkit.org/contrib/console.pl
new file mode 100755
index 0000000..efd3b63
--- /dev/null
+++ b/Websites/bugs.webkit.org/contrib/console.pl
@@ -0,0 +1,186 @@
+#!/usr/bin/env perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is the National Aeronautics
+# and Space Administration of the United States Government.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s): Jesse Clark <jjclark1982@gmail.com>
+
+use File::Basename;
+BEGIN { chdir dirname($0) . "/.."; }
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Bug;
+
+use Term::ReadLine;
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+$Data::Dumper::Terse = 1;
+$Data::Dumper::Indent = 1;
+$Data::Dumper::Useqq = 1;
+$Data::Dumper::Maxdepth = 1;
+$Data::Dumper::Deparse = 0;
+
+my $sysname = get_text('term', {term => 'Bugzilla'});
+my $term = new Term::ReadLine "$sysname Console";
+read_history($term);
+END { write_history($term) }
+
+while ( defined (my $input = $term->readline("$sysname> ")) ) {
+ my @res = eval($input);
+ if ($@) {
+ warn $@;
+ }
+ else {
+ print Dumper(@res);
+ }
+}
+print STDERR "\n";
+exit 0;
+
+# d: full dump (normal behavior is limited to depth of 1)
+sub d {
+ local $Data::Dumper::Maxdepth = 0;
+ local $Data::Dumper::Deparse = 1;
+ print Dumper(@_);
+ return ();
+}
+
+# p: print as a single string (normal behavior puts list items on separate lines)
+sub p {
+ local $^W=0; # suppress possible undefined var message
+ print(@_, "\n");
+ return ();
+}
+
+sub filter {
+ my $name = shift;
+ my $filter = Bugzilla->template->{SERVICE}->{CONTEXT}->{CONFIG}->{FILTERS}->{$name};
+ if (scalar @_) {
+ return $filter->(@_);
+ }
+ else {
+ return $filter;
+ }
+}
+
+sub b { get_object('Bugzilla::Bug', @_) }
+sub u { get_object('Bugzilla::User', @_) }
+sub f { get_object('Bugzilla::Field', @_) }
+
+sub get_object {
+ my $class = shift;
+ $_ = shift;
+ my @results = ();
+
+ if (ref $_ eq 'HASH' && keys %$_) {
+ @results = @{$class->match($_)};
+ }
+ elsif (m/^\d+$/) {
+ @results = ($class->new($_));
+ }
+ elsif (m/\w/i && grep {$_ eq 'name'} ($class->_get_db_columns)) {
+ @results = @{$class->match({name => $_})};
+ }
+ else {
+ @results = ();
+ }
+
+ if (wantarray) {
+ return @results;
+ }
+ else {
+ return shift @results;
+ }
+}
+
+sub read_history {
+ my ($term) = @_;
+
+ if (open HIST, "<$ENV{HOME}/.bugzilla_console_history") {
+ foreach (<HIST>) {
+ chomp;
+ $term->addhistory($_);
+ }
+ close HIST;
+ }
+}
+
+sub write_history {
+ my ($term) = @_;
+
+ if ($term->can('GetHistory') && open HIST, ">$ENV{HOME}/.bugzilla_console_history") {
+ my %seen_hist = ();
+ my @hist = ();
+ foreach my $line (reverse $term->GetHistory()) {
+ next unless $line =~ m/\S/;
+ next if $seen_hist{$line};
+ $seen_hist{$line} = 1;
+ push @hist, $line;
+ last if (scalar @hist > 500);
+ }
+ foreach (reverse @hist) {
+ print HIST $_, "\n";
+ }
+ close HIST;
+ }
+}
+
+__END__
+
+=head1 NAME
+
+B<console.pl> - command-line interface to Bugzilla API
+
+=head1 SYNOPSIS
+
+$ B<contrib/console.pl>
+
+Bugzilla> B<b(5)-E<gt>short_desc>
+
+=over 8
+
+"Misplaced Widget"
+
+=back
+
+Bugzilla> B<$f = f "cf_subsystem"; scalar @{$f-E<gt>legal_values}>
+
+=over 8
+
+177
+
+=back
+
+Bugzilla> B<p filter html_light, "1 E<lt> 2 E<lt>bE<gt>3E<lt>/bE<gt>">
+
+=over 8
+
+1 < 2 E<lt>bE<gt>3E<lt>/bE<gt>
+
+=back
+
+Bugzilla> B<$u = u 5; $u-E<gt>groups; d $u>
+
+=head1 DESCRIPTION
+
+Loads Bugzilla packages and prints expressions with Data::Dumper.
+Useful for checking results of Bugzilla API calls without opening
+a debug file from a cgi.
diff --git a/Websites/bugs.webkit.org/contrib/convert-workflow.pl b/Websites/bugs.webkit.org/contrib/convert-workflow.pl
new file mode 100755
index 0000000..bd19285
--- /dev/null
+++ b/Websites/bugs.webkit.org/contrib/convert-workflow.pl
@@ -0,0 +1,135 @@
+#!/usr/bin/env perl -w
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use warnings;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Config qw(:admin);
+use Bugzilla::Search::Saved;
+use Bugzilla::Status;
+use Getopt::Long;
+
+my $confirmed = new Bugzilla::Status({ name => 'CONFIRMED' });
+my $in_progress = new Bugzilla::Status({ name => 'IN_PROGRESS' });
+
+if ($confirmed and $in_progress) {
+ print "You are already using the new workflow.\n";
+ exit 1;
+}
+my $enable_unconfirmed = 0;
+my $result = GetOptions("enable-unconfirmed" => \$enable_unconfirmed);
+
+print <<END;
+WARNING: This will convert the status of all bugs using the following
+system:
+
+ "NEW" will become "CONFIRMED"
+ "ASSIGNED" will become "IN_PROGRESS"
+ "REOPENED" will become "CONFIRMED" (and the "REOPENED" status will be removed)
+ "CLOSED" will become "VERIFIED" (and the "CLOSED" status will be removed)
+
+This change will be immediate. The history of each bug will also be changed
+so that it appears that these statuses were always in existence.
+
+Emails will not be sent for the change.
+
+END
+if ($enable_unconfirmed) {
+ print "UNCONFIRMED will be enabled in all products.\n";
+} else {
+ print <<END;
+If you also want to enable the UNCONFIRMED status in every product,
+restart this script with the --enable-unconfirmed option.
+END
+}
+print "\nTo continue, press any key, or press Ctrl-C to stop this program...";
+getc;
+
+my $dbh = Bugzilla->dbh;
+# This is an array instead of a hash so that we can be sure that
+# the translation happens in the right order. In particular, we
+# want NEW to be renamed to CONFIRMED, instead of having REOPENED
+# be the one that gets renamed.
+my @translation = (
+ [NEW => 'CONFIRMED'],
+ [ASSIGNED => 'IN_PROGRESS'],
+ [REOPENED => 'CONFIRMED'],
+ [CLOSED => 'VERIFIED'],
+);
+
+my $status_field = Bugzilla::Field->check('bug_status');
+$dbh->bz_start_transaction();
+foreach my $pair (@translation) {
+ my ($from, $to) = @$pair;
+ print "Converting $from to $to...\n";
+ $dbh->do('UPDATE bugs SET bug_status = ? WHERE bug_status = ?',
+ undef, $to, $from);
+
+ if (Bugzilla->params->{'duplicate_or_move_bug_status'} eq $from) {
+ SetParam('duplicate_or_move_bug_status', $to);
+ write_params();
+ }
+
+ foreach my $what (qw(added removed)) {
+ $dbh->do("UPDATE bugs_activity SET $what = ?
+ WHERE fieldid = ? AND $what = ?",
+ undef, $to, $status_field->id, $from);
+ }
+
+ # Delete any transitions where it now appears that
+ # a bug moved from a status to itself.
+ $dbh->do('DELETE FROM bugs_activity WHERE fieldid = ? AND added = removed',
+ undef, $status_field->id);
+
+ # If the new status already exists, just delete the old one, but retain
+ # the workflow items from it.
+ if (my $existing = new Bugzilla::Status({ name => $to })) {
+ $dbh->do('DELETE FROM bug_status WHERE value = ?', undef, $from);
+ }
+ # Otherwise, rename the old status to the new one.
+ else {
+ $dbh->do('UPDATE bug_status SET value = ? WHERE value = ?',
+ undef, $to, $from);
+ }
+
+ Bugzilla::Search::Saved->rename_field_value('bug_status', $from, $to);
+ Bugzilla::Series->Bugzilla::Search::Saved::rename_field_value('bug_status',
+ $from, $to);
+}
+if ($enable_unconfirmed) {
+ print "Enabling UNCONFIRMED in all products...\n";
+ $dbh->do('UPDATE products SET allows_unconfirmed = 1');
+}
+$dbh->bz_commit_transaction();
+
+print <<END;
+Done. There are some things you may want to fix, now:
+
+ * You may want to run ./collectstats.pl --regenerate to regenerate
+ data for the Old Charts system.
+ * You may have to fix the Status Workflow using the Status Workflow
+ panel in "Administration".
+ * You will probably want to update the "mybugstemplate" and "defaultquery"
+ parameters using the Parameters panel in "Administration". (Just
+ resetting them to the default will work.)
+END
diff --git a/Websites/bugs.webkit.org/contrib/cvs-update.pl b/Websites/bugs.webkit.org/contrib/cvs-update.pl
old mode 100644
new mode 100755
diff --git a/Websites/bugs.webkit.org/contrib/extension-convert.pl b/Websites/bugs.webkit.org/contrib/extension-convert.pl
new file mode 100755
index 0000000..e779770
--- /dev/null
+++ b/Websites/bugs.webkit.org/contrib/extension-convert.pl
@@ -0,0 +1,303 @@
+#!/usr/bin/env perl -w
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use warnings;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util qw(trim);
+
+use File::Basename;
+use File::Copy qw(move);
+use File::Find;
+use File::Path qw(mkpath rmtree);
+
+my $from = $ARGV[0]
+ or die <<END;
+You must specify the name of the extension you are converting from,
+as the first argument.
+END
+my $extension_name = ucfirst($from);
+
+my $extdir = bz_locations()->{'extensionsdir'};
+
+my $from_dir = "$extdir/$from";
+if (!-d $from_dir) {
+ die "$from_dir does not exist.\n";
+}
+
+my $to_dir = "$extdir/$extension_name";
+if (-d $to_dir) {
+ die "$to_dir already exists, not converting.\n";
+}
+
+if (ON_WINDOWS) {
+ # There's no easy way to recursively copy a directory on Windows.
+ print "WARNING: This will modify the contents of $from_dir.\n",
+ "Press Ctrl-C to stop or any other key to continue...\n";
+ getc;
+ move($from_dir, $to_dir)
+ || die "rename of $from_dir to $to_dir failed: $!";
+}
+else {
+ print "Copying $from_dir to $to_dir...\n";
+ system("cp", "-r", $from_dir, $to_dir);
+}
+
+# Make sure we don't accidentally modify the $from_dir anywhere else
+# in this script.
+undef $from_dir;
+
+if (!-d $to_dir) {
+ die "$to_dir was not created.\n";
+}
+
+my $version = get_version($to_dir);
+move_template_hooks($to_dir);
+rename_module_packages($to_dir, $extension_name);
+my $install_requirements = get_install_requirements($to_dir);
+my ($modules, $subs) = code_files_to_subroutines($to_dir);
+
+my $config_pm = <<END;
+package Bugzilla::Extension::$extension_name;
+use strict;
+use constant NAME => '$extension_name';
+$install_requirements
+__PACKAGE__->NAME;
+END
+
+my $extension_pm = <<END;
+package Bugzilla::Extension::$extension_name;
+use strict;
+use base qw(Bugzilla::Extension);
+
+$modules
+
+our \$VERSION = '$version';
+
+$subs
+
+__PACKAGE__->NAME;
+END
+
+open(my $config_fh, '>', "$to_dir/Config.pm") || die "$to_dir/Config.pm: $!";
+print $config_fh $config_pm;
+close($config_fh);
+open(my $extension_fh, '>', "$to_dir/Extension.pm")
+ || die "$to_dir/Extension.pm: $!";
+print $extension_fh $extension_pm;
+close($extension_fh);
+
+rmtree("$to_dir/code");
+unlink("$to_dir/info.pl");
+
+###############
+# Subroutines #
+###############
+
+sub rename_module_packages {
+ my ($dir, $name) = @_;
+ my $lib_dir = "$dir/lib";
+
+ # We don't want things like Bugzilla::Extension::Testopia::Testopia.
+ if (-d "$lib_dir/$name") {
+ print "Moving contents of $lib_dir/$name into $lib_dir...\n";
+ foreach my $file (glob("$lib_dir/$name/*")) {
+ my $dirname = dirname($file);
+ my $basename = basename($file);
+ rename($file, "$dirname/../$basename") || warn "$file: $!\n";
+ }
+ }
+
+ my @modules;
+ find({ wanted => sub { $_ =~ /\.pm$/i and push(@modules, $_) },
+ no_chdir => 1 }, $lib_dir);
+ my %module_rename;
+ foreach my $file (@modules) {
+ open(my $fh, '<', $file) || die "$file: $!";
+ my $content = do { local $/ = undef; <$fh> };
+ close($fh);
+ if ($content =~ /^package (\S+);/m) {
+ my $package = $1;
+ my $new_name = $file;
+ $new_name =~ s/^$lib_dir\///;
+ $new_name =~ s/\.pm$//;
+ $new_name = join('::', File::Spec->splitdir($new_name));
+ $new_name = "Bugzilla::Extension::${name}::$new_name";
+ print "Renaming $package to $new_name...\n";
+ $content =~ s/^package \Q$package\E;/package \Q$new_name\E;/;
+ open(my $write_fh, '>', $file) || die "$file: $!";
+ print $write_fh $content;
+ close($write_fh);
+ $module_rename{$package} = $new_name;
+ }
+ }
+
+ print "Renaming module names inside of library and code files...\n";
+ my @code_files = glob("$dir/code/*.pl");
+ rename_modules_internally(\%module_rename, [@modules, @code_files]);
+}
+
+sub rename_modules_internally {
+ my ($rename, $files) = @_;
+
+ # We can't use \b because :: matches \b.
+ my $break = qr/^|[^\w:]|$/;
+ foreach my $file (@$files) {
+ open(my $fh, '<', $file) || die "$file: $!";
+ my $content = do { local $/ = undef; <$fh> };
+ close($fh);
+ foreach my $old_name (keys %$rename) {
+ my $new_name = $rename->{$old_name};
+ $content =~ s/($break)\Q$old_name\E($break)/$1$new_name$2/gms;
+ }
+ open(my $write_fh, '>', $file) || die "$file: $!";
+ print $write_fh $content;
+ close($write_fh);
+ }
+}
+
+sub get_version {
+ my ($dir) = @_;
+ print "Getting version info from info.pl...\n";
+ my $info;
+ {
+ local @INC = ("$dir/lib", @INC);
+ $info = do "$dir/info.pl"; die $@ if $@;
+ }
+ return $info->{version};
+}
+
+sub get_install_requirements {
+ my ($dir) = @_;
+ my $file = "$dir/code/install-requirements.pl";
+ return '' if !-f $file;
+
+ print "Moving install-requirements.pl code into Config.pm...\n";
+ my ($modules, $code) = process_code_file($file);
+ $modules = join('', @$modules);
+ $code = join('', @$code);
+ if ($modules) {
+ return "$modules\n\n$code";
+ }
+ return $code;
+}
+
+sub process_code_file {
+ my ($file) = @_;
+ open(my $fh, '<', $file) || die "$file: $!";
+ my $stuff_started;
+ my (@modules, @code);
+ foreach my $line (<$fh>) {
+ $stuff_started = 1 if $line !~ /^#/;
+ next if !$stuff_started;
+ next if $line =~ /^use (warnings|strict|lib|Bugzilla)[^\w:]/;
+ if ($line =~ /^(?:use|require)\b/) {
+ push(@modules, $line);
+ }
+ else {
+ push(@code, $line);
+ }
+ }
+ close $fh;
+ return (\@modules, \@code);
+}
+
+sub code_files_to_subroutines {
+ my ($dir) = @_;
+
+ my @dir_files = glob("$dir/code/*.pl");
+ my (@all_modules, @subroutines);
+ foreach my $file (@dir_files) {
+ next if $file =~ /install-requirements/;
+ print "Moving $file code into Extension.pm...\n";
+ my ($modules, $code) = process_code_file($file);
+ my @code_lines = map { " $_" } @$code;
+ my $code_string = join('', @code_lines);
+ $code_string =~ s/Bugzilla->hook_args/\$args/g;
+ $code_string =~ s/my\s+\$args\s+=\s+\$args;//gs;
+ chomp($code_string);
+ push(@all_modules, @$modules);
+ my $name = basename($file);
+ $name =~ s/-/_/;
+ $name =~ s/\.pl$//;
+
+ my $subroutine = <<END;
+sub $name {
+ my (\$self, \$args) = \@_;
+$code_string
+}
+END
+ push(@subroutines, $subroutine);
+ }
+
+ my %seen_modules = map { trim($_) => 1 } @all_modules;
+ my $module_string = join("\n", sort keys %seen_modules);
+ my $subroutine_string = join("\n", @subroutines);
+ return ($module_string, $subroutine_string);
+}
+
+sub move_template_hooks {
+ my ($dir) = @_;
+ foreach my $lang (glob("$dir/template/*")) {
+ next if !_file_matters($lang);
+ my $hook_container = "$lang/default/hook";
+ mkpath($hook_container) || warn "$hook_container: $!";
+ # Hooks can be in all sorts of weird places, including
+ # template/default/hook.
+ foreach my $file (glob("$lang/*")) {
+ next if !_file_matters($file, 1);
+ my $dirname = basename($file);
+ print "Moving $file to $hook_container/$dirname...\n";
+ rename($file, "$hook_container/$dirname") || die "move failed: $!";
+ }
+ }
+}
+
+sub _file_matters {
+ my ($path, $tmpl) = @_;
+ my @ignore = qw(default custom CVS);
+ my $file = basename($path);
+ return 0 if grep(lc($_) eq lc($file), @ignore);
+ # Hidden files
+ return 0 if $file =~ /^\./;
+ if ($tmpl) {
+ return 1 if $file =~ /\.tmpl$/;
+ }
+ return 0 if !-d $path;
+ return 1;
+}
+
+__END__
+
+=head1 NAME
+
+extension-convert.pl - Convert extensions from the pre-3.6 format to the
+3.6 format.
+
+=head1 SYNOPSIS
+
+ contrib/extension-convert.pl name
+
+ Converts an extension in the F<extensions/> directory into the new
+ extension layout for Bugzilla 3.6.
diff --git a/Websites/bugs.webkit.org/extensions/example/code/webservice-error_codes.pl b/Websites/bugs.webkit.org/contrib/fixperms.pl
old mode 100644
new mode 100755
similarity index 67%
rename from Websites/bugs.webkit.org/extensions/example/code/webservice-error_codes.pl
rename to Websites/bugs.webkit.org/contrib/fixperms.pl
index 94c4c52..6b9b87c
--- a/Websites/bugs.webkit.org/extensions/example/code/webservice-error_codes.pl
+++ b/Websites/bugs.webkit.org/contrib/fixperms.pl
@@ -1,4 +1,4 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
+#!/usr/bin/env perl -w
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
@@ -13,13 +13,16 @@
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
-# Portions created by Everything Solved, Inc. are Copyright (C) 2008
-# Everything Solved, Inc. All Rights Reserved.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
#
-# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
use warnings;
+use lib qw(. lib);
+
use Bugzilla;
-my $error_map = Bugzilla->hook_args->{error_map};
-$error_map->{'example_my_error'} = 10001;
+use Bugzilla::Install::Filesystem qw(fix_all_file_permissions);
+fix_all_file_permissions(1);
diff --git a/Websites/bugs.webkit.org/contrib/gnats2bz.pl b/Websites/bugs.webkit.org/contrib/gnats2bz.pl
deleted file mode 100644
index 59df3fd..0000000
--- a/Websites/bugs.webkit.org/contrib/gnats2bz.pl
+++ /dev/null
@@ -1,1067 +0,0 @@
-#!/usr/bin/env perl -w
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Gnats To Bugzilla Conversion Utility.
-#
-# The Initial Developer of the Original Code is Tom
-# Schutter. Portions created by Tom Schutter are
-# Copyright (C) 1999 Tom Schutter. All
-# Rights Reserved.
-#
-# Contributor(s): Tom Schutter <tom@platte.com>
-#
-# Perl script to convert a GNATS database to a Bugzilla database.
-# This script generates a file that contains SQL commands for MySQL.
-# This script DOES NOT MODIFY the GNATS database.
-# This script DOES NOT MODIFY the Bugzilla database.
-#
-# Usage procedure:
-# 1) Regenerate the GNATS index file. It sometimes has inconsistencies,
-# and this script relies on it being correct. Use the GNATS command:
-# gen-index --numeric --outfile=$GNATS_DIR/gnats-db/gnats-adm/index
-# 2) Modify variables at the beginning of this script to match
-# what your site requires.
-# 3) Modify translate_pr() and write_bugs() below to fixup mapping from
-# your GNATS policies to Bugzilla. For example, how do the
-# Severity/Priority fields map to bug_severity/priority?
-# 4) Run this script.
-# 5) Fix the problems in the GNATS database identified in the output
-# script file gnats2bz_cleanup.sh. Fixing problems may be a job
-# for a custom perl script. If you make changes to GNATS, goto step 2.
-# 6) Examine the statistics in the output file gnats2bz_stats.txt.
-# These may indicate some more cleanup that is needed. For example,
-# you may find that there are invalid "State"s, or not a consistent
-# scheme for "Release"s. If you make changes to GNATS, goto step 2.
-# 7) Examine the output data file gnats2bz_data.sql. If problems
-# exist, goto step 2.
-# 8) Create a new, empty Bugzilla database.
-# 9) Import the data using the command:
-# mysql -uroot -p'ROOT_PASSWORD' bugs < gnats2bz_data.sql
-# 10) Update the shadow directory with the command:
-# cd $BUGZILLA_DIR; ./processmail regenerate
-# 11) Run a sanity check by visiting the sanitycheck.cgi page.
-# 12) Manually verify that the database is ok. If it is not, goto step 2.
-#
-# Important notes:
-# Confidential is not mapped or exported.
-# Submitter-Id is not mapped or exported.
-#
-# Design decisions:
-# This script generates a SQL script file rather than dumping the data
-# directly into the database. This is to allow the user to check
-# and/or modify the results before they are put into the database.
-# The PR number is very important and must be maintained as the Bugzilla
-# bug number, because there are many references to the PR number, such
-# as in code comments, CVS comments, customer communications, etc.
-# Reading ENUMERATED and TEXT fields:
-# 1) All leading and trailing whitespace is stripped.
-# Reading MULTITEXT fields:
-# 1) All leading blank lines are stripped.
-# 2) All trailing whitespace is stripped.
-# 3) Indentation is preserved.
-# Audit-Trail is not mapped to bugs_activity table, because there
-# is no place to put the "Why" text, which can have a fair amount
-# of information content.
-#
-# 15 January 2002 - changes from Andrea Dell'Amico <adellam@link.it>
-#
-# * Adapted to the new database structure: now long_descs is a
-# separate table.
-# * Set a default for the target milestone, otherwise bugzilla
-# doesn't work with the imported database if milestones are used.
-# * In gnats version 3.113 records are separated by "|" and not ":".
-# * userid "1" is for the bugzilla administrator, so it's better to
-# start from 2.
-#
-
-use strict;
-
-# Suffix to be appended to username to make it an email address.
-my($username_suffix) = "\@platte.com";
-
-# Default organization that should be ignored and not passed on to Bugzilla.
-# Only bugs that are reported outside of the default organization will have
-# their Originator,Organization fields passed on.
-# The assumption here is that if the Organization is identical to the
-# $default_organization, then the Originator will most likely be only an
-# alias for the From field in the mail header.
-my($default_organization) = "Platte River Associates|platte";
-
-# Username for reporter field if unable to determine from mail header
-my($gnats_username) = "gnats\@platte.com";
-
-# Flag indicating if cleanup file should use edit-pr or ${EDITOR}.
-# Using edit-pr is safer, but may be too slow if there are too many
-# PRs that need cleanup. If you use ${EDITOR}, then you must make
-# sure that you have exclusive access to the database, and that you
-# do not screw up any fields.
-my($cleanup_with_edit_pr) = 0;
-
-# Component name and description for bugs imported from GNATS.
-my($default_component) = "GNATS Import";
-my($default_component_description) = "Bugs imported from GNATS.";
-
-# First generated userid. Start from 2: 1 is used for the bugzilla
-# administrator.
-my($userid_base) = 2;
-
-# Output filenames.
-my($cleanup_pathname) = "gnats2bz_cleanup.sh";
-my($stats_pathname) = "gnats2bz_stats.txt";
-my($data_pathname) = "gnats2bz_data.sql";
-
-# List of ENUMERATED and TEXT fields.
-my(@text_fields) = qw(Number Category Synopsis Confidential Severity
- Priority Responsible State Class Submitter-Id
- Arrival-Date Originator Release);
-
-# List of MULTITEXT fields.
-my(@multitext_fields) = qw(Mail-Header Organization Environment Description
- How-To-Repeat Fix Audit-Trail Unformatted);
-
-# List of fields to report statistics for.
-my(@statistics_fields) = qw(Category Confidential Severity Priority
- Responsible State Class Submitter-Id Originator
- Organization Release Environment);
-
-# Array to hold list of GNATS PRs.
-my(@pr_list);
-
-# Array to hold list of GNATS categories.
-my(@categories_list);
-
-# Array to hold list of GNATS responsible users.
-my(@responsible_list);
-
-# Array to hold list of usernames.
-my(@username_list);
-# Put the gnats_username in first.
-get_userid($gnats_username);
-
-# Hash to hold list of versions.
-my(%versions_table);
-
-# Hash to hold contents of PR.
-my(%pr_data);
-
-# String to hold duplicate fields found during read of PR.
-my($pr_data_dup_fields) = "";
-
-# String to hold badly labeled fields found during read of PR.
-# This usually happens when the user does not separate the field name
-# from the field data with whitespace.
-my($pr_data_bad_fields) = " ";
-
-# Hash to hold statistics (note that this a hash of hashes).
-my(%pr_stats);
-
-# Process commmand line.
-my($gnats_db_dir) = @ARGV;
-defined($gnats_db_dir) || die "gnats-db dir not specified";
-(-d $gnats_db_dir) || die "$gnats_db_dir is not a directory";
-
-# Load @pr_list from GNATS index file.
-my($index_pathname) = $gnats_db_dir . "/gnats-adm/index";
-(-f $index_pathname) || die "$index_pathname not found";
-print "Reading $index_pathname...\n";
-if (!load_index($index_pathname)) {
- return(0);
-}
-
-# Load @category_list from GNATS categories file.
-my($categories_pathname) = $gnats_db_dir . "/gnats-adm/categories";
-(-f $categories_pathname) || die "$categories_pathname not found";
-print "Reading $categories_pathname...\n";
-if (!load_categories($categories_pathname)) {
- return(0);
-}
-
-# Load @responsible_list from GNATS responsible file.
-my($responsible_pathname) = $gnats_db_dir . "/gnats-adm/responsible";
-(-f $responsible_pathname) || die "$responsible_pathname not found";
-print "Reading $responsible_pathname...\n";
-if (!load_responsible($responsible_pathname)) {
- return(0);
-}
-
-# Open cleanup file.
-open(CLEANUP, ">$cleanup_pathname") ||
- die "Unable to open $cleanup_pathname: $!";
-chmod(0744, $cleanup_pathname) || warn "Unable to chmod $cleanup_pathname: $!";
-print CLEANUP "#!/bin/sh\n";
-print CLEANUP "# List of PRs that have problems found by gnats2bz.pl.\n";
-
-# Open data file.
-open(DATA, ">$data_pathname") || die "Unable to open $data_pathname: $!";
-print DATA "-- Exported data from $gnats_db_dir by gnats2bz.pl.\n";
-print DATA "-- Load it into a Bugzilla database using the command:\n";
-print DATA "-- mysql -uroot -p'ROOT_PASSWORD' bugs < gnats2bz_data.sql\n";
-print DATA "--\n";
-
-# Loop over @pr_list.
-my($pr);
-foreach $pr (@pr_list) {
- print "Processing $pr...\n";
- if (!read_pr("$gnats_db_dir/$pr")) {
- next;
- }
-
- translate_pr();
-
- check_pr($pr);
-
- collect_stats();
-
- update_versions();
-
- write_bugs();
-
- write_longdescs();
-
-}
-
-write_non_bugs_tables();
-
-close(CLEANUP) || die "Unable to close $cleanup_pathname: $!";
-close(DATA) || die "Unable to close $data_pathname: $!";
-
-print "Generating $stats_pathname...\n";
-report_stats();
-
-sub load_index {
- my($pathname) = @_;
- my($record);
- my(@fields);
-
- open(INDEX, $pathname) || die "Unable to open $pathname: $!";
-
- while ($record = <INDEX>) {
- @fields = split(/\|/, $record);
- push(@pr_list, $fields[0]);
- }
-
- close(INDEX) || die "Unable to close $pathname: $!";
-
- return(1);
-}
-
-sub load_categories {
- my($pathname) = @_;
- my($record);
-
- open(CATEGORIES, $pathname) || die "Unable to open $pathname: $!";
-
- while ($record = <CATEGORIES>) {
- if ($record =~ /^#/) {
- next;
- }
- push(@categories_list, [split(/:/, $record)]);
- }
-
- close(CATEGORIES) || die "Unable to close $pathname: $!";
-
- return(1);
-}
-
-sub load_responsible {
- my($pathname) = @_;
- my($record);
-
- open(RESPONSIBLE, $pathname) || die "Unable to open $pathname: $!";
-
- while ($record = <RESPONSIBLE>) {
- if ($record =~ /^#/) {
- next;
- }
- push(@responsible_list, [split(/\|/, $record)]);
- }
-
- close(RESPONSIBLE) || die "Unable to close $pathname: $!";
-
- return(1);
-}
-
-sub read_pr {
- my($pr_filename) = @_;
- my($multitext) = "Mail-Header";
- my($field, $mail_header);
-
- # Empty the hash.
- %pr_data = ();
-
- # Empty the list of duplicate fields.
- $pr_data_dup_fields = "";
-
- # Empty the list of badly labeled fields.
- $pr_data_bad_fields = "";
-
- unless (open(PR, $pr_filename)) {
- warn "error opening $pr_filename: $!";
- return(0);
- }
-
- LINELOOP: while (<PR>) {
- chomp;
-
- if ($multitext eq "Unformatted") {
- # once we reach "Unformatted", rest of file goes there
- $pr_data{$multitext} = append_multitext($pr_data{$multitext}, $_);
- next LINELOOP;
- }
-
- # Handle ENUMERATED and TEXT fields.
- foreach $field (@text_fields) {
- if (/^>$field:($|\s+)/) {
- $pr_data{$field} = $'; # part of string after match
- $pr_data{$field} =~ s/\s+$//; # strip trailing whitespace
- $multitext = "";
- next LINELOOP;
- }
- }
-
- # Handle MULTITEXT fields.
- foreach $field (@multitext_fields) {
- if (/^>$field:\s*$/) {
- $_ = $'; # set to part of string after match part
- if (defined($pr_data{$field})) {
- if ($pr_data_dup_fields eq "") {
- $pr_data_dup_fields = $field;
- } else {
- $pr_data_dup_fields = "$pr_data_dup_fields $field";
- }
- }
- $pr_data{$field} = $_;
- $multitext = $field;
- next LINELOOP;
- }
- }
-
- # Check for badly labeled fields.
- foreach $field ((@text_fields, @multitext_fields)) {
- if (/^>$field:/) {
- if ($pr_data_bad_fields eq "") {
- $pr_data_bad_fields = $field;
- } else {
- $pr_data_bad_fields = "$pr_data_bad_fields $field";
- }
- }
- }
-
- # Handle continued MULTITEXT field.
- $pr_data{$multitext} = append_multitext($pr_data{$multitext}, $_);
- }
-
- close(PR) || warn "error closing $pr_filename: $!";
-
- # Strip trailing newlines from MULTITEXT fields.
- foreach $field (@multitext_fields) {
- if (defined($pr_data{$field})) {
- $pr_data{$field} =~ s/\s+$//;
- }
- }
-
- return(1);
-}
-
-sub append_multitext {
- my($original, $addition) = @_;
-
- if (defined($original) && $original ne "") {
- return "$original\n$addition";
- } else {
- return $addition;
- }
-}
-
-sub check_pr {
- my($pr) = @_;
- my($error_list) = "";
-
- if ($pr_data_dup_fields ne "") {
- $error_list = append_error($error_list, "Multiple '$pr_data_dup_fields'");
- }
-
- if ($pr_data_bad_fields ne "") {
- $error_list = append_error($error_list, "Bad field labels '$pr_data_bad_fields'");
- }
-
- if (!defined($pr_data{"Description"}) || $pr_data{"Description"} eq "") {
- $error_list = append_error($error_list, "Description empty");
- }
-
- if (defined($pr_data{"Unformatted"}) && $pr_data{"Unformatted"} ne "") {
- $error_list = append_error($error_list, "Unformatted text");
- }
-
- if (defined($pr_data{"Release"}) && length($pr_data{"Release"}) > 16) {
- $error_list = append_error($error_list, "Release > 16 chars");
- }
-
- if (defined($pr_data{"Fix"}) && $pr_data{"Fix"} =~ /State-Changed-/) {
- $error_list = append_error($error_list, "Audit in Fix field");
- }
-
- if (defined($pr_data{"Arrival-Date"})) {
- if ($pr_data{"Arrival-Date"} eq "") {
- $error_list = append_error($error_list, "Arrival-Date empty");
-
- } elsif (unixdate2datetime($pr, $pr_data{"Arrival-Date"}) eq "") {
- $error_list = append_error($error_list, "Arrival-Date format");
- }
- }
-
- # More checks should go here.
-
- if ($error_list ne "") {
- if ($cleanup_with_edit_pr) {
- my(@parts) = split("/", $pr);
- my($pr_num) = $parts[1];
- print CLEANUP "echo \"$error_list\"; edit-pr $pr_num\n";
- } else {
- print CLEANUP "echo \"$error_list\"; \${EDITOR} $pr\n";
- }
- }
-}
-
-sub append_error {
- my($original, $addition) = @_;
-
- if ($original ne "") {
- return "$original, $addition";
- } else {
- return $addition;
- }
-}
-
-sub translate_pr {
- # This function performs GNATS -> Bugzilla translations that should
- # happen before collect_stats().
-
- if (!defined($pr_data{"Organization"})) {
- $pr_data{"Originator"} = "";
- }
- if ($pr_data{"Organization"} =~ /$default_organization/) {
- $pr_data{"Originator"} = "";
- $pr_data{"Organization"} = "";
- }
- $pr_data{"Organization"} =~ s/^\s+//g; # strip leading whitespace
-
- if (!defined($pr_data{"Release"}) ||
- $pr_data{"Release"} eq "" ||
- $pr_data{"Release"} =~ /^unknown-1.0$/
- ) {
- $pr_data{"Release"} = "unknown";
- }
-
- if (defined($pr_data{"Responsible"})) {
- $pr_data{"Responsible"} =~ /\w+/;
- $pr_data{"Responsible"} = "$&$username_suffix";
- }
-
- my($rep_platform, $op_sys) = ("All", "All");
- if (defined($pr_data{"Environment"})) {
- if ($pr_data{"Environment"} =~ /[wW]in.*NT/) {
- $rep_platform = "PC";
- $op_sys = "Windows NT";
- } elsif ($pr_data{"Environment"} =~ /[wW]in.*95/) {
- $rep_platform = "PC";
- $op_sys = "Windows 95";
- } elsif ($pr_data{"Environment"} =~ /[wW]in.*98/) {
- $rep_platform = "PC";
- $op_sys = "Windows 98";
- } elsif ($pr_data{"Environment"} =~ /OSF/) {
- $rep_platform = "DEC";
- $op_sys = "OSF/1";
- } elsif ($pr_data{"Environment"} =~ /AIX/) {
- $rep_platform = "RS/6000";
- $op_sys = "AIX";
- } elsif ($pr_data{"Environment"} =~ /IRIX/) {
- $rep_platform = "SGI";
- $op_sys = "IRIX";
- } elsif ($pr_data{"Environment"} =~ /SunOS.*5\.\d/) {
- $rep_platform = "Sun";
- $op_sys = "Solaris";
- } elsif ($pr_data{"Environment"} =~ /SunOS.*4\.\d/) {
- $rep_platform = "Sun";
- $op_sys = "SunOS";
- }
- }
-
- $pr_data{"Environment"} = "$rep_platform:$op_sys";
-}
-
-sub collect_stats {
- my($field, $value);
-
- foreach $field (@statistics_fields) {
- $value = $pr_data{$field};
- if (!defined($value)) {
- $value = "";
- }
- if (defined($pr_stats{$field}{$value})) {
- $pr_stats{$field}{$value}++;
- } else {
- $pr_stats{$field}{$value} = 1;
- }
- }
-}
-
-sub report_stats {
- my($field, $value, $count);
-
- open(STATS, ">$stats_pathname") ||
- die "Unable to open $stats_pathname: $!";
- print STATS "Statistics of $gnats_db_dir collated by gnats2bz.pl.\n";
-
- my($field_stats);
- while (($field, $field_stats) = each(%pr_stats)) {
- print STATS "\n$field:\n";
- while (($value, $count) = each(%$field_stats)) {
- print STATS " $value: $count\n";
- }
- }
-
- close(STATS) || die "Unable to close $stats_pathname: $!";
-}
-
-sub get_userid {
- my($responsible) = @_;
- my($username, $userid);
-
- if (!defined($responsible)) {
- return(-1);
- }
-
- # Search for current username in the list.
- $userid = $userid_base;
- foreach $username (@username_list) {
- if ($username eq $responsible) {
- return($userid);
- }
- $userid++;
- }
-
- push(@username_list, $responsible);
- return($userid);
-}
-
-sub update_versions {
-
- if (!defined($pr_data{"Release"}) || !defined($pr_data{"Category"})) {
- return;
- }
-
- my($curr_product) = $pr_data{"Category"};
- my($curr_version) = $pr_data{"Release"};
-
- if ($curr_version eq "") {
- return;
- }
-
- if (!defined($versions_table{$curr_product})) {
- $versions_table{$curr_product} = [ ];
- }
-
- my($version_list) = $versions_table{$curr_product};
- my($version);
- foreach $version (@$version_list) {
- if ($version eq $curr_version) {
- return;
- }
- }
-
- push(@$version_list, $curr_version);
-}
-
-sub write_bugs {
- my($bug_id) = $pr_data{"Number"};
-
- my($userid) = get_userid($pr_data{"Responsible"});
-
- # Mapping from Class,Severity to bug_severity
- # At our site, the Severity,Priority fields have degenerated
- # into a 9-level priority field.
- my($bug_severity) = "normal";
- if ($pr_data{"Class"} eq "change-request") {
- $bug_severity = "enhancement";
- } elsif (defined($pr_data{"Synopsis"})) {
- if ($pr_data{"Synopsis"} =~ /crash|assert/i) {
- $bug_severity = "critical";
- } elsif ($pr_data{"Synopsis"} =~ /wrong|error/i) {
- $bug_severity = "major";
- }
- }
- $bug_severity = SqlQuote($bug_severity);
-
- # Mapping from Severity,Priority to priority
- # At our site, the Severity,Priority fields have degenerated
- # into a 9-level priority field.
- my($priority) = "P1";
- if (defined($pr_data{"Severity"}) && defined($pr_data{"Severity"})) {
- if ($pr_data{"Severity"} eq "critical") {
- if ($pr_data{"Priority"} eq "high") {
- $priority = "P1";
- } else {
- $priority = "P2";
- }
- } elsif ($pr_data{"Severity"} eq "serious") {
- if ($pr_data{"Priority"} eq "low") {
- $priority = "P4";
- } else {
- $priority = "P3";
- }
- } else {
- if ($pr_data{"Priority"} eq "high") {
- $priority = "P4";
- } else {
- $priority = "P5";
- }
- }
- }
- $priority = SqlQuote($priority);
-
- # Map State,Class to bug_status,resolution
- my($bug_status, $resolution);
- if ($pr_data{"State"} eq "open" || $pr_data{"State"} eq "analyzed") {
- $bug_status = "ASSIGNED";
- $resolution = "";
- } elsif ($pr_data{"State"} eq "feedback") {
- $bug_status = "RESOLVED";
- $resolution = "FIXED";
- } elsif ($pr_data{"State"} eq "closed") {
- $bug_status = "CLOSED";
- if (defined($pr_data{"Class"}) && $pr_data{"Class"} =~ /^duplicate/) {
- $resolution = "DUPLICATE";
- } elsif (defined($pr_data{"Class"}) && $pr_data{"Class"} =~ /^mistaken/) {
- $resolution = "INVALID";
- } else {
- $resolution = "FIXED";
- }
- } elsif ($pr_data{"State"} eq "suspended") {
- $bug_status = "RESOLVED";
- $resolution = "WONTFIX";
- } else {
- $bug_status = "NEW";
- $resolution = "";
- }
- $bug_status = SqlQuote($bug_status);
- $resolution = SqlQuote($resolution);
-
- my($creation_ts) = "";
- if (defined($pr_data{"Arrival-Date"}) && $pr_data{"Arrival-Date"} ne "") {
- $creation_ts = unixdate2datetime($bug_id, $pr_data{"Arrival-Date"});
- }
- $creation_ts = SqlQuote($creation_ts);
-
- my($delta_ts) = "";
- if (defined($pr_data{"Audit-Trail"})) {
- # note that (?:.|\n)+ is greedy, so this should match the
- # last Changed-When
- if ($pr_data{"Audit-Trail"} =~ /(?:.|\n)+-Changed-When: (.+)/) {
- $delta_ts = unixdate2timestamp($bug_id, $1);
- }
- }
- if ($delta_ts eq "") {
- if (defined($pr_data{"Arrival-Date"}) && $pr_data{"Arrival-Date"} ne "") {
- $delta_ts = unixdate2timestamp($bug_id, $pr_data{"Arrival-Date"});
- }
- }
- $delta_ts = SqlQuote($delta_ts);
-
- my($short_desc) = SqlQuote($pr_data{"Synopsis"});
-
- my($rep_platform, $op_sys) = split(/\|/, $pr_data{"Environment"});
- $rep_platform = SqlQuote($rep_platform);
- $op_sys = SqlQuote($op_sys);
-
- my($reporter) = get_userid($gnats_username);
- if (
- defined($pr_data{"Mail-Header"}) &&
- $pr_data{"Mail-Header"} =~ /From ([\w.]+\@[\w.]+)/
- ) {
- $reporter = get_userid($1);
- }
-
- my($version) = "";
- if (defined($pr_data{"Release"})) {
- $version = substr($pr_data{"Release"}, 0, 16);
- }
- $version = SqlQuote($version);
-
- my($product) = "";
- if (defined($pr_data{"Category"})) {
- $product = $pr_data{"Category"};
- }
- $product = SqlQuote($product);
-
- my($component) = SqlQuote($default_component);
-
- my($target_milestone) = "0";
- # $target_milestone = SqlQuote($target_milestone);
-
- my($qa_contact) = "0";
-
- # my($bug_file_loc) = "";
- # $bug_file_loc = SqlQuote($bug_file_loc);
-
- # my($status_whiteboard) = "";
- # $status_whiteboard = SqlQuote($status_whiteboard);
-
- print DATA "\ninsert into bugs (\n";
- print DATA " bug_id, assigned_to, bug_severity, priority, bug_status, creation_ts, delta_ts,\n";
- print DATA " short_desc,\n";
- print DATA " rep_platform, op_sys, reporter, version,\n";
- print DATA " product, component, resolution, target_milestone, qa_contact\n";
- print DATA ") values (\n";
- print DATA " $bug_id, $userid, $bug_severity, $priority, $bug_status, $creation_ts, $delta_ts,\n";
- print DATA " $short_desc,\n";
- print DATA " $rep_platform, $op_sys, $reporter, $version,\n";
- print DATA " $product, $component, $resolution, $target_milestone, $qa_contact\n";
- print DATA ");\n";
-}
-
-sub write_longdescs {
-
- my($bug_id) = $pr_data{"Number"};
- my($who) = get_userid($pr_data{"Responsible"});;
- my($bug_when) = "";
- if (defined($pr_data{"Arrival-Date"}) && $pr_data{"Arrival-Date"} ne "") {
- $bug_when = unixdate2datetime($bug_id, $pr_data{"Arrival-Date"});
- }
- $bug_when = SqlQuote($bug_when);
- my($thetext) = $pr_data{"Description"};
- if (defined($pr_data{"How-To-Repeat"}) && $pr_data{"How-To-Repeat"} ne "") {
- $thetext =
- $thetext . "\n\nHow-To-Repeat:\n" . $pr_data{"How-To-Repeat"};
- }
- if (defined($pr_data{"Fix"}) && $pr_data{"Fix"} ne "") {
- $thetext = $thetext . "\n\nFix:\n" . $pr_data{"Fix"};
- }
- if (defined($pr_data{"Originator"}) && $pr_data{"Originator"} ne "") {
- $thetext = $thetext . "\n\nOriginator:\n" . $pr_data{"Originator"};
- }
- if (defined($pr_data{"Organization"}) && $pr_data{"Organization"} ne "") {
- $thetext = $thetext . "\n\nOrganization:\n" . $pr_data{"Organization"};
- }
- if (defined($pr_data{"Audit-Trail"}) && $pr_data{"Audit-Trail"} ne "") {
- $thetext = $thetext . "\n\nAudit-Trail:\n" . $pr_data{"Audit-Trail"};
- }
- if (defined($pr_data{"Unformatted"}) && $pr_data{"Unformatted"} ne "") {
- $thetext = $thetext . "\n\nUnformatted:\n" . $pr_data{"Unformatted"};
- }
- $thetext = SqlQuote($thetext);
-
- print DATA "\ninsert into longdescs (\n";
- print DATA " bug_id, who, bug_when, thetext\n";
- print DATA ") values (\n";
- print DATA " $bug_id, $who, $bug_when, $thetext\n";
- print DATA ");\n";
-
-}
-
-sub write_non_bugs_tables {
-
- my($categories_record);
- foreach $categories_record (@categories_list) {
- my($component) = SqlQuote($default_component);
- my($product) = SqlQuote(@$categories_record[0]);
- my($description) = SqlQuote(@$categories_record[1]);
- my($initialowner) = SqlQuote(@$categories_record[2] . $username_suffix);
-
- print DATA "\ninsert into products (\n";
- print DATA
- " product, description, milestoneurl, disallownew\n";
- print DATA ") values (\n";
- print DATA
- " $product, $description, '', 0\n";
- print DATA ");\n";
-
- print DATA "\ninsert into components (\n";
- print DATA
- " value, program, initialowner, initialqacontact, description\n";
- print DATA ") values (\n";
- print DATA
- " $component, $product, $initialowner, '', $description\n";
- print DATA ");\n";
-
- print DATA "\ninsert into milestones (\n";
- print DATA
- " value, product, sortkey\n";
- print DATA ") values (\n";
- print DATA
- " 0, $product, 0\n";
- print DATA ");\n";
- }
-
- my($username);
- my($userid) = $userid_base;
- my($password) = "password";
- my($realname);
- my($groupset) = 0;
- foreach $username (@username_list) {
- $realname = map_username_to_realname($username);
- $username = SqlQuote($username);
- $realname = SqlQuote($realname);
- print DATA "\ninsert into profiles (\n";
- print DATA
- " userid, login_name, cryptpassword, realname, groupset\n";
- print DATA ") values (\n";
- print DATA
- " $userid, $username, encrypt('$password'), $realname, $groupset\n";
- print DATA ");\n";
- $userid++;
- }
-
- my($product);
- my($version_list);
- while (($product, $version_list) = each(%versions_table)) {
- $product = SqlQuote($product);
-
- my($version);
- foreach $version (@$version_list) {
- $version = SqlQuote($version);
-
- print DATA "\ninsert into versions (value, program) ";
- print DATA "values ($version, $product);\n";
- }
- }
-
-}
-
-sub map_username_to_realname() {
- my($username) = @_;
- my($name, $realname);
-
- # get the portion before the @
- $name = $username;
- $name =~ s/\@.*//;
-
- my($responsible_record);
- foreach $responsible_record (@responsible_list) {
- if (@$responsible_record[0] eq $name) {
- return(@$responsible_record[1]);
- }
- if (defined(@$responsible_record[2])) {
- if (@$responsible_record[2] eq $username) {
- return(@$responsible_record[1]);
- }
- }
- }
-
- return("");
-}
-
-sub detaint_string {
- my ($str) = @_;
- $str =~ m/^(.*)$/s;
- $str = $1;
-}
-
-sub SqlQuote {
- my ($str) = (@_);
- $str =~ s/([\\\'])/\\$1/g;
- $str =~ s/\0/\\0/g;
- # If it's been SqlQuote()ed, then it's safe, so we tell -T that.
- $str = detaint_string($str);
- return "'$str'";
-}
-
-sub unixdate2datetime {
- my($bugid, $unixdate) = @_;
- my($year, $month, $day, $hour, $min, $sec);
-
- if (!split_unixdate($bugid, $unixdate, \$year, \$month, \$day, \$hour, \$min, \$sec)) {
- return("");
- }
-
- return("$year-$month-$day $hour:$min:$sec");
-}
-
-sub unixdate2timestamp {
- my($bugid, $unixdate) = @_;
- my($year, $month, $day, $hour, $min, $sec);
-
- if (!split_unixdate($bugid, $unixdate, \$year, \$month, \$day, \$hour, \$min, \$sec)) {
- return("");
- }
-
- return("$year$month$day$hour$min$sec");
-}
-
-sub split_unixdate {
- # "Tue Jun 6 14:50:00 1995"
- # "Mon Nov 20 17:03:11 [MST] 1995"
- # "12/13/94"
- # "jan 1, 1995"
- my($bugid, $unixdate, $year, $month, $day, $hour, $min, $sec) = @_;
- my(@parts);
-
- $$hour = "00";
- $$min = "00";
- $$sec = "00";
-
- @parts = split(/ +/, $unixdate);
- if (@parts >= 5) {
- # year
- $$year = $parts[4];
- if ($$year =~ /[A-Z]{3}/) {
- # Must be timezone, try next field.
- $$year = $parts[5];
- }
- if ($$year =~ /\D/) {
- warn "$bugid: Error processing year part '$$year' of date '$unixdate'\n";
- return(0);
- }
- if ($$year < 30) {
- $$year = "20" . $$year;
- } elsif ($$year < 100) {
- $$year = "19" . $$year;
- } elsif ($$year < 1970 || $$year > 2029) {
- warn "$bugid: Error processing year part '$$year' of date '$unixdate'\n";
- return(0);
- }
-
- # month
- $$month = $parts[1];
- if ($$month =~ /\D/) {
- if (!month2number($month)) {
- warn "$bugid: Error processing month part '$$month' of date '$unixdate'\n";
- return(0);
- }
-
- } elsif ($$month < 1 || $$month > 12) {
- warn "$bugid: Error processing month part '$$month' of date '$unixdate'\n";
- return(0);
-
- } elsif (length($$month) == 1) {
- $$month = "0" . $$month;
- }
-
- # day
- $$day = $parts[2];
- if ($$day < 1 || $$day > 31) {
- warn "$bugid: Error processing day part '$day' of date '$unixdate'\n";
- return(0);
-
- } elsif (length($$day) == 1) {
- $$day = "0" . $$day;
- }
-
- @parts = split(/:/, $parts[3]);
- $$hour = $parts[0];
- $$min = $parts[1];
- $$sec = $parts[2];
-
- return(1);
-
- } elsif (@parts == 3) {
- # year
- $$year = $parts[2];
- if ($$year =~ /\D/) {
- warn "$bugid: Error processing year part '$$year' of date '$unixdate'\n";
- return(0);
- }
- if ($$year < 30) {
- $$year = "20" . $$year;
- } elsif ($$year < 100) {
- $$year = "19" . $$year;
- } elsif ($$year < 1970 || $$year > 2029) {
- warn "$bugid: Error processing year part '$$year' of date '$unixdate'\n";
- return(0);
- }
-
- # month
- $$month = $parts[0];
- if ($$month =~ /\D/) {
- if (!month2number($month)) {
- warn "$bugid: Error processing month part '$$month' of date '$unixdate'\n";
- return(0);
- }
-
- } elsif ($$month < 1 || $$month > 12) {
- warn "$bugid: Error processing month part '$$month' of date '$unixdate'\n";
- return(0);
-
- } elsif (length($$month) == 1) {
- $$month = "0" . $$month;
- }
-
- # day
- $$day = $parts[1];
- $$day =~ s/,//;
- if ($$day < 1 || $$day > 31) {
- warn "$bugid: Error processing day part '$day' of date '$unixdate'\n";
- return(0);
-
- } elsif (length($$day) == 1) {
- $$day = "0" . $$day;
- }
-
- return(1);
- }
-
- @parts = split(/:/, $unixdate);
- if (@parts == 3 && length($unixdate) <= 8) {
- $$year = "19" . $parts[2];
-
- $$month = $parts[0];
- if (length($$month) == 1) {
- $$month = "0" . $$month;
- }
-
- $$day = $parts[1];
- if (length($$day) == 1) {
- $$day = "0" . $$day;
- }
-
- return(1);
- }
-
- warn "$bugid: Error processing date '$unixdate'\n";
- return(0);
-}
-
-sub month2number {
- my($month) = @_;
-
- if ($$month =~ /jan/i) {
- $$month = "01";
- } elsif ($$month =~ /feb/i) {
- $$month = "02";
- } elsif ($$month =~ /mar/i) {
- $$month = "03";
- } elsif ($$month =~ /apr/i) {
- $$month = "04";
- } elsif ($$month =~ /may/i) {
- $$month = "05";
- } elsif ($$month =~ /jun/i) {
- $$month = "06";
- } elsif ($$month =~ /jul/i) {
- $$month = "07";
- } elsif ($$month =~ /aug/i) {
- $$month = "08";
- } elsif ($$month =~ /sep/i) {
- $$month = "09";
- } elsif ($$month =~ /oct/i) {
- $$month = "10";
- } elsif ($$month =~ /nov/i) {
- $$month = "11";
- } elsif ($$month =~ /dec/i) {
- $$month = "12";
- } else {
- return(0);
- }
-
- return(1);
-}
-
diff --git a/Websites/bugs.webkit.org/contrib/gnatsparse/README b/Websites/bugs.webkit.org/contrib/gnatsparse/README
deleted file mode 100755
index f7cf01c..0000000
--- a/Websites/bugs.webkit.org/contrib/gnatsparse/README
+++ /dev/null
@@ -1,62 +0,0 @@
-gnatsparse
-==========
-
-Author: Daniel Berlin <dan@dberlin.org>
-
-gnatsparse is a simple Python program that imports a GNATS database
-into a Bugzilla system. It is based on the gnats2bz.pl Perl script
-but it's a rewrite at the same time. Its parser is based on gnatsweb,
-which gives a 10 times speed improvement compared to the previous code.
-
-Features
---------
-
-* Chunks audit trail into separate comments, with the right From's, times, etc.
-
-* Handles followup emails that are in the report, with the right From's, times,
-etc.
-
-* Properly handles duplicates, adding the standard bugzilla duplicate message.
-
-* Extracts and handles gnatsweb attachments, as well as uuencoded attachments
-appearing in either followup emails, the how-to-repeat field, etc. Replaces
-them with a message to look at the attachments list, and adds the standard
-"Created an attachment" message that bugzilla uses. Handling them includes
-giving them the right name and mime-type. "attachments" means multiple
-uuencoded things/gnatsweb attachments are handled properly.
-
-* Handles reopened bug reports.
-
-* Builds the cc list from the people who have commented on the report,
-and the reporter.
-
-Requirements
-------------
-
-It requires python 2.2+, it won't work with 1.5.2 (Linux distributions
-ship with 2.2+ these days, so that shouldn't be an issue).
-
-Documentation
--------------
-
-Documentation can be found inside the scripts. The source code is self
-documenting.
-
-Issues for someone trying to use it to convert a gnats install
------------------------------------
-
-1. We have three custom fields bugzilla doesn't ship with,
-gcchost, gcctarget, and gccbuild.
-We removed two bugzilla fields, rep_platform and op_sys.
-If you use the latter instead of the former, you'll need to
-update the script to account for this.
-2. Because gcc attachments consist of preprocessed source, all attachments
-inserted into the attachment database are compressed with zlib.compress.
-This requires associated bugzilla changes to decompress before sending to
-the browser.
-Unless you want to make those changes (it's roughly 3 lines), you'll
-need to remove the zlib.compress call.
-3. You will need to come up with your own release to version mapping and
-install it.
-4. Obviously, any extra gnats fields you have added will have to
-be handled in some manner.
diff --git a/Websites/bugs.webkit.org/contrib/gnatsparse/gnatsparse.py b/Websites/bugs.webkit.org/contrib/gnatsparse/gnatsparse.py
deleted file mode 100755
index c709934..0000000
--- a/Websites/bugs.webkit.org/contrib/gnatsparse/gnatsparse.py
+++ /dev/null
@@ -1,807 +0,0 @@
-try:
-# Using Psyco makes it about 25% faster, but there's a bug in psyco in
-# handling of eval causing it to use unlimited memory with the magic
-# file enabled.
-# import psyco
-# psyco.full()
-# from psyco.classes import *
- pass
-except:
- pass
-import re
-import base64
-import cStringIO
-import specialuu
-import array
-import email.Utils
-import zlib
-import magic
-
-# Comment out if you don't want magic detection
-magicf = magic.MagicFile()
-
-# Open our output file
-outfile = open("gnats2bz_data.sql", "w")
-
-# List of GNATS fields
-fieldnames = ("Number", "Category", "Synopsis", "Confidential", "Severity",
- "Priority", "Responsible", "State", "Quarter", "Keywords",
- "Date-Required", "Class", "Submitter-Id", "Arrival-Date",
- "Closed-Date", "Last-Modified", "Originator", "Release",
- "Organization", "Environment", "Description", "How-To-Repeat",
- "Fix", "Release-Note", "Audit-Trail", "Unformatted")
-
-# Dictionary telling us which GNATS fields are multiline
-multilinefields = {"Organization":1, "Environment":1, "Description":1,
- "How-To-Repeat":1, "Fix":1, "Release-Note":1,
- "Audit-Trail":1, "Unformatted":1}
-
-# Mapping of GCC release to version. Our version string is updated every
-# so we need to funnel all release's with 3.4 in the string to be version
-# 3.4 for bug tracking purposes
-# The key is a regex to match, the value is the version it corresponds
-# with
-releasetovermap = {r"3\.4":"3.4", r"3\.3":"3.3", r"3\.2\.2":"3.2.2",
- r"3\.2\.1":"3.2.1", r"3\.2":"3.2", r"3\.1\.2":"3.1.2",
- r"3\.1\.1":"3.1.1", r"3\.1":"3.1", r"3\.0\.4":"3.0.4",
- r"3\.0\.3":"3.0.3", r"3\.0\.2":"3.0.2", r"3\.0\.1":"3.0.1",
- r"3\.0":"3.0", r"2\.95\.4":"2.95.4", r"2\.95\.3":"2.95.3",
- r"2\.95\.2":"2.95.2", r"2\.95\.1":"2.95.1",
- r"2\.95":"2.95", r"2\.97":"2.97",
- r"2\.96.*[rR][eE][dD].*[hH][aA][tT]":"2.96 (redhat)",
- r"2\.96":"2.96"}
-
-# These map the field name to the field id bugzilla assigns. We need
-# the id when doing bug activity.
-fieldids = {"State":8, "Responsible":15}
-
-# These are the keywords we use in gcc bug tracking. They are transformed
-# into bugzilla keywords. The format here is <keyword>-><bugzilla keyword id>
-keywordids = {"wrong-code":1, "ice-on-legal-code":2, "ice-on-illegal-code":3,
- "rejects-legal":4, "accepts-illegal":5, "pessimizes-code":6}
-
-# Map from GNATS states to Bugzilla states. Duplicates and reopened bugs
-# are handled when parsing the audit trail, so no need for them here.
-state_lookup = {"":"NEW", "open":"ASSIGNED", "analyzed":"ASSIGNED",
- "feedback":"WAITING", "closed":"CLOSED",
- "suspended":"SUSPENDED"}
-
-# Table of versions that exist in the bugs, built up as we go along
-versions_table = {}
-
-# Delimiter gnatsweb uses for attachments
-attachment_delimiter = "----gnatsweb-attachment----\n"
-
-# Here starts the various regular expressions we use
-# Matches an entire GNATS single line field
-gnatfieldre = re.compile(r"""^([>\w\-]+)\s*:\s*(.*)\s*$""")
-
-# Matches the name of a GNATS field
-fieldnamere = re.compile(r"""^>(.*)$""")
-
-# Matches the useless part of an envelope
-uselessre = re.compile(r"""^(\S*?):\s*""", re.MULTILINE)
-
-# Matches the filename in a content disposition
-dispositionre = re.compile("(\\S+);\\s*filename=\"([^\"]+)\"")
-
-# Matches the last changed date in the entire text of a bug
-# If you have other editable fields that get audit trail entries, modify this
-# The field names are explicitly listed in order to speed up matching
-lastdatere = re.compile(r"""^(?:(?:State|Responsible|Priority|Severity)-Changed-When: )(.+?)$""", re.MULTILINE)
-
-# Matches the From line of an email or the first line of an audit trail entry
-# We use this re to find the begin lines of all the audit trail entries
-# The field names are explicitly listed in order to speed up matching
-fromtore=re.compile(r"""^(?:(?:State|Responsible|Priority|Severity)-Changed-From-To: |From: )""", re.MULTILINE)
-
-# These re's match the various parts of an audit trail entry
-changedfromtore=re.compile(r"""^(\w+?)-Changed-From-To: (.+?)$""", re.MULTILINE)
-changedbyre=re.compile(r"""^\w+?-Changed-By: (.+?)$""", re.MULTILINE)
-changedwhenre=re.compile(r"""^\w+?-Changed-When: (.+?)$""", re.MULTILINE)
-changedwhyre=re.compile(r"""^\w+?-Changed-Why:\s*(.*?)$""", re.MULTILINE)
-
-# This re matches audit trail text saying that the current bug is a duplicate of another
-duplicatere=re.compile(r"""(?:")?Dup(?:licate)?(?:d)?(?:")? of .*?(\d+)""", re.IGNORECASE | re.MULTILINE)
-
-# Get the text of a From: line
-fromre=re.compile(r"""^From: (.*?)$""", re.MULTILINE)
-
-# Get the text of a Date: Line
-datere=re.compile(r"""^Date: (.*?)$""", re.MULTILINE)
-
-# Map of the responsible file to email addresses
-responsible_map = {}
-# List of records in the responsible file
-responsible_list = []
-# List of records in the categories file
-categories_list = []
-# List of pr's in the index
-pr_list = []
-# Map usernames to user ids
-usermapping = {}
-# Start with this user id
-userid_base = 2
-
-# Name of gnats user
-gnats_username = "gnats@gcc.gnu.org"
-# Name of unassigned user
-unassigned_username = "unassigned@gcc.gnu.org"
-
-gnats_db_dir = "."
-product = "gcc"
-productdesc = "GNU Compiler Connection"
-milestoneurl = "http://gcc/gnu.org"
-defaultmilestone = "3.4"
-
-def write_non_bug_tables():
- """ Write out the non-bug related tables, such as products, profiles, etc."""
- # Set all non-unconfirmed bugs's everconfirmed flag
- print >>outfile, "update bugs set everconfirmed=1 where bug_status != 'UNCONFIRMED';"
-
- # Set all bugs assigned to the unassigned user to NEW
- print >>outfile, "update bugs set bug_status='NEW',assigned_to='NULL' where bug_status='ASSIGNED' AND assigned_to=3;"
-
- # Insert the products
- print >>outfile, "\ninsert into products ("
- print >>outfile, " product, description, milestoneurl, disallownew,"
- print >>outfile, " defaultmilestone, votestoconfirm) values ("
- print >>outfile, " '%s', '%s', '%s', 0, '%s', 1);" % (product,
- productdesc,
- milestoneurl,
- defaultmilestone)
-
- # Insert the components
- for category in categories_list:
- component = SqlQuote(category[0])
- productstr = SqlQuote(product)
- description = SqlQuote(category[1])
- initialowner = SqlQuote("3")
- print >>outfile, "\ninsert into components (";
- print >>outfile, " value, program, initialowner, initialqacontact,"
- print >>outfile, " description) values ("
- print >>outfile, " %s, %s, %s, '', %s);" % (component, productstr,
- initialowner, description)
-
- # Insert the versions
- for productstr, version_list in versions_table.items():
- productstr = SqlQuote(productstr)
- for version in version_list:
- version = SqlQuote(version)
- print >>outfile, "\ninsert into versions (value, program) "
- print >>outfile, " values (%s, %s);" % (version, productstr)
-
- # Insert the users
- for username, userid in usermapping.items():
- realname = map_username_to_realname(username)
- username = SqlQuote(username)
- realname = SqlQuote(realname)
- print >>outfile, "\ninsert into profiles ("
- print >>outfile, " userid, login_name, password, cryptpassword, realname, groupset"
- print >>outfile, ") values ("
- print >>outfile, "%s,%s,'password',encrypt('password'), %s, 0);" % (userid, username, realname)
- print >>outfile, "update profiles set groupset=1 << 32 where login_name like '%\@gcc.gnu.org';"
-
-def unixdate2datetime(unixdate):
- """ Convert a unix date to a datetime value """
- year, month, day, hour, min, sec, x, x, x, x = email.Utils.parsedate_tz(unixdate)
- return "%d-%02d-%02d %02d:%02d:%02d" % (year,month,day,hour,min,sec)
-
-def unixdate2timestamp(unixdate):
- """ Convert a unix date to a timestamp value """
- year, month, day, hour, min, sec, x, x, x, x = email.Utils.parsedate_tz(unixdate)
- return "%d%02d%02d%02d%02d%02d" % (year,month,day,hour,min,sec)
-
-def SqlQuote(str):
- """ Perform SQL quoting on a string """
- return "'%s'" % str.replace("'", """''""").replace("\\", "\\\\").replace("\0","\\0")
-
-def convert_gccver_to_ver(gccver):
- """ Given a gcc version, convert it to a Bugzilla version. """
- for k in releasetovermap.keys():
- if re.search(".*%s.*" % k, gccver) is not None:
- return releasetovermap[k]
- result = re.search(r""".*(\d\.\d) \d+ \(experimental\).*""", gccver)
- if result is not None:
- return result.group(1)
- return "unknown"
-
-def load_index(fname):
- """ Load in the GNATS index file """
- global pr_list
- ifp = open(fname)
- for record in ifp.xreadlines():
- fields = record.split("|")
- pr_list.append(fields[0])
- ifp.close()
-
-def load_categories(fname):
- """ Load in the GNATS categories file """
- global categories_list
- cfp = open(fname)
- for record in cfp.xreadlines():
- if re.search("^#", record) is not None:
- continue
- categories_list.append(record.split(":"))
- cfp.close()
-
-def map_username_to_realname(username):
- """ Given a username, find the real name """
- name = username
- name = re.sub("@.*", "", name)
- for responsible_record in responsible_list:
- if responsible_record[0] == name:
- return responsible_record[1]
- if len(responsible_record) > 2:
- if responsible_record[2] == username:
- return responsible_record[1]
- return ""
-
-
-def get_userid(responsible):
- """ Given an email address, get the user id """
- global responsible_map
- global usermapping
- global userid_base
- if responsible is None:
- return -1
- responsible = responsible.lower()
- responsible = re.sub("sources.redhat.com", "gcc.gnu.org", responsible)
- if responsible_map.has_key(responsible):
- responsible = responsible_map[responsible]
- if usermapping.has_key(responsible):
- return usermapping[responsible]
- else:
- usermapping[responsible] = userid_base
- userid_base += 1
- return usermapping[responsible]
-
-def load_responsible(fname):
- """ Load in the GNATS responsible file """
- global responsible_map
- global responsible_list
- rfp = open(fname)
- for record in rfp.xreadlines():
- if re.search("^#", record) is not None:
- continue
- split_record = record.split(":")
- responsible_map[split_record[0]] = split_record[2].rstrip()
- responsible_list.append(record.split(":"))
- rfp.close()
-
-def split_csl(list):
- """ Split a comma separated list """
- newlist = re.split(r"""\s*,\s*""", list)
- return newlist
-
-def fix_email_addrs(addrs):
- """ Perform various fixups and cleaning on an e-mail address """
- addrs = split_csl(addrs)
- trimmed_addrs = []
- for addr in addrs:
- addr = re.sub(r"""\(.*\)""","",addr)
- addr = re.sub(r""".*<(.*)>.*""","\\1",addr)
- addr = addr.rstrip()
- addr = addr.lstrip()
- trimmed_addrs.append(addr)
- addrs = ", ".join(trimmed_addrs)
- return addrs
-
-class Bugzillabug(object):
- """ Class representing a bugzilla bug """
- def __init__(self, gbug):
- """ Initialize a bugzilla bug from a GNATS bug. """
- self.bug_id = gbug.bug_id
- self.long_descs = []
- self.bug_ccs = [get_userid("gcc-bugs@gcc.gnu.org")]
- self.bug_activity = []
- self.attachments = gbug.attachments
- self.gnatsfields = gbug.fields
- self.need_unformatted = gbug.has_unformatted_attach == 0
- self.need_unformatted &= gbug.fields.has_key("Unformatted")
- self.translate_pr()
- self.update_versions()
- if self.fields.has_key("Audit-Trail"):
- self.parse_audit_trail()
- self.write_bug()
-
- def parse_fromto(type, string):
- """ Parses the from and to parts of a changed-from-to line """
- fromstr = ""
- tostr = ""
-
- # Some slightly messed up changed lines have unassigned-new,
- # instead of unassigned->new. So we make the > optional.
- result = re.search(r"""(.*)-(?:>?)(.*)""", string)
-
- # Only know how to handle parsing of State and Responsible
- # changed-from-to right now
- if type == "State":
- fromstr = state_lookup[result.group(1)]
- tostr = state_lookup[result.group(2)]
- elif type == "Responsible":
- if result.group(1) != "":
- fromstr = result.group(1)
- if result.group(2) != "":
- tostr = result.group(2)
- if responsible_map.has_key(fromstr):
- fromstr = responsible_map[fromstr]
- if responsible_map.has_key(tostr):
- tostr = responsible_map[tostr]
- return (fromstr, tostr)
- parse_fromto = staticmethod(parse_fromto)
-
- def parse_audit_trail(self):
- """ Parse a GNATS audit trail """
- trail = self.fields["Audit-Trail"]
- # Begin to split the audit trail into pieces
- result = fromtore.finditer(trail)
- starts = []
- ends = []
- pieces = []
- # Make a list of the pieces
- for x in result:
- pieces.append (x)
- # Find the start and end of each piece
- if len(pieces) > 0:
- for x in xrange(len(pieces)-1):
- starts.append(pieces[x].start())
- ends.append(pieces[x+1].start())
- starts.append(pieces[-1].start())
- ends.append(len(trail))
- pieces = []
- # Now make the list of actual text of the pieces
- for x in xrange(len(starts)):
- pieces.append(trail[starts[x]:ends[x]])
- # And parse the actual pieces
- for piece in pieces:
- result = changedfromtore.search(piece)
- # See what things we actually have inside this entry, and
- # handle them appropriately
- if result is not None:
- type = result.group(1)
- changedfromto = result.group(2)
- # If the bug was reopened, mark it as such
- if changedfromto.find("closed->analyzed") != -1:
- if self.fields["bug_status"] == "'NEW'":
- self.fields["bug_status"] = "'REOPENED'"
- if type == "State" or type == "Responsible":
- oldstate, newstate = self.parse_fromto (type, changedfromto)
- result = changedbyre.search(piece)
- if result is not None:
- changedby = result.group(1)
- result = changedwhenre.search(piece)
- if result is not None:
- changedwhen = result.group(1)
- changedwhen = unixdate2datetime(changedwhen)
- changedwhen = SqlQuote(changedwhen)
- result = changedwhyre.search(piece)
- changedwhy = piece[result.start(1):]
- #changedwhy = changedwhy.lstrip()
- changedwhy = changedwhy.rstrip()
- changedby = get_userid(changedby)
- # Put us on the cc list if we aren't there already
- if changedby != self.fields["userid"] \
- and changedby not in self.bug_ccs:
- self.bug_ccs.append(changedby)
- # If it's a duplicate, mark it as such
- result = duplicatere.search(changedwhy)
- if result is not None:
- newtext = "*** This bug has been marked as a duplicate of %s ***" % result.group(1)
- newtext = SqlQuote(newtext)
- self.long_descs.append((self.bug_id, changedby,
- changedwhen, newtext))
- self.fields["bug_status"] = "'RESOLVED'"
- self.fields["resolution"] = "'DUPLICATE'"
- self.fields["userid"] = changedby
- else:
- newtext = "%s-Changed-From-To: %s\n%s-Changed-Why: %s\n" % (type, changedfromto, type, changedwhy)
- newtext = SqlQuote(newtext)
- self.long_descs.append((self.bug_id, changedby,
- changedwhen, newtext))
- if type == "State" or type == "Responsible":
- newstate = SqlQuote("%s" % newstate)
- oldstate = SqlQuote("%s" % oldstate)
- fieldid = fieldids[type]
- self.bug_activity.append((newstate, oldstate, fieldid, changedby, changedwhen))
-
- else:
- # It's an email
- result = fromre.search(piece)
- if result is None:
- continue
- fromstr = result.group(1)
- fromstr = fix_email_addrs(fromstr)
- fromstr = get_userid(fromstr)
- result = datere.search(piece)
- if result is None:
- continue
- datestr = result.group(1)
- datestr = SqlQuote(unixdate2timestamp(datestr))
- if fromstr != self.fields["userid"] \
- and fromstr not in self.bug_ccs:
- self.bug_ccs.append(fromstr)
- self.long_descs.append((self.bug_id, fromstr, datestr,
- SqlQuote(piece)))
-
-
-
- def write_bug(self):
- """ Output a bug to the data file """
- fields = self.fields
- print >>outfile, "\ninsert into bugs("
- print >>outfile, " bug_id, assigned_to, bug_severity, priority, bug_status, creation_ts, delta_ts,"
- print >>outfile, " short_desc,"
- print >>outfile, " reporter, version,"
- print >>outfile, " product, component, resolution, target_milestone, qa_contact,"
- print >>outfile, " gccbuild, gcctarget, gcchost, keywords"
- print >>outfile, " ) values ("
- print >>outfile, "%s, %s, %s, %s, %s, %s, %s," % (self.bug_id, fields["userid"], fields["bug_severity"], fields["priority"], fields["bug_status"], fields["creation_ts"], fields["delta_ts"])
- print >>outfile, "%s," % (fields["short_desc"])
- print >>outfile, "%s, %s," % (fields["reporter"], fields["version"])
- print >>outfile, "%s, %s, %s, %s, 0," %(fields["product"], fields["component"], fields["resolution"], fields["target_milestone"])
- print >>outfile, "%s, %s, %s, %s" % (fields["gccbuild"], fields["gcctarget"], fields["gcchost"], fields["keywords"])
- print >>outfile, ");"
- if self.fields["keywords"] != 0:
- print >>outfile, "\ninsert into keywords (bug_id, keywordid) values ("
- print >>outfile, " %s, %s);" % (self.bug_id, fields["keywordid"])
- for id, who, when, text in self.long_descs:
- print >>outfile, "\ninsert into longdescs ("
- print >>outfile, " bug_id, who, bug_when, thetext) values("
- print >>outfile, " %s, %s, %s, %s);" % (id, who, when, text)
- for name, data, who in self.attachments:
- print >>outfile, "\ninsert into attachments ("
- print >>outfile, " bug_id, filename, description, mimetype, ispatch, submitter_id) values ("
- ftype = None
- # It's *magic*!
- if name.endswith(".ii") == 1:
- ftype = "text/x-c++"
- elif name.endswith(".i") == 1:
- ftype = "text/x-c"
- else:
- ftype = magicf.detect(cStringIO.StringIO(data))
- if ftype is None:
- ftype = "application/octet-stream"
-
- print >>outfile, "%s,%s,%s, %s,0, %s,%s);" %(self.bug_id, SqlQuote(name), SqlQuote(name), SqlQuote (ftype), who)
- print >>outfile, "\ninsert into attach_data ("
- print >>outfile, "\n(id, thedata) values (last_insert_id(),"
- print >>outfile, "%s);" % (SqlQuote(zlib.compress(data)))
- for newstate, oldstate, fieldid, changedby, changedwhen in self.bug_activity:
- print >>outfile, "\ninsert into bugs_activity ("
- print >>outfile, " bug_id, who, bug_when, fieldid, added, removed) values ("
- print >>outfile, " %s, %s, %s, %s, %s, %s);" % (self.bug_id,
- changedby,
- changedwhen,
- fieldid,
- newstate,
- oldstate)
- for cc in self.bug_ccs:
- print >>outfile, "\ninsert into cc(bug_id, who) values (%s, %s);" %(self.bug_id, cc)
- def update_versions(self):
- """ Update the versions table to account for the version on this bug """
- global versions_table
- if self.fields.has_key("Release") == 0 \
- or self.fields.has_key("Category") == 0:
- return
- curr_product = "gcc"
- curr_version = self.fields["Release"]
- if curr_version == "":
- return
- curr_version = convert_gccver_to_ver (curr_version)
- if versions_table.has_key(curr_product) == 0:
- versions_table[curr_product] = []
- for version in versions_table[curr_product]:
- if version == curr_version:
- return
- versions_table[curr_product].append(curr_version)
- def translate_pr(self):
- """ Transform a GNATS PR into a Bugzilla bug """
- self.fields = self.gnatsfields
- if (self.fields.has_key("Organization") == 0) \
- or self.fields["Organization"].find("GCC"):
- self.fields["Originator"] = ""
- self.fields["Organization"] = ""
- self.fields["Organization"].lstrip()
- if (self.fields.has_key("Release") == 0) \
- or self.fields["Release"] == "" \
- or self.fields["Release"].find("unknown-1.0") != -1:
- self.fields["Release"]="unknown"
- if self.fields.has_key("Responsible"):
- result = re.search(r"""\w+""", self.fields["Responsible"])
- self.fields["Responsible"] = "%s%s" % (result.group(0), "@gcc.gnu.org")
- self.fields["gcchost"] = ""
- self.fields["gcctarget"] = ""
- self.fields["gccbuild"] = ""
- if self.fields.has_key("Environment"):
- result = re.search("^host: (.+?)$", self.fields["Environment"],
- re.MULTILINE)
- if result is not None:
- self.fields["gcchost"] = result.group(1)
- result = re.search("^target: (.+?)$", self.fields["Environment"],
- re.MULTILINE)
- if result is not None:
- self.fields["gcctarget"] = result.group(1)
- result = re.search("^build: (.+?)$", self.fields["Environment"],
- re.MULTILINE)
- if result is not None:
- self.fields["gccbuild"] = result.group(1)
- self.fields["userid"] = get_userid(self.fields["Responsible"])
- self.fields["bug_severity"] = "normal"
- if self.fields["Class"] == "change-request":
- self.fields["bug_severity"] = "enhancement"
- elif self.fields.has_key("Severity"):
- if self.fields["Severity"] == "critical":
- self.fields["bug_severity"] = "critical"
- elif self.fields["Severity"] == "serious":
- self.fields["bug_severity"] = "major"
- elif self.fields.has_key("Synopsis"):
- if re.search("crash|assert", self.fields["Synopsis"]):
- self.fields["bug_severity"] = "critical"
- elif re.search("wrong|error", self.fields["Synopsis"]):
- self.fields["bug_severity"] = "major"
- self.fields["bug_severity"] = SqlQuote(self.fields["bug_severity"])
- self.fields["keywords"] = 0
- if keywordids.has_key(self.fields["Class"]):
- self.fields["keywords"] = self.fields["Class"]
- self.fields["keywordid"] = keywordids[self.fields["Class"]]
- self.fields["keywords"] = SqlQuote(self.fields["keywords"])
- self.fields["priority"] = "P1"
- if self.fields.has_key("Severity") and self.fields.has_key("Priority"):
- severity = self.fields["Severity"]
- priority = self.fields["Priority"]
- if severity == "critical":
- if priority == "high":
- self.fields["priority"] = "P1"
- else:
- self.fields["priority"] = "P2"
- elif severity == "serious":
- if priority == "low":
- self.fields["priority"] = "P4"
- else:
- self.fields["priority"] = "P3"
- else:
- if priority == "high":
- self.fields["priority"] = "P4"
- else:
- self.fields["priority"] = "P5"
- self.fields["priority"] = SqlQuote(self.fields["priority"])
- state = self.fields["State"]
- if (state == "open" or state == "analyzed") and self.fields["userid"] != 3:
- self.fields["bug_status"] = "ASSIGNED"
- self.fields["resolution"] = ""
- elif state == "feedback":
- self.fields["bug_status"] = "WAITING"
- self.fields["resolution"] = ""
- elif state == "closed":
- self.fields["bug_status"] = "CLOSED"
- if self.fields.has_key("Class"):
- theclass = self.fields["Class"]
- if theclass.find("duplicate") != -1:
- self.fields["resolution"]="DUPLICATE"
- elif theclass.find("mistaken") != -1:
- self.fields["resolution"]="INVALID"
- else:
- self.fields["resolution"]="FIXED"
- else:
- self.fields["resolution"]="FIXED"
- elif state == "suspended":
- self.fields["bug_status"] = "SUSPENDED"
- self.fields["resolution"] = ""
- elif state == "analyzed" and self.fields["userid"] == 3:
- self.fields["bug_status"] = "NEW"
- self.fields["resolution"] = ""
- else:
- self.fields["bug_status"] = "UNCONFIRMED"
- self.fields["resolution"] = ""
- self.fields["bug_status"] = SqlQuote(self.fields["bug_status"])
- self.fields["resolution"] = SqlQuote(self.fields["resolution"])
- self.fields["creation_ts"] = ""
- if self.fields.has_key("Arrival-Date") and self.fields["Arrival-Date"] != "":
- self.fields["creation_ts"] = unixdate2datetime(self.fields["Arrival-Date"])
- self.fields["creation_ts"] = SqlQuote(self.fields["creation_ts"])
- self.fields["delta_ts"] = ""
- if self.fields.has_key("Audit-Trail"):
- result = lastdatere.findall(self.fields["Audit-Trail"])
- result.reverse()
- if len(result) > 0:
- self.fields["delta_ts"] = unixdate2timestamp(result[0])
- if self.fields["delta_ts"] == "":
- if self.fields.has_key("Arrival-Date") and self.fields["Arrival-Date"] != "":
- self.fields["delta_ts"] = unixdate2timestamp(self.fields["Arrival-Date"])
- self.fields["delta_ts"] = SqlQuote(self.fields["delta_ts"])
- self.fields["short_desc"] = SqlQuote(self.fields["Synopsis"])
- if self.fields.has_key("Reply-To") and self.fields["Reply-To"] != "":
- self.fields["reporter"] = get_userid(self.fields["Reply-To"])
- elif self.fields.has_key("Mail-Header"):
- result = re.search(r"""From .*?([\w.]+@[\w.]+)""", self.fields["Mail-Header"])
- if result:
- self.fields["reporter"] = get_userid(result.group(1))
- else:
- self.fields["reporter"] = get_userid(gnats_username)
- else:
- self.fields["reporter"] = get_userid(gnats_username)
- long_desc = self.fields["Description"]
- long_desc2 = ""
- for field in ["Release", "Environment", "How-To-Repeat"]:
- if self.fields.has_key(field) and self.fields[field] != "":
- long_desc += ("\n\n%s:\n" % field) + self.fields[field]
- if self.fields.has_key("Fix") and self.fields["Fix"] != "":
- long_desc2 = "Fix:\n" + self.fields["Fix"]
- if self.need_unformatted == 1 and self.fields["Unformatted"] != "":
- long_desc += "\n\nUnformatted:\n" + self.fields["Unformatted"]
- if long_desc != "":
- self.long_descs.append((self.bug_id, self.fields["reporter"],
- self.fields["creation_ts"],
- SqlQuote(long_desc)))
- if long_desc2 != "":
- self.long_descs.append((self.bug_id, self.fields["reporter"],
- self.fields["creation_ts"],
- SqlQuote(long_desc2)))
- for field in ["gcchost", "gccbuild", "gcctarget"]:
- self.fields[field] = SqlQuote(self.fields[field])
- self.fields["version"] = ""
- if self.fields["Release"] != "":
- self.fields["version"] = convert_gccver_to_ver (self.fields["Release"])
- self.fields["version"] = SqlQuote(self.fields["version"])
- self.fields["product"] = SqlQuote("gcc")
- self.fields["component"] = "invalid"
- if self.fields.has_key("Category"):
- self.fields["component"] = self.fields["Category"]
- self.fields["component"] = SqlQuote(self.fields["component"])
- self.fields["target_milestone"] = "---"
- if self.fields["version"].find("3.4") != -1:
- self.fields["target_milestone"] = "3.4"
- self.fields["target_milestone"] = SqlQuote(self.fields["target_milestone"])
- if self.fields["userid"] == 2:
- self.fields["userid"] = "\'NULL\'"
-
-class GNATSbug(object):
- """ Represents a single GNATS PR """
- def __init__(self, filename):
- self.attachments = []
- self.has_unformatted_attach = 0
- fp = open (filename)
- self.fields = self.parse_pr(fp.xreadlines())
- self.bug_id = int(self.fields["Number"])
- if self.fields.has_key("Unformatted"):
- self.find_gnatsweb_attachments()
- if self.fields.has_key("How-To-Repeat"):
- self.find_regular_attachments("How-To-Repeat")
- if self.fields.has_key("Fix"):
- self.find_regular_attachments("Fix")
-
- def get_attacher(fields):
- if fields.has_key("Reply-To") and fields["Reply-To"] != "":
- return get_userid(fields["Reply-To"])
- else:
- result = None
- if fields.has_key("Mail-Header"):
- result = re.search(r"""From .*?([\w.]+\@[\w.]+)""",
- fields["Mail-Header"])
- if result is not None:
- reporter = get_userid(result.group(1))
- else:
- reporter = get_userid(gnats_username)
- get_attacher = staticmethod(get_attacher)
- def find_regular_attachments(self, which):
- fields = self.fields
- while re.search("^begin [0-7]{3}", fields[which],
- re.DOTALL | re.MULTILINE):
- outfp = cStringIO.StringIO()
- infp = cStringIO.StringIO(fields[which])
- filename, start, end = specialuu.decode(infp, outfp, quiet=0)
- fields[which]=fields[which].replace(fields[which][start:end],
- "See attachments for %s\n" % filename)
- self.attachments.append((filename, outfp.getvalue(),
- self.get_attacher(fields)))
-
- def decode_gnatsweb_attachment(self, attachment):
- result = re.split(r"""\n\n""", attachment, 1)
- if len(result) == 1:
- return -1
- envelope, body = result
- envelope = uselessre.split(envelope)
- envelope.pop(0)
- # Turn the list of key, value into a dict of key => value
- attachinfo = dict([(envelope[i], envelope[i+1]) for i in xrange(0,len(envelope),2)])
- for x in attachinfo.keys():
- attachinfo[x] = attachinfo[x].rstrip()
- if (attachinfo.has_key("Content-Type") == 0) or \
- (attachinfo.has_key("Content-Disposition") == 0):
- raise ValueError, "Unable to parse file attachment"
- result = dispositionre.search(attachinfo["Content-Disposition"])
- filename = result.group(2)
- filename = re.sub(".*/","", filename)
- filename = re.sub(".*\\\\","", filename)
- attachinfo["filename"]=filename
- result = re.search("""(\S+);.*""", attachinfo["Content-Type"])
- if result is not None:
- attachinfo["Content-Type"] = result.group(1)
- if attachinfo.has_key("Content-Transfer-Encoding"):
- if attachinfo["Content-Transfer-Encoding"] == "base64":
- attachinfo["data"] = base64.decodestring(body)
- else:
- attachinfo["data"]=body
-
- return (attachinfo["filename"], attachinfo["data"],
- self.get_attacher(self.fields))
-
- def find_gnatsweb_attachments(self):
- fields = self.fields
- attachments = re.split(attachment_delimiter, fields["Unformatted"])
- fields["Unformatted"] = attachments.pop(0)
- for attachment in attachments:
- result = self.decode_gnatsweb_attachment (attachment)
- if result != -1:
- self.attachments.append(result)
- self.has_unformatted_attach = 1
- def parse_pr(lines):
- #fields = {"envelope":[]}
- fields = {"envelope":array.array("c")}
- hdrmulti = "envelope"
- for line in lines:
- line = line.rstrip('\n')
- line += '\n'
- result = gnatfieldre.search(line)
- if result is None:
- if hdrmulti != "":
- if fields.has_key(hdrmulti):
- #fields[hdrmulti].append(line)
- fields[hdrmulti].fromstring(line)
- else:
- #fields[hdrmulti] = [line]
- fields[hdrmulti] = array.array("c", line)
- continue
- hdr, arg = result.groups()
- ghdr = "*not valid*"
- result = fieldnamere.search(hdr)
- if result != None:
- ghdr = result.groups()[0]
- if ghdr in fieldnames:
- if multilinefields.has_key(ghdr):
- hdrmulti = ghdr
- #fields[ghdr] = [""]
- fields[ghdr] = array.array("c")
- else:
- hdrmulti = ""
- #fields[ghdr] = [arg]
- fields[ghdr] = array.array("c", arg)
- elif hdrmulti != "":
- #fields[hdrmulti].append(line)
- fields[hdrmulti].fromstring(line)
- if hdrmulti == "envelope" and \
- (hdr == "Reply-To" or hdr == "From" \
- or hdr == "X-GNATS-Notify"):
- arg = fix_email_addrs(arg)
- #fields[hdr] = [arg]
- fields[hdr] = array.array("c", arg)
- if fields.has_key("Reply-To") and len(fields["Reply-To"]) > 0:
- fields["Reply-To"] = fields["Reply-To"]
- else:
- fields["Reply-To"] = fields["From"]
- if fields.has_key("From"):
- del fields["From"]
- if fields.has_key("X-GNATS-Notify") == 0:
- fields["X-GNATS-Notify"] = array.array("c")
- #fields["X-GNATS-Notify"] = ""
- for x in fields.keys():
- fields[x] = fields[x].tostring()
- #fields[x] = "".join(fields[x])
- for x in fields.keys():
- if multilinefields.has_key(x):
- fields[x] = fields[x].rstrip()
-
- return fields
- parse_pr = staticmethod(parse_pr)
-load_index("%s/gnats-adm/index" % gnats_db_dir)
-load_categories("%s/gnats-adm/categories" % gnats_db_dir)
-load_responsible("%s/gnats-adm/responsible" % gnats_db_dir)
-get_userid(gnats_username)
-get_userid(unassigned_username)
-for x in pr_list:
- print "Processing %s..." % x
- a = GNATSbug ("%s/%s" % (gnats_db_dir, x))
- b = Bugzillabug(a)
-write_non_bug_tables()
-outfile.close()
diff --git a/Websites/bugs.webkit.org/contrib/gnatsparse/magic.py b/Websites/bugs.webkit.org/contrib/gnatsparse/magic.py
deleted file mode 100755
index 049a7e1..0000000
--- a/Websites/bugs.webkit.org/contrib/gnatsparse/magic.py
+++ /dev/null
@@ -1,712 +0,0 @@
-# Found on a russian zope mailing list, and modified to fix bugs in parsing
-# the magic file and string making
-# -- Daniel Berlin <dberlin@dberlin.org>
-import sys, struct, time, re, exceptions, pprint, stat, os, pwd, grp
-
-_mew = 0
-
-# _magic='/tmp/magic'
-# _magic='/usr/share/magic.mime'
-_magic='/usr/share/magic.mime'
-mime = 1
-
-_ldate_adjust = lambda x: time.mktime( time.gmtime(x) )
-
-BUFFER_SIZE = 1024 * 128 # 128K should be enough...
-
-class MagicError(exceptions.Exception): pass
-
-def _handle(fmt='@x',adj=None): return fmt, struct.calcsize(fmt), adj
-
-KnownTypes = {
- # 'byte':_handle('@b'),
- 'byte':_handle('@B'),
- 'ubyte':_handle('@B'),
-
- 'string':('s',0,None),
- 'pstring':_handle('p'),
-
-# 'short':_handle('@h'),
-# 'beshort':_handle('>h'),
-# 'leshort':_handle('<h'),
- 'short':_handle('@H'),
- 'beshort':_handle('>H'),
- 'leshort':_handle('<H'),
- 'ushort':_handle('@H'),
- 'ubeshort':_handle('>H'),
- 'uleshort':_handle('<H'),
-
- 'long':_handle('@l'),
- 'belong':_handle('>l'),
- 'lelong':_handle('<l'),
- 'ulong':_handle('@L'),
- 'ubelong':_handle('>L'),
- 'ulelong':_handle('<L'),
-
- 'date':_handle('=l'),
- 'bedate':_handle('>l'),
- 'ledate':_handle('<l'),
- 'ldate':_handle('=l',_ldate_adjust),
- 'beldate':_handle('>l',_ldate_adjust),
- 'leldate':_handle('<l',_ldate_adjust),
-}
-
-_mew_cnt = 0
-def mew(x):
- global _mew_cnt
- if _mew :
- if x=='.' :
- _mew_cnt += 1
- if _mew_cnt % 64 == 0 : sys.stderr.write( '\n' )
- sys.stderr.write( '.' )
- else:
- sys.stderr.write( '\b'+x )
-
-def has_format(s):
- n = 0
- l = None
- for c in s :
- if c == '%' :
- if l == '%' : n -= 1
- else : n += 1
- l = c
- return n
-
-def read_asciiz(file,size=None,pos=None):
- s = []
- if pos :
- mew('s')
- file.seek( pos, 0 )
- mew('z')
- if size is not None :
- s = [file.read( size ).split('\0')[0]]
- else:
- while 1 :
- c = file.read(1)
- if (not c) or (ord(c)==0) or (c=='\n') : break
- s.append (c)
- mew('Z')
- return ''.join(s)
-
-def a2i(v,base=0):
- if v[-1:] in 'lL' : v = v[:-1]
- return int( v, base )
-
-_cmap = {
- '\\' : '\\',
- '0' : '\0',
-}
-for c in range(ord('a'),ord('z')+1) :
- try : e = eval('"\\%c"' % chr(c))
- except ValueError : pass
- else : _cmap[chr(c)] = e
-else:
- del c
- del e
-
-def make_string(s):
- return eval( '"'+s.replace('"','\\"')+'"')
-
-class MagicTestError(MagicError): pass
-
-class MagicTest:
- def __init__(self,offset,mtype,test,message,line=None,level=None):
- self.line, self.level = line, level
- self.mtype = mtype
- self.mtest = test
- self.subtests = []
- self.mask = None
- self.smod = None
- self.nmod = None
- self.offset, self.type, self.test, self.message = \
- offset,mtype,test,message
- if self.mtype == 'true' : return # XXX hack to enable level skips
- if test[-1:]=='\\' and test[-2:]!='\\\\' :
- self.test += 'n' # looks like someone wanted EOL to match?
- if mtype[:6]=='string' :
- if '/' in mtype : # for strings
- self.type, self.smod = \
- mtype[:mtype.find('/')], mtype[mtype.find('/')+1:]
- else:
- for nm in '&+-' :
- if nm in mtype : # for integer-based
- self.nmod, self.type, self.mask = (
- nm,
- mtype[:mtype.find(nm)],
- # convert mask to int, autodetect base
- int( mtype[mtype.find(nm)+1:], 0 )
- )
- break
- self.struct, self.size, self.cast = KnownTypes[ self.type ]
- def __str__(self):
- return '%s %s %s %s' % (
- self.offset, self.mtype, self.mtest, self.message
- )
- def __repr__(self):
- return 'MagicTest(%s,%s,%s,%s,line=%s,level=%s,subtests=\n%s%s)' % (
- `self.offset`, `self.mtype`, `self.mtest`, `self.message`,
- `self.line`, `self.level`,
- '\t'*self.level, pprint.pformat(self.subtests)
- )
- def run(self,file):
- result = ''
- do_close = 0
- try:
- if type(file) == type('x') :
- file = open( file, 'r', BUFFER_SIZE )
- do_close = 1
-# else:
-# saved_pos = file.tell()
- if self.mtype != 'true' :
- data = self.read(file)
- last = file.tell()
- else:
- data = last = None
- if self.check( data ) :
- result = self.message+' '
- if has_format( result ) : result %= data
- for test in self.subtests :
- m = test.run(file)
- if m is not None : result += m
- return make_string( result )
- finally:
- if do_close :
- file.close()
-# else:
-# file.seek( saved_pos, 0 )
- def get_mod_and_value(self):
- if self.type[-6:] == 'string' :
- # "something like\tthis\n"
- if self.test[0] in '=<>' :
- mod, value = self.test[0], make_string( self.test[1:] )
- else:
- mod, value = '=', make_string( self.test )
- else:
- if self.test[0] in '=<>&^' :
- mod, value = self.test[0], a2i(self.test[1:])
- elif self.test[0] == 'x':
- mod = self.test[0]
- value = 0
- else:
- mod, value = '=', a2i(self.test)
- return mod, value
- def read(self,file):
- mew( 's' )
- file.seek( self.offset(file), 0 ) # SEEK_SET
- mew( 'r' )
- try:
- data = rdata = None
- # XXX self.size might be 0 here...
- if self.size == 0 :
- # this is an ASCIIZ string...
- size = None
- if self.test != '>\\0' : # magic's hack for string read...
- value = self.get_mod_and_value()[1]
- size = (value=='\0') and None or len(value)
- rdata = data = read_asciiz( file, size=size )
- else:
- rdata = file.read( self.size )
- if not rdata or (len(rdata)!=self.size) : return None
- data = struct.unpack( self.struct, rdata )[0] # XXX hack??
- except:
- print >>sys.stderr, self
- print >>sys.stderr, '@%s struct=%s size=%d rdata=%s' % (
- self.offset, `self.struct`, self.size,`rdata`)
- raise
- mew( 'R' )
- if self.cast : data = self.cast( data )
- if self.mask :
- try:
- if self.nmod == '&' : data &= self.mask
- elif self.nmod == '+' : data += self.mask
- elif self.nmod == '-' : data -= self.mask
- else: raise MagicTestError(self.nmod)
- except:
- print >>sys.stderr,'data=%s nmod=%s mask=%s' % (
- `data`, `self.nmod`, `self.mask`
- )
- raise
- return data
- def check(self,data):
- mew('.')
- if self.mtype == 'true' :
- return '' # not None !
- mod, value = self.get_mod_and_value()
- if self.type[-6:] == 'string' :
- # "something like\tthis\n"
- if self.smod :
- xdata = data
- if 'b' in self.smod : # all blanks are optional
- xdata = ''.join( data.split() )
- value = ''.join( value.split() )
- if 'c' in self.smod : # all blanks are optional
- xdata = xdata.upper()
- value = value.upper()
- # if 'B' in self.smod : # compact blanks
- ### XXX sorry, i don't understand this :-(
- # data = ' '.join( data.split() )
- # if ' ' not in data : return None
- else:
- xdata = data
- try:
- if mod == '=' : result = data == value
- elif mod == '<' : result = data < value
- elif mod == '>' : result = data > value
- elif mod == '&' : result = data & value
- elif mod == '^' : result = (data & (~value)) == 0
- elif mod == 'x' : result = 1
- else : raise MagicTestError(self.test)
- if result :
- zdata, zval = `data`, `value`
- if self.mtype[-6:]!='string' :
- try: zdata, zval = hex(data), hex(value)
- except: zdata, zval = `data`, `value`
- if 0 : print >>sys.stderr, '%s @%s %s:%s %s %s => %s (%s)' % (
- '>'*self.level, self.offset,
- zdata, self.mtype, `mod`, zval, `result`,
- self.message
- )
- return result
- except:
- print >>sys.stderr,'mtype=%s data=%s mod=%s value=%s' % (
- `self.mtype`, `data`, `mod`, `value`
- )
- raise
- def add(self,mt):
- if not isinstance(mt,MagicTest) :
- raise MagicTestError((mt,'incorrect subtest type %s'%(type(mt),)))
- if mt.level == self.level+1 :
- self.subtests.append( mt )
- elif self.subtests :
- self.subtests[-1].add( mt )
- elif mt.level > self.level+1 :
- # it's possible to get level 3 just after level 1 !!! :-(
- level = self.level + 1
- while level < mt.level :
- xmt = MagicTest(None,'true','x','',line=self.line,level=level)
- self.add( xmt )
- level += 1
- else:
- self.add( mt ) # retry...
- else:
- raise MagicTestError((mt,'incorrect subtest level %s'%(`mt.level`,)))
- def last_test(self):
- return self.subtests[-1]
-#end class MagicTest
-
-class OffsetError(MagicError): pass
-
-class Offset:
- pos_format = {'b':'<B','B':'>B','s':'<H','S':'>H','l':'<I','L':'>I',}
- pattern0 = re.compile(r''' # mere offset
- ^
- &? # possible ampersand
- ( 0 # just zero
- | [1-9]{1,1}[0-9]* # decimal
- | 0[0-7]+ # octal
- | 0x[0-9a-f]+ # hex
- )
- $
- ''', re.X|re.I
- )
- pattern1 = re.compile(r''' # indirect offset
- ^\(
- (?P<base>&?0 # just zero
- |&?[1-9]{1,1}[0-9]* # decimal
- |&?0[0-7]* # octal
- |&?0x[0-9A-F]+ # hex
- )
- (?P<type>
- \. # this dot might be alone
- [BSL]? # one of this chars in either case
- )?
- (?P<sign>
- [-+]{0,1}
- )?
- (?P<off>0 # just zero
- |[1-9]{1,1}[0-9]* # decimal
- |0[0-7]* # octal
- |0x[0-9a-f]+ # hex
- )?
- \)$''', re.X|re.I
- )
- def __init__(self,s):
- self.source = s
- self.value = None
- self.relative = 0
- self.base = self.type = self.sign = self.offs = None
- m = Offset.pattern0.match( s )
- if m : # just a number
- if s[0] == '&' :
- self.relative, self.value = 1, int( s[1:], 0 )
- else:
- self.value = int( s, 0 )
- return
- m = Offset.pattern1.match( s )
- if m : # real indirect offset
- try:
- self.base = m.group('base')
- if self.base[0] == '&' :
- self.relative, self.base = 1, int( self.base[1:], 0 )
- else:
- self.base = int( self.base, 0 )
- if m.group('type') : self.type = m.group('type')[1:]
- self.sign = m.group('sign')
- if m.group('off') : self.offs = int( m.group('off'), 0 )
- if self.sign == '-' : self.offs = 0 - self.offs
- except:
- print >>sys.stderr, '$$', m.groupdict()
- raise
- return
- raise OffsetError(`s`)
- def __call__(self,file=None):
- if self.value is not None : return self.value
- pos = file.tell()
- try:
- if not self.relative : file.seek( self.offset, 0 )
- frmt = Offset.pos_format.get( self.type, 'I' )
- size = struct.calcsize( frmt )
- data = struct.unpack( frmt, file.read( size ) )
- if self.offs : data += self.offs
- return data
- finally:
- file.seek( pos, 0 )
- def __str__(self): return self.source
- def __repr__(self): return 'Offset(%s)' % `self.source`
-#end class Offset
-
-class MagicFileError(MagicError): pass
-
-class MagicFile:
- def __init__(self,filename=_magic):
- self.file = None
- self.tests = []
- self.total_tests = 0
- self.load( filename )
- self.ack_tests = None
- self.nak_tests = None
- def __del__(self):
- self.close()
- def load(self,filename=None):
- self.open( filename )
- self.parse()
- self.close()
- def open(self,filename=None):
- self.close()
- if filename is not None :
- self.filename = filename
- self.file = open( self.filename, 'r', BUFFER_SIZE )
- def close(self):
- if self.file :
- self.file.close()
- self.file = None
- def parse(self):
- line_no = 0
- for line in self.file.xreadlines() :
- line_no += 1
- if not line or line[0]=='#' : continue
- line = line.lstrip().rstrip('\r\n')
- if not line or line[0]=='#' : continue
- try:
- x = self.parse_line( line )
- if x is None :
- print >>sys.stderr, '#[%04d]#'%line_no, line
- continue
- except:
- print >>sys.stderr, '###[%04d]###'%line_no, line
- raise
- self.total_tests += 1
- level, offset, mtype, test, message = x
- new_test = MagicTest(offset,mtype,test,message,
- line=line_no,level=level)
- try:
- if level == 0 :
- self.tests.append( new_test )
- else:
- self.tests[-1].add( new_test )
- except:
- if 1 :
- print >>sys.stderr, 'total tests=%s' % (
- `self.total_tests`,
- )
- print >>sys.stderr, 'level=%s' % (
- `level`,
- )
- print >>sys.stderr, 'tests=%s' % (
- pprint.pformat(self.tests),
- )
- raise
- else:
- while self.tests[-1].level > 0 :
- self.tests.pop()
- def parse_line(self,line):
- # print >>sys.stderr, 'line=[%s]' % line
- if (not line) or line[0]=='#' : return None
- level = 0
- offset = mtype = test = message = ''
- mask = None
- # get optional level (count leading '>')
- while line and line[0]=='>' :
- line, level = line[1:], level+1
- # get offset
- while line and not line[0].isspace() :
- offset, line = offset+line[0], line[1:]
- try:
- offset = Offset(offset)
- except:
- print >>sys.stderr, 'line=[%s]' % line
- raise
- # skip spaces
- line = line.lstrip()
- # get type
- c = None
- while line :
- last_c, c, line = c, line[0], line[1:]
- if last_c!='\\' and c.isspace() :
- break # unescaped space - end of field
- else:
- mtype += c
- if last_c == '\\' :
- c = None # don't fuck my brain with sequential backslashes
- # skip spaces
- line = line.lstrip()
- # get test
- c = None
- while line :
- last_c, c, line = c, line[0], line[1:]
- if last_c!='\\' and c.isspace() :
- break # unescaped space - end of field
- else:
- test += c
- if last_c == '\\' :
- c = None # don't fuck my brain with sequential backslashes
- # skip spaces
- line = line.lstrip()
- # get message
- message = line
- if mime and line.find("\t") != -1:
- message=line[0:line.find("\t")]
- #
- # print '>>', level, offset, mtype, test, message
- return level, offset, mtype, test, message
- def detect(self,file):
- self.ack_tests = 0
- self.nak_tests = 0
- answers = []
- for test in self.tests :
- message = test.run( file )
- if message :
- self.ack_tests += 1
- answers.append( message )
- else:
- self.nak_tests += 1
- if answers :
- return '; '.join( answers )
-#end class MagicFile
-
-def username(uid):
- try:
- return pwd.getpwuid( uid )[0]
- except:
- return '#%s'%uid
-
-def groupname(gid):
- try:
- return grp.getgrgid( gid )[0]
- except:
- return '#%s'%gid
-
-def get_file_type(fname,follow):
- t = None
- if not follow :
- try:
- st = os.lstat( fname ) # stat that entry, don't follow links!
- except os.error, why :
- pass
- else:
- if stat.S_ISLNK(st[stat.ST_MODE]) :
- t = 'symbolic link'
- try:
- lnk = os.readlink( fname )
- except:
- t += ' (unreadable)'
- else:
- t += ' to '+lnk
- if t is None :
- try:
- st = os.stat( fname )
- except os.error, why :
- return "can't stat `%s' (%s)." % (why.filename,why.strerror)
-
- dmaj, dmin = (st.st_rdev>>8)&0x0FF, st.st_rdev&0x0FF
-
- if 0 : pass
- elif stat.S_ISSOCK(st.st_mode) : t = 'socket'
- elif stat.S_ISLNK (st.st_mode) : t = follow and 'symbolic link' or t
- elif stat.S_ISREG (st.st_mode) : t = 'file'
- elif stat.S_ISBLK (st.st_mode) : t = 'block special (%d/%d)'%(dmaj,dmin)
- elif stat.S_ISDIR (st.st_mode) : t = 'directory'
- elif stat.S_ISCHR (st.st_mode) : t = 'character special (%d/%d)'%(dmaj,dmin)
- elif stat.S_ISFIFO(st.st_mode) : t = 'pipe'
- else: t = '<unknown>'
-
- if st.st_mode & stat.S_ISUID :
- t = 'setuid(%d=%s) %s'%(st.st_uid,username(st.st_uid),t)
- if st.st_mode & stat.S_ISGID :
- t = 'setgid(%d=%s) %s'%(st.st_gid,groupname(st.st_gid),t)
- if st.st_mode & stat.S_ISVTX :
- t = 'sticky '+t
-
- return t
-
-HELP = '''%s [options] [files...]
-
-Options:
-
- -?, --help -- this help
- -m, --magic=<file> -- use this magic <file> instead of %s
- -f, --files=<namefile> -- read filenames for <namefile>
-* -C, --compile -- write "compiled" magic file
- -b, --brief -- don't prepend filenames to output lines
-+ -c, --check -- check the magic file
- -i, --mime -- output MIME types
-* -k, --keep-going -- don't stop st the first match
- -n, --flush -- flush stdout after each line
- -v, --verson -- print version and exit
-* -z, --compressed -- try to look inside compressed files
- -L, --follow -- follow symlinks
- -s, --special -- don't skip special files
-
-* -- not implemented so far ;-)
-+ -- implemented, but in another way...
-'''
-
-def main():
- import getopt
- global _magic
- try:
- brief = 0
- flush = 0
- follow= 0
- mime = 0
- check = 0
- special=0
- try:
- opts, args = getopt.getopt(
- sys.argv[1:],
- '?m:f:CbciknvzLs',
- ( 'help',
- 'magic=',
- 'names=',
- 'compile',
- 'brief',
- 'check',
- 'mime',
- 'keep-going',
- 'flush',
- 'version',
- 'compressed',
- 'follow',
- 'special',
- )
- )
- except getopt.error, why:
- print >>sys.stderr, sys.argv[0], why
- return 1
- else:
- files = None
- for o,v in opts :
- if o in ('-?','--help'):
- print HELP % (
- sys.argv[0],
- _magic,
- )
- return 0
- elif o in ('-f','--files='):
- files = v
- elif o in ('-m','--magic='):
- _magic = v[:]
- elif o in ('-C','--compile'):
- pass
- elif o in ('-b','--brief'):
- brief = 1
- elif o in ('-c','--check'):
- check = 1
- elif o in ('-i','--mime'):
- mime = 1
- if os.path.exists( _magic+'.mime' ) :
- _magic += '.mime'
- print >>sys.stderr,sys.argv[0]+':',\
- "Using regular magic file `%s'" % _magic
- elif o in ('-k','--keep-going'):
- pass
- elif o in ('-n','--flush'):
- flush = 1
- elif o in ('-v','--version'):
- print 'VERSION'
- return 0
- elif o in ('-z','--compressed'):
- pass
- elif o in ('-L','--follow'):
- follow = 1
- elif o in ('-s','--special'):
- special = 1
- else:
- if files :
- files = map(lambda x: x.strip(), v.split(','))
- if '-' in files and '-' in args :
- error( 1, 'cannot use STDIN simultaneously for file list and data' )
- for file in files :
- for name in (
- (file=='-')
- and sys.stdin
- or open(file,'r',BUFFER_SIZE)
- ).xreadlines():
- name = name.strip()
- if name not in args :
- args.append( name )
- try:
- if check : print >>sys.stderr, 'Loading magic database...'
- t0 = time.time()
- m = MagicFile(_magic)
- t1 = time.time()
- if check :
- print >>sys.stderr, \
- m.total_tests, 'tests loaded', \
- 'for', '%.2f' % (t1-t0), 'seconds'
- print >>sys.stderr, len(m.tests), 'tests at top level'
- return 0 # XXX "shortened" form ;-)
-
- mlen = max( map(len, args) )+1
- for arg in args :
- if not brief : print (arg + ':').ljust(mlen),
- ftype = get_file_type( arg, follow )
- if (special and ftype.find('special')>=0) \
- or ftype[-4:] == 'file' :
- t0 = time.time()
- try:
- t = m.detect( arg )
- except (IOError,os.error), why:
- t = "can't read `%s' (%s)" % (why.filename,why.strerror)
- if ftype[-4:] == 'file' : t = ftype[:-4] + t
- t1 = time.time()
- print t and t or 'data'
- if 0 : print \
- '#\t%d tests ok, %d tests failed for %.2f seconds'%\
- (m.ack_tests, m.nak_tests, t1-t0)
- else:
- print mime and 'application/x-not-regular-file' or ftype
- if flush : sys.stdout.flush()
- # print >>sys.stderr, 'DONE'
- except:
- if check : return 1
- raise
- else:
- return 0
- finally:
- pass
-
-if __name__ == '__main__' :
- sys.exit( main() )
-# vim:ai
-# EOF #
diff --git a/Websites/bugs.webkit.org/contrib/gnatsparse/specialuu.py b/Websites/bugs.webkit.org/contrib/gnatsparse/specialuu.py
deleted file mode 100755
index b729d9c..0000000
--- a/Websites/bugs.webkit.org/contrib/gnatsparse/specialuu.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#! /usr/bin/env python2.2
-
-# Copyright 1994 by Lance Ellinghouse
-# Cathedral City, California Republic, United States of America.
-# All Rights Reserved
-# Permission to use, copy, modify, and distribute this software and its
-# documentation for any purpose and without fee is hereby granted,
-# provided that the above copyright notice appear in all copies and that
-# both that copyright notice and this permission notice appear in
-# supporting documentation, and that the name of Lance Ellinghouse
-# not be used in advertising or publicity pertaining to distribution
-# of the software without specific, written prior permission.
-# LANCE ELLINGHOUSE DISCLAIMS ALL WARRANTIES WITH REGARD TO
-# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
-# FITNESS, IN NO EVENT SHALL LANCE ELLINGHOUSE CENTRUM BE LIABLE
-# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-# Modified by Jack Jansen, CWI, July 1995:
-# - Use binascii module to do the actual line-by-line conversion
-# between ascii and binary. This results in a 1000-fold speedup. The C
-# version is still 5 times faster, though.
-# - Arguments more compliant with python standard
-
-"""Implementation of the UUencode and UUdecode functions.
-
-encode(in_file, out_file [,name, mode])
-decode(in_file [, out_file, mode])
-"""
-
-import binascii
-import os
-import sys
-from types import StringType
-
-__all__ = ["Error", "decode"]
-
-class Error(Exception):
- pass
-
-def decode(in_file, out_file=None, mode=None, quiet=0):
- """Decode uuencoded file"""
- #
- # Open the input file, if needed.
- #
- if in_file == '-':
- in_file = sys.stdin
- elif isinstance(in_file, StringType):
- in_file = open(in_file)
- #
- # Read until a begin is encountered or we've exhausted the file
- #
- while 1:
- hdr = in_file.readline()
- if not hdr:
- raise Error, 'No valid begin line found in input file'
- if hdr[:5] != 'begin':
- continue
- hdrfields = hdr.split(" ", 2)
- if len(hdrfields) == 3 and hdrfields[0] == 'begin':
- try:
- int(hdrfields[1], 8)
- start_pos = in_file.tell() - len (hdr)
- break
- except ValueError:
- pass
- if out_file is None:
- out_file = hdrfields[2].rstrip()
- if os.path.exists(out_file):
- raise Error, 'Cannot overwrite existing file: %s' % out_file
- if mode is None:
- mode = int(hdrfields[1], 8)
- #
- # Open the output file
- #
- if out_file == '-':
- out_file = sys.stdout
- elif isinstance(out_file, StringType):
- fp = open(out_file, 'wb')
- try:
- os.path.chmod(out_file, mode)
- except AttributeError:
- pass
- out_file = fp
- #
- # Main decoding loop
- #
- s = in_file.readline()
- while s and s.strip() != 'end':
- try:
- data = binascii.a2b_uu(s)
- except binascii.Error, v:
- # Workaround for broken uuencoders by /Fredrik Lundh
- nbytes = (((ord(s[0])-32) & 63) * 4 + 5) / 3
- data = binascii.a2b_uu(s[:nbytes])
- if not quiet:
- sys.stderr.write("Warning: %s\n" % str(v))
- out_file.write(data)
- s = in_file.readline()
-# if not s:
- # raise Error, 'Truncated input file'
- return (hdrfields[2].rstrip(), start_pos, in_file.tell())
diff --git a/Websites/bugs.webkit.org/contrib/jb2bz.py b/Websites/bugs.webkit.org/contrib/jb2bz.py
old mode 100644
new mode 100755
index e2f5029..55cb056
--- a/Websites/bugs.webkit.org/contrib/jb2bz.py
+++ b/Websites/bugs.webkit.org/contrib/jb2bz.py
@@ -28,7 +28,7 @@
if not mimetypes.encodings_map.has_key('.bz2'):
mimetypes.encodings_map['.bz2'] = "bzip2"
-bug_status='NEW'
+bug_status='CONFIRMED'
component="default"
version=""
product="" # this is required, the rest of these are defaulted as above
@@ -265,8 +265,8 @@
Where OPTIONS are one or more of the following:
-h This help information.
- -s STATUS One of UNCONFIRMED, NEW, ASSIGNED, REOPENED, RESOLVED, VERIFIED, CLOSED
- (default is NEW)
+ -s STATUS One of UNCONFIRMED, CONFIRMED, IN_PROGRESS, RESOLVED, VERIFIED
+ (default is CONFIRMED)
-c COMPONENT The component to attach to each bug as it is important. This should be
valid component for the Product.
-v VERSION Version to assign to these defects.
@@ -285,7 +285,7 @@
for o,a in opts:
if o == "-s":
- if a in ('UNCONFIRMED','NEW','ASSIGNED','REOPENED','RESOLVED','VERIFIED','CLOSED'):
+ if a in ('UNCONFIRMED','CONFIRMED','IN_PROGRESS','RESOLVED','VERIFIED'):
bug_status = a
elif o == '-c':
component = a
diff --git a/Websites/bugs.webkit.org/contrib/merge-users.pl b/Websites/bugs.webkit.org/contrib/merge-users.pl
old mode 100644
new mode 100755
index d781298..385c511
--- a/Websites/bugs.webkit.org/contrib/merge-users.pl
+++ b/Websites/bugs.webkit.org/contrib/merge-users.pl
@@ -121,21 +121,13 @@
# where fooN is the column to update, and barN1, barN2, ... are
# the columns to take into account to avoid duplicated entries.
# Note that the barNM columns are optional.
-my $changes = {
- # Tables affecting bugs.
- bugs => ['assigned_to', 'reporter', 'qa_contact'],
- bugs_activity => ['who'],
- attachments => ['submitter_id'],
- flags => ['setter_id', 'requestee_id'],
+#
+# We set the tables that require custom stuff (multiple columns to check)
+# here, but the simple stuff is all handled below by bz_get_related_fks.
+my %changes = (
cc => ['who bug_id'],
- longdescs => ['who'],
- votes => ['who'],
# Tables affecting global behavior / other users.
- components => ['initialowner', 'initialqacontact'],
component_cc => ['user_id component_id'],
- quips => ['userid'],
- series => ['creator'],
- whine_events => ['owner_userid'],
watch => ['watcher watched', 'watched watcher'],
# Tables affecting the user directly.
namedqueries => ['userid name'],
@@ -143,17 +135,23 @@
user_group_map => ['user_id group_id isbless grant_type'],
email_setting => ['user_id relationship event'],
profile_setting => ['user_id setting_name'],
- profiles_activity => ['userid', 'who'], # Should activity be migrated?
# Only do it if mailto_type = 0, i.e is pointing to a user account!
# This requires to be done separately due to this condition.
whine_schedules => [], # ['mailto'],
+);
- # Delete all old records for these tables; no migration.
- logincookies => [], # ['userid'],
- tokens => [], # ['userid'],
- profiles => [], # ['userid'],
-};
+my $userid_fks = $dbh->bz_get_related_fks('profiles', 'userid');
+foreach my $item (@$userid_fks) {
+ my ($table, $column) = @$item;
+ $changes{$table} ||= [];
+ push(@{ $changes{$table} }, $column);
+}
+
+# Delete all old records for these tables; no migration.
+foreach my $table (qw(logincookies tokens profiles)) {
+ $changes{$table} = [];
+}
# Start the transaction
$dbh->bz_start_transaction();
@@ -163,8 +161,8 @@
$dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $old_id);
# Migrate records from old user to new user.
-foreach my $table (keys(%$changes)) {
- foreach my $column_list (@{$changes->{$table}}) {
+foreach my $table (keys %changes) {
+ foreach my $column_list (@{ $changes{$table} }) {
# Get all columns to consider. There is always at least
# one column given: the one to update.
my @columns = split(/[\s]+/, $column_list);
diff --git a/Websites/bugs.webkit.org/contrib/new-yui.sh b/Websites/bugs.webkit.org/contrib/new-yui.sh
new file mode 100755
index 0000000..6199c2a
--- /dev/null
+++ b/Websites/bugs.webkit.org/contrib/new-yui.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Updates the version of YUI used by Bugzilla. Just pass the path to
+# an unzipped yui release directory, like:
+#
+# contrib/new-yui.sh /path/to/yui-2.8.1/
+#
+rsync -av --delete $1/build/ js/yui/
+cd js/yui
+rm -rf editor/ yuiloader-dom-event/
+find -name '*.js' -not -name '*-min.js' -not -name '*-dom-event.js' \
+ -exec rm -f {} \;
+find -name '*-skin.css' -exec rm -f {} \;
+find -depth -path '*/assets' -not -path './assets' -exec rm -rf {} \;
+rm assets/skins/sam/sprite.psd
+rm assets/skins/sam/skin.css
+rmdir utilities
diff --git a/Websites/bugs.webkit.org/contrib/recode.pl b/Websites/bugs.webkit.org/contrib/recode.pl
index 1a7d6db..40de2cd3 100755
--- a/Websites/bugs.webkit.org/contrib/recode.pl
+++ b/Websites/bugs.webkit.org/contrib/recode.pl
@@ -24,10 +24,10 @@
use Bugzilla;
use Bugzilla::Constants;
+use Bugzilla::Util qw(detect_encoding);
use Digest::MD5 qw(md5_base64);
use Encode qw(encode decode resolve_alias is_utf8);
-use Encode::Guess;
use Getopt::Long;
use Pod::Usage;
@@ -72,61 +72,6 @@
return $truncated;
}
-sub do_guess {
- my ($data) = @_;
-
- my $encoding = detect($data);
- $encoding = resolve_alias($encoding) if $encoding;
-
- # Encode::Detect is bad at detecting certain charsets, but Encode::Guess
- # is better at them. Here's the details:
-
- # shiftjis, big5-eten, euc-kr, and euc-jp: (Encode::Detect
- # tends to accidentally mis-detect UTF-8 strings as being
- # these encodings.)
- my @utf8_accidental = qw(shiftjis big5-eten euc-kr euc-jp);
- if ($encoding && grep($_ eq $encoding, @utf8_accidental)) {
- $encoding = undef;
- my $decoder = guess_encoding($data, @utf8_accidental);
- $encoding = $decoder->name if ref $decoder;
- }
-
- # Encode::Detect sometimes mis-detects various ISO encodings as iso-8859-8,
- # but Encode::Guess can usually tell which one it is.
- if ($encoding && $encoding eq 'iso-8859-8') {
- my $decoded_as = guess_iso($data, 'iso-8859-8',
- # These are ordered this way because it gives the most
- # accurate results.
- qw(iso-8859-7 iso-8859-2));
- $encoding = $decoded_as if $decoded_as;
- }
-
- # Workaround for WebKit Bug 9630 which caused WebKit nightly builds
- # to send non-breaking space characters (0xA0) when submitting
- # textarea content to Bugzilla.
- if (!$encoding) {
- my $decoder = guess_encoding($data, qw(cp1252));
- $encoding = $decoder->name if ref $decoder;
- }
-
- return $encoding;
-}
-
-# A helper for do_guess.
-sub guess_iso {
- my ($data, $versus, @isos) = @_;
-
- my $encoding;
- foreach my $iso (@isos) {
- my $decoder = guess_encoding($data, ($iso, $versus));
- if (ref $decoder) {
- $encoding = $decoder->name if ref $decoder;
- last;
- }
- }
- return $encoding;
-}
-
sub is_valid_utf8 {
my ($str) = @_;
Encode::_utf8_on($str);
@@ -152,8 +97,6 @@
}
if ($switch{'guess'}) {
- # Encode::Detect::Detector doesn't seem to return a true value.
- # So we have to check if we can run detect.
if (!eval { require Encode::Detect::Detector }) {
my $root = ROOT_USER;
print STDERR <<EOT;
@@ -165,8 +108,6 @@
EOT
exit;
}
-
- import Encode::Detect::Detector qw(detect);
}
my %overrides;
@@ -266,7 +207,7 @@
my $encoding;
if ($switch{'guess'}) {
- $encoding = do_guess($data);
+ $encoding = detect_encoding($data);
# We only show failures if they don't appear to be
# ASCII.
diff --git a/Websites/bugs.webkit.org/contrib/sendbugmail.pl b/Websites/bugs.webkit.org/contrib/sendbugmail.pl
old mode 100644
new mode 100755
index 4042e87..fc6e2c2
--- a/Websites/bugs.webkit.org/contrib/sendbugmail.pl
+++ b/Websites/bugs.webkit.org/contrib/sendbugmail.pl
@@ -4,8 +4,6 @@
#
# Nick Barnes, Ravenbrook Limited, 2004-04-01.
#
-# $Id$
-#
# Bugzilla email script for Bugzilla 2.17.4 and later. Invoke this to send
# bugmail for a bug which has been changed directly in the database.
# This uses Bugzilla's own BugMail facility, and will email the
@@ -58,13 +56,14 @@
print STDERR "Changer \"$changer\" doesn't match email regular expression.\n";
usage();
}
-if(!login_to_id($changer)) {
- print STDERR "\"$changer\" is not a login ID.\n";
+my $changer_user = new Bugzilla::User({ name => $changer });
+unless ($changer_user) {
+ print STDERR "\"$changer\" is not a valid user.\n";
usage();
}
# Send the email.
-my $outputref = Bugzilla::BugMail::Send($bugnum, {'changer' => $changer });
+my $outputref = Bugzilla::BugMail::Send($bugnum, {'changer' => $changer_user });
# Report the results.
my $sent = scalar(@{$outputref->{sent}});
diff --git a/Websites/bugs.webkit.org/contrib/sendunsentbugmail.pl b/Websites/bugs.webkit.org/contrib/sendunsentbugmail.pl
old mode 100644
new mode 100755
index 8c55332..a24fda4
--- a/Websites/bugs.webkit.org/contrib/sendunsentbugmail.pl
+++ b/Websites/bugs.webkit.org/contrib/sendunsentbugmail.pl
@@ -35,7 +35,8 @@
'SELECT bug_id FROM bugs
WHERE lastdiffed IS NULL
OR lastdiffed < delta_ts
- AND delta_ts < NOW() - ' . $dbh->sql_interval(30, 'MINUTE') .
+ AND delta_ts < '
+ . $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE') .
' ORDER BY bug_id');
if (scalar(@$list) > 0) {
diff --git a/Websites/bugs.webkit.org/contrib/yp_nomail.sh b/Websites/bugs.webkit.org/contrib/yp_nomail.sh
deleted file mode 100755
index 961916c..0000000
--- a/Websites/bugs.webkit.org/contrib/yp_nomail.sh
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/sh
-# -*- Mode: ksh -*-
-##############################################################################
-# $Id$
-# yp_nomail
-#
-# Our mail admins got annoyed when bugzilla kept sending email
-# to people who'd had bugzilla entries and left the company. They
-# were no longer in the list of valid email users so it'd bounce.
-# Maintaining the 'data/nomail' file was a pain. Luckily, our UNIX
-# admins list all the users that ever were, but the people who've left
-# have a distinct marker in their password file. For example:
-#
-# fired:*LK*:2053:1010:You're Fired Dude:/home/loser:/bin/false
-#
-# This script takes advantage of the "*LK*" convention seen via
-# ypcat passwd and dumps those people into the nomail file. Any
-# manual additions are kept in a "nomail.(domainname)" file and
-# appended to the list of yp lockouts every night via Cron
-#
-# 58 23 * * * /export/bugzilla/contrib/yp_nomail.sh > /dev/null 2>&1
-#
-# Tak ( Mark Takacs ) 08/2000
-#
-# XXX: Maybe should crosscheck w/bugzilla users?
-##############################################################################
-
-####
-# Configure this section to suite yer installation
-####
-
-DOMAIN=`domainname`
-MOZILLA_HOME="/export/mozilla"
-BUGZILLA_HOME="${MOZILLA_HOME}/bugzilla"
-NOMAIL_DIR="${BUGZILLA_HOME}/data"
-NOMAIL="${NOMAIL_DIR}/nomail"
-NOMAIL_ETIME="${NOMAIL}.${DOMAIN}"
-NOMAIL_YP="${NOMAIL}.yp"
-FIRED_FLAG="\*LK\*"
-
-YPCAT="/usr/bin/ypcat"
-GREP="/usr/bin/grep"
-SORT="/usr/bin/sort"
-
-########################## no more config needed #################
-
-# This dir comes w/Bugzilla. WAY too paranoid
-if [ ! -d ${NOMAIL_DIR} ] ; then
- echo "Creating $date_dir"
- mkdir -p ${NOMAIL_DIR}
-fi
-
-#
-# Do some (more) paranoid checking
-#
-touch ${NOMAIL}
-if [ ! -w ${NOMAIL} ] ; then
- echo "Can't write nomail file: ${NOMAIL} -- exiting"
- exit
-fi
-if [ ! -r ${NOMAIL_ETIME} ] ; then
- echo "Can't access custom nomail file: ${NOMAIL_ETIME} -- skipping"
- NOMAIL_ETIME=""
-fi
-
-#
-# add all the people with '*LK*' password to the nomail list
-# XXX: maybe I should customize the *LK* string. Doh.
-#
-
-LOCKOUT=`$YPCAT passwd | $GREP "${FIRED_FLAG}" | cut -d: -f1 | sort > ${NOMAIL_YP}`
-`cat ${NOMAIL_YP} ${NOMAIL_ETIME} > ${NOMAIL}`
-
-exit
-
-
-# end
-
diff --git a/Websites/bugs.webkit.org/createaccount.cgi b/Websites/bugs.webkit.org/createaccount.cgi
index 8387e0e..31cf5a5 100755
--- a/Websites/bugs.webkit.org/createaccount.cgi
+++ b/Websites/bugs.webkit.org/createaccount.cgi
@@ -31,47 +31,30 @@
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
-use Bugzilla::User;
-use Bugzilla::BugMail;
-use Bugzilla::Util;
+use Bugzilla::Token;
# Just in case someone already has an account, let them get the correct footer
# on an error message. The user is logged out just after the account is
# actually created.
-Bugzilla->login(LOGIN_OPTIONAL);
-
-my $dbh = Bugzilla->dbh;
+my $user = Bugzilla->login(LOGIN_OPTIONAL);
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
-
-$vars->{'doc_section'} = 'myaccount.html';
+my $vars = { doc_section => 'myaccount.html' };
print $cgi->header();
-# If we're using LDAP for login, then we can't create a new account here.
-unless (Bugzilla->user->authorizer->user_can_create_account) {
- ThrowUserError("auth_cant_create_account");
-}
-
-my $createexp = Bugzilla->params->{'createemailregexp'};
-unless ($createexp) {
- ThrowUserError("account_creation_disabled");
-}
-
+$user->check_account_creation_enabled;
my $login = $cgi->param('login');
if (defined($login)) {
- $login = Bugzilla::User->check_login_name_for_creation($login);
+ # Check the hash token to make sure this user actually submitted
+ # the create account form.
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['create_account']);
+
+ $user->check_and_send_account_creation_confirmation($login);
$vars->{'login'} = $login;
- if ($login !~ /$createexp/) {
- ThrowUserError("account_creation_restricted");
- }
-
- # Create and send a token for this new account.
- Bugzilla::Token::issue_new_user_account_token($login);
-
$template->process("account/created.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
diff --git a/Websites/bugs.webkit.org/describecomponents.cgi b/Websites/bugs.webkit.org/describecomponents.cgi
index df21a71..3c038c3 100755
--- a/Websites/bugs.webkit.org/describecomponents.cgi
+++ b/Websites/bugs.webkit.org/describecomponents.cgi
@@ -32,20 +32,21 @@
use Bugzilla::Product;
my $user = Bugzilla->login();
-
my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
my $vars = {};
print $cgi->header();
+# This script does nothing but displaying mostly static data.
+Bugzilla->switch_to_shadow_db;
+
my $product_name = trim($cgi->param('product') || '');
my $product = new Bugzilla::Product({'name' => $product_name});
-unless ($product && $user->can_enter_product($product->name)) {
+unless ($product && $user->can_access_product($product->name)) {
# Products which the user is allowed to see.
- my @products = @{$user->get_enterable_products};
+ my @products = @{$user->get_accessible_products};
if (scalar(@products) == 0) {
ThrowUserError("no_products");
diff --git a/Websites/bugs.webkit.org/describekeywords.cgi b/Websites/bugs.webkit.org/describekeywords.cgi
index bfe8990..c1a378a 100755
--- a/Websites/bugs.webkit.org/describekeywords.cgi
+++ b/Websites/bugs.webkit.org/describekeywords.cgi
@@ -35,6 +35,9 @@
my $template = Bugzilla->template;
my $vars = {};
+# Run queries against the shadow DB.
+Bugzilla->switch_to_shadow_db;
+
$vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
$vars->{'caneditkeywords'} = Bugzilla->user->in_group("editkeywords");
diff --git a/Websites/bugs.webkit.org/docs/en/.cvsignore b/Websites/bugs.webkit.org/docs/en/.cvsignore
deleted file mode 100644
index 19d1c43..0000000
--- a/Websites/bugs.webkit.org/docs/en/.cvsignore
+++ /dev/null
@@ -1,3 +0,0 @@
-txt
-pdf
-html
diff --git a/Websites/bugs.webkit.org/docs/en/README.docs b/Websites/bugs.webkit.org/docs/en/README.docs
index 5fdeb85..ae73246 100644
--- a/Websites/bugs.webkit.org/docs/en/README.docs
+++ b/Websites/bugs.webkit.org/docs/en/README.docs
@@ -42,7 +42,7 @@
Trying to set up an XML Docbook editing environment the
first time can be a daunting task.
-I use Linux-Mandrake, in part, because it has a fully-functional
+I use Mandriva Linux, in part, because it has a fully-functional
XML Docbook editing environment included as part of the
distribution CD's. If you have easier instructions for how to
do this for a particular Linux distribution or platform, please
@@ -70,9 +70,9 @@
sgml-common
-If you're getting these from RedHat, make sure you get the ones in the
+If you're getting these from Red Hat, make sure you get the ones in the
rawhide area. The ones in the 7.2 distribution are too old and don't
-include the XML stuff. The packages distrubuted with RedHat 8.0 and 9
+include the XML stuff. The packages distrubuted with Red Hat Linux 8.0 and 9
and known to work.
Download "ldp.dsl" from the Resources page on tldp.org. This is the
@@ -91,7 +91,7 @@
Note the difference is the top one points to the HTML docbook stylesheet,
and the next one points to the PRINT docbook stylesheet.
-Also note that modifying ldp.dsl doesn't seem to be needed on RedHat 9.
+Also note that modifying ldp.dsl doesn't seem to be needed on Red Hat Linux 9.
You know, this sure looks awful involved. Anyway, once you have this in
place, add to your .bashrc:
diff --git a/Websites/bugs.webkit.org/docs/en/images/bzLifecycle.png b/Websites/bugs.webkit.org/docs/en/images/bzLifecycle.png
index c1d76e2..c38b573 100644
--- a/Websites/bugs.webkit.org/docs/en/images/bzLifecycle.png
+++ b/Websites/bugs.webkit.org/docs/en/images/bzLifecycle.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/docs/en/images/bzLifecycle.xml b/Websites/bugs.webkit.org/docs/en/images/bzLifecycle.xml
index 1adc6b7..92ea281 100644
--- a/Websites/bugs.webkit.org/docs/en/images/bzLifecycle.xml
+++ b/Websites/bugs.webkit.org/docs/en/images/bzLifecycle.xml
@@ -62,17 +62,17 @@
</dia:composite>
</dia:attribute>
</dia:diagramdata>
- <dia:layer name="Background" visible="true">
+ <dia:layer name="Background" visible="true" active="true">
<dia:object type="Standard - Line" version="0" id="O0">
<dia:attribute name="obj_pos">
- <dia:point val="21,0"/>
+ <dia:point val="25.05,2.6"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="20.5,-0.05;21.5,2.05"/>
+ <dia:rectangle val="24.6882,2.55;25.4118,5.0618"/>
</dia:attribute>
<dia:attribute name="conn_endpoints">
- <dia:point val="21,0"/>
- <dia:point val="21,2"/>
+ <dia:point val="25.05,2.6"/>
+ <dia:point val="25.05,4.95"/>
</dia:attribute>
<dia:attribute name="numcp">
<dia:int val="2"/>
@@ -87,76 +87,15 @@
<dia:real val="0.5"/>
</dia:attribute>
<dia:connections>
- <dia:connection handle="1" to="O10" connection="2"/>
+ <dia:connection handle="1" to="O4" connection="2"/>
</dia:connections>
</dia:object>
- <dia:object type="Standard - BezierLine" version="0" id="O1">
- <dia:attribute name="obj_pos">
- <dia:point val="21,4"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="15.5,3.95;21.05,7.05"/>
- </dia:attribute>
- <dia:attribute name="bez_points">
- <dia:point val="21,4"/>
- <dia:point val="21,6.95"/>
- <dia:point val="16,4"/>
- <dia:point val="16,7"/>
- </dia:attribute>
- <dia:attribute name="corner_types">
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O10" connection="13"/>
- <dia:connection handle="3" to="O11" connection="2"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - BezierLine" version="0" id="O2">
- <dia:attribute name="obj_pos">
- <dia:point val="10,4"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="9.95,3.95;16.5,7.05"/>
- </dia:attribute>
- <dia:attribute name="bez_points">
- <dia:point val="10,4"/>
- <dia:point val="10,7"/>
- <dia:point val="16,4"/>
- <dia:point val="16,7"/>
- </dia:attribute>
- <dia:attribute name="corner_types">
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="3" to="O11" connection="2"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Line" version="0" id="O3">
+ <dia:object type="Standard - Line" version="0" id="O1">
<dia:attribute name="obj_pos">
<dia:point val="16,8.9"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="15.5,8.85;16.5,12.05"/>
+ <dia:rectangle val="15.6382,8.85;16.3618,12.1118"/>
</dia:attribute>
<dia:attribute name="conn_endpoints">
<dia:point val="16,8.9"/>
@@ -175,44 +114,16 @@
<dia:real val="0.5"/>
</dia:attribute>
<dia:connections>
- <dia:connection handle="0" to="O11" connection="13"/>
- <dia:connection handle="1" to="O12" connection="2"/>
+ <dia:connection handle="0" to="O5" connection="13"/>
+ <dia:connection handle="1" to="O6" connection="2"/>
</dia:connections>
</dia:object>
- <dia:object type="Standard - Arc" version="0" id="O4">
- <dia:attribute name="obj_pos">
- <dia:point val="13,13"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="10.95,7.9;13.5,13.05"/>
- </dia:attribute>
- <dia:attribute name="conn_endpoints">
- <dia:point val="13,13"/>
- <dia:point val="13,7.95"/>
- </dia:attribute>
- <dia:attribute name="curve_distance">
- <dia:real val="-1.9999999999999998"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O12" connection="7"/>
- <dia:connection handle="1" to="O11" connection="7"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Line" version="0" id="O5">
+ <dia:object type="Standard - Line" version="0" id="O2">
<dia:attribute name="obj_pos">
<dia:point val="16,14"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="15.5,13.95;16.5,17.05"/>
+ <dia:rectangle val="15.6382,13.95;16.3618,17.1118"/>
</dia:attribute>
<dia:attribute name="conn_endpoints">
<dia:point val="16,14"/>
@@ -231,37 +142,22 @@
<dia:real val="0.5"/>
</dia:attribute>
<dia:connections>
- <dia:connection handle="0" to="O12" connection="13"/>
- <dia:connection handle="1" to="O13" connection="2"/>
+ <dia:connection handle="0" to="O6" connection="13"/>
+ <dia:connection handle="1" to="O7" connection="2"/>
</dia:connections>
</dia:object>
- <dia:object type="Standard - Line" version="0" id="O6">
- <dia:attribute name="obj_pos">
- <dia:point val="10,3"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="9.95,2.95;10.05,4.05"/>
- </dia:attribute>
- <dia:attribute name="conn_endpoints">
- <dia:point val="10,3"/>
- <dia:point val="10,4"/>
- </dia:attribute>
- <dia:attribute name="numcp">
- <dia:int val="2"/>
- </dia:attribute>
- </dia:object>
- <dia:object type="Standard - BezierLine" version="0" id="O7">
+ <dia:object type="Standard - BezierLine" version="0" id="O3">
<dia:attribute name="obj_pos">
<dia:point val="16,18.9"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="15.95,18.85;21.5,22.05"/>
+ <dia:rectangle val="15.6382,18.85;16.3618,22.2"/>
</dia:attribute>
<dia:attribute name="bez_points">
<dia:point val="16,18.9"/>
<dia:point val="16,22"/>
- <dia:point val="21,19"/>
- <dia:point val="21,22"/>
+ <dia:point val="16,19.2"/>
+ <dia:point val="16,22.2"/>
</dia:attribute>
<dia:attribute name="corner_types">
<dia:enum val="0"/>
@@ -277,87 +173,28 @@
<dia:real val="0.5"/>
</dia:attribute>
<dia:connections>
- <dia:connection handle="0" to="O13" connection="13"/>
- <dia:connection handle="3" to="O14" connection="2"/>
+ <dia:connection handle="0" to="O7" connection="13"/>
+ <dia:connection handle="3" to="O8" connection="2"/>
</dia:connections>
</dia:object>
- <dia:object type="Standard - BezierLine" version="0" id="O8">
+ <dia:object type="Flowchart - Box" version="0" id="O4">
<dia:attribute name="obj_pos">
- <dia:point val="16,18.9"/>
+ <dia:point val="21.81,4.95"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="9.5,18.85;16.05,22.05"/>
- </dia:attribute>
- <dia:attribute name="bez_points">
- <dia:point val="16,18.9"/>
- <dia:point val="16,22"/>
- <dia:point val="10,19"/>
- <dia:point val="10,22"/>
- </dia:attribute>
- <dia:attribute name="corner_types">
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O13" connection="13"/>
- <dia:connection handle="3" to="O15" connection="2"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Arc" version="0" id="O9">
- <dia:attribute name="obj_pos">
- <dia:point val="8.5,22"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="8.4318,13.5624;13.6055,22.0682"/>
- </dia:attribute>
- <dia:attribute name="conn_endpoints">
- <dia:point val="8.5,22"/>
- <dia:point val="13.1464,13.8536"/>
- </dia:attribute>
- <dia:attribute name="curve_distance">
- <dia:real val="-0.71725063066182693"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O15" connection="1"/>
- <dia:connection handle="1" to="O12" connection="11"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Flowchart - Box" version="0" id="O10">
- <dia:attribute name="obj_pos">
- <dia:point val="17.9,2"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="17.85,1.95;24.15,4.05"/>
+ <dia:rectangle val="21.76,4.9;28.34,7"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="17.9,2"/>
+ <dia:point val="21.81,4.95"/>
</dia:attribute>
<dia:attribute name="elem_width">
- <dia:real val="6.1999999999999993"/>
+ <dia:real val="6.480000001490116"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
</dia:attribute>
- <dia:attribute name="inner_color">
- <dia:color val="#e5e5e5"/>
+ <dia:attribute name="border_width">
+ <dia:real val="0.10000000149011612"/>
</dia:attribute>
<dia:attribute name="show_background">
<dia:boolean val="true"/>
@@ -374,13 +211,13 @@
<dia:string>#UNCONFIRMED#</dia:string>
</dia:attribute>
<dia:attribute name="font">
- <dia:font family="sans" style="80" name="Helvetica"/>
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
</dia:attribute>
<dia:attribute name="height">
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="21,3.3"/>
+ <dia:point val="25.05,6.145"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -391,7 +228,7 @@
</dia:composite>
</dia:attribute>
</dia:object>
- <dia:object type="Flowchart - Box" version="0" id="O11">
+ <dia:object type="Flowchart - Box" version="0" id="O5">
<dia:attribute name="obj_pos">
<dia:point val="13,7"/>
</dia:attribute>
@@ -419,16 +256,16 @@
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
- <dia:string>#NEW#</dia:string>
+ <dia:string>#CONFIRMED#</dia:string>
</dia:attribute>
<dia:attribute name="font">
- <dia:font family="sans" style="80" name="Helvetica"/>
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
</dia:attribute>
<dia:attribute name="height">
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="16,8.25"/>
+ <dia:point val="16,8.145"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -439,18 +276,18 @@
</dia:composite>
</dia:attribute>
</dia:object>
- <dia:object type="Flowchart - Box" version="0" id="O12">
+ <dia:object type="Flowchart - Box" version="0" id="O6">
<dia:attribute name="obj_pos">
- <dia:point val="13,12"/>
+ <dia:point val="12.9625,12"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="12.95,11.95;19.05,14.05"/>
+ <dia:rectangle val="12.9125,11.95;19.0875,14.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="13,12"/>
+ <dia:point val="12.9625,12"/>
</dia:attribute>
<dia:attribute name="elem_width">
- <dia:real val="6"/>
+ <dia:real val="6.0749999999999993"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
@@ -467,16 +304,16 @@
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
- <dia:string>#ASSIGNED#</dia:string>
+ <dia:string>#IN_PROGRESS#</dia:string>
</dia:attribute>
<dia:attribute name="font">
- <dia:font family="sans" style="80" name="Helvetica"/>
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
</dia:attribute>
<dia:attribute name="height">
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="16,13.3"/>
+ <dia:point val="16,13.195"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -487,7 +324,7 @@
</dia:composite>
</dia:attribute>
</dia:object>
- <dia:object type="Flowchart - Box" version="0" id="O13">
+ <dia:object type="Flowchart - Box" version="0" id="O7">
<dia:attribute name="obj_pos">
<dia:point val="13,17"/>
</dia:attribute>
@@ -521,13 +358,13 @@
<dia:string>#RESOLVED#</dia:string>
</dia:attribute>
<dia:attribute name="font">
- <dia:font family="sans" style="80" name="Helvetica"/>
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
</dia:attribute>
<dia:attribute name="height">
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="16,18.25"/>
+ <dia:point val="16,18.145"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -538,15 +375,15 @@
</dia:composite>
</dia:attribute>
</dia:object>
- <dia:object type="Flowchart - Box" version="0" id="O14">
+ <dia:object type="Flowchart - Box" version="0" id="O8">
<dia:attribute name="obj_pos">
- <dia:point val="18,22"/>
+ <dia:point val="13,22.2"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="17.95,21.95;24.05,23.95"/>
+ <dia:rectangle val="12.95,22.15;19.05,24.15"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="18,22"/>
+ <dia:point val="13,22.2"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="6"/>
@@ -572,13 +409,13 @@
<dia:string>#VERIFIED#</dia:string>
</dia:attribute>
<dia:attribute name="font">
- <dia:font family="sans" style="80" name="Helvetica"/>
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
</dia:attribute>
<dia:attribute name="height">
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="21,23.25"/>
+ <dia:point val="16,23.345"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -589,387 +426,18 @@
</dia:composite>
</dia:attribute>
</dia:object>
- <dia:object type="Flowchart - Box" version="0" id="O15">
+ <dia:object type="Standard - Text" version="1" id="O9">
<dia:attribute name="obj_pos">
- <dia:point val="7,22"/>
+ <dia:point val="17.75,5.0625"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="6.95,21.95;13.05,23.95"/>
- </dia:attribute>
- <dia:attribute name="elem_corner">
- <dia:point val="7,22"/>
- </dia:attribute>
- <dia:attribute name="elem_width">
- <dia:real val="6"/>
- </dia:attribute>
- <dia:attribute name="elem_height">
- <dia:real val="1.9000000000000001"/>
- </dia:attribute>
- <dia:attribute name="show_background">
- <dia:boolean val="true"/>
- </dia:attribute>
- <dia:attribute name="corner_radius">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="padding">
- <dia:real val="0.5"/>
+ <dia:rectangle val="17.75,4.6175;21.6275,5.775"/>
</dia:attribute>
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
- <dia:string>#REOPEN#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="80" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.80000000000000004"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="10,23.25"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="1"/>
- </dia:attribute>
- </dia:composite>
- </dia:attribute>
- </dia:object>
- <dia:object type="Flowchart - Box" version="0" id="O16">
- <dia:attribute name="obj_pos">
- <dia:point val="13,27"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="12.95,26.95;19.05,28.95"/>
- </dia:attribute>
- <dia:attribute name="elem_corner">
- <dia:point val="13,27"/>
- </dia:attribute>
- <dia:attribute name="elem_width">
- <dia:real val="6"/>
- </dia:attribute>
- <dia:attribute name="elem_height">
- <dia:real val="1.9000000000000001"/>
- </dia:attribute>
- <dia:attribute name="inner_color">
- <dia:color val="#bfbfbf"/>
- </dia:attribute>
- <dia:attribute name="show_background">
- <dia:boolean val="true"/>
- </dia:attribute>
- <dia:attribute name="corner_radius">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="padding">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#CLOSED#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="80" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.80000000000000004"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="16,28.25"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="1"/>
- </dia:attribute>
- </dia:composite>
- </dia:attribute>
- </dia:object>
- <dia:object type="Standard - BezierLine" version="0" id="O17">
- <dia:attribute name="obj_pos">
- <dia:point val="21,23.9"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="15.5,23.85;21.05,27.05"/>
- </dia:attribute>
- <dia:attribute name="bez_points">
- <dia:point val="21,23.9"/>
- <dia:point val="21,27"/>
- <dia:point val="16,24"/>
- <dia:point val="16,27"/>
- </dia:attribute>
- <dia:attribute name="corner_types">
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O14" connection="13"/>
- <dia:connection handle="3" to="O16" connection="2"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - BezierLine" version="0" id="O18">
- <dia:attribute name="obj_pos">
- <dia:point val="19,17.95"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="18.945,17.8995;25.05,28.4505"/>
- </dia:attribute>
- <dia:attribute name="bez_points">
- <dia:point val="19,17.95"/>
- <dia:point val="24,18"/>
- <dia:point val="25,21"/>
- <dia:point val="25,23"/>
- <dia:point val="25,25"/>
- <dia:point val="24,28"/>
- <dia:point val="19,27.95"/>
- </dia:attribute>
- <dia:attribute name="corner_types">
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O13" connection="8"/>
- <dia:connection handle="6" to="O16" connection="8"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Line" version="0" id="O19">
- <dia:attribute name="obj_pos">
- <dia:point val="18,22.95"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="12.95,22.45;18.05,23.45"/>
- </dia:attribute>
- <dia:attribute name="conn_endpoints">
- <dia:point val="18,22.95"/>
- <dia:point val="13,22.95"/>
- </dia:attribute>
- <dia:attribute name="numcp">
- <dia:int val="1"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O14" connection="7"/>
- <dia:connection handle="1" to="O15" connection="8"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - BezierLine" version="0" id="O20">
- <dia:attribute name="obj_pos">
- <dia:point val="14.5,27"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="9.5,23.85;14.5851,27.0575"/>
- </dia:attribute>
- <dia:attribute name="bez_points">
- <dia:point val="14.5,27"/>
- <dia:point val="15,24"/>
- <dia:point val="10,27"/>
- <dia:point val="10,23.9"/>
- </dia:attribute>
- <dia:attribute name="corner_types">
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O16" connection="1"/>
- <dia:connection handle="3" to="O15" connection="13"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Arc" version="0" id="O21">
- <dia:attribute name="obj_pos">
- <dia:point val="8.5,22"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="8.42939,17.5449;13.3716,22.0706"/>
- </dia:attribute>
- <dia:attribute name="conn_endpoints">
- <dia:point val="8.5,22"/>
- <dia:point val="13,17.95"/>
- </dia:attribute>
- <dia:attribute name="curve_distance">
- <dia:real val="-0.74169570721594136"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O15" connection="1"/>
- <dia:connection handle="1" to="O13" connection="7"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Arc" version="0" id="O22">
- <dia:attribute name="obj_pos">
- <dia:point val="19,7.95"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="18.5,7.9;22.05,17.525"/>
- </dia:attribute>
- <dia:attribute name="conn_endpoints">
- <dia:point val="19,7.95"/>
- <dia:point val="19,17.475"/>
- </dia:attribute>
- <dia:attribute name="curve_distance">
- <dia:real val="-3"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O11" connection="8"/>
- <dia:connection handle="1" to="O13" connection="6"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Arc" version="0" id="O23">
- <dia:attribute name="obj_pos">
- <dia:point val="21,4"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="18.4981,3.9432;23.6948,17.5979"/>
- </dia:attribute>
- <dia:attribute name="conn_endpoints">
- <dia:point val="21,4"/>
- <dia:point val="19,17.475"/>
- </dia:attribute>
- <dia:attribute name="curve_distance">
- <dia:real val="-3.5943353432190368"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O10" connection="13"/>
- <dia:connection handle="1" to="O13" connection="6"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Arc" version="0" id="O24">
- <dia:attribute name="obj_pos">
- <dia:point val="21,4"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="18.5011,3.94034;21.8578,13.1573"/>
- </dia:attribute>
- <dia:attribute name="conn_endpoints">
- <dia:point val="21,4"/>
- <dia:point val="19,13"/>
- </dia:attribute>
- <dia:attribute name="curve_distance">
- <dia:real val="-1.6769027424613245"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O10" connection="13"/>
- <dia:connection handle="1" to="O12" connection="8"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Text" version="0" id="O25">
- <dia:attribute name="obj_pos">
- <dia:point val="10.025,0.825"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="7.2,0.225;12.85,3.225"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#New bug from a
-user with canconfirm
-or a product without
-UNCONFIRMED state#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="0" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.69999999999999996"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="10.025,0.825"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="1"/>
- </dia:attribute>
- </dia:composite>
- </dia:attribute>
- </dia:object>
- <dia:object type="Standard - Text" version="0" id="O26">
- <dia:attribute name="obj_pos">
- <dia:point val="20.325,4.48321"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="14.675,3.93321;20.325,5.28321"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#Bug confirmed or
-receives enough votes#</dia:string>
+ <dia:string>#Bug determined
+to be present#</dia:string>
</dia:attribute>
<dia:attribute name="font">
<dia:font family="sans" style="0" name="Helvetica"/>
@@ -978,38 +446,7 @@
<dia:real val="0.59999999999999998"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="20.325,4.48321"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="2"/>
- </dia:attribute>
- </dia:composite>
- </dia:attribute>
- </dia:object>
- <dia:object type="Standard - Text" version="0" id="O27">
- <dia:attribute name="obj_pos">
- <dia:point val="16.2865,10.1"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="16.2865,9.55;20.3865,10.9"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#Developer takes
-possession#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="0" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.59999999999999998"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="16.2865,10.1"/>
+ <dia:point val="17.75,5.0625"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -1019,19 +456,22 @@
</dia:attribute>
</dia:composite>
</dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
</dia:object>
- <dia:object type="Standard - Text" version="0" id="O28">
+ <dia:object type="Standard - Text" version="1" id="O10">
<dia:attribute name="obj_pos">
- <dia:point val="10.7629,9.45"/>
+ <dia:point val="16.3365,10.4"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="8.0629,8.8825;10.7804,10.285"/>
+ <dia:rectangle val="16.3365,9.955;21.394,11.1125"/>
</dia:attribute>
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
- <dia:string>#Ownership
-is changed#</dia:string>
+ <dia:string>#Developer is working
+on the bug#</dia:string>
</dia:attribute>
<dia:attribute name="font">
<dia:font family="sans" style="0" name="Helvetica"/>
@@ -1040,57 +480,29 @@
<dia:real val="0.59999999999999998"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="10.7629,9.45"/>
+ <dia:point val="16.3365,10.4"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
</dia:attribute>
<dia:attribute name="alignment">
- <dia:enum val="2"/>
+ <dia:enum val="0"/>
</dia:attribute>
</dia:composite>
</dia:attribute>
- </dia:object>
- <dia:object type="Standard - Text" version="0" id="O29">
- <dia:attribute name="obj_pos">
- <dia:point val="21.4576,6.43623"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="17.3576,5.88623;21.4576,7.23623"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#Developer takes
-possession#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="0" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.59999999999999998"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="21.4576,6.43623"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="2"/>
- </dia:attribute>
- </dia:composite>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
</dia:attribute>
</dia:object>
- <dia:object type="Flowchart - Box" version="0" id="O30">
+ <dia:object type="Flowchart - Box" version="0" id="O11">
<dia:attribute name="obj_pos">
- <dia:point val="4.81289,11.0073"/>
+ <dia:point val="-1.03711,14.8573"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="4.76289,10.9573;10.5629,16.2573"/>
+ <dia:rectangle val="-1.08711,14.8073;4.71289,20.1073"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="4.81289,11.0073"/>
+ <dia:point val="-1.03711,14.8573"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="5.6999999999999993"/>
@@ -1127,7 +539,7 @@
<dia:real val="0.59999999999999998"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="4.91289,11.7573"/>
+ <dia:point val="-0.93711,16.1023"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -1138,16 +550,16 @@
</dia:composite>
</dia:attribute>
</dia:object>
- <dia:object type="Standard - Line" version="0" id="O31">
+ <dia:object type="Standard - Line" version="0" id="O12">
<dia:attribute name="obj_pos">
- <dia:point val="10.3629,14.9073"/>
+ <dia:point val="4.66289,18.7573"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="10.3278,14.8722;13.1815,17.1815"/>
+ <dia:rectangle val="4.63788,18.7286;13.1714,18.7823"/>
</dia:attribute>
<dia:attribute name="conn_endpoints">
- <dia:point val="10.3629,14.9073"/>
- <dia:point val="13.1464,17.1464"/>
+ <dia:point val="4.66289,18.7573"/>
+ <dia:point val="13.1464,18.7536"/>
</dia:attribute>
<dia:attribute name="numcp">
<dia:int val="1"/>
@@ -1159,22 +571,21 @@
<dia:enum val="4"/>
</dia:attribute>
<dia:connections>
- <dia:connection handle="0" to="O30" connection="10"/>
- <dia:connection handle="1" to="O13" connection="0"/>
+ <dia:connection handle="0" to="O11" connection="10"/>
+ <dia:connection handle="1" to="O7" connection="11"/>
</dia:connections>
</dia:object>
- <dia:object type="Standard - Text" version="0" id="O32">
+ <dia:object type="Standard - Text" version="1" id="O13">
<dia:attribute name="obj_pos">
- <dia:point val="16.299,15.3073"/>
+ <dia:point val="16.4,15.6"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="16.299,14.7573;20.449,16.1073"/>
+ <dia:rectangle val="16.4,15.155;19.8425,15.7125"/>
</dia:attribute>
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
- <dia:string>#Development is
-finished with bug#</dia:string>
+ <dia:string>#Fix checked in#</dia:string>
</dia:attribute>
<dia:attribute name="font">
<dia:font family="sans" style="0" name="Helvetica"/>
@@ -1183,7 +594,7 @@
<dia:real val="0.59999999999999998"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="16.299,15.3073"/>
+ <dia:point val="16.4,15.6"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -1193,19 +604,22 @@
</dia:attribute>
</dia:composite>
</dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
</dia:object>
- <dia:object type="Standard - Text" version="0" id="O33">
+ <dia:object type="Standard - Text" version="1" id="O14">
<dia:attribute name="obj_pos">
- <dia:point val="11.4365,21"/>
+ <dia:point val="8.2865,16.95"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="11.4365,20.45;15.5365,21.8"/>
+ <dia:rectangle val="8.2865,16.505;12.294,17.6625"/>
</dia:attribute>
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
<dia:string>#QA not satisfied
-with solution#</dia:string>
+with the solution#</dia:string>
</dia:attribute>
<dia:attribute name="font">
<dia:font family="sans" style="0" name="Helvetica"/>
@@ -1214,7 +628,7 @@
<dia:real val="0.59999999999999998"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="11.4365,21"/>
+ <dia:point val="8.2865,16.95"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -1224,19 +638,22 @@
</dia:attribute>
</dia:composite>
</dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
</dia:object>
- <dia:object type="Standard - Text" version="0" id="O34">
+ <dia:object type="Standard - Text" version="1" id="O15">
<dia:attribute name="obj_pos">
- <dia:point val="16.8365,21.05"/>
+ <dia:point val="16.2865,20.5"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="16.8365,20.5;20.7365,21.85"/>
+ <dia:rectangle val="16.2865,20.055;20.6865,21.2125"/>
</dia:attribute>
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
- <dia:string>#QA verifies
-solution worked#</dia:string>
+ <dia:string>#QA verifies that
+the solution works#</dia:string>
</dia:attribute>
<dia:attribute name="font">
<dia:font family="sans" style="0" name="Helvetica"/>
@@ -1245,7 +662,7 @@
<dia:real val="0.59999999999999998"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="16.8365,21.05"/>
+ <dia:point val="16.2865,20.5"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -1255,18 +672,21 @@
</dia:attribute>
</dia:composite>
</dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
</dia:object>
- <dia:object type="Standard - Text" version="0" id="O35">
+ <dia:object type="Standard - Text" version="1" id="O16">
<dia:attribute name="obj_pos">
- <dia:point val="17.224,25.875"/>
+ <dia:point val="6.4365,22.7"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="17.224,25.325;20.574,26.075"/>
+ <dia:rectangle val="6.4365,22.255;12.494,22.8125"/>
</dia:attribute>
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
- <dia:string>#Bug is closed#</dia:string>
+ <dia:string>#Fix turns out to be wrong#</dia:string>
</dia:attribute>
<dia:attribute name="font">
<dia:font family="sans" style="0" name="Helvetica"/>
@@ -1275,7 +695,7 @@
<dia:real val="0.59999999999999998"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="17.224,25.875"/>
+ <dia:point val="6.4365,22.7"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -1285,305 +705,16 @@
</dia:attribute>
</dia:composite>
</dia:attribute>
- </dia:object>
- <dia:object type="Standard - Text" version="0" id="O36">
- <dia:attribute name="obj_pos">
- <dia:point val="22.5365,18.5"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="22.5365,17.95;25.8865,18.7"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#Bug is closed#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="0" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.59999999999999998"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="22.5365,18.5"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="0"/>
- </dia:attribute>
- </dia:composite>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
</dia:attribute>
</dia:object>
- <dia:object type="Standard - Text" version="0" id="O37">
+ <dia:object type="Standard - Text" version="1" id="O17">
<dia:attribute name="obj_pos">
- <dia:point val="9.62401,17.8111"/>
+ <dia:point val="22.8,22.15"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="5.52401,17.2611;9.62401,18.6111"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#Developer takes
-possession#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="0" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.59999999999999998"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="9.62401,17.8111"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="2"/>
- </dia:attribute>
- </dia:composite>
- </dia:attribute>
- </dia:object>
- <dia:object type="Standard - Text" version="0" id="O38">
- <dia:attribute name="obj_pos">
- <dia:point val="11.1865,19.15"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="11.1865,18.6;13.3365,19.95"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#Issue is
-resolved#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="0" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.59999999999999998"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="11.1865,19.15"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="0"/>
- </dia:attribute>
- </dia:composite>
- </dia:attribute>
- </dia:object>
- <dia:object type="Standard - Text" version="0" id="O39">
- <dia:attribute name="obj_pos">
- <dia:point val="11.049,25.325"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="11.049,24.775;15.049,25.525"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#Bug is reopened#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="0" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.59999999999999998"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="11.049,25.325"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="0"/>
- </dia:attribute>
- </dia:composite>
- </dia:attribute>
- </dia:object>
- <dia:object type="Standard - Text" version="0" id="O40">
- <dia:attribute name="obj_pos">
- <dia:point val="13.9365,22.75"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="13.9365,22.2;17.9365,22.95"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#Bug is reopened#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="0" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.59999999999999998"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="13.9365,22.75"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="0"/>
- </dia:attribute>
- </dia:composite>
- </dia:attribute>
- </dia:object>
- <dia:object type="Standard - BezierLine" version="0" id="O41">
- <dia:attribute name="obj_pos">
- <dia:point val="19,17.95"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="18.9494,3.46851;29.05,18.0006"/>
- </dia:attribute>
- <dia:attribute name="bez_points">
- <dia:point val="19,17.95"/>
- <dia:point val="23,18"/>
- <dia:point val="29,14"/>
- <dia:point val="29,11"/>
- <dia:point val="29,8"/>
- <dia:point val="27,7"/>
- <dia:point val="23.9286,3.85355"/>
- </dia:attribute>
- <dia:attribute name="corner_types">
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O13" connection="8"/>
- <dia:connection handle="6" to="O10" connection="15"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Text" version="0" id="O42">
- <dia:attribute name="obj_pos">
- <dia:point val="24,10"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="24,9.45;28.15,10.8"/>
- </dia:attribute>
- <dia:attribute name="text">
- <dia:composite type="text">
- <dia:attribute name="string">
- <dia:string>#Development is
-finished with bug#</dia:string>
- </dia:attribute>
- <dia:attribute name="font">
- <dia:font family="sans" style="0" name="Helvetica"/>
- </dia:attribute>
- <dia:attribute name="height">
- <dia:real val="0.59999999999999998"/>
- </dia:attribute>
- <dia:attribute name="pos">
- <dia:point val="24,10"/>
- </dia:attribute>
- <dia:attribute name="color">
- <dia:color val="#000000"/>
- </dia:attribute>
- <dia:attribute name="alignment">
- <dia:enum val="0"/>
- </dia:attribute>
- </dia:composite>
- </dia:attribute>
- </dia:object>
- <dia:object type="Standard - BezierLine" version="0" id="O43">
- <dia:attribute name="obj_pos">
- <dia:point val="23.8536,22.1464"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="23.5359,3.46851;29.0712,22.2033"/>
- </dia:attribute>
- <dia:attribute name="bez_points">
- <dia:point val="23.8536,22.1464"/>
- <dia:point val="29.6426,21.2696"/>
- <dia:point val="29,14"/>
- <dia:point val="29,11"/>
- <dia:point val="29,8"/>
- <dia:point val="27,7"/>
- <dia:point val="23.9286,3.85355"/>
- </dia:attribute>
- <dia:attribute name="corner_types">
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O14" connection="4"/>
- <dia:connection handle="6" to="O10" connection="15"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - BezierLine" version="0" id="O44">
- <dia:attribute name="obj_pos">
- <dia:point val="18.8536,28.7536"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="18.8032,3.46851;29.05,28.8043"/>
- </dia:attribute>
- <dia:attribute name="bez_points">
- <dia:point val="18.8536,28.7536"/>
- <dia:point val="28.0326,28.8213"/>
- <dia:point val="29,24"/>
- <dia:point val="29,21"/>
- <dia:point val="29,18"/>
- <dia:point val="29,14"/>
- <dia:point val="29,11"/>
- <dia:point val="29,8"/>
- <dia:point val="27,7"/>
- <dia:point val="23.9286,3.85355"/>
- </dia:attribute>
- <dia:attribute name="corner_types">
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- <dia:enum val="0"/>
- </dia:attribute>
- <dia:attribute name="end_arrow">
- <dia:enum val="22"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_length">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:attribute name="end_arrow_width">
- <dia:real val="0.5"/>
- </dia:attribute>
- <dia:connections>
- <dia:connection handle="0" to="O16" connection="15"/>
- <dia:connection handle="9" to="O10" connection="15"/>
- </dia:connections>
- </dia:object>
- <dia:object type="Standard - Text" version="0" id="O45">
- <dia:attribute name="obj_pos">
- <dia:point val="25,4"/>
- </dia:attribute>
- <dia:attribute name="obj_bb">
- <dia:rectangle val="25,3.45;30.1,4.8"/>
+ <dia:rectangle val="22.8,21.705;27.85,22.8625"/>
</dia:attribute>
<dia:attribute name="text">
<dia:composite type="text">
@@ -1598,7 +729,7 @@
<dia:real val="0.59999999999999998"/>
</dia:attribute>
<dia:attribute name="pos">
- <dia:point val="25,4"/>
+ <dia:point val="22.8,22.15"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000"/>
@@ -1608,6 +739,986 @@
</dia:attribute>
</dia:composite>
</dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O18">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16,7.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="16,7.355;16,8.1"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16,7.95"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O5" connection="16"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O19">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16.9,10.5125"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="16.9,9.9175;16.9,10.6625"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="16.9,10.5125"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O20">
+ <dia:attribute name="obj_pos">
+ <dia:point val="19.55,15.6125"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="19.55,15.0175;19.55,15.7625"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="19.55,15.6125"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O21">
+ <dia:attribute name="obj_pos">
+ <dia:point val="20.3,20.8625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="20.3,20.2675;20.3,21.0125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="20.3,20.8625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O22">
+ <dia:attribute name="obj_pos">
+ <dia:point val="20.2,20.8625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="20.2,20.2675;20.2,21.0125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="20.2,20.8625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O23">
+ <dia:attribute name="obj_pos">
+ <dia:point val="19.75,21.0625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="19.75,20.4675;19.75,21.2125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="19.75,21.0625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O24">
+ <dia:attribute name="obj_pos">
+ <dia:point val="12.8,1.5125"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="12.8,0.9175;12.8,1.6625"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="12.8,1.5125"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O25">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13.7,15.7625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="13.7,15.1675;13.7,15.9125"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="13.7,15.7625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O26">
+ <dia:attribute name="obj_pos">
+ <dia:point val="9.65,11.7125"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="9.65,11.2675;13.555,12.4247"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Developer stops
+work on bug#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59972222571740919"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="9.65,11.7125"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O27">
+ <dia:attribute name="obj_pos">
+ <dia:point val="20.55,19.0625"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="20.55,18.6175;26.5875,19.7747"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Bug is not fixable
+(e.g because it is invalid)#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59972222571740919"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="20.55,19.0625"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O28">
+ <dia:attribute name="obj_pos">
+ <dia:point val="12.9625,13"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="9.3,8.0632;13.1118,13.05"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="12.9625,13"/>
+ <dia:point val="9.35,13"/>
+ <dia:point val="9.35,8.425"/>
+ <dia:point val="13,8.425"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O6" connection="7"/>
+ <dia:connection handle="1" to="O5" connection="9"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O29">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13,17.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="7.65,7.5882;13.1118,18"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="13,17.95"/>
+ <dia:point val="7.7,17.95"/>
+ <dia:point val="7.7,7.95"/>
+ <dia:point val="13,7.95"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O7" connection="7"/>
+ <dia:connection handle="1" to="O5" connection="7"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O30">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13,23.15"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="5.9,7.1132;13.1118,23.2"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="13,23.15"/>
+ <dia:point val="5.95,23.15"/>
+ <dia:point val="5.95,7.475"/>
+ <dia:point val="13,7.475"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="4"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O8" connection="7"/>
+ <dia:connection handle="1" to="O5" connection="5"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O31">
+ <dia:attribute name="obj_pos">
+ <dia:point val="19.0498,7.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="18.8882,7.9;22.7,17.8368"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="19.0498,7.95"/>
+ <dia:point val="22.65,7.95"/>
+ <dia:point val="22.65,17.475"/>
+ <dia:point val="19,17.475"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O5" connection="16"/>
+ <dia:connection handle="1" to="O7" connection="6"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O32">
+ <dia:attribute name="obj_pos">
+ <dia:point val="26.67,6.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="18.8882,6.9;26.72,18.7868"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="26.67,6.95"/>
+ <dia:point val="26.67,18.425"/>
+ <dia:point val="19,18.425"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O4" connection="14"/>
+ <dia:connection handle="1" to="O7" connection="10"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O33">
+ <dia:attribute name="obj_pos">
+ <dia:point val="23.43,6.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="18.9257,6.9;23.48,13.3618"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="23.43,6.95"/>
+ <dia:point val="23.43,13"/>
+ <dia:point val="19.0375,13"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="4"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O4" connection="12"/>
+ <dia:connection handle="1" to="O6" connection="8"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O34">
+ <dia:attribute name="obj_pos">
+ <dia:point val="19,17.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="18.95,6.8382;25.4118,18"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="19,17.95"/>
+ <dia:point val="25.05,17.95"/>
+ <dia:point val="25.05,6.95"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O7" connection="8"/>
+ <dia:connection handle="1" to="O4" connection="13"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O35">
+ <dia:attribute name="obj_pos">
+ <dia:point val="19,23.15"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="19,6.69175;28.5054,23.2"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="19,23.15"/>
+ <dia:point val="19,23.15"/>
+ <dia:point val="28.1436,23.15"/>
+ <dia:point val="28.1436,6.80355"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="4"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O8" connection="8"/>
+ <dia:connection handle="1" to="O4" connection="15"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - Line" version="0" id="O36">
+ <dia:attribute name="obj_pos">
+ <dia:point val="16,0.65"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="15.6382,0.6;16.3618,7.1118"/>
+ </dia:attribute>
+ <dia:attribute name="conn_endpoints">
+ <dia:point val="16,0.65"/>
+ <dia:point val="16,7"/>
+ </dia:attribute>
+ <dia:attribute name="numcp">
+ <dia:int val="1"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="1" to="O5" connection="2"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - ZigZagLine" version="1" id="O37">
+ <dia:attribute name="obj_pos">
+ <dia:point val="21.81,5.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="17.1382,5.9;21.86,7.1118"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="21.81,5.95"/>
+ <dia:point val="17.5,5.95"/>
+ <dia:point val="17.5,7"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ </dia:attribute>
+ <dia:attribute name="autorouting">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O4" connection="7"/>
+ <dia:connection handle="1" to="O5" connection="3"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O38">
+ <dia:attribute name="obj_pos">
+ <dia:point val="20.65,1.05"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="20.65,0.605;28.575,2.36194"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>#Bug is filed by a non-empowered
+user in a product where the
+UNCONFIRMED state is enabled#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.59972222571740919"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="20.65,1.05"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O39">
+ <dia:attribute name="obj_pos">
+ <dia:point val="26.65,1.5"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="26.65,0.905;26.65,1.65"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="26.65,1.5"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O40">
+ <dia:attribute name="obj_pos">
+ <dia:point val="21.2,5.2"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="21.2,4.605;21.2,5.35"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="21.2,5.2"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O41">
+ <dia:attribute name="obj_pos">
+ <dia:point val="12.95,10.85"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="12.95,10.255;12.95,11"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="12.95,10.85"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O42">
+ <dia:attribute name="obj_pos">
+ <dia:point val="18.95,15.45"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="18.95,14.855;18.95,15.6"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="18.95,15.45"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O43">
+ <dia:attribute name="obj_pos">
+ <dia:point val="21,15.55"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="21,14.955;21,15.7"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="21,15.55"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O44">
+ <dia:attribute name="obj_pos">
+ <dia:point val="13.6,20.7"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="13.6,20.105;13.6,20.85"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="13.6,20.7"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O45">
+ <dia:attribute name="obj_pos">
+ <dia:point val="24.35,9.7"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="24.35,9.105;24.35,9.85"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="24.35,9.7"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Standard - Text" version="1" id="O46">
+ <dia:attribute name="obj_pos">
+ <dia:point val="23.85,9.95"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="23.85,9.355;23.85,10.1"/>
+ </dia:attribute>
+ <dia:attribute name="text">
+ <dia:composite type="text">
+ <dia:attribute name="string">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="pos">
+ <dia:point val="23.85,9.95"/>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="alignment">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="valign">
+ <dia:enum val="3"/>
+ </dia:attribute>
</dia:object>
</dia:layer>
</dia:diagram>
diff --git a/Websites/bugs.webkit.org/docs/en/images/caution.gif b/Websites/bugs.webkit.org/docs/en/images/caution.gif
index c6bfd65..a482230 100644
--- a/Websites/bugs.webkit.org/docs/en/images/caution.gif
+++ b/Websites/bugs.webkit.org/docs/en/images/caution.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/docs/en/images/tip.gif b/Websites/bugs.webkit.org/docs/en/images/tip.gif
index fe2a1d5..c8d5ae9 100644
--- a/Websites/bugs.webkit.org/docs/en/images/tip.gif
+++ b/Websites/bugs.webkit.org/docs/en/images/tip.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/docs/en/images/warning.gif b/Websites/bugs.webkit.org/docs/en/images/warning.gif
index c6f3ea9..693ffc3 100644
--- a/Websites/bugs.webkit.org/docs/en/images/warning.gif
+++ b/Websites/bugs.webkit.org/docs/en/images/warning.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/docs/en/rel_notes.txt b/Websites/bugs.webkit.org/docs/en/rel_notes.txt
index 614fcb5..4014951 100644
--- a/Websites/bugs.webkit.org/docs/en/rel_notes.txt
+++ b/Websites/bugs.webkit.org/docs/en/rel_notes.txt
@@ -3019,10 +3019,10 @@
*** USERS UPGRADING FROM 2.8 OR EARLIER ***
*******************************************
-Release notes were not compiled for versions of Bugzilla before
-2.12.
+This version of Bugzilla cannot upgrade from version 2.8 (released
+November 19, 1999). You will first have to upgrade to Bugzilla 3.6 and
+then upgrade to the latest release.
-The file 'UPGRADING-pre-2.8' contains instructions you may
-need to perform in addition to running 'checksetup.pl' if you
-are running a pre 2.8 version.
-
+If you are upgrading from a version earlier than 2.8, See the
+PGRADING-pre-2.8 file in Bugzilla 3.0 for information
+on upgrading from a version that is earlier than 2.8.
diff --git a/Websites/bugs.webkit.org/docs/en/xml/.cvsignore b/Websites/bugs.webkit.org/docs/en/xml/.cvsignore
deleted file mode 100644
index ef6b304..0000000
--- a/Websites/bugs.webkit.org/docs/en/xml/.cvsignore
+++ /dev/null
@@ -1 +0,0 @@
-bugzilla.ent
diff --git a/Websites/bugs.webkit.org/docs/en/xml/Bugzilla-Guide.xml b/Websites/bugs.webkit.org/docs/en/xml/Bugzilla-Guide.xml
index 4b77c10..bfb1146 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/Bugzilla-Guide.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/Bugzilla-Guide.xml
@@ -13,12 +13,10 @@
<!ENTITY administration SYSTEM "administration.xml">
<!ENTITY security SYSTEM "security.xml">
<!ENTITY using SYSTEM "using.xml">
-<!ENTITY integration SYSTEM "integration.xml">
<!ENTITY index SYSTEM "index.xml">
<!ENTITY customization SYSTEM "customization.xml">
<!ENTITY troubleshooting SYSTEM "troubleshooting.xml">
<!ENTITY patches SYSTEM "patches.xml">
-<!ENTITY introduction SYSTEM "introduction.xml">
<!ENTITY modules SYSTEM "modules.xml">
<!-- Things to change for a stable release:
@@ -34,12 +32,12 @@
For a devel release, simple bump bz-ver and bz-date
-->
-<!ENTITY bz-ver "3.2.3">
-<!ENTITY bz-nextver "3.4">
-<!ENTITY bz-date "2009-03-30">
-<!ENTITY current-year "2009">
+<!ENTITY bz-ver "4.2.1">
+<!ENTITY bz-nextver "4.4">
+<!ENTITY bz-date "2012-04-18">
+<!ENTITY current-year "2012">
-<!ENTITY landfillbase "http://landfill.bugzilla.org/bugzilla-3.2-branch/">
+<!ENTITY landfillbase "http://landfill.bugzilla.org/bugzilla-4.2-branch/">
<!ENTITY bz "http://www.bugzilla.org/">
<!ENTITY bzg-bugs "<ulink url='https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&component=Documentation'>Bugzilla Documentation</ulink>">
<!ENTITY mysql "http://www.mysql.com/">
diff --git a/Websites/bugs.webkit.org/docs/en/xml/about.xml b/Websites/bugs.webkit.org/docs/en/xml/about.xml
index 7bd4da4..a6cebe0 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/about.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/about.xml
@@ -1,6 +1,5 @@
<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
<!ENTITY conventions SYSTEM "conventions.xml"> ] > -->
-<!-- $Id$ -->
<chapter id="about">
<title>About This Guide</title>
@@ -65,47 +64,22 @@
<para>
This is the &bz-ver; version of The Bugzilla Guide. It is so named
to match the current version of Bugzilla.
+ <!-- BZ-DEVEL --> This version of the guide, like its associated Bugzilla version, is a
+ development version.<!-- /BZ-DEVEL -->
</para>
<para>
The latest version of this guide can always be found at <ulink
- url="http://www.bugzilla.org"/>, or checked out via CVS by
- following the <ulink url="http://www.mozilla.org/cvs.html">Mozilla
- CVS</ulink> instructions and check out the
- <filename>mozilla/webtools/bugzilla/docs/</filename>
- subtree. However, you should read the version
- which came with the Bugzilla release you are using.
- </para>
- <para>
- The Bugzilla Guide, or a section of it, is also available in
- the following languages:
- <ulink url="http://www.traduc.org/docs/guides/lecture/bugzilla/">French</ulink>,
- <ulink url="http://bugzilla-de.sourceforge.net/docs/html/">German</ulink>,
- <ulink url="http://www.bugzilla.jp/docs/2.18/">Japanese</ulink>.
- Note that these may be outdated or not up to date.
+ url="http://www.bugzilla.org/docs/"/>. However, you should read
+ the version which came with the Bugzilla release you are using.
</para>
<para>
In addition, there are Bugzilla template localization projects in
- the following languages. They may have translated documentation
- available:
- <ulink url="http://sourceforge.net/projects/bugzilla-ar/">Arabic</ulink>,
- <ulink url="http://sourceforge.net/projects/bugzilla-be/">Belarusian</ulink>,
- <ulink url="http://openfmi.net/projects/mozilla-bg/">Bulgarian</ulink>,
- <ulink url="http://sourceforge.net/projects/bugzilla-br/">Brazilian Portuguese</ulink>,
- <ulink url="http://sourceforge.net/projects/bugzilla-cn/">Chinese</ulink>,
- <ulink url="http://sourceforge.net/projects/bugzilla-fr/">French</ulink>,
- <ulink url="http://germzilla.ganderbay.net/">German</ulink>,
- <ulink url="http://sourceforge.net/projects/bugzilla-it/">Italian</ulink>,
- <ulink url="http://www.bugzilla.jp/about/jp.html">Japanese</ulink>,
- <ulink url="http://sourceforge.net/projects/bugzilla-kr/">Korean</ulink>,
- <ulink url="http://sourceforge.net/projects/bugzilla-ru/">Russian</ulink> and
- <ulink url="http://sourceforge.net/projects/bugzilla-es/">Spanish</ulink>.
- </para>
-
- <para>
- If you would like to volunteer to translate the Guide into additional
- languages, please contact
- <ulink url="mailto:justdave@bugzilla.org">Dave Miller</ulink>.
+ <ulink url="http://www.bugzilla.org/download/#localizations">several languages</ulink>.
+ They may have translated documentation available. If you would like to
+ volunteer to translate the Guide into additional languages, please visit the
+ <ulink url="https://wiki.mozilla.org/Bugzilla:L10n">Bugzilla L10n team</ulink>
+ page.
</para>
</section>
diff --git a/Websites/bugs.webkit.org/docs/en/xml/administration.xml b/Websites/bugs.webkit.org/docs/en/xml/administration.xml
index 2ed0376..111fc8b 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/administration.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/administration.xml
@@ -100,13 +100,13 @@
<varlistentry>
<term>
- ssl
+ ssl_redirect
</term>
<listitem>
<para>
- Determines when Bugzilla will force HTTPS (SSL) connections, using
- the URL defined in <command>sslbase</command>.
- Options include "always", "never", and "authenticated sessions".
+ If enabled, Bugzilla will force HTTPS (SSL) connections, by
+ automatically redirecting any users who try to use a non-SSL
+ connection.
</para>
</listitem>
</varlistentry>
@@ -147,18 +147,6 @@
<varlistentry>
<term>
- timezone
- </term>
- <listitem>
- <para>
- Timezone of server. The timezone is displayed with timestamps. If
- this parameter is left blank, the timezone is not displayed.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term>
utf8
</term>
<listitem>
@@ -262,34 +250,11 @@
<section id="param-admin-policies">
<title>Administrative Policies</title>
- <para>
- This page contains parameters for basic administrative functions.
- Options include whether to allow the deletion of bugs and users, whether
- to allow users to change their email address, and whether to allow
- user watching (one user receiving all notifications of a selected
- other user).
- </para>
-
- <variablelist>
-
- <varlistentry>
- <term>
- supportwatchers
- </term>
- <listitem>
- <para>
- Turning on this option allows users to ask to receive copies
- of bug mail sent to another user. Watching a user with
- different group permissions is not a way to 'get around' the
- system; copied emails are still subject to the normal groupset
- permissions of a bug, and <quote>watchers</quote> will only be
- copied on emails from bugs they would normally be allowed to view.
- </para>
- </listitem>
- </varlistentry>
-
- </variablelist>
-
+ <para>
+ This page contains parameters for basic administrative functions.
+ Options include whether to allow the deletion of bugs and users,
+ and whether to allow users to change their email address.
+ </para>
</section>
<section id="param-user-authentication">
@@ -516,25 +481,6 @@
<varlistentry>
<term>
- useentrygroupdefault
- </term>
- <listitem>
- <para>
- Bugzilla products can have a group associated with them, so that
- certain users can only see bugs in certain products. When this
- parameter is set to <quote>on</quote>, this
- causes the initial group controls on newly created products
- to place all newly-created bugs in the group
- having the same name as the product immediately.
- After a product is initially created, the group controls
- can be further adjusted without interference by
- this mechanism.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term>
usevisibilitygroups
</term>
<listitem>
@@ -820,16 +766,62 @@
<varlistentry>
<term>
- sendmailnow
+ smtpserver
</term>
<listitem>
<para>
- When Bugzilla is using Sendmail older than 8.12, turning this option
- off will improve performance by not waiting for Sendmail to actually
- send mail. If Sendmail 8.12 or later is being used, there is
- nothing to gain by turning this off. If another MTA is being used,
- such as Postfix, then this option *must* be turned on (even if you
- are using the fake sendmail executable that Postfix provides).
+ This is the SMTP server address, if the <quote>mail_delivery_method</quote>
+ parameter is set to SMTP. Use "localhost" if you have a local MTA
+ running, otherwise use a remote SMTP server. Append ":" and the port
+ number, if a non-default port is needed.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ smtp_username
+ </term>
+ <listitem>
+ <para>
+ Username to use for SASL authentication to the SMTP server. Leave
+ this parameter empty if your server does not require authentication.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ smtp_password
+ </term>
+ <listitem>
+ <para>
+ Password to use for SASL authentication to the SMTP server. This
+ parameter will be ignored if the <quote>smtp_username</quote>
+ parameter is left empty.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ smtp_ssl
+ </term>
+ <listitem>
+ <para>
+ Enable SSL support for connection to the SMTP server.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ smtp_debug
+ </term>
+ <listitem>
+ <para>
+ This parameter allows you to enable detailed debugging output.
+ Log messages are printed the web server's error log.
</para>
</listitem>
</varlistentry>
@@ -841,7 +833,7 @@
<listitem>
<para>
Set this to the number of days you want to let bugs go
- in the NEW or REOPENED state before notifying people they have
+ in the CONFIRMED state before notifying people they have
untouched new bugs. If you do not plan to use this feature, simply
do not set up the whining cron job described in the installation
instructions, or set this value to "0" (never whine).
@@ -932,7 +924,12 @@
contains parameters for how user names can be queried and matched
when entered.
</para>
-
+ <para>
+ Another setting called 'ajax_user_autocompletion' enables certain
+ user fields to display a list of matched user names as a drop down after typing
+ a few characters. Note that it is recommended to use mod_perl when
+ enabling 'ajax_user_autocompletion'.
+ </para>
</section>
</section>
@@ -1127,7 +1124,10 @@
<emphasis><groupname></emphasis>:
If you have created some groups, e.g. "securitysensitive", then
checkboxes will appear here to allow you to add users to, or
- remove them from, these groups.
+ remove them from, these groups. The first checkbox gives the
+ user the ability to add and remove other users as members of
+ this group. The second checkbox adds the user himself as a member
+ of the group.
</para>
</listitem>
@@ -1309,7 +1309,7 @@
basis. The number of <quote>votes</quote> available to
users is set per-product, as is the number of votes
required to move a bug automatically from the UNCONFIRMED
- status to the NEW status.
+ status to the CONFIRMED status.
</para>
<para>
@@ -1343,17 +1343,6 @@
<varlistentry>
<term>
- URL describing milestones for this product
- </term>
- <listitem>
- <para>
- If there is reference URL, provide it here
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term>
Default milestone
</term>
<listitem>
@@ -1882,12 +1871,12 @@
<note>
<para>Milestone options will only appear for a Product if you turned
- on the "usetargetmilestone" Param in the "Edit Parameters" screen.
+ on the "usetargetmilestone" parameter in the "Bug Fields" tab of the
+ "Parameters" page.
</para>
</note>
- <para>To create new Milestones, set Default Milestones, and set
- Milestone URL:</para>
+ <para>To create new Milestones, and set Default Milestones:</para>
<orderedlist>
<listitem>
@@ -1895,8 +1884,7 @@
</listitem>
<listitem>
- <para>Select "Add" in the bottom right corner.
- text</para>
+ <para>Select "Add" in the bottom right corner.</para>
</listitem>
<listitem>
@@ -1907,12 +1895,6 @@
occur in alphanumeric order For example, "Future" might be
after "Release 1.2". Select "Add".</para>
</listitem>
-
- <listitem>
- <para>From the Edit product screen, you can enter the URL of a
- page which gives information about your milestones and what
- they mean. </para>
- </listitem>
</orderedlist>
</section>
@@ -2136,8 +2118,8 @@
<section id="flags-edit">
<title>Editing a Flag</title>
<para>
- To edit a flag's properties, just click on the <quote>Edit</quote>
- link next to the flag's description. That will take you to the same
+ To edit a flag's properties, just click the flag's name.
+ That will take you to the same
form as described below (<xref linkend="flags-create"/>).
</para>
</section>
@@ -2445,6 +2427,11 @@
several types available:
<simplelist>
<member>
+ Bug ID: A field where you can enter the ID of another bug from
+ the same Bugzilla installation. To point to a bug in a remote
+ installation, use the See Also field instead.
+ </member>
+ <member>
Large Text Box: A multiple line box for entering free text.
</member>
<member>
@@ -2483,6 +2470,16 @@
<listitem>
<para>
+ <emphasis>Reverse Relationship Description:</emphasis>
+ When the custom field is of type <quote>Bug ID</quote>, you can
+ enter text here which will be used as label in the referenced
+ bug to list bugs which point to it. This gives you the ability
+ to have a mutual relationship between two bugs.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
<emphasis>Can be set on bug creation:</emphasis>
Boolean that determines whether this field can be set on
bug creation. If not selected, then a bug must be created
@@ -2507,6 +2504,45 @@
be displayed at all. Obsolete Custom Fields are hidden.
</para>
</listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Is mandatory:</emphasis>
+ Boolean that determines whether this field must be set.
+ For single and multi-select fields, this means that a (non-default)
+ value must be selected, and for text and date fields, some text
+ must be entered.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Field only appears when:</emphasis>
+ A custom field can be made visible when some criteria is met.
+ For instance, when the bug belongs to one or more products,
+ or when the bug is of some given severity. If left empty, then
+ the custom field will always be visible, in all bugs.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <emphasis>Field that controls the values that appear in this field:</emphasis>
+ When the custom field is of type <quote>Drop Down</quote> or
+ <quote>Multiple-Selection Box</quote>, you can restrict the
+ availability of the values of the custom field based on the
+ value of another field. This criteria is independent of the
+ criteria used in the <quote>Field only appears when</quote>
+ setting. For instance, you may decide that some given value
+ <quote>valueY</quote> is only available when the bug status
+ is RESOLVED while the value <quote>valueX</quote> should
+ always be listed.
+ Once you have selected the field which should control the
+ availability of the values of this custom field, you can
+ edit values of this custom field to set the criteria, see
+ <xref linkend="edit-values-list" />.
+ </para>
+ </listitem>
</itemizedlist>
</para>
</section>
@@ -2526,10 +2562,13 @@
<title>Deleting Custom Fields</title>
<para>
- It is only possible to delete obsolete Custom Fields
- if the field has never been used in the database.
- To remove a field which already has content,
- mark it as obsolete.
+ Only custom fields which are marked as obsolete, and which never
+ have been used, can be deleted completely (else the integrity
+ of the bug history would be compromised). For custom fields marked
+ as obsolete, a "Delete" link will appear in the <quote>Action</quote>
+ column. If the custom field has been used in the past, the deletion
+ will be rejected. But marking the field as obsolete is sufficient
+ to hide it from the user interface entirely.
</para>
</section>
</section>
@@ -2538,20 +2577,19 @@
<title>Legal Values</title>
<para>
- Since Bugzilla 2.20 RC1, legal values for Operating Systems, platforms,
- bug priorities and severities can be edited from the User Interface
- directly. This means that it is no longer required to manually edit
- <filename>localconfig</filename>. Starting with Bugzilla 2.23.3,
- the list of valid resolutions can be customized from the same interface.
- Since Bugzilla 3.1.1 the list of valid bug statuses can be customized
- as well.
+ Legal values for the operating system, platform, bug priority and
+ severity, custom fields of type <quote>Drop Down</quote> and
+ <quote>Multiple-Selection Box</quote> (see <xref linkend="custom-fields" />),
+ as well as the list of valid bug statuses and resolutions can be
+ customized from the same interface. You can add, edit, disable and
+ remove values which can be used with these fields.
</para>
<section id="edit-values-list">
<title>Viewing/Editing legal values</title>
<para>
Editing legal values requires <quote>admin</quote> privileges.
- Select "Legal Values" from the Administration page. A list of all
+ Select "Field Values" from the Administration page. A list of all
fields, both system fields and Custom Fields, for which legal values
can be edited appears. Click a field name to edit its legal values.
</para>
@@ -2560,6 +2598,11 @@
must be unique to that field. The sortkey is important to display these
values in the desired order.
</para>
+ <para>
+ When the availability of the values of a custom field is controlled
+ by another field, you can select from here which value of the other field
+ must be set for the value of the custom field to appear.
+ </para>
</section>
<section id="edit-values-delete">
@@ -2611,12 +2654,17 @@
<section id="voting">
<title>Voting</title>
+ <para>All of the code for voting in Bugzilla has been moved into an
+ extension, called "Voting", in the <filename>extensions/Voting/</filename>
+ directory. To enable it, you must remove the <filename>disabled</filename>
+ file from that directory, and run <filename>checksetup.pl</filename>.</para>
+
<para>Voting allows users to be given a pot of votes which they can allocate
to bugs, to indicate that they'd like them fixed.
This allows developers to gauge
user need for a particular enhancement or bugfix. By allowing bugs with
a certain number of votes to automatically move from "UNCONFIRMED" to
- "NEW", users of the bug system can help high-priority bugs garner
+ "CONFIRMED", users of the bug system can help high-priority bugs garner
attention so they don't sit for a long time awaiting triage.</para>
<para>To modify Voting settings:</para>
@@ -2645,7 +2693,7 @@
<para><emphasis>Number of votes a bug in this product needs to
automatically get out of the UNCONFIRMED state</emphasis>:
Setting this field to "0" disables the automatic move of
- bugs from UNCONFIRMED to NEW.
+ bugs from UNCONFIRMED to CONFIRMED.
</para>
</listitem>
@@ -2667,12 +2715,12 @@
</para>
<para>
- Quips are controlled by the <emphasis>enablequips</emphasis> parameter.
- It has several possible values: on, approved, frozen or off.
- In order to enable quips approval you need to set this parameter
- to "approved". In this way, users are free to submit quips for
- addition but an administrator must explicitly approve them before
- they are actually used.
+ Quip submission is controlled by the <emphasis>quip_list_entry_control</emphasis>
+ parameter. It has several possible values: open, moderated, or closed.
+ In order to enable quips approval you need to set this parameter to
+ "moderated". In this way, users are free to submit quips for addition
+ but an administrator must explicitly approve them before they are
+ actually used.
</para>
<para>
@@ -2687,7 +2735,7 @@
</para>
<para>
- Next to each tip there is a checkbox, under the
+ Next to each quip there is a checkbox, under the
"Approved" column. Quips who have this checkbox checked are
already approved and will appear next to the search results.
The ones that have it unchecked are still preserved in the
@@ -2699,6 +2747,11 @@
Also, there is a delete link next to each quip,
which can be used in order to permanently delete a quip.
</para>
+
+ <para>
+ Display of quips is controlled by the <emphasis>display_quips</emphasis>
+ user preference. Possible values are "on" and "off".
+ </para>
</section>
<section id="groups">
@@ -2706,7 +2759,7 @@
<para>
Groups allow for separating bugs into logical divisions.
- Groups are typically used to
+ Groups are typically used
to isolate bugs that should only be seen by certain people. For
example, a company might create a different group for each one of its customers
or partners. Group permissions could be set so that each partner or customer would
diff --git a/Websites/bugs.webkit.org/docs/en/xml/customization.xml b/Websites/bugs.webkit.org/docs/en/xml/customization.xml
index 81a5b49..9b62b1d 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/customization.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/customization.xml
@@ -2,6 +2,23 @@
<chapter id="customization">
<title>Customizing Bugzilla</title>
+ <section id="extensions">
+ <title>Bugzilla Extensions</title>
+
+ <para>
+ One of the best ways to customize Bugzilla is by writing a Bugzilla
+ Extension. Bugzilla Extensions let you modify both the code and
+ UI of Bugzilla in a way that can be distributed to other Bugzilla
+ users and ported forward to future versions of Bugzilla with minimal
+ effort.
+ </para>
+
+ <para>
+ See the <ulink url="api/Bugzilla/Extension.html">Bugzilla Extension
+ documentation</ulink> for information on how to write an Extension.
+ </para>
+ </section>
+
<section id="cust-skins">
<title>Custom Skins</title>
@@ -146,9 +163,9 @@
<note>
<para>
Regardless of which method you choose, it is recommended that
- you run <command>./checksetup.pl</command> after creating or
+ you run <command>./checksetup.pl</command> after
editing any templates in the <filename>template/en/default</filename>
- directory, and after editing any templates in the
+ directory, and after creating or editing any templates in the
<filename>custom</filename> directory.
</para>
</note>
@@ -190,21 +207,12 @@
This means that if the data can possibly contain special HTML characters
such as <, and the data was not intended to be HTML, they need to be
converted to entity form, i.e. &lt;. You use the 'html' filter in the
- Template Toolkit to do this. If you forget, you may open up
- your installation to cross-site scripting attacks.
+ Template Toolkit to do this (or the 'uri' filter to encode special
+ characters in URLs). If you forget, you may open up your installation
+ to cross-site scripting attacks.
</para>
<para>
- Also note that Bugzilla adds a few filters of its own, that are not
- in standard Template Toolkit. In particular, the 'url_quote' filter
- can convert characters that are illegal or have special meaning in URLs,
- such as &, to the encoded form, i.e. %26. This actually encodes most
- characters (but not the common ones such as letters and numbers and so
- on), including the HTML-special characters, so there's never a need to
- HTML filter afterwards.
- </para>
-
- <para>
Editing templates is a good way of doing a <quote>poor man's custom
fields</quote>.
For example, if you don't use the Status Whiteboard, but want to have
@@ -438,241 +446,6 @@
</section>
- <section id="cust-hooks">
- <title>The Bugzilla Extension Mechanism</title>
-
- <warning>
- <para>
- Note that the below paths are inconsistent and confusing. They will
- likely be changed in Bugzilla 4.0.
- </para>
- </warning>
-
- <para>
- Extensions are a way for extensions to Bugzilla to insert code
- into the standard Bugzilla templates and source files
- without modifying these files themselves. The extension mechanism
- defines a consistent API for extending the standard templates and source files
- in a way that cleanly separates standard code from extension code.
- Hooks reduce merge conflicts and make it easier to write extensions that work
- across multiple versions of Bugzilla, making upgrading a Bugzilla installation
- with installed extensions easier. Furthermore, they make it easy to install
- and remove extensions as each extension is nothing more than a
- simple directory structure.
- </para>
-
- <para>
- There are two main types of hooks: code hooks and template hooks. Code
- hooks allow extensions to invoke code at specific points in various
- source files, while template hooks allow extensions to add elements to
- the Bugzilla user interface.
- </para>
-
- <para>
- A hook is just a named place in a standard source or template file
- where extension source code or template files for that hook get processed.
- Each extension has a corresponding directory in the Bugzilla directory
- tree (<filename>BUGZILLA_ROOT/extensions/extension_name</filename>). Hooking
- an extension source file or template to a hook is as simple as putting
- the extension file into extension's template or code directory.
- When Bugzilla processes the source file or template and reaches the hook,
- it will process all extension files in the hook's directory.
- The hooks themselves can be added into any source file or standard template
- upon request by extension authors.
- </para>
-
- <para>
- To use hooks to extend Bugzilla, first make sure there is
- a hook at the appropriate place within the source file or template you
- want to extend. The exact appearance of a hook depends on if the hook
- is a code hook or a template hook.
- </para>
-
- <para>
- Code hooks appear in Bugzilla source files as a single method call
- in the format <literal role="code">Bugzilla::Hook->process("<varname>name</varname>");</literal>.
- For instance, <filename>enter_bug.cgi</filename> may invoke the hook
- "<varname>enter_bug-entrydefaultvars</varname>". Thus, a source file at
- <filename>BUGZILLA_ROOT/extensions/EXTENSION_NAME/code/enter_bug-entrydefaultvars.pl</filename>
- will be automatically invoked when the code hook is reached.
- </para>
-
- <para>
- Template hooks appear in the standard Bugzilla templates as a
- single directive in the format
- <literal role="code">[% Hook.process("<varname>name</varname>") %]</literal>,
- where <varname>name</varname> is the unique name of the hook.
- </para>
-
- <para>
- If you aren't sure what you want to extend or just want to browse the
- available hooks, either use your favorite multi-file search
- tool (e.g. <command>grep</command>) to search the standard templates
- for occurrences of <methodname>Hook.process</methodname> or the source
- files for occurrences of <methodname>Bugzilla::Hook::process</methodname>.
- </para>
-
- <para>
- If there is no hook at the appropriate place within the Bugzilla
- source file or template you want to extend,
- <ulink url="http://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&component=User%20Interface">file
- a bug requesting one</ulink>, specifying:
- </para>
-
- <simplelist>
- <member>the source or template file for which you are
- requesting a hook;</member>
- <member>
- where in the file you would like the hook to be placed
- (line number/position for latest version of the file in CVS
- or description of location);
- </member>
- <member>the purpose of the hook;</member>
- <member>a link to information about your extension, if any.</member>
- </simplelist>
-
- <para>
- The Bugzilla reviewers will promptly review each hook request,
- name the hook, add it to the template or source file, and check
- the new version of the template into CVS.
- </para>
-
- <para>
- You may optionally attach a patch to the bug which implements the hook
- and check it in yourself after receiving approval from a Bugzilla
- reviewer. The developers may suggest changes to the location of the
- hook based on their analysis of your needs or so the hook can satisfy
- the needs of multiple extensions, but the process of getting hooks
- approved and checked in is not as stringent as the process for general
- changes to Bugzilla, and any extension, whether released or still in
- development, can have hooks added to meet their needs.
- </para>
-
- <para>
- After making sure the hook you need exists (or getting it added if not),
- add your extension to the directory within the Bugzilla
- extensions tree corresponding to the hook.
- </para>
-
- <para>
- That's it! Now, when the source file or template containing the hook
- is processed, your extension file will be processed at the point
- where the hook appears.
- </para>
-
- <para>
- For example, let's say you have an extension named Projman that adds
- project management capabilities to Bugzilla. Projman has an
- administration interface <filename>edit-projects.cgi</filename>,
- and you want to add a link to it into the navigation bar at the bottom
- of every Bugzilla page for those users who are authorized
- to administer projects.
- </para>
-
- <para>
- The navigation bar is generated by the template file
- <filename>useful-links.html.tmpl</filename>, which is located in
- the <filename>global/</filename> subdirectory on the standard Bugzilla
- template path
- <filename>BUGZILLA_ROOT/template/en/default/</filename>.
- Looking in <filename>useful-links.html.tmpl</filename>, you find
- the following hook at the end of the list of standard Bugzilla
- administration links:
- </para>
-
- <programlisting><![CDATA[...
- [% ', <a href="editkeywords.cgi">keywords</a>'
- IF user.groups.editkeywords %]
- [% Hook.process("edit") %]
-...]]></programlisting>
-
- <para>
- The corresponding extension file for this hook is
- <filename>BUGZILLA_ROOT/extensions/projman/template/en/global/useful-links-edit.html.tmpl</filename>.
- You then create that template file and add the following constant:
- </para>
-
- <programlisting><![CDATA[...[% ', <a href="edit-projects.cgi">projects</a>' IF user.groups.projman_admins %]]]></programlisting>
-
- <para>
- Voila! The link now appears after the other administration links in the
- navigation bar for users in the <literal>projman_admins</literal> group.
- </para>
-
- <para>
- Now, let us say your extension adds a custom "project_manager" field
- to enter_bug.cgi. You want to modify the CGI script to set the default
- project manager to be productname@company.com. Looking at
- <filename>enter_bug.cgi</filename>, you see the enter_bug-entrydefaultvars
- hook near the bottom of the file before the default form values are set.
- The corresponding extension source file for this hook is located at
- <filename>BUGZILLA_ROOT/extensions/projman/code/enter_bug-entrydefaultvars.pl</filename>.
- You then create that file and add the following:
- </para>
-
- <programlisting>$default{'project_manager'} = $product.'@company.com';</programlisting>
-
- <para>
- This code will be invoked whenever enter_bug.cgi is executed.
- Assuming that the rest of the customization was completed (e.g. the
- custom field was added to the enter_bug template and the required hooks
- were used in process_bug.cgi), the new field will now have this
- default value.
- </para>
-
- <para>
- Notes:
- </para>
-
- <itemizedlist>
- <listitem>
- <para>
- If your extension includes entirely new templates in addition to
- extensions of standard templates, it should store those new
- templates in its
- <filename>BUGZILLA_ROOT/extensions/template/en/</filename>
- directory. Extension template directories, like the
- <filename>default/</filename> and <filename>custom/</filename>
- directories, are part of the template search path, so putting templates
- there enables them to be found by the template processor.
- </para>
-
- <para>
- The template processor looks for templates first in the
- <filename>custom/</filename> directory (i.e. templates added by the
- specific installation), then in the <filename>extensions/</filename>
- directory (i.e. templates added by extensions), and finally in the
- <filename>default/</filename> directory (i.e. the standard Bugzilla
- templates). Thus, installation-specific templates override both
- default and extension templates.
- </para>
- </listitem>
-
- <listitem>
- <para>
- If you are looking to customize Bugzilla, you can also take advantage
- of template hooks. To do so, create a directory in
- <filename>BUGZILLA_ROOT/template/en/custom/hook/</filename>
- that corresponds to the hook you wish to use, then place your
- customization templates into those directories. For example,
- if you wanted to use the hook "end" in
- <filename>global/useful-links.html.tmpl</filename>, you would
- create the directory <filename>BUGZILLA_ROOT/template/en/custom/hook/
- global/useful-links.html.tmpl/end/</filename> and add your customization
- template to this directory.
- </para>
-
- <para>
- Obviously this method of customizing Bugzilla only lets you add code
- to the standard source files and templates; you cannot change the
- existing code. Nevertheless, for those customizations that only add
- code, this method can reduce conflicts when merging changes,
- making upgrading your customized Bugzilla installation easier.
- </para>
- </listitem>
- </itemizedlist>
- </section>
-
<section id="cust-change-permissions">
<title>Customizing Who Can Change What</title>
@@ -746,7 +519,7 @@
positive check, which returns 1 (allow) if certain conditions are true,
or a negative check, which returns 0 (deny.) E.g.:
<programlisting> if ($field eq "qacontact") {
- if (Bugzilla->user->groups("quality_assurance")) {
+ if (Bugzilla->user->in_group("quality_assurance")) {
return 1;
}
else {
@@ -790,8 +563,17 @@
</para>
</section>
- <!-- Integrating Bugzilla with Third-Party Tools -->
- &integration;
+ <section id="integration">
+ <title>Integrating Bugzilla with Third-Party Tools</title>
+
+ <para>
+ Many utilities and applications can integrate with Bugzilla,
+ either on the client- or server-side. None of them are maintained
+ by the Bugzilla community, nor are they tested during our
+ QA tests, so use them at your own risk. They are listed at
+ <ulink url="https://wiki.mozilla.org/Bugzilla:Addons" />.
+ </para>
+ </section>
</chapter>
diff --git a/Websites/bugs.webkit.org/docs/en/xml/gfdl.xml b/Websites/bugs.webkit.org/docs/en/xml/gfdl.xml
index 1d84d12..03dd89c 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/gfdl.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/gfdl.xml
@@ -9,8 +9,8 @@
<para>Version 1.1, March 2000</para>
<blockquote>
- <para>Copyright (C) 2000 Free Software Foundation, Inc. 59 Temple Place,
- Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and
+ <para>Copyright (C) 2000 Free Software Foundation, Inc. 51 Franklin Street,
+ Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and
distribute verbatim copies of this license document, but changing it is
not allowed.</para>
</blockquote>
diff --git a/Websites/bugs.webkit.org/docs/en/xml/glossary.xml b/Websites/bugs.webkit.org/docs/en/xml/glossary.xml
index 5b6d1a6..aef3ab3 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/glossary.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/glossary.xml
@@ -271,8 +271,6 @@
Perl module, which Bugzilla uses to send email, can be configured to
use many different underlying implementations for actually sending the
mail using the <option>mail_delivery_method</option> parameter.
- Implementations other than <literal>sendmail</literal> require that the
- <option>sendmailnow</option> param be set to <literal>on</literal>.
</para>
</glossdef>
</glossentry>
@@ -281,7 +279,7 @@
<glossterm>MySQL</glossterm>
<glossdef>
- <para>MySQL is currently the required
+ <para>MySQL is one of the supported
<glossterm linkend="gloss-rdbms">RDBMS</glossterm> for Bugzilla. MySQL
can be downloaded from <ulink url="http://www.mysql.com"/>. While you
should familiarize yourself with all of the documentation, some high
@@ -306,8 +304,7 @@
<varlistentry>
<term><ulink url="http://www.mysql.com/doc/en/Privilege_system.html">Privilege System</ulink></term>
<listitem>
- <para>Much more detailed information about the suggestions in
- <xref linkend="security-mysql"/>.
+ <para>Information about how to protect your MySQL server.
</para>
</listitem>
</varlistentry>
diff --git a/Websites/bugs.webkit.org/docs/en/xml/installation.xml b/Websites/bugs.webkit.org/docs/en/xml/installation.xml
index 526306f..e9830e2 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/installation.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/installation.xml
@@ -1,5 +1,4 @@
<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"> -->
-<!-- $Id$ -->
<chapter id="installing-bugzilla">
<title>Installing Bugzilla</title>
@@ -84,7 +83,7 @@
<para>Any machine that doesn't have Perl on it is a sad machine indeed.
If you don't have it and your OS doesn't provide official packages,
- visit <ulink url="http://www.perl.com"/>.
+ visit <ulink url="http://www.perl.org"/>.
Although Bugzilla runs with Perl &min-perl-ver;,
it's a good idea to be using the latest stable version.
</para>
@@ -119,8 +118,8 @@
</note>
<para>If you install from something other than a packaging/installation
- system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
- (Windows Executable), or .msi (Microsoft Installer), make sure the MySQL
+ system, such as .rpm (RPM Package Manager), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Windows Installer), make sure the MySQL
server is started when the machine boots.
</para>
</section>
@@ -136,8 +135,8 @@
</para>
<para>If you install from something other than a packaging/installation
- system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
- (Windows Executable), or .msi (Microsoft Installer), make sure the
+ system, such as .rpm (RPM Package Manager), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Windows Installer), make sure the
PostgreSQL server is started when the machine boots.
</para>
</section>
@@ -157,8 +156,8 @@
<para>
If you install from something other than a packaging/installation
- system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
- (Windows Executable), or .msi (Microsoft Installer), make sure the
+ system, such as .rpm (RPM Package Manager), .deb (Debian Package), .exe
+ (Windows Executable), or .msi (Windows Installer), make sure the
Oracle server is started when the machine boots.
</para>
</section>
@@ -243,29 +242,20 @@
</para>
<para>
- There is a meta-module called Bundle::Bugzilla,
- which installs all the other
- modules with a single command. You should use this if you are running
- Perl 5.6.1 or above.
- </para>
-
- <para>
- The preferred way of installing Perl modules is via CPAN on Unix,
- or PPM on Windows (see <xref linkend="win32-perl-modules"/>). These
- instructions assume you are using CPAN; if for some reason you need
- to install the Perl modules manually, see
- <xref linkend="install-perlmodules-manual"/>.
+ The preferred way to install missing Perl modules is to use the package
+ manager provided by your operating system (e.g <quote>rpm</quote> or
+ <quote>yum</quote> on Linux distros, or <quote>ppm</quote> on Windows
+ if using ActivePerl, see <xref linkend="win32-perl-modules"/>).
+ If some Perl modules are still missing or are too old, then we recommend
+ using the <filename>install-module.pl</filename> script (doesn't work
+ with ActivePerl on Windows). If for some reason you really need to
+ install the Perl modules manually, see
+ <xref linkend="install-perlmodules-manual"/>. For instance, on Unix,
+ you invoke <filename>install-module.pl</filename> as follows:
</para>
- <screen><prompt>bash#</prompt> perl -MCPAN -e 'install "<modulename>"'</screen>
+ <screen><prompt>bash#</prompt> perl install-module.pl <modulename></screen>
- <para>
- If you using Bundle::Bugzilla, invoke the magic CPAN command on it.
- Otherwise, you need to work down the
- list of modules that <filename>checksetup.pl</filename> says are
- required, in the order given, invoking the command on each.
- </para>
-
<tip>
<para>Many people complain that Perl modules will not install for
them. Most times, the error messages complain that they are missing a
@@ -299,7 +289,7 @@
<listitem>
<para>
- CGI &min-cgi-ver;
+ CGI (&min-cgi-ver;)
</para>
</listitem>
@@ -311,14 +301,25 @@
<listitem>
<para>
+ DateTime (&min-datetime-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ DateTime::TimeZone (&min-datetime-timezone-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
DBI (&min-dbi-ver;)
</para>
</listitem>
<listitem>
<para>
- <link linkend="install-modules-dbd-mysql">DBD::mysql</link>
- (&min-dbd-mysql-ver;) if using MySQL
+ DBD::mysql (&min-dbd-mysql-ver;) if using MySQL
</para>
</listitem>
@@ -336,14 +337,7 @@
<listitem>
<para>
- File::Spec (&min-file-spec-ver;)
- </para>
- </listitem>
-
- <listitem>
- <para>
- <link linkend="install-modules-template">Template</link>
- (&min-template-ver;)
+ Digest::SHA (&min-digest-sha-ver;)
</para>
</listitem>
@@ -355,7 +349,19 @@
<listitem>
<para>
- Email::MIME::Modifier (&min-email-mime-modifier-ver;)
+ Email::MIME (&min-email-mime-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Template (&min-template-ver;)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ URI (&min-uri-ver;)
</para>
</listitem>
</orderedlist>
@@ -364,43 +370,38 @@
<orderedlist>
<listitem>
<para>
- <link linkend="install-modules-gd">GD</link>
- (&min-gd-ver;) for bug charting
+ GD (&min-gd-ver;) for bug charting
</para>
</listitem>
<listitem>
<para>
Template::Plugin::GD::Image
- (&min-gd-ver;) for Graphical Reports
+ (&min-template-plugin-gd-image-ver;) for Graphical Reports
</para>
</listitem>
<listitem>
<para>
- <link linkend="install-modules-chart-base">Chart::Base</link>
- (&min-chart-base-ver;) for bug charting
+ Chart::Lines (&min-chart-lines-ver;) for bug charting
</para>
</listitem>
<listitem>
<para>
- <link linkend="install-modules-gd-graph">GD::Graph</link>
- (&min-gd-graph-ver;) for bug charting
+ GD::Graph (&min-gd-graph-ver;) for bug charting
</para>
</listitem>
<listitem>
<para>
- <link linkend="install-modules-gd-text">GD::Text</link>
- (&min-gd-text-ver;) for bug charting
+ GD::Text (&min-gd-text-ver;) for bug charting
</para>
</listitem>
<listitem>
<para>
- <link linkend="install-modules-xml-twig">XML::Twig</link>
- (&min-xml-twig-ver;) for bug import/export
+ XML::Twig (&min-xml-twig-ver;) for bug import/export
</para>
</listitem>
@@ -419,14 +420,7 @@
<listitem>
<para>
- <link linkend="install-modules-patchreader">PatchReader</link>
- (&min-patchreader-ver;) for pretty HTML view of patches
- </para>
- </listitem>
-
- <listitem>
- <para>
- Image::Magick (&min-image-magick-ver;) for converting BMP image attachments to PNG
+ PatchReader (&min-patchreader-ver;) for pretty HTML view of patches
</para>
</listitem>
@@ -439,6 +433,13 @@
<listitem>
<para>
+ Authen::SASL
+ (&min-authen-sasl-ver;) for SASL Authentication
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
Authen::Radius
(&min-authen-radius-ver;) for RADIUS Authentication
</para>
@@ -446,8 +447,21 @@
<listitem>
<para>
- <link linkend="install-modules-soap-lite">SOAP::Lite</link>
- (&min-soap-lite-ver;) for the web service interface
+ SOAP::Lite (&min-soap-lite-ver;) for the web service interface
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ JSON::RPC
+ (&min-json-rpc-ver;) for the JSON-RPC interface
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Test::Taint
+ (&min-test-taint-ver;) for the web service interface
</para>
</listitem>
@@ -481,127 +495,28 @@
<listitem>
<para>
- mod_perl2
- (&min-mod_perl2-ver;) for mod_perl
+ TheSchwartz
+ (&min-theschwartz-ver;) for Mail Queueing
</para>
</listitem>
<listitem>
<para>
- CGI
- (&min-mp-cgi-ver;) for mod_perl
+ Daemon::Generic
+ (&min-daemon-generic-ver;) for Mail Queueing
</para>
</listitem>
+ <listitem>
+ <para>
+ mod_perl2
+ (&min-mod_perl2-ver;) for mod_perl
+ </para>
+ </listitem>
</orderedlist>
</para>
-
- <section id="install-modules-dbd-mysql">
- <title>DBD::mysql</title>
-
- <para>The installation process will ask you a few questions about the
- desired compilation target and your MySQL installation. For most of the
- questions the provided default will be adequate, but when asked if your
- desired target is the MySQL or mSQL packages, you should
- select the MySQL-related ones. Later you will be asked if you wish to
- provide backwards compatibility with the older MySQL packages; you
- should answer YES to this question. The default is NO.</para>
-
- <para>A host of 'localhost' should be fine. A testing user of 'test',
- with a null password, should have sufficient access to run
- tests on the 'test' database which MySQL creates upon installation.
- </para>
- </section>
-
- <section id="install-modules-template">
- <title>Template Toolkit (&min-template-ver;)</title>
-
- <para>When you install Template Toolkit, you'll get asked various
- questions about features to enable. The defaults are fine, except
- that it is recommended you use the high speed XS Stash of the Template
- Toolkit, in order to achieve best performance.
- </para>
- </section>
-
- <section id="install-modules-gd">
- <title>GD (&min-gd-ver;)</title>
-
- <para>The GD module is only required if you want graphical reports.
- </para>
-
- <note>
- <para>The Perl GD module requires some other libraries that may or
- may not be installed on your system, including
- <classname>libpng</classname>
- and
- <classname>libgd</classname>.
- The full requirements are listed in the Perl GD module README.
- If compiling GD fails, it's probably because you're
- missing a required library.</para>
- </note>
-
- <tip>
- <para>The version of the GD module you need is very closely tied
- to the <classname>libgd</classname> version installed on your system.
- If you have a version 1.x of <classname>libgd</classname> the 2.x
- versions of the GD module won't work for you.
- </para>
- </tip>
- </section>
-
- <section id="install-modules-chart-base">
- <title>Chart::Base (&min-chart-base-ver;)</title>
-
- <para>The Chart::Base module is only required if you want graphical
- reports.
- Note that earlier versions that 0.99c used GIFs, which are no longer
- supported by the latest versions of GD.</para>
- </section>
-
- <section id="install-modules-gd-graph">
- <title>GD::Graph (&min-gd-graph-ver;)</title>
-
- <para>The GD::Graph module is only required if you want graphical
- reports.
- </para>
- </section>
-
- <section id="install-modules-gd-text">
- <title>GD::Text (&min-gd-text-ver;)</title>
-
- <para>The GD::Text module is only required if you want graphical
- reports.
- </para>
- </section>
-
- <section id="install-modules-xml-twig">
- <title>XML::Twig (&min-xml-twig-ver;)</title>
-
- <para>The XML::Twig module is only required if you want to import
- XML bugs using the <filename>importxml.pl</filename>
- script. This is required to use Bugzilla's "move bugs" feature;
- you may also want to use it for migrating from another bug database.
- </para>
- </section>
-
- <section id="install-modules-soap-lite">
- <title>SOAP::Lite (&min-soap-lite-ver;)</title>
- <para>Installing SOAP::Lite enables your Bugzilla installation to be
- accessible at a standardized Web Service interface (SOAP/XML-RPC)
- by third-party applications via HTTP(S).
- </para>
- </section>
-
- <section id="install-modules-patchreader">
- <title>PatchReader (&min-patchreader-ver;)</title>
-
- <para>The PatchReader module is only required if you want to use
- Patch Viewer, a
- Bugzilla feature to show code patches in your web browser in a more
- readable form.
- </para>
- </section>
</section>
+
<section id="install-MTA">
<title>Mail Transfer Agent (MTA)</title>
@@ -665,10 +580,6 @@
<para>Bugzilla requires <literal>mod_perl</literal> to be installed, which can be
obtained from <ulink url="http://perl.apache.org"/> - Bugzilla requires
version &min-mod_perl2-ver; (AKA 2.0.0-RC5) to be installed.</para>
-
- <para>Bugzilla also requires a more up-to-date version of the CGI
- perl module to be installed, version &min-mp-cgi-ver; as opposed to &min-cgi-ver;
- </para>
</section>
</section>
@@ -707,7 +618,7 @@
the user you will create for your database. Pick a strong
password (for simplicity, it should not contain single quote
characters) and put it here. $db_driver can be either 'mysql',
- 'Pg' or 'oracle'.
+ 'Pg', 'Oracle' or 'Sqlite'.
</para>
<note>
@@ -751,8 +662,8 @@
<para>
This section deals with configuring your database server for use
with Bugzilla. Currently, MySQL (<xref linkend="mysql"/>),
- PostgreSQL (<xref linkend="postgresql"/>) and Oracle (<xref linkend="oracle"/>)
- are available.
+ PostgreSQL (<xref linkend="postgresql"/>), Oracle (<xref linkend="oracle"/>)
+ and SQLite (<xref linkend="sqlite"/>) are available.
</para>
<section id="database-schema">
@@ -773,9 +684,23 @@
<caution>
<para>
- MySQL's default configuration is very insecure.
- <xref linkend="security-mysql"/> has some good information for
- improving your installation's security.
+ MySQL's default configuration is insecure.
+ We highly recommend to run <filename>mysql_secure_installation</filename>
+ on Linux or the MySQL installer on Windows, and follow the instructions.
+ Important points to note are:
+ <orderedlist>
+ <listitem>
+ <para>Be sure that the root account has a secure password set.</para>
+ </listitem>
+ <listitem>
+ <para>Do not create an anonymous account, and if it exists, say "yes"
+ to remove it.</para>
+ </listitem>
+ <listitem>
+ <para>If your web server and MySQL server are on the same machine,
+ you should disable the network access.</para>
+ </listitem>
+ </orderedlist>
</para>
</caution>
@@ -783,11 +708,11 @@
<title>Allow large attachments and many comments</title>
<para>By default, MySQL will only allow you to insert things
- into the database that are smaller than 64KB. Attachments
+ into the database that are smaller than 1MB. Attachments
may be larger than this. Also, Bugzilla combines all comments
on a single bug into one field for full-text searching, and the
- combination of all comments on a single bug are very likely to
- be larger than 64KB.</para>
+ combination of all comments on a single bug could in some cases
+ be larger than 1MB.</para>
<para>To change MySQL's default, you need to edit your MySQL
configuration file, which is usually <filename>/etc/my.cnf</filename>
@@ -918,12 +843,12 @@
<para>As the postgres user, you then need to create a new user: </para>
- <screen> <prompt>bash$</prompt> createuser -U postgres -dAP bugs</screen>
+ <screen> <prompt>bash$</prompt> createuser -U postgres -dRSP bugs</screen>
<para>When asked for a password, provide the password which will be set as
<replaceable>$db_pass</replaceable> in <filename>localconfig</filename>.
- The created user will have the ability to create databases and will not be
- able to create new users.</para>
+ The created user will not be a superuser (-S) and will not be able to create
+ new users (-R). He will only have the ability to create databases (-d).</para>
</section>
<section>
@@ -1021,7 +946,26 @@
</para>
</section>
</section>
- </section>
+
+ <section id="sqlite">
+ <title>SQLite</title>
+
+ <caution>
+ <para>
+ Due to SQLite's <ulink url="http://sqlite.org/faq.html#q5">concurrency
+ limitations</ulink> we recommend SQLite only for small and development
+ Bugzilla installations.
+ </para>
+ </caution>
+
+ <para>
+ No special configuration is required to run Bugzilla on SQLite.
+ The database will be stored in <filename>data/db/$db_name</filename>,
+ where <literal>$db_name</literal> is the database name defined
+ in <filename>localconfig</filename>.
+ </para>
+ </section>
+ </section>
<section>
<title>checksetup.pl</title>
@@ -1107,7 +1051,7 @@
AddHandler cgi-script .cgi
Options +Indexes +ExecCGI
DirectoryIndex index.cgi
- AllowOverride Limit
+ AllowOverride Limit FileInfo Indexes
</Directory>
</programlisting>
@@ -1132,6 +1076,14 @@
when granting extra access.
</para>
</note>
+
+ <note>
+ <para>
+ On Windows, you may have to also add the
+ <computeroutput>ScriptInterpreterSource Registry-Strict</computeroutput>
+ line, see <link linkend="win32-http">Windows specific notes</link>.
+ </para>
+ </note>
</step>
<step>
@@ -1199,7 +1151,7 @@
</warning>
<programlisting>
- PerlSwitches -I/var/www/html/bugzilla -I/var/www/html/bugzilla/lib -w -T
+ PerlSwitches -w -T
PerlConfigRequire /var/www/html/bugzilla/mod_perl.pl
</programlisting>
</step>
@@ -1377,9 +1329,7 @@
<para>
Log in with the administrator account you defined in the last
<filename>checksetup.pl</filename> run. You should go through
- the parameters on the Edit Parameters page
- (see link in the footer) and see if there are any you wish to
- change.
+ the Parameters page and see if there are any you wish to change.
They key parameters are documented in <xref linkend="parameters"/>;
you should certainly alter
<command>maintainer</command> and <command>urlbase</command>;
@@ -1388,14 +1338,6 @@
</para>
<para>
- This would also be a good time to revisit the
- <filename>localconfig</filename> file and make sure that the
- names of the priorities, severities, platforms and operating systems
- are those you wish to use when you start creating bugs. Remember
- to rerun <filename>checksetup.pl</filename> if you change it.
- </para>
-
- <para>
Bugzilla has several optional features which require extra
configuration. You can read about those in
<xref linkend="extraconfig"/>.
@@ -1427,7 +1369,7 @@
daily at 5 after midnight:
</para>
- <programlisting>5 0 * * * cd <your-bugzilla-directory> ; ./collectstats.pl</programlisting>
+ <programlisting>5 0 * * * cd <your-bugzilla-directory> && ./collectstats.pl</programlisting>
<para>
After two days have passed you'll be able to view bug graphs from
@@ -1450,7 +1392,7 @@
<para>What good are
bugs if they're not annoying? To help make them more so you
can set up Bugzilla's automatic whining system to complain at engineers
- which leave their bugs in the NEW or REOPENED state without triaging them.
+ which leave their bugs in the CONFIRMED state without triaging them.
</para>
<para>
This can be done by adding the following command as a daily
@@ -1458,7 +1400,7 @@
graphs. This example runs it at 12.55am.
</para>
- <programlisting>55 0 * * * cd <your-bugzilla-directory> ; ./whineatnews.pl</programlisting>
+ <programlisting>55 0 * * * cd <your-bugzilla-directory> && ./whineatnews.pl</programlisting>
<note>
<para>
@@ -1488,7 +1430,7 @@
graphs. This example runs it every 15 minutes.
</para>
- <programlisting>*/15 * * * * cd <your-bugzilla-directory> ; ./whine.pl</programlisting>
+ <programlisting>*/15 * * * * cd <your-bugzilla-directory> && ./whine.pl</programlisting>
<note>
<para>
@@ -1688,16 +1630,6 @@
</tip>
</section>
- <section id="win32-code-changes">
- <title>Code changes required to run on Win32</title>
-
- <para>
- Bugzilla on Win32 is supported out of the box from version 2.20; this
- means that no code changes are required to get Bugzilla running.
- </para>
-
- </section>
-
<section id="win32-http">
<title>Serving the web pages</title>
@@ -1713,14 +1645,16 @@
<note>
<para>
- If using Apache on windows, you can set the <ulink
- url="http://httpd.apache.org/docs-2.0/mod/core.html#scriptinterpretersource">ScriptInterpreterSource</ulink>
- directive in your Apache config to avoid having to modify
- the first line of every script to contain your path to Perl
- instead of <filename>/usr/bin/perl</filename>. When setting
- <filename>ScriptInterpreterSource</filename>, do not forget
- to specify the <command>-T</command> flag to enable the taint
- mode. For example: <command>C:\Perl\bin\perl.exe -T</command>.
+ The web server looks at <filename>/usr/bin/perl</filename> to
+ call Perl. If you are using Apache on windows, you can set the
+ <ulink url="http://httpd.apache.org/docs-2.0/mod/core.html#scriptinterpretersource">ScriptInterpreterSource</ulink>
+ directive in your Apache config file to make it look at the
+ right place: insert the line
+ <programlisting>ScriptInterpreterSource Registry-Strict</programlisting>
+ into your <filename>httpd.conf</filename> file, and create the key
+ <programlisting>HKEY_CLASSES_ROOT\.cgi\Shell\ExecCGI\Command</programlisting>
+ with <option>C:\Perl\bin\perl.exe -T</option> as value (adapt to your
+ path if needed) in the registry. When this is done, restart Apache.
</para>
</note>
@@ -1750,13 +1684,8 @@
<ulink url="http://www.postfix.org/">Postfix</ulink>
is used as the built-in email server. Postfix provides an executable
that mimics sendmail enough to fool Bugzilla, as long as Bugzilla can
- find it.</para>
-
- <para>As of version 2.20, Bugzilla will be able to find the fake
- sendmail executable without any assistance. However, you will have
- to turn on the sendmailnow parameter before you do anything that would
- result in email being sent. For more information, see the description
- of the sendmailnow parameter in <xref linkend="parameters"/>.</para>
+ find it. Bugzilla is able to find the fake sendmail executable without
+ any assistance.</para>
</section>
@@ -1766,12 +1695,12 @@
<para>Apple does not include the GD library with Mac OS X. Bugzilla
needs this for bug graphs.</para>
- <para>You can use DarwinPorts (<ulink url="http://darwinports.com/"/>)
+ <para>You can use MacPorts (<ulink url="http://www.macports.org/"/>)
or Fink (<ulink url="http://sourceforge.net/projects/fink/"/>), both
of which are similar in nature to the CPAN installer, but install
common unix programs.</para>
- <para>Follow the instructions for setting up DarwinPorts or Fink.
+ <para>Follow the instructions for setting up MacPorts or Fink.
Once you have one installed, you'll want to use it to install the
<filename>gd2</filename> package.
</para>
@@ -1797,7 +1726,7 @@
</para>
</note>
- <para>Also available via DarwinPorts and Fink is
+ <para>Also available via MacPorts and Fink is
<filename>expat</filename>. After installing the expat package, you
will be able to install XML::Parser using CPAN. If you use fink, there
is one caveat. Unlike recent versions of
@@ -1976,7 +1905,7 @@
<screen>
<prompt>bash$</prompt>
- <command>wget http://perl.com/CPAN/src/stable.tar.gz</command>
+ <command>wget http://perl.org/CPAN/src/stable.tar.gz</command>
<prompt>bash$</prompt>
<command>tar zvxf stable.tar.gz</command>
<prompt>bash$</prompt>
@@ -2092,21 +2021,22 @@
<section id="upgrade">
<title>Upgrading to New Releases</title>
-
+
<para>Upgrading to new Bugzilla releases is very simple. There is
- a script included with Bugzilla that will automatically
- do all of the database migration for you.</para>
-
+ a script named <filename>checksetup.pl</filename> included with
+ Bugzilla that will automatically do all of the database migration
+ for you.</para>
+
<para>The following sections explain how to upgrade from one
version of Bugzilla to another. Whether you are upgrading
- from one bug-fix version to another (such as 3.0.1 to 3.0.2)
- or from one major version to another (such as from 3.0 to 3.2),
+ from one bug-fix version to another (such as 4.2 to 4.2.1)
+ or from one major version to another (such as from 4.0 to 4.2),
the instructions are always the same.</para>
<note>
<para>
Any examples in the following sections are written as though the
- user were updating to version 2.22.1, but the procedures are the
+ user were updating to version 4.2.1, but the procedures are the
same no matter what version you're updating to. Also, in the
examples, the user's Bugzilla installation is found at
<filename>/var/www/html/bugzilla</filename>. If that is not the
@@ -2202,10 +2132,10 @@
<variablelist>
<varlistentry>
- <term>CVS (<xref linkend="upgrade-cvs"/>)</term>
+ <term>Bzr (<xref linkend="upgrade-bzr"/>)</term>
<listitem>
<para>
- If have <command>cvs</command> installed on your machine
+ If you have <command>bzr</command> installed on your machine
and you have Internet access, this is the easiest way to
upgrade, particularly if you have made modifications
to the code or templates of Bugzilla.
@@ -2230,12 +2160,12 @@
<para>
If you have made modifications to your Bugzilla, and
you don't have Internet access or you don't want to use
- cvs, then this is the best way to upgrade.
+ bzr, then this is the best way to upgrade.
</para>
-
+
<para>
- You can only do minor upgrades (such as 3.0 to 3.0.1 or
- 3.0.1 to 3.0.2) with patches.
+ You can only do minor upgrades (such as 4.2 to 4.2.1 or
+ 4.2.1 to 4.2.2) with patches.
</para>
</listitem>
</varlistentry>
@@ -2255,8 +2185,8 @@
<para>
The larger the jump you are trying to make, the more difficult it
is going to be to upgrade if you have made local customizations.
- Upgrading from 3.0 to 3.0.1 should be fairly painless even if
- you are heavily customized, but going from 2.18 to 3.0 is going
+ Upgrading from 4.2 to 4.2.1 should be fairly painless even if
+ you are heavily customized, but going from 2.18 to 4.2 is going
to mean a fair bit of work re-writing your local changes to use
the new files, logic, templates, etc. If you have done no local
changes at all, however, then upgrading should be approximately
@@ -2265,41 +2195,53 @@
</para>
</section>
- <section id="upgrade-cvs">
- <title>Upgrading using CVS</title>
+ <section id="upgrade-bzr">
+ <title>Upgrading using Bzr</title>
<para>
- This requires that you have cvs installed (most Unix machines do),
- and requires that you are able to access cvs-mirror.mozilla.org
- on port 2401, which may not be an option if you are behind a
- highly restrictive firewall or don't have Internet access.
+ This requires that you have bzr installed (most Unix machines do),
+ and requires that you are able to access
+ <ulink url="http://bzr.mozilla.org/bugzilla/">bzr.mozilla.org</ulink>,
+ which may not be an option if you don't have Internet access.
</para>
<para>
The following shows the sequence of commands needed to update a
- Bugzilla installation via CVS, and a typical series of results.
+ Bugzilla installation via Bzr, and a typical series of results.
+ These commands assume that you already have Bugzilla installed
+ using Bzr.
</para>
+ <warning>
+ <para>
+ If your installation is still using CVS, you must first convert
+ it to Bzr. A very detailed step by step documentation can be
+ found on <ulink url="https://wiki.mozilla.org/Bugzilla:Moving_From_CVS_To_Bazaar">wiki.mozilla.org</ulink>.
+ </para>
+ </warning>
+
<programlisting>
bash$ <command>cd /var/www/html/bugzilla</command>
-bash$ <command>cvs login</command>
-Logging in to :pserver:anonymous@cvs-mirror.mozilla.org:2401/cvsroot
-CVS password: <emphasis>('anonymous', or just leave it blank)</emphasis>
-bash$ <command>cvs -q update -r BUGZILLA-2_22_1 -dP</command>
-P checksetup.pl
-P collectstats.pl
-P docs/rel_notes.txt
-P template/en/default/list/quips.html.tmpl
-<emphasis>(etc.)</emphasis>
+bash$ <command>bzr switch 4.2</command> (only run this command when not yet running 4.2)
+bash$ <command>bzr up -r tag:bugzilla-4.2.1</command>
++N extensions/MoreBugUrl/
++N extensions/MoreBugUrl/Config.pm
++N extensions/MoreBugUrl/Extension.pm
+...
+ M Bugzilla/Attachment.pm
+ M Bugzilla/Attachment/PatchReader.pm
+ M Bugzilla/Bug.pm
+...
+All changes applied successfully.
</programlisting>
<caution>
<para>
- If a line in the output from <command>cvs update</command> begins
- with a <computeroutput>C</computeroutput>, then that represents a
- file with local changes that CVS was unable to properly merge. You
- need to resolve these conflicts manually before Bugzilla (or at
- least the portion using that file) will be usable.
+ If a line in the output from <command>bzr up</command> mentions
+ a conflict, then that represents a file with local changes that
+ Bzr was unable to properly merge. You need to resolve these
+ conflicts manually before Bugzilla (or at least the portion using
+ that file) will be usable.
</para>
</caution>
</section>
@@ -2308,7 +2250,7 @@
<title>Upgrading using the tarball</title>
<para>
- If you are unable (or unwilling) to use CVS, another option that's
+ If you are unable (or unwilling) to use Bzr, another option that's
always available is to obtain the latest tarball from the <ulink
url="http://www.bugzilla.org/download/">Download Page</ulink> and
create a new Bugzilla installation from that.
@@ -2325,18 +2267,18 @@
<programlisting>
bash$ <command>cd /var/www/html</command>
-bash$ <command>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22.1.tar.gz</command>
+bash$ <command>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-4.2.1.tar.gz</command>
<emphasis>(Output omitted)</emphasis>
-bash$ <command>tar xzvf bugzilla-2.22.1.tar.gz</command>
-bugzilla-2.22.1/
-bugzilla-2.22.1/.cvsignore
+bash$ <command>tar xzvf bugzilla-4.2.1.tar.gz</command>
+bugzilla-4.2.1/
+bugzilla-4.2.1/colchange.cgi
<emphasis>(Output truncated)</emphasis>
-bash$ <command>cd bugzilla-2.22.1</command>
+bash$ <command>cd bugzilla-4.2.1</command>
bash$ <command>cp ../bugzilla/localconfig* .</command>
bash$ <command>cp -r ../bugzilla/data .</command>
bash$ <command>cd ..</command>
bash$ <command>mv bugzilla bugzilla.old</command>
-bash$ <command>mv bugzilla-2.22.1 bugzilla</command>
+bash$ <command>mv bugzilla-4.2.1 bugzilla</command>
</programlisting>
<warning>
@@ -2347,6 +2289,15 @@
</para>
</warning>
+ <caution>
+ <para>
+ If you have some extensions installed, you will have to copy them
+ to the new bugzilla directory too. Extensions are located in
+ <filename>bugzilla/extensions/</filename>. Only copy those you
+ installed, not those managed by the Bugzilla team.
+ </para>
+ </caution>
+
<para>
This upgrade method will give you a clean install of Bugzilla.
That's fine if you don't have any local customizations that you
@@ -2362,15 +2313,15 @@
A patch is a collection of all the bug fixes that have been made
since the last bug-fix release.
</para>
-
+
<para>
If you are doing a bug-fix upgrade—that is, one where only the
- last number of the revision changes, such as from 2.22 to
- 2.22.1—then you have the option of obtaining and applying a
+ last number of the revision changes, such as from 4.2 to
+ 4.2.1—then you have the option of obtaining and applying a
patch file from the <ulink
url="http://www.bugzilla.org/download/">Download Page</ulink>.
</para>
-
+
<para>
As above, this example starts with obtaining the file via the
command line. If you have already downloaded it, you can omit the
@@ -2379,21 +2330,21 @@
<programlisting>
bash$ <command>cd /var/www/html/bugzilla</command>
-bash$ <command>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22-to-2.22.1.diff.gz</command>
+bash$ <command>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-4.2-to-4.2.1.diff.gz</command>
<emphasis>(Output omitted)</emphasis>
-bash$ <command>gunzip bugzilla-2.22-to-2.22.1.diff.gz</command>
-bash$ <command>patch -p1 < bugzilla-2.22-to-2.22.1.diff</command>
-patching file checksetup.pl
-patching file collectstats.pl
+bash$ <command>gunzip bugzilla-4.2-to-4.2.1.diff.gz</command>
+bash$ <command>patch -p1 < bugzilla-4.2-to-4.2.1.diff</command>
+patching file Bugzilla/Constants.pm
+patching file enter_bug.cgi
<emphasis>(etc.)</emphasis>
</programlisting>
<warning>
<para>
Be aware that upgrading from a patch file does not change the
- entries in your <filename class="directory">CVS</filename> directory.
- This could make it more difficult to upgrade using CVS
- (<xref linkend="upgrade-cvs"/>) in the future.
+ entries in your <filename class="directory">.bzr</filename> directory.
+ This could make it more difficult to upgrade using Bzr
+ (<xref linkend="upgrade-bzr"/>) in the future.
</para>
</warning>
@@ -2453,7 +2404,7 @@
<caution>
<para>
- If this is a major upgrade (say, 2.22 to 3.0 or similar),
+ If this is a major upgrade (say, 3.6 to 4.2 or similar),
running <command>checksetup.pl</command> on a large
installation (75,000 or more bugs) can take a long time,
possibly several hours.
diff --git a/Websites/bugs.webkit.org/docs/en/xml/integration.xml b/Websites/bugs.webkit.org/docs/en/xml/integration.xml
deleted file mode 100644
index 485a03d..0000000
--- a/Websites/bugs.webkit.org/docs/en/xml/integration.xml
+++ /dev/null
@@ -1,120 +0,0 @@
-<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook V4.1//EN" > -->
-<!-- Keep these tools listings in alphabetical order please. -MPB -->
-<section id="integration">
- <title>Integrating Bugzilla with Third-Party Tools</title>
-
- <section id="bonsai"
- xreflabel="Bonsai, the Mozilla automated CVS management system">
- <title>Bonsai</title>
-
- <para>Bonsai is a web-based tool for managing
- <xref linkend="cvs" />
-
- . Using Bonsai, administrators can control open/closed status of trees,
- query a fast relational database back-end for change, branch, and comment
- information, and view changes made since the last time the tree was
- closed. Bonsai
- also integrates with
- <xref linkend="tinderbox" />.
- </para>
- </section>
-
- <section id="cvs" xreflabel="CVS, the Concurrent Versioning System">
- <title>CVS</title>
-
- <para>CVS integration is best accomplished, at this point, using the
- Bugzilla Email Gateway.</para>
-
- <para>Follow the instructions in this Guide for enabling Bugzilla e-mail
- integration. Ensure that your check-in script sends an email to your
- Bugzilla e-mail gateway with the subject of
- <quote>[Bug XXXX]</quote>,
- and you can have CVS check-in comments append to your Bugzilla bug. If
- you want to have the bug be closed automatically, you'll have to modify
- the <filename>contrib/bugzilla_email_append.pl</filename> script.
- </para>
-
- <para>There is also a CVSZilla project, based upon somewhat dated
- Bugzilla code, to integrate CVS and Bugzilla through CVS' ability to
- email. Check it out at: <ulink url="http://www.cvszilla.org/"/>.
- </para>
-
- <para>Another system capable of CVS integration with Bugzilla is
- Scmbug. This system provides generic integration of Source code
- Configuration Management with Bugtracking. Check it out at: <ulink
- url="http://freshmeat.net/projects/scmbug/"/>.
- </para>
-
- </section>
-
- <section id="scm"
- xreflabel="Perforce SCM (Fast Software Configuration Management System, a powerful commercial alternative to CVS">
-
- <title>Perforce SCM</title>
-
- <para>You can find the project page for Bugzilla and Teamtrack Perforce
- integration (p4dti) at:
- <ulink url="http://www.ravenbrook.com/project/p4dti/"/>
-
- .
- <quote>p4dti</quote>
-
- is now an officially supported product from Perforce, and you can find
- the "Perforce Public Depot" p4dti page at
- <ulink url="http://public.perforce.com/public/perforce/p4dti/index.html"/>
-
- .</para>
-
- <para>Integration of Perforce with Bugzilla, once patches are applied, is
- seamless. Perforce replication information will appear below the comments
- of each bug. Be certain you have a matching set of patches for the
- Bugzilla version you are installing. p4dti is designed to support
- multiple defect trackers, and maintains its own documentation for it.
- Please consult the pages linked above for further information.</para>
- </section>
-
- <section id="svn"
- xreflabel="Subversion, a compelling replacement for CVS">
- <title>Subversion</title>
- <para>Subversion is a free/open-source version control system,
- designed to overcome various limitations of CVS. Integration of
- Subversion with Bugzilla is possible using Scmbug, a system
- providing generic integration of Source Code Configuration
- Management with Bugtracking. Scmbug is available at <ulink
- url="http://freshmeat.net/projects/scmbug/"/>.</para>
- </section>
-
- <section id="tinderbox"
- xreflabel="Tinderbox, the Mozilla automated build management system">
- <title>Tinderbox/Tinderbox2</title>
-
- <para>Tinderbox is a continuous-build system which can integrate with
- Bugzilla - see
- <ulink url="http://www.mozilla.org/projects/tinderbox"/> for details
- of Tinderbox, and
- <ulink url="http://tinderbox.mozilla.org/showbuilds.cgi"/> to see it
- in action.</para>
- </section>
-</section>
-
-<!-- Keep this comment at the end of the file
-Local variables:
-mode: sgml
-sgml-always-quote-attributes:t
-sgml-auto-insert-required-elements:t
-sgml-balanced-tag-edit:t
-sgml-exposed-tags:nil
-sgml-general-insert-case:lower
-sgml-indent-data:t
-sgml-indent-step:2
-sgml-local-catalogs:nil
-sgml-local-ecat-files:nil
-sgml-minimize-attributes:nil
-sgml-namecase-general:t
-sgml-omittag:t
-sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
-sgml-shorttag:t
-sgml-tag-region-if-active:t
-End:
--->
-
diff --git a/Websites/bugs.webkit.org/docs/en/xml/introduction.xml b/Websites/bugs.webkit.org/docs/en/xml/introduction.xml
deleted file mode 100644
index 3390755..0000000
--- a/Websites/bugs.webkit.org/docs/en/xml/introduction.xml
+++ /dev/null
@@ -1,149 +0,0 @@
-<chapter id="introduction">
- <title>Introduction</title>
-
- <section id="whatis">
- <title>What is Bugzilla?</title>
-
- <para>
- Bugzilla is a bug- or issue-tracking system. Bug-tracking
- systems allow individual or groups of developers effectively to keep track
- of outstanding problems with their product.
- Bugzilla was originally
- written by Terry Weissman in a programming language called TCL, to
- replace a rudimentary bug-tracking database used internally by Netscape
- Communications. Terry later ported Bugzilla to Perl from TCL, and in Perl
- it remains to this day. Most commercial defect-tracking software vendors
- at the time charged enormous licensing fees, and Bugzilla quickly became
- a favorite of the open-source crowd (with its genesis in the open-source
- browser project, Mozilla). It is now the de-facto standard
- defect-tracking system against which all others are measured.
- </para>
-
- <para>Bugzilla boasts many advanced features. These include:
- <itemizedlist>
- <listitem>
- <para>Powerful searching</para>
- </listitem>
-
- <listitem>
- <para>User-configurable email notifications of bug changes</para>
- </listitem>
-
- <listitem>
- <para>Full change history</para>
- </listitem>
-
- <listitem>
- <para>Inter-bug dependency tracking and graphing</para>
- </listitem>
-
- <listitem>
- <para>Excellent attachment management</para>
- </listitem>
-
- <listitem>
- <para>Integrated, product-based, granular security schema</para>
- </listitem>
-
- <listitem>
- <para>Fully security-audited, and runs under Perl's taint mode</para>
- </listitem>
-
- <listitem>
- <para>A robust, stable RDBMS back-end</para>
- </listitem>
-
- <listitem>
- <para>Web, XML, email and console interfaces</para>
- </listitem>
-
- <listitem>
- <para>Completely customisable and/or localisable web user
- interface</para>
- </listitem>
-
- <listitem>
- <para>Extensive configurability</para>
- </listitem>
-
- <listitem>
- <para>Smooth upgrade pathway between versions</para>
- </listitem>
- </itemizedlist>
- </para>
- </section>
-
- <section id="why">
- <title>Why Should We Use Bugzilla?</title>
-
- <para>For many years, defect-tracking software has remained principally
- the domain of large software development houses. Even then, most shops
- never bothered with bug-tracking software, and instead simply relied on
- shared lists and email to monitor the status of defects. This procedure
- is error-prone and tends to cause those bugs judged least significant by
- developers to be dropped or ignored.</para>
-
- <para>These days, many companies are finding that integrated
- defect-tracking systems reduce downtime, increase productivity, and raise
- customer satisfaction with their systems. Along with full disclosure, an
- open bug-tracker allows manufacturers to keep in touch with their clients
- and resellers, to communicate about problems effectively throughout the
- data management chain. Many corporations have also discovered that
- defect-tracking helps reduce costs by providing IT support
- accountability, telephone support knowledge bases, and a common,
- well-understood system for accounting for unusual system or software
- issues.</para>
-
- <para>But why should
- <emphasis>you</emphasis>
-
- use Bugzilla?</para>
-
- <para>Bugzilla is very adaptable to various situations. Known uses
- currently include IT support queues, Systems Administration deployment
- management, chip design and development problem tracking (both
- pre-and-post fabrication), and software and hardware bug tracking for
- luminaries such as Redhat, NASA, Linux-Mandrake, and VA Systems.
- Combined with systems such as
- <ulink url="http://www.cvshome.org">CVS</ulink>,
- <ulink url="http://www.mozilla.org/bonsai.html">Bonsai</ulink>, or
- <ulink url="http://www.perforce.com">Perforce SCM</ulink>, Bugzilla
- provides a powerful, easy-to-use solution to configuration management and
- replication problems.</para>
-
- <para>Bugzilla can dramatically increase the productivity and
- accountability of individual employees by providing a documented workflow
- and positive feedback for good performance. How many times do you wake up
- in the morning, remembering that you were supposed to do
- <emphasis>something</emphasis>
- today, but you just can't quite remember? Put it in Bugzilla, and you
- have a record of it from which you can extrapolate milestones, predict
- product versions for integration, and follow the discussion trail
- that led to critical decisions.</para>
-
- <para>Ultimately, Bugzilla puts the power in your hands to improve your
- value to your employer or business while providing a usable framework for
- your natural attention to detail and knowledge store to flourish.</para>
- </section>
-</chapter>
-
-<!-- Keep this comment at the end of the file
-Local variables:
-mode: sgml
-sgml-always-quote-attributes:t
-sgml-auto-insert-required-elements:t
-sgml-balanced-tag-edit:t
-sgml-exposed-tags:nil
-sgml-general-insert-case:lower
-sgml-indent-data:t
-sgml-indent-step:2
-sgml-local-catalogs:nil
-sgml-local-ecat-files:nil
-sgml-minimize-attributes:nil
-sgml-namecase-general:t
-sgml-omittag:t
-sgml-parent-document:("Bugzilla-Guide.sgml" "book" "chapter")
-sgml-shorttag:t
-sgml-tag-region-if-active:t
-End:
--->
diff --git a/Websites/bugs.webkit.org/docs/en/xml/modules.xml b/Websites/bugs.webkit.org/docs/en/xml/modules.xml
index 3d4f6e5..933c9de 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/modules.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/modules.xml
@@ -98,14 +98,6 @@
</para>
<para>
- File::Spec:
- <literallayout>
- CPAN Download Page: <ulink url="http://search.cpan.org/dist/File-Spec/"/>
- Documentation: <ulink url="http://perldoc.perl.org/File/Spec.html"/>
- </literallayout>
- </para>
-
- <para>
Template-Toolkit:
<literallayout>
CPAN Download Page: <ulink url="http://search.cpan.org/dist/Template-Toolkit/"/>
@@ -143,7 +135,7 @@
<title>Optional Modules</title>
<para>
- Chart::Base:
+ Chart::Lines:
<literallayout>
CPAN Download Page: <ulink url="http://search.cpan.org/dist/Chart/"/>
Documentation: <ulink url="http://search.cpan.org/dist/Chart/Chart.pod"/>
@@ -181,13 +173,5 @@
Documentation: <ulink url="http://www.johnkeiser.com/mozilla/Patch_Viewer.html"/>
</literallayout>
</para>
-
- <para>
- Image::Magick:
- <literallayout>
- CPAN Download Page: <ulink url="http://search.cpan.org/dist/PerlMagick/"/>
- Documentation: <ulink url="http://www.imagemagick.org/script/resources.php"/>
- </literallayout>
- </para>
</section>
</appendix>
diff --git a/Websites/bugs.webkit.org/docs/en/xml/patches.xml b/Websites/bugs.webkit.org/docs/en/xml/patches.xml
index b1d9281..12efb0c 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/patches.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/patches.xml
@@ -21,7 +21,7 @@
<warning>
<para>
- These files pre-date the templatisation work done as part of the
+ These files pre-date the templatization work done as part of the
2.16 release, and have not been updated.
</para>
</warning>
diff --git a/Websites/bugs.webkit.org/docs/en/xml/requiredsoftware.xml b/Websites/bugs.webkit.org/docs/en/xml/requiredsoftware.xml
deleted file mode 100644
index 564e585..0000000
--- a/Websites/bugs.webkit.org/docs/en/xml/requiredsoftware.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-<!-- <!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
-
-<appendix id="downloadlinks">
- <title>Software Download Links</title>
- <para>
- All of these sites are current as of April, 2001. Hopefully
- they'll stay current for a while.
- </para>
- <para>
- Apache Web Server: <ulink url="http://www.apache.org/">http://www.apache.org</ulink>
- Optional web server for Bugzilla, but recommended because of broad user base and support.
- </para>
- <para>
- Bugzilla: <ulink url="http://www.mozilla.org/projects/bugzilla/">
- http://www.mozilla.org/projects/bugzilla/</ulink>
- </para>
- <para>
- MySQL: <ulink url="http://www.mysql.com/">http://www.mysql.com/</ulink>
- </para>
- <para>
- Perl: <ulink url="http://www.perl.org">http://www.perl.org/</ulink>
- </para>
- <para>
- CPAN: <ulink url="http://www.cpan.org/">http://www.cpan.org/</ulink>
- </para>
- <para>
- DBI Perl module:
- <ulink url="http://www.cpan.org/modules/by-module/DBI/">
- http://www.cpan.org/modules/by-module/DBI/</ulink>
- </para>
- <para>
- Data::Dumper module:
- <ulink url="http://www.cpan.org/modules/by-module/Data/">
- http://www.cpan.org/modules/by-module/Data/</ulink>
- </para>
- <para>
- MySQL related Perl modules:
- <ulink url="http://www.cpan.org/modules/by-module/Mysql/">
- http://www.cpan.org/modules/by-module/Mysql/</ulink>
- </para>
- <para>
- TimeDate Perl module collection:
- <ulink url="http://www.cpan.org/modules/by-module/Date/">
- http://www.cpan.org/modules/by-module/Date/</ulink>
- </para>
- <para>
- GD Perl module:
- <ulink url="http://www.cpan.org/modules/by-module/GD/">
- http://www.cpan.org/modules/by-module/GD/</ulink>
- Alternately, you should be able to find the latest version of
- GD at <ulink url="http://www.boutell.com/gd/">http://www.boutell.com/gd/</ulink>
- </para>
- <para>
- Chart::Base module:
- <ulink url="http://www.cpan.org/modules/by-module/Chart/">
- http://www.cpan.org/modules/by-module/Chart/</ulink>
- </para>
- <para>
- LinuxDoc Software:
- <ulink url="http://www.linuxdoc.org/">http://www.linuxdoc.org/</ulink>
- (for documentation maintenance)
- </para>
-
-</appendix>
-
-
-<!-- Keep this comment at the end of the file
-Local variables:
-mode: sgml
-sgml-always-quote-attributes:t
-sgml-auto-insert-required-elements:t
-sgml-balanced-tag-edit:t
-sgml-exposed-tags:nil
-sgml-general-insert-case:lower
-sgml-indent-data:t
-sgml-indent-step:2
-sgml-local-catalogs:nil
-sgml-local-ecat-files:nil
-sgml-minimize-attributes:nil
-sgml-namecase-general:t
-sgml-omittag:t
-sgml-parent-document:("Bugzilla-Guide.sgml" "book" "chapter")
-sgml-shorttag:t
-sgml-tag-region-if-active:t
-End:
--->
diff --git a/Websites/bugs.webkit.org/docs/en/xml/security.xml b/Websites/bugs.webkit.org/docs/en/xml/security.xml
index c9a3f2a..b234dd9 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/security.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/security.xml
@@ -1,5 +1,4 @@
<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"> -->
-<!-- $Id$ -->
<chapter id="security">
<title>Bugzilla Security</title>
@@ -80,96 +79,7 @@
</section>
</section>
-
-
-
- <section id="security-mysql">
- <title>MySQL</title>
-
- <section id="security-mysql-account">
- <title>The MySQL System Account</title>
-
- <para>As mentioned in <xref linkend="security-os-accounts"/>, the MySQL
- daemon should run as a non-privileged, unique user. Be sure to consult
- the MySQL documentation or the documentation that came with your system
- for instructions.
- </para>
- </section>
-
- <section id="security-mysql-root">
- <title>The MySQL <quote>root</quote> and <quote>anonymous</quote> Users</title>
-
- <para>By default, MySQL comes with a <quote>root</quote> user with a
- blank password and an <quote>anonymous</quote> user, also with a blank
- password. In order to protect your data, the <quote>root</quote> user
- should be given a password and the anonymous user should be disabled.
- </para>
-
- <example id="security-mysql-account-root">
- <title>Assigning the MySQL <quote>root</quote> User a Password</title>
-
- <screen>
-<prompt>bash$</prompt> mysql mysql
-<prompt>mysql></prompt> UPDATE user SET password = password('<replaceable>new_password</replaceable>') WHERE user = 'root';
-<prompt>mysql></prompt> FLUSH PRIVILEGES;
- </screen>
- </example>
-
- <example id="security-mysql-account-anonymous">
- <title>Disabling the MySQL <quote>anonymous</quote> User</title>
- <screen>
-<prompt>bash$</prompt> mysql -u root -p mysql <co id="security-mysql-account-anonymous-mysql"/>
-<prompt>Enter Password:</prompt> <replaceable>new_password</replaceable>
-<prompt>mysql></prompt> DELETE FROM user WHERE user = '';
-<prompt>mysql></prompt> FLUSH PRIVILEGES;
- </screen>
- <calloutlist>
- <callout arearefs="security-mysql-account-anonymous-mysql">
- <para>This command assumes that you have already completed
- <xref linkend="security-mysql-account-root"/>.
- </para>
- </callout>
- </calloutlist>
- </example>
-
- </section>
-
- <section id="security-mysql-network">
- <title>Network Access</title>
-
- <para>If MySQL and your web server both run on the same machine and you
- have no other reason to access MySQL remotely, then you should disable
- the network access. This, along with the suggestion in
- <xref linkend="security-os-ports"/>, will help protect your system from
- any remote vulnerabilities in MySQL.
- </para>
-
- <example id="security-mysql-network-ex">
- <title>Disabling Networking in MySQL</title>
-
- <para>Simply enter the following in <filename>/etc/my.cnf</filename>:
- <screen>
-[mysqld]
-# Prevent network access to MySQL.
-skip-networking
- </screen>
- </para>
- </example>
-
- </section>
-
-<!-- For possible addition in the future: How to better control the bugs user
- <section id="security-mysql-bugs">
- <title>The bugs User</title>
-
- </section>
--->
-
- </section>
-
-
-
<section id="security-webserver">
<title>Web server</title>
diff --git a/Websites/bugs.webkit.org/docs/en/xml/troubleshooting.xml b/Websites/bugs.webkit.org/docs/en/xml/troubleshooting.xml
index b8d3855..fff90a9 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/troubleshooting.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/troubleshooting.xml
@@ -1,5 +1,4 @@
<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"> -->
-<!-- $Id$ -->
<appendix id="troubleshooting">
<title>Troubleshooting</title>
@@ -22,7 +21,7 @@
<para>If you have made it all the way through
<xref linkend="installation"/> (Installation) and
<xref linkend="configuration"/> (Configuration) but accessing the Bugzilla
- URL doesn't work, the first thing to do is to check your webserver error
+ URL doesn't work, the first thing to do is to check your web server error
log. For Apache, this is often located at
<filename>/etc/logs/httpd/error_log</filename>. The error messages
you see may be self-explanatory enough to enable you to diagnose and
@@ -32,7 +31,7 @@
<para>
Bugzilla can also log all user-based errors (and many code-based errors)
- that occur, without polluting the web server error log. To enable
+ that occur, without polluting the web server's error log. To enable
Bugzilla error logging, create a file that Bugzilla can write to, named
<filename>errorlog</filename>, in the Bugzilla <filename>data</filename>
directory. Errors will be logged as they occur, and will include the type
@@ -45,10 +44,10 @@
</section>
<section id="trbl-testserver">
- <title>The Apache webserver is not serving Bugzilla pages</title>
+ <title>The Apache web server is not serving Bugzilla pages</title>
<para>After you have run <command>checksetup.pl</command> twice,
run <command>testserver.pl http://yoursite.yourdomain/yoururl</command>
- to confirm that your webserver is configured properly for
+ to confirm that your web server is configured properly for
Bugzilla.
</para>
<programlisting>
@@ -75,9 +74,9 @@
</para>
</listitem>
<listitem>
- <para>The permissions on your library directories are set incorrectly.
- They must, at the very least, be readable by the webserver user or
- group. It is recommended that they be world readable.
+ <para>The permissions on your library directories are set incorrectly.
+ They must, at the very least, be readable by the web server user or
+ group. It is recommended that they be world readable.
</para>
</listitem>
</orderedlist>
@@ -139,55 +138,12 @@
</para>
</section>
- <section id="trouble-filetemp">
- <title>Your vendor has not defined Fcntl macro O_NOINHERIT</title>
-
- <para>This is caused by a bug in the version of
- <productname>File::Temp</productname> that is distributed with perl
- 5.6.0. Many minor variations of this error have been reported:
- </para>
-
- <programlisting>Your vendor has not defined Fcntl macro O_NOINHERIT, used
-at /usr/lib/perl5/site_perl/5.6.0/File/Temp.pm line 208.
-
-Your vendor has not defined Fcntl macro O_EXLOCK, used
-at /usr/lib/perl5/site_perl/5.6.0/File/Temp.pm line 210.
-
-Your vendor has not defined Fcntl macro O_TEMPORARY, used
-at /usr/lib/perl5/site_perl/5.6.0/File/Temp.pm line 233.</programlisting>
-
- <para>Numerous people have reported that upgrading to version 5.6.1
- or higher solved the problem for them. A less involved fix is to apply
- the following patch, which is also
- available as a <ulink url="../xml/filetemp.patch">patch file</ulink>.
- </para>
-
- <programlisting><![CDATA[--- File/Temp.pm.orig Thu Feb 6 16:26:00 2003
-+++ File/Temp.pm Thu Feb 6 16:26:23 2003
-@@ -205,6 +205,7 @@
- # eg CGI::Carp
- local $SIG{__DIE__} = sub {};
- local $SIG{__WARN__} = sub {};
-+ local *CORE::GLOBAL::die = sub {};
- $bit = &$func();
- 1;
- };
-@@ -226,6 +227,7 @@
- # eg CGI::Carp
- local $SIG{__DIE__} = sub {};
- local $SIG{__WARN__} = sub {};
-+ local *CORE::GLOBAL::die = sub {};
- $bit = &$func();
- 1;
- };]]></programlisting>
- </section>
-
<section id="trbl-relogin-everyone">
<title>Everybody is constantly being forced to relogin</title>
<para>The most-likely cause is that the <quote>cookiepath</quote> parameter
is not set correctly in the Bugzilla configuration. You can change this (if
- you're a Bugzilla administrator) from the editparams.cgi page via the web.
+ you're a Bugzilla administrator) from the editparams.cgi page via the web interface.
</para>
<para>The value of the cookiepath parameter should be the actual directory
@@ -256,35 +212,6 @@
</para>
</section>
- <section id="trbl-relogin-some">
- <title>Some users are constantly being forced to relogin</title>
-
- <para>First, make sure cookies are enabled in the user's browser.
- </para>
-
- <para>If that doesn't fix the problem, it may be that the user's ISP
- implements a rotating proxy server. This causes the user's effective IP
- address (the address which the Bugzilla server perceives him coming from)
- to change periodically. Since Bugzilla cookies are tied to a specific IP
- address, each time the effective address changes, the user will have to
- log in again.
- </para>
-
- <para>If you are using 2.18 (or later), there is a
- parameter called <quote>loginnetmask</quote>, which you can use to set
- the number of bits of the user's IP address to require to be matched when
- authenticating the cookies. If you set this to something less than 32,
- then the user will be given a checkbox for <quote>Restrict this login to
- my IP address</quote> on the login screen, which defaults to checked. If
- they leave the box checked, Bugzilla will behave the same as it did
- before, requiring an exact match on their IP address to remain logged in.
- If they uncheck the box, then only the left side of their IP address (up
- to the number of bits you specified in the parameter) has to match to
- remain logged in.
- </para>
-
- </section>
-
<section id="trbl-index">
<title><filename>index.cgi</filename> doesn't show up unless specified in the URL</title>
<para>
diff --git a/Websites/bugs.webkit.org/docs/en/xml/using.xml b/Websites/bugs.webkit.org/docs/en/xml/using.xml
index 101a9d1..3bf0558 100644
--- a/Websites/bugs.webkit.org/docs/en/xml/using.xml
+++ b/Websites/bugs.webkit.org/docs/en/xml/using.xml
@@ -332,9 +332,7 @@
<para>
<emphasis>Attachments:</emphasis>
You can attach files (e.g. testcases or patches) to bugs. If there
- are any attachments, they are listed in this section. Attachments are
- normally stored in the Bugzilla database, unless they are marked as
- Big Files, which are stored directly on disk.
+ are any attachments, they are listed in this section.
</para>
</listitem>
@@ -365,10 +363,12 @@
<title>Life Cycle of a Bug</title>
<para>
- The life cycle, also known as work flow, of a bug is currently hardcoded
- into Bugzilla. <xref linkend="lifecycle-image"/> contains a graphical
- representation of this life cycle. If you wish to customize this image for
- your site, the <ulink url="../images/bzLifecycle.xml">diagram file</ulink>
+ The life cycle of a bug, also known as workflow, is customizable to match
+ the needs of your organization, see <xref linkend="bug_status_workflow"/>.
+ <xref linkend="lifecycle-image"/> contains a graphical representation of
+ the default workflow using the default bug statuses. If you wish to
+ customize this image for your site, the
+ <ulink url="../images/bzLifecycle.xml">diagram file</ulink>
is available in <ulink url="http://www.gnome.org/projects/dia">Dia's</ulink>
native XML format.
</para>
@@ -659,16 +659,6 @@
</member>
</simplelist>
</para>
-
- <para>
- If you would like to access the bug list from another program
- it is often useful to have the list returned in something other
- than HTML. By adding the ctype=type parameter into the bug list URL
- you can specify several alternate formats. Besides the types described
- above, the following formats are also supported: ECMAScript, also known
- as JavaScript (ctype=js), and Resource Description Framework RDF/XML
- (ctype=rdf).
- </para>
</section>
<section id="individual-buglists">
@@ -862,28 +852,20 @@
</para>
<para>
- If you have a really large attachment, something that does not need to
- be recorded forever (as most attachments are), or something that is too
- big for your database, you can mark your attachment as a
- <quote>Big File</quote>, assuming the administrator of the installation
- has enabled this feature. Big Files are stored directly on disk instead
- of in the database. The maximum size of a <quote>Big File</quote> is
- normally larger than the maximum size of a regular attachment. Independently
- of the storage system used, an administrator can delete these attachments
- at any time. Nevertheless, if these files are stored in the database, the
- <quote>allow_attachment_deletion</quote> parameter (which is turned off
- by default) must be enabled in order to delete them.
- </para>
-
- <para>
- Also, if the administrator turned on the <quote>allow_attach_url</quote>
- parameter, you can enter the URL pointing to the attachment instead of
+ Also, you can enter the URL pointing to the attachment instead of
uploading the attachment itself. For example, this is useful if you want to
point to an external application, a website or a very large file. Note that
there is no guarantee that the source file will always be available, nor
that its content will remain unchanged.
</para>
+ <para>
+ Another way to attach data is to paste text directly in the text field,
+ and Bugzilla will convert it into an attachment. This is pretty useful
+ when you do copy and paste, and you don't want to put the text in a temporary
+ file first.
+ </para>
+
<section id="patchviewer">
<title>Patch Viewer</title>
@@ -1383,7 +1365,18 @@
Indicates user can configure whine reports for self.
</para>
</listitem>
- </varlistentry>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ bz_quip_moderators
+ </term>
+ <listitem>
+ <para>
+ Indicates user can moderate quips.
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term>
diff --git a/Websites/bugs.webkit.org/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm b/Websites/bugs.webkit.org/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
index ab298a0..eb37773 100644
--- a/Websites/bugs.webkit.org/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
+++ b/Websites/bugs.webkit.org/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
@@ -31,8 +31,9 @@
# Note that if you leave out a category here, it will not be indexed
# in the contents file, even though its HTML POD will still exist.
use constant FILE_TRANSLATION => {
- Files => ['importxml', 'contrib', 'checksetup', 'email_in', 'install-module',
- 'sanitycheck'],
+ Files => ['importxml', 'contrib', 'checksetup', 'email_in',
+ 'install-module', 'sanitycheck', 'jobqueue', 'migrate',
+ 'collectstats'],
Modules => ['bugzilla'],
Extensions => ['extensions'],
};
@@ -106,4 +107,14 @@
return $retval;
}
+# Exclude modules being in lib/.
+sub find_all_pods {
+ my($self, $dirs) = @_;
+ my $mod2path = $self->SUPER::find_all_pods($dirs);
+ foreach my $mod (keys %$mod2path) {
+ delete $mod2path->{$mod} if $mod =~ /^lib::/;
+ }
+ return $mod2path;
+}
+
1;
diff --git a/Websites/bugs.webkit.org/docs/makedocs.pl b/Websites/bugs.webkit.org/docs/makedocs.pl
index 853ea95..999548f 100755
--- a/Websites/bugs.webkit.org/docs/makedocs.pl
+++ b/Websites/bugs.webkit.org/docs/makedocs.pl
@@ -73,13 +73,6 @@
print ENTITIES '<!ENTITY min-' . $name . '-ver "'.$version.'">' . "\n";
}
-# CGI is a special case, because for Perl versions below 5.10, it has an
-# optional version *and* a required version.
-# We check @opt_modules first, then @modules, and pick the first we get.
-# We'll get the optional one then, if it is given, otherwise the required one.
-my ($cgi_opt) = grep($_->{module} eq 'CGI', @$opt_modules, @$modules);
-print ENTITIES '<!ENTITY min-mp-cgi-ver "' . $cgi_opt->{version} . '">' . "\n";
-
print ENTITIES "\n <!-- Database Versions --> \n";
my $db_modules = DB_MODULE;
@@ -211,12 +204,12 @@
chdir 'html';
MakeDocs('separate HTML', "jade -t sgml -i html -d $LDP_HOME/ldp.dsl\#html " .
- "$JADE_PUB/xml.dcl ../xml/Bugzilla-Guide.xml");
+ "$JADE_PUB/xml.dcl ../xml/Bugzilla-Guide.xml");
MakeDocs('big HTML', "jade -V nochunks -t sgml -i html -d " .
"$LDP_HOME/ldp.dsl\#html $JADE_PUB/xml.dcl " .
- "../xml/Bugzilla-Guide.xml > Bugzilla-Guide.html");
+ "../xml/Bugzilla-Guide.xml > Bugzilla-Guide.html");
MakeDocs('big text', "lynx -dump -justify=off -nolist Bugzilla-Guide.html " .
- "> ../txt/Bugzilla-Guide.txt");
+ "> ../txt/Bugzilla-Guide.txt");
if (! grep($_ eq "--with-pdf", @ARGV)) {
next;
diff --git a/Websites/bugs.webkit.org/duplicates.cgi b/Websites/bugs.webkit.org/duplicates.cgi
index 590bb57..1571394 100755
--- a/Websites/bugs.webkit.org/duplicates.cgi
+++ b/Websites/bugs.webkit.org/duplicates.cgi
@@ -18,15 +18,11 @@
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
-# Contributor(s): Gervase Markham <gerv@gerv.net>
-#
-# Generates mostfreq list from data collected by collectstats.pl.
-
+# Contributor(s):
+# Gervase Markham <gerv@gerv.net>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
-
-use AnyDBM_File;
-
use lib qw(. lib);
use Bugzilla;
@@ -34,99 +30,131 @@
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Search;
+use Bugzilla::Field;
use Bugzilla::Product;
-my $cgi = Bugzilla->cgi;
-my $template = Bugzilla->template;
-my $vars = {};
+use constant DEFAULTS => {
+ # We want to show bugs which:
+ # a) Aren't CLOSED; and
+ # b) i) Aren't VERIFIED; OR
+ # ii) Were resolved INVALID/WONTFIX
+ #
+ # The rationale behind this is that people will eventually stop
+ # reporting fixed bugs when they get newer versions of the software,
+ # but if the bug is determined to be erroneous, people will still
+ # keep reporting it, so we do need to show it here.
+ fully_exclude_status => ['CLOSED'],
+ partly_exclude_status => ['VERIFIED'],
+ except_resolution => ['INVALID', 'WONTFIX'],
+ changedsince => 7,
+ maxrows => 20,
+ sortby => 'count',
+};
-# collectstats.pl uses duplicates.cgi to generate the RDF duplicates stats.
-# However, this conflicts with requirelogin if it's enabled; so we make
-# logging-in optional if we are running from the command line.
-if ($::ENV{'GATEWAY_INTERFACE'} eq "cmdline") {
- Bugzilla->login(LOGIN_OPTIONAL);
-}
-else {
- Bugzilla->login();
-}
+###############
+# Subroutines #
+###############
-my $dbh = Bugzilla->switch_to_shadow_db();
+# $counts is a count of exactly how many direct duplicates there are for
+# each bug we're considering. $dups is a map of duplicates, from one
+# bug_id to another. We go through the duplicates map ($dups) and if one bug
+# in $count is a duplicate of another bug in $count, we add their counts
+# together under the target bug.
+sub add_indirect_dups {
+ my ($counts, $dups) = @_;
-my %dbmcount;
-my %count;
-my %before;
-
-# Get params from URL
-sub formvalue {
- my ($name, $default) = (@_);
- return Bugzilla->cgi->param($name) || $default || "";
-}
-
-my $sortby = formvalue("sortby");
-my $changedsince = formvalue("changedsince", 7);
-my $maxrows = formvalue("maxrows", 100);
-my $openonly = formvalue("openonly");
-my $reverse = formvalue("reverse") ? 1 : 0;
-my @query_products = $cgi->param('product');
-my $sortvisible = formvalue("sortvisible");
-my @buglist = (split(/[:,]/, formvalue("bug_id")));
-
-# Make sure all products are valid.
-foreach my $p (@query_products) {
- Bugzilla::Product::check_product($p);
-}
-
-# Small backwards-compatibility hack, dated 2002-04-10.
-$sortby = "count" if $sortby eq "dup_count";
-
-# Open today's record of dupes
-my $today = days_ago(0);
-my $yesterday = days_ago(1);
-
-# We don't know the exact file name, because the extension depends on the
-# underlying dbm library, which could be anything. We can't glob, because
-# perl < 5.6 considers if (<*>) { ... } to be tainted
-# Instead, just check the return value for today's data and yesterday's,
-# and ignore file not found errors
-
-use Errno;
-use Fcntl;
-
-my $datadir = bz_locations()->{'datadir'};
-
-if (!tie(%dbmcount, 'AnyDBM_File', "$datadir/duplicates/dupes$today",
- O_RDONLY, 0644)) {
- if ($!{ENOENT}) {
- if (!tie(%dbmcount, 'AnyDBM_File', "$datadir/duplicates/dupes$yesterday",
- O_RDONLY, 0644)) {
- my $vars = { today => $today };
- if ($!{ENOENT}) {
- ThrowUserError("no_dupe_stats", $vars);
- } else {
- $vars->{'error_msg'} = $!;
- ThrowUserError("no_dupe_stats_error_yesterday", $vars);
- }
- }
- } else {
- ThrowUserError("no_dupe_stats_error_today",
- { error_msg => $! });
+ foreach my $add_from (keys %$dups) {
+ my $add_to = walk_dup_chain($dups, $add_from);
+ my $add_amount = delete $counts->{$add_from} || 0;
+ $counts->{$add_to} += $add_amount;
}
}
-# Copy hash (so we don't mess up the on-disk file when we remove entries)
-%count = %dbmcount;
-
-# Remove all those dupes under the threshold parameter.
-# We do this, before the sorting, for performance reasons.
-my $threshold = Bugzilla->params->{"mostfreqthreshold"};
-
-while (my ($key, $value) = each %count) {
- delete $count{$key} if ($value < $threshold);
-
- # If there's a buglist, restrict the bugs to that list.
- delete $count{$key} if $sortvisible && (lsearch(\@buglist, $key) == -1);
+sub walk_dup_chain {
+ my ($dups, $from_id) = @_;
+ my $to_id = $dups->{$from_id};
+ my %seen;
+ while (my $bug_id = $dups->{$to_id}) {
+ if ($seen{$bug_id}) {
+ warn "Duplicate loop: $to_id -> $bug_id\n";
+ last;
+ }
+ $seen{$bug_id} = 1;
+ $to_id = $bug_id;
+ }
+ # Optimize for future calls to add_indirect_dups.
+ $dups->{$from_id} = $to_id;
+ return $to_id;
}
+# Get params from URL
+sub formvalue {
+ my ($name) = (@_);
+ my $cgi = Bugzilla->cgi;
+ if (defined $cgi->param($name)) {
+ return $cgi->param($name);
+ }
+ elsif (exists DEFAULTS->{$name}) {
+ return ref DEFAULTS->{$name} ? @{ DEFAULTS->{$name} }
+ : DEFAULTS->{$name};
+ }
+ return undef;
+}
+
+sub sort_duplicates {
+ my ($a, $b, $sort_by) = @_;
+ if ($sort_by eq 'count' or $sort_by eq 'delta') {
+ return $a->{$sort_by} <=> $b->{$sort_by};
+ }
+ if ($sort_by =~ /^(bug_)?id$/) {
+ return $a->{'bug'}->$sort_by <=> $b->{'bug'}->$sort_by;
+ }
+ return $a->{'bug'}->$sort_by cmp $b->{'bug'}->$sort_by;
+
+}
+
+###############
+# Main Script #
+###############
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $user = Bugzilla->login();
+
+my $dbh = Bugzilla->switch_to_shadow_db();
+
+my $changedsince = formvalue("changedsince");
+my $maxrows = formvalue("maxrows");
+my $openonly = formvalue("openonly");
+my $sortby = formvalue("sortby");
+if (!grep(lc($_) eq lc($sortby), qw(count delta id))) {
+ Bugzilla::Field->check($sortby);
+}
+my $reverse = formvalue("reverse");
+# Reverse count and delta by default.
+if (!defined $reverse) {
+ if ($sortby eq 'count' or $sortby eq 'delta') {
+ $reverse = 1;
+ }
+ else {
+ $reverse = 0;
+ }
+}
+my @query_products = $cgi->param('product');
+my $sortvisible = formvalue("sortvisible");
+my @bugs;
+if ($sortvisible) {
+ my @limit_to_ids = (split(/[:,]/, formvalue("bug_id") || ''));
+ @bugs = @{ Bugzilla::Bug->new_from_list(\@limit_to_ids) };
+ @bugs = @{ $user->visible_bugs(\@bugs) };
+}
+
+# Make sure all products are valid.
+@query_products = map { Bugzilla::Product->check($_) } @query_products;
+
+# Small backwards-compatibility hack, dated 2002-04-10.
+$sortby = "count" if $sortby eq "dup_count";
+
my $origmaxrows = $maxrows;
detaint_natural($maxrows)
|| ThrowUserError("invalid_maxrows", { maxrows => $origmaxrows});
@@ -136,137 +164,100 @@
|| ThrowUserError("invalid_changedsince",
{ changedsince => $origchangedsince });
-# Try and open the database from "changedsince" days ago
-my $dobefore = 0;
-my %delta;
-my $whenever = days_ago($changedsince);
+my %total_dups = @{$dbh->selectcol_arrayref(
+ "SELECT dupe_of, COUNT(dupe)
+ FROM duplicates
+ GROUP BY dupe_of", {Columns => [1,2]})};
-if (!tie(%before, 'AnyDBM_File', "$datadir/duplicates/dupes$whenever",
- O_RDONLY, 0644)) {
- # Ignore file not found errors
- if (!$!{ENOENT}) {
- ThrowUserError("no_dupe_stats_error_whenever",
- { error_msg => $!,
- changedsince => $changedsince,
- whenever => $whenever,
- });
+my %dupe_relation = @{$dbh->selectcol_arrayref(
+ "SELECT dupe, dupe_of FROM duplicates
+ WHERE dupe IN (SELECT dupe_of FROM duplicates)",
+ {Columns => [1,2]})};
+add_indirect_dups(\%total_dups, \%dupe_relation);
+
+my $reso_field_id = get_field_id('resolution');
+my %since_dups = @{$dbh->selectcol_arrayref(
+ "SELECT dupe_of, COUNT(dupe)
+ FROM duplicates INNER JOIN bugs_activity
+ ON bugs_activity.bug_id = duplicates.dupe
+ WHERE added = 'DUPLICATE' AND fieldid = ?
+ AND bug_when >= "
+ . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '?', 'DAY') .
+ " GROUP BY dupe_of", {Columns=>[1,2]},
+ $reso_field_id, $changedsince)};
+add_indirect_dups(\%since_dups, \%dupe_relation);
+
+# Enforce the mostfreqthreshold parameter and the "bug_id" cgi param.
+my $mostfreq = Bugzilla->params->{'mostfreqthreshold'};
+foreach my $id (keys %total_dups) {
+ if ($total_dups{$id} < $mostfreq) {
+ delete $total_dups{$id};
+ next;
}
-} else {
- # Calculate the deltas
- ($delta{$_} = $count{$_} - ($before{$_} || 0)) foreach (keys(%count));
-
- $dobefore = 1;
-}
-
-my @bugs;
-my @bug_ids;
-
-if (scalar(%count)) {
- # use Bugzilla::Search so that we get the security checking
- my $params = new Bugzilla::CGI({ 'bug_id' => [keys %count] });
-
- if ($openonly) {
- $params->param('resolution', '---');
- } else {
- # We want to show bugs which:
- # a) Aren't CLOSED; and
- # b) i) Aren't VERIFIED; OR
- # ii) Were resolved INVALID/WONTFIX
-
- # The rationale behind this is that people will eventually stop
- # reporting fixed bugs when they get newer versions of the software,
- # but if the bug is determined to be erroneous, people will still
- # keep reporting it, so we do need to show it here.
-
- # a)
- $params->param('field0-0-0', 'bug_status');
- $params->param('type0-0-0', 'notequals');
- $params->param('value0-0-0', 'CLOSED');
-
- # b) i)
- $params->param('field0-1-0', 'bug_status');
- $params->param('type0-1-0', 'notequals');
- $params->param('value0-1-0', 'VERIFIED');
-
- # b) ii)
- $params->param('field0-1-1', 'resolution');
- $params->param('type0-1-1', 'anyexact');
- $params->param('value0-1-1', 'INVALID,WONTFIX');
- }
-
- # Restrict to product if requested
- if ($cgi->param('product')) {
- $params->param('product', join(',', @query_products));
- }
-
- my $query = new Bugzilla::Search('fields' => [qw(bugs.bug_id
- map_components.name
- bugs.bug_severity
- bugs.op_sys
- bugs.target_milestone
- bugs.short_desc
- bugs.bug_status
- bugs.resolution
- )
- ],
- 'params' => $params,
- );
-
- my $results = $dbh->selectall_arrayref($query->getSQL());
-
- foreach my $result (@$results) {
- # Note: maximum row count is dealt with in the template.
-
- my ($id, $component, $bug_severity, $op_sys, $target_milestone,
- $short_desc, $bug_status, $resolution) = @$result;
-
- push (@bugs, { id => $id,
- count => $count{$id},
- delta => $delta{$id},
- component => $component,
- bug_severity => $bug_severity,
- op_sys => $op_sys,
- target_milestone => $target_milestone,
- short_desc => $short_desc,
- bug_status => $bug_status,
- resolution => $resolution });
- push (@bug_ids, $id);
+ if ($sortvisible and !grep($_->id == $id, @bugs)) {
+ delete $total_dups{$id};
}
}
-$vars->{'bugs'} = \@bugs;
-$vars->{'bug_ids'} = \@bug_ids;
+if (!@bugs) {
+ @bugs = @{ Bugzilla::Bug->new_from_list([keys %total_dups]) };
+ @bugs = @{ $user->visible_bugs(\@bugs) };
+}
-$vars->{'dobefore'} = $dobefore;
-$vars->{'sortby'} = $sortby;
-$vars->{'sortvisible'} = $sortvisible;
-$vars->{'changedsince'} = $changedsince;
-$vars->{'maxrows'} = $maxrows;
-$vars->{'openonly'} = $openonly;
-$vars->{'reverse'} = $reverse;
-$vars->{'format'} = $cgi->param('format');
-$vars->{'query_products'} = \@query_products;
-$vars->{'products'} = Bugzilla->user->get_selectable_products;
+my @fully_exclude_status = formvalue('fully_exclude_status');
+my @partly_exclude_status = formvalue('partly_exclude_status');
+my @except_resolution = formvalue('except_resolution');
+# Filter bugs by criteria
+my @result_bugs;
+foreach my $bug (@bugs) {
+ # It's possible, if somebody specified a bug ID that wasn't a dup
+ # in the "buglist" parameter and specified $sortvisible that there
+ # would be bugs in the list with 0 dups, so we want to avoid that.
+ next if !$total_dups{$bug->id};
-my $format = $template->get_format("reports/duplicates",
- scalar($cgi->param('format')),
- scalar($cgi->param('ctype')));
+ next if ($openonly and !$bug->isopened);
+ # If the bug has a status in @fully_exclude_status, we skip it,
+ # no question.
+ next if grep($_ eq $bug->bug_status, @fully_exclude_status);
+ # If the bug has a status in @partly_exclude_status, we skip it...
+ if (grep($_ eq $bug->bug_status, @partly_exclude_status)) {
+ # ...unless it has a resolution in @except_resolution.
+ next if !grep($_ eq $bug->resolution, @except_resolution);
+ }
-# We set the charset in Bugzilla::CGI, but CGI.pm ignores it unless the
-# Content-Type is a text type. In some cases, such as when we are
-# generating RDF, it isn't, so we specify the charset again here.
-print $cgi->header(
- -type => $format->{'ctype'},
- (Bugzilla->params->{'utf8'} ? ('charset', 'utf8') : () )
+ if (scalar @query_products) {
+ next if !grep($_->id == $bug->product_id, @query_products);
+ }
+
+ # Note: maximum row count is dealt with later.
+ push (@result_bugs, { bug => $bug,
+ count => $total_dups{$bug->id},
+ delta => $since_dups{$bug->id} || 0 });
+}
+@bugs = @result_bugs;
+@bugs = sort { sort_duplicates($a, $b, $sortby) } @bugs;
+if ($reverse) {
+ @bugs = reverse @bugs;
+}
+@bugs = @bugs[0..$maxrows-1] if scalar(@bugs) > $maxrows;
+
+my %vars = (
+ bugs => \@bugs,
+ bug_ids => [map { $_->{'bug'}->id } @bugs],
+ sortby => $sortby,
+ openonly => $openonly,
+ maxrows => $maxrows,
+ reverse => $reverse,
+ format => scalar $cgi->param('format'),
+ product => [map { $_->name } @query_products],
+ sortvisible => $sortvisible,
+ changedsince => $changedsince,
);
+my $format = $template->get_format("reports/duplicates", $vars{'format'});
+print $cgi->header;
+
# Generate and return the UI (HTML page) from the appropriate template.
-$template->process($format->{'template'}, $vars)
+$template->process($format->{'template'}, \%vars)
|| ThrowTemplateError($template->error());
-
-
-sub days_ago {
- my ($dom, $mon, $year) = (localtime(time - ($_[0]*24*60*60)))[3, 4, 5];
- return sprintf "%04d-%02d-%02d", 1900 + $year, ++$mon, $dom;
-}
diff --git a/Websites/bugs.webkit.org/editclassifications.cgi b/Websites/bugs.webkit.org/editclassifications.cgi
index 7e744d9..0c5d89a 100755
--- a/Websites/bugs.webkit.org/editclassifications.cgi
+++ b/Websites/bugs.webkit.org/editclassifications.cgi
@@ -40,7 +40,7 @@
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
- $vars->{'classifications'} = [Bugzilla::Classification::get_all_classifications()]
+ $vars->{'classifications'} = [Bugzilla::Classification->get_all]
if ($action eq 'select');
# There is currently only one section about classifications,
# so all pages point to it. Let's define it here.
@@ -62,7 +62,7 @@
print $cgi->header();
-exists Bugzilla->user->groups->{'editclassifications'}
+Bugzilla->user->in_group('editclassifications')
|| ThrowUserError("auth_failure", {group => "editclassifications",
action => "edit",
object => "classifications"});
@@ -100,36 +100,16 @@
if ($action eq 'new') {
check_token_data($token, 'add_classification');
- $class_name || ThrowUserError("classification_not_specified");
-
my $classification =
- new Bugzilla::Classification({name => $class_name});
-
- if ($classification) {
- ThrowUserError("classification_already_exists",
- { name => $classification->name });
- }
-
- my $description = trim($cgi->param('description') || '');
-
- my $sortkey = trim($cgi->param('sortkey') || 0);
- my $stored_sortkey = $sortkey;
- detaint_natural($sortkey)
- || ThrowUserError('classification_invalid_sortkey', {'name' => $class_name,
- 'sortkey' => $stored_sortkey});
-
- trick_taint($description);
- trick_taint($class_name);
-
- # Add the new classification.
- $dbh->do("INSERT INTO classifications (name, description, sortkey)
- VALUES (?, ?, ?)", undef, ($class_name, $description, $sortkey));
+ Bugzilla::Classification->create({name => $class_name,
+ description => scalar $cgi->param('description'),
+ sortkey => scalar $cgi->param('sortkey')});
delete_token($token);
$vars->{'message'} = 'classification_created';
- $vars->{'classification'} = new Bugzilla::Classification({name => $class_name});
- $vars->{'classifications'} = [Bugzilla::Classification::get_all_classifications];
+ $vars->{'classification'} = $classification;
+ $vars->{'classifications'} = [Bugzilla::Classification->get_all];
$vars->{'token'} = issue_session_token('reclassify_classifications');
LoadTemplate('reclassify');
}
@@ -142,8 +122,7 @@
if ($action eq 'del') {
- my $classification =
- Bugzilla::Classification::check_classification($class_name);
+ my $classification = Bugzilla::Classification->check($class_name);
if ($classification->id == 1) {
ThrowUserError("classification_not_deletable");
@@ -166,29 +145,12 @@
if ($action eq 'delete') {
check_token_data($token, 'delete_classification');
- my $classification =
- Bugzilla::Classification::check_classification($class_name);
-
- if ($classification->id == 1) {
- ThrowUserError("classification_not_deletable");
- }
-
- # lock the tables before we start to change everything:
- $dbh->bz_start_transaction();
-
- # update products just in case
- $dbh->do("UPDATE products SET classification_id = 1
- WHERE classification_id = ?", undef, $classification->id);
-
- # delete
- $dbh->do("DELETE FROM classifications WHERE id = ?", undef,
- $classification->id);
-
- $dbh->bz_commit_transaction();
+ my $classification = Bugzilla::Classification->check($class_name);
+ $classification->remove_from_db;
+ delete_token($token);
$vars->{'message'} = 'classification_deleted';
- $vars->{'classification'} = $class_name;
- delete_token($token);
+ $vars->{'classification'} = $classification;
LoadTemplate('select');
}
@@ -199,9 +161,7 @@
#
if ($action eq 'edit') {
-
- my $classification =
- Bugzilla::Classification::check_classification($class_name);
+ my $classification = Bugzilla::Classification->check($class_name);
$vars->{'classification'} = $classification;
$vars->{'token'} = issue_session_token('edit_classification');
@@ -216,59 +176,19 @@
if ($action eq 'update') {
check_token_data($token, 'edit_classification');
- $class_name || ThrowUserError("classification_not_specified");
-
my $class_old_name = trim($cgi->param('classificationold') || '');
+ my $classification = Bugzilla::Classification->check($class_old_name);
- my $class_old =
- Bugzilla::Classification::check_classification($class_old_name);
+ $classification->set_name($class_name);
+ $classification->set_description(scalar $cgi->param('description'));
+ $classification->set_sortkey(scalar $cgi->param('sortkey'));
- my $description = trim($cgi->param('description') || '');
-
- my $sortkey = trim($cgi->param('sortkey') || 0);
- my $stored_sortkey = $sortkey;
- detaint_natural($sortkey)
- || ThrowUserError('classification_invalid_sortkey', {'name' => $class_old->name,
- 'sortkey' => $stored_sortkey});
-
- $dbh->bz_start_transaction();
-
- if ($class_name ne $class_old->name) {
-
- my $class = new Bugzilla::Classification({name => $class_name});
- if ($class) {
- ThrowUserError("classification_already_exists",
- { name => $class->name });
- }
- trick_taint($class_name);
- $dbh->do("UPDATE classifications SET name = ? WHERE id = ?",
- undef, ($class_name, $class_old->id));
-
- $vars->{'updated_classification'} = 1;
- }
-
- if ($description ne $class_old->description) {
- trick_taint($description);
- $dbh->do("UPDATE classifications SET description = ?
- WHERE id = ?", undef,
- ($description, $class_old->id));
-
- $vars->{'updated_description'} = 1;
- }
-
- if ($sortkey ne $class_old->sortkey) {
- $dbh->do("UPDATE classifications SET sortkey = ?
- WHERE id = ?", undef,
- ($sortkey, $class_old->id));
-
- $vars->{'updated_sortkey'} = 1;
- }
-
- $dbh->bz_commit_transaction();
+ my $changes = $classification->update;
+ delete_token($token);
$vars->{'message'} = 'classification_updated';
- $vars->{'classification'} = $class_name;
- delete_token($token);
+ $vars->{'classification'} = $classification;
+ $vars->{'changes'} = $changes;
LoadTemplate('select');
}
@@ -277,9 +197,7 @@
#
if ($action eq 'reclassify') {
-
- my $classification =
- Bugzilla::Classification::check_classification($class_name);
+ my $classification = Bugzilla::Classification->check($class_name);
my $sth = $dbh->prepare("UPDATE products SET classification_id = ?
WHERE name = ?");
@@ -304,9 +222,7 @@
delete_token($token);
}
- my @classifications =
- Bugzilla::Classification::get_all_classifications;
- $vars->{'classifications'} = \@classifications;
+ $vars->{'classifications'} = [Bugzilla::Classification->get_all];
$vars->{'classification'} = $classification;
$vars->{'token'} = issue_session_token('reclassify_classifications');
@@ -317,4 +233,4 @@
# No valid action found
#
-ThrowCodeError("action_unrecognized", {action => $action});
+ThrowUserError('unknown_action', {action => $action});
diff --git a/Websites/bugs.webkit.org/editcomponents.cgi b/Websites/bugs.webkit.org/editcomponents.cgi
index ebe9748..a3b51c2 100755
--- a/Websites/bugs.webkit.org/editcomponents.cgi
+++ b/Websites/bugs.webkit.org/editcomponents.cgi
@@ -118,7 +118,7 @@
if ($action eq 'new') {
check_token_data($token, 'add_component');
# Do the user matching
- Bugzilla::User::match_field ($cgi, {
+ Bugzilla::User::match_field ({
'initialowner' => { 'type' => 'single' },
'initialqacontact' => { 'type' => 'single' },
'initialcc' => { 'type' => 'multi' },
@@ -128,14 +128,19 @@
my $default_qa_contact = trim($cgi->param('initialqacontact') || '');
my $description = trim($cgi->param('description') || '');
my @initial_cc = $cgi->param('initialcc');
+ my $isactive = $cgi->param('isactive');
- my $component =
- Bugzilla::Component->create({ name => $comp_name,
- product => $product,
- description => $description,
- initialowner => $default_assignee,
- initialqacontact => $default_qa_contact,
- initial_cc => \@initial_cc });
+ my $component = Bugzilla::Component->create({
+ name => $comp_name,
+ product => $product,
+ description => $description,
+ initialowner => $default_assignee,
+ initialqacontact => $default_qa_contact,
+ initial_cc => \@initial_cc,
+ # XXX We should not be creating series for products that we
+ # didn't create series for.
+ create_series => 1,
+ });
$vars->{'message'} = 'component_created';
$vars->{'comp'} = $component;
@@ -215,7 +220,7 @@
if ($action eq 'update') {
check_token_data($token, 'edit_component');
# Do the user matching
- Bugzilla::User::match_field ($cgi, {
+ Bugzilla::User::match_field ({
'initialowner' => { 'type' => 'single' },
'initialqacontact' => { 'type' => 'single' },
'initialcc' => { 'type' => 'multi' },
@@ -226,7 +231,8 @@
my $default_qa_contact = trim($cgi->param('initialqacontact') || '');
my $description = trim($cgi->param('description') || '');
my @initial_cc = $cgi->param('initialcc');
-
+ my $isactive = $cgi->param('isactive');
+
my $component =
Bugzilla::Component->check({ product => $product, name => $comp_old_name });
@@ -235,6 +241,7 @@
$component->set_default_assignee($default_assignee);
$component->set_default_qa_contact($default_qa_contact);
$component->set_cc_list(\@initial_cc);
+ $component->set_is_active($isactive);
my $changes = $component->update();
$vars->{'message'} = 'component_updated';
@@ -248,7 +255,5 @@
exit;
}
-#
# No valid action found
-#
-ThrowUserError('no_valid_action', {'field' => "component"});
+ThrowUserError('unknown_action', {action => $action});
diff --git a/Websites/bugs.webkit.org/editfields.cgi b/Websites/bugs.webkit.org/editfields.cgi
index ad0abd7..f17aa77 100755
--- a/Websites/bugs.webkit.org/editfields.cgi
+++ b/Websites/bugs.webkit.org/editfields.cgi
@@ -55,7 +55,6 @@
}
elsif ($action eq 'new') {
check_token_data($token, 'add_field');
-
$vars->{'field'} = Bugzilla::Field->create({
name => scalar $cgi->param('name'),
description => scalar $cgi->param('desc'),
@@ -65,6 +64,12 @@
enter_bug => scalar $cgi->param('enter_bug'),
obsolete => scalar $cgi->param('obsolete'),
custom => 1,
+ buglist => 1,
+ visibility_field_id => scalar $cgi->param('visibility_field_id'),
+ visibility_values => [ $cgi->param('visibility_values') ],
+ value_field_id => scalar $cgi->param('value_field_id'),
+ reverse_desc => scalar $cgi->param('reverse_desc'),
+ is_mandatory => scalar $cgi->param('is_mandatory'),
});
delete_token($token);
@@ -107,6 +112,11 @@
$field->set_in_new_bugmail($cgi->param('new_bugmail'));
$field->set_enter_bug($cgi->param('enter_bug'));
$field->set_obsolete($cgi->param('obsolete'));
+ $field->set_is_mandatory($cgi->param('is_mandatory'));
+ $field->set_visibility_field($cgi->param('visibility_field_id'));
+ $field->set_visibility_values([ $cgi->param('visibility_values') ]);
+ $field->set_value_field($cgi->param('value_field_id'));
+ $field->set_reverse_desc($cgi->param('reverse_desc'));
$field->update();
delete_token($token);
@@ -161,5 +171,5 @@
|| ThrowTemplateError($template->error());
}
else {
- ThrowUserError('no_valid_action', {'field' => 'custom_field'});
+ ThrowUserError('unknown_action', {action => $action});
}
diff --git a/Websites/bugs.webkit.org/editflagtypes.cgi b/Websites/bugs.webkit.org/editflagtypes.cgi
index 97db82a..356835d 100755
--- a/Websites/bugs.webkit.org/editflagtypes.cgi
+++ b/Websites/bugs.webkit.org/editflagtypes.cgi
@@ -38,72 +38,124 @@
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Product;
-use Bugzilla::Component;
-use Bugzilla::Bug;
-use Bugzilla::Attachment;
use Bugzilla::Token;
-local our $cgi = Bugzilla->cgi;
-local our $template = Bugzilla->template;
-local our $vars = {};
-
-# Make sure the user is logged in and is an administrator.
+# Make sure the user is logged in and has the right privileges.
my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+
+print $cgi->header();
+
$user->in_group('editcomponents')
+ || scalar(@{$user->get_products_by_permission('editcomponents')})
|| ThrowUserError("auth_failure", {group => "editcomponents",
action => "edit",
object => "flagtypes"});
-################################################################################
-# Main Body Execution
-################################################################################
+# We need this everywhere.
+my $vars = get_products_and_components();
+my @products = @{$vars->{products}};
-# All calls to this script should contain an "action" variable whose value
-# determines what the user wants to do. The code below checks the value of
-# that variable and runs the appropriate code.
-
-# Determine whether to use the action specified by the user or the default.
my $action = $cgi->param('action') || 'list';
my $token = $cgi->param('token');
-my @categoryActions;
+my $product = $cgi->param('product');
+my $component = $cgi->param('component');
+my $flag_id = $cgi->param('id');
-if (@categoryActions = grep(/^categoryAction-.+/, $cgi->param())) {
- $categoryActions[0] =~ s/^categoryAction-//;
- processCategoryChange($categoryActions[0], $token);
+if ($product) {
+ # Make sure the user is allowed to view this product name.
+ # Users with global editcomponents privs can see all product names.
+ ($product) = grep { lc($_->name) eq lc($product) } @products;
+ $product || ThrowUserError('product_access_denied', { name => $cgi->param('product') });
+}
+
+if ($component) {
+ ($product && $product->id)
+ || ThrowUserError('flag_type_component_without_product');
+ ($component) = grep { lc($_->name) eq lc($component) } @{$product->components};
+ $component || ThrowUserError('product_unknown_component', { product => $product->name,
+ comp => $cgi->param('component') });
+}
+
+# If 'categoryAction' is set, it has priority over 'action'.
+if (my ($category_action) = grep { $_ =~ /^categoryAction-(?:\w+)$/ } $cgi->param()) {
+ $category_action =~ s/^categoryAction-//;
+
+ my @inclusions = $cgi->param('inclusions');
+ my @exclusions = $cgi->param('exclusions');
+ my @categories;
+ if ($category_action =~ /^(in|ex)clude$/) {
+ if (!$user->in_group('editcomponents') && !$product) {
+ # The user can only add the flag type to products he can administrate.
+ foreach my $prod (@products) {
+ push(@categories, $prod->id . ':0')
+ }
+ }
+ else {
+ my $category = ($product ? $product->id : 0) . ':' .
+ ($component ? $component->id : 0);
+ push(@categories, $category);
+ }
+ }
+
+ if ($category_action eq 'include') {
+ foreach my $category (@categories) {
+ push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
+ }
+ }
+ elsif ($category_action eq 'exclude') {
+ foreach my $category (@categories) {
+ push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
+ }
+ }
+ elsif ($category_action eq 'removeInclusion') {
+ my @inclusion_to_remove = $cgi->param('inclusion_to_remove');
+ foreach my $remove (@inclusion_to_remove) {
+ @inclusions = grep { $_ ne $remove } @inclusions;
+ }
+ }
+ elsif ($category_action eq 'removeExclusion') {
+ my @exclusion_to_remove = $cgi->param('exclusion_to_remove');
+ foreach my $remove (@exclusion_to_remove) {
+ @exclusions = grep { $_ ne $remove } @exclusions;
+ }
+ }
+
+ $vars->{'groups'} = get_settable_groups();
+ $vars->{'action'} = $action;
+
+ my $type = {};
+ $type->{$_} = $cgi->param($_) foreach $cgi->param();
+ # Make sure boolean fields are defined, else they fall back to 1.
+ foreach my $boolean (qw(is_active is_requestable is_requesteeble is_multiplicable)) {
+ $type->{$boolean} ||= 0;
+ }
+
+ # That's what I call a big hack. The template expects to see a group object.
+ $type->{'grant_group'} = {};
+ $type->{'grant_group'}->{'name'} = $cgi->param('grant_group');
+ $type->{'request_group'} = {};
+ $type->{'request_group'}->{'name'} = $cgi->param('request_group');
+
+ $vars->{'inclusions'} = clusion_array_to_hash(\@inclusions, \@products);
+ $vars->{'exclusions'} = clusion_array_to_hash(\@exclusions, \@products);
+
+ $vars->{'type'} = $type;
+ $vars->{'token'} = $token;
+ $vars->{'check_clusions'} = 1;
+ $vars->{'can_fully_edit'} = $cgi->param('can_fully_edit');
+
+ $template->process("admin/flag-type/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
exit;
}
-if ($action eq 'list') { list(); }
-elsif ($action eq 'enter') { edit($action); }
-elsif ($action eq 'copy') { edit($action); }
-elsif ($action eq 'edit') { edit($action); }
-elsif ($action eq 'insert') { insert($token); }
-elsif ($action eq 'update') { update($token); }
-elsif ($action eq 'confirmdelete') { confirmDelete(); }
-elsif ($action eq 'delete') { deleteType($token); }
-elsif ($action eq 'deactivate') { deactivate($token); }
-else {
- ThrowCodeError("action_unrecognized", { action => $action });
-}
-
-exit;
-
-################################################################################
-# Functions
-################################################################################
-
-sub list {
- # Restrict the list to the given product and component, if given.
- $vars = get_products_and_components($vars);
-
- my $product = validateProduct(scalar $cgi->param('product'));
- my $component = validateComponent($product, scalar $cgi->param('component'));
+if ($action eq 'list') {
my $product_id = $product ? $product->id : 0;
my $component_id = $component ? $component->id : 0;
-
- # Define the variables and functions that will be passed to the UI template.
- $vars->{'selected_product'} = $cgi->param('product');
- $vars->{'selected_component'} = $cgi->param('component');
+ my $show_flag_counts = $cgi->param('show_flag_counts') ? 1 : 0;
+ my $group_id = $cgi->param('group');
my $bug_flagtypes;
my $attach_flagtypes;
@@ -115,8 +167,8 @@
$attach_flagtypes = $component->flag_types->{'attachment'};
# Filter flag types if a group ID is given.
- $bug_flagtypes = filter_group($bug_flagtypes);
- $attach_flagtypes = filter_group($attach_flagtypes);
+ $bug_flagtypes = filter_group($bug_flagtypes, $group_id);
+ $attach_flagtypes = filter_group($attach_flagtypes, $group_id);
}
# If only a product is specified but no component, then restrict the list
@@ -126,48 +178,77 @@
$attach_flagtypes = $product->flag_types->{'attachment'};
# Filter flag types if a group ID is given.
- $bug_flagtypes = filter_group($bug_flagtypes);
- $attach_flagtypes = filter_group($attach_flagtypes);
+ $bug_flagtypes = filter_group($bug_flagtypes, $group_id);
+ $attach_flagtypes = filter_group($attach_flagtypes, $group_id);
}
# If no product is given, then show all flag types available.
else {
- $bug_flagtypes =
- Bugzilla::FlagType::match({'target_type' => 'bug',
- 'group' => scalar $cgi->param('group')});
-
- $attach_flagtypes =
- Bugzilla::FlagType::match({'target_type' => 'attachment',
- 'group' => scalar $cgi->param('group')});
+ my $flagtypes = get_editable_flagtypes(\@products, $group_id);
+ $bug_flagtypes = [grep { $_->target_type eq 'bug' } @$flagtypes];
+ $attach_flagtypes = [grep { $_->target_type eq 'attachment' } @$flagtypes];
}
+ if ($show_flag_counts) {
+ my %bug_lists;
+ my %map = ('+' => 'granted', '-' => 'denied', '?' => 'pending');
+
+ foreach my $flagtype (@$bug_flagtypes, @$attach_flagtypes) {
+ $bug_lists{$flagtype->id} = {};
+ my $flags = Bugzilla::Flag->match({type_id => $flagtype->id});
+ # Build lists of bugs, triaged by flag status.
+ push(@{$bug_lists{$flagtype->id}->{$map{$_->status}}}, $_->bug_id) foreach @$flags;
+ }
+ $vars->{'bug_lists'} = \%bug_lists;
+ $vars->{'show_flag_counts'} = 1;
+ }
+
+ $vars->{'selected_product'} = $product ? $product->name : '';
+ $vars->{'selected_component'} = $component ? $component->name : '';
$vars->{'bug_types'} = $bug_flagtypes;
$vars->{'attachment_types'} = $attach_flagtypes;
- # Return the appropriate HTTP response headers.
- print $cgi->header();
-
- # Generate and return the UI (HTML page) from the appropriate template.
$template->process("admin/flag-type/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
+ exit;
}
+if ($action eq 'enter') {
+ my $type = $cgi->param('target_type');
+ ($type eq 'bug' || $type eq 'attachment')
+ || ThrowCodeError('flag_type_target_type_invalid', { target_type => $type });
-sub edit {
- my ($action) = @_;
+ $vars->{'action'} = 'insert';
+ $vars->{'token'} = issue_session_token('add_flagtype');
+ $vars->{'type'} = { 'target_type' => $type };
+ # Only users with global editcomponents privs can add a flagtype
+ # to all products.
+ $vars->{'inclusions'} = { '__Any__:__Any__' => '0:0' }
+ if $user->in_group('editcomponents');
+ $vars->{'can_fully_edit'} = 1;
+ # Get a list of groups available to restrict this flag type against.
+ $vars->{'groups'} = get_settable_groups();
- my $flag_type;
- if ($action eq 'enter') {
- validateTargetType();
+ $template->process("admin/flag-type/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+if ($action eq 'edit' || $action eq 'copy') {
+ my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
+ $vars->{'type'} = $flagtype;
+ $vars->{'can_fully_edit'} = $can_fully_edit;
+
+ if ($user->in_group('editcomponents')) {
+ $vars->{'inclusions'} = $flagtype->inclusions;
+ $vars->{'exclusions'} = $flagtype->exclusions;
}
else {
- $flag_type = validateID();
+ # Filter products the user shouldn't know about.
+ $vars->{'inclusions'} = clusion_array_to_hash([values %{$flagtype->inclusions}], \@products);
+ $vars->{'exclusions'} = clusion_array_to_hash([values %{$flagtype->exclusions}], \@products);
}
- # Fill $vars with products and components data.
- $vars = get_products_and_components($vars);
-
- $vars->{'last_action'} = $cgi->param('action');
- if ($cgi->param('action') eq 'enter' || $cgi->param('action') eq 'copy') {
+ if ($action eq 'copy') {
$vars->{'action'} = "insert";
$vars->{'token'} = issue_session_token('add_flagtype');
}
@@ -176,352 +257,204 @@
$vars->{'token'} = issue_session_token('edit_flagtype');
}
- # If copying or editing an existing flag type, retrieve it.
- if ($cgi->param('action') eq 'copy' || $cgi->param('action') eq 'edit') {
- $vars->{'type'} = $flag_type;
- }
- # Otherwise set the target type (the minimal information about the type
- # that the template needs to know) from the URL parameter and default
- # the list of inclusions to all categories.
- else {
- my %inclusions;
- $inclusions{"__Any__:__Any__"} = "0:0";
- $vars->{'type'} = { 'target_type' => scalar $cgi->param('target_type'),
- 'inclusions' => \%inclusions };
- }
# Get a list of groups available to restrict this flag type against.
- my @groups = Bugzilla::Group->get_all;
- $vars->{'groups'} = \@groups;
- # Return the appropriate HTTP response headers.
- print $cgi->header();
+ $vars->{'groups'} = get_settable_groups();
- # Generate and return the UI (HTML page) from the appropriate template.
$template->process("admin/flag-type/edit.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
+ exit;
}
-sub processCategoryChange {
- my ($categoryAction, $token) = @_;
- validateIsActive();
- validateIsRequestable();
- validateIsRequesteeble();
- validateAllowMultiple();
-
- my @inclusions = $cgi->param('inclusions');
- my @exclusions = $cgi->param('exclusions');
- if ($categoryAction eq 'include') {
- my $product = validateProduct(scalar $cgi->param('product'));
- my $component = validateComponent($product, scalar $cgi->param('component'));
- my $category = ($product ? $product->id : 0) . ":" .
- ($component ? $component->id : 0);
- push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
- }
- elsif ($categoryAction eq 'exclude') {
- my $product = validateProduct(scalar $cgi->param('product'));
- my $component = validateComponent($product, scalar $cgi->param('component'));
- my $category = ($product ? $product->id : 0) . ":" .
- ($component ? $component->id : 0);
- push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
- }
- elsif ($categoryAction eq 'removeInclusion') {
- my @inclusion_to_remove = $cgi->param('inclusion_to_remove');
- @inclusions = map {(lsearch(\@inclusion_to_remove, $_) < 0) ? $_ : ()} @inclusions;
- }
- elsif ($categoryAction eq 'removeExclusion') {
- my @exclusion_to_remove = $cgi->param('exclusion_to_remove');
- @exclusions = map {(lsearch(\@exclusion_to_remove, $_) < 0) ? $_ : ()} @exclusions;
- }
-
- # Convert the array @clusions('prod_ID:comp_ID') back to a hash of
- # the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
- my %inclusions = clusion_array_to_hash(\@inclusions);
- my %exclusions = clusion_array_to_hash(\@exclusions);
-
- # Fill $vars with products and components data.
- $vars = get_products_and_components($vars);
-
- my @groups = Bugzilla::Group->get_all;
- $vars->{'groups'} = \@groups;
- $vars->{'action'} = $cgi->param('action');
-
- my $type = {};
- foreach my $key ($cgi->param()) { $type->{$key} = $cgi->param($key) }
- # That's what I call a big hack. The template expects to see a group object.
- # This script needs some rewrite anyway.
- $type->{'grant_group'} = {};
- $type->{'grant_group'}->{'name'} = $cgi->param('grant_group');
- $type->{'request_group'} = {};
- $type->{'request_group'}->{'name'} = $cgi->param('request_group');
-
- $type->{'inclusions'} = \%inclusions;
- $type->{'exclusions'} = \%exclusions;
- $vars->{'type'} = $type;
- $vars->{'token'} = $token;
-
- # Return the appropriate HTTP response headers.
- print $cgi->header();
-
- # Generate and return the UI (HTML page) from the appropriate template.
- $template->process("admin/flag-type/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
-}
-
-# Convert the array @clusions('prod_ID:comp_ID') back to a hash of
-# the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
-sub clusion_array_to_hash {
- my $array = shift;
- my %hash;
- my %products;
- my %components;
- foreach my $ids (@$array) {
- trick_taint($ids);
- my ($product_id, $component_id) = split(":", $ids);
- my $product_name = "__Any__";
- if ($product_id) {
- $products{$product_id} ||= new Bugzilla::Product($product_id);
- $product_name = $products{$product_id}->name if $products{$product_id};
- }
- my $component_name = "__Any__";
- if ($component_id) {
- $components{$component_id} ||= new Bugzilla::Component($component_id);
- $component_name = $components{$component_id}->name if $components{$component_id};
- }
- $hash{"$product_name:$component_name"} = $ids;
- }
- return %hash;
-}
-
-sub insert {
- my $token = shift;
+if ($action eq 'insert') {
check_token_data($token, 'add_flagtype');
- my $name = validateName();
- my $description = validateDescription();
- my $cc_list = validateCCList();
- validateTargetType();
- validateSortKey();
- validateIsActive();
- validateIsRequestable();
- validateIsRequesteeble();
- validateAllowMultiple();
- validateGroups();
- my $dbh = Bugzilla->dbh;
+ my $name = $cgi->param('name');
+ my $description = $cgi->param('description');
+ my $target_type = $cgi->param('target_type');
+ my $cc_list = $cgi->param('cc_list');
+ my $sortkey = $cgi->param('sortkey');
+ my $is_active = $cgi->param('is_active');
+ my $is_requestable = $cgi->param('is_requestable');
+ my $is_specifically = $cgi->param('is_requesteeble');
+ my $is_multiplicable = $cgi->param('is_multiplicable');
+ my $grant_group = $cgi->param('grant_group');
+ my $request_group = $cgi->param('request_group');
+ my @inclusions = $cgi->param('inclusions');
+ my @exclusions = $cgi->param('exclusions');
- my $target_type = $cgi->param('target_type') eq "bug" ? "b" : "a";
+ # Filter inclusion and exclusion lists to products the user can see.
+ unless ($user->in_group('editcomponents')) {
+ @inclusions = values %{clusion_array_to_hash(\@inclusions, \@products)};
+ @exclusions = values %{clusion_array_to_hash(\@exclusions, \@products)};
+ }
- $dbh->bz_start_transaction();
+ my $flagtype = Bugzilla::FlagType->create({
+ name => $name,
+ description => $description,
+ target_type => $target_type,
+ cc_list => $cc_list,
+ sortkey => $sortkey,
+ is_active => $is_active,
+ is_requestable => $is_requestable,
+ is_requesteeble => $is_specifically,
+ is_multiplicable => $is_multiplicable,
+ grant_group => $grant_group,
+ request_group => $request_group,
+ inclusions => \@inclusions,
+ exclusions => \@exclusions
+ });
- # Insert a record for the new flag type into the database.
- $dbh->do('INSERT INTO flagtypes
- (name, description, cc_list, target_type,
- sortkey, is_active, is_requestable,
- is_requesteeble, is_multiplicable,
- grant_group_id, request_group_id)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
- undef, ($name, $description, $cc_list, $target_type,
- $cgi->param('sortkey'), $cgi->param('is_active'),
- $cgi->param('is_requestable'), $cgi->param('is_requesteeble'),
- $cgi->param('is_multiplicable'), scalar($cgi->param('grant_gid')),
- scalar($cgi->param('request_gid'))));
+ delete_token($token);
- # Get the ID of the new flag type.
- my $id = $dbh->bz_last_key('flagtypes', 'id');
-
- # Populate the list of inclusions/exclusions for this flag type.
- validateAndSubmit($id);
-
- $dbh->bz_commit_transaction();
-
- $vars->{'name'} = $name;
+ $vars->{'name'} = $flagtype->name;
$vars->{'message'} = "flag_type_created";
- delete_token($token);
- $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
- $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
-
- # Return the appropriate HTTP response headers.
- print $cgi->header();
+ my $flagtypes = get_editable_flagtypes(\@products);
+ $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @$flagtypes];
+ $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @$flagtypes];
$template->process("admin/flag-type/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
+ exit;
}
-
-sub update {
- my $token = shift;
+if ($action eq 'update') {
check_token_data($token, 'edit_flagtype');
- my $flag_type = validateID();
- my $id = $flag_type->id;
- my $name = validateName();
- my $description = validateDescription();
- my $cc_list = validateCCList();
- validateTargetType();
- validateSortKey();
- validateIsActive();
- validateIsRequestable();
- validateIsRequesteeble();
- validateAllowMultiple();
- validateGroups();
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- $dbh->bz_start_transaction();
- $dbh->do('UPDATE flagtypes
- SET name = ?, description = ?, cc_list = ?,
- sortkey = ?, is_active = ?, is_requestable = ?,
- is_requesteeble = ?, is_multiplicable = ?,
- grant_group_id = ?, request_group_id = ?
- WHERE id = ?',
- undef, ($name, $description, $cc_list, $cgi->param('sortkey'),
- $cgi->param('is_active'), $cgi->param('is_requestable'),
- $cgi->param('is_requesteeble'), $cgi->param('is_multiplicable'),
- scalar($cgi->param('grant_gid')), scalar($cgi->param('request_gid')),
- $id));
-
- # Update the list of inclusions/exclusions for this flag type.
- validateAndSubmit($id);
+ my $name = $cgi->param('name');
+ my $description = $cgi->param('description');
+ my $cc_list = $cgi->param('cc_list');
+ my $sortkey = $cgi->param('sortkey');
+ my $is_active = $cgi->param('is_active');
+ my $is_requestable = $cgi->param('is_requestable');
+ my $is_specifically = $cgi->param('is_requesteeble');
+ my $is_multiplicable = $cgi->param('is_multiplicable');
+ my $grant_group = $cgi->param('grant_group');
+ my $request_group = $cgi->param('request_group');
+ my @inclusions = $cgi->param('inclusions');
+ my @exclusions = $cgi->param('exclusions');
- $dbh->bz_commit_transaction();
-
- # Clear existing flags for bugs/attachments in categories no longer on
- # the list of inclusions or that have been added to the list of exclusions.
- my $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
- FROM flags
- INNER JOIN bugs
- ON flags.bug_id = bugs.bug_id
- LEFT OUTER JOIN flaginclusions AS i
- ON (flags.type_id = i.type_id
- AND (bugs.product_id = i.product_id
- OR i.product_id IS NULL)
- AND (bugs.component_id = i.component_id
- OR i.component_id IS NULL))
- WHERE flags.type_id = ?
- AND i.type_id IS NULL',
- undef, $id);
- my $flags = Bugzilla::Flag->new_from_list($flag_ids);
- foreach my $flag (@$flags) {
- my $bug = new Bugzilla::Bug($flag->bug_id);
- Bugzilla::Flag::clear($flag, $bug, $flag->attachment);
+ my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
+ if ($cgi->param('check_clusions') && !$user->in_group('editcomponents')) {
+ # Filter inclusion and exclusion lists to products the user can edit.
+ @inclusions = values %{clusion_array_to_hash(\@inclusions, \@products)};
+ @exclusions = values %{clusion_array_to_hash(\@exclusions, \@products)};
+ # Bring back the products the user cannot edit.
+ foreach my $item (values %{$flagtype->inclusions}) {
+ my ($prod_id, $comp_id) = split(':', $item);
+ push(@inclusions, $item) unless grep { $_->id == $prod_id } @products;
+ }
+ foreach my $item (values %{$flagtype->exclusions}) {
+ my ($prod_id, $comp_id) = split(':', $item);
+ push(@exclusions, $item) unless grep { $_->id == $prod_id } @products;
+ }
}
- $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
- FROM flags
- INNER JOIN bugs
- ON flags.bug_id = bugs.bug_id
- INNER JOIN flagexclusions AS e
- ON flags.type_id = e.type_id
- WHERE flags.type_id = ?
- AND (bugs.product_id = e.product_id
- OR e.product_id IS NULL)
- AND (bugs.component_id = e.component_id
- OR e.component_id IS NULL)',
- undef, $id);
- $flags = Bugzilla::Flag->new_from_list($flag_ids);
- foreach my $flag (@$flags) {
- my $bug = new Bugzilla::Bug($flag->bug_id);
- Bugzilla::Flag::clear($flag, $bug, $flag->attachment);
+ if ($can_fully_edit) {
+ $flagtype->set_name($name);
+ $flagtype->set_description($description);
+ $flagtype->set_cc_list($cc_list);
+ $flagtype->set_sortkey($sortkey);
+ $flagtype->set_is_active($is_active);
+ $flagtype->set_is_requestable($is_requestable);
+ $flagtype->set_is_specifically_requestable($is_specifically);
+ $flagtype->set_is_multiplicable($is_multiplicable);
+ $flagtype->set_grant_group($grant_group);
+ $flagtype->set_request_group($request_group);
}
+ $flagtype->set_clusions({ inclusions => \@inclusions, exclusions => \@exclusions})
+ if $cgi->param('check_clusions');
+ my $changes = $flagtype->update();
- # Now silently remove requestees from flags which are no longer
- # specifically requestable.
- if (!$cgi->param('is_requesteeble')) {
- $dbh->do('UPDATE flags SET requestee_id = NULL WHERE type_id = ?',
- undef, $id);
- }
-
- $vars->{'name'} = $name;
- $vars->{'message'} = "flag_type_changes_saved";
delete_token($token);
- $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
- $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+ $vars->{'flagtype'} = $flagtype;
+ $vars->{'changes'} = $changes;
+ $vars->{'message'} = 'flag_type_updated';
- # Return the appropriate HTTP response headers.
- print $cgi->header();
+ my $flagtypes = get_editable_flagtypes(\@products);
+ $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @$flagtypes];
+ $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @$flagtypes];
$template->process("admin/flag-type/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
+ exit;
}
+if ($action eq 'confirmdelete') {
+ my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
+ ThrowUserError('flag_type_cannot_delete', { flagtype => $flagtype }) unless $can_fully_edit;
-sub confirmDelete {
- my $flag_type = validateID();
-
- $vars->{'flag_type'} = $flag_type;
+ $vars->{'flag_type'} = $flagtype;
$vars->{'token'} = issue_session_token('delete_flagtype');
- # Return the appropriate HTTP response headers.
- print $cgi->header();
- # Generate and return the UI (HTML page) from the appropriate template.
$template->process("admin/flag-type/confirm-delete.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
+ exit;
}
-
-sub deleteType {
- my $token = shift;
+if ($action eq 'delete') {
check_token_data($token, 'delete_flagtype');
- my $flag_type = validateID();
- my $id = $flag_type->id;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
+ my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
+ ThrowUserError('flag_type_cannot_delete', { flagtype => $flagtype }) unless $can_fully_edit;
- # Get the name of the flag type so we can tell users
- # what was deleted.
- $vars->{'name'} = $flag_type->name;
+ $flagtype->remove_from_db();
- $dbh->do('DELETE FROM flags WHERE type_id = ?', undef, $id);
- $dbh->do('DELETE FROM flaginclusions WHERE type_id = ?', undef, $id);
- $dbh->do('DELETE FROM flagexclusions WHERE type_id = ?', undef, $id);
- $dbh->do('DELETE FROM flagtypes WHERE id = ?', undef, $id);
- $dbh->bz_commit_transaction();
-
- $vars->{'message'} = "flag_type_deleted";
delete_token($token);
- $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
- $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+ $vars->{'name'} = $flagtype->name;
+ $vars->{'message'} = "flag_type_deleted";
- # Return the appropriate HTTP response headers.
- print $cgi->header();
+ my @flagtypes = Bugzilla::FlagType->get_all;
+ $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
+ $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes];
$template->process("admin/flag-type/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
+ exit;
}
-
-sub deactivate {
- my $token = shift;
+if ($action eq 'deactivate') {
check_token_data($token, 'delete_flagtype');
- my $flag_type = validateID();
- validateIsActive();
- my $dbh = Bugzilla->dbh;
+ my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_id);
+ ThrowUserError('flag_type_cannot_deactivate', { flagtype => $flagtype }) unless $can_fully_edit;
- $dbh->bz_start_transaction();
- $dbh->do('UPDATE flagtypes SET is_active = 0 WHERE id = ?', undef, $flag_type->id);
- $dbh->bz_commit_transaction();
+ $flagtype->set_is_active(0);
+ $flagtype->update();
+
+ delete_token($token);
$vars->{'message'} = "flag_type_deactivated";
- $vars->{'flag_type'} = $flag_type;
- delete_token($token);
+ $vars->{'flag_type'} = $flagtype;
- $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
- $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+ my @flagtypes = Bugzilla::FlagType->get_all;
+ $vars->{'bug_types'} = [grep { $_->target_type eq 'bug' } @flagtypes];
+ $vars->{'attachment_types'} = [grep { $_->target_type eq 'attachment' } @flagtypes];
- # Return the appropriate HTTP response headers.
- print $cgi->header();
-
- # Generate and return the UI (HTML page) from the appropriate template.
$template->process("admin/flag-type/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
+ exit;
}
-sub get_products_and_components {
- my $vars = shift;
+ThrowUserError('unknown_action', {action => $action});
- my @products = Bugzilla::Product->get_all;
+#####################
+# Helper subroutines
+#####################
+
+sub get_products_and_components {
+ my $vars = {};
+ my $user = Bugzilla->user;
+
+ my @products;
+ if ($user->in_group('editcomponents')) {
+ @products = Bugzilla::Product->get_all;
+ }
+ else {
+ @products = @{$user->get_products_by_permission('editcomponents')};
+ }
# We require all unique component names.
my %components;
foreach my $product (@products) {
@@ -534,171 +467,80 @@
return $vars;
}
-################################################################################
-# Data Validation / Security Authorization
-################################################################################
+sub get_editable_flagtypes {
+ my ($products, $group_id) = @_;
+ my $flagtypes;
-sub validateID {
- my $id = $cgi->param('id');
- my $flag_type = new Bugzilla::FlagType($id)
- || ThrowCodeError('flag_type_nonexistent', { id => $id });
-
- return $flag_type;
-}
-
-sub validateName {
- my $name = $cgi->param('name');
- ($name && $name !~ /[ ,]/ && length($name) <= 50)
- || ThrowUserError("flag_type_name_invalid",
- { name => $name });
- trick_taint($name);
- return $name;
-}
-
-sub validateDescription {
- my $description = $cgi->param('description');
- length($description) < 2**16-1
- || ThrowUserError("flag_type_description_invalid");
- trick_taint($description);
- return $description;
-}
-
-sub validateCCList {
- my $cc_list = $cgi->param('cc_list');
- length($cc_list) <= 200
- || ThrowUserError("flag_type_cc_list_invalid",
- { cc_list => $cc_list });
-
- my @addresses = split(/[, ]+/, $cc_list);
- # We do not call Util::validate_email_syntax because these
- # addresses do not require to match 'emailregexp' and do not
- # depend on 'emailsuffix'. So we limit ourselves to a simple
- # sanity check:
- # - match the syntax of a fully qualified email address;
- # - do not contain any illegal character.
- foreach my $address (@addresses) {
- ($address =~ /^[\w\.\+\-=]+@[\w\.\-]+\.[\w\-]+$/
- && $address !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/)
- || ThrowUserError('illegal_email_address',
- {addr => $address, default => 1});
+ if (Bugzilla->user->in_group('editcomponents')) {
+ $flagtypes = Bugzilla::FlagType::match({ group => $group_id });
+ return $flagtypes;
}
- trick_taint($cc_list);
- return $cc_list;
-}
-sub validateProduct {
- my $product_name = shift;
- return unless $product_name;
-
- my $product = Bugzilla::Product::check_product($product_name);
- return $product;
-}
-
-sub validateComponent {
- my ($product, $component_name) = @_;
- return unless $component_name;
-
- ($product && $product->id)
- || ThrowUserError("flag_type_component_without_product");
-
- my $component = Bugzilla::Component->check({ product => $product,
- name => $component_name });
- return $component;
-}
-
-sub validateSortKey {
- # $sortkey is destroyed if detaint_natural fails.
- my $sortkey = $cgi->param('sortkey');
- detaint_natural($sortkey)
- && $sortkey < 32768
- || ThrowUserError("flag_type_sortkey_invalid",
- { sortkey => scalar $cgi->param('sortkey') });
- $cgi->param('sortkey', $sortkey);
-}
-
-sub validateTargetType {
- grep($cgi->param('target_type') eq $_, ("bug", "attachment"))
- || ThrowCodeError("flag_type_target_type_invalid",
- { target_type => scalar $cgi->param('target_type') });
-}
-
-sub validateIsActive {
- $cgi->param('is_active', $cgi->param('is_active') ? 1 : 0);
-}
-
-sub validateIsRequestable {
- $cgi->param('is_requestable', $cgi->param('is_requestable') ? 1 : 0);
-}
-
-sub validateIsRequesteeble {
- $cgi->param('is_requesteeble', $cgi->param('is_requesteeble') ? 1 : 0);
-}
-
-sub validateAllowMultiple {
- $cgi->param('is_multiplicable', $cgi->param('is_multiplicable') ? 1 : 0);
-}
-
-sub validateGroups {
- my $dbh = Bugzilla->dbh;
- # Convert group names to group IDs
- foreach my $col ('grant', 'request') {
- my $name = $cgi->param($col . '_group');
- if ($name) {
- trick_taint($name);
- my $gid = $dbh->selectrow_array('SELECT id FROM groups
- WHERE name = ?', undef, $name);
- $gid || ThrowUserError("group_unknown", { name => $name });
- $cgi->param($col . '_gid', $gid);
+ my %visible_flagtypes;
+ foreach my $product (@$products) {
+ foreach my $target ('bug', 'attachment') {
+ my $prod_flagtypes = $product->flag_types->{$target};
+ $visible_flagtypes{$_->id} ||= $_ foreach @$prod_flagtypes;
}
}
+ @$flagtypes = sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name }
+ values %visible_flagtypes;
+ # Filter flag types if a group ID is given.
+ $flagtypes = filter_group($flagtypes, $group_id);
+ return $flagtypes;
}
-# At this point, values either come the DB itself or have been recently
-# added by the user and have passed all validation tests.
-# The only way to have invalid product/component combinations is to
-# hack the URL. So we silently ignore them, if any.
-sub validateAndSubmit {
- my ($id) = @_;
- my $dbh = Bugzilla->dbh;
-
- # Cache product objects.
- my %products;
- foreach my $category_type ("inclusions", "exclusions") {
- # Will be used several times below.
- my $sth = $dbh->prepare("INSERT INTO flag$category_type " .
- "(type_id, product_id, component_id) " .
- "VALUES (?, ?, ?)");
-
- $dbh->do("DELETE FROM flag$category_type WHERE type_id = ?", undef, $id);
- foreach my $category ($cgi->param($category_type)) {
- trick_taint($category);
- my ($product_id, $component_id) = split(":", $category);
- # Does the product exist?
- if ($product_id) {
- $products{$product_id} ||= new Bugzilla::Product($product_id);
- next unless defined $products{$product_id};
- }
- # A component was selected without a product being selected.
- next if (!$product_id && $component_id);
- # Does the component belong to this product?
- if ($component_id) {
- my @match = grep {$_->id == $component_id} @{$products{$product_id}->components};
- next unless scalar(@match);
- }
- $product_id ||= undef;
- $component_id ||= undef;
- $sth->execute($id, $product_id, $component_id);
- }
- }
+sub get_settable_groups {
+ my $user = Bugzilla->user;
+ my $groups = $user->in_group('editcomponents') ? [Bugzilla::Group->get_all] : $user->groups;
+ return $groups;
}
sub filter_group {
- my $flag_types = shift;
- return $flag_types unless Bugzilla->cgi->param('group');
+ my ($flag_types, $gid) = @_;
+ return $flag_types unless $gid;
- my $gid = scalar $cgi->param('group');
my @flag_types = grep {($_->grant_group && $_->grant_group->id == $gid)
|| ($_->request_group && $_->request_group->id == $gid)} @$flag_types;
return \@flag_types;
}
+
+# Convert the array @clusions('prod_ID:comp_ID') back to a hash of
+# the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
+sub clusion_array_to_hash {
+ my ($array, $visible_products) = @_;
+ my $user = Bugzilla->user;
+ my $has_privs = $user->in_group('editcomponents');
+
+ my %hash;
+ my %products;
+ my %components;
+
+ foreach my $ids (@$array) {
+ my ($product_id, $component_id) = split(":", $ids);
+ my $product_name = "__Any__";
+ my $component_name = "__Any__";
+
+ if ($product_id) {
+ ($products{$product_id}) = grep { $_->id == $product_id } @$visible_products;
+ next unless $products{$product_id};
+ $product_name = $products{$product_id}->name;
+
+ if ($component_id) {
+ ($components{$component_id}) =
+ grep { $_->id == $component_id } @{$products{$product_id}->components};
+ next unless $components{$component_id};
+ $component_name = $components{$component_id}->name;
+ }
+ }
+ else {
+ # Users with local editcomponents privs cannot use __Any__:__Any__.
+ next unless $has_privs;
+ # It's illegal to select a component without a product.
+ next if $component_id;
+ }
+ $hash{"$product_name:$component_name"} = $ids;
+ }
+ return \%hash;
+}
diff --git a/Websites/bugs.webkit.org/editgroups.cgi b/Websites/bugs.webkit.org/editgroups.cgi
index 06391a8..8e58f54 100755
--- a/Websites/bugs.webkit.org/editgroups.cgi
+++ b/Websites/bugs.webkit.org/editgroups.cgi
@@ -72,41 +72,6 @@
return $group_id;
}
-# This subroutine is called when:
-# - a new group is created. CheckGroupName checks that its name
-# is not empty and is not already used by any existing group.
-# - an existing group is edited. CheckGroupName checks that its
-# name has not been deleted or renamed to another existing
-# group name (whose group ID is different from $group_id).
-# In both cases, an error message is returned to the user if any
-# test fails! Else, the trimmed group name is returned.
-
-sub CheckGroupName {
- my ($name, $group_id) = @_;
- $name = trim($name || '');
- trick_taint($name);
- ThrowUserError("empty_group_name") unless $name;
- my $excludeself = (defined $group_id) ? " AND id != $group_id" : "";
- my $name_exists = Bugzilla->dbh->selectrow_array("SELECT name FROM groups " .
- "WHERE name = ? $excludeself",
- undef, $name);
- if ($name_exists) {
- ThrowUserError("group_exists", { name => $name });
- }
- return $name;
-}
-
-# CheckGroupDesc checks that a non empty description is given. The
-# trimmed description is returned.
-
-sub CheckGroupDesc {
- my ($desc) = @_;
- $desc = trim($desc || '');
- trick_taint($desc);
- ThrowUserError("empty_group_description") unless $desc;
- return $desc;
-}
-
# CheckGroupRegexp checks that the regular expression is valid
# (the regular expression being optional, the test is successful
# if none is given, as expected). The trimmed regular expression
@@ -148,16 +113,19 @@
if !grep($_->id == $group_option->id, @visible_to_me_current);
}
- # The group itself should never show up in the bless or
- # membership lists.
+ push(@bless_from_available, $group_option)
+ if !grep($_->id == $group_option->id, @bless_from_current);
+
+ # The group itself should never show up in the membership lists,
+ # and should show up in only one of the bless lists (otherwise
+ # you can try to allow it to bless itself twice, leading to a
+ # database unique constraint error).
next if $group_option->id == $group->id;
push(@members_available, $group_option)
if !grep($_->id == $group_option->id, @members_current);
push(@member_of_available, $group_option)
if !grep($_->id == $group_option->id, @member_of_current);
- push(@bless_from_available, $group_option)
- if !grep($_->id == $group_option->id, @bless_from_current);
push(@bless_to_available, $group_option)
if !grep($_->id == $group_option->id, @bless_to_current);
}
@@ -237,47 +205,22 @@
if ($action eq 'new') {
check_token_data($token, 'add_group');
- # Check that a not already used group name is given, that
- # a description is also given and check if the regular
- # expression is valid (if any).
- my $name = CheckGroupName($cgi->param('name'));
- my $desc = CheckGroupDesc($cgi->param('desc'));
- my $regexp = CheckGroupRegexp($cgi->param('regexp'));
- my $isactive = $cgi->param('isactive') ? 1 : 0;
- # This is an admin page. The URL is considered safe.
- my $icon_url;
- if ($cgi->param('icon_url')) {
- $icon_url = clean_text($cgi->param('icon_url'));
- trick_taint($icon_url);
- }
-
- # Add the new group
- $dbh->do('INSERT INTO groups
- (name, description, isbuggroup, userregexp, isactive, icon_url)
- VALUES (?, ?, 1, ?, ?, ?)',
- undef, ($name, $desc, $regexp, $isactive, $icon_url));
-
- my $group = new Bugzilla::Group({name => $name});
- my $admin = Bugzilla::Group->new({name => 'admin'})->id();
- # Since we created a new group, give the "admin" group all privileges
- # initially.
- my $sth = $dbh->prepare('INSERT INTO group_group_map
- (member_id, grantor_id, grant_type)
- VALUES (?, ?, ?)');
-
- $sth->execute($admin, $group->id, GROUP_MEMBERSHIP);
- $sth->execute($admin, $group->id, GROUP_BLESS);
- $sth->execute($admin, $group->id, GROUP_VISIBLE);
+ my $group = Bugzilla::Group->create({
+ name => scalar $cgi->param('name'),
+ description => scalar $cgi->param('desc'),
+ userregexp => scalar $cgi->param('regexp'),
+ isactive => scalar $cgi->param('isactive'),
+ icon_url => scalar $cgi->param('icon_url'),
+ isbuggroup => 1,
+ });
# Permit all existing products to use the new group if makeproductgroups.
if ($cgi->param('insertnew')) {
$dbh->do('INSERT INTO group_control_map
- (group_id, product_id, entry, membercontrol,
- othercontrol, canedit)
- SELECT ?, products.id, 0, ?, ?, 0 FROM products',
+ (group_id, product_id, membercontrol, othercontrol)
+ SELECT ?, products.id, ?, ? FROM products',
undef, ($group->id, CONTROLMAPSHOWN, CONTROLMAPNA));
}
- Bugzilla::Group::RederiveRegexp($regexp, $group->id);
delete_token($token);
$vars->{'message'} = 'group_created';
@@ -299,64 +242,15 @@
if ($action eq 'del') {
# Check that an existing group ID is given
- my $gid = CheckGroupID($cgi->param('group'));
- my ($name, $desc, $isbuggroup) =
- $dbh->selectrow_array("SELECT name, description, isbuggroup " .
- "FROM groups WHERE id = ?", undef, $gid);
-
- # System groups cannot be deleted!
- if (!$isbuggroup) {
- ThrowUserError("system_group_not_deletable", { name => $name });
- }
- # Groups having a special role cannot be deleted.
- my @special_groups;
- foreach my $special_group (SPECIAL_GROUPS) {
- if ($name eq Bugzilla->params->{$special_group}) {
- push(@special_groups, $special_group);
- }
- }
- if (scalar(@special_groups)) {
- ThrowUserError('group_has_special_role', {'name' => $name,
- 'groups' => \@special_groups});
- }
-
- # Group inheritance no longer appears in user_group_map.
- my $grouplist = join(',', @{Bugzilla::User->flatten_group_membership($gid)});
- my $hasusers =
- $dbh->selectrow_array("SELECT 1 FROM user_group_map
- WHERE group_id IN ($grouplist) AND isbless = 0 " .
- $dbh->sql_limit(1)) || 0;
-
- my ($shared_queries) =
+ my $group = Bugzilla::Group->check({ id => $cgi->param('group') });
+ $group->check_remove({ test_only => 1 });
+ $vars->{'shared_queries'} =
$dbh->selectrow_array('SELECT COUNT(*)
FROM namedquery_group_map
- WHERE group_id = ?',
- undef, $gid);
+ WHERE group_id = ?', undef, $group->id);
- my $bug_ids = $dbh->selectcol_arrayref('SELECT bug_id FROM bug_group_map
- WHERE group_id = ?', undef, $gid);
-
- my $hasbugs = scalar(@$bug_ids) ? 1 : 0;
- my $buglist = join(',', @$bug_ids);
-
- my $hasproduct = Bugzilla::Product->new({'name' => $name}) ? 1 : 0;
-
- my $hasflags = $dbh->selectrow_array('SELECT 1 FROM flagtypes
- WHERE grant_group_id = ?
- OR request_group_id = ? ' .
- $dbh->sql_limit(1),
- undef, ($gid, $gid)) || 0;
-
- $vars->{'gid'} = $gid;
- $vars->{'name'} = $name;
- $vars->{'description'} = $desc;
- $vars->{'hasusers'} = $hasusers;
- $vars->{'hasbugs'} = $hasbugs;
- $vars->{'hasproduct'} = $hasproduct;
- $vars->{'hasflags'} = $hasflags;
- $vars->{'shared_queries'} = $shared_queries;
- $vars->{'buglist'} = $buglist;
- $vars->{'token'} = issue_session_token('delete_group');
+ $vars->{'group'} = $group;
+ $vars->{'token'} = issue_session_token('delete_group');
print $cgi->header();
$template->process("admin/groups/delete.html.tmpl", $vars)
@@ -372,91 +266,14 @@
if ($action eq 'delete') {
check_token_data($token, 'delete_group');
# Check that an existing group ID is given
- my $gid = CheckGroupID($cgi->param('group'));
- my ($name, $isbuggroup) =
- $dbh->selectrow_array("SELECT name, isbuggroup FROM groups " .
- "WHERE id = ?", undef, $gid);
-
- # System groups cannot be deleted!
- if (!$isbuggroup) {
- ThrowUserError("system_group_not_deletable", { name => $name });
- }
- # Groups having a special role cannot be deleted.
- my @special_groups;
- foreach my $special_group (SPECIAL_GROUPS) {
- if ($name eq Bugzilla->params->{$special_group}) {
- push(@special_groups, $special_group);
- }
- }
- if (scalar(@special_groups)) {
- ThrowUserError('group_has_special_role', {'name' => $name,
- 'groups' => \@special_groups});
- }
-
- my $cantdelete = 0;
-
- # Group inheritance no longer appears in user_group_map.
- my $grouplist = join(',', @{Bugzilla::User->flatten_group_membership($gid)});
- my $hasusers =
- $dbh->selectrow_array("SELECT 1 FROM user_group_map
- WHERE group_id IN ($grouplist) AND isbless = 0 " .
- $dbh->sql_limit(1)) || 0;
-
- if ($hasusers && !defined $cgi->param('removeusers')) {
- $cantdelete = 1;
- }
-
- my $hasbugs = $dbh->selectrow_array('SELECT 1 FROM bug_group_map
- WHERE group_id = ? ' .
- $dbh->sql_limit(1),
- undef, $gid) || 0;
- if ($hasbugs && !defined $cgi->param('removebugs')) {
- $cantdelete = 1;
- }
-
- if (Bugzilla::Product->new({'name' => $name})
- && !defined $cgi->param('unbind'))
- {
- $cantdelete = 1;
- }
-
- my $hasflags = $dbh->selectrow_array('SELECT 1 FROM flagtypes
- WHERE grant_group_id = ?
- OR request_group_id = ? ' .
- $dbh->sql_limit(1),
- undef, ($gid, $gid)) || 0;
- if ($hasflags && !defined $cgi->param('removeflags')) {
- $cantdelete = 1;
- }
-
- $vars->{'gid'} = $gid;
- $vars->{'name'} = $name;
-
- ThrowUserError('group_cannot_delete', $vars) if $cantdelete;
-
- $dbh->do('UPDATE flagtypes SET grant_group_id = ?
- WHERE grant_group_id = ?',
- undef, (undef, $gid));
- $dbh->do('UPDATE flagtypes SET request_group_id = ?
- WHERE request_group_id = ?',
- undef, (undef, $gid));
- $dbh->do('DELETE FROM namedquery_group_map WHERE group_id = ?',
- undef, $gid);
- $dbh->do('DELETE FROM user_group_map WHERE group_id = ?',
- undef, $gid);
- $dbh->do('DELETE FROM group_group_map
- WHERE grantor_id = ? OR member_id = ?',
- undef, ($gid, $gid));
- $dbh->do('DELETE FROM bug_group_map WHERE group_id = ?',
- undef, $gid);
- $dbh->do('DELETE FROM group_control_map WHERE group_id = ?',
- undef, $gid);
- $dbh->do('DELETE FROM whine_schedules
- WHERE mailto_type = ? AND mailto = ?',
- undef, (MAILTO_GROUP, $gid));
- $dbh->do('DELETE FROM groups WHERE id = ?',
- undef, $gid);
-
+ my $group = Bugzilla::Group->check({ id => $cgi->param('group') });
+ $vars->{'name'} = $group->name;
+ $group->remove_from_db({
+ remove_from_users => scalar $cgi->param('removeusers'),
+ remove_from_bugs => scalar $cgi->param('removebugs'),
+ remove_from_flags => scalar $cgi->param('removeflags'),
+ remove_from_products => scalar $cgi->param('unbind'),
+ });
delete_token($token);
$vars->{'message'} = 'group_deleted';
@@ -540,13 +357,8 @@
exit;
}
-
-#
# No valid action found
-#
-
-ThrowCodeError("action_unrecognized", $vars);
-
+ThrowUserError('unknown_action', {action => $action});
# Helper sub to handle the making of changes to a group
sub doGroupChanges {
diff --git a/Websites/bugs.webkit.org/editkeywords.cgi b/Websites/bugs.webkit.org/editkeywords.cgi
index 5eabaed..3cd6c94 100755
--- a/Websites/bugs.webkit.org/editkeywords.cgi
+++ b/Websites/bugs.webkit.org/editkeywords.cgi
@@ -131,8 +131,10 @@
my $keyword = new Bugzilla::Keyword($key_id)
|| ThrowCodeError('invalid_keyword_id', { id => $key_id });
- $keyword->set_name($cgi->param('name'));
- $keyword->set_description($cgi->param('description'));
+ $keyword->set_all({
+ name => scalar $cgi->param('name'),
+ description => scalar $cgi->param('description'),
+ });
my $changes = $keyword->update();
delete_token($token);
@@ -167,14 +169,14 @@
my $keyword = new Bugzilla::Keyword($key_id)
|| ThrowCodeError('invalid_keyword_id', { id => $key_id });
- $dbh->do('DELETE FROM keywords WHERE keywordid = ?', undef, $keyword->id);
- $dbh->do('DELETE FROM keyworddefs WHERE id = ?', undef, $keyword->id);
+ $keyword->remove_from_db();
delete_token($token);
print $cgi->header();
$vars->{'message'} = 'keyword_deleted';
+ $vars->{'keyword'} = $keyword;
$vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
$template->process("admin/keywords/list.html.tmpl", $vars)
@@ -182,4 +184,4 @@
exit;
}
-ThrowCodeError("action_unrecognized", $vars);
+ThrowUserError('unknown_action', {action => $action});
diff --git a/Websites/bugs.webkit.org/editmilestones.cgi b/Websites/bugs.webkit.org/editmilestones.cgi
index 7b1e7cd..6f47a70 100755
--- a/Websites/bugs.webkit.org/editmilestones.cgi
+++ b/Websites/bugs.webkit.org/editmilestones.cgi
@@ -60,6 +60,7 @@
my $action = trim($cgi->param('action') || '');
my $showbugcounts = (defined $cgi->param('showbugcounts'));
my $token = $cgi->param('token');
+my $isactive = $cgi->param('isactive');
#
# product = '' -> Show nice list of products
@@ -115,9 +116,11 @@
if ($action eq 'new') {
check_token_data($token, 'add_milestone');
- my $milestone = Bugzilla::Milestone->create({ name => $milestone_name,
- product => $product,
- sortkey => $sortkey });
+
+ my $milestone = Bugzilla::Milestone->create({ value => $milestone_name,
+ product => $product,
+ sortkey => $sortkey });
+
delete_token($token);
$vars->{'message'} = 'milestone_created';
@@ -205,7 +208,11 @@
$milestone->set_name($milestone_name);
$milestone->set_sortkey($sortkey);
+ $milestone->set_is_active($isactive);
my $changes = $milestone->update();
+ # Reloading the product since the default milestone name
+ # could have been changed.
+ $product = new Bugzilla::Product({ name => $product_name });
delete_token($token);
@@ -218,7 +225,5 @@
exit;
}
-#
# No valid action found
-#
-ThrowUserError('no_valid_action', {'field' => "target_milestone"});
+ThrowUserError('unknown_action', {action => $action});
diff --git a/Websites/bugs.webkit.org/editparams.cgi b/Websites/bugs.webkit.org/editparams.cgi
index d16c90b..8e623e5 100755
--- a/Websites/bugs.webkit.org/editparams.cgi
+++ b/Websites/bugs.webkit.org/editparams.cgi
@@ -67,22 +67,28 @@
param_list => \@module_param_list,
sortkey => eval "\$${module}::sortkey;"
};
+ defined($item->{'sortkey'}) || ($item->{'sortkey'} = 100000);
push(@panels, $item);
$current_module = $panel if ($current_panel eq lc($panel));
}
+my %hook_panels = map { $_->{name} => { params => $_->{param_list} } }
+ @panels;
+# Note that this hook is also called in Bugzilla::Config.
+Bugzilla::Hook::process('config_modify_panels', { panels => \%hook_panels });
+
$vars->{panels} = \@panels;
if ($action eq 'save' && $current_module) {
check_token_data($token, 'edit_parameters');
my @changes = ();
- my @module_param_list = "$param_panels->{$current_module}"->get_param_list();
+ my @module_param_list = @{ $hook_panels{lc($current_module)}->{params} };
foreach my $i (@module_param_list) {
my $name = $i->{'name'};
my $value = $cgi->param($name);
- if (defined $cgi->param("reset-$name")) {
+ if (defined $cgi->param("reset-$name") && !$i->{'no_reset'}) {
$value = $i->{'default'};
} else {
if ($i->{'type'} eq 'm') {
@@ -94,6 +100,11 @@
# assume single linefeed is an empty string
$value =~ s/^\n$//;
}
+ # Stop complaining if the URL has no trailing slash.
+ # XXX - This hack can go away once bug 303662 is implemented.
+ if ($name =~ /(?<!webdot)base$/) {
+ $value = "$value/" if ($value && $value !~ m#/$#);
+ }
}
my $changed;
diff --git a/Websites/bugs.webkit.org/editproducts.cgi b/Websites/bugs.webkit.org/editproducts.cgi
index e430763..9499fee 100755
--- a/Websites/bugs.webkit.org/editproducts.cgi
+++ b/Websites/bugs.webkit.org/editproducts.cgi
@@ -35,17 +35,10 @@
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Error;
-use Bugzilla::Bug;
-use Bugzilla::Series;
-use Bugzilla::Mailer;
+use Bugzilla::Group;
use Bugzilla::Product;
use Bugzilla::Classification;
-use Bugzilla::Milestone;
-use Bugzilla::Group;
-use Bugzilla::User;
-use Bugzilla::Field;
use Bugzilla::Token;
-use Bugzilla::Status;
#
# Preliminary checks:
@@ -76,7 +69,6 @@
my $classification_name = trim($cgi->param('classification') || '');
my $product_name = trim($cgi->param('product') || '');
my $action = trim($cgi->param('action') || '');
-my $showbugcounts = (defined $cgi->param('showbugcounts'));
my $token = $cgi->param('token');
#
@@ -88,8 +80,18 @@
&& !$classification_name
&& !$product_name)
{
- $vars->{'classifications'} = $user->in_group('editcomponents') ?
- [Bugzilla::Classification::get_all_classifications] : $user->get_selectable_classifications;
+ my $class;
+ if ($user->in_group('editcomponents')) {
+ $class = [Bugzilla::Classification->get_all];
+ }
+ else {
+ # Only keep classifications containing at least one product
+ # which you can administer.
+ my $products = $user->get_products_by_permission('editcomponents');
+ my %class_ids = map { $_->classification_id => 1 } @$products;
+ $class = Bugzilla::Classification->new_from_list([keys %class_ids]);
+ }
+ $vars->{'classifications'} = $class;
$template->process("admin/products/list-classifications.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
@@ -107,9 +109,7 @@
my $products;
if (Bugzilla->params->{'useclassification'}) {
- $classification =
- Bugzilla::Classification::check_classification($classification_name);
-
+ $classification = Bugzilla::Classification->check($classification_name);
$products = $user->get_selectable_products($classification->id);
$vars->{'classification'} = $classification;
} else {
@@ -125,7 +125,7 @@
}
}
$vars->{'products'} = $products;
- $vars->{'showbugcounts'} = $showbugcounts;
+ $vars->{'showbugcounts'} = $cgi->param('showbugcounts') ? 1 : 0;
$template->process("admin/products/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
@@ -150,8 +150,7 @@
object => "products"});
if (Bugzilla->params->{'useclassification'}) {
- my $classification =
- Bugzilla::Classification::check_classification($classification_name);
+ my $classification = Bugzilla::Classification->check($classification_name);
$vars->{'classification'} = $classification;
}
$vars->{'token'} = issue_session_token('add_product');
@@ -176,171 +175,26 @@
object => "products"});
check_token_data($token, 'add_product');
- # Cleanups and validity checks
- my $classification_id = 1;
- if (Bugzilla->params->{'useclassification'}) {
- my $classification =
- Bugzilla::Classification::check_classification($classification_name);
- $classification_id = $classification->id;
- $vars->{'classification'} = $classification;
- }
+ my %create_params = (
+ classification => $classification_name,
+ name => $product_name,
+ description => scalar $cgi->param('description'),
+ version => scalar $cgi->param('version'),
+ defaultmilestone => scalar $cgi->param('defaultmilestone'),
+ isactive => scalar $cgi->param('is_active'),
+ create_series => scalar $cgi->param('createseries'),
+ allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'),
+ );
+ my $product = Bugzilla::Product->create(\%create_params);
- unless ($product_name) {
- ThrowUserError("product_blank_name");
- }
-
- my $product = new Bugzilla::Product({name => $product_name});
-
- if ($product) {
-
- # Check for exact case sensitive match:
- if ($product->name eq $product_name) {
- ThrowUserError("product_name_already_in_use",
- {'product' => $product->name});
- }
-
- # Next check for a case-insensitive match:
- if (lc($product->name) eq lc($product_name)) {
- ThrowUserError("product_name_diff_in_case",
- {'product' => $product_name,
- 'existing_product' => $product->name});
- }
- }
-
- my $version = trim($cgi->param('version') || '');
-
- if ($version eq '') {
- ThrowUserError("product_must_have_version",
- {'product' => $product_name});
- }
-
- my $description = trim($cgi->param('description') || '');
-
- if ($description eq '') {
- ThrowUserError('product_must_have_description',
- {'product' => $product_name});
- }
-
- my $milestoneurl = trim($cgi->param('milestoneurl') || '');
- my $disallownew = $cgi->param('disallownew') ? 1 : 0;
- my $votesperuser = $cgi->param('votesperuser') || 0;
- my $maxvotesperbug = defined($cgi->param('maxvotesperbug')) ?
- $cgi->param('maxvotesperbug') : 10000;
- my $votestoconfirm = $cgi->param('votestoconfirm') || 0;
- my $defaultmilestone = $cgi->param('defaultmilestone') || "---";
-
- # The following variables are used in placeholders only.
- trick_taint($product_name);
- trick_taint($version);
- trick_taint($description);
- trick_taint($milestoneurl);
- trick_taint($defaultmilestone);
- detaint_natural($disallownew);
- detaint_natural($votesperuser);
- detaint_natural($maxvotesperbug);
- detaint_natural($votestoconfirm);
-
- # Add the new product.
- $dbh->do('INSERT INTO products
- (name, description, milestoneurl, disallownew, votesperuser,
- maxvotesperbug, votestoconfirm, defaultmilestone, classification_id)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
- undef, ($product_name, $description, $milestoneurl, $disallownew,
- $votesperuser, $maxvotesperbug, $votestoconfirm, $defaultmilestone,
- $classification_id));
-
- $product = new Bugzilla::Product({name => $product_name});
-
- $dbh->do('INSERT INTO versions (value, product_id) VALUES (?, ?)',
- undef, ($version, $product->id));
-
- $dbh->do('INSERT INTO milestones (product_id, value) VALUES (?, ?)',
- undef, ($product->id, $defaultmilestone));
-
- # If we're using bug groups, then we need to create a group for this
- # product as well. -JMR, 2/16/00
- if (Bugzilla->params->{"makeproductgroups"}) {
- # Next we insert into the groups table
- my $productgroup = $product->name;
- while (new Bugzilla::Group({name => $productgroup})) {
- $productgroup .= '_';
- }
- my $group_description = "Access to bugs in the " .
- $product->name . " product";
-
- $dbh->do('INSERT INTO groups (name, description, isbuggroup)
- VALUES (?, ?, ?)',
- undef, ($productgroup, $group_description, 1));
-
- my $gid = $dbh->bz_last_key('groups', 'id');
-
- # If we created a new group, give the "admin" group privileges
- # initially.
- my $admin = Bugzilla::Group->new({name => 'admin'})->id();
-
- my $sth = $dbh->prepare('INSERT INTO group_group_map
- (member_id, grantor_id, grant_type)
- VALUES (?, ?, ?)');
-
- $sth->execute($admin, $gid, GROUP_MEMBERSHIP);
- $sth->execute($admin, $gid, GROUP_BLESS);
- $sth->execute($admin, $gid, GROUP_VISIBLE);
-
- # Associate the new group and new product.
- $dbh->do('INSERT INTO group_control_map
- (group_id, product_id, entry, membercontrol,
- othercontrol, canedit)
- VALUES (?, ?, ?, ?, ?, ?)',
- undef, ($gid, $product->id,
- Bugzilla->params->{'useentrygroupdefault'},
- CONTROLMAPDEFAULT, CONTROLMAPNA, 0));
- }
-
- if ($cgi->param('createseries')) {
- # Insert default charting queries for this product.
- # If they aren't using charting, this won't do any harm.
- #
- # $open_name and $product are sqlquoted by the series code
- # and never used again here, so we can trick_taint them.
- my $open_name = $cgi->param('open_name');
- trick_taint($open_name);
-
- my @series;
-
- # We do every status, every resolution, and an "opened" one as well.
- foreach my $bug_status (@{get_legal_field_values('bug_status')}) {
- push(@series, [$bug_status,
- "bug_status=" . url_quote($bug_status)]);
- }
-
- foreach my $resolution (@{get_legal_field_values('resolution')}) {
- next if !$resolution;
- push(@series, [$resolution, "resolution=" .url_quote($resolution)]);
- }
-
- # For localization reasons, we get the name of the "global" subcategory
- # and the title of the "open" query from the submitted form.
- my @openedstatuses = BUG_STATE_OPEN;
- my $query =
- join("&", map { "bug_status=" . url_quote($_) } @openedstatuses);
- push(@series, [$open_name, $query]);
-
- foreach my $sdata (@series) {
- my $series = new Bugzilla::Series(undef, $product->name,
- scalar $cgi->param('subcategory'),
- $sdata->[0], $whoid, 1,
- $sdata->[1] . "&product=" .
- url_quote($product->name), 1);
- $series->writeToDatabase();
- }
- }
delete_token($token);
$vars->{'message'} = 'product_created';
$vars->{'product'} = $product;
- $vars->{'classification'} = new Bugzilla::Classification($product->classification_id)
- if Bugzilla->params->{'useclassification'};
+ if (Bugzilla->params->{'useclassification'}) {
+ $vars->{'classification'} = new Bugzilla::Classification($product->classification_id);
+ }
$vars->{'token'} = issue_session_token('edit_product');
$template->process("admin/products/edit.html.tmpl", $vars)
@@ -358,20 +212,12 @@
my $product = $user->check_can_admin_product($product_name);
if (Bugzilla->params->{'useclassification'}) {
- my $classification =
- Bugzilla::Classification::check_classification($classification_name);
- if ($classification->id != $product->classification_id) {
- ThrowUserError('classification_doesnt_exist_for_product',
- { product => $product->name,
- classification => $classification->name });
- }
- $vars->{'classification'} = $classification;
+ $vars->{'classification'} = new Bugzilla::Classification($product->classification_id);
}
-
$vars->{'product'} = $product;
$vars->{'token'} = issue_session_token('delete_product');
- Bugzilla::Hook::process("product-confirm_delete", { vars => $vars });
+ Bugzilla::Hook::process('product_confirm_delete', { vars => $vars });
$template->process("admin/products/confirm-delete.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
@@ -386,69 +232,7 @@
my $product = $user->check_can_admin_product($product_name);
check_token_data($token, 'delete_product');
- if (Bugzilla->params->{'useclassification'}) {
- my $classification =
- Bugzilla::Classification::check_classification($classification_name);
- if ($classification->id != $product->classification_id) {
- ThrowUserError('classification_doesnt_exist_for_product',
- { product => $product->name,
- classification => $classification->name });
- }
- $vars->{'classification'} = $classification;
- }
-
- if ($product->bug_count) {
- if (Bugzilla->params->{"allowbugdeletion"}) {
- foreach my $bug_id (@{$product->bug_ids}) {
- # Note that we allow the user to delete bugs he can't see,
- # which is okay, because he's deleting the whole Product.
- my $bug = new Bugzilla::Bug($bug_id);
- $bug->remove_from_db();
- }
- }
- else {
- ThrowUserError("product_has_bugs",
- { nb => $product->bug_count });
- }
- }
-
- $dbh->bz_start_transaction();
-
- my $comp_ids = $dbh->selectcol_arrayref('SELECT id FROM components
- WHERE product_id = ?',
- undef, $product->id);
-
- $dbh->do('DELETE FROM component_cc WHERE component_id IN
- (' . join(',', @$comp_ids) . ')') if scalar(@$comp_ids);
-
- $dbh->do("DELETE FROM components WHERE product_id = ?",
- undef, $product->id);
-
- $dbh->do("DELETE FROM versions WHERE product_id = ?",
- undef, $product->id);
-
- $dbh->do("DELETE FROM milestones WHERE product_id = ?",
- undef, $product->id);
-
- $dbh->do("DELETE FROM group_control_map WHERE product_id = ?",
- undef, $product->id);
-
- $dbh->do("DELETE FROM flaginclusions WHERE product_id = ?",
- undef, $product->id);
-
- $dbh->do("DELETE FROM flagexclusions WHERE product_id = ?",
- undef, $product->id);
-
- $dbh->do("DELETE FROM products WHERE id = ?",
- undef, $product->id);
-
- $dbh->bz_commit_transaction();
-
- # We have to delete these internal variables, else we get
- # the old lists of products and classifications again.
- delete $user->{selectable_products};
- delete $user->{selectable_classifications};
-
+ $product->remove_from_db({ delete_series => scalar $cgi->param('delete_series')});
delete_token($token);
$vars->{'message'} = 'product_deleted';
@@ -457,7 +241,7 @@
if (Bugzilla->params->{'useclassification'}) {
$vars->{'classifications'} = $user->in_group('editcomponents') ?
- [Bugzilla::Classification::get_all_classifications] : $user->get_selectable_classifications;
+ [Bugzilla::Classification->get_all] : $user->get_selectable_classifications;
$template->process("admin/products/list-classifications.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
@@ -488,20 +272,7 @@
my $product = $user->check_can_admin_product($product_name);
if (Bugzilla->params->{'useclassification'}) {
- my $classification;
- if (!$classification_name) {
- $classification =
- new Bugzilla::Classification($product->classification_id);
- } else {
- $classification =
- Bugzilla::Classification::check_classification($classification_name);
- if ($classification->id != $product->classification_id) {
- ThrowUserError('classification_doesnt_exist_for_product',
- { product => $product->name,
- classification => $classification->name });
- }
- }
- $vars->{'classification'} = $classification;
+ $vars->{'classification'} = new Bugzilla::Classification($product->classification_id);
}
$vars->{'product'} = $product;
$vars->{'token'} = issue_session_token('edit_product');
@@ -512,7 +283,53 @@
}
#
-# action='updategroupcontrols' -> update the product
+# action='update' -> update the product
+#
+if ($action eq 'update') {
+ check_token_data($token, 'edit_product');
+ my $product_old_name = trim($cgi->param('product_old_name') || '');
+ my $product = $user->check_can_admin_product($product_old_name);
+
+ $product->set_all({
+ name => $product_name,
+ description => scalar $cgi->param('description'),
+ is_active => scalar $cgi->param('is_active'),
+ allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'),
+ default_milestone => scalar $cgi->param('defaultmilestone'),
+ });
+
+ my $changes = $product->update();
+
+ delete_token($token);
+
+ if (Bugzilla->params->{'useclassification'}) {
+ $vars->{'classification'} = new Bugzilla::Classification($product->classification_id);
+ }
+ $vars->{'product'} = $product;
+ $vars->{'changes'} = $changes;
+
+ $template->process("admin/products/updated.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+#
+# action='editgroupcontrols' -> display product group controls
+#
+
+if ($action eq 'editgroupcontrols') {
+ my $product = $user->check_can_admin_product($product_name);
+
+ $vars->{'product'} = $product;
+ $vars->{'token'} = issue_session_token('edit_group_controls');
+
+ $template->process("admin/products/groupcontrol/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+}
+
+#
+# action='updategroupcontrols' -> update product group controls
#
if ($action eq 'updategroupcontrols') {
@@ -547,10 +364,9 @@
{'Slice' => {}}, $product->id);
}
-#
-# return the mandatory groups which need to have bug entries added to the bug_group_map
-# and the corresponding bug count
-#
+ # return the mandatory groups which need to have bug entries
+ # added to the bug_group_map and the corresponding bug count
+
my $mandatory_groups;
if (@now_mandatory) {
$mandatory_groups = $dbh->selectall_arrayref(
@@ -578,516 +394,33 @@
$vars->{'mandatory_groups'} = $mandatory_groups;
$template->process("admin/products/groupcontrol/confirm-edit.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
- exit;
+ exit;
}
}
- my $groups = $dbh->selectall_arrayref('SELECT id, name FROM groups
- WHERE isbuggroup != 0
- AND isactive != 0');
+ my $groups = Bugzilla::Group->match({isactive => 1, isbuggroup => 1});
foreach my $group (@$groups) {
- my ($groupid, $groupname) = @$group;
- my $newmembercontrol = $cgi->param("membercontrol_$groupid") || 0;
- my $newothercontrol = $cgi->param("othercontrol_$groupid") || 0;
- # Legality of control combination is a function of
- # membercontrol\othercontrol
- # NA SH DE MA
- # NA + - - -
- # SH + + + +
- # DE + - + +
- # MA - - - +
- unless (($newmembercontrol == $newothercontrol)
- || ($newmembercontrol == CONTROLMAPSHOWN)
- || (($newmembercontrol == CONTROLMAPDEFAULT)
- && ($newothercontrol != CONTROLMAPSHOWN))) {
- ThrowUserError('illegal_group_control_combination',
- {groupname => $groupname});
- }
+ my $group_id = $group->id;
+ $product->set_group_controls($group,
+ {entry => scalar $cgi->param("entry_$group_id") || 0,
+ membercontrol => scalar $cgi->param("membercontrol_$group_id") || CONTROLMAPNA,
+ othercontrol => scalar $cgi->param("othercontrol_$group_id") || CONTROLMAPNA,
+ canedit => scalar $cgi->param("canedit_$group_id") || 0,
+ editcomponents => scalar $cgi->param("editcomponents_$group_id") || 0,
+ editbugs => scalar $cgi->param("editbugs_$group_id") || 0,
+ canconfirm => scalar $cgi->param("canconfirm_$group_id") || 0});
}
- $dbh->bz_start_transaction();
-
- my $sth_Insert = $dbh->prepare('INSERT INTO group_control_map
- (group_id, product_id, entry, membercontrol,
- othercontrol, canedit, editcomponents,
- canconfirm, editbugs)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
-
- my $sth_Update = $dbh->prepare('UPDATE group_control_map
- SET entry = ?, membercontrol = ?,
- othercontrol = ?, canedit = ?,
- editcomponents = ?, canconfirm = ?,
- editbugs = ?
- WHERE group_id = ? AND product_id = ?');
-
- my $sth_Delete = $dbh->prepare('DELETE FROM group_control_map
- WHERE group_id = ? AND product_id = ?');
-
- $groups = $dbh->selectall_arrayref('SELECT id, name, entry, membercontrol,
- othercontrol, canedit,
- editcomponents, canconfirm, editbugs
- FROM groups
- LEFT JOIN group_control_map
- ON group_control_map.group_id = id
- AND product_id = ?
- WHERE isbuggroup != 0
- AND isactive != 0',
- undef, $product->id);
-
- foreach my $group (@$groups) {
- my ($groupid, $groupname, $entry, $membercontrol, $othercontrol,
- $canedit, $editcomponents, $canconfirm, $editbugs) = @$group;
- my $newentry = $cgi->param("entry_$groupid") || 0;
- my $newmembercontrol = $cgi->param("membercontrol_$groupid") || 0;
- my $newothercontrol = $cgi->param("othercontrol_$groupid") || 0;
- my $newcanedit = $cgi->param("canedit_$groupid") || 0;
- my $new_editcomponents = $cgi->param("editcomponents_$groupid") || 0;
- my $new_canconfirm = $cgi->param("canconfirm_$groupid") || 0;
- my $new_editbugs = $cgi->param("editbugs_$groupid") || 0;
-
- my $oldentry = $entry;
- # Set undefined values to 0.
- $entry ||= 0;
- $membercontrol ||= 0;
- $othercontrol ||= 0;
- $canedit ||= 0;
- $editcomponents ||= 0;
- $canconfirm ||= 0;
- $editbugs ||= 0;
-
- # We use them in placeholders only. So it's safe to detaint them.
- detaint_natural($newentry);
- detaint_natural($newothercontrol);
- detaint_natural($newmembercontrol);
- detaint_natural($newcanedit);
- detaint_natural($new_editcomponents);
- detaint_natural($new_canconfirm);
- detaint_natural($new_editbugs);
-
- if (!defined($oldentry)
- && ($newentry || $newmembercontrol || $newcanedit
- || $new_editcomponents || $new_canconfirm || $new_editbugs))
- {
- $sth_Insert->execute($groupid, $product->id, $newentry,
- $newmembercontrol, $newothercontrol, $newcanedit,
- $new_editcomponents, $new_canconfirm, $new_editbugs);
- }
- elsif (($newentry != $entry)
- || ($newmembercontrol != $membercontrol)
- || ($newothercontrol != $othercontrol)
- || ($newcanedit != $canedit)
- || ($new_editcomponents != $editcomponents)
- || ($new_canconfirm != $canconfirm)
- || ($new_editbugs != $editbugs))
- {
- $sth_Update->execute($newentry, $newmembercontrol, $newothercontrol,
- $newcanedit, $new_editcomponents, $new_canconfirm,
- $new_editbugs, $groupid, $product->id);
- }
-
- if (!$newentry && !$newmembercontrol && !$newothercontrol
- && !$newcanedit && !$new_editcomponents && !$new_canconfirm
- && !$new_editbugs)
- {
- $sth_Delete->execute($groupid, $product->id);
- }
- }
-
- my $sth_Select = $dbh->prepare(
- 'SELECT bugs.bug_id,
- CASE WHEN (lastdiffed >= delta_ts) THEN 1 ELSE 0 END
- FROM bugs
- INNER JOIN bug_group_map
- ON bug_group_map.bug_id = bugs.bug_id
- WHERE group_id = ?
- AND bugs.product_id = ?
- ORDER BY bugs.bug_id');
-
- my $sth_Select2 = $dbh->prepare('SELECT name, NOW() FROM groups WHERE id = ?');
-
- $sth_Update = $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
-
- my $sth_Update2 = $dbh->prepare('UPDATE bugs SET delta_ts = ?, lastdiffed = ?
- WHERE bug_id = ?');
-
- $sth_Delete = $dbh->prepare('DELETE FROM bug_group_map
- WHERE bug_id = ? AND group_id = ?');
-
- my @removed_na;
- foreach my $groupid (@now_na) {
- my $count = 0;
- my $bugs = $dbh->selectall_arrayref($sth_Select, undef,
- ($groupid, $product->id));
-
- my ($removed, $timestamp) =
- $dbh->selectrow_array($sth_Select2, undef, $groupid);
-
- foreach my $bug (@$bugs) {
- my ($bugid, $mailiscurrent) = @$bug;
- $sth_Delete->execute($bugid, $groupid);
-
- LogActivityEntry($bugid, "bug_group", $removed, "",
- $whoid, $timestamp);
-
- if ($mailiscurrent) {
- $sth_Update2->execute($timestamp, $timestamp, $bugid);
- }
- else {
- $sth_Update->execute($timestamp, $bugid);
- }
- $count++;
- }
- my %group = (name => $removed, bug_count => $count);
-
- push(@removed_na, \%group);
- }
-
- $sth_Select = $dbh->prepare(
- 'SELECT bugs.bug_id,
- CASE WHEN (lastdiffed >= delta_ts) THEN 1 ELSE 0 END
- FROM bugs
- LEFT JOIN bug_group_map
- ON bug_group_map.bug_id = bugs.bug_id
- AND group_id = ?
- WHERE bugs.product_id = ?
- AND bug_group_map.bug_id IS NULL
- ORDER BY bugs.bug_id');
-
- $sth_Insert = $dbh->prepare('INSERT INTO bug_group_map
- (bug_id, group_id) VALUES (?, ?)');
-
- my @added_mandatory;
- foreach my $groupid (@now_mandatory) {
- my $count = 0;
- my $bugs = $dbh->selectall_arrayref($sth_Select, undef,
- ($groupid, $product->id));
-
- my ($added, $timestamp) =
- $dbh->selectrow_array($sth_Select2, undef, $groupid);
-
- foreach my $bug (@$bugs) {
- my ($bugid, $mailiscurrent) = @$bug;
- $sth_Insert->execute($bugid, $groupid);
-
- LogActivityEntry($bugid, "bug_group", "", $added,
- $whoid, $timestamp);
-
- if ($mailiscurrent) {
- $sth_Update2->execute($timestamp, $timestamp, $bugid);
- }
- else {
- $sth_Update->execute($timestamp, $bugid);
- }
- $count++;
- }
- my %group = (name => $added, bug_count => $count);
-
- push(@added_mandatory, \%group);
- }
- $dbh->bz_commit_transaction();
+ my $changes = $product->update;
delete_token($token);
- $vars->{'removed_na'} = \@removed_na;
- $vars->{'added_mandatory'} = \@added_mandatory;
$vars->{'product'} = $product;
+ $vars->{'changes'} = $changes;
$template->process("admin/products/groupcontrol/updated.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
-#
-# action='update' -> update the product
-#
-if ($action eq 'update') {
- check_token_data($token, 'edit_product');
- my $product_old_name = trim($cgi->param('product_old_name') || '');
- my $description = trim($cgi->param('description') || '');
- my $disallownew = trim($cgi->param('disallownew') || '');
- my $milestoneurl = trim($cgi->param('milestoneurl') || '');
- my $votesperuser = trim($cgi->param('votesperuser') || 0);
- my $maxvotesperbug = trim($cgi->param('maxvotesperbug') || 0);
- my $votestoconfirm = trim($cgi->param('votestoconfirm') || 0);
- my $defaultmilestone = trim($cgi->param('defaultmilestone') || '---');
-
- my $checkvotes = 0;
-
- my $product_old = $user->check_can_admin_product($product_old_name);
-
- if (Bugzilla->params->{'useclassification'}) {
- my $classification;
- if (!$classification_name) {
- $classification =
- new Bugzilla::Classification($product_old->classification_id);
- } else {
- $classification =
- Bugzilla::Classification::check_classification($classification_name);
- if ($classification->id != $product_old->classification_id) {
- ThrowUserError('classification_doesnt_exist_for_product',
- { product => $product_old->name,
- classification => $classification->name });
- }
- }
- $vars->{'classification'} = $classification;
- }
-
- unless ($product_name) {
- ThrowUserError('product_cant_delete_name',
- {product => $product_old->name});
- }
-
- unless ($description) {
- ThrowUserError('product_cant_delete_description',
- {product => $product_old->name});
- }
-
- my $stored_maxvotesperbug = $maxvotesperbug;
- if (!detaint_natural($maxvotesperbug)) {
- ThrowUserError('product_votes_per_bug_must_be_nonnegative',
- {maxvotesperbug => $stored_maxvotesperbug});
- }
-
- my $stored_votesperuser = $votesperuser;
- if (!detaint_natural($votesperuser)) {
- ThrowUserError('product_votes_per_user_must_be_nonnegative',
- {votesperuser => $stored_votesperuser});
- }
-
- my $stored_votestoconfirm = $votestoconfirm;
- if (!detaint_natural($votestoconfirm)) {
- ThrowUserError('product_votes_to_confirm_must_be_nonnegative',
- {votestoconfirm => $stored_votestoconfirm});
- }
-
- $dbh->bz_start_transaction();
-
- my $testproduct =
- new Bugzilla::Product({name => $product_name});
- if (lc($product_name) ne lc($product_old->name) &&
- $testproduct) {
- ThrowUserError('product_name_already_in_use',
- {product => $product_name});
- }
-
- # Only update milestone related stuff if 'usetargetmilestone' is on.
- if (Bugzilla->params->{'usetargetmilestone'}) {
- my $milestone = new Bugzilla::Milestone(
- { product => $product_old, name => $defaultmilestone });
-
- unless ($milestone) {
- ThrowUserError('product_must_define_defaultmilestone',
- {product => $product_old->name,
- defaultmilestone => $defaultmilestone,
- classification => $classification_name});
- }
-
- if ($milestoneurl ne $product_old->milestone_url) {
- trick_taint($milestoneurl);
- $dbh->do('UPDATE products SET milestoneurl = ? WHERE id = ?',
- undef, ($milestoneurl, $product_old->id));
- }
-
- if ($milestone->name ne $product_old->default_milestone) {
- $dbh->do('UPDATE products SET defaultmilestone = ? WHERE id = ?',
- undef, ($milestone->name, $product_old->id));
- }
- }
-
- $disallownew = $disallownew ? 1 : 0;
- if ($disallownew ne $product_old->disallow_new) {
- $dbh->do('UPDATE products SET disallownew = ? WHERE id = ?',
- undef, ($disallownew, $product_old->id));
- }
-
- if ($description ne $product_old->description) {
- trick_taint($description);
- $dbh->do('UPDATE products SET description = ? WHERE id = ?',
- undef, ($description, $product_old->id));
- }
-
- if ($votesperuser ne $product_old->votes_per_user) {
- $dbh->do('UPDATE products SET votesperuser = ? WHERE id = ?',
- undef, ($votesperuser, $product_old->id));
- $checkvotes = 1;
- }
-
- if ($maxvotesperbug ne $product_old->max_votes_per_bug) {
- $dbh->do('UPDATE products SET maxvotesperbug = ? WHERE id = ?',
- undef, ($maxvotesperbug, $product_old->id));
- $checkvotes = 1;
- }
-
- if ($votestoconfirm ne $product_old->votes_to_confirm) {
- $dbh->do('UPDATE products SET votestoconfirm = ? WHERE id = ?',
- undef, ($votestoconfirm, $product_old->id));
- $checkvotes = 1;
- }
-
- if ($product_name ne $product_old->name) {
- trick_taint($product_name);
- $dbh->do('UPDATE products SET name = ? WHERE id = ?',
- undef, ($product_name, $product_old->id));
- }
-
- $dbh->bz_commit_transaction();
-
- my $product = new Bugzilla::Product({name => $product_name});
-
- if ($checkvotes) {
- $vars->{'checkvotes'} = 1;
-
- # 1. too many votes for a single user on a single bug.
- my @toomanyvotes_list = ();
- if ($maxvotesperbug < $votesperuser) {
- my $votes = $dbh->selectall_arrayref(
- 'SELECT votes.who, votes.bug_id
- FROM votes
- INNER JOIN bugs
- ON bugs.bug_id = votes.bug_id
- WHERE bugs.product_id = ?
- AND votes.vote_count > ?',
- undef, ($product->id, $maxvotesperbug));
-
- foreach my $vote (@$votes) {
- my ($who, $id) = (@$vote);
- # If some votes are removed, RemoveVotes() returns a list
- # of messages to send to voters.
- my $msgs = RemoveVotes($id, $who, 'votes_too_many_per_bug');
- foreach my $msg (@$msgs) {
- MessageToMTA($msg);
- }
- my $name = user_id_to_login($who);
-
- push(@toomanyvotes_list,
- {id => $id, name => $name});
- }
- }
- $vars->{'toomanyvotes'} = \@toomanyvotes_list;
-
- # 2. too many total votes for a single user.
- # This part doesn't work in the general case because RemoveVotes
- # doesn't enforce votesperuser (except per-bug when it's less
- # than maxvotesperbug). See Bugzilla::Bug::RemoveVotes().
-
- my $votes = $dbh->selectall_arrayref(
- 'SELECT votes.who, votes.vote_count
- FROM votes
- INNER JOIN bugs
- ON bugs.bug_id = votes.bug_id
- WHERE bugs.product_id = ?',
- undef, $product->id);
-
- my %counts;
- foreach my $vote (@$votes) {
- my ($who, $count) = @$vote;
- if (!defined $counts{$who}) {
- $counts{$who} = $count;
- } else {
- $counts{$who} += $count;
- }
- }
- my @toomanytotalvotes_list = ();
- foreach my $who (keys(%counts)) {
- if ($counts{$who} > $votesperuser) {
- my $bug_ids = $dbh->selectcol_arrayref(
- 'SELECT votes.bug_id
- FROM votes
- INNER JOIN bugs
- ON bugs.bug_id = votes.bug_id
- WHERE bugs.product_id = ?
- AND votes.who = ?',
- undef, ($product->id, $who));
-
- foreach my $bug_id (@$bug_ids) {
- # RemoveVotes() returns a list of messages to send
- # in case some voters had too many votes.
- my $msgs = RemoveVotes($bug_id, $who, 'votes_too_many_per_user');
- foreach my $msg (@$msgs) {
- MessageToMTA($msg);
- }
- my $name = user_id_to_login($who);
-
- push(@toomanytotalvotes_list,
- {id => $bug_id, name => $name});
- }
- }
- }
- $vars->{'toomanytotalvotes'} = \@toomanytotalvotes_list;
-
- # 3. enough votes to confirm
- my $bug_list = $dbh->selectcol_arrayref(
- "SELECT bug_id FROM bugs
- WHERE product_id = ?
- AND bug_status = 'UNCONFIRMED'
- AND votes >= ?",
- undef, ($product->id, $votestoconfirm));
-
- my @updated_bugs = ();
- foreach my $bug_id (@$bug_list) {
- my $confirmed = CheckIfVotedConfirmed($bug_id, $whoid);
- push (@updated_bugs, $bug_id) if $confirmed;
- }
-
- $vars->{'confirmedbugs'} = \@updated_bugs;
- $vars->{'changer'} = $user->login;
- }
- delete_token($token);
-
- $vars->{'old_product'} = $product_old;
- $vars->{'product'} = $product;
-
- $template->process("admin/products/updated.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
-}
-
-#
-# action='editgroupcontrols' -> update product group controls
-#
-
-if ($action eq 'editgroupcontrols') {
- my $product = $user->check_can_admin_product($product_name);
-
- # Display a group if it is either enabled or has bugs for this product.
- my $groups = $dbh->selectall_arrayref(
- 'SELECT id, name, entry, membercontrol, othercontrol, canedit,
- editcomponents, editbugs, canconfirm,
- isactive, COUNT(bugs.bug_id) AS bugcount
- FROM groups
- LEFT JOIN group_control_map
- ON group_control_map.group_id = groups.id
- AND group_control_map.product_id = ?
- LEFT JOIN bug_group_map
- ON bug_group_map.group_id = groups.id
- LEFT JOIN bugs
- ON bugs.bug_id = bug_group_map.bug_id
- AND bugs.product_id = ?
- WHERE isbuggroup != 0
- AND (isactive != 0 OR entry IS NOT NULL OR bugs.bug_id IS NOT NULL) ' .
- $dbh->sql_group_by('name', 'id, entry, membercontrol,
- othercontrol, canedit, isactive,
- editcomponents, canconfirm, editbugs'),
- {'Slice' => {}}, ($product->id, $product->id));
-
- $vars->{'product'} = $product;
- $vars->{'groups'} = $groups;
- $vars->{'token'} = issue_session_token('edit_group_controls');
-
- $vars->{'const'} = {
- 'CONTROLMAPNA' => CONTROLMAPNA,
- 'CONTROLMAPSHOWN' => CONTROLMAPSHOWN,
- 'CONTROLMAPDEFAULT' => CONTROLMAPDEFAULT,
- 'CONTROLMAPMANDATORY' => CONTROLMAPMANDATORY,
- };
-
- $template->process("admin/products/groupcontrol/edit.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
-}
-
-
-#
# No valid action found
-#
-
-ThrowUserError('no_valid_action', {field => "product"});
+ThrowUserError('unknown_action', {action => $action});
diff --git a/Websites/bugs.webkit.org/editusers.cgi b/Websites/bugs.webkit.org/editusers.cgi
index 924cd6f..6071821 100755
--- a/Websites/bugs.webkit.org/editusers.cgi
+++ b/Websites/bugs.webkit.org/editusers.cgi
@@ -64,6 +64,9 @@
$vars->{'editusers'} = $editusers;
mirrorListSelectionValues();
+Bugzilla::Hook::process('admin_editusers_action',
+ { vars => $vars, user => $user, action => $action });
+
###########################################################################
if ($action eq 'search') {
# Allow to restrict the search to any group the user is allowed to bless.
@@ -74,10 +77,10 @@
###########################################################################
} elsif ($action eq 'list') {
my $matchvalue = $cgi->param('matchvalue') || '';
- my $matchstr = $cgi->param('matchstr');
+ my $matchstr = trim($cgi->param('matchstr'));
my $matchtype = $cgi->param('matchtype');
my $grouprestrict = $cgi->param('grouprestrict') || '0';
- my $query = 'SELECT DISTINCT userid, login_name, realname, disabledtext ' .
+ my $query = 'SELECT DISTINCT userid, login_name, realname, is_enabled ' .
'FROM profiles';
my @bindValues;
my $nextCondition;
@@ -136,30 +139,35 @@
} else {
$expr = "profiles.login_name";
}
+
+ if ($matchtype =~ /^(regexp|notregexp|exact)$/) {
+ $matchstr ||= '.';
+ }
+ else {
+ $matchstr = '' unless defined $matchstr;
+ }
+ # We can trick_taint because we use the value in a SELECT only,
+ # using a placeholder.
+ trick_taint($matchstr);
+
if ($matchtype eq 'regexp') {
- $query .= $dbh->sql_regexp($expr, '?');
- $matchstr = '.' unless $matchstr;
+ $query .= $dbh->sql_regexp($expr, '?', 0, $dbh->quote($matchstr));
} elsif ($matchtype eq 'notregexp') {
- $query .= $dbh->sql_not_regexp($expr, '?');
- $matchstr = '.' unless $matchstr;
+ $query .= $dbh->sql_not_regexp($expr, '?', 0, $dbh->quote($matchstr));
} elsif ($matchtype eq 'exact') {
$query .= $expr . ' = ?';
- $matchstr = '.' unless $matchstr;
} else { # substr or unknown
$query .= $dbh->sql_istrcmp($expr, '?', 'LIKE');
$matchstr = "%$matchstr%";
}
$nextCondition = 'AND';
- # We can trick_taint because we use the value in a SELECT only,
- # using a placeholder.
- trick_taint($matchstr);
push(@bindValues, $matchstr);
}
# Handle selection by group.
if ($grouprestrict eq '1') {
my $grouplist = join(',',
- @{Bugzilla::User->flatten_group_membership($group->id)});
+ @{Bugzilla::Group->flatten_group_membership($group->id)});
$query .= " $nextCondition ugm.group_id IN($grouplist) ";
}
$query .= ' ORDER BY profiles.login_name';
@@ -198,12 +206,19 @@
check_token_data($token, 'add_user');
+ # When e.g. the 'Env' auth method is used, the password field
+ # is not displayed. In that case, set the password to *.
+ my $password = $cgi->param('password');
+ $password = '*' if !defined $password;
+
my $new_user = Bugzilla::User->create({
login_name => scalar $cgi->param('login'),
- cryptpassword => scalar $cgi->param('password'),
+ cryptpassword => $password,
realname => scalar $cgi->param('name'),
disabledtext => scalar $cgi->param('disabledtext'),
- disable_mail => scalar $cgi->param('disable_mail')});
+ disable_mail => scalar $cgi->param('disable_mail'),
+ extern_id => scalar $cgi->param('extern_id'),
+ });
userDataToVars($new_user->id);
@@ -238,7 +253,7 @@
# Update profiles table entry; silently skip doing this if the user
# is not authorized.
- my %changes;
+ my $changes = {};
if ($editusers) {
$otherUser->set_login($cgi->param('login'));
$otherUser->set_name($cgi->param('name'));
@@ -246,7 +261,9 @@
if $cgi->param('password');
$otherUser->set_disabledtext($cgi->param('disabledtext'));
$otherUser->set_disable_mail($cgi->param('disable_mail'));
- %changes = %{$otherUser->update()};
+ $otherUser->set_extern_id($cgi->param('extern_id'))
+ if defined($cgi->param('extern_id'));
+ $changes = $otherUser->update();
}
# Update group settings.
@@ -276,9 +293,9 @@
# would allow to display a friendlier error message on page reloads.
userDataToVars($otherUserID);
my $permissions = $vars->{'permissions'};
- foreach (@{$user->bless_groups()}) {
- my $id = $$_{'id'};
- my $name = $$_{'name'};
+ foreach my $blessable (@{$user->bless_groups()}) {
+ my $id = $blessable->id;
+ my $name = $blessable->name;
# Change memberships.
my $groupid = $cgi->param("group_$id") || 0;
@@ -334,7 +351,7 @@
delete_token($token);
$vars->{'message'} = 'account_updated';
- $vars->{'changed_fields'} = [keys %changes];
+ $vars->{'changed_fields'} = [keys %$changes];
$vars->{'groups_added_to'} = \@groupsAddedTo;
$vars->{'groups_removed_from'} = \@groupsRemovedFrom;
$vars->{'groups_granted_rights_to_bless'} = \@groupsGrantedRightsToBless;
@@ -414,9 +431,6 @@
$vars->{'series'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM series WHERE creator = ?',
undef, $otherUserID);
- $vars->{'votes'} = $dbh->selectrow_array(
- 'SELECT COUNT(*) FROM votes WHERE who = ?',
- undef, $otherUserID);
$vars->{'watch'}{'watched'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM watch WHERE watched = ?',
undef, $otherUserID);
@@ -476,35 +490,36 @@
my $sth_set_bug_timestamp =
$dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
- # Reference removals which need LogActivityEntry.
- my $statement_flagupdate = 'UPDATE flags set requestee_id = NULL
- WHERE bug_id = ?
- AND attach_id %s
- AND requestee_id = ?';
- my $sth_flagupdate_attachment =
- $dbh->prepare(sprintf($statement_flagupdate, '= ?'));
- my $sth_flagupdate_bug =
- $dbh->prepare(sprintf($statement_flagupdate, 'IS NULL'));
+ my $sth_updateFlag = $dbh->prepare('INSERT INTO bugs_activity
+ (bug_id, attach_id, who, bug_when, fieldid, removed, added)
+ VALUES (?, ?, ?, ?, ?, ?, ?)');
- my $buglist = $dbh->selectall_arrayref('SELECT DISTINCT bug_id, attach_id
- FROM flags
- WHERE requestee_id = ?',
- undef, $otherUserID);
+ # Flags
+ my $flag_ids =
+ $dbh->selectcol_arrayref('SELECT id FROM flags WHERE requestee_id = ?',
+ undef, $otherUserID);
- foreach (@$buglist) {
- my ($bug_id, $attach_id) = @$_;
- my @old_summaries = Bugzilla::Flag->snapshot($bug_id, $attach_id);
- if ($attach_id) {
- $sth_flagupdate_attachment->execute($bug_id, $attach_id, $otherUserID);
+ my $flags = Bugzilla::Flag->new_from_list($flag_ids);
+
+ $dbh->do('UPDATE flags SET requestee_id = NULL, modification_date = ?
+ WHERE requestee_id = ?', undef, ($timestamp, $otherUserID));
+
+ # We want to remove the requestee but leave the requester alone,
+ # so we have to log these changes manually.
+ my %bugs;
+ push(@{$bugs{$_->bug_id}->{$_->attach_id || 0}}, $_) foreach @$flags;
+ my $fieldid = get_field_id('flagtypes.name');
+ foreach my $bug_id (keys %bugs) {
+ foreach my $attach_id (keys %{$bugs{$bug_id}}) {
+ my @old_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id});
+ $_->_set_requestee() foreach @{$bugs{$bug_id}->{$attach_id}};
+ my @new_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id});
+ my ($removed, $added) =
+ Bugzilla::Flag->update_activity(\@old_summaries, \@new_summaries);
+ $sth_updateFlag->execute($bug_id, $attach_id || undef, $userid,
+ $timestamp, $fieldid, $removed, $added);
}
- else {
- $sth_flagupdate_bug->execute($bug_id, $otherUserID);
- }
- my @new_summaries = Bugzilla::Flag->snapshot($bug_id, $attach_id);
- # Let update_activity do all the dirty work, including setting
- # the bug timestamp.
- Bugzilla::Flag::update_activity($bug_id, $attach_id, $timestamp,
- \@old_summaries, \@new_summaries);
+ $sth_set_bug_timestamp->execute($timestamp, $bug_id);
$updatedbugs{$bug_id} = 1;
}
@@ -523,18 +538,15 @@
$otherUserID);
$dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?', undef,
($otherUserID, $otherUserID));
- $dbh->do('UPDATE quips SET userid = NULL where userid = ?', undef, $otherUserID);
$dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID);
$dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef,
$otherUserID);
- $dbh->do('DELETE FROM votes WHERE who = ?', undef, $otherUserID);
$dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef,
($otherUserID, $otherUserID));
# Deletions in referred tables which need LogActivityEntry.
- $buglist = $dbh->selectcol_arrayref('SELECT bug_id FROM cc
- WHERE who = ?',
- undef, $otherUserID);
+ my $buglist = $dbh->selectcol_arrayref('SELECT bug_id FROM cc WHERE who = ?',
+ undef, $otherUserID);
$dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID);
foreach my $bug_id (@$buglist) {
LogActivityEntry($bug_id, 'cc', $otherUser->login, '', $userid,
@@ -642,7 +654,7 @@
# Send mail about what we've done to bugs.
# The deleted user is not notified of the changes.
foreach (keys(%updatedbugs)) {
- Bugzilla::BugMail::Send($_, {'changer' => $user->login} );
+ Bugzilla::BugMail::Send($_, {'changer' => $user} );
}
###########################################################################
@@ -652,7 +664,7 @@
$vars->{'profile_changes'} = $dbh->selectall_arrayref(
"SELECT profiles.login_name AS who, " .
$dbh->sql_date_format('profiles_activity.profiles_when') . " AS activity_when,
- fielddefs.description AS what,
+ fielddefs.name AS what,
profiles_activity.oldvalue AS removed,
profiles_activity.newvalue AS added
FROM profiles_activity
@@ -670,8 +682,7 @@
###########################################################################
} else {
- $vars->{'action'} = $action;
- ThrowCodeError('action_unrecognized', $vars);
+ ThrowUserError('unknown_action', {action => $action});
}
exit;
diff --git a/Websites/bugs.webkit.org/editvalues.cgi b/Websites/bugs.webkit.org/editvalues.cgi
index 463547b..acaebae 100755
--- a/Websites/bugs.webkit.org/editvalues.cgi
+++ b/Websites/bugs.webkit.org/editvalues.cgi
@@ -25,79 +25,21 @@
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Constants;
-use Bugzilla::Config qw(:admin);
use Bugzilla::Token;
use Bugzilla::Field;
-use Bugzilla::Bug;
-use Bugzilla::Status;
+use Bugzilla::Field::Choice;
-# List of different tables that contain the changeable field values
-# (the old "enums.") Keep them in alphabetical order by their
-# English name from field-descs.html.tmpl.
-# Format: Array of valid field names.
-our @valid_fields = ('op_sys', 'rep_platform', 'priority', 'bug_severity',
- 'bug_status', 'resolution');
+###############
+# Subroutines #
+###############
-# Add custom select fields.
-my @custom_fields = Bugzilla->get_fields({custom => 1,
- type => FIELD_TYPE_SINGLE_SELECT});
-push(@custom_fields, Bugzilla->get_fields({custom => 1,
- type => FIELD_TYPE_MULTI_SELECT}));
-
-push(@valid_fields, map { $_->name } @custom_fields);
-
-######################################################################
-# Subroutines
-######################################################################
-
-# Returns whether or not the specified table exists in the @tables array.
-sub FieldExists {
- my ($field) = @_;
-
- return lsearch(\@valid_fields, $field) >= 0;
-}
-
-# Same as FieldExists, but emits and error and dies if it fails.
-sub FieldMustExist {
- my ($field)= @_;
-
- $field ||
- ThrowUserError('fieldname_not_specified');
-
- # Is it a valid field to be editing?
- FieldExists($field) ||
- ThrowUserError('fieldname_invalid', {'field' => $field});
-
- return new Bugzilla::Field({name => $field});
-}
-
-# Returns if the specified value exists for the field specified.
-sub ValueExists {
- my ($field, $value) = @_;
- # Value is safe because it's being passed only to a SELECT
- # statement via a placeholder.
- trick_taint($value);
-
- my $dbh = Bugzilla->dbh;
- my $value_count =
- $dbh->selectrow_array("SELECT COUNT(*) FROM $field "
- . " WHERE value = ?", undef, $value);
-
- return $value_count;
-}
-
-# Same check as ValueExists, emits an error text and dies if it fails.
-sub ValueMustExist {
- my ($field, $value)= @_;
-
- # Values may not be empty (it's very difficult to deal
- # with empty values in the admin interface).
- trim($value) || ThrowUserError('fieldvalue_not_specified');
-
- # Does it exist in the DB?
- ValueExists($field, $value) ||
- ThrowUserError('fieldvalue_doesnt_exist', {'value' => $value,
- 'field' => $field});
+sub display_field_values {
+ my $vars = shift;
+ my $template = Bugzilla->template;
+ $vars->{'values'} = $vars->{'field'}->legal_values;
+ $template->process("admin/fieldvalues/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
######################################################################
@@ -110,7 +52,7 @@
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-local our $vars = {};
+my $vars = {};
# Replace this entry by separate entries in templates when
# the documentation about legal values becomes bigger.
@@ -118,7 +60,7 @@
print $cgi->header();
-exists Bugzilla->user->groups->{'admin'} ||
+Bugzilla->user->in_group('admin') ||
ThrowUserError('auth_failure', {group => "admin",
action => "edit",
object => "field_values"});
@@ -126,177 +68,81 @@
#
# often-used variables
#
-my $field = trim($cgi->param('field') || '');
-my $value = trim($cgi->param('value') || '');
-my $sortkey = trim($cgi->param('sortkey') || '0');
-my $action = trim($cgi->param('action') || '');
-my $token = $cgi->param('token');
-
-# Gives the name of the parameter associated with the field
-# and representing its default value.
-local our %defaults;
-$defaults{'op_sys'} = 'defaultopsys';
-$defaults{'rep_platform'} = 'defaultplatform';
-$defaults{'priority'} = 'defaultpriority';
-$defaults{'bug_severity'} = 'defaultseverity';
-
-# Alternatively, a list of non-editable values can be specified.
-# In this case, only the sortkey can be altered.
-local our %static;
-$static{'bug_status'} = ['UNCONFIRMED', Bugzilla->params->{'duplicate_or_move_bug_status'}];
-$static{'resolution'} = ['', 'FIXED', 'MOVED', 'DUPLICATE'];
-$static{$_->name} = ['---'] foreach (@custom_fields);
+my $action = trim($cgi->param('action') || '');
+my $token = $cgi->param('token');
#
# field = '' -> Show nice list of fields
#
-unless ($field) {
- # Convert @valid_fields into the format that select-field wants.
- my @field_list = ();
- foreach my $field_name (@valid_fields) {
- push(@field_list, {name => $field_name});
- }
+if (!$cgi->param('field')) {
+ my @field_list =
+ @{ Bugzilla->fields({ is_select => 1, is_abnormal => 0 }) };
$vars->{'fields'} = \@field_list;
- $template->process("admin/fieldvalues/select-field.html.tmpl",
- $vars)
+ $template->process("admin/fieldvalues/select-field.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
-# At this point, the field is defined.
-my $field_obj = FieldMustExist($field);
-$vars->{'field'} = $field_obj;
-trick_taint($field);
-
-sub display_field_values {
- my $template = Bugzilla->template;
- my $field = $vars->{'field'}->name;
- my $fieldvalues =
- Bugzilla->dbh->selectall_arrayref("SELECT value AS name, sortkey"
- . " FROM $field ORDER BY sortkey, value",
- {Slice =>{}});
-
- $vars->{'values'} = $fieldvalues;
- $vars->{'default'} = Bugzilla->params->{$defaults{$field}} if defined $defaults{$field};
- $vars->{'static'} = $static{$field} if exists $static{$field};
-
- $template->process("admin/fieldvalues/list.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+# At this point, the field must be defined.
+my $field = Bugzilla::Field->check($cgi->param('field'));
+if (!$field->is_select || $field->is_abnormal) {
+ ThrowUserError('fieldname_invalid', { field => $field });
}
+$vars->{'field'} = $field;
#
# action='' -> Show nice list of values.
#
-display_field_values() unless $action;
+display_field_values($vars) unless $action;
#
# action='add' -> show form for adding new field value.
# (next action will be 'new')
#
if ($action eq 'add') {
- $vars->{'value'} = $value;
$vars->{'token'} = issue_session_token('add_field_value');
- $template->process("admin/fieldvalues/create.html.tmpl",
- $vars)
+ $template->process("admin/fieldvalues/create.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
-
exit;
}
-
#
# action='new' -> add field value entered in the 'action=add' screen
#
if ($action eq 'new') {
check_token_data($token, 'add_field_value');
- # Cleanups and validity checks
- $value || ThrowUserError('fieldvalue_undefined');
-
- if (length($value) > 60) {
- ThrowUserError('fieldvalue_name_too_long',
- {'value' => $value});
- }
- # Need to store in case detaint_natural() clears the sortkey
- my $stored_sortkey = $sortkey;
- if (!detaint_natural($sortkey)) {
- ThrowUserError('fieldvalue_sortkey_invalid',
- {'name' => $field,
- 'sortkey' => $stored_sortkey});
- }
- if (ValueExists($field, $value)) {
- ThrowUserError('fieldvalue_already_exists',
- {'field' => $field_obj,
- 'value' => $value});
- }
- if ($field eq 'bug_status'
- && (grep { lc($value) eq $_ } SPECIAL_STATUS_WORKFLOW_ACTIONS))
- {
- $vars->{'value'} = $value;
- ThrowUserError('fieldvalue_reserved_word', $vars);
- }
-
- # Value is only used in a SELECT placeholder and through the HTML filter.
- trick_taint($value);
-
- # Add the new field value.
- $dbh->do("INSERT INTO $field (value, sortkey) VALUES (?, ?)",
- undef, ($value, $sortkey));
-
- if ($field eq 'bug_status') {
- unless ($cgi->param('is_open')) {
- # The bug status is a closed state, but they are open by default.
- $dbh->do('UPDATE bug_status SET is_open = 0 WHERE value = ?', undef, $value);
- }
- # Allow the transition from this new bug status to the one used
- # by the 'duplicate_or_move_bug_status' parameter.
- Bugzilla::Status::add_missing_bug_status_transitions();
- }
+ my $created_value = Bugzilla::Field::Choice->type($field)->create({
+ value => scalar $cgi->param('value'),
+ sortkey => scalar $cgi->param('sortkey'),
+ is_open => scalar $cgi->param('is_open'),
+ visibility_value_id => scalar $cgi->param('visibility_value_id'),
+ });
delete_token($token);
$vars->{'message'} = 'field_value_created';
- $vars->{'value'} = $value;
- display_field_values();
+ $vars->{'value'} = $created_value;
+ display_field_values($vars);
}
+# After this, we always have a value
+my $value = Bugzilla::Field::Choice->type($field)->check($cgi->param('value'));
+$vars->{'value'} = $value;
#
# action='del' -> ask if user really wants to delete
# (next action would be 'delete')
#
if ($action eq 'del') {
- ValueMustExist($field, $value);
- trick_taint($value);
-
- # See if any bugs are still using this value.
- if ($field_obj->type != FIELD_TYPE_MULTI_SELECT) {
- $vars->{'bug_count'} =
- $dbh->selectrow_array("SELECT COUNT(*) FROM bugs WHERE $field = ?",
- undef, $value);
- }
- else {
- $vars->{'bug_count'} =
- $dbh->selectrow_array("SELECT COUNT(*) FROM bug_$field WHERE value = ?",
- undef, $value);
- }
-
- $vars->{'value_count'} =
- $dbh->selectrow_array("SELECT COUNT(*) FROM $field");
-
- $vars->{'value'} = $value;
- $vars->{'param_name'} = $defaults{$field};
-
# If the value cannot be deleted, throw an error.
- if (lsearch($static{$field}, $value) >= 0) {
+ if ($value->is_static) {
ThrowUserError('fieldvalue_not_deletable', $vars);
}
$vars->{'token'} = issue_session_token('delete_field_value');
- $template->process("admin/fieldvalues/confirm-delete.html.tmpl",
- $vars)
+ $template->process("admin/fieldvalues/confirm-delete.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
@@ -308,63 +154,11 @@
#
if ($action eq 'delete') {
check_token_data($token, 'delete_field_value');
- ValueMustExist($field, $value);
-
- $vars->{'value'} = $value;
- $vars->{'param_name'} = $defaults{$field};
-
- if (defined $defaults{$field}
- && ($value eq Bugzilla->params->{$defaults{$field}}))
- {
- ThrowUserError('fieldvalue_is_default', $vars);
- }
- # If the value cannot be deleted, throw an error.
- if (lsearch($static{$field}, $value) >= 0) {
- ThrowUserError('fieldvalue_not_deletable', $vars);
- }
-
- trick_taint($value);
-
- $dbh->bz_start_transaction();
-
- # Check if there are any bugs that still have this value.
- my $bug_count;
- if ($field_obj->type != FIELD_TYPE_MULTI_SELECT) {
- $bug_count =
- $dbh->selectrow_array("SELECT COUNT(*) FROM bugs WHERE $field = ?",
- undef, $value);
- }
- else {
- $bug_count =
- $dbh->selectrow_array("SELECT COUNT(*) FROM bug_$field WHERE value = ?",
- undef, $value);
- }
-
-
- if ($bug_count) {
- # You tried to delete a field that bugs are still using.
- # You can't just delete the bugs. That's ridiculous.
- ThrowUserError("fieldvalue_still_has_bugs",
- { field => $field, value => $value,
- count => $bug_count });
- }
-
- if ($field eq 'bug_status') {
- my ($status_id) = $dbh->selectrow_array(
- 'SELECT id FROM bug_status WHERE value = ?', undef, $value);
- $dbh->do('DELETE FROM status_workflow
- WHERE old_status = ? OR new_status = ?',
- undef, ($status_id, $status_id));
- }
-
- $dbh->do("DELETE FROM $field WHERE value = ?", undef, $value);
-
- $dbh->bz_commit_transaction();
+ $value->remove_from_db();
delete_token($token);
-
$vars->{'message'} = 'field_value_deleted';
$vars->{'no_edit_link'} = 1;
- display_field_values();
+ display_field_values($vars);
}
@@ -373,20 +167,7 @@
# (next action would be 'update')
#
if ($action eq 'edit') {
- ValueMustExist($field, $value);
- trick_taint($value);
-
- $vars->{'sortkey'} = $dbh->selectrow_array(
- "SELECT sortkey FROM $field WHERE value = ?", undef, $value) || 0;
-
- $vars->{'value'} = $value;
- $vars->{'is_static'} = (lsearch($static{$field}, $value) >= 0) ? 1 : 0;
$vars->{'token'} = issue_session_token('edit_field_value');
- if ($field eq 'bug_status') {
- $vars->{'is_open'} = $dbh->selectrow_array('SELECT is_open FROM bug_status
- WHERE value = ?', undef, $value);
- }
-
$template->process("admin/fieldvalues/edit.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
@@ -399,99 +180,18 @@
#
if ($action eq 'update') {
check_token_data($token, 'edit_field_value');
- my $valueold = trim($cgi->param('valueold') || '');
- my $sortkeyold = trim($cgi->param('sortkeyold') || '0');
-
- ValueMustExist($field, $valueold);
- trick_taint($valueold);
-
- $vars->{'value'} = $value;
- # If the value cannot be renamed, throw an error.
- if (lsearch($static{$field}, $valueold) >= 0 && $value ne $valueold) {
- $vars->{'old_value'} = $valueold;
- ThrowUserError('fieldvalue_not_editable', $vars);
+ $vars->{'value_old'} = $value->name;
+ if ($cgi->should_set('is_active')) {
+ $value->set_is_active($cgi->param('is_active'));
}
-
- if (length($value) > 60) {
- ThrowUserError('fieldvalue_name_too_long', $vars);
- }
-
- $dbh->bz_start_transaction();
-
- # Need to store because detaint_natural() will delete this if
- # invalid
- my $stored_sortkey = $sortkey;
- if ($sortkey != $sortkeyold) {
-
- if (!detaint_natural($sortkey)) {
- ThrowUserError('fieldvalue_sortkey_invalid',
- {'name' => $field,
- 'sortkey' => $stored_sortkey});
-
- }
-
- $dbh->do("UPDATE $field SET sortkey = ? WHERE value = ?",
- undef, $sortkey, $valueold);
-
- $vars->{'updated_sortkey'} = 1;
- $vars->{'sortkey'} = $sortkey;
- }
-
- if ($value ne $valueold) {
-
- unless ($value) {
- ThrowUserError('fieldvalue_undefined');
- }
- if (ValueExists($field, $value)) {
- ThrowUserError('fieldvalue_already_exists', $vars);
- }
- if ($field eq 'bug_status'
- && (grep { lc($value) eq $_ } SPECIAL_STATUS_WORKFLOW_ACTIONS))
- {
- $vars->{'value'} = $value;
- ThrowUserError('fieldvalue_reserved_word', $vars);
- }
- trick_taint($value);
-
- if ($field_obj->type != FIELD_TYPE_MULTI_SELECT) {
- $dbh->do("UPDATE bugs SET $field = ? WHERE $field = ?",
- undef, $value, $valueold);
- }
- else {
- $dbh->do("UPDATE bug_$field SET value = ? WHERE value = ?",
- undef, $value, $valueold);
- }
-
- $dbh->do("UPDATE $field SET value = ? WHERE value = ?",
- undef, $value, $valueold);
-
- $vars->{'updated_value'} = 1;
- }
-
- $dbh->bz_commit_transaction();
-
- # If the old value was the default value for the field,
- # update data/params accordingly.
- # This update is done while tables are unlocked due to the
- # annoying calls in Bugzilla/Config/Common.pm.
- if (defined $defaults{$field}
- && $value ne $valueold
- && $valueold eq Bugzilla->params->{$defaults{$field}})
- {
- SetParam($defaults{$field}, $value);
- write_params();
- $vars->{'default_value_updated'} = 1;
- }
+ $value->set_name($cgi->param('value_new'));
+ $value->set_sortkey($cgi->param('sortkey'));
+ $value->set_visibility_value($cgi->param('visibility_value_id'));
+ $vars->{'changes'} = $value->update();
delete_token($token);
-
$vars->{'message'} = 'field_value_updated';
- display_field_values();
+ display_field_values($vars);
}
-
-#
# No valid action found
-#
-# We can't get here without $field being defined --
-# See the unless($field) block at the top.
-ThrowUserError('no_valid_action', { field => $field } );
+ThrowUserError('unknown_action', {action => $action});
diff --git a/Websites/bugs.webkit.org/editversions.cgi b/Websites/bugs.webkit.org/editversions.cgi
index 146f7f3..6f62177 100755
--- a/Websites/bugs.webkit.org/editversions.cgi
+++ b/Websites/bugs.webkit.org/editversions.cgi
@@ -63,6 +63,7 @@
my $action = trim($cgi->param('action') || '');
my $showbugcounts = (defined $cgi->param('showbugcounts'));
my $token = $cgi->param('token');
+my $isactive = $cgi->param('isactive');
#
# product = '' -> Show nice list of products
@@ -119,7 +120,8 @@
if ($action eq 'new') {
check_token_data($token, 'add_version');
- my $version = Bugzilla::Version::create($version_name, $product);
+ my $version = Bugzilla::Version->create(
+ { value => $version_name, product => $product });
delete_token($token);
$vars->{'message'} = 'version_created';
@@ -202,7 +204,9 @@
$dbh->bz_start_transaction();
- $vars->{'updated'} = $version->update($version_name, $product);
+ $version->set_name($version_name);
+ $version->set_is_active($isactive);
+ my $changes = $version->update();
$dbh->bz_commit_transaction();
delete_token($token);
@@ -210,13 +214,12 @@
$vars->{'message'} = 'version_updated';
$vars->{'version'} = $version;
$vars->{'product'} = $product;
+ $vars->{'changes'} = $changes;
$template->process("admin/versions/list.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
-#
# No valid action found
-#
-ThrowUserError('no_valid_action', {'field' => "version"});
+ThrowUserError('unknown_action', {action => $action});
diff --git a/Websites/bugs.webkit.org/editwhines.cgi b/Websites/bugs.webkit.org/editwhines.cgi
index dd94eeb..43dc4e1 100755
--- a/Websites/bugs.webkit.org/editwhines.cgi
+++ b/Websites/bugs.webkit.org/editwhines.cgi
@@ -36,6 +36,9 @@
use Bugzilla::User;
use Bugzilla::Group;
use Bugzilla::Token;
+use Bugzilla::Whine::Schedule;
+use Bugzilla::Whine::Query;
+use Bugzilla::Whine;
# require the user to have logged in
my $user = Bugzilla->login(LOGIN_REQUIRED);
@@ -53,10 +56,8 @@
my $token = $cgi->param('token');
my $sth; # database statement handle
-# $events is a hash ref, keyed by event id, that stores the active user's
-# events. It starts off with:
-# 'subject' - the subject line for the email message
-# 'body' - the text to be sent at the top of the message
+# $events is a hash ref of Bugzilla::Whine objects keyed by event id,
+# that stores the active user's events.
#
# Eventually, it winds up with:
# 'queries' - array ref containing hashes of:
@@ -104,20 +105,11 @@
# otherwise we could simply delete whatever matched that ID.
#
# schedules
- $sth = $dbh->prepare("SELECT whine_schedules.id " .
- "FROM whine_schedules " .
- "LEFT JOIN whine_events " .
- "ON whine_events.id = " .
- "whine_schedules.eventid " .
- "WHERE whine_events.id = ? " .
- "AND whine_events.owner_userid = ?");
- $sth->execute($eventid, $userid);
- my @ids = @{$sth->fetchall_arrayref};
+ my $schedules = Bugzilla::Whine::Schedule->match({ eventid => $eventid });
$sth = $dbh->prepare("DELETE FROM whine_schedules "
. "WHERE id=?");
- for (@ids) {
- my $delete_id = $_->[0];
- $sth->execute($delete_id);
+ foreach my $schedule (@$schedules) {
+ $sth->execute($schedule->id);
}
# queries
@@ -129,7 +121,7 @@
"WHERE whine_events.id = ? " .
"AND whine_events.owner_userid = ?");
$sth->execute($eventid, $userid);
- @ids = @{$sth->fetchall_arrayref};
+ my @ids = @{$sth->fetchall_arrayref};
$sth = $dbh->prepare("DELETE FROM whine_queries " .
"WHERE id=?");
for (@ids) {
@@ -143,20 +135,22 @@
$sth->execute($eventid, $userid);
}
else {
- # check the subject and body for changes
+ # check the subject, body and mailifnobugs for changes
my $subject = ($cgi->param("event_${eventid}_subject") or '');
my $body = ($cgi->param("event_${eventid}_body") or '');
+ my $mailifnobugs = $cgi->param("event_${eventid}_mailifnobugs") ? 1 : 0;
trick_taint($subject) if $subject;
trick_taint($body) if $body;
- if ( ($subject ne $events->{$eventid}->{'subject'})
- || ($body ne $events->{$eventid}->{'body'}) ) {
+ if ( ($subject ne $events->{$eventid}->subject)
+ || ($mailifnobugs != $events->{$eventid}->mail_if_no_bugs)
+ || ($body ne $events->{$eventid}->body) ) {
$sth = $dbh->prepare("UPDATE whine_events " .
- "SET subject=?, body=? " .
+ "SET subject=?, body=?, mailifnobugs=? " .
"WHERE id=?");
- $sth->execute($subject, $body, $eventid);
+ $sth->execute($subject, $body, $mailifnobugs, $eventid);
}
# add a schedule
@@ -181,13 +175,10 @@
# to be altered or deleted
# Check schedules for changes
- $sth = $dbh->prepare("SELECT id " .
- "FROM whine_schedules " .
- "WHERE eventid=?");
- $sth->execute($eventid);
+ my $schedules = Bugzilla::Whine::Schedule->match({ eventid => $eventid });
my @scheduleids = ();
- while (my ($sid) = $sth->fetchrow_array) {
- push @scheduleids, $sid;
+ foreach my $schedule (@$schedules) {
+ push @scheduleids, $schedule->id;
}
# we need to double-check all of the user IDs in mailto to make
@@ -201,7 +192,7 @@
}
}
if (scalar %{$arglist}) {
- &Bugzilla::User::match_field($cgi, $arglist);
+ Bugzilla::User::match_field($arglist);
}
for my $sid (@scheduleids) {
@@ -238,7 +229,6 @@
# get an id for the mailto address
if ($can_mail_others && $mailto) {
if ($mailto_type == MAILTO_USER) {
- # The user login has already been validated.
$mailto_id = login_to_id($mailto);
}
elsif ($mailto_type == MAILTO_GROUP) {
@@ -277,16 +267,9 @@
}
# Check queries for changes
- $sth = $dbh->prepare("SELECT id " .
- "FROM whine_queries " .
- "WHERE eventid=?");
- $sth->execute($eventid);
- my @queries = ();
- while (my ($qid) = $sth->fetchrow_array) {
- push @queries, $qid;
- }
-
- for my $qid (@queries) {
+ my $queries = Bugzilla::Whine::Query->match({ eventid => $eventid });
+ for my $query (@$queries) {
+ my $qid = $query->id;
if ($cgi->param("remove_query_$qid")) {
$sth = $dbh->prepare("SELECT whine_queries.id " .
@@ -364,55 +347,43 @@
#
# build the whine list by event id
for my $event_id (keys %{$events}) {
-
$events->{$event_id}->{'schedule'} = [];
$events->{$event_id}->{'queries'} = [];
# schedules
- $sth = $dbh->prepare("SELECT run_day, run_time, mailto_type, mailto, id " .
- "FROM whine_schedules " .
- "WHERE eventid=?");
- $sth->execute($event_id);
- for my $row (@{$sth->fetchall_arrayref}) {
- my $mailto_type = $row->[2];
+ my $schedules = Bugzilla::Whine::Schedule->match({ eventid => $event_id });
+ foreach my $schedule (@$schedules) {
+ my $mailto_type = $schedule->mailto_is_group ? MAILTO_GROUP
+ : MAILTO_USER;
my $mailto = '';
if ($mailto_type == MAILTO_USER) {
- my $mailto_user = new Bugzilla::User($row->[3]);
- $mailto = $mailto_user->login;
+ $mailto = $schedule->mailto->login;
}
elsif ($mailto_type == MAILTO_GROUP) {
- $sth = $dbh->prepare("SELECT name FROM groups WHERE id=?");
- $sth->execute($row->[3]);
- $mailto = $sth->fetch->[0];
- $mailto = "" unless Bugzilla::Group::ValidateGroupName(
- $mailto, ($user));
+ $mailto = $schedule->mailto->name;
}
- my $this_schedule = {
- 'day' => $row->[0],
- 'time' => $row->[1],
- 'mailto_type' => $mailto_type,
- 'mailto' => $mailto,
- 'id' => $row->[4],
- };
- push @{$events->{$event_id}->{'schedule'}}, $this_schedule;
+
+ push @{$events->{$event_id}->{'schedule'}},
+ {
+ 'day' => $schedule->run_day,
+ 'time' => $schedule->run_time,
+ 'mailto_type' => $mailto_type,
+ 'mailto' => $mailto,
+ 'id' => $schedule->id,
+ };
}
# queries
- $sth = $dbh->prepare("SELECT query_name, title, sortkey, id, " .
- "onemailperbug " .
- "FROM whine_queries " .
- "WHERE eventid=? " .
- "ORDER BY sortkey");
- $sth->execute($event_id);
- for my $row (@{$sth->fetchall_arrayref}) {
- my $this_query = {
- 'name' => $row->[0],
- 'title' => $row->[1],
- 'sort' => $row->[2],
- 'id' => $row->[3],
- 'onemailperbug' => $row->[4],
- };
- push @{$events->{$event_id}->{'queries'}}, $this_query;
+ my $queries = Bugzilla::Whine::Query->match({ eventid => $event_id });
+ for my $query (@$queries) {
+ push @{$events->{$event_id}->{'queries'}},
+ {
+ 'name' => $query->name,
+ 'title' => $query->title,
+ 'sort' => $query->sortkey,
+ 'id' => $query->id,
+ 'onemailperbug' => $query->one_email_per_bug,
+ };
}
}
@@ -427,27 +398,18 @@
push @{$vars->{'available_queries'}}, $query;
}
$vars->{'token'} = issue_session_token('edit_whine');
+$vars->{'local_timezone'} = Bugzilla->local_timezone->short_name_for_datetime(DateTime->now());
$template->process("whine/schedule.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
-# get_events takes a userid and returns a hash, keyed by event ID, containing
-# the subject and body of each event that user owns
+# get_events takes a userid and returns a hash of
+# Bugzilla::Whine objects keyed by event ID.
sub get_events {
my $userid = shift;
- my $dbh = Bugzilla->dbh;
- my $events = {};
+ my $event_rows = Bugzilla::Whine->match({ owner_userid => $userid });
+ my %events = map { $_->{id} => $_ } @$event_rows;
- my $sth = $dbh->prepare("SELECT DISTINCT id, subject, body " .
- "FROM whine_events " .
- "WHERE owner_userid=?");
- $sth->execute($userid);
- while (my ($ev, $sub, $bod) = $sth->fetchrow_array) {
- $events->{$ev} = {
- 'subject' => $sub || '',
- 'body' => $bod || '',
- };
- }
- return $events;
+ return \%events;
}
diff --git a/Websites/bugs.webkit.org/editworkflow.cgi b/Websites/bugs.webkit.org/editworkflow.cgi
index 3c33c72..c96e500 100755
--- a/Websites/bugs.webkit.org/editworkflow.cgi
+++ b/Websites/bugs.webkit.org/editworkflow.cgi
@@ -147,5 +147,5 @@
load_template('comment', 'workflow_updated');
}
else {
- ThrowCodeError("action_unrecognized", {action => $action});
+ ThrowUserError('unknown_action', {action => $action});
}
diff --git a/Websites/bugs.webkit.org/email_in.pl b/Websites/bugs.webkit.org/email_in.pl
index 84cd896..3b4f883 100755
--- a/Websites/bugs.webkit.org/email_in.pl
+++ b/Websites/bugs.webkit.org/email_in.pl
@@ -1,4 +1,4 @@
-#!/usr/bin/env perl -w
+#!/usr/bin/env perl -wT
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
@@ -24,9 +24,12 @@
# MTAs may call this script from any directory, but it should always
# run from this one so that it can find its modules.
+use Cwd qw(abs_path);
+use File::Basename qw(dirname);
BEGIN {
- require File::Basename;
- chdir(File::Basename::dirname($0));
+ # Untaint the abs_path.
+ my ($a) = abs_path($0) =~ /^(.*)$/;
+ chdir dirname($a);
}
use lib qw(. lib);
@@ -39,15 +42,19 @@
use Getopt::Long qw(:config bundling);
use Pod::Usage;
use Encode;
+use Scalar::Util qw(blessed);
use Bugzilla;
-use Bugzilla::Bug qw(ValidateBugID);
-use Bugzilla::Constants qw(USAGE_MODE_EMAIL);
+use Bugzilla::Attachment;
+use Bugzilla::Bug;
+use Bugzilla::BugMail;
+use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Mailer;
+use Bugzilla::Token;
use Bugzilla::User;
use Bugzilla::Util;
-use Bugzilla::Token;
+use Bugzilla::Hook;
#############
# Constants #
@@ -69,17 +76,24 @@
debug_print('Parsing Email');
$input_email = Email::MIME->new($mail_text);
- my %fields;
+ my %fields = %{ $switch{'default'} || {} };
+ Bugzilla::Hook::process('email_in_before_parse', { mail => $input_email,
+ fields => \%fields });
- # Email::Address->parse returns an array
- my ($reporter) = Email::Address->parse($input_email->header('From'));
- $fields{'reporter'} = $reporter->address;
my $summary = $input_email->header('Subject');
- if ($summary =~ /\[Bug (\d+)\](.*)/i) {
+ if ($summary =~ /\[\S+ (\d+)\](.*)/i) {
$fields{'bug_id'} = $1;
$summary = trim($2);
}
+ # Ignore automatic replies.
+ # XXX - Improve the way to detect such subjects in different languages.
+ my $auto_submitted = $input_email->header('Auto-Submitted') || '';
+ if ($summary =~ /out of( the)? office/i || $auto_submitted eq 'auto-replied') {
+ debug_print("Automatic reply detected: $summary");
+ exit;
+ }
+
my ($body, $attachments) = get_body_and_attachments($input_email);
if (@$attachments) {
$fields{'attachments'} = $attachments;
@@ -104,19 +118,8 @@
# Otherwise, we stop parsing fields on the first blank line.
$line = trim($line);
last if !$line;
-
- if ($line =~ /^@(\S+)\s*=\s*(.*)\s*/) {
+ if ($line =~ /^\@(\w+)\s*(?:=|\s|$)\s*(.*)\s*/) {
$current_field = lc($1);
- # It's illegal to pass the reporter field as you could
- # override the "From:" field of the message and bypass
- # authentication checks, such as PGP.
- if ($current_field eq 'reporter') {
- # We reset the $current_field variable to something
- # post_bug and process_bug will ignore, in case the
- # attacker splits the reporter field on several lines.
- $current_field = 'illegal_field';
- next;
- }
$fields{$current_field} = $2;
}
else {
@@ -125,6 +128,10 @@
}
}
+ %fields = %{ Bugzilla::Bug::map_fields(\%fields) };
+
+ my ($reporter) = Email::Address->parse($input_email->header('From'));
+ $fields{'reporter'} = $reporter->address;
# The summary line only affects us if we're doing a post_bug.
# We have to check it down here because there might have been
@@ -141,30 +148,47 @@
}
$fields{'comment'} = $comment;
+ my %override = %{ $switch{'override'} || {} };
+ foreach my $key (keys %override) {
+ $fields{$key} = $override{$key};
+ }
+
debug_print("Parsed Fields:\n" . Dumper(\%fields), 2);
return \%fields;
}
-sub post_bug {
- my ($fields_in) = @_;
- my %fields = %$fields_in;
+sub check_email_fields {
+ my ($fields) = @_;
+ my ($retval, $non_conclusive_fields) =
+ Bugzilla::User::match_field({
+ 'assigned_to' => { 'type' => 'single' },
+ 'qa_contact' => { 'type' => 'single' },
+ 'cc' => { 'type' => 'multi' },
+ 'newcc' => { 'type' => 'multi' }
+ }, $fields, MATCH_SKIP_CONFIRM);
+
+ if ($retval != USER_MATCH_SUCCESS) {
+ ThrowUserError('user_match_too_many', {fields => $non_conclusive_fields});
+ }
+}
+
+sub post_bug {
+ my ($fields) = @_;
debug_print('Posting a new bug...');
- my $cgi = Bugzilla->cgi;
- foreach my $field (keys %fields) {
- $cgi->param(-name => $field, -value => $fields{$field});
- }
+ my $user = Bugzilla->user;
- $cgi->param(-name => 'inbound_email', -value => 1);
+ check_email_fields($fields);
- require 'post_bug.cgi';
+ my $bug = Bugzilla::Bug->create($fields);
+ debug_print("Created bug " . $bug->id);
+ return ($bug, $bug->comments->[0]);
}
sub process_bug {
my ($fields_in) = @_;
-
my %fields = %$fields_in;
my $bug_id = $fields{'bug_id'};
@@ -173,8 +197,7 @@
debug_print("Updating Bug $fields{id}...");
- ValidateBugID($bug_id);
- my $bug = new Bugzilla::Bug($bug_id);
+ my $bug = Bugzilla::Bug->check($bug_id);
if ($fields{'bug_status'}) {
$fields{'knob'} = $fields{'bug_status'};
@@ -198,14 +221,63 @@
$fields{'removecc'} = 1;
}
+ check_email_fields(\%fields);
+
my $cgi = Bugzilla->cgi;
foreach my $field (keys %fields) {
$cgi->param(-name => $field, -value => $fields{$field});
}
- $cgi->param('longdesclength', scalar $bug->longdescs);
+ $cgi->param('longdesclength', scalar @{ $bug->comments });
$cgi->param('token', issue_hash_token([$bug->id, $bug->delta_ts]));
require 'process_bug.cgi';
+ debug_print("Bug processed.");
+
+ my $added_comment;
+ if (trim($fields{'comment'})) {
+ $added_comment = $bug->comments->[-1];
+ }
+ return ($bug, $added_comment);
+}
+
+sub handle_attachments {
+ my ($bug, $attachments, $comment) = @_;
+ return if !$attachments;
+ debug_print("Handling attachments...");
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+ my ($update_comment, $update_bug);
+ foreach my $attachment (@$attachments) {
+ my $data = delete $attachment->{payload};
+ debug_print("Inserting Attachment: " . Dumper($attachment), 2);
+ $attachment->{content_type} ||= 'application/octet-stream';
+ my $obj = Bugzilla::Attachment->create({
+ bug => $bug,
+ description => $attachment->{filename},
+ filename => $attachment->{filename},
+ mimetype => $attachment->{content_type},
+ data => $data,
+ });
+ # If we added a comment, and our comment does not already have a type,
+ # and this is our first attachment, then we make the comment an
+ # "attachment created" comment.
+ if ($comment and !$comment->type and !$update_comment) {
+ $comment->set_all({ type => CMT_ATTACHMENT_CREATED,
+ extra_data => $obj->id });
+ $update_comment = 1;
+ }
+ else {
+ $bug->add_comment('', { type => CMT_ATTACHMENT_CREATED,
+ extra_data => $obj->id });
+ $update_bug = 1;
+ }
+ }
+ # We only update the comments and bugs at the end of the transaction,
+ # because doing so modifies bugs_fulltext, which is a non-transactional
+ # table.
+ $bug->update() if $update_bug;
+ $comment->update() if $update_comment;
+ $dbh->bz_commit_transaction();
}
######################
@@ -226,7 +298,7 @@
my $body;
my $attachments = [];
- if ($ct =~ /^multipart\/alternative/i) {
+ if ($ct =~ /^multipart\/(alternative|signed)/i) {
$body = get_text_alternative($email);
}
else {
@@ -301,7 +373,8 @@
# In Template-Toolkit, [% RETURN %] is implemented as a call to "die".
# But of course, we really don't want to actually *die* just because
# the user-error or code-error template ended. So we don't really die.
- return if $msg->isa('Template::Exception') && $msg->type eq 'return';
+ return if blessed($msg) && $msg->isa('Template::Exception')
+ && $msg->type eq 'return';
# If this is inside an eval, then we should just act like...we're
# in an eval (instead of printing the error and exiting).
@@ -330,7 +403,7 @@
$SIG{__DIE__} = \&die_handler;
-GetOptions(\%switch, 'help|h', 'verbose|v+');
+GetOptions(\%switch, 'help|h', 'verbose|v+', 'default=s%', 'override=s%');
$switch{'verbose'} ||= 0;
# Print the help message if that switch was selected.
@@ -338,29 +411,44 @@
Bugzilla->usage_mode(USAGE_MODE_EMAIL);
-
my @mail_lines = <STDIN>;
my $mail_text = join("", @mail_lines);
my $mail_fields = parse_mail($mail_text);
+Bugzilla::Hook::process('email_in_after_parse', { fields => $mail_fields });
+
+my $attachments = delete $mail_fields->{'attachments'};
+
my $username = $mail_fields->{'reporter'};
# If emailsuffix is in use, we have to remove it from the email address.
if (my $suffix = Bugzilla->params->{'emailsuffix'}) {
$username =~ s/\Q$suffix\E$//i;
}
-my $user = Bugzilla::User->new({ name => $username })
- || ThrowUserError('invalid_username', { name => $username });
-
+my $user = Bugzilla::User->check($username);
Bugzilla->set_user($user);
+my ($bug, $comment);
if ($mail_fields->{'bug_id'}) {
- process_bug($mail_fields);
+ ($bug, $comment) = process_bug($mail_fields);
}
else {
- post_bug($mail_fields);
+ ($bug, $comment) = post_bug($mail_fields);
}
+handle_attachments($bug, $attachments, $comment);
+
+# This is here for post_bug and handle_attachments, so that when posting a bug
+# with an attachment, any comment goes out as an attachment comment.
+#
+# Eventually this should be sending the mail for process_bug, too, but we have
+# to wait for $bug->update() to be fully used in email_in.pl first. So
+# currently, process_bug.cgi does the mail sending for bugs, and this does
+# any mail sending for attachments after the first one.
+Bugzilla::BugMail::Send($bug->id, { changer => Bugzilla->user });
+debug_print("Sent bugmail");
+
+
__END__
=head1 NAME
@@ -369,13 +457,22 @@
=head1 SYNOPSIS
- ./email_in.pl [-vvv] < email.txt
+./email_in.pl [-vvv] [--default name=value] [--override name=value] < email.txt
- Reads an email on STDIN (the standard input).
+Reads an email on STDIN (the standard input).
- Options:
- --verbose (-v) - Make the script print more to STDERR.
- Specify multiple times to print even more.
+Options:
+
+ --verbose (-v) - Make the script print more to STDERR.
+ Specify multiple times to print even more.
+
+ --default name=value - Specify defaults for field values, like
+ product=TestProduct. Can be specified multiple
+ times to specify defaults for multiple fields.
+
+ --override name=value - Override field values specified in the email,
+ like product=TestProduct. Can be specified
+ multiple times to override multiple fields.
=head1 DESCRIPTION
@@ -389,9 +486,9 @@
From: account@domain.com
Subject: Bug Summary
- @product = ProductName
- @component = ComponentName
- @version = 1.0
+ @product ProductName
+ @component ComponentName
+ @version 1.0
This is a bug description. It will be entered into the bug exactly as
written here.
@@ -402,39 +499,25 @@
This is a signature line, and will be removed automatically, It will not
be included in the bug description.
-The C<@> labels can be any valid field name in Bugzilla that can be
-set on C<enter_bug.cgi>. For the list of required field names, see
-L<Bugzilla::WebService::Bug/Create>. Note, that there is some difference
-in the names of the required input fields between web and email interfaces,
-as listed below:
-
-=over
-
-=item *
-
-C<platform> in web is C<@rep_platform> in email
-
-=item *
-
-C<severity> in web is C<@bug_severity> in email
-
-=back
-
-For the list of all field names, see the C<fielddefs> table in the database.
+For the list of valid field names for the C<@> fields, including
+a list of which ones are required, see L<Bugzilla::WebService::Bug/create>.
+(Note, however, that you cannot specify C<@description> as a field--
+you just add a comment by adding text after the C<@> fields.)
The values for the fields can be split across multiple lines, but
note that a newline will be parsed as a single space, for the value.
So, for example:
- @short_desc = This is a very long
+ @summary This is a very long
description
Will be parsed as "This is a very long description".
-If you specify C<@short_desc>, it will override the summary you specify
+If you specify C<@summary>, it will override the summary you specify
in the Subject header.
-C<account@domain.com> must be a valid Bugzilla account.
+C<account@domain.com> (the value of the C<From> header) must be a valid
+Bugzilla account.
Note that signatures must start with '-- ', the standard signature
border.
@@ -451,11 +534,11 @@
=item *
-You include C<@bug_id = 123456> in the first lines of the email.
+You include C<@id 123456> in the first lines of the email.
=back
-If you do both, C<@bug_id> takes precedence.
+If you do both, C<@id> takes precedence.
You send your email in the same format as for creating a bug, except
that you only specify the fields you want to change. If the very
@@ -464,7 +547,7 @@
Note that when updating a bug, the C<Subject> header is ignored,
except for getting the bug ID. If you want to change the bug's summary,
-you have to specify C<@short_desc> as one of the fields to change.
+you have to specify C<@summary> as one of the fields to change.
Please remember not to include any extra text in your emails, as that
text will also be added as a comment. This includes any text that your
@@ -474,8 +557,6 @@
=head3 Adding/Removing CCs
To add CCs, you can specify them in a comma-separated list in C<@cc>.
-For backward compatibility, C<@newcc> can also be used. If both are
-present, C<@cc> takes precedence.
To remove CCs, specify them as a comma-separated list in C<@removecc>.
@@ -488,8 +569,6 @@
If any part of your request fails, all of it will fail. No partial
changes will happen.
-There is no attachment support yet.
-
=head1 CAUTION
The script does not do any validation that the user is who they say
@@ -500,12 +579,8 @@
=head1 LIMITATIONS
-Note that the email interface has the same limitations as the
-normal Bugzilla interface. So, for example, you cannot reassign
-a bug and change its status at the same time.
-
The email interface only accepts emails that are correctly formatted
-perl RFC2822. If you send it an incorrectly formatted message, it
+per RFC2822. If you send it an incorrectly formatted message, it
may behave in an unpredictable fashion.
You cannot send an HTML mail along with attachments. If you do, Bugzilla
diff --git a/Websites/bugs.webkit.org/enter_bug.cgi b/Websites/bugs.webkit.org/enter_bug.cgi
index 66a040a..2fa5aa4 100755
--- a/Websites/bugs.webkit.org/enter_bug.cgi
+++ b/Websites/bugs.webkit.org/enter_bug.cgi
@@ -22,6 +22,7 @@
# Joe Robins <jmrobins@tgix.com>
# Gervase Markham <gerv@gerv.net>
# Shane H. W. Travis <travis@sedsystems.ca>
+# Nitish Bezzala <nbezzala@yahoo.com>
##############################################################################
#
@@ -152,15 +153,10 @@
$product = $enterable_products[0];
}
}
-else {
- # Do not use Bugzilla::Product::check_product() here, else the user
- # could know whether the product doesn't exist or is not accessible.
- $product = new Bugzilla::Product({'name' => $product_name});
-}
# We need to check and make sure that the user has permission
# to enter a bug against this product.
-$user->can_enter_product($product ? $product->name : $product_name, THROW_ERROR);
+$product = $user->can_enter_product($product || $product_name, THROW_ERROR);
##############################################################################
# Useful Subroutines
@@ -199,44 +195,54 @@
# no choice is valid, we return "Other".
for ($ENV{'HTTP_USER_AGENT'}) {
#PowerPC
- /\(.*PowerPC.*\)/i && do {@platform = "Macintosh"; last;};
- /\(.*PPC.*\)/ && do {@platform = "Macintosh"; last;};
- /\(.*AIX.*\)/ && do {@platform = "Macintosh"; last;};
+ /\(.*PowerPC.*\)/i && do {push @platform, ("PowerPC", "Macintosh");};
+ #AMD64, Intel x86_64
+ /\(.*amd64.*\)/ && do {push @platform, ("AMD64", "x86_64", "PC");};
+ /\(.*x86_64.*\)/ && do {push @platform, ("AMD64", "x86_64", "PC");};
+ #Intel Itanium
+ /\(.*IA64.*\)/ && do {push @platform, "IA64";};
#Intel x86
- /\(.*Intel.*\)/ && do {@platform = "PC"; last;};
- /\(.*[ix0-9]86.*\)/ && do {@platform = "PC"; last;};
+ /\(.*Intel.*\)/ && do {push @platform, ("IA32", "x86", "PC");};
+ /\(.*[ix0-9]86.*\)/ && do {push @platform, ("IA32", "x86", "PC");};
#Versions of Windows that only run on Intel x86
- /\(.*Win(?:dows |)[39M].*\)/ && do {@platform = "PC"; last};
- /\(.*Win(?:dows |)16.*\)/ && do {@platform = "PC"; last;};
+ /\(.*Win(?:dows |)[39M].*\)/ && do {push @platform, ("IA32", "x86", "PC");};
+ /\(.*Win(?:dows |)16.*\)/ && do {push @platform, ("IA32", "x86", "PC");};
#Sparc
- /\(.*sparc.*\)/ && do {@platform = "Sun"; last;};
- /\(.*sun4.*\)/ && do {@platform = "Sun"; last;};
+ /\(.*sparc.*\)/ && do {push @platform, ("Sparc", "Sun");};
+ /\(.*sun4.*\)/ && do {push @platform, ("Sparc", "Sun");};
#Alpha
- /\(.*AXP.*\)/i && do {@platform = "DEC"; last;};
- /\(.*[ _]Alpha.\D/i && do {@platform = "DEC"; last;};
- /\(.*[ _]Alpha\)/i && do {@platform = "DEC"; last;};
+ /\(.*AXP.*\)/i && do {push @platform, ("Alpha", "DEC");};
+ /\(.*[ _]Alpha.\D/i && do {push @platform, ("Alpha", "DEC");};
+ /\(.*[ _]Alpha\)/i && do {push @platform, ("Alpha", "DEC");};
#MIPS
- /\(.*IRIX.*\)/i && do {@platform = "SGI"; last;};
- /\(.*MIPS.*\)/i && do {@platform = "SGI"; last;};
+ /\(.*IRIX.*\)/i && do {push @platform, ("MIPS", "SGI");};
+ /\(.*MIPS.*\)/i && do {push @platform, ("MIPS", "SGI");};
#68k
- /\(.*68K.*\)/ && do {@platform = "Macintosh"; last;};
- /\(.*680[x0]0.*\)/ && do {@platform = "Macintosh"; last;};
+ /\(.*68K.*\)/ && do {push @platform, ("68k", "Macintosh");};
+ /\(.*680[x0]0.*\)/ && do {push @platform, ("68k", "Macintosh");};
#HP
- /\(.*9000.*\)/ && do {@platform = "HP"; last;};
+ /\(.*9000.*\)/ && do {push @platform, ("PA-RISC", "HP");};
#ARM
-# /\(.*ARM.*\) && do {$platform = "ARM";};
+ /\(.*ARM.*\)/ && do {push @platform, ("ARM", "PocketPC");};
+ #PocketPC intentionally before PowerPC
+ /\(.*Windows CE.*PPC.*\)/ && do {push @platform, ("ARM", "PocketPC");};
+ #PowerPC
+ /\(.*PPC.*\)/ && do {push @platform, ("PowerPC", "Macintosh");};
+ /\(.*AIX.*\)/ && do {push @platform, ("PowerPC", "Macintosh");};
#Stereotypical and broken
- /\(.*Macintosh.*\)/ && do {@platform = "Macintosh"; last;};
- /\(.*Mac OS [89].*\)/ && do {@platform = "Macintosh"; last;};
- /\(Win.*\)/ && do {@platform = "PC"; last;};
- /\(.*Win(?:dows[ -])NT.*\)/ && do {@platform = "PC"; last;};
- /\(.*OSF.*\)/ && do {@platform = "DEC"; last;};
- /\(.*HP-?UX.*\)/i && do {@platform = "HP"; last;};
- /\(.*IRIX.*\)/i && do {@platform = "SGI"; last;};
- /\(.*(SunOS|Solaris).*\)/ && do {@platform = "Sun"; last;};
+ /\(.*Windows CE.*\)/ && do {push @platform, ("ARM", "PocketPC");};
+ /\(.*Macintosh.*\)/ && do {push @platform, ("68k", "Macintosh");};
+ /\(.*Mac OS [89].*\)/ && do {push @platform, ("68k", "Macintosh");};
+ /\(.*Win64.*\)/ && do {push @platform, "IA64";};
+ /\(Win.*\)/ && do {push @platform, ("IA32", "x86", "PC");};
+ /\(.*Win(?:dows[ -])NT.*\)/ && do {push @platform, ("IA32", "x86", "PC");};
+ /\(.*OSF.*\)/ && do {push @platform, ("Alpha", "DEC");};
+ /\(.*HP-?UX.*\)/i && do {push @platform, ("PA-RISC", "HP");};
+ /\(.*IRIX.*\)/i && do {push @platform, ("MIPS", "SGI");};
+ /\(.*(SunOS|Solaris).*\)/ && do {push @platform, ("Sparc", "Sun");};
#Braindead old browsers who didn't follow convention:
- /Amiga/ && do {@platform = "Macintosh"; last;};
- /WinMosaic/ && do {@platform = "PC"; last;};
+ /Amiga/ && do {push @platform, ("68k", "Macintosh");};
+ /WinMosaic/ && do {push @platform, ("IA32", "x86", "PC");};
}
}
@@ -257,7 +263,7 @@
# item in @os that is a valid platform choice. If
# no choice is valid, we return "Other".
for ($ENV{'HTTP_USER_AGENT'}) {
- /\(.*IRIX.*\)/ && do {push @os, "IRIX"; };
+ /\(.*IRIX.*\)/ && do {push @os, "IRIX";};
/\(.*OSF.*\)/ && do {push @os, "OSF/1";};
/\(.*Linux.*\)/ && do {push @os, "Linux";};
/\(.*Solaris.*\)/ && do {push @os, "Solaris";};
@@ -288,6 +294,8 @@
/\(.*VMS.*\)/ && do {push @os, "OpenVMS";};
/\(.*Win.*\)/ && do {
/\(.*Windows XP.*\)/ && do {push @os, "Windows XP";};
+ /\(.*Windows NT 6\.2.*\)/ && do {push @os, "Windows 8";};
+ /\(.*Windows NT 6\.1.*\)/ && do {push @os, "Windows 7";};
/\(.*Windows NT 6\.0.*\)/ && do {push @os, "Windows Vista";};
/\(.*Windows NT 5\.2.*\)/ && do {push @os, "Windows Server 2003";};
/\(.*Windows NT 5\.1.*\)/ && do {push @os, "Windows XP";};
@@ -302,8 +310,19 @@
/\(.*Windows.*NT.*\)/ && do {push @os, "Windows NT";};
};
/\(.*Mac OS X.*\)/ && do {
- /\(.*Intel.*Mac OS X 10.5.*\)/ && do {push @os, "Mac OS X 10.5";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.7.*\)/ && do {push @os, "Mac OS X 10.7";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.6.*\)/ && do {push @os, "Mac OS X 10.6";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.5.*\)/ && do {push @os, "Mac OS X 10.5";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.4.*\)/ && do {push @os, "Mac OS X 10.4";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.3.*\)/ && do {push @os, "Mac OS X 10.3";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.2.*\)/ && do {push @os, "Mac OS X 10.2";};
+ /\(.*Mac OS X (?:|Mach-O |\()10.1.*\)/ && do {push @os, "Mac OS X 10.1";};
+ # Unfortunately, OS X 10.4 was the first to support Intel. This is
+ # fallback support because some browsers refused to include the OS
+ # Version.
/\(.*Intel.*Mac OS X.*\)/ && do {push @os, "Mac OS X 10.4";};
+ # OS X 10.3 is the most likely default version of PowerPC Macs
+ # OS X 10.0 is more for configurations which didn't setup 10.x versions
/\(.*Mac OS X.*\)/ && do {push @os, ("Mac OS X 10.3", "Mac OS X 10.0", "Mac OS X");};
};
/\(.*32bit.*\)/ && do {push @os, "Windows 95";};
@@ -351,8 +370,8 @@
$cloned_bug_id = $cgi->param('cloned_bug_id');
if ($cloned_bug_id) {
- ValidateBugID($cloned_bug_id);
- $cloned_bug = new Bugzilla::Bug($cloned_bug_id);
+ $cloned_bug = Bugzilla::Bug->check($cloned_bug_id);
+ $cloned_bug_id = $cloned_bug->id;
}
if (scalar(@{$product->components}) == 1) {
@@ -369,8 +388,6 @@
$vars->{'rep_platform'} = get_legal_field_values('rep_platform');
$vars->{'op_sys'} = get_legal_field_values('op_sys');
-$vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
-
$vars->{'assigned_to'} = formvalue('assigned_to');
$vars->{'assigned_to_disabled'} = !$has_editbugs;
$vars->{'cc_disabled'} = 0;
@@ -380,14 +397,26 @@
$vars->{'cloned_bug_id'} = $cloned_bug_id;
-$vars->{'token'} = issue_session_token('createbug:');
+$vars->{'token'} = issue_session_token('create_bug');
my @enter_bug_fields = grep { $_->enter_bug } Bugzilla->active_custom_fields;
foreach my $field (@enter_bug_fields) {
- $vars->{$field->name} = formvalue($field->name);
+ my $cf_name = $field->name;
+ my $cf_value = $cgi->param($cf_name);
+ if (defined $cf_value) {
+ if ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ $cf_value = [$cgi->param($cf_name)];
+ }
+ $default{$cf_name} = $vars->{$cf_name} = $cf_value;
+ }
}
+# This allows the Field visibility and value controls to work with the
+# Classification and Product fields as a parent.
+$default{'classification'} = $product->classification->name;
+$default{'product'} = $product->name;
+
if ($cloned_bug_id) {
$default{'component_'} = $cloned_bug->component;
@@ -399,15 +428,20 @@
$vars->{'short_desc'} = $cloned_bug->short_desc;
$vars->{'bug_file_loc'} = $cloned_bug->bug_file_loc;
$vars->{'keywords'} = $cloned_bug->keywords;
- $vars->{'dependson'} = $cloned_bug_id;
- $vars->{'blocked'} = "";
+ $vars->{'dependson'} = join (", ", $cloned_bug_id, @{$cloned_bug->dependson});
+ $vars->{'blocked'} = join (", ", @{$cloned_bug->blocked});
$vars->{'deadline'} = $cloned_bug->deadline;
+ $vars->{'estimated_time'} = $cloned_bug->estimated_time;
if (defined $cloned_bug->cc) {
$vars->{'cc'} = join (", ", @{$cloned_bug->cc});
} else {
$vars->{'cc'} = formvalue('cc');
}
+
+ if ($cloned_bug->reporter->id != $user->id) {
+ $vars->{'cc'} = join (", ", $cloned_bug->reporter->login, $vars->{'cc'});
+ }
foreach my $field (@enter_bug_fields) {
my $field_name = $field->name;
@@ -417,18 +451,17 @@
# We need to ensure that we respect the 'insider' status of
# the first comment, if it has one. Either way, make a note
# that this bug was cloned from another bug.
- # We cannot use $cloned_bug->longdescs because this method
- # depends on the "comment_sort_order" user pref, and we
- # really want the first comment of the bug.
- my $bug_desc = Bugzilla::Bug::GetComments($cloned_bug_id, 'oldest_to_newest');
- my $isprivate = $bug_desc->[0]->{'isprivate'};
+ my $bug_desc = $cloned_bug->comments({ order => 'oldest_to_newest' })->[0];
+ my $isprivate = $bug_desc->is_private;
- $vars->{'comment'} = "";
- $vars->{'commentprivacy'} = 0;
+ $vars->{'comment'} = "";
+ $vars->{'comment_is_private'} = 0;
if (!$isprivate || Bugzilla->user->is_insider) {
- $vars->{'comment'} = $bug_desc->[0]->{'body'};
- $vars->{'commentprivacy'} = $isprivate;
+ # We use "body" to avoid any format_comment text, which would be
+ # pointless to clone.
+ $vars->{'comment'} = $bug_desc->body;
+ $vars->{'comment_is_private'} = $isprivate;
}
} # end of cloned bug entry form
@@ -441,17 +474,19 @@
$default{'rep_platform'} = pickplatform();
$default{'op_sys'} = pickos();
+ $vars->{'alias'} = formvalue('alias');
$vars->{'short_desc'} = formvalue('short_desc');
$vars->{'bug_file_loc'} = formvalue('bug_file_loc', "http://");
$vars->{'keywords'} = formvalue('keywords');
$vars->{'dependson'} = formvalue('dependson');
$vars->{'blocked'} = formvalue('blocked');
$vars->{'deadline'} = formvalue('deadline');
+ $vars->{'estimated_time'} = formvalue('estimated_time');
$vars->{'cc'} = join(', ', $cgi->param('cc'));
$vars->{'comment'} = formvalue('comment');
- $vars->{'commentprivacy'} = formvalue('commentprivacy');
+ $vars->{'comment_is_private'} = formvalue('comment_is_private');
} # end of normal/bookmarked entry form
@@ -469,23 +504,26 @@
#
# Eventually maybe each product should have a "current version"
# parameter.
-$vars->{'version'} = [map($_->name, @{$product->versions})];
+$vars->{'version'} = $product->versions;
+
+my $version_cookie = $cgi->cookie("VERSION-" . $product->name);
if ( ($cloned_bug_id) &&
($product->name eq $cloned_bug->product ) ) {
$default{'version'} = $cloned_bug->version;
} elsif (formvalue('version')) {
$default{'version'} = formvalue('version');
-} elsif (defined $cgi->cookie("VERSION-" . $product->name) &&
- lsearch($vars->{'version'}, $cgi->cookie("VERSION-" . $product->name)) != -1) {
- $default{'version'} = $cgi->cookie("VERSION-" . $product->name);
+} elsif (defined $version_cookie
+ and grep { $_->name eq $version_cookie } @{ $vars->{'version'} })
+{
+ $default{'version'} = $version_cookie;
} else {
- $default{'version'} = $vars->{'version'}->[$#{$vars->{'version'}}];
+ $default{'version'} = $vars->{'version'}->[$#{$vars->{'version'}}]->name;
}
# Get list of milestones.
if ( Bugzilla->params->{'usetargetmilestone'} ) {
- $vars->{'target_milestone'} = [map($_->name, @{$product->milestones})];
+ $vars->{'target_milestone'} = $product->milestones;
if (formvalue('target_milestone')) {
$default{'target_milestone'} = formvalue('target_milestone');
} else {
@@ -494,100 +532,54 @@
}
# Construct the list of allowable statuses.
-my $initial_statuses = Bugzilla::Status->can_change_to();
+my @statuses = @{ Bugzilla::Status->can_change_to() };
# Exclude closed states from the UI, even if the workflow allows them.
# The back-end code will still accept them, though.
-@$initial_statuses = grep { $_->is_open } @$initial_statuses;
+@statuses = grep { $_->is_open } @statuses;
-my @status = map { $_->name } @$initial_statuses;
-# UNCONFIRMED is illegal if votes_to_confirm = 0.
-@status = grep {$_ ne 'UNCONFIRMED'} @status unless $product->votes_to_confirm;
-scalar(@status) || ThrowUserError('no_initial_bug_status');
+# UNCONFIRMED is illegal if allows_unconfirmed is false.
+if (!$product->allows_unconfirmed) {
+ @statuses = grep { $_->name ne 'UNCONFIRMED' } @statuses;
+}
+scalar(@statuses) || ThrowUserError('no_initial_bug_status');
# If the user has no privs...
unless ($has_editbugs || $has_canconfirm) {
# ... use UNCONFIRMED if available, else use the first status of the list.
- my $bug_status = (grep {$_ eq 'UNCONFIRMED'} @status) ? 'UNCONFIRMED' : $status[0];
- @status = ($bug_status);
+ my ($unconfirmed) = grep { $_->name eq 'UNCONFIRMED' } @statuses;
+
+ # Because of an apparent Perl bug, "$unconfirmed || $statuses[0]" doesn't
+ # work, so we're using an "?:" operator. See bug 603314 for details.
+ @statuses = ($unconfirmed ? $unconfirmed : $statuses[0]);
}
-$vars->{'bug_status'} = \@status;
+$vars->{'bug_status'} = \@statuses;
# Get the default from a template value if it is legitimate.
# Otherwise, and only if the user has privs, set the default
# to the first confirmed bug status on the list, if available.
-if (formvalue('bug_status') && (lsearch(\@status, formvalue('bug_status')) >= 0)) {
+my $picked_status = formvalue('bug_status');
+if ($picked_status and grep($_->name eq $picked_status, @statuses)) {
$default{'bug_status'} = formvalue('bug_status');
-} elsif (scalar @status == 1) {
- $default{'bug_status'} = $status[0];
+} elsif (scalar @statuses == 1) {
+ $default{'bug_status'} = $statuses[0]->name;
}
else {
- $default{'bug_status'} = ($status[0] ne 'UNCONFIRMED') ? $status[0] : $status[1];
+ $default{'bug_status'} = ($statuses[0]->name ne 'UNCONFIRMED')
+ ? $statuses[0]->name : $statuses[1]->name;
}
-my $grouplist = $dbh->selectall_arrayref(
- q{SELECT DISTINCT groups.id, groups.name, groups.description,
- membercontrol, othercontrol
- FROM groups
- LEFT JOIN group_control_map
- ON group_id = id AND product_id = ?
- WHERE isbuggroup != 0 AND isactive != 0
- ORDER BY description}, undef, $product->id);
-
-my @groups;
-
-foreach my $row (@$grouplist) {
- my ($id, $groupname, $description, $membercontrol, $othercontrol) = @$row;
- # Only include groups if the entering user will have an option.
- next if ((!$membercontrol)
- || ($membercontrol == CONTROLMAPNA)
- || ($membercontrol == CONTROLMAPMANDATORY)
- || (($othercontrol != CONTROLMAPSHOWN)
- && ($othercontrol != CONTROLMAPDEFAULT)
- && (!Bugzilla->user->in_group($groupname)))
- );
- my $check;
-
- # If this is a cloned bug,
- # AND the product for this bug is the same as for the original
- # THEN set a group's checkbox if the original also had it on
- # ELSE IF this is a bookmarked template
- # THEN set a group's checkbox if was set in the bookmark
- # ELSE
- # set a groups's checkbox based on the group control map
- #
- if ( ($cloned_bug_id) &&
- ($product->name eq $cloned_bug->product ) ) {
- foreach my $i (0..(@{$cloned_bug->groups} - 1) ) {
- if ($cloned_bug->groups->[$i]->{'bit'} == $id) {
- $check = $cloned_bug->groups->[$i]->{'ison'};
- }
- }
- }
- elsif(formvalue("maketemplate") ne "") {
- $check = formvalue("bit-$id", 0);
- }
- else {
- # Checkbox is checked by default if $control is a default state.
- $check = (($membercontrol == CONTROLMAPDEFAULT)
- || (($othercontrol == CONTROLMAPDEFAULT)
- && (!Bugzilla->user->in_group($groupname))));
- }
-
- my $group =
- {
- 'bit' => $id ,
- 'checked' => $check ,
- 'description' => $description
- };
-
- push @groups, $group;
+my @groups = $cgi->param('groups');
+if ($cloned_bug) {
+ my @clone_groups = map { $_->name } @{ $cloned_bug->groups_in };
+ # It doesn't matter if there are duplicate names, since all we check
+ # for in the template is whether or not the group is set.
+ push(@groups, @clone_groups);
}
+$default{'groups'} = \@groups;
-$vars->{'group'} = \@groups;
-
-Bugzilla::Hook::process("enter_bug-entrydefaultvars", { vars => $vars });
+Bugzilla::Hook::process('enter_bug_entrydefaultvars', { vars => $vars });
$vars->{'default'} = \%default;
diff --git a/Websites/bugs.webkit.org/extensions/example/code/webservice.pl b/Websites/bugs.webkit.org/extensions/BmpConvert/Config.pm
similarity index 61%
copy from Websites/bugs.webkit.org/extensions/example/code/webservice.pl
copy to Websites/bugs.webkit.org/extensions/BmpConvert/Config.pm
index ff503be..1b31491 100644
--- a/Websites/bugs.webkit.org/extensions/example/code/webservice.pl
+++ b/Websites/bugs.webkit.org/extensions/BmpConvert/Config.pm
@@ -13,13 +13,21 @@
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
-# Portions created by Everything Solved, Inc. are Copyright (C) 2007
-# Everything Solved, Inc. All Rights Reserved.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
#
-# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+package Bugzilla::Extension::BmpConvert;
use strict;
-use warnings;
-use Bugzilla;
-my $dispatch = Bugzilla->hook_args->{dispatch};
-$dispatch->{Example} = "extensions::example::lib::WSExample";
+use constant NAME => 'BmpConvert';
+use constant REQUIRED_MODULES => [
+ {
+ package => 'PerlMagick',
+ module => 'Image::Magick',
+ version => 0,
+ },
+];
+
+__PACKAGE__->NAME;
diff --git a/Websites/bugs.webkit.org/extensions/BmpConvert/Extension.pm b/Websites/bugs.webkit.org/extensions/BmpConvert/Extension.pm
new file mode 100644
index 0000000..ee08e04
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/BmpConvert/Extension.pm
@@ -0,0 +1,57 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (C) 2009
+# Frédéric Buclin. All Rights Reserved.
+#
+# Contributor(s):
+# Frédéric Buclin <LpSolit@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::BmpConvert;
+use strict;
+use base qw(Bugzilla::Extension);
+
+use Image::Magick;
+
+our $VERSION = '1.0';
+
+sub attachment_process_data {
+ my ($self, $args) = @_;
+ return unless $args->{attributes}->{mimetype} eq 'image/bmp';
+
+ my $data = ${$args->{data}};
+ my $img = Image::Magick->new(magick => 'bmp');
+
+ # $data is a filehandle.
+ if (ref $data) {
+ $img->Read(file => \*$data);
+ $img->set(magick => 'png');
+ $img->Write(file => \*$data);
+ }
+ # $data is a blob.
+ else {
+ $img->BlobToImage($data);
+ $img->set(magick => 'png');
+ $data = $img->ImageToBlob();
+ }
+ undef $img;
+
+ ${$args->{data}} = $data;
+ $args->{attributes}->{mimetype} = 'image/png';
+ $args->{attributes}->{filename} =~ s/^(.+)\.bmp$/$1.png/i;
+}
+
+ __PACKAGE__->NAME;
diff --git a/Websites/bugs.webkit.org/extensions/example/disabled b/Websites/bugs.webkit.org/extensions/BmpConvert/disabled
similarity index 100%
copy from Websites/bugs.webkit.org/extensions/example/disabled
copy to Websites/bugs.webkit.org/extensions/BmpConvert/disabled
diff --git a/Websites/bugs.webkit.org/extensions/Example/Config.pm b/Websites/bugs.webkit.org/extensions/Example/Config.pm
new file mode 100644
index 0000000..378db35
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Example/Config.pm
@@ -0,0 +1,42 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developers are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::Example;
+use strict;
+use constant NAME => 'Example';
+use constant REQUIRED_MODULES => [
+ {
+ package => 'Data-Dumper',
+ module => 'Data::Dumper',
+ version => 0,
+ },
+];
+
+use constant OPTIONAL_MODULES => [
+ {
+ package => 'Acme',
+ module => 'Acme',
+ version => 1.11,
+ feature => ['example_acme'],
+ },
+];
+
+__PACKAGE__->NAME;
diff --git a/Websites/bugs.webkit.org/extensions/Example/Extension.pm b/Websites/bugs.webkit.org/extensions/Example/Extension.pm
new file mode 100644
index 0000000..885a8e8
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Example/Extension.pm
@@ -0,0 +1,909 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developers are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
+
+package Bugzilla::Extension::Example;
+use strict;
+use base qw(Bugzilla::Extension);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Group;
+use Bugzilla::User;
+use Bugzilla::User::Setting;
+use Bugzilla::Util qw(diff_arrays html_quote);
+use Bugzilla::Status qw(is_open_state);
+use Bugzilla::Install::Filesystem;
+
+# This is extensions/Example/lib/Util.pm. I can load this here in my
+# Extension.pm only because I have a Config.pm.
+use Bugzilla::Extension::Example::Util;
+
+use Data::Dumper;
+
+# See bugmail_relationships.
+use constant REL_EXAMPLE => -127;
+
+our $VERSION = '1.0';
+
+sub attachment_process_data {
+ my ($self, $args) = @_;
+ my $type = $args->{attributes}->{mimetype};
+ my $filename = $args->{attributes}->{filename};
+
+ # Make sure images have the correct extension.
+ # Uncomment the two lines below to make this check effective.
+ if ($type =~ /^image\/(\w+)$/) {
+ my $format = $1;
+ if ($filename =~ /^(.+)(:?\.[^\.]+)$/) {
+ my $name = $1;
+ #$args->{attributes}->{filename} = "${name}.$format";
+ }
+ else {
+ # The file has no extension. We append it.
+ #$args->{attributes}->{filename} .= ".$format";
+ }
+ }
+}
+
+sub auth_login_methods {
+ my ($self, $args) = @_;
+ my $modules = $args->{modules};
+ if (exists $modules->{Example}) {
+ $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Login.pm';
+ }
+}
+
+sub auth_verify_methods {
+ my ($self, $args) = @_;
+ my $modules = $args->{modules};
+ if (exists $modules->{Example}) {
+ $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Verify.pm';
+ }
+}
+
+sub bug_columns {
+ my ($self, $args) = @_;
+ my $columns = $args->{'columns'};
+ push (@$columns, "delta_ts AS example")
+}
+
+sub bug_end_of_create {
+ my ($self, $args) = @_;
+
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my $bug = $args->{'bug'};
+ my $timestamp = $args->{'timestamp'};
+
+ my $bug_id = $bug->id;
+ # Uncomment this line to see a line in your webserver's error log whenever
+ # you file a bug.
+ # warn "Bug $bug_id has been filed!";
+}
+
+sub bug_end_of_create_validators {
+ my ($self, $args) = @_;
+
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my $bug_params = $args->{'params'};
+
+ # Uncomment this line below to see a line in your webserver's error log
+ # containing all validated bug field values every time you file a bug.
+ # warn Dumper($bug_params);
+
+ # This would remove all ccs from the bug, preventing ANY ccs from being
+ # added on bug creation.
+ # $bug_params->{cc} = [];
+}
+
+sub bug_end_of_update {
+ my ($self, $args) = @_;
+
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my ($bug, $old_bug, $timestamp, $changes) =
+ @$args{qw(bug old_bug timestamp changes)};
+
+ foreach my $field (keys %$changes) {
+ my $used_to_be = $changes->{$field}->[0];
+ my $now_it_is = $changes->{$field}->[1];
+ }
+
+ my $old_summary = $old_bug->short_desc;
+
+ my $status_message;
+ if (my $status_change = $changes->{'bug_status'}) {
+ my $old_status = new Bugzilla::Status({ name => $status_change->[0] });
+ my $new_status = new Bugzilla::Status({ name => $status_change->[1] });
+ if ($new_status->is_open && !$old_status->is_open) {
+ $status_message = "Bug re-opened!";
+ }
+ if (!$new_status->is_open && $old_status->is_open) {
+ $status_message = "Bug closed!";
+ }
+ }
+
+ my $bug_id = $bug->id;
+ my $num_changes = scalar keys %$changes;
+ my $result = "There were $num_changes changes to fields on bug $bug_id"
+ . " at $timestamp.";
+ # Uncomment this line to see $result in your webserver's error log whenever
+ # you update a bug.
+ # warn $result;
+}
+
+sub bug_fields {
+ my ($self, $args) = @_;
+
+ my $fields = $args->{'fields'};
+ push (@$fields, "example")
+}
+
+sub bug_format_comment {
+ my ($self, $args) = @_;
+
+ # This replaces every occurrence of the word "foo" with the word
+ # "bar"
+
+ my $regexes = $args->{'regexes'};
+ push(@$regexes, { match => qr/\bfoo\b/, replace => 'bar' });
+
+ # And this links every occurrence of the word "bar" to example.com,
+ # but it won't affect "foo"s that have already been turned into "bar"
+ # above (because each regex is run in order, and later regexes don't modify
+ # earlier matches, due to some cleverness in Bugzilla's internals).
+ #
+ # For example, the phrase "foo bar" would become:
+ # bar <a href="http://example.com/bar">bar</a>
+ my $bar_match = qr/\b(bar)\b/;
+ push(@$regexes, { match => $bar_match, replace => \&_replace_bar });
+}
+
+# Used by bug_format_comment--see its code for an explanation.
+sub _replace_bar {
+ my $args = shift;
+ # $match is the first parentheses match in the $bar_match regex
+ # in bug-format_comment.pl. We get up to 10 regex matches as
+ # arguments to this function.
+ my $match = $args->{matches}->[0];
+ # Remember, you have to HTML-escape any data that you are returning!
+ $match = html_quote($match);
+ return qq{<a href="http://example.com/">$match</a>};
+};
+
+sub buglist_columns {
+ my ($self, $args) = @_;
+
+ my $columns = $args->{'columns'};
+ $columns->{'example'} = { 'name' => 'bugs.delta_ts' , 'title' => 'Example' };
+ $columns->{'product_desc'} = { 'name' => 'prod_desc.description',
+ 'title' => 'Product Description' };
+}
+
+sub buglist_column_joins {
+ my ($self, $args) = @_;
+ my $joins = $args->{'column_joins'};
+
+ # This column is added using the "buglist_columns" hook
+ $joins->{'product_desc'} = {
+ from => 'product_id',
+ to => 'id',
+ table => 'products',
+ as => 'prod_desc',
+ join => 'INNER',
+ };
+}
+
+sub search_operator_field_override {
+ my ($self, $args) = @_;
+
+ my $operators = $args->{'operators'};
+
+ my $original = $operators->{component}->{_non_changed};
+ $operators->{component} = {
+ _non_changed => sub { _component_nonchanged($original, @_) }
+ };
+}
+
+sub _component_nonchanged {
+ my $original = shift;
+ my ($invocant, $args) = @_;
+
+ $invocant->$original($args);
+ # Actually, it does not change anything in the result,
+ # just an example.
+ $args->{term} = $args->{term} . " OR 1=2";
+}
+
+sub bugmail_recipients {
+ my ($self, $args) = @_;
+ my $recipients = $args->{recipients};
+ my $bug = $args->{bug};
+
+ my $user =
+ new Bugzilla::User({ name => Bugzilla->params->{'maintainer'} });
+
+ if ($bug->id == 1) {
+ # Uncomment the line below to add the maintainer to the recipients
+ # list of every bugmail from bug 1 as though that the maintainer
+ # were on the CC list.
+ #$recipients->{$user->id}->{+REL_CC} = 1;
+
+ # And this line adds the maintainer as though he had the "REL_EXAMPLE"
+ # relationship from the bugmail_relationships hook below.
+ #$recipients->{$user->id}->{+REL_EXAMPLE} = 1;
+ }
+}
+
+sub bugmail_relationships {
+ my ($self, $args) = @_;
+ my $relationships = $args->{relationships};
+ $relationships->{+REL_EXAMPLE} = 'Example';
+}
+
+sub config_add_panels {
+ my ($self, $args) = @_;
+
+ my $modules = $args->{panel_modules};
+ $modules->{Example} = "Bugzilla::Extension::Example::Config";
+}
+
+sub config_modify_panels {
+ my ($self, $args) = @_;
+
+ my $panels = $args->{panels};
+
+ # Add the "Example" auth methods.
+ my $auth_params = $panels->{'auth'}->{params};
+ my ($info_class) = grep($_->{name} eq 'user_info_class', @$auth_params);
+ my ($verify_class) = grep($_->{name} eq 'user_verify_class', @$auth_params);
+
+ push(@{ $info_class->{choices} }, 'CGI,Example');
+ push(@{ $verify_class->{choices} }, 'Example');
+
+ push(@$auth_params, { name => 'param_example',
+ type => 't',
+ default => 0,
+ checker => \&check_numeric });
+}
+
+sub db_schema_abstract_schema {
+ my ($self, $args) = @_;
+# $args->{'schema'}->{'example_table'} = {
+# FIELDS => [
+# id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
+# PRIMARYKEY => 1},
+# for_key => {TYPE => 'INT3', NOTNULL => 1,
+# REFERENCES => {TABLE => 'example_table2',
+# COLUMN => 'id',
+# DELETE => 'CASCADE'}},
+# col_3 => {TYPE => 'varchar(64)', NOTNULL => 1},
+# ],
+# INDEXES => [
+# id_index_idx => {FIELDS => ['col_3'], TYPE => 'UNIQUE'},
+# for_id_idx => ['for_key'],
+# ],
+# };
+}
+
+sub email_in_before_parse {
+ my ($self, $args) = @_;
+
+ my $subject = $args->{mail}->header('Subject');
+ # Correctly extract the bug ID from email subjects of the form [Bug comp/NNN].
+ if ($subject =~ /\[.*(\d+)\].*/) {
+ $args->{fields}->{bug_id} = $1;
+ }
+}
+
+sub email_in_after_parse {
+ my ($self, $args) = @_;
+ my $reporter = $args->{fields}->{reporter};
+ my $dbh = Bugzilla->dbh;
+
+ # No other check needed if this is a valid regular user.
+ return if login_to_id($reporter);
+
+ # The reporter is not a regular user. We create an account for him,
+ # but he can only comment on existing bugs.
+ # This is useful for people who reply by email to bugmails received
+ # in mailing-lists.
+ if ($args->{fields}->{bug_id}) {
+ # WARNING: we return now to skip the remaining code below.
+ # You must understand that removing this line would make the code
+ # below effective! Do it only if you are OK with the behavior
+ # described here.
+ return;
+
+ Bugzilla::User->create({ login_name => $reporter, cryptpassword => '*' });
+
+ # For security reasons, delete all fields unrelated to comments.
+ foreach my $field (keys %{$args->{fields}}) {
+ next if $field =~ /^(?:bug_id|comment|reporter)$/;
+ delete $args->{fields}->{$field};
+ }
+ }
+ else {
+ ThrowUserError('invalid_username', { name => $reporter });
+ }
+}
+
+sub enter_bug_entrydefaultvars {
+ my ($self, $args) = @_;
+
+ my $vars = $args->{vars};
+ $vars->{'example'} = 1;
+}
+
+sub error_catch {
+ my ($self, $args) = @_;
+ # Customize the error message displayed when someone tries to access
+ # page.cgi with an invalid page ID, and keep track of this attempt
+ # in the web server log.
+ return unless Bugzilla->error_mode == ERROR_MODE_WEBPAGE;
+ return unless $args->{error} eq 'bad_page_cgi_id';
+
+ my $page_id = $args->{vars}->{page_id};
+ my $login = Bugzilla->user->identity || "Someone";
+ warn "$login attempted to access page.cgi with id = $page_id";
+
+ my $page = $args->{message};
+ my $new_error_msg = "Ah ah, you tried to access $page_id? Good try!";
+ $new_error_msg = html_quote($new_error_msg);
+ # There are better tools to parse an HTML page, but it's just an example.
+ $$page =~ s/(?<=<td id="error_msg" class="throw_error">).*(?=<\/td>)/$new_error_msg/si;
+}
+
+sub flag_end_of_update {
+ my ($self, $args) = @_;
+
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my $flag_params = $args;
+ my ($object, $timestamp, $old_flags, $new_flags) =
+ @$flag_params{qw(object timestamp old_flags new_flags)};
+ my ($removed, $added) = diff_arrays($old_flags, $new_flags);
+ my ($granted, $denied) = (0, 0);
+ foreach my $new_flag (@$added) {
+ $granted++ if $new_flag =~ /\+$/;
+ $denied++ if $new_flag =~ /-$/;
+ }
+ my $bug_id = $object->isa('Bugzilla::Bug') ? $object->id
+ : $object->bug_id;
+ my $result = "$granted flags were granted and $denied flags were denied"
+ . " on bug $bug_id at $timestamp.";
+ # Uncomment this line to see $result in your webserver's error log whenever
+ # you update flags.
+ # warn $result;
+}
+
+sub group_before_delete {
+ my ($self, $args) = @_;
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+
+ my $group = $args->{'group'};
+ my $group_id = $group->id;
+ # Uncomment this line to see a line in your webserver's error log whenever
+ # you file a bug.
+ # warn "Group $group_id is about to be deleted!";
+}
+
+sub group_end_of_create {
+ my ($self, $args) = @_;
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+ my $group = $args->{'group'};
+
+ my $group_id = $group->id;
+ # Uncomment this line to see a line in your webserver's error log whenever
+ # you create a new group.
+ #warn "Group $group_id has been created!";
+}
+
+sub group_end_of_update {
+ my ($self, $args) = @_;
+ # This code doesn't actually *do* anything, it's just here to show you
+ # how to use this hook.
+
+ my ($group, $changes) = @$args{qw(group changes)};
+
+ foreach my $field (keys %$changes) {
+ my $used_to_be = $changes->{$field}->[0];
+ my $now_it_is = $changes->{$field}->[1];
+ }
+
+ my $group_id = $group->id;
+ my $num_changes = scalar keys %$changes;
+ my $result =
+ "There were $num_changes changes to fields on group $group_id.";
+ # Uncomment this line to see $result in your webserver's error log whenever
+ # you update a group.
+ #warn $result;
+}
+
+sub install_before_final_checks {
+ my ($self, $args) = @_;
+ print "Install-before_final_checks hook\n" unless $args->{silent};
+
+ # Add a new user setting like this:
+ #
+ # add_setting('product_chooser', # setting name
+ # ['pretty', 'full', 'small'], # options
+ # 'pretty'); # default
+ #
+ # To add descriptions for the setting and choices, add extra values to
+ # the hash defined in global/setting-descs.none.tmpl. Do this in a hook:
+ # hook/global/setting-descs-settings.none.tmpl .
+}
+
+sub install_filesystem {
+ my ($self, $args) = @_;
+ my $create_dirs = $args->{'create_dirs'};
+ my $recurse_dirs = $args->{'recurse_dirs'};
+ my $htaccess = $args->{'htaccess'};
+
+ # Create a new directory in datadir specifically for this extension.
+ # The directory will need to allow files to be created by the extension
+ # code as well as allow the webserver to server content from it.
+ # my $data_path = bz_locations->{'datadir'} . "/" . __PACKAGE__->NAME;
+ # $create_dirs->{$data_path} = Bugzilla::Install::Filesystem::DIR_CGI_WRITE;
+
+ # Update the permissions of any files and directories that currently reside
+ # in the extension's directory.
+ # $recurse_dirs->{$data_path} = {
+ # files => Bugzilla::Install::Filesystem::CGI_READ,
+ # dirs => Bugzilla::Install::Filesystem::DIR_CGI_WRITE
+ # };
+
+ # Create a htaccess file that allows specific content to be served from the
+ # extension's directory.
+ # $htaccess->{"$data_path/.htaccess"} = {
+ # perms => Bugzilla::Install::Filesystem::WS_SERVE,
+ # contents => Bugzilla::Install::Filesystem::HT_DEFAULT_DENY
+ # };
+}
+
+sub install_update_db {
+ my $dbh = Bugzilla->dbh;
+# $dbh->bz_add_column('example', 'new_column',
+# {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+# $dbh->bz_add_index('example', 'example_new_column_idx', [qw(value)]);
+}
+
+sub install_update_db_fielddefs {
+ my $dbh = Bugzilla->dbh;
+# $dbh->bz_add_column('fielddefs', 'example_column',
+# {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => ''});
+}
+
+sub job_map {
+ my ($self, $args) = @_;
+
+ my $job_map = $args->{job_map};
+
+ # This adds the named class (an instance of TheSchwartz::Worker) as a
+ # handler for when a job is added with the name "some_task".
+ $job_map->{'some_task'} = 'Bugzilla::Extension::Example::Job::SomeClass';
+
+ # Schedule a job like this:
+ # my $queue = Bugzilla->job_queue();
+ # $queue->insert('some_task', { some_parameter => $some_variable });
+}
+
+sub mailer_before_send {
+ my ($self, $args) = @_;
+
+ my $email = $args->{email};
+ # If you add a header to an email, it's best to start it with
+ # 'X-Bugzilla-<Extension>' so that you don't conflict with
+ # other extensions.
+ $email->header_set('X-Bugzilla-Example-Header', 'Example');
+}
+
+sub object_before_create {
+ my ($self, $args) = @_;
+
+ my $class = $args->{'class'};
+ my $object_params = $args->{'params'};
+
+ # Note that this is a made-up class, for this example.
+ if ($class->isa('Bugzilla::ExampleObject')) {
+ warn "About to create an ExampleObject!";
+ warn "Got the following parameters: "
+ . join(', ', keys(%$object_params));
+ }
+}
+
+sub object_before_delete {
+ my ($self, $args) = @_;
+
+ my $object = $args->{'object'};
+
+ # Note that this is a made-up class, for this example.
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ my $id = $object->id;
+ warn "An object with id $id is about to be deleted!";
+ }
+}
+
+sub object_before_set {
+ my ($self, $args) = @_;
+
+ my ($object, $field, $value) = @$args{qw(object field value)};
+
+ # Note that this is a made-up class, for this example.
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ warn "The field $field is changing from " . $object->{$field}
+ . " to $value!";
+ }
+}
+
+sub object_columns {
+ my ($self, $args) = @_;
+ my ($class, $columns) = @$args{qw(class columns)};
+
+ if ($class->isa('Bugzilla::ExampleObject')) {
+ push(@$columns, 'example');
+ }
+}
+
+sub object_end_of_create {
+ my ($self, $args) = @_;
+
+ my $class = $args->{'class'};
+ my $object = $args->{'object'};
+
+ warn "Created a new $class object!";
+}
+
+sub object_end_of_create_validators {
+ my ($self, $args) = @_;
+
+ my $class = $args->{'class'};
+ my $object_params = $args->{'params'};
+
+ # Note that this is a made-up class, for this example.
+ if ($class->isa('Bugzilla::ExampleObject')) {
+ # Always set example_field to 1, even if the validators said otherwise.
+ $object_params->{example_field} = 1;
+ }
+
+}
+
+sub object_end_of_set {
+ my ($self, $args) = @_;
+
+ my ($object, $field) = @$args{qw(object field)};
+
+ # Note that this is a made-up class, for this example.
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ warn "The field $field has changed to " . $object->{$field};
+ }
+}
+
+sub object_end_of_set_all {
+ my ($self, $args) = @_;
+
+ my $object = $args->{'object'};
+ my $object_params = $args->{'params'};
+
+ # Note that this is a made-up class, for this example.
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ if ($object_params->{example_field} == 1) {
+ $object->{example_field} = 1;
+ }
+ }
+
+}
+
+sub object_end_of_update {
+ my ($self, $args) = @_;
+
+ my ($object, $old_object, $changes) =
+ @$args{qw(object old_object changes)};
+
+ # Note that this is a made-up class, for this example.
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ if (defined $changes->{'name'}) {
+ my ($old, $new) = @{ $changes->{'name'} };
+ print "The name field changed from $old to $new!";
+ }
+ }
+}
+
+sub object_update_columns {
+ my ($self, $args) = @_;
+ my ($object, $columns) = @$args{qw(object columns)};
+
+ if ($object->isa('Bugzilla::ExampleObject')) {
+ push(@$columns, 'example');
+ }
+}
+
+sub object_validators {
+ my ($self, $args) = @_;
+ my ($class, $validators) = @$args{qw(class validators)};
+
+ if ($class->isa('Bugzilla::Bug')) {
+ # This is an example of adding a new validator.
+ # See the _check_example subroutine below.
+ $validators->{example} = \&_check_example;
+
+ # This is an example of overriding an existing validator.
+ # See the check_short_desc validator below.
+ my $original = $validators->{short_desc};
+ $validators->{short_desc} = sub { _check_short_desc($original, @_) };
+ }
+}
+
+sub _check_example {
+ my ($invocant, $value, $field) = @_;
+ warn "I was called to validate the value of $field.";
+ warn "The value of $field that I was passed in is: $value";
+
+ # Make the value always be 1.
+ my $fixed_value = 1;
+ return $fixed_value;
+}
+
+sub _check_short_desc {
+ my $original = shift;
+ my $invocant = shift;
+ my $value = $invocant->$original(@_);
+ if ($value !~ /example/i) {
+ # Uncomment this line to make Bugzilla throw an error every time
+ # you try to file a bug or update a bug without the word "example"
+ # in the summary.
+ #ThrowUserError('example_short_desc_invalid');
+ }
+ return $value;
+}
+
+sub page_before_template {
+ my ($self, $args) = @_;
+
+ my ($vars, $page) = @$args{qw(vars page_id)};
+
+ # You can see this hook in action by loading page.cgi?id=example.html
+ if ($page eq 'example.html') {
+ $vars->{cgi_variables} = { Bugzilla->cgi->Vars };
+ }
+}
+
+sub post_bug_after_creation {
+ my ($self, $args) = @_;
+
+ my $vars = $args->{vars};
+ $vars->{'example'} = 1;
+}
+
+sub product_confirm_delete {
+ my ($self, $args) = @_;
+
+ my $vars = $args->{vars};
+ $vars->{'example'} = 1;
+}
+
+
+sub product_end_of_create {
+ my ($self, $args) = @_;
+
+ my $product = $args->{product};
+
+ # For this example, any lines of code that actually make changes to your
+ # database have been commented out.
+
+ # This section will take a group that exists in your installation
+ # (possible called test_group) and automatically makes the new
+ # product hidden to only members of the group. Just remove
+ # the restriction if you want the new product to be public.
+
+ my $example_group = new Bugzilla::Group({ name => 'example_group' });
+
+ if ($example_group) {
+ $product->set_group_controls($example_group,
+ { entry => 1,
+ membercontrol => CONTROLMAPMANDATORY,
+ othercontrol => CONTROLMAPMANDATORY });
+# $product->update();
+ }
+
+ # This section will automatically add a default component
+ # to the new product called 'No Component'.
+
+ my $default_assignee = new Bugzilla::User(
+ { name => Bugzilla->params->{maintainer} });
+
+ if ($default_assignee) {
+# Bugzilla::Component->create(
+# { name => 'No Component',
+# product => $product,
+# description => 'Select this component if one does not ' .
+# 'exist in the current list of components',
+# initialowner => $default_assignee });
+ }
+}
+
+sub quicksearch_map {
+ my ($self, $args) = @_;
+ my $map = $args->{'map'};
+
+ # This demonstrates adding a shorter alias for a long custom field name.
+ $map->{'impact'} = $map->{'cf_long_field_name_for_impact_field'};
+}
+
+sub sanitycheck_check {
+ my ($self, $args) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $sth;
+
+ my $status = $args->{'status'};
+
+ # Check that all users are Australian
+ $status->('example_check_au_user');
+
+ $sth = $dbh->prepare("SELECT userid, login_name
+ FROM profiles
+ WHERE login_name NOT LIKE '%.au'");
+ $sth->execute;
+
+ my $seen_nonau = 0;
+ while (my ($userid, $login, $numgroups) = $sth->fetchrow_array) {
+ $status->('example_check_au_user_alert',
+ { userid => $userid, login => $login },
+ 'alert');
+ $seen_nonau = 1;
+ }
+
+ $status->('example_check_au_user_prompt') if $seen_nonau;
+}
+
+sub sanitycheck_repair {
+ my ($self, $args) = @_;
+
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+
+ my $status = $args->{'status'};
+
+ if ($cgi->param('example_repair_au_user')) {
+ $status->('example_repair_au_user_start');
+
+ #$dbh->do("UPDATE profiles
+ # SET login_name = CONCAT(login_name, '.au')
+ # WHERE login_name NOT LIKE '%.au'");
+
+ $status->('example_repair_au_user_end');
+ }
+}
+
+sub template_before_create {
+ my ($self, $args) = @_;
+
+ my $config = $args->{'config'};
+ # This will be accessible as "example_global_variable" in every
+ # template in Bugzilla. See Bugzilla/Template.pm's create() function
+ # for more things that you can set.
+ $config->{VARIABLES}->{example_global_variable} = sub { return 'value' };
+}
+
+sub template_before_process {
+ my ($self, $args) = @_;
+
+ my ($vars, $file, $context) = @$args{qw(vars file context)};
+
+ if ($file eq 'bug/edit.html.tmpl') {
+ $vars->{'viewing_the_bug_form'} = 1;
+ }
+}
+
+sub bug_check_can_change_field {
+ my ($self, $args) = @_;
+
+ my ($bug, $field, $new_value, $old_value, $priv_results)
+ = @$args{qw(bug field new_value old_value priv_results)};
+
+ my $user = Bugzilla->user;
+
+ # Disallow a bug from being reopened if currently closed unless user
+ # is in 'admin' group
+ if ($field eq 'bug_status' && $bug->product_obj->name eq 'Example') {
+ if (!is_open_state($old_value) && is_open_state($new_value)
+ && !$user->in_group('admin'))
+ {
+ push(@$priv_results, PRIVILEGES_REQUIRED_EMPOWERED);
+ return;
+ }
+ }
+
+ # Disallow a bug's keywords from being edited unless user is the
+ # reporter of the bug
+ if ($field eq 'keywords' && $bug->product_obj->name eq 'Example'
+ && $user->login ne $bug->reporter->login)
+ {
+ push(@$priv_results, PRIVILEGES_REQUIRED_REPORTER);
+ return;
+ }
+
+ # Allow updating of priority even if user cannot normally edit the bug
+ # and they are in group 'engineering'
+ if ($field eq 'priority' && $bug->product_obj->name eq 'Example'
+ && $user->in_group('engineering'))
+ {
+ push(@$priv_results, PRIVILEGES_REQUIRED_NONE);
+ return;
+ }
+}
+
+sub admin_editusers_action {
+ my ($self, $args) = @_;
+ my ($vars, $action, $user) = @$args{qw(vars action user)};
+ my $template = Bugzilla->template;
+
+ if ($action eq 'my_action') {
+ # Allow to restrict the search to any group the user is allowed to bless.
+ $vars->{'restrictablegroups'} = $user->bless_groups();
+ $template->process('admin/users/search.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+}
+
+sub user_preferences {
+ my ($self, $args) = @_;
+ my $tab = $args->{current_tab};
+ my $save = $args->{save_changes};
+ my $handled = $args->{handled};
+
+ return unless $tab eq 'my_tab';
+
+ my $value = Bugzilla->input_params->{'example_pref'};
+ if ($save) {
+ # Validate your data and update the DB accordingly.
+ $value =~ s/\s+/:/g;
+ }
+ $args->{'vars'}->{example_pref} = $value;
+
+ # Set the 'handled' scalar reference to true so that the caller
+ # knows the panel name is valid and that an extension took care of it.
+ $$handled = 1;
+}
+
+sub webservice {
+ my ($self, $args) = @_;
+
+ my $dispatch = $args->{dispatch};
+ $dispatch->{Example} = "Bugzilla::Extension::Example::WebService";
+}
+
+sub webservice_error_codes {
+ my ($self, $args) = @_;
+
+ my $error_map = $args->{error_map};
+ $error_map->{'example_my_error'} = 10001;
+}
+
+# This must be the last line of your extension.
+__PACKAGE__->NAME;
diff --git a/Websites/bugs.webkit.org/extensions/example/disabled b/Websites/bugs.webkit.org/extensions/Example/disabled
similarity index 100%
rename from Websites/bugs.webkit.org/extensions/example/disabled
rename to Websites/bugs.webkit.org/extensions/Example/disabled
diff --git a/Websites/bugs.webkit.org/extensions/example/code/config.pl b/Websites/bugs.webkit.org/extensions/Example/lib/Auth/Login.pm
similarity index 67%
copy from Websites/bugs.webkit.org/extensions/example/code/config.pl
copy to Websites/bugs.webkit.org/extensions/Example/lib/Auth/Login.pm
index 1da490c..9f4f37d 100644
--- a/Websites/bugs.webkit.org/extensions/example/code/config.pl
+++ b/Websites/bugs.webkit.org/extensions/Example/lib/Auth/Login.pm
@@ -13,14 +13,20 @@
# The Original Code is the Bugzilla Example Plugin.
#
# The Initial Developer of the Original Code is Canonical Ltd.
-# Portions created by Canonical Ltd. are Copyright (C) 2008
-# Canonical Ltd. All Rights Reserved.
+# Portions created by Canonical are Copyright (C) 2008 Canonical Ltd.
+# All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
-# Bradley Baetz <bbaetz@acm.org>
+package Bugzilla::Extension::Example::Auth::Login;
use strict;
-use warnings;
-use Bugzilla;
-my $config = Bugzilla->hook_args->{config};
-$config->{Example} = "extensions::example::lib::ConfigExample";
+use base qw(Bugzilla::Auth::Login);
+use constant user_can_create_account => 0;
+use Bugzilla::Constants;
+
+# Always returns no data.
+sub get_login_info {
+ return { failure => AUTH_NODATA };
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/extensions/example/code/config.pl b/Websites/bugs.webkit.org/extensions/Example/lib/Auth/Verify.pm
similarity index 68%
rename from Websites/bugs.webkit.org/extensions/example/code/config.pl
rename to Websites/bugs.webkit.org/extensions/Example/lib/Auth/Verify.pm
index 1da490c..0141a0d 100644
--- a/Websites/bugs.webkit.org/extensions/example/code/config.pl
+++ b/Websites/bugs.webkit.org/extensions/Example/lib/Auth/Verify.pm
@@ -13,14 +13,19 @@
# The Original Code is the Bugzilla Example Plugin.
#
# The Initial Developer of the Original Code is Canonical Ltd.
-# Portions created by Canonical Ltd. are Copyright (C) 2008
-# Canonical Ltd. All Rights Reserved.
+# Portions created by Canonical are Copyright (C) 2008 Canonical Ltd.
+# All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
-# Bradley Baetz <bbaetz@acm.org>
+package Bugzilla::Extension::Example::Auth::Verify;
use strict;
-use warnings;
-use Bugzilla;
-my $config = Bugzilla->hook_args->{config};
-$config->{Example} = "extensions::example::lib::ConfigExample";
+use base qw(Bugzilla::Auth::Verify);
+use Bugzilla::Constants;
+
+# A verifier that always fails.
+sub check_credentials {
+ return { failure => AUTH_NO_SUCH_USER };
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/extensions/example/lib/ConfigExample.pm b/Websites/bugs.webkit.org/extensions/Example/lib/Config.pm
similarity index 93%
rename from Websites/bugs.webkit.org/extensions/example/lib/ConfigExample.pm
rename to Websites/bugs.webkit.org/extensions/Example/lib/Config.pm
index 5ee612d..75db229 100644
--- a/Websites/bugs.webkit.org/extensions/example/lib/ConfigExample.pm
+++ b/Websites/bugs.webkit.org/extensions/Example/lib/Config.pm
@@ -19,12 +19,14 @@
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
# Bradley Baetz <bbaetz@acm.org>
-package extensions::example::lib::ConfigExample;
+package Bugzilla::Extension::Example::Config;
use strict;
use warnings;
use Bugzilla::Config::Common;
+our $sortkey = 5000;
+
sub get_param_list {
my ($class) = @_;
diff --git a/Websites/bugs.webkit.org/extensions/example/code/webservice.pl b/Websites/bugs.webkit.org/extensions/Example/lib/Util.pm
similarity index 84%
rename from Websites/bugs.webkit.org/extensions/example/code/webservice.pl
rename to Websites/bugs.webkit.org/extensions/Example/lib/Util.pm
index ff503be..596f048 100644
--- a/Websites/bugs.webkit.org/extensions/example/code/webservice.pl
+++ b/Websites/bugs.webkit.org/extensions/Example/lib/Util.pm
@@ -13,13 +13,16 @@
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
-# Portions created by Everything Solved, Inc. are Copyright (C) 2007
+# Portions created by Everything Solved, Inc. are Copyright (C) 2009
# Everything Solved, Inc. All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+package Bugzilla::Extension::Example::Util;
use strict;
use warnings;
-use Bugzilla;
-my $dispatch = Bugzilla->hook_args->{dispatch};
-$dispatch->{Example} = "extensions::example::lib::WSExample";
+
+# This file exists only to demonstrate how to use and name your
+# modules in an extension.
+
+1;
diff --git a/Websites/bugs.webkit.org/extensions/example/lib/WSExample.pm b/Websites/bugs.webkit.org/extensions/Example/lib/WebService.pm
similarity index 89%
rename from Websites/bugs.webkit.org/extensions/example/lib/WSExample.pm
rename to Websites/bugs.webkit.org/extensions/Example/lib/WebService.pm
index 1468672..8563ec7 100644
--- a/Websites/bugs.webkit.org/extensions/example/lib/WSExample.pm
+++ b/Websites/bugs.webkit.org/extensions/Example/lib/WebService.pm
@@ -18,13 +18,13 @@
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
-package extensions::example::lib::WSExample;
+package Bugzilla::Extension::Example::WebService;
use strict;
use warnings;
use base qw(Bugzilla::WebService);
use Bugzilla::Error;
-# This can be called as Example.hello() from XML-RPC.
+# This can be called as Example.hello() from the WebService.
sub hello { return 'Hello!'; }
sub throw_an_error { ThrowUserError('example_my_error') }
diff --git a/Websites/bugs.webkit.org/extensions/Example/template/en/default/account/prefs/my_tab.html.tmpl b/Websites/bugs.webkit.org/extensions/Example/template/en/default/account/prefs/my_tab.html.tmpl
new file mode 100644
index 0000000..ca7a3bd
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Example/template/en/default/account/prefs/my_tab.html.tmpl
@@ -0,0 +1,30 @@
+[%#
+ # The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Example Plugin.
+ #
+ # The Initial Developer of the Original Code is Frédéric Buclin.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+<p>
+ Type some short text in the field below. Whitespaces will be replaced
+ by colons.
+</p>
+
+<p>
+ <label for="example_pref">Short text:</label>
+ <input type="text" id="example_pref" name="example_pref" size="30"
+ maxlength="50" value="[% example_pref FILTER html %]">
+</p>
diff --git a/Websites/bugs.webkit.org/extensions/example/template/en/default/admin/params/example.html.tmpl b/Websites/bugs.webkit.org/extensions/Example/template/en/default/admin/params/example.html.tmpl
similarity index 100%
rename from Websites/bugs.webkit.org/extensions/example/template/en/default/admin/params/example.html.tmpl
rename to Websites/bugs.webkit.org/extensions/Example/template/en/default/admin/params/example.html.tmpl
diff --git a/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl b/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl
new file mode 100644
index 0000000..8380154
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl
@@ -0,0 +1,23 @@
+[%#
+ # The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Example Plugin.
+ #
+ # The Initial Developer of the Original Code is Frédéric Buclin.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% tabs = tabs.import([{ name => "my_tab", label => "Example Custom Preferences",
+ link => "userprefs.cgi?tab=my_tab", saveable => 1 }
+ ]) %]
diff --git a/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl b/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl
new file mode 100644
index 0000000..a128480
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl
@@ -0,0 +1,23 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Example Bugzilla Extension.
+ #
+ # The Initial Developer of the Original Code is Ali Ustek
+ # Portions created by the Initial Developer are Copyright (C) 2011 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Ali Ustek <aliustek@gmail.com>
+ #%]
+
+[% IF panel.name == "auth" %]
+ [% panel.param_descs.param_example = 'Example new parameter' %]
+[% END -%]
diff --git a/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl b/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
new file mode 100644
index 0000000..639752ed
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
@@ -0,0 +1,36 @@
+[%# -*- Mode: perl; indent-tabs-mode: nil -*-
+ #
+ # The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Example Plugin.
+ #
+ # The Initial Developer of the Original Code is ITA Software
+ # Portions created by the Initial Developer are Copyright (C) 2009
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Bradley Baetz <bbaetz@everythingsolved.com>
+ #%]
+
+[% IF san_tag == "example_check_au_user" %]
+ <em>EXAMPLE PLUGIN</em> - Checking for non-Australian users.
+[% ELSIF san_tag == "example_check_au_user_alert" %]
+ User <[% login FILTER html %]> isn't Australian.
+ [% IF user.in_group('editusers') %]
+ <a href="editusers.cgi?id=[% userid FILTER none %]">Edit this user</a>.
+ [% END %]
+[% ELSIF san_tag == "example_check_au_user_prompt" %]
+ <a href="sanitycheck.cgi?example_repair_au_user=1&token=
+ [%- issue_hash_token(['sanitycheck']) FILTER uri %]">Fix these users</a>.
+[% ELSIF san_tag == "example_repair_au_user_start" %]
+ <em>EXAMPLE PLUGIN</em> - OK, would now make users Australian.
+[% ELSIF san_tag == "example_repair_au_user_end" %]
+ <em>EXAMPLE PLUGIN</em> - Users would now be Australian.
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/global/setting-descs-settings.none.tmpl b/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/global/setting-descs-settings.none.tmpl
new file mode 100644
index 0000000..9dc5fc7
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/global/setting-descs-settings.none.tmpl
@@ -0,0 +1,6 @@
+[%
+ setting_descs.product_chooser = "Product chooser to use when entering $terms.bugs",
+ setting_descs.pretty = "Pretty chooser with common products and icons",
+ setting_descs.full = "Full chooser with all products",
+ setting_descs.small = "Product chooser for mobile devices",
+%]
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/extensions/example/template/en/global/user-error-errors.html.tmpl b/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/global/user-error-errors.html.tmpl
similarity index 81%
rename from Websites/bugs.webkit.org/extensions/example/template/en/global/user-error-errors.html.tmpl
rename to Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/global/user-error-errors.html.tmpl
index df5a203..50d20a9 100644
--- a/Websites/bugs.webkit.org/extensions/example/template/en/global/user-error-errors.html.tmpl
+++ b/Websites/bugs.webkit.org/extensions/Example/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -9,4 +9,7 @@
[% IF error == "example_my_error" %]
[% title = "Example Error Title" %]
This is the error message! It contains <em>some html</em>.
+[% ELSIF error == "example_short_desc_invalid" %]
+ [% title = "Bad Summary" %]
+ The Summary must contain the word "example".
[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Example/template/en/default/pages/example.html.tmpl b/Websites/bugs.webkit.org/extensions/Example/template/en/default/pages/example.html.tmpl
new file mode 100644
index 0000000..919fa15
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Example/template/en/default/pages/example.html.tmpl
@@ -0,0 +1,32 @@
+[%#
+ # The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Example Plugin.
+ #
+ # The Initial Developer of the Original Code is Canonical Ltd.
+ # Portions created by Canonical Ltd. are Copyright (C) 2009
+ # Canonical Ltd. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Example Page"
+%]
+
+<p>Here's what you passed me:</p>
+[% USE Dumper %]
+<pre>
+ [% Dumper.dump_html(cgi_variables) FILTER none %]
+</pre>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/extensions/example/code/webservice-error_codes.pl b/Websites/bugs.webkit.org/extensions/Example/template/en/default/setup/strings.txt.pl
similarity index 62%
copy from Websites/bugs.webkit.org/extensions/example/code/webservice-error_codes.pl
copy to Websites/bugs.webkit.org/extensions/Example/template/en/default/setup/strings.txt.pl
index 94c4c52..8da19c0 100644
--- a/Websites/bugs.webkit.org/extensions/example/code/webservice-error_codes.pl
+++ b/Websites/bugs.webkit.org/extensions/Example/template/en/default/setup/strings.txt.pl
@@ -1,5 +1,3 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
@@ -10,16 +8,17 @@
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
# The Original Code is the Bugzilla Bug Tracking System.
#
-# The Initial Developer of the Original Code is Everything Solved, Inc.
-# Portions created by Everything Solved, Inc. are Copyright (C) 2008
-# Everything Solved, Inc. All Rights Reserved.
-#
-# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
-use strict;
-use warnings;
-use Bugzilla;
-my $error_map = Bugzilla->hook_args->{error_map};
-$error_map->{'example_my_error'} = 10001;
+%strings = (
+ feature_example_acme => 'Example Extension: Acme Feature',
+);
+
+1;
diff --git a/Websites/bugs.webkit.org/extensions/example/code/webservice-error_codes.pl b/Websites/bugs.webkit.org/extensions/OldBugMove/Config.pm
similarity index 62%
copy from Websites/bugs.webkit.org/extensions/example/code/webservice-error_codes.pl
copy to Websites/bugs.webkit.org/extensions/OldBugMove/Config.pm
index 94c4c52..e401260 100644
--- a/Websites/bugs.webkit.org/extensions/example/code/webservice-error_codes.pl
+++ b/Websites/bugs.webkit.org/extensions/OldBugMove/Config.pm
@@ -10,16 +10,16 @@
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
-# The Original Code is the Bugzilla Bug Tracking System.
+# The Original Code is the OldBugMove Bugzilla Extension.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
-# Portions created by Everything Solved, Inc. are Copyright (C) 2008
-# Everything Solved, Inc. All Rights Reserved.
+# Portions created by the Initial Developer is Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
#
-# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+package Bugzilla::Extension::OldBugMove;
use strict;
-use warnings;
-use Bugzilla;
-my $error_map = Bugzilla->hook_args->{error_map};
-$error_map->{'example_my_error'} = 10001;
+use constant NAME => 'OldBugMove';
+__PACKAGE__->NAME;
diff --git a/Websites/bugs.webkit.org/extensions/OldBugMove/Extension.pm b/Websites/bugs.webkit.org/extensions/OldBugMove/Extension.pm
new file mode 100644
index 0000000..b12d36a
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/OldBugMove/Extension.pm
@@ -0,0 +1,208 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the OldBugMove Bugzilla Extension.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::OldBugMove;
+use strict;
+use base qw(Bugzilla::Extension);
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field::Choice;
+use Bugzilla::Mailer;
+use Bugzilla::User;
+use Bugzilla::Util qw(trim);
+
+use Scalar::Util qw(blessed);
+use Storable qw(dclone);
+
+use constant VERSION => BUGZILLA_VERSION;
+
+# This is 4 because that's what it originally was when this code was
+# a part of Bugzilla.
+use constant CMT_MOVED_TO => 4;
+
+sub install_update_db {
+ my $reso_type = Bugzilla::Field::Choice->type('resolution');
+ my $moved_reso = $reso_type->new({ name => 'MOVED' });
+ # We make the MOVED resolution inactive, so that it doesn't show up
+ # as a valid drop-down option.
+ if ($moved_reso) {
+ $moved_reso->set_is_active(0);
+ $moved_reso->update();
+ }
+ else {
+ print "Creating the MOVED resolution...\n";
+ $reso_type->create(
+ { value => 'MOVED', sortkey => '30000', isactive => 0 });
+ }
+}
+
+sub config_add_panels {
+ my ($self, $args) = @_;
+ my $modules = $args->{'panel_modules'};
+ $modules->{'OldBugMove'} = 'Bugzilla::Extension::OldBugMove::Params';
+}
+
+sub template_before_create {
+ my ($self, $args) = @_;
+ my $config = $args->{config};
+
+ my $constants = $config->{CONSTANTS};
+ $constants->{CMT_MOVED_TO} = CMT_MOVED_TO;
+
+ my $vars = $config->{VARIABLES};
+ $vars->{oldbugmove_user_is_mover} = \&_user_is_mover;
+}
+
+sub object_before_delete {
+ my ($self, $args) = @_;
+ my $object = $args->{'object'};
+ if ($object->isa('Bugzilla::Field::Choice::resolution')) {
+ if ($object->name eq 'MOVED') {
+ ThrowUserError('oldbugmove_no_delete_moved');
+ }
+ }
+}
+
+sub object_before_set {
+ my ($self, $args) = @_;
+ my ($object, $field) = @$args{qw(object field)};
+ if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) {
+ # Store the old value so that end_of_set can check it.
+ $object->{'_oldbugmove_old_resolution'} = $object->resolution;
+ }
+}
+
+sub object_end_of_set {
+ my ($self, $args) = @_;
+ my ($object, $field) = @$args{qw(object field)};
+ if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) {
+ my $old_value = delete $object->{'_oldbugmove_old_resolution'};
+ return if $old_value eq $object->resolution;
+ if ($object->resolution eq 'MOVED') {
+ $object->add_comment('', { type => CMT_MOVED_TO,
+ extra_data => Bugzilla->user->login });
+ }
+ }
+}
+
+sub object_end_of_set_all {
+ my ($self, $args) = @_;
+ my $object = $args->{'object'};
+
+ if ($object->isa('Bugzilla::Bug') and Bugzilla->input_params->{'oldbugmove'}) {
+ my $new_status = Bugzilla->params->{'duplicate_or_move_bug_status'};
+ $object->set_bug_status($new_status, { resolution => 'MOVED' });
+ }
+}
+
+sub object_validators {
+ my ($self, $args) = @_;
+ my ($class, $validators) = @$args{qw(class validators)};
+ if ($class->isa('Bugzilla::Comment')) {
+ my $extra_data_validator = $validators->{extra_data};
+ $validators->{extra_data} =
+ sub { _check_comment_extra_data($extra_data_validator, @_) };
+ }
+ elsif ($class->isa('Bugzilla::Bug')) {
+ my $reso_validator = $validators->{resolution};
+ $validators->{resolution} =
+ sub { _check_bug_resolution($reso_validator, @_) };
+ }
+}
+
+sub _check_bug_resolution {
+ my $original_validator = shift;
+ my ($invocant, $resolution) = @_;
+
+ if ($resolution eq 'MOVED' and !Bugzilla->input_params->{'oldbugmove'}) {
+ # MOVED has a special meaning and can only be used when
+ # really moving bugs to another installation.
+ ThrowUserError('oldbugmove_no_manual_move');
+ }
+
+ return $original_validator->(@_);
+}
+
+sub _check_comment_extra_data {
+ my $original_validator = shift;
+ my ($invocant, $extra_data, undef, $params) = @_;
+ my $type = blessed($invocant) ? $invocant->type : $params->{type};
+
+ if ($type == CMT_MOVED_TO) {
+ return Bugzilla::User->check($extra_data)->login;
+ }
+ return $original_validator->(@_);
+}
+
+sub bug_end_of_update {
+ my ($self, $args) = @_;
+ my ($bug, $old_bug, $changes) = @$args{qw(bug old_bug changes)};
+ if (defined $changes->{'resolution'}
+ and $changes->{'resolution'}->[1] eq 'MOVED')
+ {
+ $self->_move_bug($bug, $old_bug);
+ }
+}
+
+sub _move_bug {
+ my ($self, $bug, $old_bug) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ my $template = Bugzilla->template;
+
+ _user_is_mover(Bugzilla->user)
+ or ThrowUserError("auth_failure", { action => 'move',
+ object => 'bugs' });
+
+ # Don't export the new status and resolution. We want the current
+ # ones.
+ local $Storable::forgive_me = 1;
+ my $export_me = dclone($bug);
+ $export_me->{bug_status} = $old_bug->bug_status;
+ delete $export_me->{status};
+ $export_me->{resolution} = $old_bug->resolution;
+
+ # Prepare and send all data about these bugs to the new database
+ my $to = Bugzilla->params->{'move-to-address'};
+ $to =~ s/@/\@/;
+ my $from = Bugzilla->params->{'mailfrom'};
+ $from =~ s/@/\@/;
+ my $msg = "To: $to\n";
+ $msg .= "From: Bugzilla <" . $from . ">\n";
+ $msg .= "Subject: Moving bug " . $bug->id . "\n\n";
+ my @fieldlist = (Bugzilla::Bug->fields, 'group', 'long_desc',
+ 'attachment', 'attachmentdata');
+ my %displayfields = map { $_ => 1 } @fieldlist;
+ my $vars = { bugs => [$export_me], displayfields => \%displayfields };
+ $template->process("bug/show.xml.tmpl", $vars, \$msg)
+ || ThrowTemplateError($template->error());
+ $msg .= "\n";
+ MessageToMTA($msg);
+}
+
+sub _user_is_mover {
+ my $user = shift;
+
+ my @movers = map { trim($_) } split(',', Bugzilla->params->{'movers'});
+ return ($user->id and grep($_ eq $user->login, @movers)) ? 1 : 0;
+}
+
+__PACKAGE__->NAME;
diff --git a/Websites/bugs.webkit.org/extensions/example/disabled b/Websites/bugs.webkit.org/extensions/OldBugMove/disabled
similarity index 100%
copy from Websites/bugs.webkit.org/extensions/example/disabled
copy to Websites/bugs.webkit.org/extensions/OldBugMove/disabled
diff --git a/Websites/bugs.webkit.org/Bugzilla/Config/BugMove.pm b/Websites/bugs.webkit.org/extensions/OldBugMove/lib/Params.pm
similarity index 72%
rename from Websites/bugs.webkit.org/Bugzilla/Config/BugMove.pm
rename to Websites/bugs.webkit.org/extensions/OldBugMove/lib/Params.pm
index 87f6cbd..a8617e3 100644
--- a/Websites/bugs.webkit.org/Bugzilla/Config/BugMove.pm
+++ b/Websites/bugs.webkit.org/extensions/OldBugMove/lib/Params.pm
@@ -29,29 +29,15 @@
# Frédéric Buclin <LpSolit@gmail.com>
#
-package Bugzilla::Config::BugMove;
+package Bugzilla::Extension::OldBugMove::Params;
use strict;
use Bugzilla::Config::Common;
-$Bugzilla::Config::BugMove::sortkey = "05";
+our $sortkey = 700;
-sub get_param_list {
- my $class = shift;
- my @param_list = (
- {
- name => 'move-enabled',
- type => 'b',
- default => 0
- },
-
- {
- name => 'move-button-text',
- type => 't',
- default => 'Move To Bugscape'
- },
-
+use constant get_param_list => (
{
name => 'move-to-url',
type => 't',
@@ -65,29 +51,10 @@
},
{
- name => 'moved-from-address',
- type => 't',
- default => 'bugzilla-admin'
- },
-
- {
name => 'movers',
type => 't',
default => ''
},
-
- {
- name => 'moved-default-product',
- type => 't',
- default => ''
- },
-
- {
- name => 'moved-default-component',
- type => 't',
- default => ''
- } );
- return @param_list;
-}
+);
1;
diff --git a/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/admin/params/oldbugmove.html.tmpl b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/admin/params/oldbugmove.html.tmpl
new file mode 100644
index 0000000..ce588b1
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/admin/params/oldbugmove.html.tmpl
@@ -0,0 +1,40 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+[%
+ title = "$terms.Bug Moving"
+ desc = "Set up parameters to move $terms.bugs to/from another installation"
+%]
+
+[% param_descs = {
+
+ "move-to-url" =>
+ "The URL of the database we allow some of our $terms.bugs to"
+ _ " be moved to.",
+
+ "move-to-address" =>
+ "To move ${terms.bugs}, an email is sent to the target database."
+ _ " This is the email address that that database uses to listen"
+ _ " for incoming ${terms.bugs}.",
+
+ movers =>
+ "A list of people with permission to move $terms.bugs ",
+
+} %]
diff --git a/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl
new file mode 100644
index 0000000..0a7a4fa
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl
@@ -0,0 +1,27 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF oldbugmove_user_is_mover(user) AND bug.resolution != 'MOVED' %]
+ <p>
+ <input type="submit" id="oldbugmove" name="oldbugmove"
+ value="Move [% terms.Bug FILTER html %] to
+ [%= Param('move-to-url') FILTER html %]">
+ </p>
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/bug/format_comment-type.txt.tmpl b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/bug/format_comment-type.txt.tmpl
new file mode 100644
index 0000000..1ce8e36
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/bug/format_comment-type.txt.tmpl
@@ -0,0 +1,29 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF comment.type == constants.CMT_MOVED_TO %]
+[% comment.body %]
+
+[%+ terms.Bug %] moved to [% Param("move-to-url") %].
+If the move succeeded, [% comment.extra_data FILTER email %] will receive a mail
+containing the number of the new [% terms.bug %] in the other database.
+If all went well, please paste in a link to the new [% terms.bug %].
+Otherwise, reopen this [% terms.bug %].
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/global/user-error-auth_failure_action.html.tmpl b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/global/user-error-auth_failure_action.html.tmpl
new file mode 100644
index 0000000..5850097
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/global/user-error-auth_failure_action.html.tmpl
@@ -0,0 +1,23 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF action == "move" %]
+ move
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/global/user-error-errors.html.tmpl b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644
index 0000000..9351177
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -0,0 +1,29 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF error == "oldbugmove_no_delete_moved" %]
+ As long as the OldBugMove extension is active, you cannot
+ delete the [%+ display_value("resolution", "MOVED") FILTER html %]
+ resolution.
+[% ELSIF error == "oldbugmove_no_manual_move" %]
+ You cannot set the resolution of [% terms.abug %] to
+ [%+ display_value("resolution", "MOVED") FILTER html %] without
+ moving the [% terms.bug %].
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/list/edit-multiple-after_groups.html.tmpl b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/list/edit-multiple-after_groups.html.tmpl
new file mode 100644
index 0000000..10e6f73
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/OldBugMove/template/en/default/hook/list/edit-multiple-after_groups.html.tmpl
@@ -0,0 +1,28 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% IF oldbugmove_user_is_mover(user) %]
+ <p>
+ <input type="submit" id="oldbugmove" name="oldbugmove"
+ value="Move [% terms.Bugs FILTER html %] to
+ [%= Param('move-to-url') FILTER html %]">
+ </p>
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/Extension.pm b/Websites/bugs.webkit.org/extensions/Voting/Extension.pm
new file mode 100644
index 0000000..ead8126
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/Extension.pm
@@ -0,0 +1,888 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Terry Weissman <terry@mozilla.org>
+# Stephan Niemz <st.n@gmx.net>
+# Christopher Aillon <christopher@aillon.com>
+# Gervase Markham <gerv@gerv.net>
+# Frédéric Buclin <LpSolit@gmail.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension::Voting;
+use strict;
+use base qw(Bugzilla::Extension);
+
+use Bugzilla::Bug;
+use Bugzilla::BugMail;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field;
+use Bugzilla::Mailer;
+use Bugzilla::User;
+use Bugzilla::Util qw(detaint_natural);
+use Bugzilla::Token;
+
+use List::Util qw(min);
+
+use constant NAME => 'Voting';
+use constant VERSION => BUGZILLA_VERSION;
+use constant DEFAULT_VOTES_PER_BUG => 1;
+# These came from Bugzilla itself, so they maintain the old numbers
+# they had before.
+use constant CMT_POPULAR_VOTES => 3;
+use constant REL_VOTER => 4;
+
+################
+# Installation #
+################
+
+BEGIN {
+ *Bugzilla::Bug::votes = \&votes;
+}
+
+sub votes {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ return $self->{votes} if exists $self->{votes};
+
+ $self->{votes} = $dbh->selectrow_array('SELECT votes FROM bugs WHERE bug_id = ?',
+ undef, $self->id);
+ return $self->{votes};
+}
+
+sub db_schema_abstract_schema {
+ my ($self, $args) = @_;
+ $args->{'schema'}->{'votes'} = {
+ FIELDS => [
+ who => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'CASCADE'}},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'}},
+ vote_count => {TYPE => 'INT2', NOTNULL => 1},
+ ],
+ INDEXES => [
+ votes_who_idx => ['who'],
+ votes_bug_id_idx => ['bug_id'],
+ ],
+ };
+}
+
+sub install_update_db {
+ my $dbh = Bugzilla->dbh;
+ # Note that before Bugzilla 4.0, voting was a built-in part of Bugzilla,
+ # so updates to the columns for old versions of Bugzilla happen in
+ # Bugzilla::Install::DB, and can't safely be moved to this extension.
+
+ my $field = new Bugzilla::Field({ name => 'votes' });
+ if (!$field) {
+ Bugzilla::Field->create(
+ { name => 'votes', description => 'Votes', buglist => 1 });
+ }
+
+ $dbh->bz_add_column('products', 'votesperuser',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_add_column('products', 'maxvotesperbug',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => DEFAULT_VOTES_PER_BUG});
+ $dbh->bz_add_column('products', 'votestoconfirm',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0});
+
+ $dbh->bz_add_column('bugs', 'votes',
+ {TYPE => 'INT3', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_add_index('bugs', 'bugs_votes_idx', ['votes']);
+
+ # maxvotesperbug used to default to 10,000, which isn't very sensible.
+ my $per_bug = $dbh->bz_column_info('products', 'maxvotesperbug');
+ if ($per_bug->{DEFAULT} != DEFAULT_VOTES_PER_BUG) {
+ $dbh->bz_alter_column('products', 'maxvotesperbug',
+ {TYPE => 'INT2', NOTNULL => 1, DEFAULT => DEFAULT_VOTES_PER_BUG});
+ }
+}
+
+###########
+# Objects #
+###########
+
+sub object_columns {
+ my ($self, $args) = @_;
+ my ($class, $columns) = @$args{qw(class columns)};
+ if ($class->isa('Bugzilla::Bug')) {
+ push(@$columns, 'votes');
+ }
+ elsif ($class->isa('Bugzilla::Product')) {
+ push(@$columns, qw(votesperuser maxvotesperbug votestoconfirm));
+ }
+}
+
+sub bug_fields {
+ my ($self, $args) = @_;
+ my $fields = $args->{fields};
+ push(@$fields, 'votes');
+}
+
+sub object_update_columns {
+ my ($self, $args) = @_;
+ my ($object, $columns) = @$args{qw(object columns)};
+ if ($object->isa('Bugzilla::Product')) {
+ push(@$columns, qw(votesperuser maxvotesperbug votestoconfirm));
+ }
+}
+
+sub object_validators {
+ my ($self, $args) = @_;
+ my ($class, $validators) = @$args{qw(class validators)};
+ if ($class->isa('Bugzilla::Product')) {
+ $validators->{'votesperuser'} = \&_check_votesperuser;
+ $validators->{'maxvotesperbug'} = \&_check_maxvotesperbug;
+ $validators->{'votestoconfirm'} = \&_check_votestoconfirm;
+ }
+}
+
+sub object_before_create {
+ my ($self, $args) = @_;
+ my ($class, $params) = @$args{qw(class params)};
+ if ($class->isa('Bugzilla::Bug')) {
+ # Don't ever allow people to directly specify "votes" into the bugs
+ # table.
+ delete $params->{votes};
+ }
+ elsif ($class->isa('Bugzilla::Product')) {
+ my $input = Bugzilla->input_params;
+ $params->{votesperuser} = $input->{'votesperuser'};
+ $params->{maxvotesperbug} = $input->{'maxvotesperbug'};
+ $params->{votestoconfirm} = $input->{'votestoconfirm'};
+ }
+}
+
+sub object_end_of_set_all {
+ my ($self, $args) = @_;
+ my ($object) = $args->{object};
+ if ($object->isa('Bugzilla::Product')) {
+ my $input = Bugzilla->input_params;
+ $object->set('votesperuser', $input->{'votesperuser'});
+ $object->set('maxvotesperbug', $input->{'maxvotesperbug'});
+ $object->set('votestoconfirm', $input->{'votestoconfirm'});
+ }
+}
+
+sub object_end_of_update {
+ my ($self, $args) = @_;
+ my ($object, $changes) = @$args{qw(object changes)};
+ if ( $object->isa('Bugzilla::Product')
+ and ($changes->{maxvotesperbug} or $changes->{votesperuser}
+ or $changes->{votestoconfirm}) )
+ {
+ _modify_bug_votes($object, $changes);
+ }
+}
+
+sub bug_end_of_update {
+ my ($self, $args) = @_;
+ my ($bug, $changes) = @$args{qw(bug changes)};
+
+ if ($changes->{'product'}) {
+ my @msgs;
+ # If some votes have been removed, RemoveVotes() returns
+ # a list of messages to send to voters.
+ @msgs = _remove_votes($bug->id, 0, 'votes_bug_moved');
+ _confirm_if_vote_confirmed($bug->id);
+
+ foreach my $msg (@msgs) {
+ MessageToMTA($msg);
+ }
+ }
+}
+
+#############
+# Templates #
+#############
+
+sub template_before_create {
+ my ($self, $args) = @_;
+ my $config = $args->{config};
+ my $constants = $config->{CONSTANTS};
+ $constants->{REL_VOTER} = REL_VOTER;
+ $constants->{CMT_POPULAR_VOTES} = CMT_POPULAR_VOTES;
+ $constants->{DEFAULT_VOTES_PER_BUG} = DEFAULT_VOTES_PER_BUG;
+}
+
+
+sub template_before_process {
+ my ($self, $args) = @_;
+ my ($vars, $file) = @$args{qw(vars file)};
+ if ($file eq 'admin/users/confirm-delete.html.tmpl') {
+ my $who = $vars->{otheruser};
+ my $votes = Bugzilla->dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM votes WHERE who = ?', undef, $who->id);
+ if ($votes) {
+ $vars->{other_safe} = 1;
+ $vars->{votes} = $votes;
+ }
+ }
+}
+
+###########
+# Bugmail #
+###########
+
+sub bugmail_recipients {
+ my ($self, $args) = @_;
+ my ($bug, $recipients) = @$args{qw(bug recipients)};
+ my $dbh = Bugzilla->dbh;
+
+ my $voters = $dbh->selectcol_arrayref(
+ "SELECT who FROM votes WHERE bug_id = ?", undef, $bug->id);
+ $recipients->{$_}->{+REL_VOTER} = 1 foreach (@$voters);
+}
+
+sub bugmail_relationships {
+ my ($self, $args) = @_;
+ my $relationships = $args->{relationships};
+ $relationships->{+REL_VOTER} = 'Voter';
+}
+
+###############
+# Sanitycheck #
+###############
+
+sub sanitycheck_check {
+ my ($self, $args) = @_;
+ my $status = $args->{status};
+
+ # Vote Cache
+ $status->('voting_count_start');
+ my $dbh = Bugzilla->dbh;
+ my %cached_counts = @{ $dbh->selectcol_arrayref(
+ 'SELECT bug_id, votes FROM bugs', {Columns=>[1,2]}) };
+
+ my %real_counts = @{ $dbh->selectcol_arrayref(
+ 'SELECT bug_id, SUM(vote_count) FROM votes '
+ . $dbh->sql_group_by('bug_id'), {Columns=>[1,2]}) };
+
+ my $needs_rebuild;
+ foreach my $id (keys %cached_counts) {
+ my $cached_count = $cached_counts{$id};
+ my $real_count = $real_counts{$id} || 0;
+ if ($cached_count < 0) {
+ $status->('voting_count_alert', { id => $id }, 'alert');
+ }
+ elsif ($cached_count != $real_count) {
+ $status->('voting_cache_alert', { id => $id }, 'alert');
+ $needs_rebuild = 1;
+ }
+ }
+
+ $status->('voting_cache_rebuild_fix') if $needs_rebuild;
+}
+
+sub sanitycheck_repair {
+ my ($self, $args) = @_;
+ my $status = $args->{status};
+ my $input = Bugzilla->input_params;
+ my $dbh = Bugzilla->dbh;
+
+ return if !$input->{rebuild_vote_cache};
+
+ $status->('voting_cache_rebuild_start');
+ $dbh->bz_start_transaction();
+ $dbh->do('UPDATE bugs SET votes = 0');
+
+ my $sth = $dbh->prepare(
+ 'SELECT bug_id, SUM(vote_count) FROM votes '
+ . $dbh->sql_group_by('bug_id'));
+ $sth->execute();
+
+ my $sth_update = $dbh->prepare(
+ 'UPDATE bugs SET votes = ? WHERE bug_id = ?');
+ while (my ($id, $count) = $sth->fetchrow_array) {
+ $sth_update->execute($count, $id);
+ }
+ $dbh->bz_commit_transaction();
+ $status->('voting_cache_rebuild_end');
+}
+
+
+##############
+# Validators #
+##############
+
+sub _check_votesperuser {
+ return _check_votes(0, @_);
+}
+
+sub _check_maxvotesperbug {
+ return _check_votes(DEFAULT_VOTES_PER_BUG, @_);
+}
+
+sub _check_votestoconfirm {
+ return _check_votes(0, @_);
+}
+
+# This subroutine is only used internally by other _check_votes_* validators.
+sub _check_votes {
+ my ($default, $invocant, $votes, $field) = @_;
+
+ detaint_natural($votes) if defined $votes;
+ # On product creation, if the number of votes is not a valid integer,
+ # we silently fall back to the given default value.
+ # If the product already exists and the change is illegal, we complain.
+ if (!defined $votes) {
+ if (ref $invocant) {
+ ThrowUserError('voting_product_illegal_votes',
+ { field => $field, votes => $_[2] });
+ }
+ else {
+ $votes = $default;
+ }
+ }
+ return $votes;
+}
+
+#########
+# Pages #
+#########
+
+sub page_before_template {
+ my ($self, $args) = @_;
+ my $page = $args->{page_id};
+ my $vars = $args->{vars};
+
+ if ($page =~ m{^voting/bug\.}) {
+ _page_bug($vars);
+ }
+ elsif ($page =~ m{^voting/user\.}) {
+ _page_user($vars);
+ }
+}
+
+sub _page_bug {
+ my ($vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $input = Bugzilla->input_params;
+
+ my $bug_id = $input->{bug_id};
+ my $bug = Bugzilla::Bug->check($bug_id);
+
+ $vars->{'bug'} = $bug;
+ $vars->{'users'} =
+ $dbh->selectall_arrayref('SELECT profiles.login_name,
+ profiles.userid AS id,
+ votes.vote_count
+ FROM votes
+ INNER JOIN profiles
+ ON profiles.userid = votes.who
+ WHERE votes.bug_id = ?',
+ {Slice=>{}}, $bug->id);
+}
+
+sub _page_user {
+ my ($vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ my $input = Bugzilla->input_params;
+
+ my $action = $input->{action};
+ if ($action and $action eq 'vote') {
+ _update_votes($vars);
+ }
+
+ # If a bug_id is given, and we're editing, we'll add it to the votes list.
+
+ my $bug_id = $input->{bug_id};
+ my $bug = Bugzilla::Bug->check($bug_id) if $bug_id;
+ my $who_id = $input->{user_id} || $user->id;
+
+ # Logged-out users must specify a user_id.
+ Bugzilla->login(LOGIN_REQUIRED) if !$who_id;
+
+ my $who = Bugzilla::User->check({ id => $who_id });
+
+ my $canedit = $user->id == $who->id;
+
+ $dbh->bz_start_transaction();
+
+ if ($canedit && $bug) {
+ # Make sure there is an entry for this bug
+ # in the vote table, just so that things display right.
+ my $has_votes = $dbh->selectrow_array('SELECT vote_count FROM votes
+ WHERE bug_id = ? AND who = ?',
+ undef, ($bug->id, $who->id));
+ if (!$has_votes) {
+ $dbh->do('INSERT INTO votes (who, bug_id, vote_count)
+ VALUES (?, ?, 0)', undef, ($who->id, $bug->id));
+ }
+ }
+
+ my (@products, @all_bug_ids);
+ # Read the votes data for this user for each product.
+ foreach my $product (@{ $user->get_selectable_products }) {
+ next unless ($product->{votesperuser} > 0);
+
+ my @bugs;
+ my @bug_ids;
+ my $total = 0;
+ my $onevoteonly = 0;
+
+ my $vote_list =
+ $dbh->selectall_arrayref('SELECT votes.bug_id, votes.vote_count,
+ bugs.short_desc
+ FROM votes
+ INNER JOIN bugs
+ ON votes.bug_id = bugs.bug_id
+ WHERE votes.who = ?
+ AND bugs.product_id = ?
+ ORDER BY votes.bug_id',
+ undef, ($who->id, $product->id));
+
+ foreach (@$vote_list) {
+ my ($id, $count, $summary) = @$_;
+ $total += $count;
+
+ # Next if user can't see this bug. So, the totals will be correct
+ # and they can see there are votes 'missing', but not on what bug
+ # they are. This seems a reasonable compromise; the alternative is
+ # to lie in the totals.
+ next if !$user->can_see_bug($id);
+
+ push (@bugs, { id => $id,
+ summary => $summary,
+ count => $count });
+ push (@bug_ids, $id);
+ push (@all_bug_ids, $id);
+ }
+
+ $onevoteonly = 1 if (min($product->{votesperuser},
+ $product->{maxvotesperbug}) == 1);
+
+ # Only add the product for display if there are any bugs in it.
+ if ($#bugs > -1) {
+ push (@products, { name => $product->name,
+ bugs => \@bugs,
+ bug_ids => \@bug_ids,
+ onevoteonly => $onevoteonly,
+ total => $total,
+ maxvotes => $product->{votesperuser},
+ maxperbug => $product->{maxvotesperbug} });
+ }
+ }
+
+ if ($canedit && $bug) {
+ $dbh->do('DELETE FROM votes WHERE vote_count = 0 AND who = ?',
+ undef, $who->id);
+ }
+ $dbh->bz_commit_transaction();
+
+ $vars->{'canedit'} = $canedit;
+ $vars->{'voting_user'} = { "login" => $who->name };
+ $vars->{'products'} = \@products;
+ $vars->{'this_bug'} = $bug;
+ $vars->{'all_bug_ids'} = \@all_bug_ids;
+}
+
+sub _update_votes {
+ my ($vars) = @_;
+
+ ############################################################################
+ # Begin Data/Security Validation
+ ############################################################################
+
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my $template = Bugzilla->template;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $input = Bugzilla->input_params;
+
+ # Build a list of bug IDs for which votes have been submitted. Votes
+ # are submitted in form fields in which the field names are the bug
+ # IDs and the field values are the number of votes.
+
+ my @buglist = grep {/^\d+$/} keys %$input;
+
+ # If no bugs are in the buglist, let's make sure the user gets notified
+ # that their votes will get nuked if they continue.
+ if (scalar(@buglist) == 0) {
+ if (!defined $cgi->param('delete_all_votes')) {
+ print $cgi->header();
+ $template->process("voting/delete-all.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+ elsif ($cgi->param('delete_all_votes') == 0) {
+ print $cgi->redirect("page.cgi?id=voting/user.html");
+ exit;
+ }
+ }
+
+ # Call check() on each bug ID to make sure it is a positive
+ # integer representing an existing bug that the user is authorized
+ # to access, and make sure the number of votes submitted is also
+ # a non-negative integer (a series of digits not preceded by a
+ # minus sign).
+ my (%votes, @bugs);
+ foreach my $id (@buglist) {
+ my $bug = Bugzilla::Bug->check($id);
+ push(@bugs, $bug);
+ $id = $bug->id;
+ $votes{$id} = $input->{$id};
+ detaint_natural($votes{$id})
+ || ThrowUserError("voting_must_be_nonnegative");
+ }
+
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['vote']);
+
+ ############################################################################
+ # End Data/Security Validation
+ ############################################################################
+ my $who = $user->id;
+
+ # If the user is voting for bugs, make sure they aren't overstuffing
+ # the ballot box.
+ if (scalar @bugs) {
+ my (%prodcount, %products);
+ foreach my $bug (@bugs) {
+ my $bug_id = $bug->id;
+ my $prod = $bug->product;
+ $products{$prod} ||= $bug->product_obj;
+ $prodcount{$prod} ||= 0;
+ $prodcount{$prod} += $votes{$bug_id};
+
+ # Make sure we haven't broken the votes-per-bug limit
+ ($votes{$bug_id} <= $products{$prod}->{maxvotesperbug})
+ || ThrowUserError("voting_too_many_votes_for_bug",
+ {max => $products{$prod}->{maxvotesperbug},
+ product => $prod,
+ votes => $votes{$bug_id}});
+ }
+
+ # Make sure we haven't broken the votes-per-product limit
+ foreach my $prod (keys(%prodcount)) {
+ ($prodcount{$prod} <= $products{$prod}->{votesperuser})
+ || ThrowUserError("voting_too_many_votes_for_product",
+ {max => $products{$prod}->{votesperuser},
+ product => $prod,
+ votes => $prodcount{$prod}});
+ }
+ }
+
+ # Update the user's votes in the database. If the user did not submit
+ # any votes, they may be using a form with checkboxes to remove all their
+ # votes (checkboxes are not submitted along with other form data when
+ # they are not checked, and Bugzilla uses them to represent single votes
+ # for products that only allow one vote per bug). In that case, we still
+ # need to clear the user's votes from the database.
+ my %affected;
+ $dbh->bz_start_transaction();
+
+ # Take note of, and delete the user's old votes from the database.
+ my $bug_list = $dbh->selectcol_arrayref('SELECT bug_id FROM votes
+ WHERE who = ?', undef, $who);
+
+ foreach my $id (@$bug_list) {
+ $affected{$id} = 1;
+ }
+ $dbh->do('DELETE FROM votes WHERE who = ?', undef, $who);
+
+ my $sth_insertVotes = $dbh->prepare('INSERT INTO votes (who, bug_id, vote_count)
+ VALUES (?, ?, ?)');
+
+ # Insert the new values in their place
+ foreach my $id (@buglist) {
+ if ($votes{$id} > 0) {
+ $sth_insertVotes->execute($who, $id, $votes{$id});
+ }
+ $affected{$id} = 1;
+ }
+
+ # Update the cached values in the bugs table
+ print $cgi->header();
+ my @updated_bugs = ();
+
+ my $sth_getVotes = $dbh->prepare("SELECT SUM(vote_count) FROM votes
+ WHERE bug_id = ?");
+
+ my $sth_updateVotes = $dbh->prepare("UPDATE bugs SET votes = ?
+ WHERE bug_id = ?");
+
+ foreach my $id (keys %affected) {
+ $sth_getVotes->execute($id);
+ my $v = $sth_getVotes->fetchrow_array || 0;
+ $sth_updateVotes->execute($v, $id);
+
+ my $confirmed = _confirm_if_vote_confirmed($id);
+ push (@updated_bugs, $id) if $confirmed;
+ }
+
+ $dbh->bz_commit_transaction();
+
+ $vars->{'type'} = "votes";
+ $vars->{'title_tag'} = 'change_votes';
+ foreach my $bug_id (@updated_bugs) {
+ $vars->{'id'} = $bug_id;
+ $vars->{'sent_bugmail'} =
+ Bugzilla::BugMail::Send($bug_id, { 'changer' => $user });
+
+ $template->process("bug/process/results.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ # Set header_done to 1 only after the first bug.
+ $vars->{'header_done'} = 1;
+ }
+ $vars->{'votes_recorded'} = 1;
+}
+
+######################
+# Helper Subroutines #
+######################
+
+sub _modify_bug_votes {
+ my ($product, $changes) = @_;
+ my $dbh = Bugzilla->dbh;
+ my @msgs;
+
+ # 1. too many votes for a single user on a single bug.
+ my @toomanyvotes_list;
+ if ($product->{maxvotesperbug} < $product->{votesperuser}) {
+ my $votes = $dbh->selectall_arrayref(
+ 'SELECT votes.who, votes.bug_id
+ FROM votes
+ INNER JOIN bugs ON bugs.bug_id = votes.bug_id
+ WHERE bugs.product_id = ?
+ AND votes.vote_count > ?',
+ undef, ($product->id, $product->{maxvotesperbug}));
+
+ foreach my $vote (@$votes) {
+ my ($who, $id) = (@$vote);
+ # If some votes are removed, _remove_votes() returns a list
+ # of messages to send to voters.
+ push(@msgs, _remove_votes($id, $who, 'votes_too_many_per_bug'));
+ my $name = user_id_to_login($who);
+
+ push(@toomanyvotes_list, {id => $id, name => $name});
+ }
+ }
+
+ $changes->{'_too_many_votes'} = \@toomanyvotes_list;
+
+ # 2. too many total votes for a single user.
+ # This part doesn't work in the general case because _remove_votes
+ # doesn't enforce votesperuser (except per-bug when it's less
+ # than maxvotesperbug). See _remove_votes().
+
+ my $votes = $dbh->selectall_arrayref(
+ 'SELECT votes.who, votes.vote_count
+ FROM votes
+ INNER JOIN bugs ON bugs.bug_id = votes.bug_id
+ WHERE bugs.product_id = ?',
+ undef, $product->id);
+
+ my %counts;
+ foreach my $vote (@$votes) {
+ my ($who, $count) = @$vote;
+ if (!defined $counts{$who}) {
+ $counts{$who} = $count;
+ } else {
+ $counts{$who} += $count;
+ }
+ }
+
+ my @toomanytotalvotes_list;
+ foreach my $who (keys(%counts)) {
+ if ($counts{$who} > $product->{votesperuser}) {
+ my $bug_ids = $dbh->selectcol_arrayref(
+ 'SELECT votes.bug_id
+ FROM votes
+ INNER JOIN bugs ON bugs.bug_id = votes.bug_id
+ WHERE bugs.product_id = ?
+ AND votes.who = ?',
+ undef, $product->id, $who);
+
+ foreach my $bug_id (@$bug_ids) {
+ # _remove_votes returns a list of messages to send
+ # in case some voters had too many votes.
+ push(@msgs, _remove_votes($bug_id, $who,
+ 'votes_too_many_per_user'));
+ my $name = user_id_to_login($who);
+
+ push(@toomanytotalvotes_list, {id => $bug_id, name => $name});
+ }
+ }
+ }
+
+ $changes->{'_too_many_total_votes'} = \@toomanytotalvotes_list;
+
+ # 3. enough votes to confirm
+ my $bug_list = $dbh->selectcol_arrayref(
+ 'SELECT bug_id FROM bugs
+ WHERE product_id = ? AND bug_status = ? AND votes >= ?',
+ undef, ($product->id, 'UNCONFIRMED', $product->{votestoconfirm}));
+
+ my @updated_bugs;
+ foreach my $bug_id (@$bug_list) {
+ my $confirmed = _confirm_if_vote_confirmed($bug_id);
+ push (@updated_bugs, $bug_id) if $confirmed;
+ }
+ $changes->{'_confirmed_bugs'} = \@updated_bugs;
+
+ # Now that changes are done, we can send emails to voters.
+ foreach my $msg (@msgs) {
+ MessageToMTA($msg);
+ }
+ # And send out emails about changed bugs
+ foreach my $bug_id (@updated_bugs) {
+ my $sent_bugmail = Bugzilla::BugMail::Send(
+ $bug_id, { changer => Bugzilla->user });
+ $changes->{'_confirmed_bugs_sent_bugmail'}->{$bug_id} = $sent_bugmail;
+ }
+}
+
+# If a bug is moved to a product which allows less votes per bug
+# compared to the previous product, extra votes need to be removed.
+sub _remove_votes {
+ my ($id, $who, $reason) = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my $whopart = ($who) ? " AND votes.who = $who" : "";
+
+ my $sth = $dbh->prepare("SELECT profiles.login_name, " .
+ "profiles.userid, votes.vote_count, " .
+ "products.votesperuser, products.maxvotesperbug " .
+ "FROM profiles " .
+ "LEFT JOIN votes ON profiles.userid = votes.who " .
+ "LEFT JOIN bugs ON votes.bug_id = bugs.bug_id " .
+ "LEFT JOIN products ON products.id = bugs.product_id " .
+ "WHERE votes.bug_id = ? " . $whopart);
+ $sth->execute($id);
+ my @list;
+ while (my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = $sth->fetchrow_array()) {
+ push(@list, [$name, $userid, $oldvotes, $votesperuser, $maxvotesperbug]);
+ }
+
+ # @messages stores all emails which have to be sent, if any.
+ # This array is passed to the caller which will send these emails itself.
+ my @messages = ();
+
+ if (scalar(@list)) {
+ foreach my $ref (@list) {
+ my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = (@$ref);
+
+ $maxvotesperbug = min($votesperuser, $maxvotesperbug);
+
+ # If this product allows voting and the user's votes are in
+ # the acceptable range, then don't do anything.
+ next if $votesperuser && $oldvotes <= $maxvotesperbug;
+
+ # If the user has more votes on this bug than this product
+ # allows, then reduce the number of votes so it fits
+ my $newvotes = $maxvotesperbug;
+
+ my $removedvotes = $oldvotes - $newvotes;
+
+ if ($newvotes) {
+ $dbh->do("UPDATE votes SET vote_count = ? " .
+ "WHERE bug_id = ? AND who = ?",
+ undef, ($newvotes, $id, $userid));
+ } else {
+ $dbh->do("DELETE FROM votes WHERE bug_id = ? AND who = ?",
+ undef, ($id, $userid));
+ }
+
+ # Notice that we did not make sure that the user fit within the $votesperuser
+ # range. This is considered to be an acceptable alternative to losing votes
+ # during product moves. Then next time the user attempts to change their votes,
+ # they will be forced to fit within the $votesperuser limit.
+
+ # Now lets send the e-mail to alert the user to the fact that their votes have
+ # been reduced or removed.
+ my $vars = {
+ 'to' => $name . Bugzilla->params->{'emailsuffix'},
+ 'bugid' => $id,
+ 'reason' => $reason,
+
+ 'votesremoved' => $removedvotes,
+ 'votesold' => $oldvotes,
+ 'votesnew' => $newvotes,
+ };
+
+ my $voter = new Bugzilla::User($userid);
+ my $template = Bugzilla->template_inner($voter->setting('lang'));
+
+ my $msg;
+ $template->process("voting/votes-removed.txt.tmpl", $vars, \$msg);
+ push(@messages, $msg);
+ }
+
+ my $votes = $dbh->selectrow_array("SELECT SUM(vote_count) " .
+ "FROM votes WHERE bug_id = ?",
+ undef, $id) || 0;
+ $dbh->do("UPDATE bugs SET votes = ? WHERE bug_id = ?",
+ undef, ($votes, $id));
+ }
+ # Now return the array containing emails to be sent.
+ return @messages;
+}
+
+# If a user votes for a bug, or the number of votes required to
+# confirm a bug has been reduced, check if the bug is now confirmed.
+sub _confirm_if_vote_confirmed {
+ my $id = shift;
+ my $bug = new Bugzilla::Bug($id);
+
+ my $ret = 0;
+ if (!$bug->everconfirmed
+ and $bug->product_obj->{votestoconfirm}
+ and $bug->votes >= $bug->product_obj->{votestoconfirm})
+ {
+ $bug->add_comment('', { type => CMT_POPULAR_VOTES });
+
+ if ($bug->bug_status eq 'UNCONFIRMED') {
+ # Get a valid open state.
+ my $new_status;
+ foreach my $state (@{$bug->status->can_change_to}) {
+ if ($state->is_open && $state->name ne 'UNCONFIRMED') {
+ $new_status = $state->name;
+ last;
+ }
+ }
+ ThrowCodeError('voting_no_open_bug_status') unless $new_status;
+
+ # We cannot call $bug->set_bug_status() here, because a user without
+ # canconfirm privs should still be able to confirm a bug by
+ # popular vote. We already know the new status is valid, so it's safe.
+ $bug->{bug_status} = $new_status;
+ $bug->{everconfirmed} = 1;
+ delete $bug->{'status'}; # Contains the status object.
+ }
+ else {
+ # If the bug is in a closed state, only set everconfirmed to 1.
+ # Do not call $bug->_set_everconfirmed(), for the same reason as above.
+ $bug->{everconfirmed} = 1;
+ }
+ $bug->update();
+
+ $ret = 1;
+ }
+ return $ret;
+}
+
+
+__PACKAGE__->NAME;
diff --git a/Websites/bugs.webkit.org/extensions/example/disabled b/Websites/bugs.webkit.org/extensions/Voting/disabled
similarity index 100%
copy from Websites/bugs.webkit.org/extensions/example/disabled
copy to Websites/bugs.webkit.org/extensions/Voting/disabled
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl
new file mode 100644
index 0000000..0bd81ea
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/account/prefs/email-relationships.html.tmpl
@@ -0,0 +1,22 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% relationships.push({ id = constants.REL_VOTER, description = "Voter" }) %]
+[% no_added_removed.push(constants.REL_VOTER) %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl
new file mode 100644
index 0000000..fbbda3e
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/products/edit-common-rows.html.tmpl
@@ -0,0 +1,60 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% DEFAULT
+ product.maxvotesperbug = constants.DEFAULT_VOTES_PER_BUG
+ product.votesperuser = 0
+ product.votestoconfirm = 0
+%]
+
+<tr>
+ <th align="right">Maximum votes per person:</th>
+ <td><input size="5" maxlength="5" name="votesperuser" id="votesperuser"
+ value="[% product.votesperuser FILTER html %]">
+ </td>
+</tr>
+
+<tr>
+ <th align="right">
+ Maximum votes a person can put on a single [% terms.bug %]:
+ </th>
+ <td><input size="5" maxlength="5" name="maxvotesperbug" id="maxvotesperbug"
+ value="[% product.maxvotesperbug FILTER html %]">
+ </td>
+</tr>
+
+<tr id="votes_to_confirm_container"
+ [%- ' class="bz_default_hidden"' IF !product.allows_unconfirmed %]>
+ <th align="right">
+ Confirm [% terms.abug %] if it gets this many votes:
+ </th>
+ <td>
+ <input size="3" maxlength="5" name="votestoconfirm" id="votestoconfirm"
+ value="[% product.votestoconfirm FILTER html %]">
+ <br>(Setting this to 0 disables auto-confirming [% terms.bugs %]
+ by vote.)
+ <script type="text/javascript">
+ YAHOO.util.Event.addListener('allows_unconfirmed', 'change',
+ function() { bz_toggleClass('votes_to_confirm_container',
+ 'bz_default_hidden'); });
+ </script>
+ </td>
+</tr>
+
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl
new file mode 100644
index 0000000..15fb1ef
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/products/updated-changes.html.tmpl
@@ -0,0 +1,102 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% SET checkvotes = 0 %]
+
+[% IF changes.votesperuser.defined %]
+ <p>
+ Updated votes per user from
+ [%+ changes.votesperuser.0 FILTER html %] to
+ [%+ product.votesperuser FILTER html %].
+ </p>
+ [% checkvotes = 1 %]
+[% END %]
+
+[% IF changes.maxvotesperbug.defined %]
+ <p>
+ Updated maximum votes per [% terms.bug %] from
+ [%+ changes.maxvotesperbug.0 FILTER html %] to
+ [%+ product.maxvotesperbug FILTER html %].
+ </p>
+ [% checkvotes = 1 %]
+[% END %]
+
+[% IF changes.votestoconfirm.defined %]
+ <p>
+ Updated number of votes needed to confirm a [% terms.bug %] from
+ [%+ changes.votestoconfirm.0 FILTER html %] to
+ [%+ product.votestoconfirm FILTER html %].
+ </p>
+ [% checkvotes = 1 %]
+[% END %]
+
+[%# Note that this display of changed votes and/or confirmed bugs is
+ not very scalable. We could have a _lot_, and we just list them all.
+ One day we should limit this perhaps, or have a more scalable display %]
+
+[% IF checkvotes %]
+ <hr>
+
+ <p>Checking existing votes in this product for anybody who now
+ has too many votes for [% terms.abug %]...<br>
+ [% IF changes._too_many_votes.size %]
+ [% FOREACH detail = changes._too_many_votes %]
+ →removed votes for [% terms.bug %] <a href="show_bug.cgi?id=
+ [%- detail.id FILTER uri %]">
+ [%- detail.id FILTER html %]</a> from [% detail.name FILTER html %]<br>
+ [% END %]
+ [% ELSE %]
+ →there were none.
+ [% END %]
+ </p>
+
+ <p>Checking existing votes in this product for anybody
+ who now has too many total votes...<br>
+ [% IF changes._too_many_total_votes.size %]
+ [% FOREACH detail = changes._too_many_total_votes %]
+ →removed votes for [% terms.bug %] <a href="show_bug.cgi?id=
+ [%- detail.id FILTER uri %]">
+ [%- detail.id FILTER html %]</a> from [% detail.name FILTER html %]<br>
+ [% END %]
+ [% ELSE %]
+ →there were none.
+ [% END %]
+ </p>
+
+ <p>Checking unconfirmed [% terms.bugs %] in this product for any which now have
+ sufficient votes...<br>
+ [% IF changes._confirmed_bugs.size %]
+ [% FOREACH id = changes._confirmed_bugs %]
+
+ [%# This is INCLUDED instead of PROCESSED to avoid variables getting
+ overwritten, which happens otherwise %]
+ [% INCLUDE bug/process/results.html.tmpl
+ type = 'votes'
+ header_done = 1
+ sent_bugmail = changes._confirmed_bugs_sent_bugmail.$id
+ id = id
+ %]
+ [% END %]
+ [% ELSE %]
+ →there were none.
+ [% END %]
+ </p>
+
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
new file mode 100644
index 0000000..bbf0350
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
@@ -0,0 +1,41 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF san_tag == "voting_cache_rebuild_fix" %]
+ <a href="sanitycheck.cgi?rebuild_vote_cache=1&token=
+ [%- issue_hash_token(['sanitycheck']) FILTER uri %]">Click here to
+ rebuild the vote cache</a>
+
+[% ELSIF san_tag == "voting_cache_alert" %]
+ Bad vote cache for [% PROCESS bug_link bug_id = id %]
+
+[% ELSIF san_tag == "voting_count_start" %]
+ Checking cached vote counts.
+
+[% ELSIF san_tag == "voting_count_alert" %]
+ Bad vote sum for [% terms.bug %] [%+ id FILTER html %].
+
+[% ELSIF san_tag == "voting_cache_rebuild_start" %]
+ OK, now rebuilding vote cache.
+
+[% ELSIF san_tag == "voting_cache_rebuild_end" %]
+ Vote cache has been rebuilt
+
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl
new file mode 100644
index 0000000..a753e3a
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/admin/users/confirm-delete-warn_safe.html.tmpl
@@ -0,0 +1,38 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF votes %]
+ <li>
+ [% otheruser.login FILTER html %] has voted on
+ [% IF votes == 1 %]
+ [%+ terms.abug %]
+ [% ELSE %]
+ [%+ votes FILTER html %] [%+ terms.bugs %]
+ [% END %].
+
+ If you delete the user account,
+ [% IF votes == 1 %]
+ this vote
+ [% ELSE %]
+ these votes
+ [% END %]
+ will be deleted along with the user account.
+ </li>
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl
new file mode 100644
index 0000000..f73ffae
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/edit-after_importance.html.tmpl
@@ -0,0 +1,37 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+[% IF bug.product_obj.votesperuser %]
+ <span id="votes_container">
+ [% IF bug.votes %]
+ with
+ <a href="page.cgi?id=voting/bug.html&bug_id=
+ [%- bug.id FILTER uri %]">
+ [%- bug.votes FILTER html %]
+ [% IF bug.votes == 1 %]
+ vote
+ [% ELSE %]
+ votes
+ [% END %]</a>
+ [% END %]
+ (<a href="page.cgi?id=voting/user.html&bug_id=
+ [%- bug.id FILTER uri %]#vote_
+ [%- bug.id FILTER uri %]">vote</a>)
+ </span>
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl
new file mode 100644
index 0000000..ebba6fc
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/format_comment-type.txt.tmpl
@@ -0,0 +1,23 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF comment.type == constants.CMT_POPULAR_VOTES %]
+*** This [% terms.bug %] has been confirmed by popular vote. ***
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl
new file mode 100644
index 0000000..a453065
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/process/header-title.html.tmpl
@@ -0,0 +1,24 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF title_tag == "change_votes" %]
+ [% title = "Change Votes" %]
+[% END %]
+
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl
new file mode 100644
index 0000000..ae0d465
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/process/results-title.html.tmpl
@@ -0,0 +1,21 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% title.votes = "$Link confirmed by number of votes" %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/show-header-end.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/show-header-end.html.tmpl
new file mode 100644
index 0000000..2e2c2d9
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/bug/show-header-end.html.tmpl
@@ -0,0 +1,21 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% style_urls.push('extensions/Voting/web/style.css') %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/code-error-errors.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/code-error-errors.html.tmpl
new file mode 100644
index 0000000..50e9159
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/code-error-errors.html.tmpl
@@ -0,0 +1,25 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF error == "voting_no_open_bug_status" %]
+ [% title = "$terms.Bug Cannot Be Confirmed" %]
+ There is no valid transition from
+ [%+ display_value("bug_status", "UNCONFIRMED") FILTER html %] to an open state
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl
new file mode 100644
index 0000000..1becab4
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/field-descs-end.none.tmpl
@@ -0,0 +1,23 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF in_template_var %]
+ [% vars.field_descs.votes = "Votes" %]
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl
new file mode 100644
index 0000000..3a1f5a1
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/reason-descs-end.none.tmpl
@@ -0,0 +1,23 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% reason_descs.${constants.REL_VOTER} = "You voted for the ${terms.bug}." %]
+[% watch_reason_descs.${constants.REL_VOTER} =
+ "You are watching a voter for the ${terms.bug}." %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644
index 0000000..c2ff707
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -0,0 +1,55 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF error == "voting_must_be_nonnegative" %]
+ [% title = "Votes Must Be Non-negative" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ Only use non-negative numbers for your [% terms.bug %] votes.
+
+[% ELSIF error == "voting_product_illegal_votes" %]
+ [% title = "Votes Must Be Non-negative" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ '[% votes FILTER html %]' is an invalid value for the
+ <em>
+ [% IF field == "votesperuser" %]
+ Votes Per User
+ [% ELSIF field == "maxvotesperbug" %]
+ Maximum Votes Per [% terms.Bug %]
+ [% ELSIF field == "votestoconfirm" %]
+ Votes To Confirm
+ [% END %]
+ </em> field, which should contain a non-negative number.
+
+[% ELSIF error == "voting_too_many_votes_for_bug" %]
+ [% title = "Illegal Vote" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ You may only use at most [% max FILTER html %] votes for a single
+ [%+ terms.bug %] in the
+ <tt>[% product FILTER html %]</tt> product, but you are trying to
+ use [% votes FILTER html %].
+
+[% ELSIF error == "voting_too_many_votes_for_product" %]
+ [% title = "Illegal Vote" %]
+ [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
+ You tried to use [% votes FILTER html %] votes in the
+ <tt>[% product FILTER html %]</tt> product, which exceeds the maximum of
+ [%+ max FILTER html %] votes for this product.
+
+[% END %]
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/search/form-after_freetext_fields.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/search/form-after_freetext_fields.html.tmpl
new file mode 100644
index 0000000..dca1dba
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/search/form-after_freetext_fields.html.tmpl
@@ -0,0 +1,28 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+<div class="search_field_row">
+ <span class="field_label ">
+ <label for="votes">Only [% terms.bugs %] with at least</label>:
+ </span>
+ <input name="votes" id="votes" size="3"
+ value="[% default.votes.0 FILTER html %]"> votes
+ <input type="hidden" name="votes_type" value="greaterthaneq">
+</div>
diff --git a/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl
new file mode 100644
index 0000000..ca74f6d
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/hook/search/search-report-select-rep_fields.html.tmpl
@@ -0,0 +1,21 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% rep_fields.push('votes') %]
diff --git a/Websites/bugs.webkit.org/template/en/default/pages/voting.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting.html.tmpl
similarity index 97%
rename from Websites/bugs.webkit.org/template/en/default/pages/voting.html.tmpl
rename to Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting.html.tmpl
index 4e6fb47..99026c0 100644
--- a/Websites/bugs.webkit.org/template/en/default/pages/voting.html.tmpl
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting.html.tmpl
@@ -64,6 +64,6 @@
on [% terms.bugs %] you vote for.</p>
<p>You may review your votes at any time by clicking on the "<a href=
-"votes.cgi?action=show_user">My Votes</a>" link in the page footer.</p>
+"page.cgi?id=voting/user.html">My Votes</a>" link in the page footer.</p>
[% INCLUDE global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/votes/list-for-bug.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl
similarity index 73%
rename from Websites/bugs.webkit.org/template/en/default/bug/votes/list-for-bug.html.tmpl
rename to Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl
index b93d1f3..4c69a45 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/votes/list-for-bug.html.tmpl
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting/bug.html.tmpl
@@ -16,10 +16,11 @@
# Rights Reserved.
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
#%]
[%# INTERFACE:
- # bug_id: integer. ID of the bug we are listing the votes for.
+ # bug: Bugzilla::Bug that we are listing the votes for.
# users: list of hashes. May be empty. Each hash has two members:
# login_name: string. The login name of the user whose vote is attached
# vote_count: integer. The number of times that user has votes for this bug.
@@ -27,9 +28,13 @@
[% PROCESS global/variables.none.tmpl %]
+[% subheader = BLOCK %]
+ [% "$terms.Bug $bug.id" FILTER bug_link(bug) FILTER none %] - [% bug.short_desc FILTER html %]
+[% END %]
+
[% PROCESS global/header.html.tmpl
title = "Show Votes"
- subheader = "$terms.Bug <a href=\"show_bug.cgi?id=$bug_id\">$bug_id</a>"
+ subheader = subheader
%]
[% total = 0 %]
@@ -43,17 +48,18 @@
[% total = total + voter.vote_count %]
<tr>
<td>
- <a href="votes.cgi?action=show_user&user=[% voter.login_name FILTER url_quote %]">
- [% voter.login_name FILTER html %]
+ <a href="page.cgi?id=voting/user.html&user_id=
+ [%- voter.id FILTER uri %]">
+ [% voter.login_name FILTER email FILTER html %]
</a>
</td>
<td align="right">
- [% voter.vote_count %]
+ [% voter.vote_count FILTER html %]
</td>
</tr>
[% END %]
</table>
-<p>Total votes: [% total %]</p>
+<p>Total votes: [% total FILTER html %]</p>
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/votes/list-for-user.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting/user.html.tmpl
similarity index 74%
rename from Websites/bugs.webkit.org/template/en/default/bug/votes/list-for-user.html.tmpl
rename to Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting/user.html.tmpl
index 50dff7d..61eaf84 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/votes/list-for-user.html.tmpl
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/pages/voting/user.html.tmpl
@@ -31,7 +31,7 @@
# maxvotes: max votes allowed for a user in this product
# maxperbug: max votes per bug allowed for a user in this product
#
- # bug_id: number; if the user is voting for a bug, this is the bug id
+ # this_bug: Bugzilla::Bug; if the user is voting for a bug, this is the bug
#
# canedit: boolean; Should the votes be presented in a form, or readonly?
#
@@ -44,18 +44,18 @@
[% subheader = voting_user.login FILTER html %]
[% IF canedit %]
[% title = "Change Votes" %]
- [% IF bug_id %]
+ [% IF this_bug %]
[%# We .select and .focus the input so it works for textbox and
checkbox %]
- [% onload = "document.forms['voting_form'].bug_" _ bug_id _
- ".select();document.forms['voting_form'].bug_" _ bug_id _
+ [% onload = "document.forms['voting_form'].bug_" _ this_bug.id _
+ ".select();document.forms['voting_form'].bug_" _ this_bug.id _
".focus()" %]
[% END %]
[% ELSE %]
[% title = "Show Votes" %]
[% END %]
[% PROCESS global/header.html.tmpl
- style_urls = [ "skins/standard/voting.css" ]
+ style_urls = [ "extensions/Voting/web/style.css" ]
%]
[% ELSE %]
<hr>
@@ -72,8 +72,9 @@
[% END %]
[% IF products.size %]
- <form name="voting_form" method="post" action="votes.cgi">
+ <form name="voting_form" method="post" action="page.cgi?id=voting/user.html">
<input type="hidden" name="action" value="vote">
+ <input type="hidden" name="token" value="[% issue_hash_token(['vote']) FILTER html %]">
<table cellspacing="4">
<tr>
<td></td>
@@ -93,13 +94,13 @@
<tr>
<th>[% product.name FILTER html %]</th>
<td colspan="2" ><a href="buglist.cgi?bug_id=
- [%- product.bug_ids.join(",") FILTER url_quote %]">([% terms.bug %] list)</a>
+ [%- product.bug_ids.join(",") FILTER uri %]">([% terms.bug %] list)</a>
</td>
<td>
[% IF product.maxperbug < product.maxvotes AND
product.maxperbug > 1 %]
<font size="-1">
- (Note: only [% product.maxperbug %] vote
+ (Note: only [% product.maxperbug FILTER html %] vote
[% "s" IF product.maxperbug != 1 %] allowed per [% terms.bug %] in
this product.)
</font>
@@ -108,37 +109,44 @@
</tr>
[% FOREACH bug = product.bugs %]
- <tr [% IF bug.id == bug_id && canedit %]
+ <tr [% IF bug.id == this_bug.id && canedit %]
class="bz_bug_being_voted_on" [% END %]>
- <td>[% IF bug.id == bug_id && canedit %]Enter New Vote here →
- [%- END %]</td>
- <td align="right"><a name="vote_[% bug.id %]">
+ <td>
+ [% IF bug.id == this_bug.id && canedit %]
+ [% IF product.onevoteonly %]
+ Vote For This [% terms.Bug %] →
+ [% ELSE %]
+ Enter Votes Here →
+ [% END %]
+ [%- END %]
+ </td>
+ <td align="right"><a name="vote_[% bug.id FILTER html %]">
[% IF canedit %]
[% IF product.onevoteonly %]
- <input type="checkbox" name="[% bug.id %]" value="1"
- [% " checked" IF bug.count %] id="bug_[% bug.id %]">
+ <input type="checkbox" name="[% bug.id FILTER html %]" value="1"
+ [% " checked" IF bug.count %] id="bug_[% bug.id FILTER html %]">
[% ELSE %]
- <input name="[% bug.id %]" value="[% bug.count %]"
- size="2" id="bug_[% bug.id %]">
+ <input name="[% bug.id FILTER html %]" value="[% bug.count FILTER html %]"
+ size="2" id="bug_[% bug.id FILTER html %]">
[% END %]
[% ELSE %]
- [% bug.count %]
+ [% bug.count FILTER html %]
[% END %]
</a></td>
<td align="center">
- [% bug.id FILTER bug_link(bug.id) FILTER none %]
+ [% bug.id FILTER bug_link(bug) FILTER none %]
</td>
<td>
[% bug.summary FILTER html %]
- (<a href="votes.cgi?action=show_bug&bug_id=[% bug.id %]">Show Votes</a>)
+ (<a href="page.cgi?id=voting/bug.html&bug_id=[% bug.id FILTER uri %]">Show Votes</a>)
</td>
</tr>
[% END %]
<tr>
<td></td>
- <td colspan="3">[% product.total %] vote
- [% "s" IF product.total != 1 %] used out of [% product.maxvotes %]
+ <td colspan="3">[% product.total FILTER html %] vote
+ [% "s" IF product.total != 1 %] used out of [% product.maxvotes FILTER html %]
allowed.
<br>
<br>
@@ -149,7 +157,7 @@
[% IF canedit %]
<input type="submit" value="Change My Votes" id="change"> or
- <a href="buglist.cgi?bug_id=[% all_bug_ids.join(",") FILTER url_quote %]">view all
+ <a href="buglist.cgi?bug_id=[% all_bug_ids.join(",") FILTER uri %]">view all
as [% terms.bug %] list</a>
<br>
<br>
@@ -163,7 +171,7 @@
[% END %]
and then click <b>Change My Votes</b>.
[% ELSE %]
- <a href="buglist.cgi?bug_id=[% all_bug_ids.join(",") FILTER url_quote %]">View all
+ <a href="buglist.cgi?bug_id=[% all_bug_ids.join(",") FILTER uri %]">View all
as [% terms.bug %] list</a>
[% END %]
</form>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/votes/delete-all.html.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/voting/delete-all.html.tmpl
similarity index 90%
rename from Websites/bugs.webkit.org/template/en/default/bug/votes/delete-all.html.tmpl
rename to Websites/bugs.webkit.org/extensions/Voting/template/en/default/voting/delete-all.html.tmpl
index 41b7512..f0d3b7e 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/votes/delete-all.html.tmpl
+++ b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/voting/delete-all.html.tmpl
@@ -33,8 +33,9 @@
remove your vote from every [% terms.bug %] you've voted on?
</p>
-<form action="votes.cgi" method="post">
+<form action="page.cgi?id=voting/user.html" method="post">
<input type="hidden" name="action" value="vote">
+ <input type="hidden" name="token" value="[% issue_hash_token(['vote']) FILTER html %]">
<p>
<input type="radio" name="delete_all_votes" value="1">
Yes, delete all my votes
diff --git a/Websites/bugs.webkit.org/template/en/default/email/votes-removed.txt.tmpl b/Websites/bugs.webkit.org/extensions/Voting/template/en/default/voting/votes-removed.txt.tmpl
similarity index 100%
rename from Websites/bugs.webkit.org/template/en/default/email/votes-removed.txt.tmpl
rename to Websites/bugs.webkit.org/extensions/Voting/template/en/default/voting/votes-removed.txt.tmpl
diff --git a/Websites/bugs.webkit.org/skins/standard/voting.css b/Websites/bugs.webkit.org/extensions/Voting/web/style.css
similarity index 93%
rename from Websites/bugs.webkit.org/skins/standard/voting.css
rename to Websites/bugs.webkit.org/extensions/Voting/web/style.css
index 5d9c9af..c2b934e 100644
--- a/Websites/bugs.webkit.org/skins/standard/voting.css
+++ b/Websites/bugs.webkit.org/extensions/Voting/web/style.css
@@ -22,3 +22,7 @@
border-style: solid none solid none;
border-width: thin;
}
+
+#votes_container {
+ white-space: nowrap;
+}
diff --git a/Websites/bugs.webkit.org/extensions/create.pl b/Websites/bugs.webkit.org/extensions/create.pl
new file mode 100755
index 0000000..d6ecf12
--- /dev/null
+++ b/Websites/bugs.webkit.org/extensions/create.pl
@@ -0,0 +1,86 @@
+#!/usr/bin/env perl -w
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use lib qw(. lib);
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util qw(get_text);
+
+use File::Path qw(mkpath);
+use DateTime;
+
+my $base_dir = bz_locations()->{'extensionsdir'};
+
+my $name = $ARGV[0] or ThrowUserError('extension_create_no_name');
+if ($name !~ /^[A-Z]/) {
+ ThrowUserError('extension_first_letter_caps', { name => $name });
+}
+
+my $extension_dir = "$base_dir/$name";
+mkpath($extension_dir)
+ || die "$extension_dir already exists or cannot be created.\n";
+
+my $lcname = lc($name);
+foreach my $path (qw(lib web template/en/default/hook),
+ "template/en/default/$lcname")
+{
+ mkpath("$extension_dir/$path") || die "$extension_dir/$path: $!";
+}
+
+my $year = DateTime->now()->year;
+
+my $template = Bugzilla->template;
+my $vars = { year => $year, name => $name, path => $extension_dir };
+my %create_files = (
+ 'config.pm.tmpl' => 'Config.pm',
+ 'extension.pm.tmpl' => 'Extension.pm',
+ 'util.pm.tmpl' => 'lib/Util.pm',
+ 'web-readme.txt.tmpl' => 'web/README',
+ 'hook-readme.txt.tmpl' => 'template/en/default/hook/README',
+ 'name-readme.txt.tmpl' => "template/en/default/$lcname/README",
+);
+
+foreach my $template_file (keys %create_files) {
+ my $target = $create_files{$template_file};
+ my $output;
+ $template->process("extensions/$template_file", $vars, \$output)
+ or ThrowTemplateError($template->error());
+ open(my $fh, '>', "$extension_dir/$target");
+ print $fh $output;
+ close($fh);
+}
+
+print get_text('extension_created', $vars), "\n";
+
+__END__
+
+=head1 NAME
+
+extensions/create.pl - Create a framework for a new Bugzilla Extension.
+
+=head1 SYNOPSIS
+
+ extensions/create.pl NAME
+
+ Creates a framework for an extension called NAME in the F<extensions/>
+ directory.
diff --git a/Websites/bugs.webkit.org/extensions/example/code/bug-end_of_update.pl b/Websites/bugs.webkit.org/extensions/example/code/bug-end_of_update.pl
deleted file mode 100644
index 0365635..0000000
--- a/Websites/bugs.webkit.org/extensions/example/code/bug-end_of_update.pl
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Example Plugin.
-#
-# The Initial Developer of the Original Code is Everything Solved, Inc.
-# Portions created by Everything Solved are Copyright (C) 2008
-# Everything Solved, Inc. All Rights Reserved.
-#
-# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
-
-use strict;
-use warnings;
-use Bugzilla;
-use Bugzilla::Status;
-
-# This code doesn't actually *do* anything, it's just here to show you
-# how to use this hook.
-my $args = Bugzilla->hook_args;
-my $bug = $args->{'bug'};
-my $timestamp = $args->{'timestamp'};
-my $changes = $args->{'changes'};
-
-foreach my $field (keys %$changes) {
- my $used_to_be = $changes->{$field}->[0];
- my $now_it_is = $changes->{$field}->[1];
-}
-
-my $status_message;
-if (my $status_change = $changes->{'bug_status'}) {
- my $old_status = new Bugzilla::Status({ name => $status_change->[0] });
- my $new_status = new Bugzilla::Status({ name => $status_change->[1] });
- if ($new_status->is_open && !$old_status->is_open) {
- $status_message = "Bug re-opened!";
- }
- if (!$new_status->is_open && $old_status->is_open) {
- $status_message = "Bug closed!";
- }
-}
-
-my $bug_id = $bug->id;
-my $num_changes = scalar keys %$changes;
-my $result = "There were $num_changes changes to fields on bug $bug_id"
- . " at $timestamp.";
-# Uncomment this line to see $result in your webserver's error log whenever
-# you update a bug.
-# warn $result;
diff --git a/Websites/bugs.webkit.org/extensions/example/code/buglist-columns.pl b/Websites/bugs.webkit.org/extensions/example/code/buglist-columns.pl
deleted file mode 100644
index 4487b2d..0000000
--- a/Websites/bugs.webkit.org/extensions/example/code/buglist-columns.pl
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-## The Original Code is the Bugzilla Example Plugin.
-#
-# The Initial Developer of the Original Code is Canonical Ltd.
-# Portions created by Canonical Ltd. are Copyright (C) 2008
-# Canonical Ltd. All Rights Reserved.
-#
-# Contributor(s): Elliotte Martin <elliotte_martin@yahoo.com>
-
-use strict;
-use warnings;
-use Bugzilla;
-
-my $columns = Bugzilla->hook_args->{'columns'};
-$columns->{'example'} = { 'name' => 'bugs.delta_ts' , 'title' => 'Example' };
diff --git a/Websites/bugs.webkit.org/extensions/example/code/colchange-columns.pl b/Websites/bugs.webkit.org/extensions/example/code/colchange-columns.pl
deleted file mode 100644
index 6174d39..0000000
--- a/Websites/bugs.webkit.org/extensions/example/code/colchange-columns.pl
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Example Plugin.
-#
-# The Initial Developer of the Original Code is Canonical Ltd.
-# Portions created by Canonical Ltd. are Copyright (C) 2008
-# Canonical Ltd. All Rights Reserved.
-#
-# Contributor(s): Elliotte Martin <elliotte_martin@yahoo.com>
-
-
-use strict;
-use warnings;
-use Bugzilla;
-
-my $columns = Bugzilla->hook_args->{'columns'};
-push (@$columns, "example")
diff --git a/Websites/bugs.webkit.org/extensions/example/code/flag-end_of_update.pl b/Websites/bugs.webkit.org/extensions/example/code/flag-end_of_update.pl
deleted file mode 100644
index 6371bd1..0000000
--- a/Websites/bugs.webkit.org/extensions/example/code/flag-end_of_update.pl
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Example Plugin.
-#
-# The Initial Developer of the Original Code is Everything Solved, Inc.
-# Portions created by Everything Solved are Copyright (C) 2008
-# Everything Solved, Inc. All Rights Reserved.
-#
-# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
-
-use strict;
-use warnings;
-use Bugzilla;
-use Bugzilla::Util qw(diff_arrays);
-
-# This code doesn't actually *do* anything, it's just here to show you
-# how to use this hook.
-my $args = Bugzilla->hook_args;
-my ($bug, $timestamp, $old_flags, $new_flags) =
- @$args{qw(bug timestamp old_flags new_flags)};
-my ($removed, $added) = diff_arrays($old_flags, $new_flags);
-my ($granted, $denied) = (0, 0);
-foreach my $new_flag (@$added) {
- $granted++ if $new_flag =~ /\+$/;
- $denied++ if $new_flag =~ /-$/;
-}
-my $bug_id = $bug->id;
-my $result = "$granted flags were granted and $denied flags were denied"
- . " on bug $bug_id at $timestamp.";
-# Uncomment this line to see $result in your webserver's error log whenever
-# you update flags.
-# warn $result;
diff --git a/Websites/bugs.webkit.org/extensions/example/code/install-before_final_checks.pl b/Websites/bugs.webkit.org/extensions/example/code/install-before_final_checks.pl
deleted file mode 100644
index ef1bee2..0000000
--- a/Websites/bugs.webkit.org/extensions/example/code/install-before_final_checks.pl
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Example Plugin.
-#
-# The Initial Developer of the Original Code is Canonical Ltd.
-# Portions created by Canonical Ltd. are Copyright (C) 2008
-# Canonical Ltd. All Rights Reserved.
-#
-# Contributor(s): Elliotte Martin <elliotte_martin@yahoo.com>
-
-
-use strict;
-use warnings;
-use Bugzilla;
-
-my $silent = Bugzilla->hook_args->{'silent'};
-
-print "Install-before_final_checks hook\n" unless $silent;
diff --git a/Websites/bugs.webkit.org/extensions/example/code/product-confirm_delete.pl b/Websites/bugs.webkit.org/extensions/example/code/product-confirm_delete.pl
deleted file mode 100644
index 1f4c374..0000000
--- a/Websites/bugs.webkit.org/extensions/example/code/product-confirm_delete.pl
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/perl -w
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Testopia System.
-#
-# The Initial Developer of the Original Code is Greg Hendricks.
-# Portions created by Greg Hendricks are Copyright (C) 2008
-# Novell. All Rights Reserved.
-#
-# Contributor(s): Greg Hendricks <ghendricks@novell.com>
-
-use strict;
-
-my $vars = Bugzilla->hook_args->{vars};
-
-$vars->{'example'} = 1
diff --git a/Websites/bugs.webkit.org/extensions/example/info.pl b/Websites/bugs.webkit.org/extensions/example/info.pl
deleted file mode 100644
index b4620ee..0000000
--- a/Websites/bugs.webkit.org/extensions/example/info.pl
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Example WebService Plugin
-#
-# The Initial Developer of the Original Code is Everything Solved, Inc.
-# Portions created by Everything Solved, Inc. are Copyright (C) 2007
-# Everything Solved, Inc. All Rights Reserved.
-#
-# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
-# Colin Ogilvie <colin.ogilvie@gmail.com>
-
-# This script does some code to return a hash about the Extension.
-# You are required to return a hash containing the Extension version
-# You can optionaally add any other values to the hash too, as long as
-# they begin with an x_
-#
-# Eg:
-# {
-# 'version' => '0.1', # required
-# 'x_name' => 'Example Extension'
-# }
-
-use strict;
-no warnings qw(void); # Avoid "useless use of a constant in void context"
-use Bugzilla::Constants;
-
-{
- 'version' => BUGZILLA_VERSION,
- 'x_blah' => 'Hello....',
-
-};
diff --git a/Websites/bugs.webkit.org/importxml.pl b/Websites/bugs.webkit.org/importxml.pl
index 6309b4e..c457f69 100755
--- a/Websites/bugs.webkit.org/importxml.pl
+++ b/Websites/bugs.webkit.org/importxml.pl
@@ -70,6 +70,7 @@
use Bugzilla;
+use Bugzilla::Object;
use Bugzilla::Bug;
use Bugzilla::Product;
use Bugzilla::Version;
@@ -87,25 +88,23 @@
use MIME::Base64;
use MIME::Parser;
-use Date::Format;
use Getopt::Long;
use Pod::Usage;
use XML::Twig;
-# We want to capture errors and handle them here rather than have the Template
-# code barf all over the place.
-Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE);
-
my $debug = 0;
my $mail = '';
my $attach_path = '';
my $help = 0;
+my ($default_product_name, $default_component_name);
my $result = GetOptions(
"verbose|debug+" => \$debug,
"mail|sendmail!" => \$mail,
"attach_path=s" => \$attach_path,
- "help|?" => \$help
+ "help|?" => \$help,
+ "product=s" => \$default_product_name,
+ "component=s" => \$default_component_name,
);
pod2usage(0) if $help;
@@ -122,6 +121,9 @@
my $params = Bugzilla->params;
my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
+$default_product_name = '' if !defined $default_product_name;
+$default_component_name = '' if !defined $default_component_name;
+
###############################################################################
# Helper sub routines #
###############################################################################
@@ -131,7 +133,7 @@
my $subject = shift;
my $message = shift;
my @recipients = @_;
- my $from = $params->{"moved-from-address"};
+ my $from = $params->{"mailfrom"};
$from =~ s/@/\@/g;
foreach my $to (@recipients){
@@ -171,7 +173,7 @@
my (
$name, $status, $setter_login,
$requestee_login, $exporterid, $bugid,
- $productid, $componentid, $attachid
+ $componentid, $productid, $attachid
)
= @_;
@@ -321,22 +323,8 @@
}
Error( "no maintainer", "REOPEN", $exporter ) unless ($maintainer);
Error( "no exporter", "REOPEN", $exporter ) unless ($exporter);
- Error( "bug importing is disabled here", undef, $exporter ) unless ( $params->{"move-enabled"} );
Error( "invalid exporter: $exporter", "REOPEN", $exporter ) if ( !login_to_id($exporter) );
Error( "no urlbase set", "REOPEN", $exporter ) unless ($urlbase);
- my $def_product =
- new Bugzilla::Product( { name => $params->{"moved-default-product"} } )
- || Error("an invalid default product was defined for the target DB. " .
- $params->{"maintainer"} . " needs to fix the definitions of " .
- "moved-default-product. \n", "REOPEN", $exporter);
- my $def_component = new Bugzilla::Component(
- {
- product => $def_product,
- name => $params->{"moved-default-component"}
- })
- || Error("an invalid default component was defined for the target DB. " .
- $params->{"maintainer"} . " needs to fix the definitions of " .
- "moved-default-component.\n", "REOPEN", $exporter);
}
@@ -505,21 +493,17 @@
}
}
- my @long_descs;
- my $private = 0;
-
# Parse long descriptions
+ my @long_descs;
foreach my $comment ( $bug->children('long_desc') ) {
Debug( "Parsing Long Description", DEBUG_LEVEL );
- my %long_desc;
- $long_desc{'who'} = $comment->field('who');
- $long_desc{'bug_when'} = $comment->field('bug_when');
- $long_desc{'isprivate'} = $comment->{'att'}->{'isprivate'} || 0;
+ my %long_desc = ( who => $comment->field('who'),
+ bug_when => $comment->field('bug_when'),
+ isprivate => $comment->{'att'}->{'isprivate'} || 0 );
- # if one of the comments is private we need to set this flag
- if ( $long_desc{'isprivate'} && $exporter->in_group($params->{'insidergroup'})) {
- $private = 1;
- }
+ # If the exporter is not in the insidergroup, keep the comment public.
+ $long_desc{isprivate} = 0 unless $exporter->is_insider;
+
my $data = $comment->field('thetext');
if ( defined $comment->first_child('thetext')->{'att'}->{'encoding'}
&& $comment->first_child('thetext')->{'att'}->{'encoding'} =~
@@ -528,6 +512,8 @@
$data = decode_base64($data);
}
+ # For backwards-compatibility with Bugzillas before 3.6:
+ #
# If we leave the attachment ID in the comment it will be made a link
# to the wrong attachment. Since the new attachment ID is unknown yet
# let's strip it out for now. We will make a comment with the right ID
@@ -540,42 +526,22 @@
my $url = $urlbase . "show_bug.cgi?id=";
$data =~ s/([Bb]ugs?\s*\#?\s*(\d+))/$url$2/g;
+ # Keep the original commenter if possible, else we will fall back
+ # to the exporter account.
+ $long_desc{whoid} = login_to_id($long_desc{who});
+
+ if (!$long_desc{whoid}) {
+ $data = "The original author of this comment is $long_desc{who}.\n\n" . $data;
+ }
+
$long_desc{'thetext'} = $data;
push @long_descs, \%long_desc;
}
- # instead of giving each comment its own item in the longdescs
- # table like it should have, lets cat them all into one big
- # comment otherwise we would have to lie often about who
- # authored the comment since commenters in one bugzilla probably
- # don't have accounts in the other one.
- # If one of the comments is private the whole comment will be
- # private since we don't want to expose these unnecessarily
- sub by_date { my @a; my @b; $a->{'bug_when'} cmp $b->{'bug_when'}; }
- my @sorted_descs = sort by_date @long_descs;
- my $long_description = "";
- for ( my $z = 0 ; $z <= $#sorted_descs ; $z++ ) {
- if ( $z == 0 ) {
- $long_description .= "\n\n\n---- Reported by ";
- }
- else {
- $long_description .= "\n\n\n---- Additional Comments From ";
- }
- $long_description .= "$sorted_descs[$z]->{'who'} ";
- $long_description .= "$sorted_descs[$z]->{'bug_when'}";
- $long_description .= " ----";
- $long_description .= "\n\n";
- $long_description .= "THIS COMMENT IS PRIVATE \n"
- if ( $sorted_descs[$z]->{'isprivate'} );
- $long_description .= $sorted_descs[$z]->{'thetext'};
- $long_description .= "\n";
- }
+ my @sorted_descs = sort { $a->{'bug_when'} cmp $b->{'bug_when'} } @long_descs;
- my $comments;
-
- $comments .= "\n\n--- Bug imported by $exporter_login ";
- $comments .= time2str( "%Y-%m-%d %H:%M", time ) . " ";
- $comments .= $params->{'timezone'};
+ my $comments = "\n\n--- Bug imported by $exporter_login ";
+ $comments .= format_time(scalar localtime(time()), '%Y-%m-%d %R %Z') . " ";
$comments .= " ---\n\n";
$comments .= "This bug was previously known as _bug_ $bug_fields{'bug_id'} at ";
$comments .= $urlbase . "show_bug.cgi?id=" . $bug_fields{'bug_id'} . "\n";
@@ -623,12 +589,12 @@
# Timestamps
push( @query, "creation_ts" );
push( @values,
- format_time( $bug_fields{'creation_ts'}, "%Y-%m-%d %X" )
+ format_time( $bug_fields{'creation_ts'}, "%Y-%m-%d %T" )
|| $timestamp );
push( @query, "delta_ts" );
push( @values,
- format_time( $bug_fields{'delta_ts'}, "%Y-%m-%d %X" )
+ format_time( $bug_fields{'delta_ts'}, "%Y-%m-%d %T" )
|| $timestamp );
# Bug Access
@@ -638,49 +604,34 @@
push( @query, "reporter_accessible" );
push( @values, $bug_fields{'reporter_accessible'} ? 1 : 0 );
- # Product and Component if there is no valid default product and
- # component defined in the parameters, we wouldn't be here
- my $def_product =
- new Bugzilla::Product( { name => $params->{"moved-default-product"} } );
- my $def_component = new Bugzilla::Component(
- {
- product => $def_product,
- name => $params->{"moved-default-component"}
- }
- );
- my $product;
- my $component;
+ my $product = new Bugzilla::Product(
+ { name => $bug_fields{'product'} || '' });
+ if (!$product) {
+ $err .= "Unknown Product " . $bug_fields{'product'} . "\n";
+ $err .= " Using default product set at the command line.\n";
+ $product = new Bugzilla::Product({ name => $default_product_name })
+ or Error("an invalid default product was defined for the target"
+ . " DB. " . $params->{"maintainer"} . " needs to specify "
+ . "--product when calling importxml.pl", "REOPEN",
+ $exporter);
+ }
+ my $component = new Bugzilla::Component({
+ product => $product, name => $bug_fields{'component'} || '' });
+ if (!$component) {
+ $err .= "Unknown Component " . $bug_fields{'component'} . "\n";
+ $err .= " Using default product and component set ";
+ $err .= "at the command line.\n";
- if ( defined $bug_fields{'product'} ) {
- $product = new Bugzilla::Product( { name => $bug_fields{'product'} } );
- unless ($product) {
- $product = $def_product;
- $err .= "Unknown Product " . $bug_fields{'product'} . "\n";
- $err .= " Using default product set in Parameters \n";
+ $product = new Bugzilla::Product({ name => $default_product_name });
+ $component = new Bugzilla::Component({
+ name => $default_component_name, product => $product });
+ if (!$component) {
+ Error("an invalid default component was defined for the target"
+ . " DB. ". $params->{"maintainer"} . " needs to specify "
+ . "--component when calling importxml.pl", "REOPEN",
+ $exporter);
}
}
- else {
- $product = $def_product;
- }
- if ( defined $bug_fields{'component'} ) {
- $component = new Bugzilla::Component(
- {
- product => $product,
- name => $bug_fields{'component'}
- }
- );
- unless ($component) {
- $component = $def_component;
- $product = $def_product;
- $err .= "Unknown Component " . $bug_fields{'component'} . "\n";
- $err .= " Using default product and component set ";
- $err .= "in Parameters \n";
- }
- }
- else {
- $component = $def_component;
- $product = $def_product;
- }
my $prod_id = $product->id;
my $comp_id = $component->id;
@@ -808,13 +759,12 @@
# Process time fields
if ( $params->{"timetrackinggroup"} ) {
- my $date = format_time( $bug_fields{'deadline'}, "%Y-%m-%d" )
- || undef;
+ my $date = validate_date( $bug_fields{'deadline'} ) ? $bug_fields{'deadline'} : undef;
push( @values, $date );
push( @query, "deadline" );
if ( defined $bug_fields{'estimated_time'} ) {
eval {
- Bugzilla::Bug::ValidateTime($bug_fields{'estimated_time'}, "e");
+ Bugzilla::Object::_validate_time($bug_fields{'estimated_time'}, "e");
};
if (!$@){
push( @values, $bug_fields{'estimated_time'} );
@@ -823,7 +773,7 @@
}
if ( defined $bug_fields{'remaining_time'} ) {
eval {
- Bugzilla::Bug::ValidateTime($bug_fields{'remaining_time'}, "r");
+ Bugzilla::Object::_validate_time($bug_fields{'remaining_time'}, "r");
};
if (!$@){
push( @values, $bug_fields{'remaining_time'} );
@@ -832,7 +782,7 @@
}
if ( defined $bug_fields{'actual_time'} ) {
eval {
- Bugzilla::Bug::ValidateTime($bug_fields{'actual_time'}, "a");
+ Bugzilla::Object::_validate_time($bug_fields{'actual_time'}, "a");
};
if ($@){
$bug_fields{'actual_time'} = 0.0;
@@ -904,8 +854,6 @@
}
# Status & Resolution
- my $has_res = defined($bug_fields{'resolution'});
- my $has_status = defined($bug_fields{'bug_status'});
my $valid_res = check_field('resolution',
scalar $bug_fields{'resolution'},
undef, ERR_LEVEL );
@@ -918,7 +866,7 @@
# Check everconfirmed
my $everconfirmed;
- if ($product->votes_to_confirm) {
+ if ($product->allows_unconfirmed) {
$everconfirmed = $bug_fields{'everconfirmed'} || 0;
}
else {
@@ -932,9 +880,9 @@
# that might not yet be in the database we have no way of populating
# this table. Change the resolution instead.
if ( $valid_res && ( $bug_fields{'resolution'} eq "DUPLICATE" ) ) {
- $resolution = "MOVED";
+ $resolution = "INVALID";
$err .= "This bug was marked DUPLICATE in the database ";
- $err .= "it was moved from.\n Changing resolution to \"MOVED\"\n";
+ $err .= "it was moved from.\n Changing resolution to \"INVALID\"\n";
}
# If there is at least 1 initial bug status different from UNCO, use it,
@@ -947,7 +895,7 @@
$initial_status = $bug_statuses[0]->name;
}
else {
- @bug_statuses = @{Bugzilla::Status->get_all()};
+ @bug_statuses = Bugzilla::Status->get_all();
# Exclude UNCO and inactive bug statuses.
@bug_statuses = grep { $_->is_active && $_->name ne 'UNCONFIRMED'} @bug_statuses;
my @open_statuses = grep { $_->is_open } @bug_statuses;
@@ -960,10 +908,10 @@
}
}
- if($has_status){
+ if ($status) {
if($valid_status){
if($is_open){
- if($has_res){
+ if ($resolution) {
$err .= "Resolution set on an open status.\n";
$err .= " Dropping resolution $resolution\n";
$resolution = undef;
@@ -985,7 +933,6 @@
if($status eq "UNCONFIRMED"){
$err .= "Bug Status was UNCONFIRMED but everconfirmed was true\n";
$err .= " Setting status to $initial_status\n";
- $err .= "Resetting votes to 0\n" if ( $bug_fields{'votes'} );
$status = $initial_status;
}
}
@@ -998,7 +945,7 @@
}
}
else{ # $is_open is false
- if(!$has_res){
+ if (!$resolution) {
$err .= "Missing Resolution. Setting status to ";
if($everconfirmed){
$status = $initial_status;
@@ -1009,10 +956,10 @@
$err .= "UNCONFIRMED\n";
}
}
- if(!$valid_res){
+ elsif (!$valid_res) {
$err .= "Unknown resolution \"$resolution\".\n";
- $err .= " Setting resolution to MOVED\n";
- $resolution = "MOVED";
+ $err .= " Setting resolution to INVALID\n";
+ $resolution = "INVALID";
}
}
}
@@ -1028,9 +975,8 @@
$err .= $bug_fields{'bug_status'} . "\".\n";
$resolution = undef;
}
-
}
- else{ #has_status is false
+ else {
if($everconfirmed){
$status = $initial_status;
}
@@ -1041,8 +987,8 @@
$err .= " Previous status was unknown\n";
$resolution = undef;
}
-
- if (defined $resolution){
+
+ if ($resolution) {
push( @query, "resolution" );
push( @values, $resolution );
}
@@ -1166,15 +1112,6 @@
$keywordseen{$keyword_obj->id} = 1;
}
}
- my ($keywordarray) = $dbh->selectcol_arrayref(
- "SELECT d.name FROM keyworddefs d
- INNER JOIN keywords k
- ON d.id = k.keywordid
- WHERE k.bug_id = ?
- ORDER BY d.name", undef, $id);
- my $keywordstring = join( ", ", @{$keywordarray} );
- $dbh->do( "UPDATE bugs SET keywords = ? WHERE bug_id = ?",
- undef, $keywordstring, $id )
}
# Insert values of custom multi-select fields. They have already
@@ -1205,7 +1142,7 @@
$err .= "No attachment ID specified, dropping attachment\n";
next;
}
- if (!$exporter->in_group($params->{'insidergroup'}) && $att->{'isprivate'}){
+ if (!$exporter->is_insider && $att->{'isprivate'}) {
$err .= "Exporter not in insidergroup and attachment marked private.\n";
$err .= " Marking attachment public\n";
$att->{'isprivate'} = 0;
@@ -1256,19 +1193,21 @@
# Clear the attachments array for the next bug
@attachments = ();
- # Insert longdesc and append any errors
+ # Insert comments and append any errors
my $worktime = $bug_fields{'actual_time'} || 0.0;
- $worktime = 0.0 if (!$exporter->in_group($params->{'timetrackinggroup'}));
- $long_description .= "\n" . $comments;
- if ($err) {
- $long_description .= "\n$err\n";
+ $worktime = 0.0 if (!$exporter->is_timetracker);
+ $comments .= "\n$err\n" if $err;
+
+ my $sth_comment =
+ $dbh->prepare('INSERT INTO longdescs (bug_id, who, bug_when, isprivate,
+ thetext, work_time)
+ VALUES (?, ?, ?, ?, ?, ?)');
+
+ foreach my $c (@sorted_descs) {
+ $sth_comment->execute($id, $c->{whoid} || $exporterid, $c->{bug_when},
+ $c->{isprivate}, $c->{thetext}, 0);
}
- trick_taint($long_description);
- $dbh->do("INSERT INTO longdescs
- (bug_id, who, bug_when, work_time, isprivate, thetext)
- VALUES (?,?,?,?,?,?)", undef,
- $id, $exporterid, $timestamp, $worktime, $private, $long_description
- );
+ $sth_comment->execute($id, $exporterid, $timestamp, 0, $comments, $worktime);
Bugzilla::Bug->new($id)->_sync_fulltext('new_bug');
# Add this bug to each group of which its product is a member.
@@ -1291,7 +1230,7 @@
}
Debug( $log, OK_LEVEL );
push(@logs, $log);
- Bugzilla::BugMail::Send( $id, { 'changer' => $exporter_login } ) if ($mail);
+ Bugzilla::BugMail::Send( $id, { 'changer' => $exporter } ) if ($mail);
# done with the xml data. Lets clear it from memory
$twig->purge;
@@ -1352,31 +1291,38 @@
=head1 SYNOPSIS
- importxml.pl [options] [file ...]
-
- Options:
- -? --help brief help message
- -v --verbose print error and debug information.
- Multiple -v increases verbosity
- -m --sendmail send mail to recipients with log of bugs imported
- --attach_path The path to the attachment files.
- (Required if encoding="filename" is used for attachments.)
+ importxml.pl [options] [file ...]
=head1 OPTIONS
-=over 8
+=over
=item B<-?>
- Print a brief help message and exits.
+Print a brief help message and exit.
=item B<-v>
- Print error and debug information. Mulltiple -v increases verbosity
+Print error and debug information. Mulltiple -v increases verbosity
-=item B<-m>
+=item B<-m> B<--sendmail>
- Send mail to exporter with a log of bugs imported and any errors.
+Send mail to exporter with a log of bugs imported and any errors.
+
+=item B<--attach_path>
+
+The path to the attachment files. (Required if encoding="filename"
+is used for attachments.)
+
+=item B<--product=name>
+
+The product to put the bug in if the product specified in the
+XML doesn't exist.
+
+=item B<--component=name>
+
+The component to put the bug in if the component specified in the
+XML doesn't exist.
=back
diff --git a/Websites/bugs.webkit.org/index.cgi b/Websites/bugs.webkit.org/index.cgi
index 32ed1c7..ee6fa36 100755
--- a/Websites/bugs.webkit.org/index.cgi
+++ b/Websites/bugs.webkit.org/index.cgi
@@ -38,23 +38,24 @@
# Check whether or not the user is logged in
my $user = Bugzilla->login(LOGIN_OPTIONAL);
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+
+# And log out the user if requested. We do this first so that nothing
+# else accidentally relies on the current login.
+if ($cgi->param('logout')) {
+ Bugzilla->logout();
+ $user = Bugzilla->user;
+ $vars->{'message'} = "logged_out";
+ # Make sure that templates or other code doesn't get confused about this.
+ $cgi->delete('logout');
+}
###############################################################################
# Main Body Execution
###############################################################################
-my $cgi = Bugzilla->cgi;
-# Force to use HTTPS unless Bugzilla->params->{'ssl'} equals 'never'.
-# This is required because the user may want to log in from here.
-if ($cgi->protocol ne 'https' && Bugzilla->params->{'sslbase'} ne ''
- && Bugzilla->params->{'ssl'} ne 'never')
-{
- $cgi->require_https(Bugzilla->params->{'sslbase'});
-}
-
-my $template = Bugzilla->template;
-my $vars = {};
-
# Return the appropriate HTTP response headers.
print $cgi->header();
diff --git a/Websites/bugs.webkit.org/install-module.pl b/Websites/bugs.webkit.org/install-module.pl
index 918323e..e532b37 100755
--- a/Websites/bugs.webkit.org/install-module.pl
+++ b/Websites/bugs.webkit.org/install-module.pl
@@ -26,7 +26,7 @@
# CPAN has chdir'ed around. We do all of this in this funny order to
# make sure that we use the lib/ modules instead of the base Perl modules,
# in case the lib/ modules are newer.
-use Cwd qw(abs_path);
+use Cwd qw(abs_path cwd);
use lib abs_path('.');
use Bugzilla::Constants;
use lib abs_path(bz_locations()->{ext_libpath});
@@ -35,21 +35,28 @@
use Bugzilla::Constants;
use Bugzilla::Install::Requirements;
+use Bugzilla::Install::Util qw(bin_loc init_console vers_cmp);
use Data::Dumper;
use Getopt::Long;
use Pod::Usage;
-our %switch;
+init_console();
+my @original_args = @ARGV;
+my $original_dir = cwd();
+our %switch;
GetOptions(\%switch, 'all|a', 'upgrade-all|u', 'show-config|s', 'global|g',
- 'help|h');
+ 'shell', 'help|h');
pod2usage({ -verbose => 1 }) if $switch{'help'};
-if (ON_WINDOWS) {
- print "\nYou cannot run this script on Windows. Please follow instructions\n";
- print "given by checksetup.pl to install missing Perl modules.\n\n";
+if (ON_ACTIVESTATE) {
+ print <<END;
+You cannot run this script when using ActiveState Perl. Please follow
+the instructions given by checksetup.pl to install missing Perl modules.
+
+END
exit;
}
@@ -58,15 +65,15 @@
set_cpan_config($switch{'global'});
if ($switch{'show-config'}) {
- print Dumper($CPAN::Config);
- exit;
+ print Dumper($CPAN::Config);
+ exit;
}
-my $can_notest = 1;
-if (substr(CPAN->VERSION, 0, 3) < 1.8) {
- $can_notest = 0;
- print "* Note: If you upgrade your CPAN module, installs will be faster.\n";
- print "* You can upgrade CPAN by doing: $^X install-module.pl CPAN\n";
+check_cpan_requirements($original_dir, \@original_args);
+
+if ($switch{'shell'}) {
+ CPAN::shell();
+ exit;
}
if ($switch{'all'} || $switch{'upgrade-all'}) {
@@ -93,12 +100,13 @@
# configuration, and really should be installed on its own.
next if $cpan_name eq 'mod_perl2';
next if $cpan_name eq 'DBD::Oracle' and !$ENV{ORACLE_HOME};
- install_module($cpan_name, $can_notest);
+ next if $cpan_name eq 'DBD::Pg' and !bin_loc('pg_config');
+ install_module($cpan_name);
}
}
foreach my $module (@ARGV) {
- install_module($module, $can_notest);
+ install_module($module);
}
__END__
@@ -112,8 +120,9 @@
./install-module.pl Module::Name [--global]
./install-module.pl --all [--global]
- ./install-module.pl --all-upgrade [--global]
+ ./install-module.pl --upgrade-all [--global]
./install-module.pl --show-config
+ ./install-module.pl --shell
Do "./install-module.pl --help" for more information.
@@ -154,6 +163,10 @@
Prints out the CPAN configuration in raw Perl format. Useful for debugging.
+=item B<--shell>
+
+Starts a CPAN shell using the configuration of F<install-module.pl>.
+
=item B<--help>
Shows this help.
diff --git a/Websites/bugs.webkit.org/jobqueue.pl b/Websites/bugs.webkit.org/jobqueue.pl
new file mode 100755
index 0000000..d5080da
--- /dev/null
+++ b/Websites/bugs.webkit.org/jobqueue.pl
@@ -0,0 +1,81 @@
+#!/usr/bin/env perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# Mozilla Corporation. All Rights Reserved.
+#
+# Contributor(s):
+# Mark Smith <mark@mozilla.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use File::Basename;
+BEGIN { chdir dirname($0); }
+
+use lib qw(. lib);
+use Bugzilla;
+use Bugzilla::JobQueue::Runner;
+
+Bugzilla::JobQueue::Runner->new();
+
+=head1 NAME
+
+jobqueue.pl - Runs jobs in the background for Bugzilla.
+
+=head1 SYNOPSIS
+
+ ./jobqueue.pl [OPTIONS] COMMAND
+
+ OPTIONS:
+ -f Run in the foreground (don't detach)
+ -d Output a lot of debugging information
+ -p file Specify the file where jobqueue.pl should store its current
+ process id. Defaults to F<data/jobqueue.pl.pid>.
+ -n name What should this process call itself in the system log?
+ Defaults to the full path you used to invoke the script.
+
+ COMMANDS:
+ start Starts a new jobqueue daemon if there isn't one running already
+ stop Stops a running jobqueue daemon
+ restart Stops a running jobqueue if one is running, and then
+ starts a new one.
+ once Checks the job queue once, executes the first item found (if
+ any) and then exits
+ check Report the current status of the daemon.
+ install On some *nix systems, this automatically installs and
+ configures jobqueue.pl as a system service so that it will
+ start every time the machine boots.
+ uninstall Removes the system service for jobqueue.pl.
+ help Display this usage info
+ version Display the version of jobqueue.pl
+
+=head1 DESCRIPTION
+
+See L<Bugzilla::JobQueue> and L<Bugzilla::JobQueue::Runner>.
+
+=head1 Running jobqueue.pl as a System Service
+
+For systems that use Upstart or SysV Init, there is a SysV/Upstart init
+script included with Bugzilla for jobqueue.pl: F<contrib/bugzilla-queue>.
+It should work out-of-the-box on RHEL, Fedora, CentOS etc.
+
+You can install it by doing C<./jobqueue.pl install> as root, after
+already having run L<checksetup> at least once to completion
+on this Bugzilla installation.
+
+If you are using a system that isn't RHEL, Fedora, CentOS, etc., then you
+may have to modify F<contrib/bugzilla-queue> and install it yourself
+manually in order to get C<jobqueue.pl> running as a system service.
diff --git a/Websites/bugs.webkit.org/js/TUI.js b/Websites/bugs.webkit.org/js/TUI.js
index 6ebc7c7..34a79dc 100644
--- a/Websites/bugs.webkit.org/js/TUI.js
+++ b/Websites/bugs.webkit.org/js/TUI.js
@@ -16,153 +16,94 @@
* Rights Reserved.
*
* Contributor(s): Dennis Melentyev <dennis.melentyev@infopulse.com.ua>
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
*/
- /* This file provides JavaScript functions to be included once one wish
- * to add a hide/reveal/collapse per-class functionality
- *
- *
- * This file contains hide/reveal API for customizable page views
- * TUI stands for Tweak UI.
- *
- * See bug 262592 for usage examples.
- *
- * Note: this interface is experimental and under development.
- * We may and probably will make breaking changes to it in the future.
- */
+/* This file provides JavaScript functions to be included when one wishes
+ * to show/hide certain UI elements, and have the state of them being
+ * shown/hidden stored in a cookie.
+ *
+ * TUI stands for Tweak UI.
+ *
+ * Requires js/util.js and the YUI Dom and Cookie libraries.
+ *
+ * See template/en/default/bug/create/create.html.tmpl for a usage example.
+ */
- var TUIClasses = new Array;
- var TUICookiesEnabled = -1;
+var TUI_HIDDEN_CLASS = 'bz_tui_hidden';
+var TUI_COOKIE_NAME = 'TUI';
- // Internal function to demangle cookies
- function TUI_demangle(value) {
- var pair;
- var pairs = value.split(",");
- for (i = 0; i < pairs.length; i++) {
- pair = pairs[i].split(":");
- if (pair[0] != null && pair[1] != null)
- TUIClasses[pair[0]] = pair[1];
+var TUI_alternates = new Array();
+
+/**
+ * Hides a particular class of elements if they are shown,
+ * or shows them if they are hidden. Then it stores whether that
+ * class is now hidden or shown.
+ *
+ * @param className The name of the CSS class to hide.
+ */
+function TUI_toggle_class(className) {
+ var elements = YAHOO.util.Dom.getElementsByClassName(className);
+ for (var i = 0; i < elements.length; i++) {
+ bz_toggleClass(elements[i], TUI_HIDDEN_CLASS);
}
- }
+ _TUI_save_class_state(elements, className);
+ _TUI_toggle_control_link(className);
+}
- /* TUI_tweak: Function to redraw whole document.
- * Also, initialize TUIClasses array with defaults, then override it
- * with values from cookie
- */
- function TUI_tweak( cookiesuffix, classes ) {
- var dc = document.cookie;
- var begin = -1;
- var end = 0;
-
- // Register classes and their defaults
- TUI_demangle(classes);
-
- if (TUICookiesEnabled > 0) {
- // If cookies enabled, process them
- TUI_demangle(TUI_getCookie(cookiesuffix));
- }
- else if (TUICookiesEnabled == -1) {
- // If cookies availability not checked yet since browser does
- // not has navigator.cookieEnabled property, let's check it manualy
- var cookie = TUI_getCookie(cookiesuffix);
- if (cookie.length == 0)
- {
- TUI_setCookie(cookiesuffix);
- // Cookies are definitely disabled for JS.
- if (TUI_getCookie(cookiesuffix).length == 0)
- TUICookiesEnabled = 0;
- else
- TUICookiesEnabled = 1;
- }
- else {
- // Have cookie set, pretend to be able to reset them later on
- TUI_demangle(cookie);
- TUICookiesEnabled = 1;
- }
- }
-
- if (TUICookiesEnabled > 0) {
- var els = document.getElementsByTagName('*');
- for (i = 0; i < els.length; i++) {
- if (null != TUIClasses[els[i].className]) {
- TUI_apply(els[i], TUIClasses[els[i].className]);
+/**
+ * Specifies that a certain class of items should be hidden by default,
+ * if the user doesn't have a TUI cookie.
+ *
+ * @param className The class to hide by default.
+ */
+function TUI_hide_default(className) {
+ YAHOO.util.Event.onDOMReady(function () {
+ if (!YAHOO.util.Cookie.getSub('TUI', className)) {
+ TUI_toggle_class(className);
}
- }
+ });
+}
+
+function _TUI_toggle_control_link(className) {
+ var link = document.getElementById(className + "_controller");
+ if (!link) return;
+ var original_text = link.innerHTML;
+ link.innerHTML = TUI_alternates[className];
+ TUI_alternates[className] = original_text;
+}
+
+function _TUI_save_class_state(elements, aClass) {
+ // We just check the first element to see if it's hidden or not, and
+ // consider that all elements are the same.
+ if (YAHOO.util.Dom.hasClass(elements[0], TUI_HIDDEN_CLASS)) {
+ _TUI_store(aClass, 0);
}
- return;
- }
-
- // TUI_apply: Function to draw certain element.
- // Receives element itself and style value: hide, reveal or collapse
-
- function TUI_apply(element, value) {
- if (TUICookiesEnabled > 0 && element != null) {
- switch (value)
- {
- case 'hide':
- element.style.visibility="hidden";
- break;
- case 'collapse':
- element.style.visibility="hidden";
- element.style.display="none";
- break;
- case 'reveal': // Shown item must expand
- default: // The default is to show & expand
- element.style.visibility="visible";
- element.style.display="";
- break;
- }
+ else {
+ _TUI_store(aClass, 1);
}
- }
+}
- // TUI_change: Function to process class.
- // Usualy called from onclick event of button
+function _TUI_store(aClass, state) {
+ YAHOO.util.Cookie.setSub(TUI_COOKIE_NAME, aClass, state,
+ {
+ expires: new Date('January 1, 2038'),
+ path: BUGZILLA.param.cookie_path
+ });
+}
- function TUI_change(cookiesuffix, clsname, action) {
- if (TUICookiesEnabled > 0) {
- var els, i;
- els = document.getElementsByTagName('*');
- for (i=0; i<els.length; i++) {
- if (els[i].className.match(clsname)) {
- TUI_apply(els[i], action);
+function _TUI_restore() {
+ var yui_classes = YAHOO.util.Cookie.getSubs(TUI_COOKIE_NAME);
+ for (yui_item in yui_classes) {
+ if (yui_classes[yui_item] == 0) {
+ var elements = YAHOO.util.Dom.getElementsByClassName(yui_item);
+ for (var i = 0; i < elements.length; i++) {
+ YAHOO.util.Dom.addClass(elements[i], 'bz_tui_hidden');
+ }
+ _TUI_toggle_control_link(yui_item);
}
- }
- TUIClasses[clsname]=action;
- TUI_setCookie(cookiesuffix);
}
- }
-
- // TUI_setCookie: Function to set TUI cookie.
- // Used internally
+}
- function TUI_setCookie(cookiesuffix) {
- var cookieval = "";
- var expireOn = new Date();
- expireOn.setYear(expireOn.getFullYear() + 25);
- for (clsname in TUIClasses) {
- if (cookieval.length > 0)
- cookieval += ",";
- cookieval += clsname+":"+TUIClasses[clsname];
- }
- document.cookie="Bugzilla_TUI_"+cookiesuffix+"="+cookieval+"; expires="+expireOn.toString();
- }
-
- // TUI_getCookie: Function to get TUI cookie.
- // Used internally
-
- function TUI_getCookie(cookiesuffix) {
- var dc = document.cookie;
- var begin, end;
- var cookiePrefix = "Bugzilla_TUI_"+cookiesuffix+"=";
- begin = dc.indexOf(cookiePrefix, end);
- if (begin != -1) {
- begin += cookiePrefix.length;
- end = dc.indexOf(";", begin);
- if (end == -1) {
- end = dc.length;
- }
- return unescape(dc.substring(begin, end));
- }
- return "";
- }
+YAHOO.util.Event.onDOMReady(_TUI_restore);
diff --git a/Websites/bugs.webkit.org/js/attachment.js b/Websites/bugs.webkit.org/js/attachment.js
index 90480d4..240ac3b 100644
--- a/Websites/bugs.webkit.org/js/attachment.js
+++ b/Websites/bugs.webkit.org/js/attachment.js
@@ -19,8 +19,18 @@
* Joel Peshkin <bugreport@peshkin.net>
* Erik Stambaugh <erik@dasbistro.com>
* Marc Schumann <wurblzap@gmail.com>
+ * Guy Pyrzak <guy.pyrzak@gmail.com>
*/
+function validateAttachmentForm(theform) {
+ var desc_value = YAHOO.lang.trim(theform.description.value);
+ if (desc_value == '') {
+ alert(BUGZILLA.string.attach_desc_required);
+ return false;
+ }
+ return true;
+}
+
function updateCommentPrivacy(checkbox) {
var text_elem = document.getElementById('comment');
if (checkbox.checked) {
@@ -53,14 +63,13 @@
// endif WEBKIT_CHANGES
}
-function URLFieldHandler() {
- var field_attachurl = document.getElementById("attachurl");
+function TextFieldHandler() {
+ var field_text = document.getElementById("attach_text");
var greyfields = new Array("data", "ispatch", "autodetect",
- "list", "manual", "bigfile",
- "contenttypeselection",
+ "list", "manual", "contenttypeselection",
"contenttypeentry");
var i, thisfield;
- if (field_attachurl.value.match(/^\s*$/)) {
+ if (field_text.value.match(/^\s*$/)) {
for (i = 0; i < greyfields.length; i++) {
thisfield = document.getElementById(greyfields[i]);
if (thisfield) {
@@ -79,7 +88,7 @@
function DataFieldHandler() {
var field_data = document.getElementById("data");
- var greyfields = new Array("attachurl");
+ var greyfields = new Array("attach_text");
var i, thisfield;
if (field_data.value.match(/^\s*$/)) {
for (i = 0; i < greyfields.length; i++) {
@@ -103,14 +112,284 @@
document.getElementById('data').value = '';
DataFieldHandler();
- if ((element = document.getElementById('bigfile')))
- element.checked = '';
- if ((element = document.getElementById('attachurl'))) {
+ if ((element = document.getElementById('attach_text'))) {
element.value = '';
- URLFieldHandler();
+ TextFieldHandler();
}
document.getElementById('description').value = '';
- document.getElementById('ispatch').checked = '';
+ /* Fire onchange so that the disabled state of the content-type
+ * radio buttons are also reset
+ */
+ element = document.getElementById('ispatch');
+ element.checked = '';
+ bz_fireEvent(element, 'change');
if ((element = document.getElementById('isprivate')))
element.checked = '';
}
+
+/* Functions used when viewing patches in Diff mode. */
+
+function collapse_all() {
+ var elem = document.checkboxform.firstChild;
+ while (elem != null) {
+ if (elem.firstChild != null) {
+ var tbody = elem.firstChild.nextSibling;
+ if (tbody.className == 'file') {
+ tbody.className = 'file_collapse';
+ twisty = get_twisty_from_tbody(tbody);
+ twisty.firstChild.nodeValue = '(+)';
+ twisty.nextSibling.checked = false;
+ }
+ }
+ elem = elem.nextSibling;
+ }
+ return false;
+}
+
+function expand_all() {
+ var elem = document.checkboxform.firstChild;
+ while (elem != null) {
+ if (elem.firstChild != null) {
+ var tbody = elem.firstChild.nextSibling;
+ if (tbody.className == 'file_collapse') {
+ tbody.className = 'file';
+ twisty = get_twisty_from_tbody(tbody);
+ twisty.firstChild.nodeValue = '(-)';
+ twisty.nextSibling.checked = true;
+ }
+ }
+ elem = elem.nextSibling;
+ }
+ return false;
+}
+
+var current_restore_elem;
+
+function restore_all() {
+ current_restore_elem = null;
+ incremental_restore();
+}
+
+function incremental_restore() {
+ if (!document.checkboxform.restore_indicator.checked) {
+ return;
+ }
+ var next_restore_elem;
+ if (current_restore_elem) {
+ next_restore_elem = current_restore_elem.nextSibling;
+ } else {
+ next_restore_elem = document.checkboxform.firstChild;
+ }
+ while (next_restore_elem != null) {
+ current_restore_elem = next_restore_elem;
+ if (current_restore_elem.firstChild != null) {
+ restore_elem(current_restore_elem.firstChild.nextSibling);
+ }
+ next_restore_elem = current_restore_elem.nextSibling;
+ }
+}
+
+function restore_elem(elem, alertme) {
+ if (elem.className == 'file_collapse') {
+ twisty = get_twisty_from_tbody(elem);
+ if (twisty.nextSibling.checked) {
+ elem.className = 'file';
+ twisty.firstChild.nodeValue = '(-)';
+ }
+ } else if (elem.className == 'file') {
+ twisty = get_twisty_from_tbody(elem);
+ if (!twisty.nextSibling.checked) {
+ elem.className = 'file_collapse';
+ twisty.firstChild.nodeValue = '(+)';
+ }
+ }
+}
+
+function twisty_click(twisty) {
+ tbody = get_tbody_from_twisty(twisty);
+ if (tbody.className == 'file') {
+ tbody.className = 'file_collapse';
+ twisty.firstChild.nodeValue = '(+)';
+ twisty.nextSibling.checked = false;
+ } else {
+ tbody.className = 'file';
+ twisty.firstChild.nodeValue = '(-)';
+ twisty.nextSibling.checked = true;
+ }
+ return false;
+}
+
+function get_tbody_from_twisty(twisty) {
+ return twisty.parentNode.parentNode.parentNode.nextSibling;
+}
+function get_twisty_from_tbody(tbody) {
+ return tbody.previousSibling.firstChild.nextSibling.firstChild.firstChild;
+}
+
+var prev_mode = 'raw';
+var current_mode = 'raw';
+var has_edited = 0;
+var has_viewed_as_diff = 0;
+// if WEBKIT_CHANGES
+var viewing_formatted_diff = false;
+// endif WEBKIT_CHANGES
+function editAsComment(patchviewerinstalled)
+{
+ switchToMode('edit', patchviewerinstalled);
+ has_edited = 1;
+}
+function undoEditAsComment(patchviewerinstalled)
+{
+ switchToMode(prev_mode, patchviewerinstalled);
+}
+function redoEditAsComment(patchviewerinstalled)
+{
+ switchToMode('edit', patchviewerinstalled);
+}
+
+// if WEBKIT_CHANGES
+function viewPrettyPatch(attachment_id)
+{
+ viewing_formatted_diff = !viewing_formatted_diff;
+ var src = "attachment.cgi?id=" . $attachment_id;
+ var buttonText = "View Formatted Diff";
+ if (viewing_formatted_diff) {
+ src += "&action=prettypatch"
+ buttonText = "View Plain Diff";
+ }
+
+ document.getElementById('viewFrame').src = src;
+ document.getElementById('viewPrettyPatchButton').innerHTML = buttonText;
+}
+// endif WEBKIT_CHANGES
+
+function viewDiff(attachment_id, patchviewerinstalled)
+{
+ switchToMode('diff', patchviewerinstalled);
+
+ // If we have not viewed as diff before, set the view diff frame URL
+ if (!has_viewed_as_diff) {
+ var viewDiffFrame = document.getElementById('viewDiffFrame');
+ viewDiffFrame.src =
+ 'attachment.cgi?id=' + attachment_id + '&action=diff&headers=0';
+ has_viewed_as_diff = 1;
+ }
+}
+
+function viewRaw(patchviewerinstalled)
+{
+ switchToMode('raw', patchviewerinstalled);
+}
+
+function switchToMode(mode, patchviewerinstalled)
+{
+ if (mode == current_mode) {
+ alert('switched to same mode! This should not happen.');
+ return;
+ }
+
+ // Switch out of current mode
+ if (current_mode == 'edit') {
+ hideElementById('editFrame');
+ hideElementById('undoEditButton');
+ } else if (current_mode == 'raw') {
+ hideElementById('viewFrame');
+// if WEBKIT_CHANGES
+ hideElementById('viewPrettyPatchButton');
+// endif WEBKIT_CHANGES
+ if (patchviewerinstalled)
+ hideElementById('viewDiffButton');
+ hideElementById(has_edited ? 'redoEditButton' : 'editButton');
+ hideElementById('smallCommentFrame');
+ } else if (current_mode == 'diff') {
+ if (patchviewerinstalled)
+ hideElementById('viewDiffFrame');
+ hideElementById('viewRawButton');
+ hideElementById(has_edited ? 'redoEditButton' : 'editButton');
+ hideElementById('smallCommentFrame');
+ }
+
+ // Switch into new mode
+ if (mode == 'edit') {
+ showElementById('editFrame');
+ showElementById('undoEditButton');
+ } else if (mode == 'raw') {
+ showElementById('viewFrame');
+// if WEBKIT_CHANGES
+ showElementById('viewPrettyPatchButton');
+// endif WEBKIT_CHANGES
+ if (patchviewerinstalled)
+ showElementById('viewDiffButton');
+
+ showElementById(has_edited ? 'redoEditButton' : 'editButton');
+ showElementById('smallCommentFrame');
+ } else if (mode == 'diff') {
+ if (patchviewerinstalled)
+ showElementById('viewDiffFrame');
+
+ showElementById('viewRawButton');
+ showElementById(has_edited ? 'redoEditButton' : 'editButton');
+ showElementById('smallCommentFrame');
+ }
+
+ prev_mode = current_mode;
+ current_mode = mode;
+}
+
+function hideElementById(id)
+{
+ var elm = document.getElementById(id);
+ if (elm) {
+ YAHOO.util.Dom.addClass(elm, 'bz_default_hidden');
+ }
+}
+
+function showElementById(id)
+{
+ var elm = document.getElementById(id);
+ if (elm) {
+ YAHOO.util.Dom.removeClass(elm, 'bz_default_hidden');
+ }
+}
+
+function normalizeComments()
+{
+ // Remove the unused comment field from the document so its contents
+ // do not get transmitted back to the server.
+
+ var small = document.getElementById('smallCommentFrame');
+ var big = document.getElementById('editFrame');
+ if ( (small) && YAHOO.util.Dom.hasClass(small, 'bz_default_hidden') )
+ {
+ small.parentNode.removeChild(small);
+ }
+ if ( (big) && YAHOO.util.Dom.hasClass(big, 'bz_default_hidden') )
+ {
+ big.parentNode.removeChild(big);
+ }
+}
+
+function toggle_attachment_details_visibility ( )
+{
+ // show hide classes
+ var container = document.getElementById('attachment_info');
+ if( YAHOO.util.Dom.hasClass(container, 'read') ){
+ YAHOO.util.Dom.replaceClass(container, 'read', 'edit');
+ }else{
+ YAHOO.util.Dom.replaceClass(container, 'edit', 'read');
+ }
+}
+
+/* Used in bug/create.html.tmpl to show/hide the attachment field. */
+
+function handleWantsAttachment(wants_attachment) {
+ if (wants_attachment) {
+ hideElementById('attachment_false');
+ showElementById('attachment_true');
+ }
+ else {
+ showElementById('attachment_false');
+ hideElementById('attachment_true');
+ clearAttachmentFields();
+ }
+}
diff --git a/Websites/bugs.webkit.org/js/bug.js b/Websites/bugs.webkit.org/js/bug.js
new file mode 100644
index 0000000..06ef03d
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/bug.js
@@ -0,0 +1,131 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Everything Solved, Inc.
+ * Portions created by Everything Solved are Copyright (C) 2010 Everything
+ * Solved, Inc. All Rights Reserved.
+ *
+ * Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+/* This library assumes that the needed YUI libraries have been loaded
+ already. */
+
+YAHOO.bugzilla.dupTable = {
+ counter: 0,
+ dataSource: null,
+ updateTable: function(dataTable, product_name, summary_field) {
+ if (summary_field.value.length < 4) return;
+
+ YAHOO.bugzilla.dupTable.counter = YAHOO.bugzilla.dupTable.counter + 1;
+ YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
+ var json_object = {
+ version : "1.1",
+ method : "Bug.possible_duplicates",
+ id : YAHOO.bugzilla.dupTable.counter,
+ params : {
+ product : product_name,
+ summary : summary_field.value,
+ limit : 7,
+ include_fields : [ "id", "summary", "status", "resolution",
+ "update_token" ]
+ }
+ };
+ var post_data = YAHOO.lang.JSON.stringify(json_object);
+
+ var callback = {
+ success: dataTable.onDataReturnInitializeTable,
+ failure: dataTable.onDataReturnInitializeTable,
+ scope: dataTable,
+ argument: dataTable.getState()
+ };
+ dataTable.showTableMessage(dataTable.get("MSG_LOADING"),
+ YAHOO.widget.DataTable.CLASS_LOADING);
+ YAHOO.util.Dom.removeClass('possible_duplicates_container',
+ 'bz_default_hidden');
+ dataTable.getDataSource().sendRequest(post_data, callback);
+ },
+ // This is the keyup event handler. It calls updateTable with a relatively
+ // long delay, to allow additional input. However, the delay is short
+ // enough that nobody could get from the summary field to the Submit
+ // Bug button before the table is shown (which is important, because
+ // the showing of the table causes the Submit Bug button to move, and
+ // if the table shows at the exact same time as the button is clicked,
+ // the click on the button won't register.)
+ doUpdateTable: function(e, args) {
+ var dt = args[0];
+ var product_name = args[1];
+ var summary = YAHOO.util.Event.getTarget(e);
+ clearTimeout(YAHOO.bugzilla.dupTable.lastTimeout);
+ YAHOO.bugzilla.dupTable.lastTimeout = setTimeout(function() {
+ YAHOO.bugzilla.dupTable.updateTable(dt, product_name, summary) },
+ 600);
+ },
+ formatBugLink: function(el, oRecord, oColumn, oData) {
+ el.innerHTML = '<a href="show_bug.cgi?id=' + oData + '">'
+ + oData + '</a>';
+ },
+ formatStatus: function(el, oRecord, oColumn, oData) {
+ var resolution = oRecord.getData('resolution');
+ var bug_status = display_value('bug_status', oData);
+ if (resolution) {
+ el.innerHTML = bug_status + ' '
+ + display_value('resolution', resolution);
+ }
+ else {
+ el.innerHTML = bug_status;
+ }
+ },
+ formatCcButton: function(el, oRecord, oColumn, oData) {
+ var url = 'process_bug.cgi?id=' + oRecord.getData('id')
+ + '&addselfcc=1&token=' + escape(oData);
+ var button = document.createElement('a');
+ button.setAttribute('href', url);
+ button.innerHTML = YAHOO.bugzilla.dupTable.addCcMessage;
+ el.appendChild(button);
+ new YAHOO.widget.Button(button);
+ },
+ init_ds: function() {
+ var new_ds = new YAHOO.util.XHRDataSource("jsonrpc.cgi");
+ new_ds.connTimeout = 30000;
+ new_ds.connMethodPost = true;
+ new_ds.connXhrMode = "cancelStaleRequests";
+ new_ds.maxCacheEntries = 3;
+ new_ds.responseSchema = {
+ resultsList : "result.bugs",
+ metaFields : { error: "error", jsonRpcId: "id" }
+ };
+ // DataSource can't understand a JSON-RPC error response, so
+ // we have to modify the result data if we get one.
+ new_ds.doBeforeParseData =
+ function(oRequest, oFullResponse, oCallback) {
+ if (oFullResponse.error) {
+ oFullResponse.result = {};
+ oFullResponse.result.bugs = [];
+ if (console) {
+ console.log("JSON-RPC error:", oFullResponse.error);
+ }
+ }
+ return oFullResponse;
+ }
+
+ this.dataSource = new_ds;
+ },
+ init: function(data) {
+ if (this.dataSource == null) this.init_ds();
+ data.options.initialLoad = false;
+ var dt = new YAHOO.widget.DataTable(data.container, data.columns,
+ this.dataSource, data.options);
+ YAHOO.util.Event.on(data.summary_field, 'keyup', this.doUpdateTable,
+ [dt, data.product_name]);
+ }
+};
diff --git a/Websites/bugs.webkit.org/js/change-columns.js b/Websites/bugs.webkit.org/js/change-columns.js
new file mode 100644
index 0000000..241f0bb
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/change-columns.js
@@ -0,0 +1,142 @@
+/*# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Pascal Held.
+ #
+ # Contributor(s): Pascal Held <paheld@gmail.com>
+ #
+*/
+
+function initChangeColumns() {
+ window.onunload = unload;
+ var av_select = document.getElementById("available_columns");
+ var sel_select = document.getElementById("selected_columns");
+ YAHOO.util.Dom.removeClass(
+ ['avail_header', av_select, 'select_button',
+ 'deselect_button', 'up_button', 'down_button'], 'bz_default_hidden');
+ switch_options(sel_select, av_select, false);
+ sel_select.selectedIndex = -1;
+ updateView();
+}
+
+function switch_options(from_box, to_box, selected) {
+ for (var i = 0; i<from_box.options.length; i++) {
+ var opt = from_box.options[i];
+ if (opt.selected == selected) {
+ var newopt = new Option(opt.text, opt.value, opt.defaultselected, opt.selected);
+ to_box.options[to_box.options.length] = newopt;
+ from_box.options[i] = null;
+ i = i - 1;
+ }
+
+ }
+}
+
+function move_select() {
+ var av_select = document.getElementById("available_columns");
+ var sel_select = document.getElementById("selected_columns");
+ switch_options(av_select, sel_select, true);
+ updateView();
+}
+
+function move_deselect() {
+ var av_select = document.getElementById("available_columns");
+ var sel_select = document.getElementById("selected_columns");
+ switch_options(sel_select, av_select, true);
+ updateView();
+}
+
+function move_up() {
+ var sel_select = document.getElementById("selected_columns");
+ var last = sel_select.options[0];
+ var dummy = new Option("", "", false, false);
+ for (var i = 1; i<sel_select.options.length; i++) {
+ var opt = sel_select.options[i];
+ if (opt.selected) {
+ sel_select.options[i] = dummy;
+ sel_select.options[i-1] = opt;
+ sel_select.options[i] = last;
+ }
+ else{
+ last = opt;
+ }
+ }
+ updateView();
+}
+
+function move_down() {
+ var sel_select = document.getElementById("selected_columns");
+ var last = sel_select.options[sel_select.options.length-1];
+ var dummy = new Option("", "", false, false);
+ for (var i = sel_select.options.length-2; i >= 0; i--) {
+ var opt = sel_select.options[i];
+ if (opt.selected) {
+ sel_select.options[i] = dummy;
+ sel_select.options[i + 1] = opt;
+ sel_select.options[i] = last;
+ }
+ else{
+ last = opt;
+ }
+ }
+ updateView();
+}
+
+function updateView() {
+ var select_button = document.getElementById("select_button");
+ var deselect_button = document.getElementById("deselect_button");
+ var up_button = document.getElementById("up_button");
+ var down_button = document.getElementById("down_button");
+ select_button.disabled = true;
+ deselect_button.disabled = true;
+ up_button.disabled = true;
+ down_button.disabled = true;
+ var av_select = document.getElementById("available_columns");
+ var sel_select = document.getElementById("selected_columns");
+ for (var i = 0; i < av_select.options.length; i++) {
+ if (av_select.options[i].selected) {
+ select_button.disabled = false;
+ break;
+ }
+ }
+ for (var i = 0; i < sel_select.options.length; i++) {
+ if (sel_select.options[i].selected) {
+ deselect_button.disabled = false;
+ up_button.disabled = false;
+ down_button.disabled = false;
+ break;
+ }
+ }
+ if (sel_select.options.length > 0) {
+ if (sel_select.options[0].selected) {
+ up_button.disabled = true;
+ }
+ if (sel_select.options[sel_select.options.length - 1].selected) {
+ down_button.disabled = true;
+ }
+ }
+}
+
+function change_submit() {
+ var sel_select = document.getElementById("selected_columns");
+ for (var i = 0; i < sel_select.options.length; i++) {
+ sel_select.options[i].selected = true;
+ }
+ return false;
+}
+
+function unload() {
+ var sel_select = document.getElementById("selected_columns");
+ for (var i = 0; i < sel_select.options.length; i++) {
+ sel_select.options[i].selected = true;
+ }
+}
diff --git a/Websites/bugs.webkit.org/js/comments.js b/Websites/bugs.webkit.org/js/comments.js
new file mode 100644
index 0000000..e7163a0
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/comments.js
@@ -0,0 +1,145 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation. Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
+ * Edmund Wong <ewong@pw-wspx.org>
+ * Anthony Pipkin <a.pipkin@yahoo.com>
+ */
+
+function updateCommentPrivacy(checkbox, id) {
+ var comment_elem = document.getElementById('comment_text_'+id).parentNode;
+ if (checkbox.checked) {
+ if (!comment_elem.className.match('bz_private')) {
+ comment_elem.className = comment_elem.className.concat(' bz_private');
+ }
+ }
+ else {
+ comment_elem.className =
+ comment_elem.className.replace(/(\s*|^)bz_private(\s*|$)/, '$2');
+ }
+}
+
+/* The functions below expand and collapse comments */
+
+function toggle_comment_display(link, comment_id) {
+ var comment = document.getElementById('comment_text_' + comment_id);
+ var re = new RegExp(/\bcollapsed\b/);
+ if (comment.className.match(re))
+ expand_comment(link, comment);
+ else
+ collapse_comment(link, comment);
+}
+
+function toggle_all_comments(action) {
+ // If for some given ID the comment doesn't exist, this doesn't mean
+ // there are no more comments, but that the comment is private and
+ // the user is not allowed to view it.
+
+ var comments = YAHOO.util.Dom.getElementsByClassName('bz_comment_text');
+ for (var i = 0; i < comments.length; i++) {
+ var comment = comments[i];
+ if (!comment)
+ continue;
+
+ var id = comments[i].id.match(/\d*$/);
+ var link = document.getElementById('comment_link_' + id);
+ if (action == 'collapse')
+ collapse_comment(link, comment);
+ else
+ expand_comment(link, comment);
+ }
+}
+
+function collapse_comment(link, comment) {
+ link.innerHTML = "[+]";
+ YAHOO.util.Dom.addClass(comment, 'collapsed');
+}
+
+function expand_comment(link, comment) {
+ link.innerHTML = "[-]";
+ YAHOO.util.Dom.removeClass(comment, 'collapsed');
+}
+
+function wrapReplyText(text) {
+ // This is -3 to account for "\n> "
+ var maxCol = BUGZILLA.constant.COMMENT_COLS - 3;
+ var text_lines = text.replace(/[\s\n]+$/, '').split("\n");
+ var wrapped_lines = new Array();
+
+ for (var i = 0; i < text_lines.length; i++) {
+ var paragraph = text_lines[i];
+ // Don't wrap already-quoted text.
+ if (paragraph.indexOf('>') == 0) {
+ wrapped_lines.push('> ' + paragraph);
+ continue;
+ }
+
+ var replace_lines = new Array();
+ while (paragraph.length > maxCol) {
+ var testLine = paragraph.substring(0, maxCol);
+ var pos = testLine.search(/\s\S*$/);
+
+ if (pos < 1) {
+ // Try to find some ASCII punctuation that's reasonable
+ // to break on.
+ var punct = '\\-\\./,!;:';
+ var punctRe = new RegExp('[' + punct + '][^' + punct + ']+$');
+ pos = testLine.search(punctRe) + 1;
+ // Try to find some CJK Punctuation that's reasonable
+ // to break on.
+ if (pos == 0)
+ pos = testLine.search(/[\u3000\u3001\u3002\u303E\u303F]/) + 1;
+ // If we can't find any break point, we simply break long
+ // words. This makes long, punctuation-less CJK text wrap,
+ // even if it wraps incorrectly.
+ if (pos == 0) pos = maxCol;
+ }
+
+ var wrapped_line = paragraph.substring(0, pos);
+ replace_lines.push(wrapped_line);
+ paragraph = paragraph.substring(pos);
+ // Strip whitespace from the start of the line
+ paragraph = paragraph.replace(/^\s+/, '');
+ }
+ replace_lines.push(paragraph);
+ wrapped_lines.push("> " + replace_lines.join("\n> "));
+ }
+ return wrapped_lines.join("\n") + "\n\n";
+}
+
+/* This way, we are sure that browsers which do not support JS
+ * won't display this link */
+
+function addCollapseLink(count, title) {
+ document.write(' <a href="#" class="bz_collapse_comment"' +
+ ' id="comment_link_' + count +
+ '" onclick="toggle_comment_display(this, ' + count +
+ '); return false;" title="' + title + '">[-]<\/a> ');
+}
+
+function goto_add_comments( anchor ){
+ anchor = (anchor || "add_comment");
+ // we need this line to expand the comment box
+ document.getElementById('comment').focus();
+ setTimeout(function(){
+ document.location.hash = anchor;
+ // firefox doesn't seem to keep focus through the anchor change
+ document.getElementById('comment').focus();
+ },10);
+ return false;
+}
diff --git a/Websites/bugs.webkit.org/js/custom-search.js b/Websites/bugs.webkit.org/js/custom-search.js
new file mode 100644
index 0000000..0ee7d24
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/custom-search.js
@@ -0,0 +1,208 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is BugzillaSource, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+var PAREN_INDENT_EM = 2;
+var ANY_ALL_SELECT_CLASS = 'any_all_select';
+
+// When somebody chooses to "Hide Advanced Features" for Custom Search,
+// we don't want to hide "Not" checkboxes if they've been checked. We
+// accomplish this by removing the custom_search_advanced class from Not
+// checkboxes when they are checked.
+//
+// We never add the custom_search_advanced class back. If we did, TUI
+// would get confused in this case: Check Not, Hide Advanced Features,
+// Uncheck Not, Show Advanced Features. (It hides "Not" when it shouldn't.)
+function custom_search_not_changed(id) {
+ var container = document.getElementById('custom_search_not_container_' + id);
+ YAHOO.util.Dom.removeClass(container, 'custom_search_advanced');
+
+ fix_query_string(container);
+}
+
+function custom_search_new_row() {
+ var row = document.getElementById('custom_search_last_row');
+ var clone = row.cloneNode(true);
+
+ _cs_fix_ids(clone);
+
+ // We only want one copy of the buttons, in the new row. So the old
+ // ones get deleted.
+ var op_button = document.getElementById('op_button');
+ row.removeChild(op_button);
+ var cp_button = document.getElementById('cp_container');
+ row.removeChild(cp_button);
+ var add_button = document.getElementById('add_button');
+ row.removeChild(add_button);
+ _remove_any_all(clone);
+
+ // Always make sure there's only one row with this id.
+ row.id = null;
+ row.parentNode.appendChild(clone);
+ fix_query_string(row);
+ return clone;
+}
+
+function custom_search_open_paren() {
+ var row = document.getElementById('custom_search_last_row');
+
+ // If there's an "Any/All" select in this row, it needs to stay as
+ // part of the parent paren set.
+ var old_any_all = _remove_any_all(row);
+ if (old_any_all) {
+ var any_all_row = row.cloneNode(false);
+ any_all_row.id = null;
+ any_all_row.appendChild(old_any_all);
+ row.parentNode.insertBefore(any_all_row, row);
+ }
+
+ // We also need a "Not" checkbox to stay in the parent paren set.
+ var new_not = YAHOO.util.Dom.getElementsByClassName(
+ 'custom_search_not_container', null, row);
+ var not_for_paren = new_not[0].cloneNode(true);
+
+ // Preserve the values when modifying the row.
+ var id = _cs_fix_ids(row, true);
+ var prev_id = id - 1;
+
+ var paren_row = row.cloneNode(false);
+ paren_row.id = null;
+ paren_row.innerHTML = '(<input type="hidden" name="f' + prev_id
+ + '" value="OP">';
+ paren_row.insertBefore(not_for_paren, paren_row.firstChild);
+ row.parentNode.insertBefore(paren_row, row);
+
+ // New paren set needs a new "Any/All" select.
+ var any_all_container = document.createElement('div');
+ YAHOO.util.Dom.addClass(any_all_container, ANY_ALL_SELECT_CLASS);
+ var j_top = document.getElementById('j_top');
+ var any_all = j_top.cloneNode(true);
+ any_all.name = 'j' + prev_id;
+ any_all.id = any_all.name;
+ any_all_container.appendChild(any_all);
+ row.insertBefore(any_all_container, row.firstChild);
+
+ var margin = YAHOO.util.Dom.getStyle(row, 'margin-left');
+ var int_match = margin.match(/\d+/);
+ var new_margin = parseInt(int_match[0]) + PAREN_INDENT_EM;
+ YAHOO.util.Dom.setStyle(row, 'margin-left', new_margin + 'em');
+ YAHOO.util.Dom.removeClass('cp_container', 'bz_default_hidden');
+
+ fix_query_string(any_all_container);
+}
+
+function custom_search_close_paren() {
+ var new_row = custom_search_new_row();
+
+ // We need to up the new row's id by one more, because we're going
+ // to insert a "CP" before it.
+ var id = _cs_fix_ids(new_row);
+
+ var margin = YAHOO.util.Dom.getStyle(new_row, 'margin-left');
+ var int_match = margin.match(/\d+/);
+ var new_margin = parseInt(int_match[0]) - PAREN_INDENT_EM;
+ YAHOO.util.Dom.setStyle(new_row, 'margin-left', new_margin + 'em');
+
+ var paren_row = new_row.cloneNode(false);
+ paren_row.id = null;
+ paren_row.innerHTML = ')<input type="hidden" name="f' + (id - 1)
+ + '" value="CP">';
+
+ new_row.parentNode.insertBefore(paren_row, new_row);
+
+ if (new_margin == 0) {
+ YAHOO.util.Dom.addClass('cp_container', 'bz_default_hidden');
+ }
+
+ fix_query_string(new_row);
+}
+
+// When a user goes Back in their browser after searching, some browsers
+// (Chrome, as of September 2011) do not remember the DOM that was created
+// by the Custom Search JS. (In other words, their whole entered Custom
+// Search disappears.) The solution is to update the History object,
+// using the query string, which query.cgi can read to re-create the page
+// exactly as the user had it before.
+function fix_query_string(form_member) {
+ // window.History comes from history.js.
+ // It falls back to the normal window.history object for HTML5 browsers.
+ if (!(window.History && window.History.replaceState))
+ return;
+
+ var form = YAHOO.util.Dom.getAncestorByTagName(form_member, 'form');
+ var query = YAHOO.util.Connect.setForm(form);
+ window.History.replaceState(null, document.title, '?' + query);
+}
+
+// Browsers that do not support HTML5's history.replaceState gain an emulated
+// version via history.js, with the full query_string in the location's hash.
+// We rewrite the URL to include the hash and redirect to the new location,
+// which causes custom search fields to work.
+function redirect_html4_browsers() {
+ var hash = document.location.hash;
+ if (hash == '') return;
+ hash = hash.substring(1);
+ if (hash.substring(0, 10) != 'query.cgi?') return;
+ var url = document.location + "";
+ url = url.substring(0, url.indexOf('query.cgi#')) + hash;
+ document.location = url;
+}
+
+function _cs_fix_ids(parent, preserve_values) {
+ // Update the label of the checkbox.
+ var label = YAHOO.util.Dom.getElementBy(function() { return true },
+ 'label', parent);
+ var id_match = label.htmlFor.match(/\d+$/);
+ var id = parseInt(id_match[0]) + 1;
+ label.htmlFor = label.htmlFor.replace(/\d+$/, id);
+
+ // Sets all the inputs in the parent back to their default
+ // and fixes their id.
+ var fields =
+ YAHOO.util.Dom.getElementsByClassName('custom_search_form_field', null,
+ parent);
+ for (var i = 0; i < fields.length; i++) {
+ var field = fields[i];
+
+ if (!preserve_values) {
+ if (field.type == "checkbox") {
+ field.checked = false;
+ }
+ else {
+ field.value = '';
+ }
+ }
+
+ // Update the numeric id for the new row.
+ field.name = field.name.replace(/\d+$/, id);
+ field.id = field.name;
+ }
+
+ return id;
+}
+
+function _remove_any_all(parent) {
+ var any_all = YAHOO.util.Dom.getElementsByClassName(
+ ANY_ALL_SELECT_CLASS, null, parent);
+ if (any_all[0]) {
+ parent.removeChild(any_all[0]);
+ return any_all[0];
+ }
+ return null;
+}
diff --git a/Websites/bugs.webkit.org/js/field.js b/Websites/bugs.webkit.org/js/field.js
index a7890e6..744f193 100644
--- a/Websites/bugs.webkit.org/js/field.js
+++ b/Websites/bugs.webkit.org/js/field.js
@@ -16,11 +16,86 @@
*
* Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
* Guy Pyrzak <guy.pyrzak@gmail.com>
+ * Reed Loden <reed@reedloden.com>
*/
/* This library assumes that the needed YUI libraries have been loaded
already. */
+var bz_no_validate_enter_bug = false;
+function validateEnterBug(theform) {
+ // This is for the "bookmarkable templates" button.
+ if (bz_no_validate_enter_bug) {
+ // Set it back to false for people who hit the "back" button
+ bz_no_validate_enter_bug = false;
+ return true;
+ }
+
+ var component = theform.component;
+ var short_desc = theform.short_desc;
+ var version = theform.version;
+ var bug_status = theform.bug_status;
+ var description = theform.comment;
+ var attach_data = theform.data;
+ var attach_desc = theform.description;
+
+ var current_errors = YAHOO.util.Dom.getElementsByClassName(
+ 'validation_error_text', null, theform);
+ for (var i = 0; i < current_errors.length; i++) {
+ current_errors[i].parentNode.removeChild(current_errors[i]);
+ }
+ var current_error_fields = YAHOO.util.Dom.getElementsByClassName(
+ 'validation_error_field', null, theform);
+ for (var i = 0; i < current_error_fields.length; i++) {
+ var field = current_error_fields[i];
+ YAHOO.util.Dom.removeClass(field, 'validation_error_field');
+ }
+
+ var focus_me;
+
+ // These are checked in the reverse order that they appear on the page,
+ // so that the one closest to the top of the form will be focused.
+ if (attach_data.value && YAHOO.lang.trim(attach_desc.value) == '') {
+ _errorFor(attach_desc, 'attach_desc');
+ focus_me = attach_desc;
+ }
+ var check_description = status_comment_required[bug_status.value];
+ if (check_description && YAHOO.lang.trim(description.value) == '') {
+ _errorFor(description, 'description');
+ focus_me = description;
+ }
+ if (YAHOO.lang.trim(short_desc.value) == '') {
+ _errorFor(short_desc);
+ focus_me = short_desc;
+ }
+ if (version.selectedIndex < 0) {
+ _errorFor(version);
+ focus_me = version;
+ }
+ if (component.selectedIndex < 0) {
+ _errorFor(component);
+ focus_me = component;
+ }
+
+ if (focus_me) {
+ focus_me.focus();
+ return false;
+ }
+
+ return true;
+}
+
+function _errorFor(field, name) {
+ if (!name) name = field.id;
+ var string_name = name + '_required';
+ var error_text = BUGZILLA.string[string_name];
+ var new_node = document.createElement('div');
+ YAHOO.util.Dom.addClass(new_node, 'validation_error_text');
+ new_node.innerHTML = error_text;
+ YAHOO.util.Dom.insertAfter(new_node, field);
+ YAHOO.util.Dom.addClass(field, 'validation_error_field');
+}
+
function createCalendar(name) {
var cal = new YAHOO.widget.Calendar('calendar_' + name,
'con_calendar_' + name);
@@ -136,32 +211,34 @@
}
}
+function setupEditLink(id) {
+ var link_container = 'container_showhide_' + id;
+ var input_container = 'container_' + id;
+ var link = 'showhide_' + id;
+ hideEditableField(link_container, input_container, link);
+}
-/* Hide input fields and show the text with (edit) next to it */
-function hideEditableField( container, input, action, field1_id, field2_id, original_value ) {
- YAHOO.util.Dom.setStyle(container, 'display', 'inline');
- YAHOO.util.Dom.setStyle(input, 'display', 'none');
+/* Hide input/select fields and show the text with (edit) next to it */
+function hideEditableField( container, input, action, field_id, original_value, new_value ) {
+ YAHOO.util.Dom.removeClass(container, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(input, 'bz_default_hidden');
YAHOO.util.Event.addListener(action, 'click', showEditableField,
- new Array(container, input, field2_id));
- YAHOO.util.Event.addListener(field1_id + '_nonedit_display', 'click', showEditableField,
- new Array(container, input, field1_id));
- YAHOO.util.Event.addListener(field2_id + '_nonedit_display', 'click', showEditableField,
- new Array(container, input, field2_id));
- if(field2_id != ""){
+ new Array(container, input, field_id, new_value));
+ if(field_id != ""){
YAHOO.util.Event.addListener(window, 'load', checkForChangedFieldValues,
- new Array(container, input, field2_id, original_value));
+ new Array(container, input, field_id, original_value));
}
}
/* showEditableField (e, ContainerInputArray)
- * Function hides the (edit) link and the text and displays the input
+ * Function hides the (edit) link and the text and displays the input/select field
*
* var e: the event
* var ContainerInputArray: An array containing the (edit) and text area and the input being displayed
- * var ContainerInputArray[0]: the conainer that will be hidden usually shows the (edit) text
+ * var ContainerInputArray[0]: the container that will be hidden usually shows the (edit) or (take) text
* var ContainerInputArray[1]: the input area and label that will be displayed
- * var ContainerInputArray[2]: [optional] the id of the input element to focus
- *
+ * var ContainerInputArray[2]: the input/select field id for which the new value must be set
+ * var ContainerInputArray[3]: the new value to set the input/select field to when (take) is clicked
*/
function showEditableField (e, ContainerInputArray) {
var inputs = new Array();
@@ -170,22 +247,34 @@
YAHOO.util.Event.preventDefault(e);
return;
}
- YAHOO.util.Dom.setStyle(ContainerInputArray[0], 'display', 'none');
- YAHOO.util.Dom.setStyle(inputArea, 'display', 'inline');
+ YAHOO.util.Dom.addClass(ContainerInputArray[0], 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(inputArea, 'bz_default_hidden');
if ( inputArea.tagName.toLowerCase() == "input" ) {
inputs.push(inputArea);
+ } else if (ContainerInputArray[2]) {
+ inputs.push(document.getElementById(ContainerInputArray[2]));
} else {
inputs = inputArea.getElementsByTagName('input');
}
if ( inputs.length > 0 ) {
- var elementToFocus = YAHOO.util.Dom.get(ContainerInputArray[2]);
- if (elementToFocus) {
- // focus on the requested field
- elementToFocus.focus();
- elementToFocus.select();
- } else {
- // focus on the first field, this makes it easier to edit
- inputs[0].focus();
+ // Change the first field's value to ContainerInputArray[2]
+ // if present before focusing.
+ var type = inputs[0].tagName.toLowerCase();
+ if (ContainerInputArray[3]) {
+ if ( type == "input" ) {
+ inputs[0].value = ContainerInputArray[3];
+ } else {
+ for (var i = 0; inputs[0].length; i++) {
+ if ( inputs[0].options[i].value == ContainerInputArray[3] ) {
+ inputs[0].options[i].selected = true;
+ break;
+ }
+ }
+ }
+ }
+ // focus on the first field, this makes it easier to edit
+ inputs[0].focus();
+ if ( type == "input" ) {
inputs[0].select();
}
}
@@ -224,8 +313,8 @@
}
}
if(unhide){
- YAHOO.util.Dom.setStyle(ContainerInputArray[0], 'display', 'none');
- YAHOO.util.Dom.setStyle(ContainerInputArray[1], 'display', 'inline');
+ YAHOO.util.Dom.addClass(ContainerInputArray[0], 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(ContainerInputArray[1], 'bz_default_hidden');
}
}
@@ -233,7 +322,7 @@
function hideAliasAndSummary(short_desc_value, alias_value) {
// check the short desc field
hideEditableField( 'summary_alias_container','summary_alias_input',
- 'editme_action', 'alias', 'short_desc', short_desc_value);
+ 'editme_action','short_desc', short_desc_value);
// check that the alias hasn't changed
var bz_alias_check_array = new Array('summary_alias_container',
'summary_alias_input', 'alias', alias_value);
@@ -279,20 +368,41 @@
// finish doing stuff based on the selection.
if ( el ) {
showDuplicateItem(el);
- YAHOO.util.Dom.setStyle('resolution_settings', 'display', 'none');
- if (document.getElementById('resolution_settings_warning')) {
- YAHOO.util.Dom.setStyle('resolution_settings_warning', 'display', 'none');
- }
- YAHOO.util.Dom.setStyle('duplicate_display', 'display', 'none');
- if ( el.value == dupArrayInfo[1] && dupArrayInfo[0] == "is_duplicate" ) {
- YAHOO.util.Dom.setStyle('resolution_settings', 'display', 'inline');
- YAHOO.util.Dom.setStyle('resolution_settings_warning', 'display', 'block');
+ // Make sure that fields whose visibility or values are controlled
+ // by "resolution" behave properly when resolution is hidden.
+ var resolution = document.getElementById('resolution');
+ if (resolution && resolution.options[0].value != '') {
+ resolution.bz_lastSelected = resolution.selectedIndex;
+ var emptyOption = new Option('', '');
+ resolution.insertBefore(emptyOption, resolution.options[0]);
+ emptyOption.selected = true;
}
- else if ( bz_isValueInArray(close_status_array, el.value) ) {
- // hide duplicate and show resolution
- YAHOO.util.Dom.setStyle('resolution_settings', 'display', 'inline');
- YAHOO.util.Dom.setStyle('resolution_settings_warning', 'display', 'block');
+ YAHOO.util.Dom.addClass('resolution_settings', 'bz_default_hidden');
+ if (document.getElementById('resolution_settings_warning')) {
+ YAHOO.util.Dom.addClass('resolution_settings_warning',
+ 'bz_default_hidden');
+ }
+ YAHOO.util.Dom.addClass('duplicate_display', 'bz_default_hidden');
+
+
+ if ( (el.value == dupArrayInfo[1] && dupArrayInfo[0] == "is_duplicate")
+ || bz_isValueInArray(close_status_array, el.value) )
+ {
+ YAHOO.util.Dom.removeClass('resolution_settings',
+ 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass('resolution_settings_warning',
+ 'bz_default_hidden');
+
+ // Remove the blank option we inserted.
+ if (resolution && resolution.options[0].value == '') {
+ resolution.removeChild(resolution.options[0]);
+ resolution.selectedIndex = resolution.bz_lastSelected;
+ }
+ }
+
+ if (resolution) {
+ bz_fireEvent(resolution, 'change');
}
}
}
@@ -304,14 +414,19 @@
if (resolution) {
if (resolution.value == 'DUPLICATE' && bz_isValueInArray( close_status_array, bug_status.value) ) {
// hide resolution show duplicate
- YAHOO.util.Dom.setStyle('duplicate_settings', 'display', 'inline');
- YAHOO.util.Dom.setStyle('dup_id_discoverable', 'display', 'none');
- dup_id.focus();
- dup_id.select();
+ YAHOO.util.Dom.removeClass('duplicate_settings',
+ 'bz_default_hidden');
+ YAHOO.util.Dom.addClass('dup_id_discoverable', 'bz_default_hidden');
+ // check to make sure the field is visible or IE throws errors
+ if( ! YAHOO.util.Dom.hasClass( dup_id, 'bz_default_hidden' ) ){
+ dup_id.focus();
+ dup_id.select();
+ }
}
else {
- YAHOO.util.Dom.setStyle('duplicate_settings', 'display', 'none');
- YAHOO.util.Dom.setStyle('dup_id_discoverable', 'display', 'block');
+ YAHOO.util.Dom.addClass('duplicate_settings', 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass('dup_id_discoverable',
+ 'bz_default_hidden');
dup_id.blur();
}
}
@@ -321,18 +436,11 @@
function setResolutionToDuplicate(e, duplicate_or_move_bug_status) {
var status = document.getElementById('bug_status');
var resolution = document.getElementById('resolution');
- YAHOO.util.Dom.setStyle('dup_id_discoverable', 'display', 'none');
+ YAHOO.util.Dom.addClass('dup_id_discoverable', 'bz_default_hidden');
status.value = duplicate_or_move_bug_status;
+ bz_fireEvent(status, 'change');
resolution.value = "DUPLICATE";
- showHideStatusItems("", ["",""]);
-
- // Show edit field for bug number, so that the user doesn't have to click on "(edit)".
- YAHOO.util.Dom.setStyle('dup_id', 'display', 'inline');
- YAHOO.util.Dom.setStyle('dup_id_container', 'display', 'none');
- var dup_id = document.getElementById('dup_id');
- dup_id.focus();
- dup_id.select();
-
+ bz_fireEvent(resolution, 'change');
YAHOO.util.Event.preventDefault(e);
}
@@ -357,3 +465,326 @@
}
}
}
+
+function updateCommentTagControl(checkbox, field) {
+ if (checkbox.checked) {
+ YAHOO.util.Dom.addClass(field, 'bz_private');
+ } else {
+ YAHOO.util.Dom.removeClass(field, 'bz_private');
+ }
+}
+
+/**
+ * Reset the value of the classification field and fire an event change
+ * on it. Called when the product changes, in case the classification
+ * field (which is hidden) controls the visibility of any other fields.
+ */
+function setClassification() {
+ var classification = document.getElementById('classification');
+ var product = document.getElementById('product');
+ var selected_product = product.value;
+ var select_classification = all_classifications[selected_product];
+ classification.value = select_classification;
+ bz_fireEvent(classification, 'change');
+}
+
+/**
+ * Says that a field should only be displayed when another field has
+ * a certain value. May only be called after the controller has already
+ * been added to the DOM.
+ */
+function showFieldWhen(controlled_id, controller_id, values) {
+ var controller = document.getElementById(controller_id);
+ // Note that we don't get an object for "controlled" here, because it
+ // might not yet exist in the DOM. We just pass along its id.
+ YAHOO.util.Event.addListener(controller, 'change',
+ handleVisControllerValueChange, [controlled_id, controller, values]);
+}
+
+/**
+ * Called by showFieldWhen when a field's visibility controller
+ * changes values.
+ */
+function handleVisControllerValueChange(e, args) {
+ var controlled_id = args[0];
+ var controller = args[1];
+ var values = args[2];
+
+ var label_container =
+ document.getElementById('field_label_' + controlled_id);
+ var field_container =
+ document.getElementById('field_container_' + controlled_id);
+ var selected = false;
+ for (var i = 0; i < values.length; i++) {
+ if (bz_valueSelected(controller, values[i])) {
+ selected = true;
+ break;
+ }
+ }
+
+ if (selected) {
+ YAHOO.util.Dom.removeClass(label_container, 'bz_hidden_field');
+ YAHOO.util.Dom.removeClass(field_container, 'bz_hidden_field');
+ }
+ else {
+ YAHOO.util.Dom.addClass(label_container, 'bz_hidden_field');
+ YAHOO.util.Dom.addClass(field_container, 'bz_hidden_field');
+ }
+}
+
+function showValueWhen(controlled_field_id, controlled_value_ids,
+ controller_field_id, controller_value_id)
+{
+ var controller_field = document.getElementById(controller_field_id);
+ // Note that we don't get an object for the controlled field here,
+ // because it might not yet exist in the DOM. We just pass along its id.
+ YAHOO.util.Event.addListener(controller_field, 'change',
+ handleValControllerChange, [controlled_field_id, controlled_value_ids,
+ controller_field, controller_value_id]);
+}
+
+function handleValControllerChange(e, args) {
+ var controlled_field = document.getElementById(args[0]);
+ var controlled_value_ids = args[1];
+ var controller_field = args[2];
+ var controller_value_id = args[3];
+
+ var controller_item = document.getElementById(
+ _value_id(controller_field.id, controller_value_id));
+
+ for (var i = 0; i < controlled_value_ids.length; i++) {
+ var item = getPossiblyHiddenOption(controlled_field,
+ controlled_value_ids[i]);
+ if (item.disabled && controller_item && controller_item.selected) {
+ item = showOptionInIE(item, controlled_field);
+ YAHOO.util.Dom.removeClass(item, 'bz_hidden_option');
+ item.disabled = false;
+ }
+ else if (!item.disabled && controller_item && !controller_item.selected) {
+ YAHOO.util.Dom.addClass(item, 'bz_hidden_option');
+ if (item.selected) {
+ item.selected = false;
+ bz_fireEvent(controlled_field, 'change');
+ }
+ item.disabled = true;
+ hideOptionInIE(item, controlled_field);
+ }
+ }
+}
+
+// A convenience function to generate the "id" tag of an <option>
+// based on the numeric id that Bugzilla uses for that value.
+function _value_id(field_name, id) {
+ return 'v' + id + '_' + field_name;
+}
+
+/*********************************/
+/* Code for Hiding Options in IE */
+/*********************************/
+
+/* IE 7 and below (and some other browsers) don't respond to "display: none"
+ * on <option> tags. However, you *can* insert a Comment Node as a
+ * child of a <select> tag. So we just insert a Comment where the <option>
+ * used to be. */
+var ie_hidden_options = new Array();
+function hideOptionInIE(anOption, aSelect) {
+ if (browserCanHideOptions(aSelect)) return;
+
+ var commentNode = document.createComment(anOption.value);
+ commentNode.id = anOption.id;
+ // This keeps the interface of Comments and Options the same for
+ // our other functions.
+ commentNode.disabled = true;
+ // replaceChild is very slow on IE in a <select> that has a lot of
+ // options, so we use replaceNode when we can.
+ if (anOption.replaceNode) {
+ anOption.replaceNode(commentNode);
+ }
+ else {
+ aSelect.replaceChild(commentNode, anOption);
+ }
+
+ // Store the comment node for quick access for getPossiblyHiddenOption
+ if (!ie_hidden_options[aSelect.id]) {
+ ie_hidden_options[aSelect.id] = new Array();
+ }
+ ie_hidden_options[aSelect.id][anOption.id] = commentNode;
+}
+
+function showOptionInIE(aNode, aSelect) {
+ if (browserCanHideOptions(aSelect)) return aNode;
+
+ // We do this crazy thing with innerHTML and createElement because
+ // this is the ONLY WAY that this works properly in IE.
+ var optionNode = document.createElement('option');
+ optionNode.innerHTML = aNode.data;
+ optionNode.value = aNode.data;
+ optionNode.id = aNode.id;
+ // replaceChild is very slow on IE in a <select> that has a lot of
+ // options, so we use replaceNode when we can.
+ if (aNode.replaceNode) {
+ aNode.replaceNode(optionNode);
+ }
+ else {
+ aSelect.replaceChild(optionNode, aNode);
+ }
+ delete ie_hidden_options[aSelect.id][optionNode.id];
+ return optionNode;
+}
+
+function initHidingOptionsForIE(select_name) {
+ var aSelect = document.getElementById(select_name);
+ if (browserCanHideOptions(aSelect)) return;
+
+ for (var i = 0; ;i++) {
+ var item = aSelect.options[i];
+ if (!item) break;
+ if (item.disabled) {
+ hideOptionInIE(item, aSelect);
+ i--; // Hiding an option means that the options array has changed.
+ }
+ }
+}
+
+function getPossiblyHiddenOption(aSelect, optionId) {
+ // Works always for <option> tags, and works for commentNodes
+ // in IE (but not in Webkit).
+ var id = _value_id(aSelect.id, optionId);
+ var val = document.getElementById(id);
+
+ // This is for WebKit and other browsers that can't "display: none"
+ // an <option> and also can't getElementById for a commentNode.
+ if (!val && ie_hidden_options[aSelect.id]) {
+ val = ie_hidden_options[aSelect.id][id];
+ }
+
+ return val;
+}
+
+var browser_can_hide_options;
+function browserCanHideOptions(aSelect) {
+ /* As far as I can tell, browsers that don't hide <option> tags
+ * also never have a X position for <option> tags, even if
+ * they're visible. This is the only reliable way I found to
+ * differentiate browsers. So we create a visible option, see
+ * if it has a position, and then remove it. */
+ if (typeof(browser_can_hide_options) == "undefined") {
+ var new_opt = bz_createOptionInSelect(aSelect, '', '');
+ var opt_pos = YAHOO.util.Dom.getX(new_opt);
+ aSelect.removeChild(new_opt);
+ if (opt_pos) {
+ browser_can_hide_options = true;
+ }
+ else {
+ browser_can_hide_options = false;
+ }
+ }
+ return browser_can_hide_options;
+}
+
+/* (end) option hiding code */
+
+/**
+ * The Autoselect
+ */
+YAHOO.bugzilla.userAutocomplete = {
+ counter : 0,
+ dataSource : null,
+ generateRequest : function ( enteredText ){
+ YAHOO.bugzilla.userAutocomplete.counter =
+ YAHOO.bugzilla.userAutocomplete.counter + 1;
+ YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
+ var json_object = {
+ method : "User.get",
+ id : YAHOO.bugzilla.userAutocomplete.counter,
+ params : [ {
+ match : [ decodeURIComponent(enteredText) ],
+ include_fields : [ "name", "real_name" ]
+ } ]
+ };
+ var stringified = YAHOO.lang.JSON.stringify(json_object);
+ var debug = { msg: "json-rpc obj debug info", "json obj": json_object,
+ "param" : stringified}
+ YAHOO.bugzilla.userAutocomplete.debug_helper( debug );
+ return stringified;
+ },
+ resultListFormat : function(oResultData, enteredText, sResultMatch) {
+ return ( YAHOO.lang.escapeHTML(oResultData.real_name) + " ("
+ + YAHOO.lang.escapeHTML(oResultData.name) + ")");
+ },
+ debug_helper : function ( ){
+ /* used to help debug any errors that might happen */
+ if( typeof(console) !== 'undefined' && console != null && arguments.length > 0 ){
+ console.log("debug helper info:", arguments);
+ }
+ return true;
+ },
+ init_ds : function(){
+ this.dataSource = new YAHOO.util.XHRDataSource("jsonrpc.cgi");
+ this.dataSource.connTimeout = 30000;
+ this.dataSource.connMethodPost = true;
+ this.dataSource.connXhrMode = "cancelStaleRequests";
+ this.dataSource.maxCacheEntries = 5;
+ this.dataSource.responseSchema = {
+ resultsList : "result.users",
+ metaFields : { error: "error", jsonRpcId: "id"},
+ fields : [
+ { key : "name" },
+ { key : "real_name"}
+ ]
+ };
+ },
+ init : function( field, container, multiple ) {
+ if( this.dataSource == null ){
+ this.init_ds();
+ }
+ var userAutoComp = new YAHOO.widget.AutoComplete( field, container,
+ this.dataSource );
+ // other stuff we might want to do with the autocomplete goes here
+ userAutoComp.maxResultsDisplayed = BUGZILLA.param.maxusermatches;
+ userAutoComp.generateRequest = this.generateRequest;
+ userAutoComp.formatResult = this.resultListFormat;
+ userAutoComp.doBeforeLoadData = this.debug_helper;
+ userAutoComp.minQueryLength = 3;
+ userAutoComp.autoHighlight = false;
+ // this is a throttle to determine the delay of the query from typing
+ // set this higher to cause fewer calls to the server
+ userAutoComp.queryDelay = 0.05;
+ userAutoComp.useIFrame = true;
+ userAutoComp.resultTypeList = false;
+ if( multiple == true ){
+ userAutoComp.delimChar = [","];
+ }
+
+ }
+};
+
+YAHOO.bugzilla.keywordAutocomplete = {
+ dataSource : null,
+ init_ds : function(){
+ this.dataSource = new YAHOO.util.LocalDataSource( YAHOO.bugzilla.keyword_array );
+ },
+ init : function( field, container ) {
+ if( this.dataSource == null ){
+ this.init_ds();
+ }
+ var keywordAutoComp = new YAHOO.widget.AutoComplete(field, container, this.dataSource);
+ keywordAutoComp.maxResultsDisplayed = YAHOO.bugzilla.keyword_array.length;
+ keywordAutoComp.minQueryLength = 0;
+ keywordAutoComp.useIFrame = true;
+ keywordAutoComp.delimChar = [","," "];
+ keywordAutoComp.resultTypeList = false;
+ keywordAutoComp.queryDelay = 0;
+ /* Causes all the possibilities in the keyword to appear when a user
+ * focuses on the textbox
+ */
+ keywordAutoComp.textboxFocusEvent.subscribe( function(){
+ var sInputValue = YAHOO.util.Dom.get('keywords').value;
+ if( sInputValue.length === 0 ){
+ this.sendQuery(sInputValue);
+ this.collapseContainer();
+ this.expandContainer();
+ }
+ });
+ }
+};
diff --git a/Websites/bugs.webkit.org/js/flag.js b/Websites/bugs.webkit.org/js/flag.js
new file mode 100644
index 0000000..64b4ad8
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/flag.js
@@ -0,0 +1,75 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Myk Melez <myk@mozilla.org>
+ * Frédéric Buclin <LpSolit@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Shows or hides a requestee field depending on whether or not
+// the user is requesting the corresponding flag.
+function toggleRequesteeField(flagField, no_focus)
+{
+ // Convert the ID of the flag field into the ID of its corresponding
+ // requestee field and then use the ID to get the field.
+ var id = flagField.name.replace(/flag(_type)?-(\d+)/, "requestee$1-$2");
+ var requesteeField = document.getElementById(id);
+ if (!requesteeField) return;
+
+ // Show or hide the requestee field based on the value
+ // of the flag field.
+ if (flagField.value == "?") {
+ YAHOO.util.Dom.removeClass(requesteeField.parentNode, 'bz_default_hidden');
+ if (!no_focus) requesteeField.focus();
+ } else
+ YAHOO.util.Dom.addClass(requesteeField.parentNode, 'bz_default_hidden');
+}
+
+// Hides requestee fields when the window is loaded since they shouldn't
+// be enabled until the user requests that flag type.
+function hideRequesteeFields()
+{
+ var inputElements = document.getElementsByTagName("input");
+ var selectElements = document.getElementsByTagName("select");
+ //You cannot update Node lists, so you must create an array to combine the NodeLists
+ var allElements = [];
+ for( var i=0; i < inputElements.length; i++ ) {
+ allElements[allElements.length] = inputElements.item(i);
+ }
+ for( var i=0; i < selectElements.length; i++ ) { //Combine inputs with selects
+ allElements[allElements.length] = selectElements.item(i);
+ }
+ var inputElement, id, flagField;
+ for ( var i=0 ; i<allElements.length ; i++ )
+ {
+ inputElement = allElements[i];
+ if (inputElement.name.search(/^requestee(_type)?-(\d+)$/) != -1)
+ {
+ // Convert the ID of the requestee field into the ID of its corresponding
+ // flag field and then use the ID to get the field.
+ id = inputElement.name.replace(/requestee(_type)?-(\d+)/, "flag$1-$2");
+ flagField = document.getElementById(id);
+ if (flagField && flagField.value != "?")
+ YAHOO.util.Dom.addClass(inputElement.parentNode, 'bz_default_hidden');
+ }
+ }
+}
+YAHOO.util.Event.onDOMReady(hideRequesteeFields);
diff --git a/Websites/bugs.webkit.org/js/global.js b/Websites/bugs.webkit.org/js/global.js
new file mode 100644
index 0000000..b62d7b9
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/global.js
@@ -0,0 +1,131 @@
+/* The contents of this file are subject to the Mozilla Public
+* License Version 1.1 (the "License"); you may not use this file
+* except in compliance with the License. You may obtain a copy of
+* the License at http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS
+* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+* implied. See the License for the specific language governing
+* rights and limitations under the License.
+*
+* The Original Code is the Bugzilla Bug Tracking System.
+*
+* Contributor(s):
+* Guy Pyrzak <guy.pyrzak@gmail.com>
+* Max Kanat-Alexander <mkanat@bugzilla.org>
+*
+*/
+
+var mini_login_constants;
+
+function show_mini_login_form( suffix ) {
+ var login_link = document.getElementById('login_link' + suffix);
+ var login_form = document.getElementById('mini_login' + suffix);
+ var account_container = document.getElementById('new_account_container'
+ + suffix);
+
+ YAHOO.util.Dom.addClass(login_link, 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(login_form, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(account_container, 'bz_default_hidden');
+ return false;
+}
+
+function hide_mini_login_form( suffix ) {
+ var login_link = document.getElementById('login_link' + suffix);
+ var login_form = document.getElementById('mini_login' + suffix);
+ var account_container = document.getElementById('new_account_container'
+ + suffix);
+
+ YAHOO.util.Dom.removeClass(login_link, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(login_form, 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(account_container, 'bz_default_hidden');
+ return false;
+}
+
+function show_forgot_form( suffix ) {
+ var forgot_link = document.getElementById('forgot_link' + suffix);
+ var forgot_form = document.getElementById('forgot_form' + suffix);
+ var login_container = document.getElementById('mini_login_container'
+ + suffix);
+ YAHOO.util.Dom.addClass(forgot_link, 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(forgot_form, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(login_container, 'bz_default_hidden');
+ return false;
+}
+
+function hide_forgot_form( suffix ) {
+ var forgot_link = document.getElementById('forgot_link' + suffix);
+ var forgot_form = document.getElementById('forgot_form' + suffix);
+ var login_container = document.getElementById('mini_login_container'
+ + suffix);
+ YAHOO.util.Dom.removeClass(forgot_link, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(forgot_form, 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(login_container, 'bz_default_hidden');
+ return false;
+}
+
+function init_mini_login_form( suffix ) {
+ var mini_login = document.getElementById('Bugzilla_login' + suffix );
+ var mini_password = document.getElementById('Bugzilla_password' + suffix );
+ var mini_dummy = document.getElementById(
+ 'Bugzilla_password_dummy' + suffix);
+ // If the login and password are blank when the page loads, we display
+ // "login" and "password" in the boxes by default.
+ if (mini_login.value == "" && mini_password.value == "") {
+ mini_login.value = mini_login_constants.login;
+ YAHOO.util.Dom.addClass(mini_login, "bz_mini_login_help");
+ YAHOO.util.Dom.addClass(mini_password, 'bz_default_hidden');
+ YAHOO.util.Dom.removeClass(mini_dummy, 'bz_default_hidden');
+ }
+ else {
+ show_mini_login_form(suffix);
+ }
+}
+
+// Clear the words "login" and "password" from the form when you click
+// in one of the boxes. We clear them both when you click in either box
+// so that the browser's password-autocomplete can work.
+function mini_login_on_focus( suffix ) {
+ var mini_login = document.getElementById('Bugzilla_login' + suffix );
+ var mini_password = document.getElementById('Bugzilla_password' + suffix );
+ var mini_dummy = document.getElementById(
+ 'Bugzilla_password_dummy' + suffix);
+
+ YAHOO.util.Dom.removeClass(mini_login, "bz_mini_login_help");
+ if (mini_login.value == mini_login_constants.login) {
+ mini_login.value = '';
+ }
+ YAHOO.util.Dom.removeClass(mini_password, 'bz_default_hidden');
+ YAHOO.util.Dom.addClass(mini_dummy, 'bz_default_hidden');
+}
+
+function check_mini_login_fields( suffix ) {
+ var mini_login = document.getElementById('Bugzilla_login' + suffix );
+ var mini_password = document.getElementById('Bugzilla_password' + suffix );
+ if( (mini_login.value != "" && mini_password.value != "")
+ && mini_login.value != mini_login_constants.login )
+ {
+ return true;
+ }
+ window.alert( mini_login_constants.warning );
+ return false;
+}
+
+function set_language( value ) {
+ YAHOO.util.Cookie.set('LANG', value,
+ {
+ expires: new Date('January 1, 2038'),
+ path: BUGZILLA.param.cookie_path
+ });
+ window.location.reload()
+}
+
+// This basically duplicates Bugzilla::Util::display_value for code that
+// can't go through the template and has to be in JS.
+function display_value(field, value) {
+ var field_trans = BUGZILLA.value_descs[field];
+ if (!field_trans) return value;
+ var translated = field_trans[value];
+ if (translated) return translated;
+ return value;
+}
diff --git a/Websites/bugs.webkit.org/js/help.js b/Websites/bugs.webkit.org/js/help.js
deleted file mode 100644
index 938a73a..0000000
--- a/Websites/bugs.webkit.org/js/help.js
+++ /dev/null
@@ -1,108 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is the Bugzilla Bug Tracking System.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Gervase Markham <gerv@gerv.net>
- *
- * ***** END LICENSE BLOCK ***** */
-
-var g_helpTexts = new Object();
-var g_helpIframe;
-var g_helpDiv;
-
-/**
- * Generate help controls during page construction.
- *
- * @return Boolean; true if controls were created and false if not.
- */
-function generateHelp()
-{
- // Only enable help if controls can be hidden
- if (!document.body.style)
- return false;
-
- // Create help controls (a div to hold help text and an iframe
- // to mask any and all controls under the popup)
- document.write('<div id="helpDiv" style="display: none;"><\/div>');
- document.write('<iframe id="helpIframe" src="about:blank"');
- document.write(' frameborder="0" scrolling="no"><\/iframe>');
-
- return true;
-}
-
-/**
- * Enable help popups for all form elements after the page has finished loading.
- *
- * @return Boolean; true if help was enabled and false if not.
- */
-function enableHelp()
-{
- g_helpIframe = document.getElementById('helpIframe');
- g_helpDiv = document.getElementById('helpDiv');
- if (!g_helpIframe || !g_helpDiv) // Disabled if no controls found
- return false;
-
- // MS decided to add fieldsets to the elements array; and
- // Mozilla decided to copy this brokenness. Grr.
- for (var i = 0; i < document.forms.length; i++) {
- for (var j = 0; j < document.forms[i].elements.length; j++) {
- if (document.forms[i].elements[j].tagName != 'FIELDSET') {
- document.forms[i].elements[j].onmouseover = showHelp;
- }
- }
- }
-
- document.body.onclick = hideHelp;
- return true;
-}
-
-/**
- * Show the help popup for a form element.
- */
-function showHelp() {
- if (!g_helpIframe || !g_helpDiv || !g_helpTexts[this.name])
- return;
-
- // Get the position and size of the form element in the document
- var elemY = bz_findPosY(this);
- var elemX = bz_findPosX(this);
- var elemH = this.offsetHeight;
-
- // Update help text displayed in the div
- g_helpDiv.innerHTML = ''; // Helps IE 5 Mac
- g_helpDiv.innerHTML = g_helpTexts[this.name];
-
- // Position and display the help popup
- g_helpIframe.style.top = g_helpDiv.style.top = elemY + elemH + 5 + "px";
- g_helpIframe.style.left = g_helpDiv.style.left = elemX + "px";
- g_helpIframe.style.display = g_helpDiv.style.display = '';
- g_helpIframe.style.width = g_helpDiv.offsetWidth + "px";
- g_helpIframe.style.height = g_helpDiv.offsetHeight + "px";
-}
-
-/**
- * Hide the help popup.
- */
-function hideHelp() {
- if (!g_helpIframe || !g_helpDiv)
- return;
-
- g_helpIframe.style.display = g_helpDiv.style.display = 'none';
-}
diff --git a/Websites/bugs.webkit.org/js/history.js/license.txt b/Websites/bugs.webkit.org/js/history.js/license.txt
new file mode 100644
index 0000000..719aafc
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/history.js/license.txt
@@ -0,0 +1,10 @@
+Copyright (c) 2011, Benjamin Arthur Lupton
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ • Neither the name of Benjamin Arthur Lupton nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Websites/bugs.webkit.org/js/history.js/native.history.js b/Websites/bugs.webkit.org/js/history.js/native.history.js
new file mode 100644
index 0000000..3d34166
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/history.js/native.history.js
@@ -0,0 +1 @@
+window.JSON||(window.JSON={}),function(){function f(a){return a<10?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";return e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g,e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)d=rep[c],typeof d=="string"&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));return e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g,e}}"use strict",typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()});var JSON=window.JSON,cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;typeof JSON.stringify!="function"&&(JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")}),typeof JSON.parse!="function"&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),typeof reviver=="function"?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(),function(a,b){"use strict";var c=a.History=a.History||{};if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={handlers:{},_uid:1,uid:function(a){return a._uid||(a._uid=c.Adapter._uid++)},bind:function(a,b,d){var e=c.Adapter.uid(a);c.Adapter.handlers[e]=c.Adapter.handlers[e]||{},c.Adapter.handlers[e][b]=c.Adapter.handlers[e][b]||[],c.Adapter.handlers[e][b].push(d),a["on"+b]=function(a,b){return function(d){c.Adapter.trigger(a,b,d)}}(a,b)},trigger:function(a,b,d){d=d||{};var e=c.Adapter.uid(a),f,g;c.Adapter.handlers[e]=c.Adapter.handlers[e]||{},c.Adapter.handlers[e][b]=c.Adapter.handlers[e][b]||[];for(f=0,g=c.Adapter.handlers[e][b].length;f<g;++f)c.Adapter.handlers[e][b][f].apply(this,[d])},extractEventData:function(a,c){var d=c&&c[a]||b;return d},onDomLoad:function(b){var c=a.setTimeout(function(){b()},2e3);a.onload=function(){clearTimeout(c),b()}}},typeof c.init!="undefined"&&c.init()}(window),function(a,b){"use strict";var c=a.document,d=a.setTimeout||d,e=a.clearTimeout||e,f=a.setInterval||f,g=a.History=a.History||{};if(typeof g.initHtml4!="undefined")throw new Error("History.js HTML4 Support has already been loaded...");g.initHtml4=function(){if(typeof g.initHtml4.initialized!="undefined")return!1;g.initHtml4.initialized=!0,g.enabled=!0,g.savedHashes=[],g.isLastHash=function(a){var b=g.getHashByIndex(),c;return c=a===b,c},g.saveHash=function(a){return g.isLastHash(a)?!1:(g.savedHashes.push(a),!0)},g.getHashByIndex=function(a){var b=null;return typeof a=="undefined"?b=g.savedHashes[g.savedHashes.length-1]:a<0?b=g.savedHashes[g.savedHashes.length+a]:b=g.savedHashes[a],b},g.discardedHashes={},g.discardedStates={},g.discardState=function(a,b,c){var d=g.getHashByState(a),e;return e={discardedState:a,backState:c,forwardState:b},g.discardedStates[d]=e,!0},g.discardHash=function(a,b,c){var d={discardedHash:a,backState:c,forwardState:b};return g.discardedHashes[a]=d,!0},g.discardedState=function(a){var b=g.getHashByState(a),c;return c=g.discardedStates[b]||!1,c},g.discardedHash=function(a){var b=g.discardedHashes[a]||!1;return b},g.recycleState=function(a){var b=g.getHashByState(a);return g.discardedState(a)&&delete g.discardedStates[b],!0},g.emulated.hashChange&&(g.hashChangeInit=function(){g.checkerFunction=null;var b="",d,e,h,i;return g.isInternetExplorer()?(d="historyjs-iframe",e=c.createElement("iframe"),e.setAttribute("id",d),e.style.display="none",c.body.appendChild(e),e.contentWindow.document.open(),e.contentWindow.document.close(),h="",i=!1,g.checkerFunction=function(){if(i)return!1;i=!0;var c=g.getHash()||"",d=g.unescapeHash(e.contentWindow.document.location.hash)||"";return c!==b?(b=c,d!==c&&(h=d=c,e.contentWindow.document.open(),e.contentWindow.document.close(),e.contentWindow.document.location.hash=g.escapeHash(c)),g.Adapter.trigger(a,"hashchange")):d!==h&&(h=d,g.setHash(d,!1)),i=!1,!0}):g.checkerFunction=function(){var c=g.getHash();return c!==b&&(b=c,g.Adapter.trigger(a,"hashchange")),!0},g.intervalList.push(f(g.checkerFunction,g.options.hashChangeInterval)),!0},g.Adapter.onDomLoad(g.hashChangeInit)),g.emulated.pushState&&(g.onHashChange=function(b){var d=b&&b.newURL||c.location.href,e=g.getHashByUrl(d),f=null,h=null,i=null,j;return g.isLastHash(e)?(g.busy(!1),!1):(g.doubleCheckComplete(),g.saveHash(e),e&&g.isTraditionalAnchor(e)?(g.Adapter.trigger(a,"anchorchange"),g.busy(!1),!1):(f=g.extractState(g.getFullUrl(e||c.location.href,!1),!0),g.isLastSavedState(f)?(g.busy(!1),!1):(h=g.getHashByState(f),j=g.discardedState(f),j?(g.getHashByIndex(-2)===g.getHashByState(j.forwardState)?g.back(!1):g.forward(!1),!1):(g.pushState(f.data,f.title,f.url,!1),!0))))},g.Adapter.bind(a,"hashchange",g.onHashChange),g.pushState=function(b,d,e,f){if(g.getHashByUrl(e))throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(f!==!1&&g.busy())return g.pushQueue({scope:g,callback:g.pushState,args:arguments,queue:f}),!1;g.busy(!0);var h=g.createStateObject(b,d,e),i=g.getHashByState(h),j=g.getState(!1),k=g.getHashByState(j),l=g.getHash();return g.storeState(h),g.expectedStateId=h.id,g.recycleState(h),g.setTitle(h),i===k?(g.busy(!1),!1):i!==l&&i!==g.getShortUrl(c.location.href)?(g.setHash(i,!1),!1):(g.saveState(h),g.Adapter.trigger(a,"statechange"),g.busy(!1),!0)},g.replaceState=function(a,b,c,d){if(g.getHashByUrl(c))throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(d!==!1&&g.busy())return g.pushQueue({scope:g,callback:g.replaceState,args:arguments,queue:d}),!1;g.busy(!0);var e=g.createStateObject(a,b,c),f=g.getState(!1),h=g.getStateByIndex(-2);return g.discardState(f,e,h),g.pushState(e.data,e.title,e.url,!1),!0}),g.emulated.pushState&&g.getHash()&&!g.emulated.hashChange&&g.Adapter.onDomLoad(function(){g.Adapter.trigger(a,"hashchange")})},typeof g.init!="undefined"&&g.init()}(window),function(a,b){"use strict";var c=a.console||b,d=a.document,e=a.navigator,f=a.sessionStorage||!1,g=a.setTimeout,h=a.clearTimeout,i=a.setInterval,j=a.clearInterval,k=a.JSON,l=a.alert,m=a.History=a.History||{},n=a.history;k.stringify=k.stringify||k.encode,k.parse=k.parse||k.decode;if(typeof m.init!="undefined")throw new Error("History.js Core has already been loaded...");m.init=function(){return typeof m.Adapter=="undefined"?!1:(typeof m.initCore!="undefined"&&m.initCore(),typeof m.initHtml4!="undefined"&&m.initHtml4(),!0)},m.initCore=function(){if(typeof m.initCore.initialized!="undefined")return!1;m.initCore.initialized=!0,m.options=m.options||{},m.options.hashChangeInterval=m.options.hashChangeInterval||100,m.options.safariPollInterval=m.options.safariPollInterval||500,m.options.doubleCheckInterval=m.options.doubleCheckInterval||500,m.options.storeInterval=m.options.storeInterval||1e3,m.options.busyDelay=m.options.busyDelay||250,m.options.debug=m.options.debug||!1,m.options.initialTitle=m.options.initialTitle||d.title,m.intervalList=[],m.clearAllIntervals=function(){var a,b=m.intervalList;if(typeof b!="undefined"&&b!==null){for(a=0;a<b.length;a++)j(b[a]);m.intervalList=null}},m.debug=function(){(m.options.debug||!1)&&m.log.apply(m,arguments)},m.log=function(){var a=typeof c!="undefined"&&typeof c.log!="undefined"&&typeof c.log.apply!="undefined",b=d.getElementById("log"),e,f,g,h,i;a?(h=Array.prototype.slice.call(arguments),e=h.shift(),typeof c.debug!="undefined"?c.debug.apply(c,[e,h]):c.log.apply(c,[e,h])):e="\n"+arguments[0]+"\n";for(f=1,g=arguments.length;f<g;++f){i=arguments[f];if(typeof i=="object"&&typeof k!="undefined")try{i=k.stringify(i)}catch(j){}e+="\n"+i+"\n"}return b?(b.value+=e+"\n-----\n",b.scrollTop=b.scrollHeight-b.clientHeight):a||l(e),!0},m.getInternetExplorerMajorVersion=function(){var a=m.getInternetExplorerMajorVersion.cached=typeof m.getInternetExplorerMajorVersion.cached!="undefined"?m.getInternetExplorerMajorVersion.cached:function(){var a=3,b=d.createElement("div"),c=b.getElementsByTagName("i");while((b.innerHTML="<!--[if gt IE "+ ++a+"]><i></i><![endif]-->")&&c[0]);return a>4?a:!1}();return a},m.isInternetExplorer=function(){var a=m.isInternetExplorer.cached=typeof m.isInternetExplorer.cached!="undefined"?m.isInternetExplorer.cached:Boolean(m.getInternetExplorerMajorVersion());return a},m.emulated={pushState:!Boolean(a.history&&a.history.pushState&&a.history.replaceState&&!/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i.test(e.userAgent)&&!/AppleWebKit\/5([0-2]|3[0-2])/i.test(e.userAgent)),hashChange:Boolean(!("onhashchange"in a||"onhashchange"in d)||m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8)},m.enabled=!m.emulated.pushState,m.bugs={setHash:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),safariPoll:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),ieDoubleCheck:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8),hashEscape:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<7)},m.isEmptyObject=function(a){for(var b in a)return!1;return!0},m.cloneObject=function(a){var b,c;return a?(b=k.stringify(a),c=k.parse(b)):c={},c},m.getRootUrl=function(){var a=d.location.protocol+"//"+(d.location.hostname||d.location.host);if(d.location.port||!1)a+=":"+d.location.port;return a+="/",a},m.getBaseHref=function(){var a=d.getElementsByTagName("base"),b=null,c="";return a.length===1&&(b=a[0],c=b.href.replace(/[^\/]+$/,"")),c=c.replace(/\/+$/,""),c&&(c+="/"),c},m.getBaseUrl=function(){var a=m.getBaseHref()||m.getBasePageUrl()||m.getRootUrl();return a},m.getPageUrl=function(){var a=m.getState(!1,!1),b=(a||{}).url||d.location.href,c;return c=b.replace(/\/+$/,"").replace(/[^\/]+$/,function(a,b,c){return/\./.test(a)?a:a+"/"}),c},m.getBasePageUrl=function(){var a=d.location.href.replace(/[#\?].*/,"").replace(/[^\/]+$/,function(a,b,c){return/[^\/]$/.test(a)?"":a}).replace(/\/+$/,"")+"/";return a},m.getFullUrl=function(a,b){var c=a,d=a.substring(0,1);return b=typeof b=="undefined"?!0:b,/[a-z]+\:\/\//.test(a)||(d==="/"?c=m.getRootUrl()+a.replace(/^\/+/,""):d==="#"?c=m.getPageUrl().replace(/#.*/,"")+a:d==="?"?c=m.getPageUrl().replace(/[\?#].*/,"")+a:b?c=m.getBaseUrl()+a.replace(/^(\.\/)+/,""):c=m.getBasePageUrl()+a.replace(/^(\.\/)+/,"")),c.replace(/\#$/,"")},m.getShortUrl=function(a){var b=a,c=m.getBaseUrl(),d=m.getRootUrl();return m.emulated.pushState&&(b=b.replace(c,"")),b=b.replace(d,"/"),m.isTraditionalAnchor(b)&&(b="./"+b),b=b.replace(/^(\.\/)+/g,"./").replace(/\#$/,""),b},m.store={},m.idToState=m.idToState||{},m.stateToId=m.stateToId||{},m.urlToId=m.urlToId||{},m.storedStates=m.storedStates||[],m.savedStates=m.savedStates||[],m.normalizeStore=function(){m.store.idToState=m.store.idToState||{},m.store.urlToId=m.store.urlToId||{},m.store.stateToId=m.store.stateToId||{}},m.getState=function(a,b){typeof a=="undefined"&&(a=!0),typeof b=="undefined"&&(b=!0);var c=m.getLastSavedState();return!c&&b&&(c=m.createStateObject()),a&&(c=m.cloneObject(c),c.url=c.cleanUrl||c.url),c},m.getIdByState=function(a){var b=m.extractId(a.url),c;if(!b){c=m.getStateString(a);if(typeof m.stateToId[c]!="undefined")b=m.stateToId[c];else if(typeof m.store.stateToId[c]!="undefined")b=m.store.stateToId[c];else{for(;;){b=(new Date).getTime()+String(Math.random()).replace(/\D/g,"");if(typeof m.idToState[b]=="undefined"&&typeof m.store.idToState[b]=="undefined")break}m.stateToId[c]=b,m.idToState[b]=a}}return b},m.normalizeState=function(a){var b,c;if(!a||typeof a!="object")a={};if(typeof a.normalized!="undefined")return a;if(!a.data||typeof a.data!="object")a.data={};b={},b.normalized=!0,b.title=a.title||"",b.url=m.getFullUrl(m.unescapeString(a.url||d.location.href)),b.hash=m.getShortUrl(b.url),b.data=m.cloneObject(a.data),b.id=m.getIdByState(b),b.cleanUrl=b.url.replace(/\??\&_suid.*/,""),b.url=b.cleanUrl,c=!m.isEmptyObject(b.data);if(b.title||c)b.hash=m.getShortUrl(b.url).replace(/\??\&_suid.*/,""),/\?/.test(b.hash)||(b.hash+="?"),b.hash+="&_suid="+b.id;return b.hashedUrl=m.getFullUrl(b.hash),(m.emulated.pushState||m.bugs.safariPoll)&&m.hasUrlDuplicate(b)&&(b.url=b.hashedUrl),b},m.createStateObject=function(a,b,c){var d={data:a,title:b,url:c};return d=m.normalizeState(d),d},m.getStateById=function(a){a=String(a);var c=m.idToState[a]||m.store.idToState[a]||b;return c},m.getStateString=function(a){var b,c,d;return b=m.normalizeState(a),c={data:b.data,title:a.title,url:a.url},d=k.stringify(c),d},m.getStateId=function(a){var b,c;return b=m.normalizeState(a),c=b.id,c},m.getHashByState=function(a){var b,c;return b=m.normalizeState(a),c=b.hash,c},m.extractId=function(a){var b,c,d;return c=/(.*)\&_suid=([0-9]+)$/.exec(a),d=c?c[1]||a:a,b=c?String(c[2]||""):"",b||!1},m.isTraditionalAnchor=function(a){var b=!/[\/\?\.]/.test(a);return b},m.extractState=function(a,b){var c=null,d,e;return b=b||!1,d=m.extractId(a),d&&(c=m.getStateById(d)),c||(e=m.getFullUrl(a),d=m.getIdByUrl(e)||!1,d&&(c=m.getStateById(d)),!c&&b&&!m.isTraditionalAnchor(a)&&(c=m.createStateObject(null,null,e))),c},m.getIdByUrl=function(a){var c=m.urlToId[a]||m.store.urlToId[a]||b;return c},m.getLastSavedState=function(){return m.savedStates[m.savedStates.length-1]||b},m.getLastStoredState=function(){return m.storedStates[m.storedStates.length-1]||b},m.hasUrlDuplicate=function(a){var b=!1,c;return c=m.extractState(a.url),b=c&&c.id!==a.id,b},m.storeState=function(a){return m.urlToId[a.url]=a.id,m.storedStates.push(m.cloneObject(a)),a},m.isLastSavedState=function(a){var b=!1,c,d,e;return m.savedStates.length&&(c=a.id,d=m.getLastSavedState(),e=d.id,b=c===e),b},m.saveState=function(a){return m.isLastSavedState(a)?!1:(m.savedStates.push(m.cloneObject(a)),!0)},m.getStateByIndex=function(a){var b=null;return typeof a=="undefined"?b=m.savedStates[m.savedStates.length-1]:a<0?b=m.savedStates[m.savedStates.length+a]:b=m.savedStates[a],b},m.getHash=function(){var a=m.unescapeHash(d.location.hash);return a},m.unescapeString=function(b){var c=b,d;for(;;){d=a.unescape(c);if(d===c)break;c=d}return c},m.unescapeHash=function(a){var b=m.normalizeHash(a);return b=m.unescapeString(b),b},m.normalizeHash=function(a){var b=a.replace(/[^#]*#/,"").replace(/#.*/,"");return b},m.setHash=function(a,b){var c,e,f;return b!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.setHash,args:arguments,queue:b}),!1):(c=m.escapeHash(a),m.busy(!0),e=m.extractState(a,!0),e&&!m.emulated.pushState?m.pushState(e.data,e.title,e.url,!1):d.location.hash!==c&&(m.bugs.setHash?(f=m.getPageUrl(),m.pushState(null,null,f+"#"+c,!1)):d.location.hash=c),m)},m.escapeHash=function(b){var c=m.normalizeHash(b);return c=a.escape(c),m.bugs.hashEscape||(c=c.replace(/\%21/g,"!").replace(/\%26/g,"&").replace(/\%3D/g,"=").replace(/\%3F/g,"?")),c},m.getHashByUrl=function(a){var b=String(a).replace(/([^#]*)#?([^#]*)#?(.*)/,"$2");return b=m.unescapeHash(b),b},m.setTitle=function(a){var b=a.title,c;b||(c=m.getStateByIndex(0),c&&c.url===a.url&&(b=c.title||m.options.initialTitle));try{d.getElementsByTagName("title")[0].innerHTML=b.replace("<","<").replace(">",">").replace(" & "," & ")}catch(e){}return d.title=b,m},m.queues=[],m.busy=function(a){typeof a!="undefined"?m.busy.flag=a:typeof m.busy.flag=="undefined"&&(m.busy.flag=!1);if(!m.busy.flag){h(m.busy.timeout);var b=function(){var a,c,d;if(m.busy.flag)return;for(a=m.queues.length-1;a>=0;--a){c=m.queues[a];if(c.length===0)continue;d=c.shift(),m.fireQueueItem(d),m.busy.timeout=g(b,m.options.busyDelay)}};m.busy.timeout=g(b,m.options.busyDelay)}return m.busy.flag},m.busy.flag=!1,m.fireQueueItem=function(a){return a.callback.apply(a.scope||m,a.args||[])},m.pushQueue=function(a){return m.queues[a.queue||0]=m.queues[a.queue||0]||[],m.queues[a.queue||0].push(a),m},m.queue=function(a,b){return typeof a=="function"&&(a={callback:a}),typeof b!="undefined"&&(a.queue=b),m.busy()?m.pushQueue(a):m.fireQueueItem(a),m},m.clearQueue=function(){return m.busy.flag=!1,m.queues=[],m},m.stateChanged=!1,m.doubleChecker=!1,m.doubleCheckComplete=function(){return m.stateChanged=!0,m.doubleCheckClear(),m},m.doubleCheckClear=function(){return m.doubleChecker&&(h(m.doubleChecker),m.doubleChecker=!1),m},m.doubleCheck=function(a){return m.stateChanged=!1,m.doubleCheckClear(),m.bugs.ieDoubleCheck&&(m.doubleChecker=g(function(){return m.doubleCheckClear(),m.stateChanged||a(),!0},m.options.doubleCheckInterval)),m},m.safariStatePoll=function(){var b=m.extractState(d.location.href),c;if(!m.isLastSavedState(b))c=b;else return;return c||(c=m.createStateObject()),m.Adapter.trigger(a,"popstate"),m},m.back=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.back,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.back(!1)}),n.go(-1),!0)},m.forward=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.forward,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.forward(!1)}),n.go(1),!0)},m.go=function(a,b){var c;if(a>0)for(c=1;c<=a;++c)m.forward(b);else{if(!(a<0))throw new Error("History.go: History.go requires a positive or negative integer passed.");for(c=-1;c>=a;--c)m.back(b)}return m};if(m.emulated.pushState){var o=function(){};m.pushState=m.pushState||o,m.replaceState=m.replaceState||o}else m.onPopState=function(b,c){var e=!1,f=!1,g,h;return m.doubleCheckComplete(),g=m.getHash(),g?(h=m.extractState(g||d.location.href,!0),h?m.replaceState(h.data,h.title,h.url,!1):(m.Adapter.trigger(a,"anchorchange"),m.busy(!1)),m.expectedStateId=!1,!1):(e=m.Adapter.extractEventData("state",b,c)||!1,e?f=m.getStateById(e):m.expectedStateId?f=m.getStateById(m.expectedStateId):f=m.extractState(d.location.href),f||(f=m.createStateObject(null,null,d.location.href)),m.expectedStateId=!1,m.isLastSavedState(f)?(m.busy(!1),!1):(m.storeState(f),m.saveState(f),m.setTitle(f),m.Adapter.trigger(a,"statechange"),m.busy(!1),!0))},m.Adapter.bind(a,"popstate",m.onPopState),m.pushState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.pushState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.pushState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0},m.replaceState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.replaceState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.replaceState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0};if(f){try{m.store=k.parse(f.getItem("History.store"))||{}}catch(p){m.store={}}m.normalizeStore()}else m.store={},m.normalizeStore();m.Adapter.bind(a,"beforeunload",m.clearAllIntervals),m.Adapter.bind(a,"unload",m.clearAllIntervals),m.saveState(m.storeState(m.extractState(d.location.href,!0))),f&&(m.onUnload=function(){var a,b;try{a=k.parse(f.getItem("History.store"))||{}}catch(c){a={}}a.idToState=a.idToState||{},a.urlToId=a.urlToId||{},a.stateToId=a.stateToId||{};for(b in m.idToState){if(!m.idToState.hasOwnProperty(b))continue;a.idToState[b]=m.idToState[b]}for(b in m.urlToId){if(!m.urlToId.hasOwnProperty(b))continue;a.urlToId[b]=m.urlToId[b]}for(b in m.stateToId){if(!m.stateToId.hasOwnProperty(b))continue;a.stateToId[b]=m.stateToId[b]}m.store=a,m.normalizeStore(),f.setItem("History.store",k.stringify(a))},m.intervalList.push(i(m.onUnload,m.options.storeInterval)),m.Adapter.bind(a,"beforeunload",m.onUnload),m.Adapter.bind(a,"unload",m.onUnload));if(!m.emulated.pushState){m.bugs.safariPoll&&m.intervalList.push(i(m.safariStatePoll,m.options.safariPollInterval));if(e.vendor==="Apple Computer, Inc."||(e.appCodeName||"")==="Mozilla")m.Adapter.bind(a,"hashchange",function(){m.Adapter.trigger(a,"popstate")}),m.getHash()&&m.Adapter.onDomLoad(function(){m.Adapter.trigger(a,"hashchange")})}},m.init()}(window)
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/history.js/readme.txt b/Websites/bugs.webkit.org/js/history.js/readme.txt
new file mode 100644
index 0000000..1288d07
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/history.js/readme.txt
@@ -0,0 +1,2 @@
+History.js (v1.7.1 - October 4 2011)
+http://github.com/balupton/history.js
diff --git a/Websites/bugs.webkit.org/js/util.js b/Websites/bugs.webkit.org/js/util.js
index ce7ea4c..6dcabbb 100644
--- a/Websites/bugs.webkit.org/js/util.js
+++ b/Websites/bugs.webkit.org/js/util.js
@@ -154,3 +154,121 @@
return false;
}
+
+/**
+ * Create wanted options in a select form control.
+ *
+ * @param aSelect Select form control to manipulate.
+ * @param aValue Value attribute of the new option element.
+ * @param aTextValue Value of a text node appended to the new option
+ * element.
+ * @return Created option element.
+ */
+function bz_createOptionInSelect(aSelect, aTextValue, aValue) {
+ var myOption = new Option(aTextValue, aValue);
+ aSelect.options[aSelect.length] = myOption;
+ return myOption;
+}
+
+/**
+ * Clears all options from a select form control.
+ *
+ * @param aSelect Select form control of which options to clear.
+ */
+function bz_clearOptions(aSelect) {
+
+ var length = aSelect.options.length;
+
+ for (var i = 0; i < length; i++) {
+ aSelect.removeChild(aSelect.options[0]);
+ }
+}
+
+/**
+ * Takes an array and moves all the values to an select.
+ *
+ * @param aSelect Select form control to populate. Will be cleared
+ * before array values are created in it.
+ * @param aArray Array with values to populate select with.
+ */
+function bz_populateSelectFromArray(aSelect, aArray) {
+ // Clear the field
+ bz_clearOptions(aSelect);
+
+ for (var i = 0; i < aArray.length; i++) {
+ var item = aArray[i];
+ bz_createOptionInSelect(aSelect, item[1], item[0]);
+ }
+}
+
+/**
+ * Tells you whether or not a particular value is selected in a select,
+ * whether it's a multi-select or a single-select. The check is
+ * case-sensitive.
+ *
+ * @param aSelect The select you're checking.
+ * @param aValue The value that you want to know about.
+ */
+function bz_valueSelected(aSelect, aValue) {
+ var options = aSelect.options;
+ for (var i = 0; i < options.length; i++) {
+ if (options[i].selected && options[i].value == aValue) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Tells you where (what index) in a <select> a particular option is.
+ * Returns -1 if the value is not in the <select>
+ *
+ * @param aSelect The select you're checking.
+ * @param aValue The value you want to know the index of.
+ */
+function bz_optionIndex(aSelect, aValue) {
+ for (var i = 0; i < aSelect.options.length; i++) {
+ if (aSelect.options[i].value == aValue) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/**
+ * Used to fire an event programmatically.
+ *
+ * @param anElement The element you want to fire the event of.
+ * @param anEvent The name of the event you want to fire,
+ * without the word "on" in front of it.
+ */
+function bz_fireEvent(anElement, anEvent) {
+ if (document.createEvent) {
+ // DOM-compliant browser
+ var evt = document.createEvent("HTMLEvents");
+ evt.initEvent(anEvent, true, true);
+ return !anElement.dispatchEvent(evt);
+ } else {
+ // IE
+ var evt = document.createEventObject();
+ return anElement.fireEvent('on' + anEvent, evt);
+ }
+}
+
+/**
+ * Adds a CSS class to an element if it doesn't have it. Removes the
+ * CSS class from the element if the element does have the class.
+ *
+ * Requires YUI's Dom library.
+ *
+ * @param anElement The element to toggle the class on
+ * @param aClass The name of the CSS class to toggle.
+ */
+function bz_toggleClass(anElement, aClass) {
+ if (YAHOO.util.Dom.hasClass(anElement, aClass)) {
+ YAHOO.util.Dom.removeClass(anElement, aClass);
+ }
+ else {
+ YAHOO.util.Dom.addClass(anElement, aClass);
+ }
+}
diff --git a/Websites/bugs.webkit.org/js/yui/animation/animation-min.js b/Websites/bugs.webkit.org/js/yui/animation/animation-min.js
new file mode 100644
index 0000000..26dde3d
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/animation/animation-min.js
@@ -0,0 +1,23 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var b=YAHOO.util;var a=function(d,c,e,f){if(!d){}this.init(d,c,e,f);};a.NAME="Anim";a.prototype={toString:function(){var c=this.getEl()||{};var d=c.id||c.tagName;return(this.constructor.NAME+": "+d);},patterns:{noNegatives:/width|height|opacity|padding/i,offsetAttribute:/^((width|height)|(top|left))$/,defaultUnit:/width|height|top$|bottom$|left$|right$/i,offsetUnit:/\d+(em|%|en|ex|pt|in|cm|mm|pc)$/i},doMethod:function(c,e,d){return this.method(this.currentFrame,e,d-e,this.totalFrames);},setAttribute:function(c,f,e){var d=this.getEl();if(this.patterns.noNegatives.test(c)){f=(f>0)?f:0;}if(c in d&&!("style" in d&&c in d.style)){d[c]=f;}else{b.Dom.setStyle(d,c,f+e);}},getAttribute:function(c){var e=this.getEl();var g=b.Dom.getStyle(e,c);if(g!=="auto"&&!this.patterns.offsetUnit.test(g)){return parseFloat(g);}var d=this.patterns.offsetAttribute.exec(c)||[];var h=!!(d[3]);var f=!!(d[2]);if("style" in e){if(f||(b.Dom.getStyle(e,"position")=="absolute"&&h)){g=e["offset"+d[0].charAt(0).toUpperCase()+d[0].substr(1)];}else{g=0;}}else{if(c in e){g=e[c];}}return g;},getDefaultUnit:function(c){if(this.patterns.defaultUnit.test(c)){return"px";}return"";},setRuntimeAttribute:function(d){var j;var e;var f=this.attributes;this.runtimeAttributes[d]={};var h=function(i){return(typeof i!=="undefined");};if(!h(f[d]["to"])&&!h(f[d]["by"])){return false;}j=(h(f[d]["from"]))?f[d]["from"]:this.getAttribute(d);if(h(f[d]["to"])){e=f[d]["to"];}else{if(h(f[d]["by"])){if(j.constructor==Array){e=[];for(var g=0,c=j.length;g<c;++g){e[g]=j[g]+f[d]["by"][g]*1;}}else{e=j+f[d]["by"]*1;}}}this.runtimeAttributes[d].start=j;this.runtimeAttributes[d].end=e;this.runtimeAttributes[d].unit=(h(f[d].unit))?f[d]["unit"]:this.getDefaultUnit(d);return true;},init:function(f,c,h,i){var d=false;var e=null;var g=0;f=b.Dom.get(f);this.attributes=c||{};this.duration=!YAHOO.lang.isUndefined(h)?h:1;this.method=i||b.Easing.easeNone;this.useSeconds=true;this.currentFrame=0;this.totalFrames=b.AnimMgr.fps;this.setEl=function(j){f=b.Dom.get(j);};this.getEl=function(){return f;};this.isAnimated=function(){return d;};this.getStartTime=function(){return e;};this.runtimeAttributes={};this.animate=function(){if(this.isAnimated()){return false;}this.currentFrame=0;this.totalFrames=(this.useSeconds)?Math.ceil(b.AnimMgr.fps*this.duration):this.duration;if(this.duration===0&&this.useSeconds){this.totalFrames=1;}b.AnimMgr.registerElement(this);return true;};this.stop=function(j){if(!this.isAnimated()){return false;}if(j){this.currentFrame=this.totalFrames;this._onTween.fire();}b.AnimMgr.stop(this);};this._handleStart=function(){this.onStart.fire();this.runtimeAttributes={};for(var j in this.attributes){if(this.attributes.hasOwnProperty(j)){this.setRuntimeAttribute(j);}}d=true;g=0;e=new Date();};this._handleTween=function(){var l={duration:new Date()-this.getStartTime(),currentFrame:this.currentFrame};l.toString=function(){return("duration: "+l.duration+", currentFrame: "+l.currentFrame);};this.onTween.fire(l);var k=this.runtimeAttributes;for(var j in k){if(k.hasOwnProperty(j)){this.setAttribute(j,this.doMethod(j,k[j].start,k[j].end),k[j].unit);}}this.afterTween.fire(l);g+=1;};this._handleComplete=function(){var j=(new Date()-e)/1000;var k={duration:j,frames:g,fps:g/j};k.toString=function(){return("duration: "+k.duration+", frames: "+k.frames+", fps: "+k.fps);};d=false;g=0;this.onComplete.fire(k);};this._onStart=new b.CustomEvent("_start",this,true);this.onStart=new b.CustomEvent("start",this);this.onTween=new b.CustomEvent("tween",this);this.afterTween=new b.CustomEvent("afterTween",this);this._onTween=new b.CustomEvent("_tween",this,true);this.onComplete=new b.CustomEvent("complete",this);this._onComplete=new b.CustomEvent("_complete",this,true);this._onStart.subscribe(this._handleStart);this._onTween.subscribe(this._handleTween);this._onComplete.subscribe(this._handleComplete);}};b.Anim=a;})();YAHOO.util.AnimMgr=new function(){var e=null;var c=[];var g=0;this.fps=1000;this.delay=20;this.registerElement=function(j){c[c.length]=j;g+=1;j._onStart.fire();this.start();};var f=[];var d=false;var h=function(){var j=f.shift();b.apply(YAHOO.util.AnimMgr,j);if(f.length){arguments.callee();}};var b=function(k,j){j=j||a(k);if(!k.isAnimated()||j===-1){return false;}k._onComplete.fire();c.splice(j,1);g-=1;if(g<=0){this.stop();}return true;};this.unRegister=function(){f.push(arguments);if(!d){d=true;h();d=false;}};this.start=function(){if(e===null){e=setInterval(this.run,this.delay);}};this.stop=function(l){if(!l){clearInterval(e);for(var k=0,j=c.length;k<j;++k){this.unRegister(c[0],0);}c=[];e=null;g=0;}else{this.unRegister(l);}};this.run=function(){for(var l=0,j=c.length;l<j;++l){var k=c[l];if(!k||!k.isAnimated()){continue;}if(k.currentFrame<k.totalFrames||k.totalFrames===null){k.currentFrame+=1;if(k.useSeconds){i(k);}k._onTween.fire();}else{YAHOO.util.AnimMgr.stop(k,l);}}};var a=function(l){for(var k=0,j=c.length;k<j;++k){if(c[k]===l){return k;}}return -1;};var i=function(k){var n=k.totalFrames;var m=k.currentFrame;var l=(k.currentFrame*k.duration*1000/k.totalFrames);var j=(new Date()-k.getStartTime());var o=0;if(j<k.duration*1000){o=Math.round((j/l-1)*k.currentFrame);}else{o=n-(m+1);}if(o>0&&isFinite(o)){if(k.currentFrame+o>=n){o=n-(m+1);}k.currentFrame+=o;}};this._queue=c;this._getIndex=a;};YAHOO.util.Bezier=new function(){this.getPosition=function(e,d){var f=e.length;var c=[];for(var b=0;b<f;++b){c[b]=[e[b][0],e[b][1]];}for(var a=1;a<f;++a){for(b=0;b<f-a;++b){c[b][0]=(1-d)*c[b][0]+d*c[parseInt(b+1,10)][0];c[b][1]=(1-d)*c[b][1]+d*c[parseInt(b+1,10)][1];}}return[c[0][0],c[0][1]];};};(function(){var a=function(f,e,g,h){a.superclass.constructor.call(this,f,e,g,h);};a.NAME="ColorAnim";a.DEFAULT_BGCOLOR="#fff";var c=YAHOO.util;YAHOO.extend(a,c.Anim);var d=a.superclass;var b=a.prototype;b.patterns.color=/color$/i;b.patterns.rgb=/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i;b.patterns.hex=/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i;b.patterns.hex3=/^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i;
+b.patterns.transparent=/^transparent|rgba\(0, 0, 0, 0\)$/;b.parseColor=function(e){if(e.length==3){return e;}var f=this.patterns.hex.exec(e);if(f&&f.length==4){return[parseInt(f[1],16),parseInt(f[2],16),parseInt(f[3],16)];}f=this.patterns.rgb.exec(e);if(f&&f.length==4){return[parseInt(f[1],10),parseInt(f[2],10),parseInt(f[3],10)];}f=this.patterns.hex3.exec(e);if(f&&f.length==4){return[parseInt(f[1]+f[1],16),parseInt(f[2]+f[2],16),parseInt(f[3]+f[3],16)];}return null;};b.getAttribute=function(e){var g=this.getEl();if(this.patterns.color.test(e)){var i=YAHOO.util.Dom.getStyle(g,e);var h=this;if(this.patterns.transparent.test(i)){var f=YAHOO.util.Dom.getAncestorBy(g,function(j){return !h.patterns.transparent.test(i);});if(f){i=c.Dom.getStyle(f,e);}else{i=a.DEFAULT_BGCOLOR;}}}else{i=d.getAttribute.call(this,e);}return i;};b.doMethod=function(f,k,g){var j;if(this.patterns.color.test(f)){j=[];for(var h=0,e=k.length;h<e;++h){j[h]=d.doMethod.call(this,f,k[h],g[h]);}j="rgb("+Math.floor(j[0])+","+Math.floor(j[1])+","+Math.floor(j[2])+")";}else{j=d.doMethod.call(this,f,k,g);}return j;};b.setRuntimeAttribute=function(f){d.setRuntimeAttribute.call(this,f);if(this.patterns.color.test(f)){var h=this.attributes;var k=this.parseColor(this.runtimeAttributes[f].start);var g=this.parseColor(this.runtimeAttributes[f].end);if(typeof h[f]["to"]==="undefined"&&typeof h[f]["by"]!=="undefined"){g=this.parseColor(h[f].by);for(var j=0,e=k.length;j<e;++j){g[j]=k[j]+g[j];}}this.runtimeAttributes[f].start=k;this.runtimeAttributes[f].end=g;}};c.ColorAnim=a;})();
+/*!
+TERMS OF USE - EASING EQUATIONS
+Open source under the BSD License.
+Copyright 2001 Robert Penner All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+YAHOO.util.Easing={easeNone:function(e,a,g,f){return g*e/f+a;},easeIn:function(e,a,g,f){return g*(e/=f)*e+a;},easeOut:function(e,a,g,f){return -g*(e/=f)*(e-2)+a;},easeBoth:function(e,a,g,f){if((e/=f/2)<1){return g/2*e*e+a;}return -g/2*((--e)*(e-2)-1)+a;},easeInStrong:function(e,a,g,f){return g*(e/=f)*e*e*e+a;},easeOutStrong:function(e,a,g,f){return -g*((e=e/f-1)*e*e*e-1)+a;},easeBothStrong:function(e,a,g,f){if((e/=f/2)<1){return g/2*e*e*e*e+a;}return -g/2*((e-=2)*e*e*e-2)+a;},elasticIn:function(g,e,k,j,f,i){if(g==0){return e;}if((g/=j)==1){return e+k;}if(!i){i=j*0.3;}if(!f||f<Math.abs(k)){f=k;var h=i/4;}else{var h=i/(2*Math.PI)*Math.asin(k/f);}return -(f*Math.pow(2,10*(g-=1))*Math.sin((g*j-h)*(2*Math.PI)/i))+e;},elasticOut:function(g,e,k,j,f,i){if(g==0){return e;}if((g/=j)==1){return e+k;}if(!i){i=j*0.3;}if(!f||f<Math.abs(k)){f=k;var h=i/4;}else{var h=i/(2*Math.PI)*Math.asin(k/f);}return f*Math.pow(2,-10*g)*Math.sin((g*j-h)*(2*Math.PI)/i)+k+e;},elasticBoth:function(g,e,k,j,f,i){if(g==0){return e;}if((g/=j/2)==2){return e+k;}if(!i){i=j*(0.3*1.5);}if(!f||f<Math.abs(k)){f=k;var h=i/4;}else{var h=i/(2*Math.PI)*Math.asin(k/f);}if(g<1){return -0.5*(f*Math.pow(2,10*(g-=1))*Math.sin((g*j-h)*(2*Math.PI)/i))+e;}return f*Math.pow(2,-10*(g-=1))*Math.sin((g*j-h)*(2*Math.PI)/i)*0.5+k+e;},backIn:function(e,a,h,g,f){if(typeof f=="undefined"){f=1.70158;}return h*(e/=g)*e*((f+1)*e-f)+a;},backOut:function(e,a,h,g,f){if(typeof f=="undefined"){f=1.70158;}return h*((e=e/g-1)*e*((f+1)*e+f)+1)+a;},backBoth:function(e,a,h,g,f){if(typeof f=="undefined"){f=1.70158;}if((e/=g/2)<1){return h/2*(e*e*(((f*=(1.525))+1)*e-f))+a;}return h/2*((e-=2)*e*(((f*=(1.525))+1)*e+f)+2)+a;},bounceIn:function(e,a,g,f){return g-YAHOO.util.Easing.bounceOut(f-e,0,g,f)+a;},bounceOut:function(e,a,g,f){if((e/=f)<(1/2.75)){return g*(7.5625*e*e)+a;}else{if(e<(2/2.75)){return g*(7.5625*(e-=(1.5/2.75))*e+0.75)+a;}else{if(e<(2.5/2.75)){return g*(7.5625*(e-=(2.25/2.75))*e+0.9375)+a;}}}return g*(7.5625*(e-=(2.625/2.75))*e+0.984375)+a;},bounceBoth:function(e,a,g,f){if(e<f/2){return YAHOO.util.Easing.bounceIn(e*2,0,g,f)*0.5+a;}return YAHOO.util.Easing.bounceOut(e*2-f,0,g,f)*0.5+g*0.5+a;}};(function(){var a=function(h,g,i,j){if(h){a.superclass.constructor.call(this,h,g,i,j);}};a.NAME="Motion";var e=YAHOO.util;YAHOO.extend(a,e.ColorAnim);var f=a.superclass;var c=a.prototype;c.patterns.points=/^points$/i;c.setAttribute=function(g,i,h){if(this.patterns.points.test(g)){h=h||"px";f.setAttribute.call(this,"left",i[0],h);f.setAttribute.call(this,"top",i[1],h);}else{f.setAttribute.call(this,g,i,h);}};c.getAttribute=function(g){if(this.patterns.points.test(g)){var h=[f.getAttribute.call(this,"left"),f.getAttribute.call(this,"top")];}else{h=f.getAttribute.call(this,g);}return h;};c.doMethod=function(g,k,h){var j=null;if(this.patterns.points.test(g)){var i=this.method(this.currentFrame,0,100,this.totalFrames)/100;j=e.Bezier.getPosition(this.runtimeAttributes[g],i);
+}else{j=f.doMethod.call(this,g,k,h);}return j;};c.setRuntimeAttribute=function(q){if(this.patterns.points.test(q)){var h=this.getEl();var k=this.attributes;var g;var m=k["points"]["control"]||[];var j;var n,p;if(m.length>0&&!(m[0] instanceof Array)){m=[m];}else{var l=[];for(n=0,p=m.length;n<p;++n){l[n]=m[n];}m=l;}if(e.Dom.getStyle(h,"position")=="static"){e.Dom.setStyle(h,"position","relative");}if(d(k["points"]["from"])){e.Dom.setXY(h,k["points"]["from"]);}else{e.Dom.setXY(h,e.Dom.getXY(h));}g=this.getAttribute("points");if(d(k["points"]["to"])){j=b.call(this,k["points"]["to"],g);var o=e.Dom.getXY(this.getEl());for(n=0,p=m.length;n<p;++n){m[n]=b.call(this,m[n],g);}}else{if(d(k["points"]["by"])){j=[g[0]+k["points"]["by"][0],g[1]+k["points"]["by"][1]];for(n=0,p=m.length;n<p;++n){m[n]=[g[0]+m[n][0],g[1]+m[n][1]];}}}this.runtimeAttributes[q]=[g];if(m.length>0){this.runtimeAttributes[q]=this.runtimeAttributes[q].concat(m);}this.runtimeAttributes[q][this.runtimeAttributes[q].length]=j;}else{f.setRuntimeAttribute.call(this,q);}};var b=function(g,i){var h=e.Dom.getXY(this.getEl());g=[g[0]-h[0]+i[0],g[1]-h[1]+i[1]];return g;};var d=function(g){return(typeof g!=="undefined");};e.Motion=a;})();(function(){var d=function(f,e,g,h){if(f){d.superclass.constructor.call(this,f,e,g,h);}};d.NAME="Scroll";var b=YAHOO.util;YAHOO.extend(d,b.ColorAnim);var c=d.superclass;var a=d.prototype;a.doMethod=function(e,h,f){var g=null;if(e=="scroll"){g=[this.method(this.currentFrame,h[0],f[0]-h[0],this.totalFrames),this.method(this.currentFrame,h[1],f[1]-h[1],this.totalFrames)];}else{g=c.doMethod.call(this,e,h,f);}return g;};a.getAttribute=function(e){var g=null;var f=this.getEl();if(e=="scroll"){g=[f.scrollLeft,f.scrollTop];}else{g=c.getAttribute.call(this,e);}return g;};a.setAttribute=function(e,h,g){var f=this.getEl();if(e=="scroll"){f.scrollLeft=h[0];f.scrollTop=h[1];}else{c.setAttribute.call(this,e,h,g);}};b.Scroll=d;})();YAHOO.register("animation",YAHOO.util.Anim,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/ajax-loader.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/ajax-loader.gif
new file mode 100644
index 0000000..fe2cd23
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/ajax-loader.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/asc.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/asc.gif
new file mode 100644
index 0000000..a1fe738
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/asc.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/autocomplete.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/autocomplete.css
new file mode 100644
index 0000000..54541c5
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/autocomplete.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-skin-sam .yui-ac{position:relative;font-family:arial;font-size:100%}.yui-skin-sam .yui-ac-input{position:absolute;width:100%}.yui-skin-sam .yui-ac-container{position:absolute;top:1.6em;width:100%}.yui-skin-sam .yui-ac-content{position:absolute;width:100%;border:1px solid #808080;background:#fff;overflow:hidden;z-index:9050}.yui-skin-sam .yui-ac-shadow{position:absolute;margin:.3em;width:100%;background:#000;-moz-opacity:.10;opacity:.10;filter:alpha(opacity=10);z-index:9049}.yui-skin-sam .yui-ac iframe{opacity:0;filter:alpha(opacity=0);padding-right:.3em;padding-bottom:.3em}.yui-skin-sam .yui-ac-content ul{margin:0;padding:0;width:100%}.yui-skin-sam .yui-ac-content li{margin:0;padding:2px 5px;cursor:default;white-space:nowrap;list-style:none;zoom:1}.yui-skin-sam .yui-ac-content li.yui-ac-prehighlight{background:#b3d4ff}.yui-skin-sam .yui-ac-content li.yui-ac-highlight{background:#426fd9;color:#FFF}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/back-h.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/back-h.png
new file mode 100644
index 0000000..5f69f4e
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/back-h.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/back-v.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/back-v.png
new file mode 100644
index 0000000..658574a
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/back-v.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bar-h.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bar-h.png
new file mode 100644
index 0000000..fea13b1
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bar-h.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bar-v.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bar-v.png
new file mode 100644
index 0000000..2efd664
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bar-v.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bg-h.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bg-h.gif
new file mode 100644
index 0000000..9962889
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bg-h.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bg-v.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bg-v.gif
new file mode 100644
index 0000000..8e287cd
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/bg-v.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/blankimage.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/blankimage.png
new file mode 100644
index 0000000..b87bb24
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/blankimage.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/button.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/button.css
new file mode 100644
index 0000000..c54fc72
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/button.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-button{display:-moz-inline-box;display:inline-block;vertical-align:text-bottom;}.yui-button .first-child{display:block;*display:inline-block;}.yui-button button,.yui-button a{display:block;*display:inline-block;border:none;margin:0;}.yui-button button{background-color:transparent;*overflow:visible;cursor:pointer;}.yui-button a{text-decoration:none;}.yui-skin-sam .yui-button{border-width:1px 0;border-style:solid;border-color:#808080;background:url(sprite.png) repeat-x 0 0;margin:auto .25em;}.yui-skin-sam .yui-button .first-child{border-width:0 1px;border-style:solid;border-color:#808080;margin:0 -1px;_margin:0;}.yui-skin-sam .yui-button button,.yui-skin-sam .yui-button a,.yui-skin-sam .yui-button a:visited{padding:0 10px;font-size:93%;line-height:2;*line-height:1.7;min-height:2em;*min-height:auto;color:#000;}.yui-skin-sam .yui-button a{*line-height:1.875;*padding-bottom:1px;}.yui-skin-sam .yui-split-button button,.yui-skin-sam .yui-menu-button button{padding-right:20px;background-position:right center;background-repeat:no-repeat;}.yui-skin-sam .yui-menu-button button{background-image:url(menu-button-arrow.png);}.yui-skin-sam .yui-split-button button{background-image:url(split-button-arrow.png);}.yui-skin-sam .yui-button-focus{border-color:#7D98B8;background-position:0 -1300px;}.yui-skin-sam .yui-button-focus .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-split-button-focus button{background-image:url(split-button-arrow-focus.png);}.yui-skin-sam .yui-button-hover{border-color:#7D98B8;background-position:0 -1300px;}.yui-skin-sam .yui-button-hover .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-split-button-hover button{background-image:url(split-button-arrow-hover.png);}.yui-skin-sam .yui-button-active{border-color:#7D98B8;background-position:0 -1700px;}.yui-skin-sam .yui-button-active .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-split-button-activeoption{border-color:#808080;background-position:0 0;}.yui-skin-sam .yui-split-button-activeoption .first-child{border-color:#808080;}.yui-skin-sam .yui-split-button-activeoption button{background-image:url(split-button-arrow-active.png);}.yui-skin-sam .yui-radio-button-checked,.yui-skin-sam .yui-checkbox-button-checked{border-color:#304369;background-position:0 -1400px;}.yui-skin-sam .yui-radio-button-checked .first-child,.yui-skin-sam .yui-checkbox-button-checked .first-child{border-color:#304369;}.yui-skin-sam .yui-radio-button-checked button,.yui-skin-sam .yui-checkbox-button-checked button{color:#fff;}.yui-skin-sam .yui-button-disabled{border-color:#ccc;background-position:0 -1500px;}.yui-skin-sam .yui-button-disabled .first-child{border-color:#ccc;}.yui-skin-sam .yui-button-disabled button,.yui-skin-sam .yui-button-disabled a,.yui-skin-sam .yui-button-disabled a:visited{color:#A6A6A6;cursor:default;}.yui-skin-sam .yui-menu-button-disabled button{background-image:url(menu-button-arrow-disabled.png);}.yui-skin-sam .yui-split-button-disabled button{background-image:url(split-button-arrow-disabled.png);}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/calendar.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/calendar.css
new file mode 100644
index 0000000..df1d705
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/calendar.css
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-calcontainer{position:relative;float:left;_overflow:hidden}.yui-calcontainer iframe{position:absolute;border:0;margin:0;padding:0;z-index:0;width:100%;height:100%;left:0;top:0}.yui-calcontainer iframe.fixedsize{width:50em;height:50em;top:-1px;left:-1px}.yui-calcontainer.multi .groupcal{z-index:1;float:left;position:relative}.yui-calcontainer .title{position:relative;z-index:1}.yui-calcontainer .close-icon{position:absolute;z-index:1;text-indent:-10000em;overflow:hidden}.yui-calendar{position:relative}.yui-calendar .calnavleft{position:absolute;z-index:1;text-indent:-10000em;overflow:hidden}.yui-calendar .calnavright{position:absolute;z-index:1;text-indent:-10000em;overflow:hidden}.yui-calendar .calheader{position:relative;width:100%;text-align:center}.yui-calcontainer .yui-cal-nav-mask{position:absolute;z-index:2;margin:0;padding:0;width:100%;height:100%;_width:0;_height:0;left:0;top:0;display:none}.yui-calcontainer .yui-cal-nav{position:absolute;z-index:3;top:0;display:none}.yui-calcontainer .yui-cal-nav .yui-cal-nav-btn{display:-moz-inline-box;display:inline-block}.yui-calcontainer .yui-cal-nav .yui-cal-nav-btn button{display:block;*display:inline-block;*overflow:visible;border:0;background-color:transparent;cursor:pointer}.yui-calendar .calbody a:hover{background:inherit}p#clear{clear:left;padding-top:10px}.yui-skin-sam .yui-calcontainer{background-color:#f2f2f2;border:1px solid #808080;padding:10px}.yui-skin-sam .yui-calcontainer.multi{padding:0 5px 0 5px}.yui-skin-sam .yui-calcontainer.multi .groupcal{background-color:transparent;border:0;padding:10px 5px 10px 5px;margin:0}.yui-skin-sam .yui-calcontainer .title{background:url(sprite.png) repeat-x 0 0;border-bottom:1px solid #ccc;font:100% sans-serif;color:#000;font-weight:bold;height:auto;padding:.4em;margin:0 -10px 10px -10px;top:0;left:0;text-align:left}.yui-skin-sam .yui-calcontainer.multi .title{margin:0 -5px 0 -5px}.yui-skin-sam .yui-calcontainer.withtitle{padding-top:0}.yui-skin-sam .yui-calcontainer .calclose{background:url(sprite.png) no-repeat 0 -300px;width:25px;height:15px;top:.4em;right:.4em;cursor:pointer}.yui-skin-sam .yui-calendar{border-spacing:0;border-collapse:collapse;font:100% sans-serif;text-align:center;margin:0}.yui-skin-sam .yui-calendar .calhead{background:transparent;border:0;vertical-align:middle;padding:0}.yui-skin-sam .yui-calendar .calheader{background:transparent;font-weight:bold;padding:0 0 .6em 0;text-align:center}.yui-skin-sam .yui-calendar .calheader img{border:0}.yui-skin-sam .yui-calendar .calnavleft{background:url(sprite.png) no-repeat 0 -450px;width:25px;height:15px;top:0;bottom:0;left:-10px;margin-left:.4em;cursor:pointer}.yui-skin-sam .yui-calendar .calnavright{background:url(sprite.png) no-repeat 0 -500px;width:25px;height:15px;top:0;bottom:0;right:-10px;margin-right:.4em;cursor:pointer}.yui-skin-sam .yui-calendar .calweekdayrow{height:2em}.yui-skin-sam .yui-calendar .calweekdayrow th{padding:0;border:0}.yui-skin-sam .yui-calendar .calweekdaycell{color:#000;font-weight:bold;text-align:center;width:2em}.yui-skin-sam .yui-calendar .calfoot{background-color:#f2f2f2}.yui-skin-sam .yui-calendar .calrowhead,.yui-skin-sam .yui-calendar .calrowfoot{color:#a6a6a6;font-size:85%;font-style:normal;font-weight:normal;border:0}.yui-skin-sam .yui-calendar .calrowhead{text-align:right;padding:0 2px 0 0}.yui-skin-sam .yui-calendar .calrowfoot{text-align:left;padding:0 0 0 2px}.yui-skin-sam .yui-calendar td.calcell{border:1px solid #ccc;background:#fff;padding:1px;height:1.6em;line-height:1.6em;text-align:center;white-space:nowrap}.yui-skin-sam .yui-calendar td.calcell a{color:#06c;display:block;height:100%;text-decoration:none}.yui-skin-sam .yui-calendar td.calcell.today{background-color:#000}.yui-skin-sam .yui-calendar td.calcell.today a{background-color:#fff}.yui-skin-sam .yui-calendar td.calcell.oom{background-color:#ccc;color:#a6a6a6;cursor:default}.yui-skin-sam .yui-calendar td.calcell.oom a{color:#a6a6a6}.yui-skin-sam .yui-calendar td.calcell.selected{background-color:#fff;color:#000}.yui-skin-sam .yui-calendar td.calcell.selected a{background-color:#b3d4ff;color:#000}.yui-skin-sam .yui-calendar td.calcell.calcellhover{background-color:#426fd9;color:#fff;cursor:pointer}.yui-skin-sam .yui-calendar td.calcell.calcellhover a{background-color:#426fd9;color:#fff}.yui-skin-sam .yui-calendar td.calcell.previous{color:#e0e0e0}.yui-skin-sam .yui-calendar td.calcell.restricted{text-decoration:line-through}.yui-skin-sam .yui-calendar td.calcell.highlight1{background-color:#cf9}.yui-skin-sam .yui-calendar td.calcell.highlight2{background-color:#9cf}.yui-skin-sam .yui-calendar td.calcell.highlight3{background-color:#fcc}.yui-skin-sam .yui-calendar td.calcell.highlight4{background-color:#cf9}.yui-skin-sam .yui-calendar a.calnav{border:1px solid #f2f2f2;padding:0 4px;text-decoration:none;color:#000;zoom:1}.yui-skin-sam .yui-calendar a.calnav:hover{background:url(sprite.png) repeat-x 0 0;border-color:#a0a0a0;cursor:pointer}.yui-skin-sam .yui-calcontainer .yui-cal-nav-mask{background-color:#000;opacity:.25;filter:alpha(opacity=25)}.yui-skin-sam .yui-calcontainer .yui-cal-nav{font-family:arial,helvetica,clean,sans-serif;font-size:93%;border:1px solid #808080;left:50%;margin-left:-7em;width:14em;padding:0;top:2.5em;background-color:#f2f2f2}.yui-skin-sam .yui-calcontainer.withtitle .yui-cal-nav{top:4.5em}.yui-skin-sam .yui-calcontainer.multi .yui-cal-nav{width:16em;margin-left:-8em}.yui-skin-sam .yui-calcontainer .yui-cal-nav-y,.yui-skin-sam .yui-calcontainer .yui-cal-nav-m,.yui-skin-sam .yui-calcontainer .yui-cal-nav-b{padding:5px 10px 5px 10px}.yui-skin-sam .yui-calcontainer .yui-cal-nav-b{text-align:center}.yui-skin-sam .yui-calcontainer .yui-cal-nav-e{margin-top:5px;padding:5px;background-color:#edf5ff;border-top:1px solid black;display:none}.yui-skin-sam .yui-calcontainer .yui-cal-nav label{display:block;font-weight:bold}
+.yui-skin-sam .yui-calcontainer .yui-cal-nav-mc{width:100%;_width:auto}.yui-skin-sam .yui-calcontainer .yui-cal-nav-y input.yui-invalid{background-color:#ffee69;border:1px solid #000}.yui-skin-sam .yui-calcontainer .yui-cal-nav-yc{width:4em}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn{border:1px solid #808080;background:url(sprite.png) repeat-x 0 0;background-color:#ccc;margin:auto .15em}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn button{padding:0 8px;font-size:93%;line-height:2;*line-height:1.7;min-height:2em;*min-height:auto;color:#000}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn.yui-default{border:1px solid #304369;background-color:#426fd9;background:url(sprite.png) repeat-x 0 -1400px}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn.yui-default button{color:#fff}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/carousel.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/carousel.css
new file mode 100644
index 0000000..b3d86a6
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/carousel.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-carousel{visibility:hidden;overflow:hidden;position:relative;text-align:left;zoom:1;}.yui-carousel.yui-carousel-visible{visibility:visible;}.yui-carousel-content{overflow:hidden;position:relative;text-align:center;}.yui-carousel-element li{border:1px solid #ccc;list-style:none;margin:1px;overflow:hidden;padding:0;position:absolute;text-align:center;}.yui-carousel-vertical .yui-carousel-element li{display:block;float:none;}.yui-log .carousel{background:#f2e886;}.yui-carousel-nav{zoom:1;}.yui-carousel-nav:after{content:".";display:block;height:0;clear:both;visibility:hidden;}.yui-carousel-button-focus{outline:1px dotted #000;}.yui-carousel-min-width{min-width:115px;}.yui-carousel-element{overflow:hidden;position:relative;margin:0 auto;padding:0;text-align:left;*margin:0;}.yui-carousel-horizontal .yui-carousel-element{width:320000px;}.yui-carousel-vertical .yui-carousel-element{height:320000px;}.yui-skin-sam .yui-carousel-nav select{position:static;}.yui-carousel .yui-carousel-item-selected{border:1px dashed #000;margin:1px;}.yui-skin-sam .yui-carousel,.yui-skin-sam .yui-carousel-vertical{border:1px solid #808080;}.yui-skin-sam .yui-carousel-nav{background:url(sprite.png) repeat-x 0 0;padding:3px;text-align:right;}.yui-skin-sam .yui-carousel-button{background:url(sprite.png) no-repeat 0 -600px;float:right;height:19px;margin:5px;overflow:hidden;width:40px;}.yui-skin-sam .yui-carousel-vertical .yui-carousel-button{background-position:0 -800px;}.yui-skin-sam .yui-carousel-button-disabled{background-position:0 -2000px;}.yui-skin-sam .yui-carousel-vertical .yui-carousel-button-disabled{background-position:0 -2100px;}.yui-skin-sam .yui-carousel-button input,.yui-skin-sam .yui-carousel-button button{background-color:transparent;border:0;cursor:pointer;display:block;height:44px;margin:-2px 0 0 -2px;padding:0 0 0 50px;}.yui-skin-sam span.yui-carousel-first-button{background-position:0 -550px;margin-left:-100px;margin-right:50px;*margin:5px 5px 5px -90px;}.yui-skin-sam .yui-carousel-vertical span.yui-carousel-first-button{background-position:0 -750px;}.yui-skin-sam span.yui-carousel-first-button-disabled{background-position:0 -1950px;}.yui-skin-sam .yui-carousel-vertical span.yui-carousel-first-button-disabled{background-position:0 -2050px;}.yui-skin-sam .yui-carousel-nav ul{float:right;height:19px;margin:0;margin-left:-220px;margin-right:100px;*margin-left:-160px;*margin-right:0;padding:0;}.yui-skin-sam .yui-carousel-min-width .yui-carousel-nav ul{*margin-left:-170px;}.yui-skin-sam .yui-carousel-nav select{position:relative;*right:50px;top:4px;}.yui-skin-sam .yui-carousel-vertical .yui-carousel-nav select{position:static;}.yui-skin-sam .yui-carousel-vertical .yui-carousel-nav ul,.yui-skin-sam .yui-carousel-vertical .yui-carousel-nav select{float:none;margin:0;*zoom:1;}.yui-skin-sam .yui-carousel-nav ul li{background:url(sprite.png) no-repeat 0 -650px;cursor:pointer;float:left;height:9px;list-style:none;margin:10px 0 0 5px;overflow:hidden;padding:0;width:9px;}.yui-skin-sam .yui-carousel-nav ul:after{content:".";display:block;height:0;clear:both;visibility:hidden;}.yui-skin-sam .yui-carousel-nav ul li a{display:block;width:100%;height:100%;text-indent:-10000px;text-align:left;overflow:hidden;}.yui-skin-sam .yui-carousel-nav ul li.yui-carousel-nav-page-focus{outline:1px dotted #000;}.yui-skin-sam .yui-carousel-nav ul li.yui-carousel-nav-page-selected{background-position:0 -700px;}.yui-skin-sam .yui-carousel-item-loading{background:url(ajax-loader.gif) no-repeat 50% 50%;position:absolute;text-indent:-150px;}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/check0.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/check0.gif
new file mode 100644
index 0000000..193028b
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/check0.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/check1.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/check1.gif
new file mode 100644
index 0000000..7d9ceba
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/check1.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/check2.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/check2.gif
new file mode 100644
index 0000000..1813175
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/check2.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/colorpicker.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/colorpicker.css
new file mode 100644
index 0000000..77ac7c1
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/colorpicker.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-picker-panel{background:#e3e3e3;border-color:#888}.yui-picker-panel .hd{background-color:#ccc;font-size:100%;line-height:100%;border:1px solid #e3e3e3;font-weight:bold;overflow:hidden;padding:6px;color:#000}.yui-picker-panel .bd{background:#e8e8e8;margin:1px;height:200px}.yui-picker-panel .ft{background:#e8e8e8;margin:1px;padding:1px}.yui-picker{position:relative}.yui-picker-hue-thumb{cursor:default;width:18px;height:18px;top:-8px;left:-2px;z-index:9;position:absolute}.yui-picker-hue-bg{-moz-outline:0;outline:0 none;position:absolute;left:200px;height:183px;width:14px;background:url(hue_bg.png) no-repeat;top:4px}.yui-picker-bg{-moz-outline:0;outline:0 none;position:absolute;top:4px;left:4px;height:182px;width:182px;background-color:#F00;background-image:url(picker_mask.png)}*html .yui-picker-bg{background-image:none;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='picker_mask.png',sizingMethod='scale')}.yui-picker-mask{position:absolute;z-index:1;top:0;left:0}.yui-picker-thumb{cursor:default;width:11px;height:11px;z-index:9;position:absolute;top:-4px;left:-4px}.yui-picker-swatch{position:absolute;left:240px;top:4px;height:60px;width:55px;border:1px solid #888}.yui-picker-websafe-swatch{position:absolute;left:304px;top:4px;height:24px;width:24px;border:1px solid #888}.yui-picker-controls{position:absolute;top:72px;left:226px;font:1em monospace}.yui-picker-controls .hd{background:transparent;border-width:0!important}.yui-picker-controls .bd{height:100px;border-width:0!important}.yui-picker-controls ul{float:left;padding:0 2px 0 0;margin:0}.yui-picker-controls li{padding:2px;list-style:none;margin:0}.yui-picker-controls input{font-size:.85em;width:2.4em}.yui-picker-hex-controls{clear:both;padding:2px}.yui-picker-hex-controls input{width:4.6em}.yui-picker-controls a{font:1em arial,helvetica,clean,sans-serif;display:block;*display:inline-block;padding:0;color:#000}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/container.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/container.css
new file mode 100644
index 0000000..e4db6e3
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/container.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-overlay,.yui-panel-container{visibility:hidden;position:absolute;z-index:2}.yui-panel{position:relative}.yui-panel-container form{margin:0}.mask{z-index:1;display:none;position:absolute;top:0;left:0;right:0;bottom:0}.mask.block-scrollbars{overflow:auto}.masked select,.drag select,.hide-select select{_visibility:hidden}.yui-panel-container select{_visibility:inherit}.hide-scrollbars,.hide-scrollbars *{overflow:hidden}.hide-scrollbars select{display:none}.show-scrollbars{overflow:auto}.yui-panel-container.show-scrollbars,.yui-tt.show-scrollbars{overflow:visible}.yui-panel-container.show-scrollbars .underlay,.yui-tt.show-scrollbars .yui-tt-shadow{overflow:auto}.yui-panel-container.shadow .underlay.yui-force-redraw{padding-bottom:1px}.yui-effect-fade .underlay,.yui-effect-fade .yui-tt-shadow{display:none}.yui-tt-shadow{position:absolute}.yui-override-padding{padding:0!important}.yui-panel-container .container-close{overflow:hidden;text-indent:-10000em;text-decoration:none}.yui-overlay.yui-force-redraw,.yui-panel-container.yui-force-redraw{margin-bottom:1px}.yui-skin-sam .mask{background-color:#000;opacity:.25;filter:alpha(opacity=25)}.yui-skin-sam .yui-panel-container{padding:0 1px;*padding:2px}.yui-skin-sam .yui-panel{position:relative;left:0;top:0;border-style:solid;border-width:1px 0;border-color:#808080;z-index:1;*border-width:1px;*zoom:1;_zoom:normal}.yui-skin-sam .yui-panel .hd,.yui-skin-sam .yui-panel .bd,.yui-skin-sam .yui-panel .ft{border-style:solid;border-width:0 1px;border-color:#808080;margin:0 -1px;*margin:0;*border:0}.yui-skin-sam .yui-panel .hd{border-bottom:solid 1px #ccc}.yui-skin-sam .yui-panel .bd,.yui-skin-sam .yui-panel .ft{background-color:#f2f2f2}.yui-skin-sam .yui-panel .hd{padding:0 10px;font-size:93%;line-height:2;*line-height:1.9;font-weight:bold;color:#000;background:url(sprite.png) repeat-x 0 -200px}.yui-skin-sam .yui-panel .bd{padding:10px}.yui-skin-sam .yui-panel .ft{border-top:solid 1px #808080;padding:5px 10px;font-size:77%}.yui-skin-sam .container-close{position:absolute;top:5px;right:6px;width:25px;height:15px;background:url(sprite.png) no-repeat 0 -300px;cursor:pointer}.yui-skin-sam .yui-panel-container .underlay{right:-1px;left:-1px}.yui-skin-sam .yui-panel-container.matte{padding:9px 10px;background-color:#fff}.yui-skin-sam .yui-panel-container.shadow{_padding:2px 4px 0 2px}.yui-skin-sam .yui-panel-container.shadow .underlay{position:absolute;top:2px;left:-3px;right:-3px;bottom:-3px;*top:4px;*left:-1px;*right:-1px;*bottom:-1px;_top:0;_left:0;_right:0;_bottom:0;_margin-top:3px;_margin-left:-1px;background-color:#000;opacity:.12;filter:alpha(opacity=12)}.yui-skin-sam .yui-dialog .ft{border-top:0;padding:0 10px 10px 10px;font-size:100%}.yui-skin-sam .yui-dialog .ft .button-group{display:block;text-align:right}.yui-skin-sam .yui-dialog .ft button.default{font-weight:bold}.yui-skin-sam .yui-dialog .ft span.default{border-color:#304369;background-position:0 -1400px}.yui-skin-sam .yui-dialog .ft span.default .first-child{border-color:#304369}.yui-skin-sam .yui-dialog .ft span.default button{color:#fff}.yui-skin-sam .yui-dialog .ft span.yui-button-disabled{background-position:0 -1500px;border-color:#ccc}.yui-skin-sam .yui-dialog .ft span.yui-button-disabled .first-child{border-color:#ccc}.yui-skin-sam .yui-dialog .ft span.yui-button-disabled button{color:#a6a6a6}.yui-skin-sam .yui-simple-dialog .bd .yui-icon{background:url(sprite.png) no-repeat 0 0;width:16px;height:16px;margin-right:10px;float:left}.yui-skin-sam .yui-simple-dialog .bd span.blckicon{background-position:0 -1100px}.yui-skin-sam .yui-simple-dialog .bd span.alrticon{background-position:0 -1050px}.yui-skin-sam .yui-simple-dialog .bd span.hlpicon{background-position:0 -1150px}.yui-skin-sam .yui-simple-dialog .bd span.infoicon{background-position:0 -1200px}.yui-skin-sam .yui-simple-dialog .bd span.warnicon{background-position:0 -1900px}.yui-skin-sam .yui-simple-dialog .bd span.tipicon{background-position:0 -1250px}.yui-skin-sam .yui-tt .bd{position:relative;top:0;left:0;z-index:1;color:#000;padding:2px 5px;border-color:#d4c237 #A6982b #a6982b #A6982B;border-width:1px;border-style:solid;background-color:#ffee69}.yui-skin-sam .yui-tt.show-scrollbars .bd{overflow:auto}.yui-skin-sam .yui-tt-shadow{top:2px;right:-3px;left:-3px;bottom:-3px;background-color:#000}.yui-skin-sam .yui-tt-shadow-visible{opacity:.12;filter:alpha(opacity=12)}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/datatable.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/datatable.css
new file mode 100644
index 0000000..91d07c3
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/datatable.css
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-skin-sam .yui-dt-mask{position:absolute;z-index:9500}.yui-dt-tmp{position:absolute;left:-9000px}.yui-dt-scrollable .yui-dt-bd{overflow:auto}.yui-dt-scrollable .yui-dt-hd{overflow:hidden;position:relative}.yui-dt-scrollable .yui-dt-bd thead tr,.yui-dt-scrollable .yui-dt-bd thead th{position:absolute;left:-1500px}.yui-dt-scrollable tbody{-moz-outline:0}.yui-skin-sam thead .yui-dt-sortable{cursor:pointer}.yui-skin-sam thead .yui-dt-draggable{cursor:move}.yui-dt-coltarget{position:absolute;z-index:999}.yui-dt-hd{zoom:1}th.yui-dt-resizeable .yui-dt-resizerliner{position:relative}.yui-dt-resizer{position:absolute;right:0;bottom:0;height:100%;cursor:e-resize;cursor:col-resize;background-color:#CCC;opacity:0;filter:alpha(opacity=0)}.yui-dt-resizerproxy{visibility:hidden;position:absolute;z-index:9000;background-color:#CCC;opacity:0;filter:alpha(opacity=0)}th.yui-dt-hidden .yui-dt-liner,td.yui-dt-hidden .yui-dt-liner,th.yui-dt-hidden .yui-dt-resizer{display:none}.yui-dt-editor,.yui-dt-editor-shim{position:absolute;z-index:9000}.yui-skin-sam .yui-dt table{margin:0;padding:0;font-family:arial;font-size:inherit;border-collapse:separate;*border-collapse:collapse;border-spacing:0;border:1px solid #7f7f7f}.yui-skin-sam .yui-dt thead{border-spacing:0}.yui-skin-sam .yui-dt caption{color:#000;font-size:85%;font-weight:normal;font-style:italic;line-height:1;padding:1em 0;text-align:center}.yui-skin-sam .yui-dt th{background:#d8d8da url(sprite.png) repeat-x 0 0}.yui-skin-sam .yui-dt th,.yui-skin-sam .yui-dt th a{font-weight:normal;text-decoration:none;color:#000;vertical-align:bottom}.yui-skin-sam .yui-dt th{margin:0;padding:0;border:0;border-right:1px solid #cbcbcb}.yui-skin-sam .yui-dt tr.yui-dt-first td{border-top:1px solid #7f7f7f}.yui-skin-sam .yui-dt th .yui-dt-liner{white-space:nowrap}.yui-skin-sam .yui-dt-liner{margin:0;padding:0;padding:4px 10px 4px 10px}.yui-skin-sam .yui-dt-coltarget{width:5px;background-color:red}.yui-skin-sam .yui-dt td{margin:0;padding:0;border:0;border-right:1px solid #cbcbcb;text-align:left}.yui-skin-sam .yui-dt-list td{border-right:0}.yui-skin-sam .yui-dt-resizer{width:6px}.yui-skin-sam .yui-dt-mask{background-color:#000;opacity:.25;filter:alpha(opacity=25)}.yui-skin-sam .yui-dt-message{background-color:#FFF}.yui-skin-sam .yui-dt-scrollable table{border:0}.yui-skin-sam .yui-dt-scrollable .yui-dt-hd{border-left:1px solid #7f7f7f;border-top:1px solid #7f7f7f;border-right:1px solid #7f7f7f}.yui-skin-sam .yui-dt-scrollable .yui-dt-bd{border-left:1px solid #7f7f7f;border-bottom:1px solid #7f7f7f;border-right:1px solid #7f7f7f;background-color:#FFF}.yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td{border-bottom:1px solid #7f7f7f}.yui-skin-sam th.yui-dt-asc,.yui-skin-sam th.yui-dt-desc{background:url(sprite.png) repeat-x 0 -100px}.yui-skin-sam th.yui-dt-sortable .yui-dt-label{margin-right:10px}.yui-skin-sam th.yui-dt-asc .yui-dt-liner{background:url(dt-arrow-up.png) no-repeat right}.yui-skin-sam th.yui-dt-desc .yui-dt-liner{background:url(dt-arrow-dn.png) no-repeat right}tbody .yui-dt-editable{cursor:pointer}.yui-dt-editor{text-align:left;background-color:#f2f2f2;border:1px solid #808080;padding:6px}.yui-dt-editor label{padding-left:4px;padding-right:6px}.yui-dt-editor .yui-dt-button{padding-top:6px;text-align:right}.yui-dt-editor .yui-dt-button button{background:url(sprite.png) repeat-x 0 0;border:1px solid #999;width:4em;height:1.8em;margin-left:6px}.yui-dt-editor .yui-dt-button button.yui-dt-default{background:url(sprite.png) repeat-x 0 -1400px;background-color:#5584e0;border:1px solid #304369;color:#FFF}.yui-dt-editor .yui-dt-button button:hover{background:url(sprite.png) repeat-x 0 -1300px;color:#000}.yui-dt-editor .yui-dt-button button:active{background:url(sprite.png) repeat-x 0 -1700px;color:#000}.yui-skin-sam tr.yui-dt-even{background-color:#FFF}.yui-skin-sam tr.yui-dt-odd{background-color:#edf5ff}.yui-skin-sam tr.yui-dt-even td.yui-dt-asc,.yui-skin-sam tr.yui-dt-even td.yui-dt-desc{background-color:#edf5ff}.yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,.yui-skin-sam tr.yui-dt-odd td.yui-dt-desc{background-color:#dbeaff}.yui-skin-sam .yui-dt-list tr.yui-dt-even{background-color:#FFF}.yui-skin-sam .yui-dt-list tr.yui-dt-odd{background-color:#FFF}.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc{background-color:#edf5ff}.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc{background-color:#edf5ff}.yui-skin-sam th.yui-dt-highlighted,.yui-skin-sam th.yui-dt-highlighted a{background-color:#b2d2ff}.yui-skin-sam tr.yui-dt-highlighted,.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,.yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,.yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted{cursor:pointer;background-color:#b2d2ff}.yui-skin-sam .yui-dt-list th.yui-dt-highlighted,.yui-skin-sam .yui-dt-list th.yui-dt-highlighted a{background-color:#b2d2ff}.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted{cursor:pointer;background-color:#b2d2ff}.yui-skin-sam th.yui-dt-selected,.yui-skin-sam th.yui-dt-selected a{background-color:#446cd7}.yui-skin-sam tr.yui-dt-selected td,.yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,.yui-skin-sam tr.yui-dt-selected td.yui-dt-desc{background-color:#426fd9;color:#FFF}.yui-skin-sam tr.yui-dt-even td.yui-dt-selected,.yui-skin-sam tr.yui-dt-odd td.yui-dt-selected{background-color:#446cd7;color:#FFF}.yui-skin-sam .yui-dt-list th.yui-dt-selected,.yui-skin-sam .yui-dt-list th.yui-dt-selected a{background-color:#446cd7}
+.yui-skin-sam .yui-dt-list tr.yui-dt-selected td,.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc{background-color:#426fd9;color:#FFF}.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected{background-color:#446cd7;color:#FFF}.yui-skin-sam .yui-dt-paginator{display:block;margin:6px 0;white-space:nowrap}.yui-skin-sam .yui-dt-paginator .yui-dt-first,.yui-skin-sam .yui-dt-paginator .yui-dt-last,.yui-skin-sam .yui-dt-paginator .yui-dt-selected{padding:2px 6px}.yui-skin-sam .yui-dt-paginator a.yui-dt-first,.yui-skin-sam .yui-dt-paginator a.yui-dt-last{text-decoration:none}.yui-skin-sam .yui-dt-paginator .yui-dt-previous,.yui-skin-sam .yui-dt-paginator .yui-dt-next{display:none}.yui-skin-sam a.yui-dt-page{border:1px solid #cbcbcb;padding:2px 6px;text-decoration:none;background-color:#fff}.yui-skin-sam .yui-dt-selected{border:1px solid #fff;background-color:#fff}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/desc.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/desc.gif
new file mode 100644
index 0000000..c114f29
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/desc.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/dt-arrow-dn.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/dt-arrow-dn.png
new file mode 100644
index 0000000..85fda0b
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/dt-arrow-dn.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/dt-arrow-up.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/dt-arrow-up.png
new file mode 100644
index 0000000..1c67431
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/dt-arrow-up.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-knob.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-knob.gif
new file mode 100644
index 0000000..03feab3
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-knob.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-sprite-active.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-sprite-active.gif
new file mode 100644
index 0000000..3e9d420
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-sprite-active.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-sprite.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-sprite.gif
new file mode 100644
index 0000000..02042fa
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor-sprite.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor.css
new file mode 100644
index 0000000..d3cde1c
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/editor.css
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-busy{cursor:wait!important;}.yui-toolbar-container fieldset,.yui-editor-container fieldset{padding:0;margin:0;border:0;}.yui-toolbar-container legend{display:none;}.yui-skin-sam .yui-toolbar-container .yui-button button,.yui-skin-sam .yui-toolbar-container .yui-button a,.yui-skin-sam .yui-toolbar-container .yui-button a:visited{font-size:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select button,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a:visited,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton button,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a:visited{font-size:12px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{font-size:0;line-height:0;padding:0;}.yui-toolbar-container .yui-toolbar-subcont{padding:.25em 0;zoom:1;}.yui-toolbar-container-collapsed .yui-toolbar-subcont{display:none;}.yui-toolbar-container .yui-toolbar-subcont:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-container span.yui-toolbar-draghandle{cursor:move;border-left:1px solid #999;border-right:1px solid #999;overflow:hidden;text-indent:77777px;width:2px;height:20px;display:block;clear:none;float:left;margin:0 0 0 .2em;}.yui-toolbar-container .yui-toolbar-titlebar.draggable{cursor:move;}.yui-toolbar-container .yui-toolbar-titlebar{position:relative;}.yui-toolbar-container .yui-toolbar-titlebar h2{font-weight:bold;letter-spacing:0;border:none;color:#000;margin:0;padding:.2em;}.yui-toolbar-container .yui-toolbar-titlebar h2 a{text-decoration:none;color:#000;cursor:default;}.yui-toolbar-container.yui-toolbar-grouped span.yui-toolbar-draghandle{height:40px;}.yui-toolbar-container .yui-toolbar-group{float:left;margin-right:.5em;zoom:1;}.yui-toolbar-container .yui-toolbar-group:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-container .yui-toolbar-group h3{font-size:75%;padding:0 0 0 .25em;margin:0;}.yui-toolbar-container span.yui-toolbar-separator{width:2px;padding:0;height:18px;margin:.2em 0 .2em .1em;display:none;float:left;}.yui-toolbar-container.yui-toolbar-grouped span.yui-toolbar-separator{height:45px;*height:50px;}.yui-toolbar-container.yui-toolbar-grouped .yui-toolbar-group span.yui-toolbar-separator{height:18px;display:block;}.yui-toolbar-container ul li{margin:0;padding:0;list-style-type:none;}.yui-toolbar-container .yui-toolbar-nogrouplabels h3{display:none;}.yui-toolbar-container .yui-push-button,.yui-toolbar-container .yui-color-button,.yui-toolbar-container .yui-menu-button{position:relative;cursor:pointer;}.yui-toolbar-container .yui-button .first-child,.yui-toolbar-container .yui-button .first-child a{height:100%;width:100%;overflow:hidden;font-size:0;}.yui-toolbar-container .yui-button-disabled{cursor:default;}.yui-toolbar-container .yui-button-disabled .yui-toolbar-icon{opacity:.5;filter:alpha(opacity=50);}.yui-toolbar-container .yui-button-disabled .up,.yui-toolbar-container .yui-button-disabled .down{opacity:.5;filter:alpha(opacity=50);}.yui-toolbar-container .yui-button a{overflow:hidden;}.yui-toolbar-container .yui-toolbar-select .first-child a{cursor:pointer;}.yui-toolbar-fontname-arial{font-family:Arial;}.yui-toolbar-fontname-arial-black{font-family:Arial Black;}.yui-toolbar-fontname-comic-sans-ms{font-family:Comic Sans MS;}.yui-toolbar-fontname-courier-new{font-family:Courier New;}.yui-toolbar-fontname-times-new-roman{font-family:Times New Roman;}.yui-toolbar-fontname-verdana{font-family:Verdana;}.yui-toolbar-fontname-impact{font-family:Impact;}.yui-toolbar-fontname-lucida-console{font-family:Lucida Console;}.yui-toolbar-fontname-tahoma{font-family:Tahoma;}.yui-toolbar-fontname-trebuchet-ms{font-family:Trebuchet MS;}.yui-toolbar-container .yui-toolbar-spinbutton{position:relative;}.yui-toolbar-container .yui-toolbar-spinbutton .first-child a{z-index:0;opacity:1;}.yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-toolbar-container .yui-toolbar-spinbutton a.down{position:absolute;display:block;right:0;cursor:pointer;z-index:1;padding:0;margin:0;}.yui-toolbar-container .yui-overlay{position:absolute;}.yui-toolbar-container .yui-overlay ul li{margin:0;list-style-type:none;}.yui-toolbar-container{z-index:1;}.yui-editor-container .yui-editor-editable-container{position:relative;z-index:0;width:100%;}.yui-editor-container .yui-editor-masked{background-color:#CCC;height:100%;width:100%;position:absolute;top:0;left:0;opacity:.5;filter:alpha(opacity=50);}.yui-editor-container iframe{border:0;padding:0;margin:0;zoom:1;display:block;}.yui-editor-container .yui-editor-editable{padding:0;margin:0;}.yui-editor-container .dompath{font-size:85%;}.yui-editor-panel .hd{text-align:left;position:relative;}.yui-editor-panel .hd h3{font-weight:bold;padding:.25em 0 .25em .25em;margin:0;}.yui-editor-panel .bd{width:100%;zoom:1;position:relative;}.yui-editor-panel .bd div.yui-editor-body-cont{padding:.25em .1em;zoom:1;}.yui-editor-panel .bd .gecko form{overflow:auto;}.yui-editor-panel .bd div.yui-editor-body-cont:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-editor-panel .ft{text-align:right;width:99%;float:left;clear:both;}.yui-editor-panel .ft span.tip{display:block;position:relative;padding:.5em .5em .5em 23px;text-align:left;zoom:1;}.yui-editor-panel label{clear:both;float:left;padding:0;width:100%;text-align:left;zoom:1;}.yui-editor-panel .gecko label{overflow:auto;}.yui-editor-panel label strong{float:left;width:6em;}.yui-editor-panel .removeLink{width:80%;text-align:right;}.yui-editor-panel label input{margin-left:.25em;float:left;}.yui-editor-panel .yui-toolbar-group{margin-bottom:.75em;}.yui-editor-panel .height-width{float:left;}.yui-editor-panel .height-width span{font-style:italic;display:block;float:left;overflow:visible;}.yui-editor-panel .height-width span.info{font-size:70%;margin-top:3px;float:none;}
+.yui-editor-panel .yui-toolbar-bordersize,.yui-editor-panel .yui-toolbar-bordertype{font-size:75%;}.yui-editor-panel .yui-toolbar-container span.yui-toolbar-separator{border:none;}.yui-editor-panel .yui-toolbar-bordersize span a span,.yui-editor-panel .yui-toolbar-bordertype span a span{display:block;height:8px;left:4px;position:absolute;top:3px;_top:-5px;width:24px;text-indent:52px;font-size:0;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-solid{border-bottom:1px solid black;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-dotted{border-bottom:1px dotted black;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-dashed{border-bottom:1px dashed black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-0{*top:0;text-indent:0;font-size:75%;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-1{border-bottom:1px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-2{border-bottom:2px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-3{top:2px;*top:-5px;border-bottom:3px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-4{top:1px;*top:-5px;border-bottom:4px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-5{top:1px;*top:-5px;border-bottom:5px solid black;}.yui-toolbar-container .yui-toolbar-bordersize-menu,.yui-toolbar-container .yui-toolbar-bordertype-menu{width:95px!important;}.yui-toolbar-bordersize-menu .yuimenuitemlabel,.yui-toolbar-bordertype-menu .yuimenuitemlabel,.yui-toolbar-bordersize-menu .yuimenuitemlabel,.yui-toolbar-bordertype-menu .yuimenuitemlabel:hover{margin:0 3px 7px 17px;}.yui-toolbar-bordersize-menu .yuimenuitemlabel .checkedindicator,.yui-toolbar-bordertype-menu .yuimenuitemlabel .checkedindicator{position:absolute;left:-12px;*top:14px;*left:0;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-1 a{border-bottom:1px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-2 a{border-bottom:2px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-3 a{border-bottom:3px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-4 a{border-bottom:4px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-5 a{border-bottom:5px solid black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-solid a{border-bottom:1px solid black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-dashed a{border-bottom:1px dashed black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-dotted a{border-bottom:1px dotted black;height:14px;}h2.yui-editor-skipheader,h3.yui-editor-skipheader{height:0;margin:0;padding:0;border:none;width:0;overflow:hidden;position:absolute;}.yui-toolbar-colors{width:133px;zoom:1;display:none;z-index:100;overflow:hidden;}.yui-toolbar-colors:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-colors a{height:9px;width:9px;float:left;display:block;overflow:hidden;text-indent:999px;margin:0;cursor:pointer;border:1px solid #F6F7EE;}.yui-toolbar-colors a:hover{border:1px solid black;}.yui-color-button-menu{overflow:visible;background-color:transparent;}.yui-toolbar-colors span{position:relative;display:block;padding:3px;overflow:hidden;float:left;width:100%;zoom:1;}.yui-toolbar-colors span:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-colors span em{height:35px;width:30px;float:left;display:block;overflow:hidden;text-indent:999px;margin:.75px;border:1px solid black;}.yui-toolbar-colors span strong{font-weight:normal;padding-left:3px;display:block;font-size:85%;float:left;width:65%;}.yui-toolbar-group-undoredo h3,.yui-toolbar-group-insertitem h3,.yui-toolbar-group-indentlist h3{width:68px;}.yui-toolbar-group-indentlist2 h3{width:122px;}.yui-toolbar-group-alignment h3{width:130px;}.yui-skin-sam .yui-editor-container{border:1px solid #808080;}.yui-skin-sam .yui-toolbar-container{zoom:1;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar{background:url(sprite.png) repeat-x 0 -200px;position:relative;}.yui-skin-sam .yui-editor-container .draggable .yui-toolbar-titlebar{cursor:move;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar h2{color:#000;font-weight:bold;margin:0;padding:.3em 1em;font-size:100%;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-group h3{color:#808080;font-size:75%;margin:1em 0 0;padding-bottom:0;padding-left:.25em;text-align:left;}.yui-toolbar-container span.yui-toolbar-separator{border:none;text-indent:33px;overflow:hidden;margin:0 .25em;}.yui-skin-sam .yui-toolbar-container{background-color:#F2F2F2;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-subcont{padding:0 1em .35em;border-bottom:1px solid #808080;}.yui-skin-sam .yui-toolbar-container-collapsed .yui-toolbar-titlebar{border-bottom:1px solid #808080;}.yui-skin-sam .yui-editor-container .visible .yui-menu-shadow,.yui-skin-sam .yui-editor-panel .visible .yui-menu-shadow{display:none;}.yui-skin-sam .yui-editor-container ul{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-editor-container ul li{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-toolbar-group ul li.yui-toolbar-groupitem{float:left;}.yui-skin-sam .yui-editor-container .dompath{background-color:#F2F2F2;border-top:1px solid #808080;color:#999;text-align:left;padding:.25em;}.yui-skin-sam .yui-toolbar-container .collapse{background:url(sprite.png) no-repeat 0 -400px;}.yui-skin-sam .yui-toolbar-container .collapsed{background:url(sprite.png) no-repeat 0 -350px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar span.collapse{cursor:pointer;position:absolute;top:4px;right:2px;display:block;overflow:hidden;height:15px;width:15px;text-indent:9999px;}
+.yui-skin-sam .yui-toolbar-container .yui-push-button,.yui-skin-sam .yui-toolbar-container .yui-color-button,.yui-skin-sam .yui-toolbar-container .yui-menu-button{background:url(sprite.png) repeat-x 0 0;position:relative;display:block;height:22px;width:30px;_font-size:0;margin:0;border-color:#808080;color:#f2f2f2;border-style:solid;border-width:1px 0;zoom:1;}.yui-skin-sam .yui-toolbar-container .yui-push-button a,.yui-skin-sam .yui-toolbar-container .yui-color-button a,.yui-skin-sam .yui-toolbar-container .yui-menu-button a{padding-left:35px;height:20px;text-decoration:none;font-size:0;line-height:2;display:block;color:#000;overflow:hidden;white-space:nowrap;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a{font-size:12px;}.yui-skin-sam .yui-toolbar-container .yui-push-button .first-child,.yui-skin-sam .yui-toolbar-container .yui-color-button .first-child,.yui-skin-sam .yui-toolbar-container .yui-menu-button .first-child{border-color:#808080;border-style:solid;border-width:0 1px;margin:0 -1px;display:block;position:relative;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled .first-child,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled .first-child,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled .first-child{border-color:#ccc;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled a,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled a,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled a{color:#A6A6A6;cursor:default;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled{border-color:#ccc;}.yui-skin-sam .yui-toolbar-container .yui-button .first-child{*left:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-fontname{width:135px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-heading{width:92px;}.yui-skin-sam .yui-toolbar-container .yui-button-hover{background:url(sprite.png) repeat-x 0 -1300px;border-color:#808080;}.yui-skin-sam .yui-toolbar-container .yui-button-selected{background:url(sprite.png) repeat-x 0 -1700px;border-color:#808080;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-nogrouplabels h3{display:none;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-nogrouplabels .yui-toolbar-group{margin-top:.75em;}.yui-skin-sam .yui-toolbar-container .yui-push-button span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-color-button span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-menu-button span.yui-toolbar-icon{display:block;position:absolute;top:2px;height:18px;width:18px;overflow:hidden;background:url(editor-sprite.gif) no-repeat 30px 30px;}.yui-skin-sam .yui-toolbar-container .yui-button-selected span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-button-hover span.yui-toolbar-icon{background-image:url(editor-sprite-active.gif);}.yui-skin-sam .yui-toolbar-container .visible .yuimenuitemlabel{cursor:pointer;color:#000;*position:relative;}.yui-skin-sam .yui-toolbar-container .yui-button-menu{background-color:#fff;}.yui-skin-sam .yui-toolbar-container .yui-button-menu .yui-menu-body-scrolled{position:relative;}.yui-skin-sam div.yuimenu li.selected{background-color:#B3D4FF;}.yui-skin-sam div.yuimenu li.selected a.selected{color:#000;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-bold span.yui-toolbar-icon{background-position:0 0;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-strikethrough span.yui-toolbar-icon{background-position:0 -108px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-italic span.yui-toolbar-icon{background-position:0 -36px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-undo span.yui-toolbar-icon{background-position:0 -1326px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-redo span.yui-toolbar-icon{background-position:0 -1355px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-underline span.yui-toolbar-icon{background-position:0 -72px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-subscript span.yui-toolbar-icon{background-position:0 -180px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-superscript span.yui-toolbar-icon{background-position:0 -144px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-forecolor span.yui-toolbar-icon{background-position:0 -216px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-backcolor span.yui-toolbar-icon{background-position:0 -288px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyleft span.yui-toolbar-icon{background-position:0 -324px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifycenter span.yui-toolbar-icon{background-position:0 -360px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyright span.yui-toolbar-icon{background-position:0 -396px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyfull span.yui-toolbar-icon{background-position:0 -432px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-indent span.yui-toolbar-icon{background-position:0 -720px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-outdent span.yui-toolbar-icon{background-position:0 -684px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-createlink span.yui-toolbar-icon{background-position:0 -792px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertimage span.yui-toolbar-icon{background-position:1px -756px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-left span.yui-toolbar-icon{background-position:0 -972px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-right span.yui-toolbar-icon{background-position:0 -936px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-inline span.yui-toolbar-icon{background-position:0 -900px;left:5px;}
+.yui-skin-sam .yui-toolbar-container .yui-toolbar-block span.yui-toolbar-icon{background-position:0 -864px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-bordercolor span.yui-toolbar-icon{background-position:0 -252px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-removeformat span.yui-toolbar-icon{background-position:0 -1080px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-hiddenelements span.yui-toolbar-icon{background-position:0 -1044px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertunorderedlist span.yui-toolbar-icon{background-position:0 -468px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertorderedlist span.yui-toolbar-icon{background-position:0 -504px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton .first-child{width:35px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton .first-child a{padding-left:2px;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton span.yui-toolbar-icon{display:none;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{right:2px;background:url(editor-sprite.gif) no-repeat 0 -1222px;overflow:hidden;height:6px;width:7px;min-height:0;padding:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up{top:2px;background-position:0 -1222px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{bottom:2px;background-position:0 -1187px;}.yui-skin-sam .yui-toolbar-container select{height:22px;border:1px solid #808080;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select .first-child a{padding-left:5px;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select span.yui-toolbar-icon{background:url(editor-sprite.gif) no-repeat 0 -1144px;overflow:hidden;right:-2px;top:0;height:20px;}.yui-skin-sam .yui-editor-panel .yui-color-button-menu .bd{background-color:transparent;border:none;width:135px;}.yui-skin-sam .yui-color-button-menu .yui-toolbar-colors{border:1px solid #808080;}.yui-skin-sam .yui-editor-panel{padding:0;margin:0;border:none;background-color:transparent;overflow:visible;position:absolute;}.yui-skin-sam .yui-editor-panel .hd{margin:10px 0 0;padding:0;border:none;}.yui-skin-sam .yui-editor-panel .hd h3{color:#000;border:1px solid #808080;background:url(sprite.png) repeat-x 0 -200px;width:99%;position:relative;margin:0;padding:3px 0 0 0;font-size:93%;text-indent:5px;height:20px;}.yui-skin-sam .yui-editor-panel .bd{background-color:#F2F2F2;border-left:1px solid #808080;border-right:1px solid #808080;width:99%;margin:0;padding:0;overflow:visible;}.yui-skin-sam .yui-editor-panel ul{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-editor-panel ul li{margin:0;padding:0;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container .yui-toolbar-subcont{padding:0;border:none;margin-top:.35em;}.yui-skin-sam .yui-editor-panel .yui-toolbar-bordersize,.yui-skin-sam .yui-editor-panel .yui-toolbar-bordertype{width:50px;}.yui-skin-sam .yui-editor-panel label{display:block;float:none;padding:4px 0;margin-bottom:7px;}.yui-skin-sam .yui-editor-panel label strong{font-weight:normal;font-size:93%;text-align:right;padding-top:2px;}.yui-skin-sam .yui-editor-panel label input{width:75%;}.yui-skin-sam .yui-editor-panel .createlink_target,.yui-skin-sam .yui-editor-panel .insertimage_target{width:auto;margin-right:5px;}.yui-skin-sam .yui-editor-panel .removeLink{width:98%;}.yui-skin-sam .yui-editor-panel label input.warning{background-color:#FFEE69;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group h3{color:#000;float:left;font-weight:normal;font-size:93%;margin:5px 0 0 0;padding:0 3px 0 0;text-align:right;}.yui-skin-sam .yui-editor-panel .height-width h3{margin:3px 0 0 10px;}.yui-skin-sam .yui-editor-panel .height-width{margin:3px 0 0 35px;*margin-left:14px;width:42%;*width:44%;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-border{width:190px;}.yui-skin-sam .yui-editor-panel .no-button .yui-toolbar-group-border{width:210px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-padding{width:203px;_width:198px;}.yui-skin-sam .yui-editor-panel .no-button .yui-toolbar-group-padding{width:172px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-padding h3{margin-left:25px;*margin-left:12px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-textflow{width:182px;}.yui-skin-sam .yui-editor-panel .hd{background:none;}.yui-skin-sam .yui-editor-panel .ft{background-color:#F2F2F2;border:1px solid #808080;border-top:none;padding:0;margin:0 0 2px 0;}.yui-skin-sam .yui-editor-panel .hd span.close{background:url(sprite.png) no-repeat 0 -300px;cursor:pointer;display:block;height:16px;overflow:hidden;position:absolute;right:5px;text-indent:500px;top:2px;width:26px;}.yui-skin-sam .yui-editor-panel .ft span.tip{background-color:#EDF5FF;border-top:1px solid #808080;font-size:85%;}.yui-skin-sam .yui-editor-panel .ft span.tip strong{display:block;float:left;margin:0 2px 8px 0;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon{background:url(editor-sprite.gif) no-repeat 0 -1260px;display:block;height:20px;left:2px;position:absolute;top:8px;width:20px;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon-info{background-position:2px -1260px;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon-warn{background-position:2px -1296px;}.yui-skin-sam .yui-editor-panel .hd span.knob{position:absolute;height:10px;width:28px;top:-10px;left:25px;text-indent:9999px;overflow:hidden;background:url(editor-knob.gif) no-repeat 0 0;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container{float:left;width:100%;background-image:none;border:none;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container .bd{background-color:#fff;}.yui-editor-blankimage{background-image:url(blankimage.png);}.yui-skin-sam .yui-editor-container .yui-resize-handle-br{height:11px;width:11px;background-position:-20px -60px;background-color:transparent;}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/header_background.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/header_background.png
new file mode 100644
index 0000000..3ef7909
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/header_background.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/hue_bg.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/hue_bg.png
new file mode 100644
index 0000000..d9bcdeb
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/hue_bg.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/imagecropper.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/imagecropper.css
new file mode 100644
index 0000000..87a081a
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/imagecropper.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-crop{position:relative;}.yui-crop .yui-crop-mask{position:absolute;top:0;left:0;height:100%;width:100%;}.yui-crop .yui-resize{position:absolute;top:10px;left:10px;border:0;}.yui-crop .yui-crop-resize-mask{position:absolute;top:0;left:0;height:100%;width:100%;background-position:-10px -10px;overflow:hidden;}.yui-skin-sam .yui-crop .yui-crop-mask{background-color:#000;opacity:.5;filter:alpha(opacity=50);}.yui-skin-sam .yui-crop .yui-resize{border:1px dashed #fff;}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/layout.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/layout.css
new file mode 100644
index 0000000..de790e9
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/layout.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-layout-loading{visibility:hidden;}body.yui-layout{overflow:hidden;position:relative;padding:0;margin:0;}.yui-layout-doc{position:relative;overflow:hidden;padding:0;margin:0;}.yui-layout-unit{height:50px;width:50px;padding:0;margin:0;float:none;z-index:0;}.yui-layout-unit-top{position:absolute;top:0;left:0;width:100%;}.yui-layout-unit-left{position:absolute;top:0;left:0;}.yui-layout-unit-right{position:absolute;top:0;right:0;}.yui-layout-unit-bottom{position:absolute;bottom:0;left:0;width:100%;}.yui-layout-unit-center{position:absolute;top:0;left:0;width:100%;}.yui-layout div.yui-layout-hd{position:absolute;top:0;left:0;zoom:1;width:100%;}.yui-layout div.yui-layout-bd{position:absolute;top:0;left:0;zoom:1;width:100%;}.yui-layout .yui-layout-noscroll div.yui-layout-bd{overflow:hidden;}.yui-layout .yui-layout-scroll div.yui-layout-bd{overflow:auto;}.yui-layout div.yui-layout-ft{position:absolute;bottom:0;left:0;width:100%;zoom:1;}.yui-layout .yui-layout-unit div.yui-layout-hd h2{text-align:left;}.yui-layout .yui-layout-unit div.yui-layout-hd .collapse{cursor:pointer;height:13px;position:absolute;right:2px;top:2px;width:17px;font-size:0;}.yui-layout .yui-layout-unit div.yui-layout-hd .close{cursor:pointer;height:13px;position:absolute;right:2px;top:2px;width:17px;font-size:0;}.yui-layout .yui-layout-unit div.yui-layout-hd .collapse-close{right:25px;}.yui-layout .yui-layout-clip{position:absolute;height:20px;background-color:#c0c0c0;display:none;}.yui-layout .yui-layout-clip .collapse{cursor:pointer;height:13px;position:absolute;right:2px;top:2px;width:17px;font-size:0;}.yui-layout .yui-layout-wrap{height:100%;width:100%;position:absolute;left:0;}.yui-skin-sam .yui-layout .yui-resize-proxy{border:none;font-size:0;margin:0;padding:0;}.yui-skin-sam .yui-layout .yui-resize-resizing .yui-resize-handle{display:none;zoom:1;}.yui-skin-sam .yui-layout .yui-resize-proxy div{position:absolute;border:1px solid #808080;background-color:#EDF5FF;}.yui-skin-sam .yui-layout .yui-resize .yui-resize-handle-active{zoom:1;}.yui-skin-sam .yui-layout .yui-resize-proxy .yui-layout-handle-l{width:5px;height:100%;top:0;left:0;zoom:1;}.yui-skin-sam .yui-layout .yui-resize-proxy .yui-layout-handle-r{width:5px;top:0;right:0;height:100%;position:absolute;zoom:1;}.yui-skin-sam .yui-layout .yui-resize-proxy .yui-layout-handle-b{width:100%;bottom:0;left:0;height:5px;}.yui-skin-sam .yui-layout .yui-resize-proxy .yui-layout-handle-t{width:100%;top:0;left:0;height:5px;}.yui-skin-sam .yui-layout .yui-layout-unit-left div.yui-layout-hd .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -160px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-clip-left .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -140px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-unit-right div.yui-layout-hd .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -200px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-clip-right .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -120px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-unit-top div.yui-layout-hd .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -220px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-clip-top .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -240px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-unit-bottom div.yui-layout-hd .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -260px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-clip-bottom .collapse{background:transparent url(layout_sprite.png) no-repeat -20px -180px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-hd .close{background:transparent url(layout_sprite.png) no-repeat -20px -100px;border:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-hd{background:url(sprite.png) repeat-x 0 -1400px;border:1px solid #808080;}.yui-skin-sam .yui-layout{background-color:#EDF5FF;}.yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-hd h2{font-weight:bold;color:#fff;padding:3px;margin:0;}.yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-bd{border:1px solid #808080;border-bottom:none;border-top:none;*border-bottom-width:0;*border-top-width:0;background-color:#f2f2f2;text-align:left;}.yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-bd-noft{border-bottom:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-bd-nohd{border-top:1px solid #808080;}.yui-skin-sam .yui-layout .yui-layout-clip{position:absolute;height:20px;background-color:#EDF5FF;display:none;border:1px solid #808080;}.yui-skin-sam .yui-layout div.yui-layout-ft{border:1px solid #808080;border-top:none;*border-top-width:0;background-color:#f2f2f2;}.yui-skin-sam .yui-layout-unit .yui-resize-handle{background-color:transparent;zoom:1;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-r{right:0;top:0;background-image:none;zoom:1;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-l{left:0;top:0;background-image:none;zoom:1;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-b{right:0;bottom:0;background-image:none;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-t{right:0;top:0;background-image:none;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-r .yui-layout-resize-knob,.yui-skin-sam .yui-layout-unit .yui-resize-handle-l .yui-layout-resize-knob{position:absolute;height:16px;width:6px;top:45%;left:0;display:block;background:transparent url(layout_sprite.png) no-repeat 0 -5px;}.yui-skin-sam .yui-layout-unit .yui-resize-handle-t .yui-layout-resize-knob,.yui-skin-sam .yui-layout-unit .yui-resize-handle-b .yui-layout-resize-knob{position:absolute;height:6px;width:16px;left:45%;background:transparent url(layout_sprite.png) no-repeat -20px 0;zoom:1;}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/layout_sprite.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/layout_sprite.png
new file mode 100644
index 0000000..d6fce3c
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/layout_sprite.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/loading.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/loading.gif
new file mode 100644
index 0000000..0bbf3bc
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/loading.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/logger.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/logger.css
new file mode 100644
index 0000000..bc5397e
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/logger.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-skin-sam .yui-log{padding:1em;width:31em;background-color:#AAA;color:#000;border:1px solid black;font-family:monospace;font-size:77%;text-align:left;z-index:9000}.yui-skin-sam .yui-log-container{position:absolute;top:1em;right:1em}.yui-skin-sam .yui-log input{margin:0;padding:0;font-family:arial;font-size:100%;font-weight:normal}.yui-skin-sam .yui-log .yui-log-btns{position:relative;float:right;bottom:.25em}.yui-skin-sam .yui-log .yui-log-hd{margin-top:1em;padding:.5em;background-color:#575757}.yui-skin-sam .yui-log .yui-log-hd h4{margin:0;padding:0;font-size:108%;font-weight:bold;color:#FFF}.yui-skin-sam .yui-log .yui-log-bd{width:100%;height:20em;background-color:#FFF;border:1px solid gray;overflow:auto}.yui-skin-sam .yui-log p{margin:1px;padding:.1em}.yui-skin-sam .yui-log pre{margin:0;padding:0}.yui-skin-sam .yui-log pre.yui-log-verbose{white-space:pre-wrap;white-space:-moz-pre-wrap!important;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}.yui-skin-sam .yui-log .yui-log-ft{margin-top:.5em}.yui-skin-sam .yui-log .yui-log-ft .yui-log-sourcefilters{width:100%;border-top:1px solid #575757;margin-top:.75em;padding-top:.75em}.yui-skin-sam .yui-log .yui-log-filtergrp{margin-right:.5em}.yui-skin-sam .yui-log .info{background-color:#a7cc25}.yui-skin-sam .yui-log .warn{background-color:#f58516}.yui-skin-sam .yui-log .error{background-color:#e32f0b}.yui-skin-sam .yui-log .time{background-color:#a6c9d7}.yui-skin-sam .yui-log .window{background-color:#f2e886}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu-button-arrow-disabled.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu-button-arrow-disabled.png
new file mode 100644
index 0000000..8cef2ab
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu-button-arrow-disabled.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu-button-arrow.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu-button-arrow.png
new file mode 100644
index 0000000..f03dfee
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu-button-arrow.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu.css
new file mode 100644
index 0000000..744ef09
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menu.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yuimenu{top:-999em;left:-999em;}.yuimenubar{position:static;}.yuimenu .yuimenu,.yuimenubar .yuimenu{position:absolute;}.yuimenubar li,.yuimenu li{list-style-type:none;}.yuimenubar ul,.yuimenu ul,.yuimenubar li,.yuimenu li,.yuimenu h6,.yuimenubar h6{margin:0;padding:0;}.yuimenuitemlabel,.yuimenubaritemlabel{text-align:left;white-space:nowrap;}.yuimenubar ul{*zoom:1;}.yuimenubar .yuimenu ul{*zoom:normal;}.yuimenubar>.bd>ul:after{content:".";display:block;clear:both;visibility:hidden;height:0;line-height:0;}.yuimenubaritem{float:left;}.yuimenubaritemlabel,.yuimenuitemlabel{display:block;}.yuimenuitemlabel .helptext{font-style:normal;display:block;margin:-1em 0 0 10em;}.yui-menu-shadow{position:absolute;visibility:hidden;z-index:-1;}.yui-menu-shadow-visible{top:2px;right:-3px;left:-3px;bottom:-3px;visibility:visible;}.hide-scrollbars *{overflow:hidden;}.hide-scrollbars select{display:none;}.yuimenu.show-scrollbars,.yuimenubar.show-scrollbars{overflow:visible;}.yuimenu.hide-scrollbars .yui-menu-shadow,.yuimenubar.hide-scrollbars .yui-menu-shadow{overflow:hidden;}.yuimenu.show-scrollbars .yui-menu-shadow,.yuimenubar.show-scrollbars .yui-menu-shadow{overflow:auto;}.yui-overlay.yui-force-redraw{margin-bottom:1px;}.yui-skin-sam .yuimenubar{font-size:93%;line-height:2;*line-height:1.9;border:solid 1px #808080;background:url(sprite.png) repeat-x 0 0;}.yui-skin-sam .yuimenubarnav .yuimenubaritem{border-right:solid 1px #ccc;}.yui-skin-sam .yuimenubaritemlabel{padding:0 10px;color:#000;text-decoration:none;cursor:default;border-style:solid;border-color:#808080;border-width:1px 0;*position:relative;margin:-1px 0;}.yui-skin-sam .yuimenubaritemlabel:visited{color:#000;}.yui-skin-sam .yuimenubarnav .yuimenubaritemlabel{padding-right:20px;*display:inline-block;}.yui-skin-sam .yuimenubarnav .yuimenubaritemlabel-hassubmenu{background:url(menubaritem_submenuindicator.png) right center no-repeat;}.yui-skin-sam .yuimenubaritem-selected{background:url(sprite.png) repeat-x 0 -1700px;}.yui-skin-sam .yuimenubaritemlabel-selected{border-color:#7D98B8;}.yui-skin-sam .yuimenubarnav .yuimenubaritemlabel-selected{border-left-width:1px;margin-left:-1px;*left:-1px;}.yui-skin-sam .yuimenubaritemlabel-disabled,.yui-skin-sam .yuimenubaritemlabel-disabled:visited{cursor:default;color:#A6A6A6;}.yui-skin-sam .yuimenubarnav .yuimenubaritemlabel-hassubmenu-disabled{background-image:url(menubaritem_submenuindicator_disabled.png);}.yui-skin-sam .yuimenu{font-size:93%;line-height:1.5;*line-height:1.45;}.yui-skin-sam .yuimenubar .yuimenu,.yui-skin-sam .yuimenu .yuimenu{font-size:100%;}.yui-skin-sam .yuimenu .bd{*zoom:1;_zoom:normal;border:solid 1px #808080;background-color:#fff;}.yui-skin-sam .yuimenu .yuimenu .bd{*zoom:normal;}.yui-skin-sam .yuimenu ul{padding:3px 0;border-width:1px 0 0 0;border-color:#ccc;border-style:solid;}.yui-skin-sam .yuimenu ul.first-of-type{border-width:0;}.yui-skin-sam .yuimenu h6{font-weight:bold;border-style:solid;border-color:#ccc;border-width:1px 0 0 0;color:#a4a4a4;padding:3px 10px 0 10px;}.yui-skin-sam .yuimenu ul.hastitle,.yui-skin-sam .yuimenu h6.first-of-type{border-width:0;}.yui-skin-sam .yuimenu .yui-menu-body-scrolled{border-color:#ccc #808080;overflow:hidden;}.yui-skin-sam .yuimenu .topscrollbar,.yui-skin-sam .yuimenu .bottomscrollbar{height:16px;border:solid 1px #808080;background:#fff url(sprite.png) no-repeat 0 0;}.yui-skin-sam .yuimenu .topscrollbar{border-bottom-width:0;background-position:center -950px;}.yui-skin-sam .yuimenu .topscrollbar_disabled{background-position:center -975px;}.yui-skin-sam .yuimenu .bottomscrollbar{border-top-width:0;background-position:center -850px;}.yui-skin-sam .yuimenu .bottomscrollbar_disabled{background-position:center -875px;}.yui-skin-sam .yuimenuitem{_border-bottom:solid 1px #fff;}.yui-skin-sam .yuimenuitemlabel{padding:0 20px;color:#000;text-decoration:none;cursor:default;}.yui-skin-sam .yuimenuitemlabel:visited{color:#000;}.yui-skin-sam .yuimenuitemlabel .helptext{margin-top:-1.5em;*margin-top:-1.45em;}.yui-skin-sam .yuimenuitem-hassubmenu{background-image:url(menuitem_submenuindicator.png);background-position:right center;background-repeat:no-repeat;}.yui-skin-sam .yuimenuitem-checked{background-image:url(menuitem_checkbox.png);background-position:left center;background-repeat:no-repeat;}.yui-skin-sam .yui-menu-shadow-visible{background-color:#000;opacity:.12;filter:alpha(opacity=12);}.yui-skin-sam .yuimenuitem-selected{background-color:#B3D4FF;}.yui-skin-sam .yuimenuitemlabel-disabled,.yui-skin-sam .yuimenuitemlabel-disabled:visited{cursor:default;color:#A6A6A6;}.yui-skin-sam .yuimenuitem-hassubmenu-disabled{background-image:url(menuitem_submenuindicator_disabled.png);}.yui-skin-sam .yuimenuitem-checked-disabled{background-image:url(menuitem_checkbox_disabled.png);}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menubaritem_submenuindicator.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menubaritem_submenuindicator.png
new file mode 100644
index 0000000..030941c
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menubaritem_submenuindicator.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menubaritem_submenuindicator_disabled.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menubaritem_submenuindicator_disabled.png
new file mode 100644
index 0000000..6c16122
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menubaritem_submenuindicator_disabled.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_checkbox.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_checkbox.png
new file mode 100644
index 0000000..1437a4f
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_checkbox.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_checkbox_disabled.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_checkbox_disabled.png
new file mode 100644
index 0000000..5d5b998
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_checkbox_disabled.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_submenuindicator.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_submenuindicator.png
new file mode 100644
index 0000000..ea4f660
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_submenuindicator.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_submenuindicator_disabled.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_submenuindicator_disabled.png
new file mode 100644
index 0000000..427d60a
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/menuitem_submenuindicator_disabled.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/paginator.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/paginator.css
new file mode 100644
index 0000000..57a10bc
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/paginator.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-skin-sam .yui-pg-container{display:block;margin:6px 0;white-space:nowrap}.yui-skin-sam .yui-pg-first,.yui-skin-sam .yui-pg-previous,.yui-skin-sam .yui-pg-next,.yui-skin-sam .yui-pg-last,.yui-skin-sam .yui-pg-current,.yui-skin-sam .yui-pg-pages,.yui-skin-sam .yui-pg-page{display:inline-block;font-family:arial,helvetica,clean,sans-serif;padding:3px 6px;zoom:1}.yui-skin-sam .yui-pg-pages{padding:0}.yui-skin-sam .yui-pg-current{padding:3px 0}.yui-skin-sam a.yui-pg-first:link,.yui-skin-sam a.yui-pg-first:visited,.yui-skin-sam a.yui-pg-first:active,.yui-skin-sam a.yui-pg-first:hover,.yui-skin-sam a.yui-pg-previous:link,.yui-skin-sam a.yui-pg-previous:visited,.yui-skin-sam a.yui-pg-previous:active,.yui-skin-sam a.yui-pg-previous:hover,.yui-skin-sam a.yui-pg-next:link,.yui-skin-sam a.yui-pg-next:visited,.yui-skin-sam a.yui-pg-next:active,.yui-skin-sam a.yui-pg-next:hover,.yui-skin-sam a.yui-pg-last:link,.yui-skin-sam a.yui-pg-last:visited,.yui-skin-sam a.yui-pg-last:active,.yui-skin-sam a.yui-pg-last:hover,.yui-skin-sam a.yui-pg-page:link,.yui-skin-sam a.yui-pg-page:visited,.yui-skin-sam a.yui-pg-page:active,.yui-skin-sam a.yui-pg-page:hover{color:#06c;text-decoration:underline;outline:0}.yui-skin-sam span.yui-pg-first,.yui-skin-sam span.yui-pg-previous,.yui-skin-sam span.yui-pg-next,.yui-skin-sam span.yui-pg-last{color:#a6a6a6}.yui-skin-sam .yui-pg-page{background-color:#fff;border:1px solid #cbcbcb;padding:2px 6px;text-decoration:none}.yui-skin-sam .yui-pg-current-page{background-color:transparent;border:0;font-weight:bold;padding:3px 6px}.yui-skin-sam .yui-pg-page{margin-left:1px;margin-right:1px}.yui-skin-sam .yui-pg-first,.yui-skin-sam .yui-pg-previous{padding-left:0}.yui-skin-sam .yui-pg-next,.yui-skin-sam .yui-pg-last{padding-right:0}.yui-skin-sam .yui-pg-current,.yui-skin-sam .yui-pg-rpp-options{margin-left:1em;margin-right:1em}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/picker_mask.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/picker_mask.png
new file mode 100644
index 0000000..f8d9193
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/picker_mask.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/profilerviewer.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/profilerviewer.css
new file mode 100644
index 0000000..8b3bfb4
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/profilerviewer.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-skin-sam .yui-pv{background-color:#4a4a4a;font-family:arial;position:relative;width:99%;z-index:1000;margin-bottom:1em;overflow:hidden;}.yui-skin-sam .yui-pv .hd{background:url(header_background.png) repeat-x;min-height:30px;overflow:hidden;zoom:1;padding:2px 0;}.yui-skin-sam .yui-pv .hd h4{padding:8px 10px;margin:0;font:bold 14px arial;color:#fff;}.yui-skin-sam .yui-pv .hd a{background:#3f6bc3;font:bold 11px arial;color:#fff;padding:4px;margin:3px 10px 0 0;border:1px solid #3f567d;cursor:pointer;display:block;float:right;}.yui-skin-sam .yui-pv .hd span{display:none;}.yui-skin-sam .yui-pv .hd span.yui-pv-busy{height:18px;width:18px;background:url(wait.gif) no-repeat;overflow:hidden;display:block;float:right;margin:4px 10px 0 0;}.yui-skin-sam .yui-pv .hd:after,.yui-pv .bd:after,.yui-skin-sam .yui-pv-chartlegend dl:after{content:'.';visibility:hidden;clear:left;height:0;display:block;}.yui-skin-sam .yui-pv .bd{position:relative;zoom:1;overflow-x:auto;overflow-y:hidden;}.yui-skin-sam .yui-pv .yui-pv-table{padding:0 10px;margin:5px 0 10px 0;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-bd td{color:#eeee5c;font:12px arial;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-odd{background:#929292;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-even{background:#58637a;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-even td.yui-dt-asc,.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-even td.yui-dt-desc{background:#384970;}.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-odd td.yui-dt-asc,.yui-skin-sam .yui-pv .yui-pv-table tr.yui-dt-odd td.yui-dt-desc{background:#6F6E6E;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th{background-image:none;background:#2E2D2D;}.yui-skin-sam .yui-pv th.yui-dt-asc .yui-dt-liner{background:transparent url(asc.gif) no-repeat scroll right center;}.yui-skin-sam .yui-pv th.yui-dt-desc .yui-dt-liner{background:transparent url(desc.gif) no-repeat scroll right center;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th a{color:#fff;font:bold 12px arial;}.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th.yui-dt-asc,.yui-skin-sam .yui-pv .yui-pv-table .yui-dt-hd th.yui-dt-desc{background:#333;}.yui-skin-sam .yui-pv-chartcontainer{padding:0 10px;}.yui-skin-sam .yui-pv-chart{height:250px;clear:right;margin:5px 0 0 0;color:#fff;}.yui-skin-sam .yui-pv-chartlegend div{float:right;margin:0 0 0 10px;_width:250px;}.yui-skin-sam .yui-pv-chartlegend dl{border:1px solid #999;padding:.2em 0 .2em .5em;zoom:1;margin:5px 0;}.yui-skin-sam .yui-pv-chartlegend dt{float:left;display:block;height:.7em;width:.7em;padding:0;}.yui-skin-sam .yui-pv-chartlegend dd{float:left;display:block;color:#fff;margin:0 1em 0 .5em;padding:0;font:11px arial;}.yui-skin-sam .yui-pv-minimized{height:35px;}.yui-skin-sam .yui-pv-minimized .bd{top:-3000px;}.yui-skin-sam .yui-pv-minimized .hd a.yui-pv-refresh{display:none;}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/progressbar.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/progressbar.css
new file mode 100644
index 0000000..b44a87d
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/progressbar.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-pb-bar,.yui-pb-mask{width:100%;height:100%}.yui-pb{position:relative;top:0;left:0;width:200px;height:20px;padding:0;border:0;margin:0;text-align:left}.yui-pb-mask{position:absolute;top:0;left:0;z-index:2}.yui-pb-mask div{width:50%;height:50%;background-repeat:no-repeat;padding:0;position:absolute}.yui-pb-tl{background-position:top left}.yui-pb-tr{background-position:top right;left:50%}.yui-pb-bl{background-position:bottom left;top:50%}.yui-pb-br{background-position:bottom right;left:50%;top:50%}.yui-pb-bar{margin:0;position:absolute;left:0;top:0;z-index:1}.yui-pb-ltr .yui-pb-bar{_position:static}.yui-pb-rtl .yui-pb-bar{background-position:right}.yui-pb-btt .yui-pb-bar{background-position:left bottom}.yui-pb-bar{background-color:blue}.yui-pb{border:thin solid #808080}.yui-skin-sam .yui-pb{background-color:transparent;border:solid #808080;border-width:1px 0}.yui-skin-sam .yui-pb-rtl,.yui-skin-sam .yui-pb-ltr{background-image:url(back-h.png);background-repeat:repeat-x}.yui-skin-sam .yui-pb-ttb,.yui-skin-sam .yui-pb-btt{background-image:url(back-v.png);background-repeat:repeat-y}.yui-skin-sam .yui-pb-bar{background-color:transparent}.yui-skin-sam .yui-pb-ltr .yui-pb-bar,.yui-skin-sam .yui-pb-rtl .yui-pb-bar{background-image:url(bar-h.png);background-repeat:repeat-x}.yui-skin-sam .yui-pb-ttb .yui-pb-bar,.yui-skin-sam .yui-pb-btt .yui-pb-bar{background-image:url(bar-v.png);background-repeat:repeat-y}.yui-skin-sam .yui-pb-mask{border:solid #808080;border-width:0 1px;margin:0 -1px}.yui-skin-sam .yui-pb-caption{color:#000;text-align:center;margin:0 auto}.yui-skin-sam .yui-pb-range{color:#a6a6a6}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/resize.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/resize.css
new file mode 100644
index 0000000..e332fe9
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/resize.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-resize{position:relative;zoom:1;z-index:0;}.yui-resize-wrap{zoom:1;}.yui-draggable{cursor:move;}.yui-resize .yui-resize-handle{position:absolute;z-index:1;font-size:0;margin:0;padding:0;zoom:1;height:1px;width:1px;}.yui-resize .yui-resize-handle-br{height:5px;width:5px;bottom:0;right:0;cursor:se-resize;z-index:2;zoom:1;}.yui-resize .yui-resize-handle-bl{height:5px;width:5px;bottom:0;left:0;cursor:sw-resize;z-index:2;zoom:1;}.yui-resize .yui-resize-handle-tl{height:5px;width:5px;top:0;left:0;cursor:nw-resize;z-index:2;zoom:1;}.yui-resize .yui-resize-handle-tr{height:5px;width:5px;top:0;right:0;cursor:ne-resize;z-index:2;zoom:1;}.yui-resize .yui-resize-handle-r{width:5px;height:100%;top:0;right:0;cursor:e-resize;zoom:1;}.yui-resize .yui-resize-handle-l{height:100%;width:5px;top:0;left:0;cursor:w-resize;zoom:1;}.yui-resize .yui-resize-handle-b{width:100%;height:5px;bottom:0;right:0;cursor:s-resize;zoom:1;}.yui-resize .yui-resize-handle-t{width:100%;height:5px;top:0;right:0;cursor:n-resize;zoom:1;}.yui-resize-proxy{position:absolute;border:1px dashed #000;visibility:hidden;z-index:1000;}.yui-resize-hover .yui-resize-handle,.yui-resize-hidden .yui-resize-handle{opacity:0;filter:alpha(opacity=0);}.yui-resize-ghost{opacity:.5;filter:alpha(opacity=50);}.yui-resize-knob .yui-resize-handle{height:6px;width:6px;}.yui-resize-knob .yui-resize-handle-tr{right:-3px;top:-3px;}.yui-resize-knob .yui-resize-handle-tl{left:-3px;top:-3px;}.yui-resize-knob .yui-resize-handle-bl{left:-3px;bottom:-3px;}.yui-resize-knob .yui-resize-handle-br{right:-3px;bottom:-3px;}.yui-resize-knob .yui-resize-handle-t{left:45%;top:-3px;}.yui-resize-knob .yui-resize-handle-r{right:-3px;top:45%;}.yui-resize-knob .yui-resize-handle-l{left:-3px;top:45%;}.yui-resize-knob .yui-resize-handle-b{left:45%;bottom:-3px;}.yui-resize-status{position:absolute;top:-999px;left:-999px;padding:2px;font-size:80%;display:none;zoom:1;z-index:9999;}.yui-resize-status strong,.yui-resize-status em{font-weight:normal;font-style:normal;padding:1px;zoom:1;}.yui-skin-sam .yui-resize .yui-resize-handle{background-color:#F2F2F2;zoom:1;}.yui-skin-sam .yui-resize .yui-resize-handle-active{background-color:#7D98B8;zoom:1;}.yui-skin-sam .yui-resize .yui-resize-handle-l,.yui-skin-sam .yui-resize .yui-resize-handle-r,.yui-skin-sam .yui-resize .yui-resize-handle-l-active,.yui-skin-sam .yui-resize .yui-resize-handle-r-active{height:100%;zoom:1;}.yui-skin-sam .yui-resize-knob .yui-resize-handle{border:1px solid #808080;}.yui-skin-sam .yui-resize-hover .yui-resize-handle-active{opacity:1;filter:alpha(opacity=100);}.yui-skin-sam .yui-resize-proxy{border:1px dashed #426FD9;}.yui-skin-sam .yui-resize-status{border:1px solid #A6982B;border-top:1px solid #D4C237;background-color:#FFEE69;color:#000;}.yui-skin-sam .yui-resize-status strong,.yui-skin-sam .yui-resize-status em{float:left;display:block;clear:both;padding:1px;text-align:center;}.yui-skin-sam .yui-resize .yui-resize-handle-inner-r,.yui-skin-sam .yui-resize .yui-resize-handle-inner-l{background:transparent url(layout_sprite.png) no-repeat 0 -5px;height:16px;width:5px;position:absolute;top:45%;}.yui-skin-sam .yui-resize .yui-resize-handle-inner-t,.yui-skin-sam .yui-resize .yui-resize-handle-inner-b{background:transparent url(layout_sprite.png) no-repeat -20px 0;height:5px;width:16px;position:absolute;left:50%;}.yui-skin-sam .yui-resize .yui-resize-handle-br{background-image:url(layout_sprite.png);background-repeat:no-repeat;background-position:-22px -62px;}.yui-skin-sam .yui-resize .yui-resize-handle-tr{background-image:url(layout_sprite.png);background-repeat:no-repeat;background-position:-22px -42px;}.yui-skin-sam .yui-resize .yui-resize-handle-tl{background-image:url(layout_sprite.png);background-repeat:no-repeat;background-position:-22px -82px;}.yui-skin-sam .yui-resize .yui-resize-handle-bl{background-image:url(layout_sprite.png);background-repeat:no-repeat;background-position:-22px -23px;}.yui-skin-sam .yui-resize-knob .yui-resize-handle-t,.yui-skin-sam .yui-resize-knob .yui-resize-handle-r,.yui-skin-sam .yui-resize-knob .yui-resize-handle-b,.yui-skin-sam .yui-resize-knob .yui-resize-handle-l,.yui-skin-sam .yui-resize-knob .yui-resize-handle-tl,.yui-skin-sam .yui-resize-knob .yui-resize-handle-tr,.yui-skin-sam .yui-resize-knob .yui-resize-handle-bl,.yui-skin-sam .yui-resize-knob .yui-resize-handle-br,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-t,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-r,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-b,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-l,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-tl,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-tr,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-bl,.yui-skin-sam .yui-resize-knob .yui-resize-handle-inner-br{background-image:none;}.yui-skin-sam .yui-resize-knob .yui-resize-handle-l,.yui-skin-sam .yui-resize-knob .yui-resize-handle-r,.yui-skin-sam .yui-resize-knob .yui-resize-handle-l-active,.yui-skin-sam .yui-resize-knob .yui-resize-handle-r-active{height:6px;width:6px;}.yui-skin-sam .yui-resize-textarea .yui-resize-handle-r{right:-8px;}.yui-skin-sam .yui-resize-textarea .yui-resize-handle-b{bottom:-8px;}.yui-skin-sam .yui-resize-textarea .yui-resize-handle-br{right:-8px;bottom:-8px;}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/simpleeditor.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/simpleeditor.css
new file mode 100644
index 0000000..bf4016e
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/simpleeditor.css
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-busy{cursor:wait!important;}.yui-toolbar-container fieldset,.yui-editor-container fieldset{padding:0;margin:0;border:0;}.yui-toolbar-container legend{display:none;}.yui-skin-sam .yui-toolbar-container .yui-button button,.yui-skin-sam .yui-toolbar-container .yui-button a,.yui-skin-sam .yui-toolbar-container .yui-button a:visited{font-size:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select button,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a:visited,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton button,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a:visited{font-size:12px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{font-size:0;line-height:0;padding:0;}.yui-toolbar-container .yui-toolbar-subcont{padding:.25em 0;zoom:1;}.yui-toolbar-container-collapsed .yui-toolbar-subcont{display:none;}.yui-toolbar-container .yui-toolbar-subcont:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-container span.yui-toolbar-draghandle{cursor:move;border-left:1px solid #999;border-right:1px solid #999;overflow:hidden;text-indent:77777px;width:2px;height:20px;display:block;clear:none;float:left;margin:0 0 0 .2em;}.yui-toolbar-container .yui-toolbar-titlebar.draggable{cursor:move;}.yui-toolbar-container .yui-toolbar-titlebar{position:relative;}.yui-toolbar-container .yui-toolbar-titlebar h2{font-weight:bold;letter-spacing:0;border:none;color:#000;margin:0;padding:.2em;}.yui-toolbar-container .yui-toolbar-titlebar h2 a{text-decoration:none;color:#000;cursor:default;}.yui-toolbar-container.yui-toolbar-grouped span.yui-toolbar-draghandle{height:40px;}.yui-toolbar-container .yui-toolbar-group{float:left;margin-right:.5em;zoom:1;}.yui-toolbar-container .yui-toolbar-group:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-container .yui-toolbar-group h3{font-size:75%;padding:0 0 0 .25em;margin:0;}.yui-toolbar-container span.yui-toolbar-separator{width:2px;padding:0;height:18px;margin:.2em 0 .2em .1em;display:none;float:left;}.yui-toolbar-container.yui-toolbar-grouped span.yui-toolbar-separator{height:45px;*height:50px;}.yui-toolbar-container.yui-toolbar-grouped .yui-toolbar-group span.yui-toolbar-separator{height:18px;display:block;}.yui-toolbar-container ul li{margin:0;padding:0;list-style-type:none;}.yui-toolbar-container .yui-toolbar-nogrouplabels h3{display:none;}.yui-toolbar-container .yui-push-button,.yui-toolbar-container .yui-color-button,.yui-toolbar-container .yui-menu-button{position:relative;cursor:pointer;}.yui-toolbar-container .yui-button .first-child,.yui-toolbar-container .yui-button .first-child a{height:100%;width:100%;overflow:hidden;font-size:0;}.yui-toolbar-container .yui-button-disabled{cursor:default;}.yui-toolbar-container .yui-button-disabled .yui-toolbar-icon{opacity:.5;filter:alpha(opacity=50);}.yui-toolbar-container .yui-button-disabled .up,.yui-toolbar-container .yui-button-disabled .down{opacity:.5;filter:alpha(opacity=50);}.yui-toolbar-container .yui-button a{overflow:hidden;}.yui-toolbar-container .yui-toolbar-select .first-child a{cursor:pointer;}.yui-toolbar-fontname-arial{font-family:Arial;}.yui-toolbar-fontname-arial-black{font-family:Arial Black;}.yui-toolbar-fontname-comic-sans-ms{font-family:Comic Sans MS;}.yui-toolbar-fontname-courier-new{font-family:Courier New;}.yui-toolbar-fontname-times-new-roman{font-family:Times New Roman;}.yui-toolbar-fontname-verdana{font-family:Verdana;}.yui-toolbar-fontname-impact{font-family:Impact;}.yui-toolbar-fontname-lucida-console{font-family:Lucida Console;}.yui-toolbar-fontname-tahoma{font-family:Tahoma;}.yui-toolbar-fontname-trebuchet-ms{font-family:Trebuchet MS;}.yui-toolbar-container .yui-toolbar-spinbutton{position:relative;}.yui-toolbar-container .yui-toolbar-spinbutton .first-child a{z-index:0;opacity:1;}.yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-toolbar-container .yui-toolbar-spinbutton a.down{position:absolute;display:block;right:0;cursor:pointer;z-index:1;padding:0;margin:0;}.yui-toolbar-container .yui-overlay{position:absolute;}.yui-toolbar-container .yui-overlay ul li{margin:0;list-style-type:none;}.yui-toolbar-container{z-index:1;}.yui-editor-container .yui-editor-editable-container{position:relative;z-index:0;width:100%;}.yui-editor-container .yui-editor-masked{background-color:#CCC;height:100%;width:100%;position:absolute;top:0;left:0;opacity:.5;filter:alpha(opacity=50);}.yui-editor-container iframe{border:0;padding:0;margin:0;zoom:1;display:block;}.yui-editor-container .yui-editor-editable{padding:0;margin:0;}.yui-editor-container .dompath{font-size:85%;}.yui-editor-panel .hd{text-align:left;position:relative;}.yui-editor-panel .hd h3{font-weight:bold;padding:.25em 0 .25em .25em;margin:0;}.yui-editor-panel .bd{width:100%;zoom:1;position:relative;}.yui-editor-panel .bd div.yui-editor-body-cont{padding:.25em .1em;zoom:1;}.yui-editor-panel .bd .gecko form{overflow:auto;}.yui-editor-panel .bd div.yui-editor-body-cont:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-editor-panel .ft{text-align:right;width:99%;float:left;clear:both;}.yui-editor-panel .ft span.tip{display:block;position:relative;padding:.5em .5em .5em 23px;text-align:left;zoom:1;}.yui-editor-panel label{clear:both;float:left;padding:0;width:100%;text-align:left;zoom:1;}.yui-editor-panel .gecko label{overflow:auto;}.yui-editor-panel label strong{float:left;width:6em;}.yui-editor-panel .removeLink{width:80%;text-align:right;}.yui-editor-panel label input{margin-left:.25em;float:left;}.yui-editor-panel .yui-toolbar-group{margin-bottom:.75em;}.yui-editor-panel .height-width{float:left;}.yui-editor-panel .height-width span{font-style:italic;display:block;float:left;overflow:visible;}.yui-editor-panel .height-width span.info{font-size:70%;margin-top:3px;float:none;}
+.yui-editor-panel .yui-toolbar-bordersize,.yui-editor-panel .yui-toolbar-bordertype{font-size:75%;}.yui-editor-panel .yui-toolbar-container span.yui-toolbar-separator{border:none;}.yui-editor-panel .yui-toolbar-bordersize span a span,.yui-editor-panel .yui-toolbar-bordertype span a span{display:block;height:8px;left:4px;position:absolute;top:3px;_top:-5px;width:24px;text-indent:52px;font-size:0;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-solid{border-bottom:1px solid black;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-dotted{border-bottom:1px dotted black;}.yui-editor-panel .yui-toolbar-bordertype span a span.yui-toolbar-bordertype-dashed{border-bottom:1px dashed black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-0{*top:0;text-indent:0;font-size:75%;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-1{border-bottom:1px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-2{border-bottom:2px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-3{top:2px;*top:-5px;border-bottom:3px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-4{top:1px;*top:-5px;border-bottom:4px solid black;}.yui-editor-panel .yui-toolbar-bordersize span a span.yui-toolbar-bordersize-5{top:1px;*top:-5px;border-bottom:5px solid black;}.yui-toolbar-container .yui-toolbar-bordersize-menu,.yui-toolbar-container .yui-toolbar-bordertype-menu{width:95px!important;}.yui-toolbar-bordersize-menu .yuimenuitemlabel,.yui-toolbar-bordertype-menu .yuimenuitemlabel,.yui-toolbar-bordersize-menu .yuimenuitemlabel,.yui-toolbar-bordertype-menu .yuimenuitemlabel:hover{margin:0 3px 7px 17px;}.yui-toolbar-bordersize-menu .yuimenuitemlabel .checkedindicator,.yui-toolbar-bordertype-menu .yuimenuitemlabel .checkedindicator{position:absolute;left:-12px;*top:14px;*left:0;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-1 a{border-bottom:1px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-2 a{border-bottom:2px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-3 a{border-bottom:3px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-4 a{border-bottom:4px solid black;height:14px;}.yui-toolbar-bordersize-menu li.yui-toolbar-bordersize-5 a{border-bottom:5px solid black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-solid a{border-bottom:1px solid black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-dashed a{border-bottom:1px dashed black;height:14px;}.yui-toolbar-bordertype-menu li.yui-toolbar-bordertype-dotted a{border-bottom:1px dotted black;height:14px;}h2.yui-editor-skipheader,h3.yui-editor-skipheader{height:0;margin:0;padding:0;border:none;width:0;overflow:hidden;position:absolute;}.yui-toolbar-colors{width:133px;zoom:1;display:none;z-index:100;overflow:hidden;}.yui-toolbar-colors:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-colors a{height:9px;width:9px;float:left;display:block;overflow:hidden;text-indent:999px;margin:0;cursor:pointer;border:1px solid #F6F7EE;}.yui-toolbar-colors a:hover{border:1px solid black;}.yui-color-button-menu{overflow:visible;background-color:transparent;}.yui-toolbar-colors span{position:relative;display:block;padding:3px;overflow:hidden;float:left;width:100%;zoom:1;}.yui-toolbar-colors span:after{display:block;clear:both;visibility:hidden;content:'.';height:0;}.yui-toolbar-colors span em{height:35px;width:30px;float:left;display:block;overflow:hidden;text-indent:999px;margin:.75px;border:1px solid black;}.yui-toolbar-colors span strong{font-weight:normal;padding-left:3px;display:block;font-size:85%;float:left;width:65%;}.yui-toolbar-group-undoredo h3,.yui-toolbar-group-insertitem h3,.yui-toolbar-group-indentlist h3{width:68px;}.yui-toolbar-group-indentlist2 h3{width:122px;}.yui-toolbar-group-alignment h3{width:130px;}.yui-skin-sam .yui-editor-container{border:1px solid #808080;}.yui-skin-sam .yui-toolbar-container{zoom:1;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar{background:url(sprite.png) repeat-x 0 -200px;position:relative;}.yui-skin-sam .yui-editor-container .draggable .yui-toolbar-titlebar{cursor:move;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar h2{color:#000;font-weight:bold;margin:0;padding:.3em 1em;font-size:100%;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-group h3{color:#808080;font-size:75%;margin:1em 0 0;padding-bottom:0;padding-left:.25em;text-align:left;}.yui-toolbar-container span.yui-toolbar-separator{border:none;text-indent:33px;overflow:hidden;margin:0 .25em;}.yui-skin-sam .yui-toolbar-container{background-color:#F2F2F2;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-subcont{padding:0 1em .35em;border-bottom:1px solid #808080;}.yui-skin-sam .yui-toolbar-container-collapsed .yui-toolbar-titlebar{border-bottom:1px solid #808080;}.yui-skin-sam .yui-editor-container .visible .yui-menu-shadow,.yui-skin-sam .yui-editor-panel .visible .yui-menu-shadow{display:none;}.yui-skin-sam .yui-editor-container ul{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-editor-container ul li{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-toolbar-group ul li.yui-toolbar-groupitem{float:left;}.yui-skin-sam .yui-editor-container .dompath{background-color:#F2F2F2;border-top:1px solid #808080;color:#999;text-align:left;padding:.25em;}.yui-skin-sam .yui-toolbar-container .collapse{background:url(sprite.png) no-repeat 0 -400px;}.yui-skin-sam .yui-toolbar-container .collapsed{background:url(sprite.png) no-repeat 0 -350px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-titlebar span.collapse{cursor:pointer;position:absolute;top:4px;right:2px;display:block;overflow:hidden;height:15px;width:15px;text-indent:9999px;}
+.yui-skin-sam .yui-toolbar-container .yui-push-button,.yui-skin-sam .yui-toolbar-container .yui-color-button,.yui-skin-sam .yui-toolbar-container .yui-menu-button{background:url(sprite.png) repeat-x 0 0;position:relative;display:block;height:22px;width:30px;_font-size:0;margin:0;border-color:#808080;color:#f2f2f2;border-style:solid;border-width:1px 0;zoom:1;}.yui-skin-sam .yui-toolbar-container .yui-push-button a,.yui-skin-sam .yui-toolbar-container .yui-color-button a,.yui-skin-sam .yui-toolbar-container .yui-menu-button a{padding-left:35px;height:20px;text-decoration:none;font-size:0;line-height:2;display:block;color:#000;overflow:hidden;white-space:nowrap;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a,.yui-skin-sam .yui-toolbar-container .yui-toolbar-select a{font-size:12px;}.yui-skin-sam .yui-toolbar-container .yui-push-button .first-child,.yui-skin-sam .yui-toolbar-container .yui-color-button .first-child,.yui-skin-sam .yui-toolbar-container .yui-menu-button .first-child{border-color:#808080;border-style:solid;border-width:0 1px;margin:0 -1px;display:block;position:relative;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled .first-child,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled .first-child,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled .first-child{border-color:#ccc;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled a,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled a,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled a{color:#A6A6A6;cursor:default;}.yui-skin-sam .yui-toolbar-container .yui-push-button-disabled,.yui-skin-sam .yui-toolbar-container .yui-color-button-disabled,.yui-skin-sam .yui-toolbar-container .yui-menu-button-disabled{border-color:#ccc;}.yui-skin-sam .yui-toolbar-container .yui-button .first-child{*left:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-fontname{width:135px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-heading{width:92px;}.yui-skin-sam .yui-toolbar-container .yui-button-hover{background:url(sprite.png) repeat-x 0 -1300px;border-color:#808080;}.yui-skin-sam .yui-toolbar-container .yui-button-selected{background:url(sprite.png) repeat-x 0 -1700px;border-color:#808080;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-nogrouplabels h3{display:none;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-nogrouplabels .yui-toolbar-group{margin-top:.75em;}.yui-skin-sam .yui-toolbar-container .yui-push-button span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-color-button span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-menu-button span.yui-toolbar-icon{display:block;position:absolute;top:2px;height:18px;width:18px;overflow:hidden;background:url(editor-sprite.gif) no-repeat 30px 30px;}.yui-skin-sam .yui-toolbar-container .yui-button-selected span.yui-toolbar-icon,.yui-skin-sam .yui-toolbar-container .yui-button-hover span.yui-toolbar-icon{background-image:url(editor-sprite-active.gif);}.yui-skin-sam .yui-toolbar-container .visible .yuimenuitemlabel{cursor:pointer;color:#000;*position:relative;}.yui-skin-sam .yui-toolbar-container .yui-button-menu{background-color:#fff;}.yui-skin-sam .yui-toolbar-container .yui-button-menu .yui-menu-body-scrolled{position:relative;}.yui-skin-sam div.yuimenu li.selected{background-color:#B3D4FF;}.yui-skin-sam div.yuimenu li.selected a.selected{color:#000;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-bold span.yui-toolbar-icon{background-position:0 0;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-strikethrough span.yui-toolbar-icon{background-position:0 -108px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-italic span.yui-toolbar-icon{background-position:0 -36px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-undo span.yui-toolbar-icon{background-position:0 -1326px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-redo span.yui-toolbar-icon{background-position:0 -1355px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-underline span.yui-toolbar-icon{background-position:0 -72px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-subscript span.yui-toolbar-icon{background-position:0 -180px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-superscript span.yui-toolbar-icon{background-position:0 -144px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-forecolor span.yui-toolbar-icon{background-position:0 -216px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-backcolor span.yui-toolbar-icon{background-position:0 -288px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyleft span.yui-toolbar-icon{background-position:0 -324px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifycenter span.yui-toolbar-icon{background-position:0 -360px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyright span.yui-toolbar-icon{background-position:0 -396px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-justifyfull span.yui-toolbar-icon{background-position:0 -432px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-indent span.yui-toolbar-icon{background-position:0 -720px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-outdent span.yui-toolbar-icon{background-position:0 -684px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-createlink span.yui-toolbar-icon{background-position:0 -792px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertimage span.yui-toolbar-icon{background-position:1px -756px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-left span.yui-toolbar-icon{background-position:0 -972px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-right span.yui-toolbar-icon{background-position:0 -936px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-inline span.yui-toolbar-icon{background-position:0 -900px;left:5px;}
+.yui-skin-sam .yui-toolbar-container .yui-toolbar-block span.yui-toolbar-icon{background-position:0 -864px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-bordercolor span.yui-toolbar-icon{background-position:0 -252px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-removeformat span.yui-toolbar-icon{background-position:0 -1080px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-hiddenelements span.yui-toolbar-icon{background-position:0 -1044px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertunorderedlist span.yui-toolbar-icon{background-position:0 -468px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-insertorderedlist span.yui-toolbar-icon{background-position:0 -504px;left:5px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton .first-child{width:35px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton .first-child a{padding-left:2px;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton span.yui-toolbar-icon{display:none;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up,.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{right:2px;background:url(editor-sprite.gif) no-repeat 0 -1222px;overflow:hidden;height:6px;width:7px;min-height:0;padding:0;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.up{top:2px;background-position:0 -1222px;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-spinbutton a.down{bottom:2px;background-position:0 -1187px;}.yui-skin-sam .yui-toolbar-container select{height:22px;border:1px solid #808080;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select .first-child a{padding-left:5px;text-align:left;}.yui-skin-sam .yui-toolbar-container .yui-toolbar-select span.yui-toolbar-icon{background:url(editor-sprite.gif) no-repeat 0 -1144px;overflow:hidden;right:-2px;top:0;height:20px;}.yui-skin-sam .yui-editor-panel .yui-color-button-menu .bd{background-color:transparent;border:none;width:135px;}.yui-skin-sam .yui-color-button-menu .yui-toolbar-colors{border:1px solid #808080;}.yui-skin-sam .yui-editor-panel{padding:0;margin:0;border:none;background-color:transparent;overflow:visible;position:absolute;}.yui-skin-sam .yui-editor-panel .hd{margin:10px 0 0;padding:0;border:none;}.yui-skin-sam .yui-editor-panel .hd h3{color:#000;border:1px solid #808080;background:url(sprite.png) repeat-x 0 -200px;width:99%;position:relative;margin:0;padding:3px 0 0 0;font-size:93%;text-indent:5px;height:20px;}.yui-skin-sam .yui-editor-panel .bd{background-color:#F2F2F2;border-left:1px solid #808080;border-right:1px solid #808080;width:99%;margin:0;padding:0;overflow:visible;}.yui-skin-sam .yui-editor-panel ul{list-style-type:none;margin:0;padding:0;}.yui-skin-sam .yui-editor-panel ul li{margin:0;padding:0;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container .yui-toolbar-subcont{padding:0;border:none;margin-top:.35em;}.yui-skin-sam .yui-editor-panel .yui-toolbar-bordersize,.yui-skin-sam .yui-editor-panel .yui-toolbar-bordertype{width:50px;}.yui-skin-sam .yui-editor-panel label{display:block;float:none;padding:4px 0;margin-bottom:7px;}.yui-skin-sam .yui-editor-panel label strong{font-weight:normal;font-size:93%;text-align:right;padding-top:2px;}.yui-skin-sam .yui-editor-panel label input{width:75%;}.yui-skin-sam .yui-editor-panel .createlink_target,.yui-skin-sam .yui-editor-panel .insertimage_target{width:auto;margin-right:5px;}.yui-skin-sam .yui-editor-panel .removeLink{width:98%;}.yui-skin-sam .yui-editor-panel label input.warning{background-color:#FFEE69;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group h3{color:#000;float:left;font-weight:normal;font-size:93%;margin:5px 0 0 0;padding:0 3px 0 0;text-align:right;}.yui-skin-sam .yui-editor-panel .height-width h3{margin:3px 0 0 10px;}.yui-skin-sam .yui-editor-panel .height-width{margin:3px 0 0 35px;*margin-left:14px;width:42%;*width:44%;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-border{width:190px;}.yui-skin-sam .yui-editor-panel .no-button .yui-toolbar-group-border{width:210px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-padding{width:203px;_width:198px;}.yui-skin-sam .yui-editor-panel .no-button .yui-toolbar-group-padding{width:172px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-padding h3{margin-left:25px;*margin-left:12px;}.yui-skin-sam .yui-editor-panel .yui-toolbar-group-textflow{width:182px;}.yui-skin-sam .yui-editor-panel .hd{background:none;}.yui-skin-sam .yui-editor-panel .ft{background-color:#F2F2F2;border:1px solid #808080;border-top:none;padding:0;margin:0 0 2px 0;}.yui-skin-sam .yui-editor-panel .hd span.close{background:url(sprite.png) no-repeat 0 -300px;cursor:pointer;display:block;height:16px;overflow:hidden;position:absolute;right:5px;text-indent:500px;top:2px;width:26px;}.yui-skin-sam .yui-editor-panel .ft span.tip{background-color:#EDF5FF;border-top:1px solid #808080;font-size:85%;}.yui-skin-sam .yui-editor-panel .ft span.tip strong{display:block;float:left;margin:0 2px 8px 0;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon{background:url(editor-sprite.gif) no-repeat 0 -1260px;display:block;height:20px;left:2px;position:absolute;top:8px;width:20px;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon-info{background-position:2px -1260px;}.yui-skin-sam .yui-editor-panel .ft span.tip span.icon-warn{background-position:2px -1296px;}.yui-skin-sam .yui-editor-panel .hd span.knob{position:absolute;height:10px;width:28px;top:-10px;left:25px;text-indent:9999px;overflow:hidden;background:url(editor-knob.gif) no-repeat 0 0;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container{float:left;width:100%;background-image:none;border:none;}.yui-skin-sam .yui-editor-panel .yui-toolbar-container .bd{background-color:#fff;}.yui-editor-blankimage{background-image:url(blankimage.png);}.yui-skin-sam .yui-editor-container .yui-resize-handle-br{height:11px;width:11px;background-position:-20px -60px;background-color:transparent;}
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/slider.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/slider.css
new file mode 100644
index 0000000..3947edc
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/slider.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-h-slider,.yui-v-slider,.yui-region-slider{position:relative;}.yui-h-slider .yui-slider-thumb,.yui-v-slider .yui-slider-thumb,.yui-region-slider .yui-slider-thumb{position:absolute;cursor:default;}.yui-skin-sam .yui-h-slider{background:url(bg-h.gif) no-repeat 5px 0;height:28px;width:228px;}.yui-skin-sam .yui-h-slider .yui-slider-thumb{top:4px;}.yui-skin-sam .yui-v-slider{background:url(bg-v.gif) no-repeat 12px 0;height:228px;width:48px;}.yui-skin-sam .yui-region-slider{height:228px;width:228px;}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-active.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-active.png
new file mode 100644
index 0000000..fa58c50
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-active.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-disabled.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-disabled.png
new file mode 100644
index 0000000..0a6a82c
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-disabled.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-focus.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-focus.png
new file mode 100644
index 0000000..167d71e
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-focus.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-hover.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-hover.png
new file mode 100644
index 0000000..167d71e
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow-hover.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow.png
new file mode 100644
index 0000000..b33a93f
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/split-button-arrow.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/sprite.png b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/sprite.png
new file mode 100644
index 0000000..73634d6
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/sprite.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/tabview.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/tabview.css
new file mode 100644
index 0000000..9c3a358
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/tabview.css
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+.yui-navset .yui-nav li,.yui-navset .yui-navset-top .yui-nav li,.yui-navset .yui-navset-bottom .yui-nav li{margin:0 .5em 0 0}.yui-navset-left .yui-nav li,.yui-navset-right .yui-nav li{margin:0 0 .5em}.yui-navset .yui-content .yui-hidden{border:0;height:0;width:0;padding:0;position:absolute;left:-999999px;overflow:hidden;visibility:hidden}.yui-navset .yui-navset-left .yui-nav,.yui-navset .yui-navset-right .yui-nav,.yui-navset-left .yui-nav,.yui-navset-right .yui-nav{width:6em}.yui-navset-top .yui-nav,.yui-navset-bottom .yui-nav{width:auto}.yui-navset .yui-navset-left,.yui-navset-left{padding:0 0 0 6em}.yui-navset-right{padding:0 6em 0 0}.yui-navset-top,.yui-navset-bottom{padding:auto}.yui-nav,.yui-nav li{margin:0;padding:0;list-style:none}.yui-navset li em{font-style:normal}.yui-navset{position:relative;zoom:1}.yui-navset .yui-content,.yui-navset .yui-content div{zoom:1}.yui-navset .yui-content:after{content:'';display:block;clear:both}.yui-navset .yui-nav li,.yui-navset .yui-navset-top .yui-nav li,.yui-navset .yui-navset-bottom .yui-nav li{display:inline-block;display:-moz-inline-stack;*display:inline;vertical-align:bottom;cursor:pointer;zoom:1}.yui-navset-left .yui-nav li,.yui-navset-right .yui-nav li{display:block}.yui-navset .yui-nav a{position:relative}.yui-navset .yui-nav li a,.yui-navset-top .yui-nav li a,.yui-navset-bottom .yui-nav li a{display:block;display:inline-block;vertical-align:bottom;zoom:1}.yui-navset-left .yui-nav li a,.yui-navset-right .yui-nav li a{display:block}.yui-navset-bottom .yui-nav li a{vertical-align:text-top}.yui-navset .yui-nav li a em,.yui-navset-top .yui-nav li a em,.yui-navset-bottom .yui-nav li a em{display:block}.yui-navset .yui-navset-left .yui-nav,.yui-navset .yui-navset-right .yui-nav,.yui-navset-left .yui-nav,.yui-navset-right .yui-nav{position:absolute;z-index:1}.yui-navset-top .yui-nav,.yui-navset-bottom .yui-nav{position:static}.yui-navset .yui-navset-left .yui-nav,.yui-navset-left .yui-nav{left:0;right:auto}.yui-navset .yui-navset-right .yui-nav,.yui-navset-right .yui-nav{right:0;left:auto}.yui-skin-sam .yui-navset .yui-nav,.yui-skin-sam .yui-navset .yui-navset-top .yui-nav{border:solid #2647a0;border-width:0 0 5px;zoom:1}.yui-skin-sam .yui-navset .yui-nav li,.yui-skin-sam .yui-navset .yui-navset-top .yui-nav li{margin:0 .16em 0 0;padding:1px 0 0;zoom:1}.yui-skin-sam .yui-navset .yui-nav .selected,.yui-skin-sam .yui-navset .yui-navset-top .yui-nav .selected{margin:0 .16em -1px 0}.yui-skin-sam .yui-navset .yui-nav a,.yui-skin-sam .yui-navset .yui-navset-top .yui-nav a{background:#d8d8d8 url(sprite.png) repeat-x;border:solid #a3a3a3;border-width:0 1px;color:#000;position:relative;text-decoration:none}.yui-skin-sam .yui-navset .yui-nav a em,.yui-skin-sam .yui-navset .yui-navset-top .yui-nav a em{border:solid #a3a3a3;border-width:1px 0 0;padding:.25em .75em;left:0;right:0;bottom:0;top:-1px;position:relative}.yui-skin-sam .yui-navset .yui-nav .selected a,.yui-skin-sam .yui-navset .yui-nav .selected a:focus,.yui-skin-sam .yui-navset .yui-nav .selected a:hover{background:#2647a0 url(sprite.png) repeat-x left -1400px;color:#fff}.yui-skin-sam .yui-navset .yui-nav a:hover,.yui-skin-sam .yui-navset .yui-nav a:focus{background:#bfdaff url(sprite.png) repeat-x left -1300px;outline:0}.yui-skin-sam .yui-navset .yui-nav .selected a em{padding:.35em .75em}.yui-skin-sam .yui-navset .yui-nav .selected a,.yui-skin-sam .yui-navset .yui-nav .selected a em{border-color:#243356}.yui-skin-sam .yui-navset .yui-content{background:#edf5ff}.yui-skin-sam .yui-navset .yui-content,.yui-skin-sam .yui-navset .yui-navset-top .yui-content{border:1px solid #808080;border-top-color:#243356;padding:.25em .5em}.yui-skin-sam .yui-navset-left .yui-nav,.yui-skin-sam .yui-navset .yui-navset-left .yui-nav,.yui-skin-sam .yui-navset .yui-navset-right .yui-nav,.yui-skin-sam .yui-navset-right .yui-nav{border-width:0 5px 0 0;Xposition:absolute;top:0;bottom:0}.yui-skin-sam .yui-navset .yui-navset-right .yui-nav,.yui-skin-sam .yui-navset-right .yui-nav{border-width:0 0 0 5px}.yui-skin-sam .yui-navset-left .yui-nav li,.yui-skin-sam .yui-navset .yui-navset-left .yui-nav li,.yui-skin-sam .yui-navset-right .yui-nav li{margin:0 0 .16em;padding:0 0 0 1px}.yui-skin-sam .yui-navset-right .yui-nav li{padding:0 1px 0 0}.yui-skin-sam .yui-navset-left .yui-nav .selected,.yui-skin-sam .yui-navset .yui-navset-left .yui-nav .selected{margin:0 -1px .16em 0}.yui-skin-sam .yui-navset-right .yui-nav .selected{margin:0 0 .16em -1px}.yui-skin-sam .yui-navset-left .yui-nav a,.yui-skin-sam .yui-navset-right .yui-nav a{border-width:1px 0}.yui-skin-sam .yui-navset-left .yui-nav a em,.yui-skin-sam .yui-navset .yui-navset-left .yui-nav a em,.yui-skin-sam .yui-navset-right .yui-nav a em{border-width:0 0 0 1px;padding:.2em .75em;top:auto;left:-1px}.yui-skin-sam .yui-navset-right .yui-nav a em{border-width:0 1px 0 0;left:auto;right:-1px}.yui-skin-sam .yui-navset-left .yui-nav a,.yui-skin-sam .yui-navset-left .yui-nav .selected a,.yui-skin-sam .yui-navset-left .yui-nav a:hover,.yui-skin-sam .yui-navset-right .yui-nav a,.yui-skin-sam .yui-navset-right .yui-nav .selected a,.yui-skin-sam .yui-navset-right .yui-nav a:hover,.yui-skin-sam .yui-navset-bottom .yui-nav a,.yui-skin-sam .yui-navset-bottom .yui-nav .selected a,.yui-skin-sam .yui-navset-bottom .yui-nav a:hover{background-image:none}.yui-skin-sam .yui-navset-left .yui-content{border:1px solid #808080;border-left-color:#243356}.yui-skin-sam .yui-navset-bottom .yui-nav,.yui-skin-sam .yui-navset .yui-navset-bottom .yui-nav{border-width:5px 0 0}.yui-skin-sam .yui-navset .yui-navset-bottom .yui-nav .selected,.yui-skin-sam .yui-navset-bottom .yui-nav .selected{margin:-1px .16em 0 0}.yui-skin-sam .yui-navset .yui-navset-bottom .yui-nav li,.yui-skin-sam .yui-navset-bottom .yui-nav li{padding:0 0 1px 0;vertical-align:top}.yui-skin-sam .yui-navset .yui-navset-bottom .yui-nav a em,.yui-skin-sam .yui-navset-bottom .yui-nav a em{border-width:0 0 1px;top:auto;bottom:-1px}
+.yui-skin-sam .yui-navset-bottom .yui-content,.yui-skin-sam .yui-navset .yui-navset-bottom .yui-content{border:1px solid #808080;border-bottom-color:#243356}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview-loading.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview-loading.gif
new file mode 100644
index 0000000..0bbf3bc
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview-loading.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview-sprite.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview-sprite.gif
new file mode 100644
index 0000000..8fb3f01
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview-sprite.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview.css
new file mode 100644
index 0000000..8fa8da8
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/treeview.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+table.ygtvtable{margin-bottom:0;border:0;border-collapse:collapse}td.ygtvcell{border:0;padding:0}a.ygtvspacer{text-decoration:none;outline-style:none;display:block}.ygtvtn{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -5600px no-repeat;cursor:pointer}.ygtvtm{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -4000px no-repeat}.ygtvtmh,.ygtvtmhh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -4800px no-repeat}.ygtvtp{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -6400px no-repeat}.ygtvtph,.ygtvtphh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -7200px no-repeat}.ygtvln{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -1600px no-repeat;cursor:pointer}.ygtvlm{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 0 no-repeat}.ygtvlmh,.ygtvlmhh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -800px no-repeat}.ygtvlp{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -2400px no-repeat}.ygtvlph,.ygtvlphh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -3200px no-repeat;cursor:pointer}.ygtvloading{width:18px;height:22px;background:url(treeview-loading.gif) 0 0 no-repeat}.ygtvdepthcell{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -8000px no-repeat}.ygtvblankdepthcell{width:18px;height:22px}* html .ygtvchildren{height:2%}.ygtvlabel,.ygtvlabel:link,.ygtvlabel:visited,.ygtvlabel:hover{margin-left:2px;text-decoration:none;background-color:white;cursor:pointer}.ygtvcontent{cursor:default}.ygtvspacer{height:22px;width:18px}.ygtvfocus{background-color:#c0e0e0;border:0}.ygtvfocus .ygtvlabel,.ygtvfocus .ygtvlabel:link,.ygtvfocus .ygtvlabel:visited,.ygtvfocus .ygtvlabel:hover{background-color:#c0e0e0}.ygtvfocus a{outline-style:none}.ygtvok{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -8800px no-repeat}.ygtvok:hover{background:url(treeview-sprite.gif) 0 -8844px no-repeat}.ygtvcancel{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -8822px no-repeat}.ygtvcancel:hover{background:url(treeview-sprite.gif) 0 -8866px no-repeat}.ygtv-label-editor{background-color:#f2f2f2;border:1px solid silver;position:absolute;display:none;overflow:hidden;margin:auto;z-index:9000}.ygtv-edit-TextNode{width:190px}.ygtv-edit-TextNode .ygtvcancel,.ygtv-edit-TextNode .ygtvok{border:0}.ygtv-edit-TextNode .ygtv-button-container{float:right}.ygtv-edit-TextNode .ygtv-input input{width:140px}.ygtv-edit-DateNode .ygtvcancel{border:0}.ygtv-edit-DateNode .ygtvok{display:none}.ygtv-edit-DateNode .ygtv-button-container{text-align:right;margin:auto}.ygtv-highlight .ygtv-highlight1,.ygtv-highlight .ygtv-highlight1 .ygtvlabel{background-color:blue;color:white}.ygtv-highlight .ygtv-highlight2,.ygtv-highlight .ygtv-highlight2 .ygtvlabel{background-color:silver}.ygtv-highlight .ygtv-highlight0 .ygtvfocus .ygtvlabel,.ygtv-highlight .ygtv-highlight1 .ygtvfocus .ygtvlabel,.ygtv-highlight .ygtv-highlight2 .ygtvfocus .ygtvlabel{background-color:#c0e0e0}.ygtv-highlight .ygtvcontent{padding-right:1em}.ygtv-checkbox .ygtv-highlight0 .ygtvcontent{padding-left:1em;background:url(check0.gif) no-repeat}.ygtv-checkbox .ygtv-highlight0 .ygtvfocus.ygtvcontent,.ygtv-checkbox .ygtv-highlight1 .ygtvfocus.ygtvcontent,.ygtv-checkbox .ygtv-highlight2 .ygtvfocus.ygtvcontent{background-color:#c0e0e0}.ygtv-checkbox .ygtv-highlight1 .ygtvcontent{padding-left:1em;background:url(check1.gif) no-repeat}.ygtv-checkbox .ygtv-highlight2 .ygtvcontent{padding-left:1em;background:url(check2.gif) no-repeat}
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/wait.gif b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/wait.gif
new file mode 100644
index 0000000..471c1a4
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/wait.gif
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/assets/skins/sam/yuitest.css b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/yuitest.css
new file mode 100644
index 0000000..7cfa36b
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/assets/skins/sam/yuitest.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+
diff --git a/Websites/bugs.webkit.org/js/yui/autocomplete/autocomplete-min.js b/Websites/bugs.webkit.org/js/yui/autocomplete/autocomplete-min.js
new file mode 100644
index 0000000..5a588b8
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/autocomplete/autocomplete-min.js
@@ -0,0 +1,12 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.widget.DS_JSArray=YAHOO.util.LocalDataSource;YAHOO.widget.DS_JSFunction=YAHOO.util.FunctionDataSource;YAHOO.widget.DS_XHR=function(b,a,d){var c=new YAHOO.util.XHRDataSource(b,d);c._aDeprecatedSchema=a;return c;};YAHOO.widget.DS_ScriptNode=function(b,a,d){var c=new YAHOO.util.ScriptNodeDataSource(b,d);c._aDeprecatedSchema=a;return c;};YAHOO.widget.DS_XHR.TYPE_JSON=YAHOO.util.DataSourceBase.TYPE_JSON;YAHOO.widget.DS_XHR.TYPE_XML=YAHOO.util.DataSourceBase.TYPE_XML;YAHOO.widget.DS_XHR.TYPE_FLAT=YAHOO.util.DataSourceBase.TYPE_TEXT;YAHOO.widget.AutoComplete=function(g,b,j,c){if(g&&b&&j){if(j&&YAHOO.lang.isFunction(j.sendRequest)){this.dataSource=j;}else{return;}this.key=0;var d=j.responseSchema;if(j._aDeprecatedSchema){var k=j._aDeprecatedSchema;if(YAHOO.lang.isArray(k)){if((j.responseType===YAHOO.util.DataSourceBase.TYPE_JSON)||(j.responseType===YAHOO.util.DataSourceBase.TYPE_UNKNOWN)){d.resultsList=k[0];this.key=k[1];d.fields=(k.length<3)?null:k.slice(1);}else{if(j.responseType===YAHOO.util.DataSourceBase.TYPE_XML){d.resultNode=k[0];this.key=k[1];d.fields=k.slice(1);}else{if(j.responseType===YAHOO.util.DataSourceBase.TYPE_TEXT){d.recordDelim=k[0];d.fieldDelim=k[1];}}}j.responseSchema=d;}}if(YAHOO.util.Dom.inDocument(g)){if(YAHOO.lang.isString(g)){this._sName="instance"+YAHOO.widget.AutoComplete._nIndex+" "+g;this._elTextbox=document.getElementById(g);}else{this._sName=(g.id)?"instance"+YAHOO.widget.AutoComplete._nIndex+" "+g.id:"instance"+YAHOO.widget.AutoComplete._nIndex;this._elTextbox=g;}YAHOO.util.Dom.addClass(this._elTextbox,"yui-ac-input");}else{return;}if(YAHOO.util.Dom.inDocument(b)){if(YAHOO.lang.isString(b)){this._elContainer=document.getElementById(b);}else{this._elContainer=b;}if(this._elContainer.style.display=="none"){}var e=this._elContainer.parentNode;var a=e.tagName.toLowerCase();if(a=="div"){YAHOO.util.Dom.addClass(e,"yui-ac");}else{}}else{return;}if(this.dataSource.dataType===YAHOO.util.DataSourceBase.TYPE_LOCAL){this.applyLocalFilter=true;}if(c&&(c.constructor==Object)){for(var i in c){if(i){this[i]=c[i];}}}this._initContainerEl();this._initProps();this._initListEl();this._initContainerHelperEls();var h=this;var f=this._elTextbox;YAHOO.util.Event.addListener(f,"keyup",h._onTextboxKeyUp,h);YAHOO.util.Event.addListener(f,"keydown",h._onTextboxKeyDown,h);YAHOO.util.Event.addListener(f,"focus",h._onTextboxFocus,h);YAHOO.util.Event.addListener(f,"blur",h._onTextboxBlur,h);YAHOO.util.Event.addListener(b,"mouseover",h._onContainerMouseover,h);YAHOO.util.Event.addListener(b,"mouseout",h._onContainerMouseout,h);YAHOO.util.Event.addListener(b,"click",h._onContainerClick,h);YAHOO.util.Event.addListener(b,"scroll",h._onContainerScroll,h);YAHOO.util.Event.addListener(b,"resize",h._onContainerResize,h);YAHOO.util.Event.addListener(f,"keypress",h._onTextboxKeyPress,h);YAHOO.util.Event.addListener(window,"unload",h._onWindowUnload,h);this.textboxFocusEvent=new YAHOO.util.CustomEvent("textboxFocus",this);this.textboxKeyEvent=new YAHOO.util.CustomEvent("textboxKey",this);this.dataRequestEvent=new YAHOO.util.CustomEvent("dataRequest",this);this.dataRequestCancelEvent=new YAHOO.util.CustomEvent("dataRequestCancel",this);this.dataReturnEvent=new YAHOO.util.CustomEvent("dataReturn",this);this.dataErrorEvent=new YAHOO.util.CustomEvent("dataError",this);this.containerPopulateEvent=new YAHOO.util.CustomEvent("containerPopulate",this);this.containerExpandEvent=new YAHOO.util.CustomEvent("containerExpand",this);this.typeAheadEvent=new YAHOO.util.CustomEvent("typeAhead",this);this.itemMouseOverEvent=new YAHOO.util.CustomEvent("itemMouseOver",this);this.itemMouseOutEvent=new YAHOO.util.CustomEvent("itemMouseOut",this);this.itemArrowToEvent=new YAHOO.util.CustomEvent("itemArrowTo",this);this.itemArrowFromEvent=new YAHOO.util.CustomEvent("itemArrowFrom",this);this.itemSelectEvent=new YAHOO.util.CustomEvent("itemSelect",this);this.unmatchedItemSelectEvent=new YAHOO.util.CustomEvent("unmatchedItemSelect",this);this.selectionEnforceEvent=new YAHOO.util.CustomEvent("selectionEnforce",this);this.containerCollapseEvent=new YAHOO.util.CustomEvent("containerCollapse",this);this.textboxBlurEvent=new YAHOO.util.CustomEvent("textboxBlur",this);this.textboxChangeEvent=new YAHOO.util.CustomEvent("textboxChange",this);f.setAttribute("autocomplete","off");YAHOO.widget.AutoComplete._nIndex++;}else{}};YAHOO.widget.AutoComplete.prototype.dataSource=null;YAHOO.widget.AutoComplete.prototype.applyLocalFilter=null;YAHOO.widget.AutoComplete.prototype.queryMatchCase=false;YAHOO.widget.AutoComplete.prototype.queryMatchContains=false;YAHOO.widget.AutoComplete.prototype.queryMatchSubset=false;YAHOO.widget.AutoComplete.prototype.minQueryLength=1;YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed=10;YAHOO.widget.AutoComplete.prototype.queryDelay=0.2;YAHOO.widget.AutoComplete.prototype.typeAheadDelay=0.5;YAHOO.widget.AutoComplete.prototype.queryInterval=500;YAHOO.widget.AutoComplete.prototype.highlightClassName="yui-ac-highlight";YAHOO.widget.AutoComplete.prototype.prehighlightClassName=null;YAHOO.widget.AutoComplete.prototype.delimChar=null;YAHOO.widget.AutoComplete.prototype.autoHighlight=true;YAHOO.widget.AutoComplete.prototype.typeAhead=false;YAHOO.widget.AutoComplete.prototype.animHoriz=false;YAHOO.widget.AutoComplete.prototype.animVert=true;YAHOO.widget.AutoComplete.prototype.animSpeed=0.3;YAHOO.widget.AutoComplete.prototype.forceSelection=false;YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete=true;YAHOO.widget.AutoComplete.prototype.alwaysShowContainer=false;YAHOO.widget.AutoComplete.prototype.useIFrame=false;YAHOO.widget.AutoComplete.prototype.useShadow=false;YAHOO.widget.AutoComplete.prototype.suppressInputUpdate=false;YAHOO.widget.AutoComplete.prototype.resultTypeList=true;YAHOO.widget.AutoComplete.prototype.queryQuestionMark=true;YAHOO.widget.AutoComplete.prototype.autoSnapContainer=true;YAHOO.widget.AutoComplete.prototype.toString=function(){return"AutoComplete "+this._sName;};YAHOO.widget.AutoComplete.prototype.getInputEl=function(){return this._elTextbox;
+};YAHOO.widget.AutoComplete.prototype.getContainerEl=function(){return this._elContainer;};YAHOO.widget.AutoComplete.prototype.isFocused=function(){return this._bFocused;};YAHOO.widget.AutoComplete.prototype.isContainerOpen=function(){return this._bContainerOpen;};YAHOO.widget.AutoComplete.prototype.getListEl=function(){return this._elList;};YAHOO.widget.AutoComplete.prototype.getListItemMatch=function(a){if(a._sResultMatch){return a._sResultMatch;}else{return null;}};YAHOO.widget.AutoComplete.prototype.getListItemData=function(a){if(a._oResultData){return a._oResultData;}else{return null;}};YAHOO.widget.AutoComplete.prototype.getListItemIndex=function(a){if(YAHOO.lang.isNumber(a._nItemIndex)){return a._nItemIndex;}else{return null;}};YAHOO.widget.AutoComplete.prototype.setHeader=function(b){if(this._elHeader){var a=this._elHeader;if(b){a.innerHTML=b;a.style.display="";}else{a.innerHTML="";a.style.display="none";}}};YAHOO.widget.AutoComplete.prototype.setFooter=function(b){if(this._elFooter){var a=this._elFooter;if(b){a.innerHTML=b;a.style.display="";}else{a.innerHTML="";a.style.display="none";}}};YAHOO.widget.AutoComplete.prototype.setBody=function(a){if(this._elBody){var b=this._elBody;YAHOO.util.Event.purgeElement(b,true);if(a){b.innerHTML=a;b.style.display="";}else{b.innerHTML="";b.style.display="none";}this._elList=null;}};YAHOO.widget.AutoComplete.prototype.generateRequest=function(b){var a=this.dataSource.dataType;if(a===YAHOO.util.DataSourceBase.TYPE_XHR){if(!this.dataSource.connMethodPost){b=(this.queryQuestionMark?"?":"")+(this.dataSource.scriptQueryParam||"query")+"="+b+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}else{b=(this.dataSource.scriptQueryParam||"query")+"="+b+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}}else{if(a===YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE){b="&"+(this.dataSource.scriptQueryParam||"query")+"="+b+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}}return b;};YAHOO.widget.AutoComplete.prototype.sendQuery=function(b){this._bFocused=true;var a=(this.delimChar)?this._elTextbox.value+b:b;this._sendQuery(a);};YAHOO.widget.AutoComplete.prototype.snapContainer=function(){var a=this._elTextbox,b=YAHOO.util.Dom.getXY(a);b[1]+=YAHOO.util.Dom.get(a).offsetHeight+2;YAHOO.util.Dom.setXY(this._elContainer,b);};YAHOO.widget.AutoComplete.prototype.expandContainer=function(){this._toggleContainer(true);};YAHOO.widget.AutoComplete.prototype.collapseContainer=function(){this._toggleContainer(false);};YAHOO.widget.AutoComplete.prototype.clearList=function(){var b=this._elList.childNodes,a=b.length-1;for(;a>-1;a--){b[a].style.display="none";}};YAHOO.widget.AutoComplete.prototype.getSubsetMatches=function(e){var d,c,a;for(var b=e.length;b>=this.minQueryLength;b--){a=this.generateRequest(e.substr(0,b));this.dataRequestEvent.fire(this,d,a);c=this.dataSource.getCachedResponse(a);if(c){return this.filterResults.apply(this.dataSource,[e,c,c,{scope:this}]);}}return null;};YAHOO.widget.AutoComplete.prototype.preparseRawResponse=function(c,b,a){var d=((this.responseStripAfter!=="")&&(b.indexOf))?b.indexOf(this.responseStripAfter):-1;if(d!=-1){b=b.substring(0,d);}return b;};YAHOO.widget.AutoComplete.prototype.filterResults=function(l,n,r,m){if(m&&m.argument&&YAHOO.lang.isValue(m.argument.query)){l=m.argument.query;}if(l&&l!==""){r=YAHOO.widget.AutoComplete._cloneObject(r);var j=m.scope,q=this,c=r.results,o=[],b=j.maxResultsDisplayed,k=(q.queryMatchCase||j.queryMatchCase),a=(q.queryMatchContains||j.queryMatchContains);for(var d=0,h=c.length;d<h;d++){var f=c[d];var e=null;if(YAHOO.lang.isString(f)){e=f;}else{if(YAHOO.lang.isArray(f)){e=f[0];}else{if(this.responseSchema.fields){var p=this.responseSchema.fields[0].key||this.responseSchema.fields[0];e=f[p];}else{if(this.key){e=f[this.key];}}}}if(YAHOO.lang.isString(e)){var g=(k)?e.indexOf(decodeURIComponent(l)):e.toLowerCase().indexOf(decodeURIComponent(l).toLowerCase());if((!a&&(g===0))||(a&&(g>-1))){o.push(f);}}if(h>b&&o.length===b){break;}}r.results=o;}else{}return r;};YAHOO.widget.AutoComplete.prototype.handleResponse=function(c,a,b){if((this instanceof YAHOO.widget.AutoComplete)&&this._sName){this._populateList(c,a,b);}};YAHOO.widget.AutoComplete.prototype.doBeforeLoadData=function(c,a,b){return true;};YAHOO.widget.AutoComplete.prototype.formatResult=function(b,d,a){var c=(a)?a:"";return c;};YAHOO.widget.AutoComplete.prototype.formatEscapedResult=function(c,d,b){var a=(b)?b:"";return YAHOO.lang.escapeHTML(a);};YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer=function(d,a,c,b){return true;};YAHOO.widget.AutoComplete.prototype.destroy=function(){var b=this.toString();var a=this._elTextbox;var d=this._elContainer;this.textboxFocusEvent.unsubscribeAll();this.textboxKeyEvent.unsubscribeAll();this.dataRequestEvent.unsubscribeAll();this.dataReturnEvent.unsubscribeAll();this.dataErrorEvent.unsubscribeAll();this.containerPopulateEvent.unsubscribeAll();this.containerExpandEvent.unsubscribeAll();this.typeAheadEvent.unsubscribeAll();this.itemMouseOverEvent.unsubscribeAll();this.itemMouseOutEvent.unsubscribeAll();this.itemArrowToEvent.unsubscribeAll();this.itemArrowFromEvent.unsubscribeAll();this.itemSelectEvent.unsubscribeAll();this.unmatchedItemSelectEvent.unsubscribeAll();this.selectionEnforceEvent.unsubscribeAll();this.containerCollapseEvent.unsubscribeAll();this.textboxBlurEvent.unsubscribeAll();this.textboxChangeEvent.unsubscribeAll();YAHOO.util.Event.purgeElement(a,true);YAHOO.util.Event.purgeElement(d,true);d.innerHTML="";for(var c in this){if(YAHOO.lang.hasOwnProperty(this,c)){this[c]=null;}}};YAHOO.widget.AutoComplete.prototype.textboxFocusEvent=null;YAHOO.widget.AutoComplete.prototype.textboxKeyEvent=null;YAHOO.widget.AutoComplete.prototype.dataRequestEvent=null;YAHOO.widget.AutoComplete.prototype.dataRequestCancelEvent=null;YAHOO.widget.AutoComplete.prototype.dataReturnEvent=null;YAHOO.widget.AutoComplete.prototype.dataErrorEvent=null;
+YAHOO.widget.AutoComplete.prototype.containerPopulateEvent=null;YAHOO.widget.AutoComplete.prototype.containerExpandEvent=null;YAHOO.widget.AutoComplete.prototype.typeAheadEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowToEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent=null;YAHOO.widget.AutoComplete.prototype.itemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent=null;YAHOO.widget.AutoComplete.prototype.containerCollapseEvent=null;YAHOO.widget.AutoComplete.prototype.textboxBlurEvent=null;YAHOO.widget.AutoComplete.prototype.textboxChangeEvent=null;YAHOO.widget.AutoComplete._nIndex=0;YAHOO.widget.AutoComplete.prototype._sName=null;YAHOO.widget.AutoComplete.prototype._elTextbox=null;YAHOO.widget.AutoComplete.prototype._elContainer=null;YAHOO.widget.AutoComplete.prototype._elContent=null;YAHOO.widget.AutoComplete.prototype._elHeader=null;YAHOO.widget.AutoComplete.prototype._elBody=null;YAHOO.widget.AutoComplete.prototype._elFooter=null;YAHOO.widget.AutoComplete.prototype._elShadow=null;YAHOO.widget.AutoComplete.prototype._elIFrame=null;YAHOO.widget.AutoComplete.prototype._bFocused=false;YAHOO.widget.AutoComplete.prototype._oAnim=null;YAHOO.widget.AutoComplete.prototype._bContainerOpen=false;YAHOO.widget.AutoComplete.prototype._bOverContainer=false;YAHOO.widget.AutoComplete.prototype._elList=null;YAHOO.widget.AutoComplete.prototype._nDisplayedItems=0;YAHOO.widget.AutoComplete.prototype._sCurQuery=null;YAHOO.widget.AutoComplete.prototype._sPastSelections="";YAHOO.widget.AutoComplete.prototype._sInitInputValue=null;YAHOO.widget.AutoComplete.prototype._elCurListItem=null;YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem=null;YAHOO.widget.AutoComplete.prototype._bItemSelected=false;YAHOO.widget.AutoComplete.prototype._nKeyCode=null;YAHOO.widget.AutoComplete.prototype._nDelayID=-1;YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID=-1;YAHOO.widget.AutoComplete.prototype._iFrameSrc="javascript:false;";YAHOO.widget.AutoComplete.prototype._queryInterval=null;YAHOO.widget.AutoComplete.prototype._sLastTextboxValue=null;YAHOO.widget.AutoComplete.prototype._initProps=function(){var b=this.minQueryLength;if(!YAHOO.lang.isNumber(b)){this.minQueryLength=1;}var e=this.maxResultsDisplayed;if(!YAHOO.lang.isNumber(e)||(e<1)){this.maxResultsDisplayed=10;}var f=this.queryDelay;if(!YAHOO.lang.isNumber(f)||(f<0)){this.queryDelay=0.2;}var c=this.typeAheadDelay;if(!YAHOO.lang.isNumber(c)||(c<0)){this.typeAheadDelay=0.2;}var a=this.delimChar;if(YAHOO.lang.isString(a)&&(a.length>0)){this.delimChar=[a];}else{if(!YAHOO.lang.isArray(a)){this.delimChar=null;}}var d=this.animSpeed;if((this.animHoriz||this.animVert)&&YAHOO.util.Anim){if(!YAHOO.lang.isNumber(d)||(d<0)){this.animSpeed=0.3;}if(!this._oAnim){this._oAnim=new YAHOO.util.Anim(this._elContent,{},this.animSpeed);}else{this._oAnim.duration=this.animSpeed;}}if(this.forceSelection&&a){}};YAHOO.widget.AutoComplete.prototype._initContainerHelperEls=function(){if(this.useShadow&&!this._elShadow){var a=document.createElement("div");a.className="yui-ac-shadow";a.style.width=0;a.style.height=0;this._elShadow=this._elContainer.appendChild(a);}if(this.useIFrame&&!this._elIFrame){var b=document.createElement("iframe");b.src=this._iFrameSrc;b.frameBorder=0;b.scrolling="no";b.style.position="absolute";b.style.width=0;b.style.height=0;b.style.padding=0;b.tabIndex=-1;b.role="presentation";b.title="Presentational iframe shim";this._elIFrame=this._elContainer.appendChild(b);}};YAHOO.widget.AutoComplete.prototype._initContainerEl=function(){YAHOO.util.Dom.addClass(this._elContainer,"yui-ac-container");if(!this._elContent){var c=document.createElement("div");c.className="yui-ac-content";c.style.display="none";this._elContent=this._elContainer.appendChild(c);var b=document.createElement("div");b.className="yui-ac-hd";b.style.display="none";this._elHeader=this._elContent.appendChild(b);var d=document.createElement("div");d.className="yui-ac-bd";this._elBody=this._elContent.appendChild(d);var a=document.createElement("div");a.className="yui-ac-ft";a.style.display="none";this._elFooter=this._elContent.appendChild(a);}else{}};YAHOO.widget.AutoComplete.prototype._initListEl=function(){var c=this.maxResultsDisplayed,a=this._elList||document.createElement("ul"),b;while(a.childNodes.length<c){b=document.createElement("li");b.style.display="none";b._nItemIndex=a.childNodes.length;a.appendChild(b);}if(!this._elList){var d=this._elBody;YAHOO.util.Event.purgeElement(d,true);d.innerHTML="";this._elList=d.appendChild(a);}this._elBody.style.display="";};YAHOO.widget.AutoComplete.prototype._focus=function(){var a=this;setTimeout(function(){try{a._elTextbox.focus();}catch(b){}},0);};YAHOO.widget.AutoComplete.prototype._enableIntervalDetection=function(){var a=this;if(!a._queryInterval&&a.queryInterval){a._queryInterval=setInterval(function(){a._onInterval();},a.queryInterval);}};YAHOO.widget.AutoComplete.prototype.enableIntervalDetection=YAHOO.widget.AutoComplete.prototype._enableIntervalDetection;YAHOO.widget.AutoComplete.prototype._onInterval=function(){var a=this._elTextbox.value;var b=this._sLastTextboxValue;if(a!=b){this._sLastTextboxValue=a;this._sendQuery(a);}};YAHOO.widget.AutoComplete.prototype._clearInterval=function(){if(this._queryInterval){clearInterval(this._queryInterval);this._queryInterval=null;}};YAHOO.widget.AutoComplete.prototype._isIgnoreKey=function(a){if((a==9)||(a==13)||(a==16)||(a==17)||(a>=18&&a<=20)||(a==27)||(a>=33&&a<=35)||(a>=36&&a<=40)||(a>=44&&a<=45)||(a==229)){return true;}return false;};YAHOO.widget.AutoComplete.prototype._sendQuery=function(d){if(this.minQueryLength<0){this._toggleContainer(false);return;}if(this.delimChar){var a=this._extractQuery(d);d=a.query;this._sPastSelections=a.previous;}if((d&&(d.length<this.minQueryLength))||(!d&&this.minQueryLength>0)){if(this._nDelayID!=-1){clearTimeout(this._nDelayID);
+}this._toggleContainer(false);return;}d=encodeURIComponent(d);this._nDelayID=-1;if(this.dataSource.queryMatchSubset||this.queryMatchSubset){var c=this.getSubsetMatches(d);if(c){this.handleResponse(d,c,{query:d});return;}}if(this.dataSource.responseStripAfter){this.dataSource.doBeforeParseData=this.preparseRawResponse;}if(this.applyLocalFilter){this.dataSource.doBeforeCallback=this.filterResults;}var b=this.generateRequest(d);if(b!==undefined){this.dataRequestEvent.fire(this,d,b);this.dataSource.sendRequest(b,{success:this.handleResponse,failure:this.handleResponse,scope:this,argument:{query:d}});}else{this.dataRequestCancelEvent.fire(this,d);}};YAHOO.widget.AutoComplete.prototype._populateListItem=function(b,a,c){b.innerHTML=this.formatResult(a,c,b._sResultMatch);};YAHOO.widget.AutoComplete.prototype._populateList=function(n,f,c){if(this._nTypeAheadDelayID!=-1){clearTimeout(this._nTypeAheadDelayID);}n=(c&&c.query)?c.query:n;var h=this.doBeforeLoadData(n,f,c);if(h&&!f.error){this.dataReturnEvent.fire(this,n,f.results);if(this._bFocused){var p=decodeURIComponent(n);this._sCurQuery=p;this._bItemSelected=false;var u=f.results,a=Math.min(u.length,this.maxResultsDisplayed),m=(this.dataSource.responseSchema.fields)?(this.dataSource.responseSchema.fields[0].key||this.dataSource.responseSchema.fields[0]):0;if(a>0){if(!this._elList||(this._elList.childNodes.length<a)){this._initListEl();}this._initContainerHelperEls();var l=this._elList.childNodes;for(var t=a-1;t>=0;t--){var s=l[t],e=u[t];if(this.resultTypeList){var b=[];b[0]=(YAHOO.lang.isString(e))?e:e[m]||e[this.key];var o=this.dataSource.responseSchema.fields;if(YAHOO.lang.isArray(o)&&(o.length>1)){for(var q=1,v=o.length;q<v;q++){b[b.length]=e[o[q].key||o[q]];}}else{if(YAHOO.lang.isArray(e)){b=e;}else{if(YAHOO.lang.isString(e)){b=[e];}else{b[1]=e;}}}e=b;}s._sResultMatch=(YAHOO.lang.isString(e))?e:(YAHOO.lang.isArray(e))?e[0]:(e[m]||"");s._oResultData=e;this._populateListItem(s,e,p);s.style.display="";}if(a<l.length){var g;for(var r=l.length-1;r>=a;r--){g=l[r];g.style.display="none";}}this._nDisplayedItems=a;this.containerPopulateEvent.fire(this,n,u);if(this.autoHighlight){var d=this._elList.firstChild;this._toggleHighlight(d,"to");this.itemArrowToEvent.fire(this,d);this._typeAhead(d,n);}else{this._toggleHighlight(this._elCurListItem,"from");}h=this._doBeforeExpandContainer(this._elTextbox,this._elContainer,n,u);this._toggleContainer(h);}else{this._toggleContainer(false);}return;}}else{this.dataErrorEvent.fire(this,n,f);}};YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer=function(d,a,c,b){if(this.autoSnapContainer){this.snapContainer();}return this.doBeforeExpandContainer(d,a,c,b);};YAHOO.widget.AutoComplete.prototype._clearSelection=function(){var a=(this.delimChar)?this._extractQuery(this._elTextbox.value):{previous:"",query:this._elTextbox.value};this._elTextbox.value=a.previous;this.selectionEnforceEvent.fire(this,a.query);};YAHOO.widget.AutoComplete.prototype._textMatchesOption=function(){var a=null;for(var b=0;b<this._nDisplayedItems;b++){var c=this._elList.childNodes[b];var d=(""+c._sResultMatch).toLowerCase();if(d==this._sCurQuery.toLowerCase()){a=c;break;}}return(a);};YAHOO.widget.AutoComplete.prototype._typeAhead=function(b,d){if(!this.typeAhead||(this._nKeyCode==8)){return;}var a=this,c=this._elTextbox;if(c.setSelectionRange||c.createTextRange){this._nTypeAheadDelayID=setTimeout(function(){var f=c.value.length;a._updateValue(b);var g=c.value.length;a._selectText(c,f,g);var e=c.value.substr(f,g);a._sCurQuery=b._sResultMatch;a.typeAheadEvent.fire(a,d,e);},(this.typeAheadDelay*1000));}};YAHOO.widget.AutoComplete.prototype._selectText=function(d,a,b){if(d.setSelectionRange){d.setSelectionRange(a,b);}else{if(d.createTextRange){var c=d.createTextRange();c.moveStart("character",a);c.moveEnd("character",b-d.value.length);c.select();}else{d.select();}}};YAHOO.widget.AutoComplete.prototype._extractQuery=function(h){var c=this.delimChar,f=-1,g,e,b=c.length-1,d;for(;b>=0;b--){g=h.lastIndexOf(c[b]);if(g>f){f=g;}}if(c[b]==" "){for(var a=c.length-1;a>=0;a--){if(h[f-1]==c[a]){f--;break;}}}if(f>-1){e=f+1;while(h.charAt(e)==" "){e+=1;}d=h.substring(0,e);h=h.substr(e);}else{d="";}return{previous:d,query:h};};YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers=function(d){var e=this._elContent.offsetWidth+"px";var b=this._elContent.offsetHeight+"px";if(this.useIFrame&&this._elIFrame){var c=this._elIFrame;if(d){c.style.width=e;c.style.height=b;c.style.padding="";}else{c.style.width=0;c.style.height=0;c.style.padding=0;}}if(this.useShadow&&this._elShadow){var a=this._elShadow;if(d){a.style.width=e;a.style.height=b;}else{a.style.width=0;a.style.height=0;}}};YAHOO.widget.AutoComplete.prototype._toggleContainer=function(i){var d=this._elContainer;if(this.alwaysShowContainer&&this._bContainerOpen){return;}if(!i){this._toggleHighlight(this._elCurListItem,"from");this._nDisplayedItems=0;this._sCurQuery=null;if(this._elContent.style.display=="none"){return;}}var a=this._oAnim;if(a&&a.getEl()&&(this.animHoriz||this.animVert)){if(a.isAnimated()){a.stop(true);}var g=this._elContent.cloneNode(true);d.appendChild(g);g.style.top="-9000px";g.style.width="";g.style.height="";g.style.display="";var f=g.offsetWidth;var c=g.offsetHeight;var b=(this.animHoriz)?0:f;var e=(this.animVert)?0:c;a.attributes=(i)?{width:{to:f},height:{to:c}}:{width:{to:b},height:{to:e}};if(i&&!this._bContainerOpen){this._elContent.style.width=b+"px";this._elContent.style.height=e+"px";}else{this._elContent.style.width=f+"px";this._elContent.style.height=c+"px";}d.removeChild(g);g=null;var h=this;var j=function(){a.onComplete.unsubscribeAll();if(i){h._toggleContainerHelpers(true);h._bContainerOpen=i;h.containerExpandEvent.fire(h);}else{h._elContent.style.display="none";h._bContainerOpen=i;h.containerCollapseEvent.fire(h);}};this._toggleContainerHelpers(false);this._elContent.style.display="";a.onComplete.subscribe(j);a.animate();}else{if(i){this._elContent.style.display="";this._toggleContainerHelpers(true);
+this._bContainerOpen=i;this.containerExpandEvent.fire(this);}else{this._toggleContainerHelpers(false);this._elContent.style.display="none";this._bContainerOpen=i;this.containerCollapseEvent.fire(this);}}};YAHOO.widget.AutoComplete.prototype._toggleHighlight=function(a,c){if(a){var b=this.highlightClassName;if(this._elCurListItem){YAHOO.util.Dom.removeClass(this._elCurListItem,b);this._elCurListItem=null;}if((c=="to")&&b){YAHOO.util.Dom.addClass(a,b);this._elCurListItem=a;}}};YAHOO.widget.AutoComplete.prototype._togglePrehighlight=function(b,c){var a=this.prehighlightClassName;if(this._elCurPrehighlightItem){YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem,a);}if(b==this._elCurListItem){return;}if((c=="mouseover")&&a){YAHOO.util.Dom.addClass(b,a);this._elCurPrehighlightItem=b;}else{YAHOO.util.Dom.removeClass(b,a);}};YAHOO.widget.AutoComplete.prototype._updateValue=function(c){if(!this.suppressInputUpdate){var f=this._elTextbox;var e=(this.delimChar)?(this.delimChar[0]||this.delimChar):null;var b=c._sResultMatch;var d="";if(e){d=this._sPastSelections;d+=b+e;if(e!=" "){d+=" ";}}else{d=b;}f.value=d;if(f.type=="textarea"){f.scrollTop=f.scrollHeight;}var a=f.value.length;this._selectText(f,a,a);this._elCurListItem=c;}};YAHOO.widget.AutoComplete.prototype._selectItem=function(a){this._bItemSelected=true;this._updateValue(a);this._sPastSelections=this._elTextbox.value;this._clearInterval();this.itemSelectEvent.fire(this,a,a._oResultData);this._toggleContainer(false);};YAHOO.widget.AutoComplete.prototype._jumpSelection=function(){if(this._elCurListItem){this._selectItem(this._elCurListItem);}else{this._toggleContainer(false);}};YAHOO.widget.AutoComplete.prototype._moveSelection=function(g){if(this._bContainerOpen){var h=this._elCurListItem,d=-1;if(h){d=h._nItemIndex;}var e=(g==40)?(d+1):(d-1);if(e<-2||e>=this._nDisplayedItems){return;}if(h){this._toggleHighlight(h,"from");this.itemArrowFromEvent.fire(this,h);}if(e==-1){if(this.delimChar){this._elTextbox.value=this._sPastSelections+this._sCurQuery;}else{this._elTextbox.value=this._sCurQuery;}return;}if(e==-2){this._toggleContainer(false);return;}var f=this._elList.childNodes[e],b=this._elContent,c=YAHOO.util.Dom.getStyle(b,"overflow"),i=YAHOO.util.Dom.getStyle(b,"overflowY"),a=((c=="auto")||(c=="scroll")||(i=="auto")||(i=="scroll"));if(a&&(e>-1)&&(e<this._nDisplayedItems)){if(g==40){if((f.offsetTop+f.offsetHeight)>(b.scrollTop+b.offsetHeight)){b.scrollTop=(f.offsetTop+f.offsetHeight)-b.offsetHeight;}else{if((f.offsetTop+f.offsetHeight)<b.scrollTop){b.scrollTop=f.offsetTop;}}}else{if(f.offsetTop<b.scrollTop){this._elContent.scrollTop=f.offsetTop;}else{if(f.offsetTop>(b.scrollTop+b.offsetHeight)){this._elContent.scrollTop=(f.offsetTop+f.offsetHeight)-b.offsetHeight;}}}}this._toggleHighlight(f,"to");this.itemArrowToEvent.fire(this,f);if(this.typeAhead){this._updateValue(f);this._sCurQuery=f._sResultMatch;}}};YAHOO.widget.AutoComplete.prototype._onContainerMouseover=function(a,c){var d=YAHOO.util.Event.getTarget(a);var b=d.nodeName.toLowerCase();while(d&&(b!="table")){switch(b){case"body":return;case"li":if(c.prehighlightClassName){c._togglePrehighlight(d,"mouseover");}else{c._toggleHighlight(d,"to");}c.itemMouseOverEvent.fire(c,d);break;case"div":if(YAHOO.util.Dom.hasClass(d,"yui-ac-container")){c._bOverContainer=true;return;}break;default:break;}d=d.parentNode;if(d){b=d.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerMouseout=function(a,c){var d=YAHOO.util.Event.getTarget(a);var b=d.nodeName.toLowerCase();while(d&&(b!="table")){switch(b){case"body":return;case"li":if(c.prehighlightClassName){c._togglePrehighlight(d,"mouseout");}else{c._toggleHighlight(d,"from");}c.itemMouseOutEvent.fire(c,d);break;case"ul":c._toggleHighlight(c._elCurListItem,"to");break;case"div":if(YAHOO.util.Dom.hasClass(d,"yui-ac-container")){c._bOverContainer=false;return;}break;default:break;}d=d.parentNode;if(d){b=d.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerClick=function(a,c){var d=YAHOO.util.Event.getTarget(a);var b=d.nodeName.toLowerCase();while(d&&(b!="table")){switch(b){case"body":return;case"li":c._toggleHighlight(d,"to");c._selectItem(d);return;default:break;}d=d.parentNode;if(d){b=d.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerScroll=function(a,b){b._focus();};YAHOO.widget.AutoComplete.prototype._onContainerResize=function(a,b){b._toggleContainerHelpers(b._bContainerOpen);};YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown=function(a,b){var c=a.keyCode;if(b._nTypeAheadDelayID!=-1){clearTimeout(b._nTypeAheadDelayID);}switch(c){case 9:if(!YAHOO.env.ua.opera&&(navigator.userAgent.toLowerCase().indexOf("mac")==-1)||(YAHOO.env.ua.webkit>420)){if(b._elCurListItem){if(b.delimChar&&(b._nKeyCode!=c)){if(b._bContainerOpen){YAHOO.util.Event.stopEvent(a);}}b._selectItem(b._elCurListItem);}else{b._toggleContainer(false);}}break;case 13:if(!YAHOO.env.ua.opera&&(navigator.userAgent.toLowerCase().indexOf("mac")==-1)||(YAHOO.env.ua.webkit>420)){if(b._elCurListItem){if(b._nKeyCode!=c){if(b._bContainerOpen){YAHOO.util.Event.stopEvent(a);}}b._selectItem(b._elCurListItem);}else{b._toggleContainer(false);}}break;case 27:b._toggleContainer(false);return;case 39:b._jumpSelection();break;case 38:if(b._bContainerOpen){YAHOO.util.Event.stopEvent(a);b._moveSelection(c);}break;case 40:if(b._bContainerOpen){YAHOO.util.Event.stopEvent(a);b._moveSelection(c);}break;default:b._bItemSelected=false;b._toggleHighlight(b._elCurListItem,"from");b.textboxKeyEvent.fire(b,c);break;}if(c===18){b._enableIntervalDetection();}b._nKeyCode=c;};YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress=function(a,b){var c=a.keyCode;if(YAHOO.env.ua.opera||(navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&(YAHOO.env.ua.webkit<420)){switch(c){case 9:if(b._bContainerOpen){if(b.delimChar){YAHOO.util.Event.stopEvent(a);}if(b._elCurListItem){b._selectItem(b._elCurListItem);}else{b._toggleContainer(false);}}break;case 13:if(b._bContainerOpen){YAHOO.util.Event.stopEvent(a);
+if(b._elCurListItem){b._selectItem(b._elCurListItem);}else{b._toggleContainer(false);}}break;default:break;}}else{if(c==229){b._enableIntervalDetection();}}};YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp=function(a,c){var b=this.value;c._initProps();var d=a.keyCode;if(c._isIgnoreKey(d)){return;}if(c._nDelayID!=-1){clearTimeout(c._nDelayID);}c._nDelayID=setTimeout(function(){c._sendQuery(b);},(c.queryDelay*1000));};YAHOO.widget.AutoComplete.prototype._onTextboxFocus=function(a,b){if(!b._bFocused){b._elTextbox.setAttribute("autocomplete","off");b._bFocused=true;b._sInitInputValue=b._elTextbox.value;b.textboxFocusEvent.fire(b);}};YAHOO.widget.AutoComplete.prototype._onTextboxBlur=function(a,c){if(!c._bOverContainer||(c._nKeyCode==9)){if(!c._bItemSelected){var b=c._textMatchesOption();if(!c._bContainerOpen||(c._bContainerOpen&&(b===null))){if(c.forceSelection){c._clearSelection();}else{c.unmatchedItemSelectEvent.fire(c,c._sCurQuery);}}else{if(c.forceSelection){c._selectItem(b);}}}c._clearInterval();c._bFocused=false;if(c._sInitInputValue!==c._elTextbox.value){c.textboxChangeEvent.fire(c);}c.textboxBlurEvent.fire(c);c._toggleContainer(false);}else{c._focus();}};YAHOO.widget.AutoComplete.prototype._onWindowUnload=function(a,b){if(b&&b._elTextbox&&b.allowBrowserAutocomplete){b._elTextbox.setAttribute("autocomplete","on");}};YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery=function(a){return this.generateRequest(a);};YAHOO.widget.AutoComplete.prototype.getListItems=function(){var c=[],b=this._elList.childNodes;for(var a=b.length-1;a>=0;a--){c[a]=b[a];}return c;};YAHOO.widget.AutoComplete._cloneObject=function(d){if(!YAHOO.lang.isValue(d)){return d;}var f={};if(YAHOO.lang.isFunction(d)){f=d;}else{if(YAHOO.lang.isArray(d)){var e=[];for(var c=0,b=d.length;c<b;c++){e[c]=YAHOO.widget.AutoComplete._cloneObject(d[c]);}f=e;}else{if(YAHOO.lang.isObject(d)){for(var a in d){if(YAHOO.lang.hasOwnProperty(d,a)){if(YAHOO.lang.isValue(d[a])&&YAHOO.lang.isObject(d[a])||YAHOO.lang.isArray(d[a])){f[a]=YAHOO.widget.AutoComplete._cloneObject(d[a]);}else{f[a]=d[a];}}}}else{f=d;}}}return f;};YAHOO.register("autocomplete",YAHOO.widget.AutoComplete,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/base/base-min.css b/Websites/bugs.webkit.org/js/yui/base/base-min.css
new file mode 100644
index 0000000..f71b9a5
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/base/base-min.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+body{margin:10px}h1{font-size:138.5%}h2{font-size:123.1%}h3{font-size:108%}h1,h2,h3{margin:1em 0}h1,h2,h3,h4,h5,h6,strong,dt{font-weight:bold}optgroup{font-weight:normal}abbr,acronym{border-bottom:1px dotted #000;cursor:help}em{font-style:italic}del{text-decoration:line-through}blockquote,ul,ol,dl{margin:1em}ol,ul,dl{margin-left:2em}ol{list-style:decimal outside}ul{list-style:disc outside}dl dd{margin-left:1em}th,td{border:1px solid #000;padding:.5em}th{font-weight:bold;text-align:center}caption{margin-bottom:.5em;text-align:center}sup{vertical-align:super}sub{vertical-align:sub}p,fieldset,table,pre{margin-bottom:1em}button,input[type="checkbox"],input[type="radio"],input[type="reset"],input[type="submit"]{padding:1px}img{-ms-interpolation-mode:bicubic}
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/base/base.css b/Websites/bugs.webkit.org/js/yui/base/base.css
new file mode 100644
index 0000000..557c75e
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/base/base.css
@@ -0,0 +1,137 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+/**
+ * YUI Base
+ * @module base
+ * @namespace yui-
+ * @requires reset, fonts
+*/
+
+body {
+ /* For breathing room between content and viewport. */
+ margin:10px;
+}
+
+h1 {
+ /* 18px via YUI Fonts CSS foundation. */
+ font-size: 138.5%;
+}
+
+h2 {
+ /* 16px via YUI Fonts CSS foundation. */
+ font-size: 123.1%;
+}
+
+h3 {
+ /* 14px via YUI Fonts CSS foundation. */
+ font-size: 108%;
+}
+
+h1,h2,h3 {
+ /* Top & bottom margin based on font size. */
+ margin: 1em 0;
+}
+
+h1,h2,h3,h4,h5,h6,strong,dt {
+ /* Bringing boldness back to headers and the strong element. */
+ font-weight: bold;
+}
+optgroup {
+ font-weight:normal;
+}
+
+abbr,acronym {
+ /* Indicating to users that more info is available. */
+ border-bottom: 1px dotted #000;
+ cursor: help;
+}
+
+em {
+ /* Bringing italics back to the em element. */
+ font-style: italic;
+}
+
+del {
+ /* Striking deleted phrases. */
+ text-decoration: line-through;
+}
+
+blockquote,ul,ol,dl {
+ /* Giving blockquotes and lists room to breath. */
+ margin: 1em;
+}
+
+ol,ul,dl {
+ /* Bringing lists on to the page with breathing room. */
+ margin-left: 2em;
+}
+
+ol {
+ /* Giving OL's LIs generated numbers. */
+ list-style: decimal outside;
+}
+
+ul {
+ /* Giving UL's LIs generated disc markers. */
+ list-style: disc outside;
+}
+
+dl dd {
+ /* Giving DD default indent. */
+ margin-left: 1em;
+}
+
+th,td {
+ /* Borders and padding to make the table readable. */
+ border: 1px solid #000;
+ padding: .5em;
+}
+
+th {
+ /* Distinguishing table headers from data cells. */
+ font-weight: bold;
+ text-align: center;
+}
+
+caption {
+ /* Coordinated margin to match cell's padding. */
+ margin-bottom: .5em;
+ /* Centered so it doesn't blend in to other content. */
+ text-align: center;
+}
+
+sup {
+ /* to preserve line-height and selector appearance */
+ vertical-align: super;
+}
+
+sub {
+ /* to preserve line-height and selector appearance */
+ vertical-align: sub;
+}
+
+p,
+fieldset,
+table,
+pre {
+ /* So things don't run into each other. */
+ margin-bottom: 1em;
+}
+/* Opera requires 1px of padding to render with contemporary native chrome */
+button,
+input[type="checkbox"],
+input[type="radio"],
+input[type="reset"],
+input[type="submit"] {
+ padding:1px;
+}
+
+/* make IE scale images properly */
+/* see http://code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ */
+ img {
+ -ms-interpolation-mode:bicubic;
+}
diff --git a/Websites/bugs.webkit.org/js/yui/button/button-min.js b/Websites/bugs.webkit.org/js/yui/button/button-min.js
new file mode 100644
index 0000000..15411fc
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/button/button-min.js
@@ -0,0 +1,11 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var G=YAHOO.util.Dom,M=YAHOO.util.Event,I=YAHOO.lang,L=YAHOO.env.ua,B=YAHOO.widget.Overlay,J=YAHOO.widget.Menu,D={},K=null,E=null,C=null;function F(O,N,R,P){var S,Q;if(I.isString(O)&&I.isString(N)){if(L.ie&&(L.ie<9)){Q='<input type="'+O+'" name="'+N+'"';if(P){Q+=" checked";}Q+=">";S=document.createElement(Q);S.value=R;}else{S=document.createElement("input");S.name=N;S.type=O;S.value=R;if(P){S.checked=true;}}}return S;}function H(O,V){var N=O.nodeName.toUpperCase(),S=(this.CLASS_NAME_PREFIX+this.CSS_CLASS_NAME),T=this,U,P,Q;function W(X){if(!(X in V)){U=O.getAttributeNode(X);if(U&&("value" in U)){V[X]=U.value;}}}function R(){W("type");if(V.type=="button"){V.type="push";}if(!("disabled" in V)){V.disabled=O.disabled;}W("name");W("value");W("title");}switch(N){case"A":V.type="link";W("href");W("target");break;case"INPUT":R();if(!("checked" in V)){V.checked=O.checked;}break;case"BUTTON":R();P=O.parentNode.parentNode;if(G.hasClass(P,S+"-checked")){V.checked=true;}if(G.hasClass(P,S+"-disabled")){V.disabled=true;}O.removeAttribute("value");O.setAttribute("type","button");break;}O.removeAttribute("id");O.removeAttribute("name");if(!("tabindex" in V)){V.tabindex=O.tabIndex;}if(!("label" in V)){Q=N=="INPUT"?O.value:O.innerHTML;if(Q&&Q.length>0){V.label=Q;}}}function A(P){var O=P.attributes,N=O.srcelement,R=N.nodeName.toUpperCase(),Q=this;if(R==this.NODE_NAME){P.element=N;P.id=N.id;G.getElementsBy(function(S){switch(S.nodeName.toUpperCase()){case"BUTTON":case"A":case"INPUT":H.call(Q,S,O);break;}},"*",N);}else{switch(R){case"BUTTON":case"A":case"INPUT":H.call(this,N,O);break;}}}YAHOO.widget.Button=function(R,O){if(!B&&YAHOO.widget.Overlay){B=YAHOO.widget.Overlay;}if(!J&&YAHOO.widget.Menu){J=YAHOO.widget.Menu;}var Q=YAHOO.widget.Button.superclass.constructor,P,N;if(arguments.length==1&&!I.isString(R)&&!R.nodeName){if(!R.id){R.id=G.generateId();}Q.call(this,(this.createButtonElement(R.type)),R);}else{P={element:null,attributes:(O||{})};if(I.isString(R)){N=G.get(R);if(N){if(!P.attributes.id){P.attributes.id=R;}P.attributes.srcelement=N;A.call(this,P);if(!P.element){P.element=this.createButtonElement(P.attributes.type);}Q.call(this,P.element,P.attributes);}}else{if(R.nodeName){if(!P.attributes.id){if(R.id){P.attributes.id=R.id;}else{P.attributes.id=G.generateId();}}P.attributes.srcelement=R;A.call(this,P);if(!P.element){P.element=this.createButtonElement(P.attributes.type);}Q.call(this,P.element,P.attributes);}}}};YAHOO.extend(YAHOO.widget.Button,YAHOO.util.Element,{_button:null,_menu:null,_hiddenFields:null,_onclickAttributeValue:null,_activationKeyPressed:false,_activationButtonPressed:false,_hasKeyEventHandlers:false,_hasMouseEventHandlers:false,_nOptionRegionX:0,CLASS_NAME_PREFIX:"yui-",NODE_NAME:"SPAN",CHECK_ACTIVATION_KEYS:[32],ACTIVATION_KEYS:[13,32],OPTION_AREA_WIDTH:20,CSS_CLASS_NAME:"button",_setType:function(N){if(N=="split"){this.on("option",this._onOption);}},_setLabel:function(O){this._button.innerHTML=O;var P,N=L.gecko;if(N&&N<1.9&&G.inDocument(this.get("element"))){P=(this.CLASS_NAME_PREFIX+this.CSS_CLASS_NAME);this.removeClass(P);I.later(0,this,this.addClass,P);}},_setTabIndex:function(N){this._button.tabIndex=N;},_setTitle:function(N){if(this.get("type")!="link"){this._button.title=N;}},_setDisabled:function(N){if(this.get("type")!="link"){if(N){if(this._menu){this._menu.hide();}if(this.hasFocus()){this.blur();}this._button.setAttribute("disabled","disabled");this.addStateCSSClasses("disabled");this.removeStateCSSClasses("hover");this.removeStateCSSClasses("active");this.removeStateCSSClasses("focus");}else{this._button.removeAttribute("disabled");this.removeStateCSSClasses("disabled");}}},_setHref:function(N){if(this.get("type")=="link"){this._button.href=N;}},_setTarget:function(N){if(this.get("type")=="link"){this._button.setAttribute("target",N);}},_setChecked:function(N){var O=this.get("type");if(O=="checkbox"||O=="radio"){if(N){this.addStateCSSClasses("checked");}else{this.removeStateCSSClasses("checked");}}},_setMenu:function(U){var P=this.get("lazyloadmenu"),R=this.get("element"),N,W=false,X,O,Q;function V(){X.render(R.parentNode);this.removeListener("appendTo",V);}function T(){X.cfg.queueProperty("container",R.parentNode);this.removeListener("appendTo",T);}function S(){var Y;if(X){G.addClass(X.element,this.get("menuclassname"));G.addClass(X.element,this.CLASS_NAME_PREFIX+this.get("type")+"-button-menu");X.showEvent.subscribe(this._onMenuShow,null,this);X.hideEvent.subscribe(this._onMenuHide,null,this);X.renderEvent.subscribe(this._onMenuRender,null,this);if(J&&X instanceof J){if(P){Y=this.get("container");if(Y){X.cfg.queueProperty("container",Y);}else{this.on("appendTo",T);}}X.cfg.queueProperty("clicktohide",false);X.keyDownEvent.subscribe(this._onMenuKeyDown,this,true);X.subscribe("click",this._onMenuClick,this,true);this.on("selectedMenuItemChange",this._onSelectedMenuItemChange);Q=X.srcElement;if(Q&&Q.nodeName.toUpperCase()=="SELECT"){Q.style.display="none";Q.parentNode.removeChild(Q);}}else{if(B&&X instanceof B){if(!K){K=new YAHOO.widget.OverlayManager();}K.register(X);}}this._menu=X;if(!W&&!P){if(G.inDocument(R)){X.render(R.parentNode);}else{this.on("appendTo",V);}}}}if(B){if(J){N=J.prototype.CSS_CLASS_NAME;}if(U&&J&&(U instanceof J)){X=U;W=true;S.call(this);}else{if(B&&U&&(U instanceof B)){X=U;W=true;X.cfg.queueProperty("visible",false);S.call(this);}else{if(J&&I.isArray(U)){X=new J(G.generateId(),{lazyload:P,itemdata:U});this._menu=X;this.on("appendTo",S);}else{if(I.isString(U)){O=G.get(U);if(O){if(J&&G.hasClass(O,N)||O.nodeName.toUpperCase()=="SELECT"){X=new J(U,{lazyload:P});S.call(this);}else{if(B){X=new B(U,{visible:false});S.call(this);}}}}else{if(U&&U.nodeName){if(J&&G.hasClass(U,N)||U.nodeName.toUpperCase()=="SELECT"){X=new J(U,{lazyload:P});S.call(this);}else{if(B){if(!U.id){G.generateId(U);}X=new B(U,{visible:false});S.call(this);}}}}}}}}},_setOnClick:function(N){if(this._onclickAttributeValue&&(this._onclickAttributeValue!=N)){this.removeListener("click",this._onclickAttributeValue.fn);
+this._onclickAttributeValue=null;}if(!this._onclickAttributeValue&&I.isObject(N)&&I.isFunction(N.fn)){this.on("click",N.fn,N.obj,N.scope);this._onclickAttributeValue=N;}},_isActivationKey:function(N){var S=this.get("type"),O=(S=="checkbox"||S=="radio")?this.CHECK_ACTIVATION_KEYS:this.ACTIVATION_KEYS,Q=O.length,R=false,P;if(Q>0){P=Q-1;do{if(N==O[P]){R=true;break;}}while(P--);}return R;},_isSplitButtonOptionKey:function(P){var O=(M.getCharCode(P)==40);var N=function(Q){M.preventDefault(Q);this.removeListener("keypress",N);};if(O){if(L.opera){this.on("keypress",N);}M.preventDefault(P);}return O;},_addListenersToForm:function(){var T=this.getForm(),S=YAHOO.widget.Button.onFormKeyPress,R,N,Q,P,O;if(T){M.on(T,"reset",this._onFormReset,null,this);M.on(T,"submit",this._onFormSubmit,null,this);N=this.get("srcelement");if(this.get("type")=="submit"||(N&&N.type=="submit")){Q=M.getListeners(T,"keypress");R=false;if(Q){P=Q.length;if(P>0){O=P-1;do{if(Q[O].fn==S){R=true;break;}}while(O--);}}if(!R){M.on(T,"keypress",S);}}}},_showMenu:function(R){if(YAHOO.widget.MenuManager){YAHOO.widget.MenuManager.hideVisible();}if(K){K.hideAll();}var N=this._menu,Q=this.get("menualignment"),P=this.get("focusmenu"),O;if(this._renderedMenu){N.cfg.setProperty("context",[this.get("element"),Q[0],Q[1]]);N.cfg.setProperty("preventcontextoverlap",true);N.cfg.setProperty("constraintoviewport",true);}else{N.cfg.queueProperty("context",[this.get("element"),Q[0],Q[1]]);N.cfg.queueProperty("preventcontextoverlap",true);N.cfg.queueProperty("constraintoviewport",true);}this.focus();if(J&&N&&(N instanceof J)){O=N.focus;N.focus=function(){};if(this._renderedMenu){N.cfg.setProperty("minscrollheight",this.get("menuminscrollheight"));N.cfg.setProperty("maxheight",this.get("menumaxheight"));}else{N.cfg.queueProperty("minscrollheight",this.get("menuminscrollheight"));N.cfg.queueProperty("maxheight",this.get("menumaxheight"));}N.show();N.focus=O;N.align();if(R.type=="mousedown"){M.stopPropagation(R);}if(P){N.focus();}}else{if(B&&N&&(N instanceof B)){if(!this._renderedMenu){N.render(this.get("element").parentNode);}N.show();N.align();}}},_hideMenu:function(){var N=this._menu;if(N){N.hide();}},_onMouseOver:function(O){var Q=this.get("type"),N,P;if(Q==="split"){N=this.get("element");P=(G.getX(N)+(N.offsetWidth-this.OPTION_AREA_WIDTH));this._nOptionRegionX=P;}if(!this._hasMouseEventHandlers){if(Q==="split"){this.on("mousemove",this._onMouseMove);}this.on("mouseout",this._onMouseOut);this._hasMouseEventHandlers=true;}this.addStateCSSClasses("hover");if(Q==="split"&&(M.getPageX(O)>P)){this.addStateCSSClasses("hoveroption");}if(this._activationButtonPressed){this.addStateCSSClasses("active");}if(this._bOptionPressed){this.addStateCSSClasses("activeoption");}if(this._activationButtonPressed||this._bOptionPressed){M.removeListener(document,"mouseup",this._onDocumentMouseUp);}},_onMouseMove:function(N){var O=this._nOptionRegionX;if(O){if(M.getPageX(N)>O){this.addStateCSSClasses("hoveroption");}else{this.removeStateCSSClasses("hoveroption");}}},_onMouseOut:function(N){var O=this.get("type");this.removeStateCSSClasses("hover");if(O!="menu"){this.removeStateCSSClasses("active");}if(this._activationButtonPressed||this._bOptionPressed){M.on(document,"mouseup",this._onDocumentMouseUp,null,this);}if(O==="split"&&(M.getPageX(N)>this._nOptionRegionX)){this.removeStateCSSClasses("hoveroption");}},_onDocumentMouseUp:function(P){this._activationButtonPressed=false;this._bOptionPressed=false;var Q=this.get("type"),N,O;if(Q=="menu"||Q=="split"){N=M.getTarget(P);O=this._menu.element;if(N!=O&&!G.isAncestor(O,N)){this.removeStateCSSClasses((Q=="menu"?"active":"activeoption"));this._hideMenu();}}M.removeListener(document,"mouseup",this._onDocumentMouseUp);},_onMouseDown:function(P){var Q,O=true;function N(){this._hideMenu();this.removeListener("mouseup",N);}if((P.which||P.button)==1){if(!this.hasFocus()){I.later(0,this,this.focus);}Q=this.get("type");if(Q=="split"){if(M.getPageX(P)>this._nOptionRegionX){this.fireEvent("option",P);O=false;}else{this.addStateCSSClasses("active");this._activationButtonPressed=true;}}else{if(Q=="menu"){if(this.isActive()){this._hideMenu();this._activationButtonPressed=false;}else{this._showMenu(P);this._activationButtonPressed=true;}}else{this.addStateCSSClasses("active");this._activationButtonPressed=true;}}if(Q=="split"||Q=="menu"){this._hideMenuTimer=I.later(250,this,this.on,["mouseup",N]);}}return O;},_onMouseUp:function(P){this.inMouseDown=false;var Q=this.get("type"),N=this._hideMenuTimer,O=true;if(N){N.cancel();}if(Q=="checkbox"||Q=="radio"){if((P.which||P.button)!=1){return;}this.set("checked",!(this.get("checked")));}this._activationButtonPressed=false;if(Q!="menu"){this.removeStateCSSClasses("active");}if(Q=="split"&&M.getPageX(P)>this._nOptionRegionX){O=false;}return O;},_onFocus:function(O){var N;this.addStateCSSClasses("focus");if(this._activationKeyPressed){this.addStateCSSClasses("active");}C=this;if(!this._hasKeyEventHandlers){N=this._button;M.on(N,"blur",this._onBlur,null,this);M.on(N,"keydown",this._onKeyDown,null,this);M.on(N,"keyup",this._onKeyUp,null,this);this._hasKeyEventHandlers=true;}this.fireEvent("focus",O);},_onBlur:function(N){this.removeStateCSSClasses("focus");if(this.get("type")!="menu"){this.removeStateCSSClasses("active");}if(this._activationKeyPressed){M.on(document,"keyup",this._onDocumentKeyUp,null,this);}C=null;this.fireEvent("blur",N);},_onDocumentKeyUp:function(N){if(this._isActivationKey(M.getCharCode(N))){this._activationKeyPressed=false;M.removeListener(document,"keyup",this._onDocumentKeyUp);}},_onKeyDown:function(O){var N=this._menu;if(this.get("type")=="split"&&this._isSplitButtonOptionKey(O)){this.fireEvent("option",O);}else{if(this._isActivationKey(M.getCharCode(O))){if(this.get("type")=="menu"){this._showMenu(O);}else{this._activationKeyPressed=true;this.addStateCSSClasses("active");}}}if(N&&N.cfg.getProperty("visible")&&M.getCharCode(O)==27){N.hide();this.focus();}},_onKeyUp:function(N){var O;
+if(this._isActivationKey(M.getCharCode(N))){O=this.get("type");if(O=="checkbox"||O=="radio"){this.set("checked",!(this.get("checked")));}this._activationKeyPressed=false;if(this.get("type")!="menu"){this.removeStateCSSClasses("active");}}},_onClick:function(P){var R=this.get("type"),Q,N,O;switch(R){case"submit":if(P.returnValue!==false){this.submitForm();}break;case"reset":Q=this.getForm();if(Q){Q.reset();}break;case"split":if(this._nOptionRegionX>0&&(M.getPageX(P)>this._nOptionRegionX)){O=false;}else{this._hideMenu();N=this.get("srcelement");if(N&&N.type=="submit"&&P.returnValue!==false){this.submitForm();}}break;}return O;},_onDblClick:function(O){var N=true;if(this.get("type")=="split"&&M.getPageX(O)>this._nOptionRegionX){N=false;}return N;},_onAppendTo:function(N){I.later(0,this,this._addListenersToForm);},_onFormReset:function(O){var P=this.get("type"),N=this._menu;if(P=="checkbox"||P=="radio"){this.resetValue("checked");}if(J&&N&&(N instanceof J)){this.resetValue("selectedMenuItem");}},_onFormSubmit:function(N){this.createHiddenFields();},_onDocumentMouseDown:function(R){var O=M.getTarget(R),Q=this.get("element"),P=this._menu.element;function N(T){var V,S,U;if(!T){return true;}for(V=0,S=T.length;V<S;V++){U=T[V].element;if(O==U||G.isAncestor(U,O)){return true;}if(T[V]&&T[V].getSubmenus){if(N(T[V].getSubmenus())){return true;}}}return false;}if(O!=Q&&!G.isAncestor(Q,O)&&O!=P&&!G.isAncestor(P,O)){if(this._menu&&this._menu.getSubmenus){if(!N(this._menu.getSubmenus())){return;}}this._hideMenu();if(L.ie&&(L.ie<9)&&O.focus){O.setActive();}M.removeListener(document,"mousedown",this._onDocumentMouseDown);}},_onOption:function(N){if(this.hasClass(this.CLASS_NAME_PREFIX+"split-button-activeoption")){this._hideMenu();this._bOptionPressed=false;}else{this._showMenu(N);this._bOptionPressed=true;}},_onMenuShow:function(N){M.on(document,"mousedown",this._onDocumentMouseDown,null,this);var O=(this.get("type")=="split")?"activeoption":"active";this.addStateCSSClasses(O);},_onMenuHide:function(N){var O=(this.get("type")=="split")?"activeoption":"active";this.removeStateCSSClasses(O);if(this.get("type")=="split"){this._bOptionPressed=false;}},_onMenuKeyDown:function(P,O){var N=O[0];if(M.getCharCode(N)==27){this.focus();if(this.get("type")=="split"){this._bOptionPressed=false;}}},_onMenuRender:function(P){var S=this.get("element"),O=S.parentNode,N=this._menu,R=N.element,Q=N.srcElement,T;if(O!=R.parentNode){O.appendChild(R);}this._renderedMenu=true;if(Q&&Q.nodeName.toLowerCase()==="select"&&Q.value){T=N.getItem(Q.selectedIndex);this.set("selectedMenuItem",T,true);this._onSelectedMenuItemChange({newValue:T});}},_onMenuClick:function(O,N){var Q=N[1],P;if(Q){this.set("selectedMenuItem",Q);P=this.get("srcelement");if(P&&P.type=="submit"){this.submitForm();}this._hideMenu();}},_onSelectedMenuItemChange:function(O){var P=O.prevValue,Q=O.newValue,N=this.CLASS_NAME_PREFIX;if(P){G.removeClass(P.element,(N+"button-selectedmenuitem"));}if(Q){G.addClass(Q.element,(N+"button-selectedmenuitem"));}},_onLabelClick:function(N){this.focus();var O=this.get("type");if(O=="radio"||O=="checkbox"){this.set("checked",(!this.get("checked")));}},createButtonElement:function(N){var P=this.NODE_NAME,O=document.createElement(P);O.innerHTML="<"+P+' class="first-child">'+(N=="link"?"<a></a>":'<button type="button"></button>')+"</"+P+">";return O;},addStateCSSClasses:function(O){var P=this.get("type"),N=this.CLASS_NAME_PREFIX;if(I.isString(O)){if(O!="activeoption"&&O!="hoveroption"){this.addClass(N+this.CSS_CLASS_NAME+("-"+O));}this.addClass(N+P+("-button-"+O));}},removeStateCSSClasses:function(O){var P=this.get("type"),N=this.CLASS_NAME_PREFIX;if(I.isString(O)){this.removeClass(N+this.CSS_CLASS_NAME+("-"+O));this.removeClass(N+P+("-button-"+O));}},createHiddenFields:function(){this.removeHiddenFields();var V=this.getForm(),Z,O,S,X,Y,T,U,N,R,W,P,Q=false;if(V&&!this.get("disabled")){O=this.get("type");S=(O=="checkbox"||O=="radio");if((S&&this.get("checked"))||(E==this)){Z=F((S?O:"hidden"),this.get("name"),this.get("value"),this.get("checked"));if(Z){if(S){Z.style.display="none";}V.appendChild(Z);}}X=this._menu;if(J&&X&&(X instanceof J)){Y=this.get("selectedMenuItem");P=X.srcElement;Q=(P&&P.nodeName.toUpperCase()=="SELECT");if(Y){U=(Y.value===null||Y.value==="")?Y.cfg.getProperty("text"):Y.value;T=this.get("name");if(Q){W=P.name;}else{if(T){W=(T+"_options");}}if(U&&W){N=F("hidden",W,U);V.appendChild(N);}}else{if(Q){N=V.appendChild(P);}}}if(Z&&N){this._hiddenFields=[Z,N];}else{if(!Z&&N){this._hiddenFields=N;}else{if(Z&&!N){this._hiddenFields=Z;}}}R=this._hiddenFields;}return R;},removeHiddenFields:function(){var Q=this._hiddenFields,O,P;function N(R){if(G.inDocument(R)){R.parentNode.removeChild(R);}}if(Q){if(I.isArray(Q)){O=Q.length;if(O>0){P=O-1;do{N(Q[P]);}while(P--);}}else{N(Q);}this._hiddenFields=null;}},submitForm:function(){var Q=this.getForm(),P=this.get("srcelement"),O=false,N;if(Q){if(this.get("type")=="submit"||(P&&P.type=="submit")){E=this;}if(L.ie&&(L.ie<9)){O=Q.fireEvent("onsubmit");}else{N=document.createEvent("HTMLEvents");N.initEvent("submit",true,true);O=Q.dispatchEvent(N);}if((L.ie||L.webkit)&&O){Q.submit();}}return O;},init:function(P,d){var V=d.type=="link"?"a":"button",a=d.srcelement,S=P.getElementsByTagName(V)[0],U;if(!S){U=P.getElementsByTagName("input")[0];if(U){S=document.createElement("button");S.setAttribute("type","button");U.parentNode.replaceChild(S,U);}}this._button=S;YAHOO.widget.Button.superclass.init.call(this,P,d);var T=this.get("id"),Z=T+"-button";S.id=Z;var X,Q;var e=function(f){return(f.htmlFor===T);};var c=function(){Q.setAttribute((L.ie?"htmlFor":"for"),Z);};if(a&&this.get("type")!="link"){X=G.getElementsBy(e,"label");if(I.isArray(X)&&X.length>0){Q=X[0];}}D[T]=this;var b=this.CLASS_NAME_PREFIX;this.addClass(b+this.CSS_CLASS_NAME);this.addClass(b+this.get("type")+"-button");M.on(this._button,"focus",this._onFocus,null,this);this.on("mouseover",this._onMouseOver);this.on("mousedown",this._onMouseDown);
+this.on("mouseup",this._onMouseUp);this.on("click",this._onClick);var R=this.get("onclick");this.set("onclick",null);this.set("onclick",R);this.on("dblclick",this._onDblClick);var O;if(Q){if(this.get("replaceLabel")){this.set("label",Q.innerHTML);O=Q.parentNode;O.removeChild(Q);}else{this.on("appendTo",c);M.on(Q,"click",this._onLabelClick,null,this);this._label=Q;}}this.on("appendTo",this._onAppendTo);var N=this.get("container"),Y=this.get("element"),W=G.inDocument(Y);if(N){if(a&&a!=Y){O=a.parentNode;if(O){O.removeChild(a);}}if(I.isString(N)){M.onContentReady(N,this.appendTo,N,this);}else{this.on("init",function(){I.later(0,this,this.appendTo,N);});}}else{if(!W&&a&&a!=Y){O=a.parentNode;if(O){this.fireEvent("beforeAppendTo",{type:"beforeAppendTo",target:O});O.replaceChild(Y,a);this.fireEvent("appendTo",{type:"appendTo",target:O});}}else{if(this.get("type")!="link"&&W&&a&&a==Y){this._addListenersToForm();}}}this.fireEvent("init",{type:"init",target:this});},initAttributes:function(O){var N=O||{};YAHOO.widget.Button.superclass.initAttributes.call(this,N);this.setAttributeConfig("type",{value:(N.type||"push"),validator:I.isString,writeOnce:true,method:this._setType});this.setAttributeConfig("label",{value:N.label,validator:I.isString,method:this._setLabel});this.setAttributeConfig("value",{value:N.value});this.setAttributeConfig("name",{value:N.name,validator:I.isString});this.setAttributeConfig("tabindex",{value:N.tabindex,validator:I.isNumber,method:this._setTabIndex});this.configureAttribute("title",{value:N.title,validator:I.isString,method:this._setTitle});this.setAttributeConfig("disabled",{value:(N.disabled||false),validator:I.isBoolean,method:this._setDisabled});this.setAttributeConfig("href",{value:N.href,validator:I.isString,method:this._setHref});this.setAttributeConfig("target",{value:N.target,validator:I.isString,method:this._setTarget});this.setAttributeConfig("checked",{value:(N.checked||false),validator:I.isBoolean,method:this._setChecked});this.setAttributeConfig("container",{value:N.container,writeOnce:true});this.setAttributeConfig("srcelement",{value:N.srcelement,writeOnce:true});this.setAttributeConfig("menu",{value:null,method:this._setMenu,writeOnce:true});this.setAttributeConfig("lazyloadmenu",{value:(N.lazyloadmenu===false?false:true),validator:I.isBoolean,writeOnce:true});this.setAttributeConfig("menuclassname",{value:(N.menuclassname||(this.CLASS_NAME_PREFIX+"button-menu")),validator:I.isString,method:this._setMenuClassName,writeOnce:true});this.setAttributeConfig("menuminscrollheight",{value:(N.menuminscrollheight||90),validator:I.isNumber});this.setAttributeConfig("menumaxheight",{value:(N.menumaxheight||0),validator:I.isNumber});this.setAttributeConfig("menualignment",{value:(N.menualignment||["tl","bl"]),validator:I.isArray});this.setAttributeConfig("selectedMenuItem",{value:null});this.setAttributeConfig("onclick",{value:N.onclick,method:this._setOnClick});this.setAttributeConfig("focusmenu",{value:(N.focusmenu===false?false:true),validator:I.isBoolean});this.setAttributeConfig("replaceLabel",{value:false,validator:I.isBoolean,writeOnce:true});},focus:function(){if(!this.get("disabled")){try{this._button.focus();}catch(N){}}},blur:function(){if(!this.get("disabled")){try{this._button.blur();}catch(N){}}},hasFocus:function(){return(C==this);},isActive:function(){return this.hasClass(this.CLASS_NAME_PREFIX+this.CSS_CLASS_NAME+"-active");},getMenu:function(){return this._menu;},getForm:function(){var N=this._button,O;if(N){O=N.form;}return O;},getHiddenFields:function(){return this._hiddenFields;},destroy:function(){var P=this.get("element"),N=this._menu,T=this._label,O,S;if(N){if(K&&K.find(N)){K.remove(N);}N.destroy();}M.purgeElement(P);M.purgeElement(this._button);M.removeListener(document,"mouseup",this._onDocumentMouseUp);M.removeListener(document,"keyup",this._onDocumentKeyUp);M.removeListener(document,"mousedown",this._onDocumentMouseDown);if(T){M.removeListener(T,"click",this._onLabelClick);O=T.parentNode;O.removeChild(T);}var Q=this.getForm();if(Q){M.removeListener(Q,"reset",this._onFormReset);M.removeListener(Q,"submit",this._onFormSubmit);}this.unsubscribeAll();O=P.parentNode;if(O){O.removeChild(P);}delete D[this.get("id")];var R=(this.CLASS_NAME_PREFIX+this.CSS_CLASS_NAME);S=G.getElementsByClassName(R,this.NODE_NAME,Q);if(I.isArray(S)&&S.length===0){M.removeListener(Q,"keypress",YAHOO.widget.Button.onFormKeyPress);}},fireEvent:function(O,N){var P=arguments[0];if(this.DOM_EVENTS[P]&&this.get("disabled")){return false;}return YAHOO.widget.Button.superclass.fireEvent.apply(this,arguments);},toString:function(){return("Button "+this.get("id"));}});YAHOO.widget.Button.onFormKeyPress=function(R){var P=M.getTarget(R),S=M.getCharCode(R),Q=P.nodeName&&P.nodeName.toUpperCase(),N=P.type,T=false,V,X,O,W;function U(a){var Z,Y;switch(a.nodeName.toUpperCase()){case"INPUT":case"BUTTON":if(a.type=="submit"&&!a.disabled){if(!T&&!O){O=a;}}break;default:Z=a.id;if(Z){V=D[Z];if(V){T=true;if(!V.get("disabled")){Y=V.get("srcelement");if(!X&&(V.get("type")=="submit"||(Y&&Y.type=="submit"))){X=V;}}}}break;}}if(S==13&&((Q=="INPUT"&&(N=="text"||N=="password"||N=="checkbox"||N=="radio"||N=="file"))||Q=="SELECT")){G.getElementsBy(U,"*",this);if(O){O.focus();}else{if(!O&&X){M.preventDefault(R);if(L.ie){X.get("element").fireEvent("onclick");}else{W=document.createEvent("HTMLEvents");W.initEvent("click",true,true);if(L.gecko<1.9){X.fireEvent("click",W);}else{X.get("element").dispatchEvent(W);}}}}}};YAHOO.widget.Button.addHiddenFieldsToForm=function(N){var R=YAHOO.widget.Button.prototype,T=G.getElementsByClassName((R.CLASS_NAME_PREFIX+R.CSS_CLASS_NAME),"*",N),Q=T.length,S,O,P;if(Q>0){for(P=0;P<Q;P++){O=T[P].id;if(O){S=D[O];if(S){S.createHiddenFields();}}}}};YAHOO.widget.Button.getButton=function(N){return D[N];};})();(function(){var C=YAHOO.util.Dom,B=YAHOO.util.Event,D=YAHOO.lang,A=YAHOO.widget.Button,E={};YAHOO.widget.ButtonGroup=function(J,H){var I=YAHOO.widget.ButtonGroup.superclass.constructor,K,G,F;
+if(arguments.length==1&&!D.isString(J)&&!J.nodeName){if(!J.id){F=C.generateId();J.id=F;}I.call(this,(this._createGroupElement()),J);}else{if(D.isString(J)){G=C.get(J);if(G){if(G.nodeName.toUpperCase()==this.NODE_NAME){I.call(this,G,H);}}}else{K=J.nodeName.toUpperCase();if(K&&K==this.NODE_NAME){if(!J.id){J.id=C.generateId();}I.call(this,J,H);}}}};YAHOO.extend(YAHOO.widget.ButtonGroup,YAHOO.util.Element,{_buttons:null,NODE_NAME:"DIV",CLASS_NAME_PREFIX:"yui-",CSS_CLASS_NAME:"buttongroup",_createGroupElement:function(){var F=document.createElement(this.NODE_NAME);return F;},_setDisabled:function(G){var H=this.getCount(),F;if(H>0){F=H-1;do{this._buttons[F].set("disabled",G);}while(F--);}},_onKeyDown:function(K){var G=B.getTarget(K),I=B.getCharCode(K),H=G.parentNode.parentNode.id,J=E[H],F=-1;if(I==37||I==38){F=(J.index===0)?(this._buttons.length-1):(J.index-1);}else{if(I==39||I==40){F=(J.index===(this._buttons.length-1))?0:(J.index+1);}}if(F>-1){this.check(F);this.getButton(F).focus();}},_onAppendTo:function(H){var I=this._buttons,G=I.length,F;for(F=0;F<G;F++){I[F].appendTo(this.get("element"));}},_onButtonCheckedChange:function(G,F){var I=G.newValue,H=this.get("checkedButton");if(I&&H!=F){if(H){H.set("checked",false,true);}this.set("checkedButton",F);this.set("value",F.get("value"));}else{if(H&&!H.set("checked")){H.set("checked",true,true);}}},init:function(I,H){this._buttons=[];YAHOO.widget.ButtonGroup.superclass.init.call(this,I,H);this.addClass(this.CLASS_NAME_PREFIX+this.CSS_CLASS_NAME);var K=(YAHOO.widget.Button.prototype.CLASS_NAME_PREFIX+"radio-button"),J=this.getElementsByClassName(K);if(J.length>0){this.addButtons(J);}function F(L){return(L.type=="radio");}J=C.getElementsBy(F,"input",this.get("element"));if(J.length>0){this.addButtons(J);}this.on("keydown",this._onKeyDown);this.on("appendTo",this._onAppendTo);var G=this.get("container");if(G){if(D.isString(G)){B.onContentReady(G,function(){this.appendTo(G);},null,this);}else{this.appendTo(G);}}},initAttributes:function(G){var F=G||{};YAHOO.widget.ButtonGroup.superclass.initAttributes.call(this,F);this.setAttributeConfig("name",{value:F.name,validator:D.isString});this.setAttributeConfig("disabled",{value:(F.disabled||false),validator:D.isBoolean,method:this._setDisabled});this.setAttributeConfig("value",{value:F.value});this.setAttributeConfig("container",{value:F.container,writeOnce:true});this.setAttributeConfig("checkedButton",{value:null});},addButton:function(J){var L,K,G,F,H,I;if(J instanceof A&&J.get("type")=="radio"){L=J;}else{if(!D.isString(J)&&!J.nodeName){J.type="radio";L=new A(J);}else{L=new A(J,{type:"radio"});}}if(L){F=this._buttons.length;H=L.get("name");I=this.get("name");L.index=F;this._buttons[F]=L;E[L.get("id")]=L;if(H!=I){L.set("name",I);}if(this.get("disabled")){L.set("disabled",true);}if(L.get("checked")){this.set("checkedButton",L);}K=L.get("element");G=this.get("element");if(K.parentNode!=G){G.appendChild(K);}L.on("checkedChange",this._onButtonCheckedChange,L,this);}return L;},addButtons:function(G){var H,I,J,F;if(D.isArray(G)){H=G.length;J=[];if(H>0){for(F=0;F<H;F++){I=this.addButton(G[F]);if(I){J[J.length]=I;}}}}return J;},removeButton:function(H){var I=this.getButton(H),G,F;if(I){this._buttons.splice(H,1);delete E[I.get("id")];I.removeListener("checkedChange",this._onButtonCheckedChange);I.destroy();G=this._buttons.length;if(G>0){F=this._buttons.length-1;do{this._buttons[F].index=F;}while(F--);}}},getButton:function(F){return this._buttons[F];},getButtons:function(){return this._buttons;},getCount:function(){return this._buttons.length;},focus:function(H){var I,G,F;if(D.isNumber(H)){I=this._buttons[H];if(I){I.focus();}}else{G=this.getCount();for(F=0;F<G;F++){I=this._buttons[F];if(!I.get("disabled")){I.focus();break;}}}},check:function(F){var G=this.getButton(F);if(G){G.set("checked",true);}},destroy:function(){var I=this._buttons.length,H=this.get("element"),F=H.parentNode,G;if(I>0){G=this._buttons.length-1;do{this._buttons[G].destroy();}while(G--);}B.purgeElement(H);F.removeChild(H);},toString:function(){return("ButtonGroup "+this.get("id"));}});})();YAHOO.register("button",YAHOO.widget.Button,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/calendar.js b/Websites/bugs.webkit.org/js/yui/calendar.js
deleted file mode 100644
index a8eff37..0000000
--- a/Websites/bugs.webkit.org/js/yui/calendar.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
-Copyright (c) 2007, Yahoo! Inc. All rights reserved.
-Code licensed under the BSD License:
-http://developer.yahoo.net/yui/license.txt
-version: 2.3.1
-*/
-(function(){YAHOO.util.Config=function(D){if(D){this.init(D);}if(!D){}};var B=YAHOO.lang,C=YAHOO.util.CustomEvent,A=YAHOO.util.Config;A.CONFIG_CHANGED_EVENT="configChanged";A.BOOLEAN_TYPE="boolean";A.prototype={owner:null,queueInProgress:false,config:null,initialConfig:null,eventQueue:null,configChangedEvent:null,init:function(D){this.owner=D;this.configChangedEvent=this.createEvent(A.CONFIG_CHANGED_EVENT);this.configChangedEvent.signature=C.LIST;this.queueInProgress=false;this.config={};this.initialConfig={};this.eventQueue=[];},checkBoolean:function(D){return(typeof D==A.BOOLEAN_TYPE);},checkNumber:function(D){return(!isNaN(D));},fireEvent:function(D,F){var E=this.config[D];if(E&&E.event){E.event.fire(F);}},addProperty:function(E,D){E=E.toLowerCase();this.config[E]=D;D.event=this.createEvent(E,{scope:this.owner});D.event.signature=C.LIST;D.key=E;if(D.handler){D.event.subscribe(D.handler,this.owner);}this.setProperty(E,D.value,true);if(!D.suppressEvent){this.queueProperty(E,D.value);}},getConfig:function(){var D={},F,E;for(F in this.config){E=this.config[F];if(E&&E.event){D[F]=E.value;}}return D;},getProperty:function(D){var E=this.config[D.toLowerCase()];if(E&&E.event){return E.value;}else{return undefined;}},resetProperty:function(D){D=D.toLowerCase();var E=this.config[D];if(E&&E.event){if(this.initialConfig[D]&&!B.isUndefined(this.initialConfig[D])){this.setProperty(D,this.initialConfig[D]);return true;}}else{return false;}},setProperty:function(E,G,D){var F;E=E.toLowerCase();if(this.queueInProgress&&!D){this.queueProperty(E,G);return true;}else{F=this.config[E];if(F&&F.event){if(F.validator&&!F.validator(G)){return false;}else{F.value=G;if(!D){this.fireEvent(E,G);this.configChangedEvent.fire([E,G]);}return true;}}else{return false;}}},queueProperty:function(S,P){S=S.toLowerCase();var R=this.config[S],K=false,J,G,H,I,O,Q,F,M,N,D,L,T,E;if(R&&R.event){if(!B.isUndefined(P)&&R.validator&&!R.validator(P)){return false;}else{if(!B.isUndefined(P)){R.value=P;}else{P=R.value;}K=false;J=this.eventQueue.length;for(L=0;L<J;L++){G=this.eventQueue[L];if(G){H=G[0];I=G[1];if(H==S){this.eventQueue[L]=null;this.eventQueue.push([S,(!B.isUndefined(P)?P:I)]);K=true;break;}}}if(!K&&!B.isUndefined(P)){this.eventQueue.push([S,P]);}}if(R.supercedes){O=R.supercedes.length;for(T=0;T<O;T++){Q=R.supercedes[T];F=this.eventQueue.length;for(E=0;E<F;E++){M=this.eventQueue[E];if(M){N=M[0];D=M[1];if(N==Q.toLowerCase()){this.eventQueue.push([N,D]);this.eventQueue[E]=null;break;}}}}}return true;}else{return false;}},refireEvent:function(D){D=D.toLowerCase();var E=this.config[D];if(E&&E.event&&!B.isUndefined(E.value)){if(this.queueInProgress){this.queueProperty(D);}else{this.fireEvent(D,E.value);}}},applyConfig:function(E,H){var G,D,F;if(H){F={};for(G in E){if(B.hasOwnProperty(E,G)){F[G.toLowerCase()]=E[G];}}this.initialConfig=F;}for(G in E){if(B.hasOwnProperty(E,G)){this.queueProperty(G,E[G]);}}},refresh:function(){var D;for(D in this.config){this.refireEvent(D);}},fireQueue:function(){var E,H,D,G,F;this.queueInProgress=true;for(E=0;E<this.eventQueue.length;E++){H=this.eventQueue[E];if(H){D=H[0];G=H[1];F=this.config[D];F.value=G;this.fireEvent(D,G);}}this.queueInProgress=false;this.eventQueue=[];},subscribeToConfigEvent:function(E,F,H,D){var G=this.config[E.toLowerCase()];if(G&&G.event){if(!A.alreadySubscribed(G.event,F,H)){G.event.subscribe(F,H,D);}return true;}else{return false;}},unsubscribeFromConfigEvent:function(D,E,G){var F=this.config[D.toLowerCase()];if(F&&F.event){return F.event.unsubscribe(E,G);}else{return false;}},toString:function(){var D="Config";if(this.owner){D+=" ["+this.owner.toString()+"]";}return D;},outputEventQueue:function(){var D="",G,E,F=this.eventQueue.length;for(E=0;E<F;E++){G=this.eventQueue[E];if(G){D+=G[0]+"="+G[1]+", ";}}return D;},destroy:function(){var E=this.config,D,F;for(D in E){if(B.hasOwnProperty(E,D)){F=E[D];F.event.unsubscribeAll();F.event=null;}}this.configChangedEvent.unsubscribeAll();this.configChangedEvent=null;this.owner=null;this.config=null;this.initialConfig=null;this.eventQueue=null;}};A.alreadySubscribed=function(E,H,I){var F=E.subscribers.length,D,G;if(F>0){G=F-1;do{D=E.subscribers[G];if(D&&D.obj==I&&D.fn==H){return true;}}while(G--);}return false;};YAHOO.lang.augmentProto(A,YAHOO.util.EventProvider);}());YAHOO.widget.DateMath={DAY:"D",WEEK:"W",YEAR:"Y",MONTH:"M",ONE_DAY_MS:1000*60*60*24,add:function(A,D,C){var F=new Date(A.getTime());switch(D){case this.MONTH:var E=A.getMonth()+C;var B=0;if(E<0){while(E<0){E+=12;B-=1;}}else{if(E>11){while(E>11){E-=12;B+=1;}}}F.setMonth(E);F.setFullYear(A.getFullYear()+B);break;case this.DAY:F.setDate(A.getDate()+C);break;case this.YEAR:F.setFullYear(A.getFullYear()+C);break;case this.WEEK:F.setDate(A.getDate()+(C*7));break;}return F;},subtract:function(A,C,B){return this.add(A,C,(B*-1));},before:function(C,B){var A=B.getTime();if(C.getTime()<A){return true;}else{return false;}},after:function(C,B){var A=B.getTime();if(C.getTime()>A){return true;}else{return false;}},between:function(B,A,C){if(this.after(B,A)&&this.before(B,C)){return true;}else{return false;}},getJan1:function(A){return new Date(A,0,1);},getDayOffset:function(B,D){var C=this.getJan1(D);var A=Math.ceil((B.getTime()-C.getTime())/this.ONE_DAY_MS);return A;},getWeekNumber:function(C,F){C=this.clearTime(C);var E=new Date(C.getTime()+(4*this.ONE_DAY_MS)-((C.getDay())*this.ONE_DAY_MS));var B=new Date(E.getFullYear(),0,1);var A=((E.getTime()-B.getTime())/this.ONE_DAY_MS)-1;var D=Math.ceil((A)/7);return D;},isYearOverlapWeek:function(A){var C=false;var B=this.add(A,this.DAY,6);if(B.getFullYear()!=A.getFullYear()){C=true;}return C;},isMonthOverlapWeek:function(A){var C=false;var B=this.add(A,this.DAY,6);if(B.getMonth()!=A.getMonth()){C=true;}return C;},findMonthStart:function(A){var B=new Date(A.getFullYear(),A.getMonth(),1);return B;},findMonthEnd:function(B){var D=this.findMonthStart(B);var C=this.add(D,this.MONTH,1);var A=this.subtract(C,this.DAY,1);return A;},clearTime:function(A){A.setHours(12,0,0,0);
-return A;}};YAHOO.widget.Calendar=function(C,A,B){this.init(C,A,B);};YAHOO.widget.Calendar.IMG_ROOT=null;YAHOO.widget.Calendar.DATE="D";YAHOO.widget.Calendar.MONTH_DAY="MD";YAHOO.widget.Calendar.WEEKDAY="WD";YAHOO.widget.Calendar.RANGE="R";YAHOO.widget.Calendar.MONTH="M";YAHOO.widget.Calendar.DISPLAY_DAYS=42;YAHOO.widget.Calendar.STOP_RENDER="S";YAHOO.widget.Calendar.SHORT="short";YAHOO.widget.Calendar.LONG="long";YAHOO.widget.Calendar.MEDIUM="medium";YAHOO.widget.Calendar.ONE_CHAR="1char";YAHOO.widget.Calendar._DEFAULT_CONFIG={PAGEDATE:{key:"pagedate",value:null},SELECTED:{key:"selected",value:null},TITLE:{key:"title",value:""},CLOSE:{key:"close",value:false},IFRAME:{key:"iframe",value:(YAHOO.env.ua.ie&&YAHOO.env.ua.ie<=6)?true:false},MINDATE:{key:"mindate",value:null},MAXDATE:{key:"maxdate",value:null},MULTI_SELECT:{key:"multi_select",value:false},START_WEEKDAY:{key:"start_weekday",value:0},SHOW_WEEKDAYS:{key:"show_weekdays",value:true},SHOW_WEEK_HEADER:{key:"show_week_header",value:false},SHOW_WEEK_FOOTER:{key:"show_week_footer",value:false},HIDE_BLANK_WEEKS:{key:"hide_blank_weeks",value:false},NAV_ARROW_LEFT:{key:"nav_arrow_left",value:null},NAV_ARROW_RIGHT:{key:"nav_arrow_right",value:null},MONTHS_SHORT:{key:"months_short",value:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]},MONTHS_LONG:{key:"months_long",value:["January","February","March","April","May","June","July","August","September","October","November","December"]},WEEKDAYS_1CHAR:{key:"weekdays_1char",value:["S","M","T","W","T","F","S"]},WEEKDAYS_SHORT:{key:"weekdays_short",value:["Su","Mo","Tu","We","Th","Fr","Sa"]},WEEKDAYS_MEDIUM:{key:"weekdays_medium",value:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},WEEKDAYS_LONG:{key:"weekdays_long",value:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},LOCALE_MONTHS:{key:"locale_months",value:"long"},LOCALE_WEEKDAYS:{key:"locale_weekdays",value:"short"},DATE_DELIMITER:{key:"date_delimiter",value:","},DATE_FIELD_DELIMITER:{key:"date_field_delimiter",value:"/"},DATE_RANGE_DELIMITER:{key:"date_range_delimiter",value:"-"},MY_MONTH_POSITION:{key:"my_month_position",value:1},MY_YEAR_POSITION:{key:"my_year_position",value:2},MD_MONTH_POSITION:{key:"md_month_position",value:1},MD_DAY_POSITION:{key:"md_day_position",value:2},MDY_MONTH_POSITION:{key:"mdy_month_position",value:1},MDY_DAY_POSITION:{key:"mdy_day_position",value:2},MDY_YEAR_POSITION:{key:"mdy_year_position",value:3},MY_LABEL_MONTH_POSITION:{key:"my_label_month_position",value:1},MY_LABEL_YEAR_POSITION:{key:"my_label_year_position",value:2},MY_LABEL_MONTH_SUFFIX:{key:"my_label_month_suffix",value:" "},MY_LABEL_YEAR_SUFFIX:{key:"my_label_year_suffix",value:""}};YAHOO.widget.Calendar._EVENT_TYPES={BEFORE_SELECT:"beforeSelect",SELECT:"select",BEFORE_DESELECT:"beforeDeselect",DESELECT:"deselect",CHANGE_PAGE:"changePage",BEFORE_RENDER:"beforeRender",RENDER:"render",RESET:"reset",CLEAR:"clear"};YAHOO.widget.Calendar._STYLES={CSS_ROW_HEADER:"calrowhead",CSS_ROW_FOOTER:"calrowfoot",CSS_CELL:"calcell",CSS_CELL_SELECTOR:"selector",CSS_CELL_SELECTED:"selected",CSS_CELL_SELECTABLE:"selectable",CSS_CELL_RESTRICTED:"restricted",CSS_CELL_TODAY:"today",CSS_CELL_OOM:"oom",CSS_CELL_OOB:"previous",CSS_HEADER:"calheader",CSS_HEADER_TEXT:"calhead",CSS_BODY:"calbody",CSS_WEEKDAY_CELL:"calweekdaycell",CSS_WEEKDAY_ROW:"calweekdayrow",CSS_FOOTER:"calfoot",CSS_CALENDAR:"yui-calendar",CSS_SINGLE:"single",CSS_CONTAINER:"yui-calcontainer",CSS_NAV_LEFT:"calnavleft",CSS_NAV_RIGHT:"calnavright",CSS_CLOSE:"calclose",CSS_CELL_TOP:"calcelltop",CSS_CELL_LEFT:"calcellleft",CSS_CELL_RIGHT:"calcellright",CSS_CELL_BOTTOM:"calcellbottom",CSS_CELL_HOVER:"calcellhover",CSS_CELL_HIGHLIGHT1:"highlight1",CSS_CELL_HIGHLIGHT2:"highlight2",CSS_CELL_HIGHLIGHT3:"highlight3",CSS_CELL_HIGHLIGHT4:"highlight4"};YAHOO.widget.Calendar.prototype={Config:null,parent:null,index:-1,cells:null,cellDates:null,id:null,oDomContainer:null,today:null,renderStack:null,_renderStack:null,_selectedDates:null,domEventMap:null};YAHOO.widget.Calendar.prototype.init=function(C,A,B){this.initEvents();this.today=new Date();YAHOO.widget.DateMath.clearTime(this.today);this.id=C;this.oDomContainer=document.getElementById(A);this.cfg=new YAHOO.util.Config(this);this.Options={};this.Locale={};this.initStyles();YAHOO.util.Dom.addClass(this.oDomContainer,this.Style.CSS_CONTAINER);YAHOO.util.Dom.addClass(this.oDomContainer,this.Style.CSS_SINGLE);this.cellDates=[];this.cells=[];this.renderStack=[];this._renderStack=[];this.setupConfig();if(B){this.cfg.applyConfig(B,true);}this.cfg.fireQueue();};YAHOO.widget.Calendar.prototype.configIframe=function(C,B,D){var A=B[0];if(!this.parent){if(YAHOO.util.Dom.inDocument(this.oDomContainer)){if(A){var E=YAHOO.util.Dom.getStyle(this.oDomContainer,"position");if(E=="absolute"||E=="relative"){if(!YAHOO.util.Dom.inDocument(this.iframe)){this.iframe=document.createElement("iframe");this.iframe.src="javascript:false;";YAHOO.util.Dom.setStyle(this.iframe,"opacity","0");if(YAHOO.env.ua.ie&&YAHOO.env.ua.ie<=6){YAHOO.util.Dom.addClass(this.iframe,"fixedsize");}this.oDomContainer.insertBefore(this.iframe,this.oDomContainer.firstChild);}}}else{if(this.iframe){if(this.iframe.parentNode){this.iframe.parentNode.removeChild(this.iframe);}this.iframe=null;}}}}};YAHOO.widget.Calendar.prototype.configTitle=function(B,A,C){var E=A[0],F;if(E){this.createTitleBar(E);}else{var D=this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.CLOSE.key);if(!D){this.removeTitleBar();}else{this.createTitleBar(" ");}}};YAHOO.widget.Calendar.prototype.configClose=function(B,A,C){var E=A[0],D=this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.TITLE.key);if(E){if(!D){this.createTitleBar(" ");}this.createCloseButton();}else{this.removeCloseButton();if(!D){this.removeTitleBar();}}};YAHOO.widget.Calendar.prototype.initEvents=function(){var A=YAHOO.widget.Calendar._EVENT_TYPES;this.beforeSelectEvent=new YAHOO.util.CustomEvent(A.BEFORE_SELECT);this.selectEvent=new YAHOO.util.CustomEvent(A.SELECT);
-this.beforeDeselectEvent=new YAHOO.util.CustomEvent(A.BEFORE_DESELECT);this.deselectEvent=new YAHOO.util.CustomEvent(A.DESELECT);this.changePageEvent=new YAHOO.util.CustomEvent(A.CHANGE_PAGE);this.beforeRenderEvent=new YAHOO.util.CustomEvent(A.BEFORE_RENDER);this.renderEvent=new YAHOO.util.CustomEvent(A.RENDER);this.resetEvent=new YAHOO.util.CustomEvent(A.RESET);this.clearEvent=new YAHOO.util.CustomEvent(A.CLEAR);this.beforeSelectEvent.subscribe(this.onBeforeSelect,this,true);this.selectEvent.subscribe(this.onSelect,this,true);this.beforeDeselectEvent.subscribe(this.onBeforeDeselect,this,true);this.deselectEvent.subscribe(this.onDeselect,this,true);this.changePageEvent.subscribe(this.onChangePage,this,true);this.renderEvent.subscribe(this.onRender,this,true);this.resetEvent.subscribe(this.onReset,this,true);this.clearEvent.subscribe(this.onClear,this,true);};YAHOO.widget.Calendar.prototype.doSelectCell=function(G,A){var L,F,I,C;var H=YAHOO.util.Event.getTarget(G);var B=H.tagName.toLowerCase();var E=false;while(B!="td"&&!YAHOO.util.Dom.hasClass(H,A.Style.CSS_CELL_SELECTABLE)){if(!E&&B=="a"&&YAHOO.util.Dom.hasClass(H,A.Style.CSS_CELL_SELECTOR)){E=true;}H=H.parentNode;B=H.tagName.toLowerCase();if(B=="html"){return ;}}if(E){YAHOO.util.Event.preventDefault(G);}L=H;if(YAHOO.util.Dom.hasClass(L,A.Style.CSS_CELL_SELECTABLE)){F=L.id.split("cell")[1];I=A.cellDates[F];C=new Date(I[0],I[1]-1,I[2]);var K;if(A.Options.MULTI_SELECT){K=L.getElementsByTagName("a")[0];if(K){K.blur();}var D=A.cellDates[F];var J=A._indexOfSelectedFieldArray(D);if(J>-1){A.deselectCell(F);}else{A.selectCell(F);}}else{K=L.getElementsByTagName("a")[0];if(K){K.blur();}A.selectCell(F);}}};YAHOO.widget.Calendar.prototype.doCellMouseOver=function(C,B){var A;if(C){A=YAHOO.util.Event.getTarget(C);}else{A=this;}while(A.tagName.toLowerCase()!="td"){A=A.parentNode;if(A.tagName.toLowerCase()=="html"){return ;}}if(YAHOO.util.Dom.hasClass(A,B.Style.CSS_CELL_SELECTABLE)){YAHOO.util.Dom.addClass(A,B.Style.CSS_CELL_HOVER);}};YAHOO.widget.Calendar.prototype.doCellMouseOut=function(C,B){var A;if(C){A=YAHOO.util.Event.getTarget(C);}else{A=this;}while(A.tagName.toLowerCase()!="td"){A=A.parentNode;if(A.tagName.toLowerCase()=="html"){return ;}}if(YAHOO.util.Dom.hasClass(A,B.Style.CSS_CELL_SELECTABLE)){YAHOO.util.Dom.removeClass(A,B.Style.CSS_CELL_HOVER);}};YAHOO.widget.Calendar.prototype.setupConfig=function(){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;this.cfg.addProperty(A.PAGEDATE.key,{value:new Date(),handler:this.configPageDate});this.cfg.addProperty(A.SELECTED.key,{value:[],handler:this.configSelected});this.cfg.addProperty(A.TITLE.key,{value:A.TITLE.value,handler:this.configTitle});this.cfg.addProperty(A.CLOSE.key,{value:A.CLOSE.value,handler:this.configClose});this.cfg.addProperty(A.IFRAME.key,{value:A.IFRAME.value,handler:this.configIframe,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.MINDATE.key,{value:A.MINDATE.value,handler:this.configMinDate});this.cfg.addProperty(A.MAXDATE.key,{value:A.MAXDATE.value,handler:this.configMaxDate});this.cfg.addProperty(A.MULTI_SELECT.key,{value:A.MULTI_SELECT.value,handler:this.configOptions,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.START_WEEKDAY.key,{value:A.START_WEEKDAY.value,handler:this.configOptions,validator:this.cfg.checkNumber});this.cfg.addProperty(A.SHOW_WEEKDAYS.key,{value:A.SHOW_WEEKDAYS.value,handler:this.configOptions,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.SHOW_WEEK_HEADER.key,{value:A.SHOW_WEEK_HEADER.value,handler:this.configOptions,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.SHOW_WEEK_FOOTER.key,{value:A.SHOW_WEEK_FOOTER.value,handler:this.configOptions,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.HIDE_BLANK_WEEKS.key,{value:A.HIDE_BLANK_WEEKS.value,handler:this.configOptions,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.NAV_ARROW_LEFT.key,{value:A.NAV_ARROW_LEFT.value,handler:this.configOptions});this.cfg.addProperty(A.NAV_ARROW_RIGHT.key,{value:A.NAV_ARROW_RIGHT.value,handler:this.configOptions});this.cfg.addProperty(A.MONTHS_SHORT.key,{value:A.MONTHS_SHORT.value,handler:this.configLocale});this.cfg.addProperty(A.MONTHS_LONG.key,{value:A.MONTHS_LONG.value,handler:this.configLocale});this.cfg.addProperty(A.WEEKDAYS_1CHAR.key,{value:A.WEEKDAYS_1CHAR.value,handler:this.configLocale});this.cfg.addProperty(A.WEEKDAYS_SHORT.key,{value:A.WEEKDAYS_SHORT.value,handler:this.configLocale});this.cfg.addProperty(A.WEEKDAYS_MEDIUM.key,{value:A.WEEKDAYS_MEDIUM.value,handler:this.configLocale});this.cfg.addProperty(A.WEEKDAYS_LONG.key,{value:A.WEEKDAYS_LONG.value,handler:this.configLocale});var B=function(){this.cfg.refireEvent(A.LOCALE_MONTHS.key);this.cfg.refireEvent(A.LOCALE_WEEKDAYS.key);};this.cfg.subscribeToConfigEvent(A.START_WEEKDAY.key,B,this,true);this.cfg.subscribeToConfigEvent(A.MONTHS_SHORT.key,B,this,true);this.cfg.subscribeToConfigEvent(A.MONTHS_LONG.key,B,this,true);this.cfg.subscribeToConfigEvent(A.WEEKDAYS_1CHAR.key,B,this,true);this.cfg.subscribeToConfigEvent(A.WEEKDAYS_SHORT.key,B,this,true);this.cfg.subscribeToConfigEvent(A.WEEKDAYS_MEDIUM.key,B,this,true);this.cfg.subscribeToConfigEvent(A.WEEKDAYS_LONG.key,B,this,true);this.cfg.addProperty(A.LOCALE_MONTHS.key,{value:A.LOCALE_MONTHS.value,handler:this.configLocaleValues});this.cfg.addProperty(A.LOCALE_WEEKDAYS.key,{value:A.LOCALE_WEEKDAYS.value,handler:this.configLocaleValues});this.cfg.addProperty(A.DATE_DELIMITER.key,{value:A.DATE_DELIMITER.value,handler:this.configLocale});this.cfg.addProperty(A.DATE_FIELD_DELIMITER.key,{value:A.DATE_FIELD_DELIMITER.value,handler:this.configLocale});this.cfg.addProperty(A.DATE_RANGE_DELIMITER.key,{value:A.DATE_RANGE_DELIMITER.value,handler:this.configLocale});this.cfg.addProperty(A.MY_MONTH_POSITION.key,{value:A.MY_MONTH_POSITION.value,handler:this.configLocale,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MY_YEAR_POSITION.key,{value:A.MY_YEAR_POSITION.value,handler:this.configLocale,validator:this.cfg.checkNumber});
-this.cfg.addProperty(A.MD_MONTH_POSITION.key,{value:A.MD_MONTH_POSITION.value,handler:this.configLocale,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MD_DAY_POSITION.key,{value:A.MD_DAY_POSITION.value,handler:this.configLocale,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MDY_MONTH_POSITION.key,{value:A.MDY_MONTH_POSITION.value,handler:this.configLocale,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MDY_DAY_POSITION.key,{value:A.MDY_DAY_POSITION.value,handler:this.configLocale,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MDY_YEAR_POSITION.key,{value:A.MDY_YEAR_POSITION.value,handler:this.configLocale,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MY_LABEL_MONTH_POSITION.key,{value:A.MY_LABEL_MONTH_POSITION.value,handler:this.configLocale,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MY_LABEL_YEAR_POSITION.key,{value:A.MY_LABEL_YEAR_POSITION.value,handler:this.configLocale,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MY_LABEL_MONTH_SUFFIX.key,{value:A.MY_LABEL_MONTH_SUFFIX.value,handler:this.configLocale});this.cfg.addProperty(A.MY_LABEL_YEAR_SUFFIX.key,{value:A.MY_LABEL_YEAR_SUFFIX.value,handler:this.configLocale});};YAHOO.widget.Calendar.prototype.configPageDate=function(B,A,C){this.cfg.setProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key,this._parsePageDate(A[0]),true);};YAHOO.widget.Calendar.prototype.configMinDate=function(B,A,C){var D=A[0];if(YAHOO.lang.isString(D)){D=this._parseDate(D);this.cfg.setProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.MINDATE.key,new Date(D[0],(D[1]-1),D[2]));}};YAHOO.widget.Calendar.prototype.configMaxDate=function(B,A,C){var D=A[0];if(YAHOO.lang.isString(D)){D=this._parseDate(D);this.cfg.setProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.MAXDATE.key,new Date(D[0],(D[1]-1),D[2]));}};YAHOO.widget.Calendar.prototype.configSelected=function(C,A,E){var B=A[0];var D=YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;if(B){if(YAHOO.lang.isString(B)){this.cfg.setProperty(D,this._parseDates(B),true);}}if(!this._selectedDates){this._selectedDates=this.cfg.getProperty(D);}};YAHOO.widget.Calendar.prototype.configOptions=function(B,A,C){this.Options[B.toUpperCase()]=A[0];};YAHOO.widget.Calendar.prototype.configLocale=function(C,B,D){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;this.Locale[C.toUpperCase()]=B[0];this.cfg.refireEvent(A.LOCALE_MONTHS.key);this.cfg.refireEvent(A.LOCALE_WEEKDAYS.key);};YAHOO.widget.Calendar.prototype.configLocaleValues=function(D,C,E){var B=YAHOO.widget.Calendar._DEFAULT_CONFIG;D=D.toLowerCase();var G=C[0];switch(D){case B.LOCALE_MONTHS.key:switch(G){case YAHOO.widget.Calendar.SHORT:this.Locale.LOCALE_MONTHS=this.cfg.getProperty(B.MONTHS_SHORT.key).concat();break;case YAHOO.widget.Calendar.LONG:this.Locale.LOCALE_MONTHS=this.cfg.getProperty(B.MONTHS_LONG.key).concat();break;}break;case B.LOCALE_WEEKDAYS.key:switch(G){case YAHOO.widget.Calendar.ONE_CHAR:this.Locale.LOCALE_WEEKDAYS=this.cfg.getProperty(B.WEEKDAYS_1CHAR.key).concat();break;case YAHOO.widget.Calendar.SHORT:this.Locale.LOCALE_WEEKDAYS=this.cfg.getProperty(B.WEEKDAYS_SHORT.key).concat();break;case YAHOO.widget.Calendar.MEDIUM:this.Locale.LOCALE_WEEKDAYS=this.cfg.getProperty(B.WEEKDAYS_MEDIUM.key).concat();break;case YAHOO.widget.Calendar.LONG:this.Locale.LOCALE_WEEKDAYS=this.cfg.getProperty(B.WEEKDAYS_LONG.key).concat();break;}var F=this.cfg.getProperty(B.START_WEEKDAY.key);if(F>0){for(var A=0;A<F;++A){this.Locale.LOCALE_WEEKDAYS.push(this.Locale.LOCALE_WEEKDAYS.shift());}}break;}};YAHOO.widget.Calendar.prototype.initStyles=function(){var A=YAHOO.widget.Calendar._STYLES;this.Style={CSS_ROW_HEADER:A.CSS_ROW_HEADER,CSS_ROW_FOOTER:A.CSS_ROW_FOOTER,CSS_CELL:A.CSS_CELL,CSS_CELL_SELECTOR:A.CSS_CELL_SELECTOR,CSS_CELL_SELECTED:A.CSS_CELL_SELECTED,CSS_CELL_SELECTABLE:A.CSS_CELL_SELECTABLE,CSS_CELL_RESTRICTED:A.CSS_CELL_RESTRICTED,CSS_CELL_TODAY:A.CSS_CELL_TODAY,CSS_CELL_OOM:A.CSS_CELL_OOM,CSS_CELL_OOB:A.CSS_CELL_OOB,CSS_HEADER:A.CSS_HEADER,CSS_HEADER_TEXT:A.CSS_HEADER_TEXT,CSS_BODY:A.CSS_BODY,CSS_WEEKDAY_CELL:A.CSS_WEEKDAY_CELL,CSS_WEEKDAY_ROW:A.CSS_WEEKDAY_ROW,CSS_FOOTER:A.CSS_FOOTER,CSS_CALENDAR:A.CSS_CALENDAR,CSS_SINGLE:A.CSS_SINGLE,CSS_CONTAINER:A.CSS_CONTAINER,CSS_NAV_LEFT:A.CSS_NAV_LEFT,CSS_NAV_RIGHT:A.CSS_NAV_RIGHT,CSS_CLOSE:A.CSS_CLOSE,CSS_CELL_TOP:A.CSS_CELL_TOP,CSS_CELL_LEFT:A.CSS_CELL_LEFT,CSS_CELL_RIGHT:A.CSS_CELL_RIGHT,CSS_CELL_BOTTOM:A.CSS_CELL_BOTTOM,CSS_CELL_HOVER:A.CSS_CELL_HOVER,CSS_CELL_HIGHLIGHT1:A.CSS_CELL_HIGHLIGHT1,CSS_CELL_HIGHLIGHT2:A.CSS_CELL_HIGHLIGHT2,CSS_CELL_HIGHLIGHT3:A.CSS_CELL_HIGHLIGHT3,CSS_CELL_HIGHLIGHT4:A.CSS_CELL_HIGHLIGHT4};};YAHOO.widget.Calendar.prototype.buildMonthLabel=function(){var A=this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key);var C=this.Locale.LOCALE_MONTHS[A.getMonth()]+this.Locale.MY_LABEL_MONTH_SUFFIX;var B=A.getFullYear()+this.Locale.MY_LABEL_YEAR_SUFFIX;if(this.Locale.MY_LABEL_MONTH_POSITION==2||this.Locale.MY_LABEL_YEAR_POSITION==1){return B+C;}else{return C+B;}};YAHOO.widget.Calendar.prototype.buildDayLabel=function(A){return A.getDate();};YAHOO.widget.Calendar.prototype.createTitleBar=function(A){var B=YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE,"div",this.oDomContainer)[0]||document.createElement("div");B.className=YAHOO.widget.CalendarGroup.CSS_2UPTITLE;B.innerHTML=A;this.oDomContainer.insertBefore(B,this.oDomContainer.firstChild);YAHOO.util.Dom.addClass(this.oDomContainer,"withtitle");return B;};YAHOO.widget.Calendar.prototype.removeTitleBar=function(){var A=YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE,"div",this.oDomContainer)[0]||null;if(A){YAHOO.util.Event.purgeElement(A);this.oDomContainer.removeChild(A);}YAHOO.util.Dom.removeClass(this.oDomContainer,"withtitle");};YAHOO.widget.Calendar.prototype.createCloseButton=function(){var D=YAHOO.util.Dom,A=YAHOO.util.Event,C=YAHOO.widget.CalendarGroup.CSS_2UPCLOSE,F="us/my/bn/x_d.gif";var E=D.getElementsByClassName("link-close","a",this.oDomContainer)[0];
-if(!E){E=document.createElement("a");A.addListener(E,"click",function(H,G){G.hide();A.preventDefault(H);},this);}E.href="#";E.className="link-close";if(YAHOO.widget.Calendar.IMG_ROOT!==null){var B=D.getElementsByClassName(C,"img",E)[0]||document.createElement("img");B.src=YAHOO.widget.Calendar.IMG_ROOT+F;B.className=C;E.appendChild(B);}else{E.innerHTML="<span class=\""+C+" "+this.Style.CSS_CLOSE+"\"></span>";}this.oDomContainer.appendChild(E);return E;};YAHOO.widget.Calendar.prototype.removeCloseButton=function(){var A=YAHOO.util.Dom.getElementsByClassName("link-close","a",this.oDomContainer)[0]||null;if(A){YAHOO.util.Event.purgeElement(A);this.oDomContainer.removeChild(A);}};YAHOO.widget.Calendar.prototype.renderHeader=function(E){var H=7;var F="us/tr/callt.gif";var G="us/tr/calrt.gif";var L=YAHOO.widget.Calendar._DEFAULT_CONFIG;if(this.cfg.getProperty(L.SHOW_WEEK_HEADER.key)){H+=1;}if(this.cfg.getProperty(L.SHOW_WEEK_FOOTER.key)){H+=1;}E[E.length]="<thead>";E[E.length]="<tr>";E[E.length]="<th colspan=\""+H+"\" class=\""+this.Style.CSS_HEADER_TEXT+"\">";E[E.length]="<div class=\""+this.Style.CSS_HEADER+"\">";var J,K=false;if(this.parent){if(this.index===0){J=true;}if(this.index==(this.parent.cfg.getProperty("pages")-1)){K=true;}}else{J=true;K=true;}var B=this.parent||this;if(J){var A=this.cfg.getProperty(L.NAV_ARROW_LEFT.key);if(A===null&&YAHOO.widget.Calendar.IMG_ROOT!==null){A=YAHOO.widget.Calendar.IMG_ROOT+F;}var C=(A===null)?"":" style=\"background-image:url("+A+")\"";E[E.length]="<a class=\""+this.Style.CSS_NAV_LEFT+"\""+C+" > </a>";}E[E.length]=this.buildMonthLabel();if(K){var D=this.cfg.getProperty(L.NAV_ARROW_RIGHT.key);if(D===null&&YAHOO.widget.Calendar.IMG_ROOT!==null){D=YAHOO.widget.Calendar.IMG_ROOT+G;}var I=(D===null)?"":" style=\"background-image:url("+D+")\"";E[E.length]="<a class=\""+this.Style.CSS_NAV_RIGHT+"\""+I+" > </a>";}E[E.length]="</div>\n</th>\n</tr>";if(this.cfg.getProperty(L.SHOW_WEEKDAYS.key)){E=this.buildWeekdays(E);}E[E.length]="</thead>";return E;};YAHOO.widget.Calendar.prototype.buildWeekdays=function(C){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;C[C.length]="<tr class=\""+this.Style.CSS_WEEKDAY_ROW+"\">";if(this.cfg.getProperty(A.SHOW_WEEK_HEADER.key)){C[C.length]="<th> </th>";}for(var B=0;B<this.Locale.LOCALE_WEEKDAYS.length;++B){C[C.length]="<th class=\"calweekdaycell\">"+this.Locale.LOCALE_WEEKDAYS[B]+"</th>";}if(this.cfg.getProperty(A.SHOW_WEEK_FOOTER.key)){C[C.length]="<th> </th>";}C[C.length]="</tr>";return C;};YAHOO.widget.Calendar.prototype.renderBody=function(c,a){var m=YAHOO.widget.Calendar._DEFAULT_CONFIG;var AC=this.cfg.getProperty(m.START_WEEKDAY.key);this.preMonthDays=c.getDay();if(AC>0){this.preMonthDays-=AC;}if(this.preMonthDays<0){this.preMonthDays+=7;}this.monthDays=YAHOO.widget.DateMath.findMonthEnd(c).getDate();this.postMonthDays=YAHOO.widget.Calendar.DISPLAY_DAYS-this.preMonthDays-this.monthDays;c=YAHOO.widget.DateMath.subtract(c,YAHOO.widget.DateMath.DAY,this.preMonthDays);var Q,H;var G="w";var W="_cell";var U="wd";var k="d";var I;var h;var O=this.today.getFullYear();var j=this.today.getMonth();var D=this.today.getDate();var q=this.cfg.getProperty(m.PAGEDATE.key);var C=this.cfg.getProperty(m.HIDE_BLANK_WEEKS.key);var Z=this.cfg.getProperty(m.SHOW_WEEK_FOOTER.key);var T=this.cfg.getProperty(m.SHOW_WEEK_HEADER.key);var M=this.cfg.getProperty(m.MINDATE.key);var S=this.cfg.getProperty(m.MAXDATE.key);if(M){M=YAHOO.widget.DateMath.clearTime(M);}if(S){S=YAHOO.widget.DateMath.clearTime(S);}a[a.length]="<tbody class=\"m"+(q.getMonth()+1)+" "+this.Style.CSS_BODY+"\">";var AA=0;var J=document.createElement("div");var b=document.createElement("td");J.appendChild(b);var z=new Date(q.getFullYear(),0,1);var o=this.parent||this;for(var u=0;u<6;u++){Q=YAHOO.widget.DateMath.getWeekNumber(c,q.getFullYear(),AC);H=G+Q;if(u!==0&&C===true&&c.getMonth()!=q.getMonth()){break;}else{a[a.length]="<tr class=\""+H+"\">";if(T){a=this.renderRowHeader(Q,a);}for(var AB=0;AB<7;AB++){I=[];h=null;this.clearElement(b);b.className=this.Style.CSS_CELL;b.id=this.id+W+AA;if(c.getDate()==D&&c.getMonth()==j&&c.getFullYear()==O){I[I.length]=o.renderCellStyleToday;}var R=[c.getFullYear(),c.getMonth()+1,c.getDate()];this.cellDates[this.cellDates.length]=R;if(c.getMonth()!=q.getMonth()){I[I.length]=o.renderCellNotThisMonth;}else{YAHOO.util.Dom.addClass(b,U+c.getDay());YAHOO.util.Dom.addClass(b,k+c.getDate());for(var t=0;t<this.renderStack.length;++t){var l=this.renderStack[t];var AD=l[0];var B;var V;var F;switch(AD){case YAHOO.widget.Calendar.DATE:B=l[1][1];V=l[1][2];F=l[1][0];if(c.getMonth()+1==B&&c.getDate()==V&&c.getFullYear()==F){h=l[2];this.renderStack.splice(t,1);}break;case YAHOO.widget.Calendar.MONTH_DAY:B=l[1][0];V=l[1][1];if(c.getMonth()+1==B&&c.getDate()==V){h=l[2];this.renderStack.splice(t,1);}break;case YAHOO.widget.Calendar.RANGE:var Y=l[1][0];var X=l[1][1];var e=Y[1];var L=Y[2];var P=Y[0];var y=new Date(P,e-1,L);var E=X[1];var g=X[2];var A=X[0];var w=new Date(A,E-1,g);if(c.getTime()>=y.getTime()&&c.getTime()<=w.getTime()){h=l[2];if(c.getTime()==w.getTime()){this.renderStack.splice(t,1);}}break;case YAHOO.widget.Calendar.WEEKDAY:var K=l[1][0];if(c.getDay()+1==K){h=l[2];}break;case YAHOO.widget.Calendar.MONTH:B=l[1][0];if(c.getMonth()+1==B){h=l[2];}break;}if(h){I[I.length]=h;}}}if(this._indexOfSelectedFieldArray(R)>-1){I[I.length]=o.renderCellStyleSelected;}if((M&&(c.getTime()<M.getTime()))||(S&&(c.getTime()>S.getTime()))){I[I.length]=o.renderOutOfBoundsDate;}else{I[I.length]=o.styleCellDefault;I[I.length]=o.renderCellDefault;}for(var n=0;n<I.length;++n){if(I[n].call(o,c,b)==YAHOO.widget.Calendar.STOP_RENDER){break;}}c.setTime(c.getTime()+YAHOO.widget.DateMath.ONE_DAY_MS);if(AA>=0&&AA<=6){YAHOO.util.Dom.addClass(b,this.Style.CSS_CELL_TOP);}if((AA%7)===0){YAHOO.util.Dom.addClass(b,this.Style.CSS_CELL_LEFT);}if(((AA+1)%7)===0){YAHOO.util.Dom.addClass(b,this.Style.CSS_CELL_RIGHT);}var f=this.postMonthDays;if(C&&f>=7){var N=Math.floor(f/7);for(var v=0;
-v<N;++v){f-=7;}}if(AA>=((this.preMonthDays+f+this.monthDays)-7)){YAHOO.util.Dom.addClass(b,this.Style.CSS_CELL_BOTTOM);}a[a.length]=J.innerHTML;AA++;}if(Z){a=this.renderRowFooter(Q,a);}a[a.length]="</tr>";}}a[a.length]="</tbody>";return a;};YAHOO.widget.Calendar.prototype.renderFooter=function(A){return A;};YAHOO.widget.Calendar.prototype.render=function(){this.beforeRenderEvent.fire();var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;var C=YAHOO.widget.DateMath.findMonthStart(this.cfg.getProperty(A.PAGEDATE.key));this.resetRenderers();this.cellDates.length=0;YAHOO.util.Event.purgeElement(this.oDomContainer,true);var B=[];B[B.length]="<table cellSpacing=\"0\" class=\""+this.Style.CSS_CALENDAR+" y"+C.getFullYear()+"\" id=\""+this.id+"\">";B=this.renderHeader(B);B=this.renderBody(C,B);B=this.renderFooter(B);B[B.length]="</table>";this.oDomContainer.innerHTML=B.join("\n");this.applyListeners();this.cells=this.oDomContainer.getElementsByTagName("td");this.cfg.refireEvent(A.TITLE.key);this.cfg.refireEvent(A.CLOSE.key);this.cfg.refireEvent(A.IFRAME.key);this.renderEvent.fire();};YAHOO.widget.Calendar.prototype.applyListeners=function(){var K=this.oDomContainer;var B=this.parent||this;var G="a";var D="mousedown";var H=YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_LEFT,G,K);var C=YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_RIGHT,G,K);if(H&&H.length>0){this.linkLeft=H[0];YAHOO.util.Event.addListener(this.linkLeft,D,B.previousMonth,B,true);}if(C&&C.length>0){this.linkRight=C[0];YAHOO.util.Event.addListener(this.linkRight,D,B.nextMonth,B,true);}if(this.domEventMap){var E,A;for(var M in this.domEventMap){if(YAHOO.lang.hasOwnProperty(this.domEventMap,M)){var I=this.domEventMap[M];if(!(I instanceof Array)){I=[I];}for(var F=0;F<I.length;F++){var L=I[F];A=YAHOO.util.Dom.getElementsByClassName(M,L.tag,this.oDomContainer);for(var J=0;J<A.length;J++){E=A[J];YAHOO.util.Event.addListener(E,L.event,L.handler,L.scope,L.correct);}}}}}YAHOO.util.Event.addListener(this.oDomContainer,"click",this.doSelectCell,this);YAHOO.util.Event.addListener(this.oDomContainer,"mouseover",this.doCellMouseOver,this);YAHOO.util.Event.addListener(this.oDomContainer,"mouseout",this.doCellMouseOut,this);};YAHOO.widget.Calendar.prototype.getDateByCellId=function(B){var A=this.getDateFieldsByCellId(B);return new Date(A[0],A[1]-1,A[2]);};YAHOO.widget.Calendar.prototype.getDateFieldsByCellId=function(A){A=A.toLowerCase().split("_cell")[1];A=parseInt(A,10);return this.cellDates[A];};YAHOO.widget.Calendar.prototype.renderOutOfBoundsDate=function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_OOB);A.innerHTML=B.getDate();return YAHOO.widget.Calendar.STOP_RENDER;};YAHOO.widget.Calendar.prototype.renderRowHeader=function(B,A){A[A.length]="<th class=\"calrowhead\">"+B+"</th>";return A;};YAHOO.widget.Calendar.prototype.renderRowFooter=function(B,A){A[A.length]="<th class=\"calrowfoot\">"+B+"</th>";return A;};YAHOO.widget.Calendar.prototype.renderCellDefault=function(B,A){A.innerHTML="<a href=\"#\" class=\""+this.Style.CSS_CELL_SELECTOR+"\">"+this.buildDayLabel(B)+"</a>";};YAHOO.widget.Calendar.prototype.styleCellDefault=function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_SELECTABLE);};YAHOO.widget.Calendar.prototype.renderCellStyleHighlight1=function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT1);};YAHOO.widget.Calendar.prototype.renderCellStyleHighlight2=function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT2);};YAHOO.widget.Calendar.prototype.renderCellStyleHighlight3=function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT3);};YAHOO.widget.Calendar.prototype.renderCellStyleHighlight4=function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_HIGHLIGHT4);};YAHOO.widget.Calendar.prototype.renderCellStyleToday=function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_TODAY);};YAHOO.widget.Calendar.prototype.renderCellStyleSelected=function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_SELECTED);};YAHOO.widget.Calendar.prototype.renderCellNotThisMonth=function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_OOM);A.innerHTML=B.getDate();return YAHOO.widget.Calendar.STOP_RENDER;};YAHOO.widget.Calendar.prototype.renderBodyCellRestricted=function(B,A){YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL);YAHOO.util.Dom.addClass(A,this.Style.CSS_CELL_RESTRICTED);A.innerHTML=B.getDate();return YAHOO.widget.Calendar.STOP_RENDER;};YAHOO.widget.Calendar.prototype.addMonths=function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.add(this.cfg.getProperty(A),YAHOO.widget.DateMath.MONTH,B));this.resetRenderers();this.changePageEvent.fire();};YAHOO.widget.Calendar.prototype.subtractMonths=function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.subtract(this.cfg.getProperty(A),YAHOO.widget.DateMath.MONTH,B));this.resetRenderers();this.changePageEvent.fire();};YAHOO.widget.Calendar.prototype.addYears=function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.add(this.cfg.getProperty(A),YAHOO.widget.DateMath.YEAR,B));this.resetRenderers();this.changePageEvent.fire();};YAHOO.widget.Calendar.prototype.subtractYears=function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;this.cfg.setProperty(A,YAHOO.widget.DateMath.subtract(this.cfg.getProperty(A),YAHOO.widget.DateMath.YEAR,B));this.resetRenderers();this.changePageEvent.fire();};YAHOO.widget.Calendar.prototype.nextMonth=function(){this.addMonths(1);};YAHOO.widget.Calendar.prototype.previousMonth=function(){this.subtractMonths(1);};YAHOO.widget.Calendar.prototype.nextYear=function(){this.addYears(1);};YAHOO.widget.Calendar.prototype.previousYear=function(){this.subtractYears(1);};YAHOO.widget.Calendar.prototype.reset=function(){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;this.cfg.resetProperty(A.SELECTED.key);this.cfg.resetProperty(A.PAGEDATE.key);this.resetEvent.fire();
-};YAHOO.widget.Calendar.prototype.clear=function(){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;this.cfg.setProperty(A.SELECTED.key,[]);this.cfg.setProperty(A.PAGEDATE.key,new Date(this.today.getTime()));this.clearEvent.fire();};YAHOO.widget.Calendar.prototype.select=function(C){var F=this._toFieldArray(C);var B=[];var E=[];var G=YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;for(var A=0;A<F.length;++A){var D=F[A];if(!this.isDateOOB(this._toDate(D))){if(B.length===0){this.beforeSelectEvent.fire();E=this.cfg.getProperty(G);}B.push(D);if(this._indexOfSelectedFieldArray(D)==-1){E[E.length]=D;}}}if(B.length>0){if(this.parent){this.parent.cfg.setProperty(G,E);}else{this.cfg.setProperty(G,E);}this.selectEvent.fire(B);}return this.getSelectedDates();};YAHOO.widget.Calendar.prototype.selectCell=function(D){var B=this.cells[D];var H=this.cellDates[D];var G=this._toDate(H);var C=YAHOO.util.Dom.hasClass(B,this.Style.CSS_CELL_SELECTABLE);if(C){this.beforeSelectEvent.fire();var F=YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;var E=this.cfg.getProperty(F);var A=H.concat();if(this._indexOfSelectedFieldArray(A)==-1){E[E.length]=A;}if(this.parent){this.parent.cfg.setProperty(F,E);}else{this.cfg.setProperty(F,E);}this.renderCellStyleSelected(G,B);this.selectEvent.fire([A]);this.doCellMouseOut.call(B,null,this);}return this.getSelectedDates();};YAHOO.widget.Calendar.prototype.deselect=function(E){var A=this._toFieldArray(E);var D=[];var G=[];var H=YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;for(var B=0;B<A.length;++B){var F=A[B];if(!this.isDateOOB(this._toDate(F))){if(D.length===0){this.beforeDeselectEvent.fire();G=this.cfg.getProperty(H);}D.push(F);var C=this._indexOfSelectedFieldArray(F);if(C!=-1){G.splice(C,1);}}}if(D.length>0){if(this.parent){this.parent.cfg.setProperty(H,G);}else{this.cfg.setProperty(H,G);}this.deselectEvent.fire(D);}return this.getSelectedDates();};YAHOO.widget.Calendar.prototype.deselectCell=function(E){var H=this.cells[E];var B=this.cellDates[E];var F=this._indexOfSelectedFieldArray(B);var G=YAHOO.util.Dom.hasClass(H,this.Style.CSS_CELL_SELECTABLE);if(G){this.beforeDeselectEvent.fire();var I=YAHOO.widget.Calendar._DEFAULT_CONFIG;var D=this.cfg.getProperty(I.SELECTED.key);var C=this._toDate(B);var A=B.concat();if(F>-1){if(this.cfg.getProperty(I.PAGEDATE.key).getMonth()==C.getMonth()&&this.cfg.getProperty(I.PAGEDATE.key).getFullYear()==C.getFullYear()){YAHOO.util.Dom.removeClass(H,this.Style.CSS_CELL_SELECTED);}D.splice(F,1);}if(this.parent){this.parent.cfg.setProperty(I.SELECTED.key,D);}else{this.cfg.setProperty(I.SELECTED.key,D);}this.deselectEvent.fire(A);}return this.getSelectedDates();};YAHOO.widget.Calendar.prototype.deselectAll=function(){this.beforeDeselectEvent.fire();var D=YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;var A=this.cfg.getProperty(D);var B=A.length;var C=A.concat();if(this.parent){this.parent.cfg.setProperty(D,[]);}else{this.cfg.setProperty(D,[]);}if(B>0){this.deselectEvent.fire(C);}return this.getSelectedDates();};YAHOO.widget.Calendar.prototype._toFieldArray=function(B){var A=[];if(B instanceof Date){A=[[B.getFullYear(),B.getMonth()+1,B.getDate()]];}else{if(YAHOO.lang.isString(B)){A=this._parseDates(B);}else{if(YAHOO.lang.isArray(B)){for(var C=0;C<B.length;++C){var D=B[C];A[A.length]=[D.getFullYear(),D.getMonth()+1,D.getDate()];}}}}return A;};YAHOO.widget.Calendar.prototype._toDate=function(A){if(A instanceof Date){return A;}else{return new Date(A[0],A[1]-1,A[2]);}};YAHOO.widget.Calendar.prototype._fieldArraysAreEqual=function(C,B){var A=false;if(C[0]==B[0]&&C[1]==B[1]&&C[2]==B[2]){A=true;}return A;};YAHOO.widget.Calendar.prototype._indexOfSelectedFieldArray=function(E){var D=-1;var A=this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key);for(var C=0;C<A.length;++C){var B=A[C];if(E[0]==B[0]&&E[1]==B[1]&&E[2]==B[2]){D=C;break;}}return D;};YAHOO.widget.Calendar.prototype.isDateOOM=function(A){return(A.getMonth()!=this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key).getMonth());};YAHOO.widget.Calendar.prototype.isDateOOB=function(D){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;var E=this.cfg.getProperty(A.MINDATE.key);var F=this.cfg.getProperty(A.MAXDATE.key);var C=YAHOO.widget.DateMath;if(E){E=C.clearTime(E);}if(F){F=C.clearTime(F);}var B=new Date(D.getTime());B=C.clearTime(B);return((E&&B.getTime()<E.getTime())||(F&&B.getTime()>F.getTime()));};YAHOO.widget.Calendar.prototype._parsePageDate=function(B){var E;var A=YAHOO.widget.Calendar._DEFAULT_CONFIG;if(B){if(B instanceof Date){E=YAHOO.widget.DateMath.findMonthStart(B);}else{var F,D,C;C=B.split(this.cfg.getProperty(A.DATE_FIELD_DELIMITER.key));F=parseInt(C[this.cfg.getProperty(A.MY_MONTH_POSITION.key)-1],10)-1;D=parseInt(C[this.cfg.getProperty(A.MY_YEAR_POSITION.key)-1],10);E=new Date(D,F,1);}}else{E=new Date(this.today.getFullYear(),this.today.getMonth(),1);}return E;};YAHOO.widget.Calendar.prototype.onBeforeSelect=function(){if(this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.MULTI_SELECT.key)===false){if(this.parent){this.parent.callChildFunction("clearAllBodyCellStyles",this.Style.CSS_CELL_SELECTED);this.parent.deselectAll();}else{this.clearAllBodyCellStyles(this.Style.CSS_CELL_SELECTED);this.deselectAll();}}};YAHOO.widget.Calendar.prototype.onSelect=function(A){};YAHOO.widget.Calendar.prototype.onBeforeDeselect=function(){};YAHOO.widget.Calendar.prototype.onDeselect=function(A){};YAHOO.widget.Calendar.prototype.onChangePage=function(){this.render();};YAHOO.widget.Calendar.prototype.onRender=function(){};YAHOO.widget.Calendar.prototype.onReset=function(){this.render();};YAHOO.widget.Calendar.prototype.onClear=function(){this.render();};YAHOO.widget.Calendar.prototype.validate=function(){return true;};YAHOO.widget.Calendar.prototype._parseDate=function(C){var D=C.split(this.Locale.DATE_FIELD_DELIMITER);var A;if(D.length==2){A=[D[this.Locale.MD_MONTH_POSITION-1],D[this.Locale.MD_DAY_POSITION-1]];A.type=YAHOO.widget.Calendar.MONTH_DAY;}else{A=[D[this.Locale.MDY_YEAR_POSITION-1],D[this.Locale.MDY_MONTH_POSITION-1],D[this.Locale.MDY_DAY_POSITION-1]];
-A.type=YAHOO.widget.Calendar.DATE;}for(var B=0;B<A.length;B++){A[B]=parseInt(A[B],10);}return A;};YAHOO.widget.Calendar.prototype._parseDates=function(B){var I=[];var H=B.split(this.Locale.DATE_DELIMITER);for(var G=0;G<H.length;++G){var F=H[G];if(F.indexOf(this.Locale.DATE_RANGE_DELIMITER)!=-1){var A=F.split(this.Locale.DATE_RANGE_DELIMITER);var E=this._parseDate(A[0]);var J=this._parseDate(A[1]);var D=this._parseRange(E,J);I=I.concat(D);}else{var C=this._parseDate(F);I.push(C);}}return I;};YAHOO.widget.Calendar.prototype._parseRange=function(A,F){var E=new Date(A[0],A[1]-1,A[2]);var B=YAHOO.widget.DateMath.add(new Date(A[0],A[1]-1,A[2]),YAHOO.widget.DateMath.DAY,1);var D=new Date(F[0],F[1]-1,F[2]);var C=[];C.push(A);while(B.getTime()<=D.getTime()){C.push([B.getFullYear(),B.getMonth()+1,B.getDate()]);B=YAHOO.widget.DateMath.add(B,YAHOO.widget.DateMath.DAY,1);}return C;};YAHOO.widget.Calendar.prototype.resetRenderers=function(){this.renderStack=this._renderStack.concat();};YAHOO.widget.Calendar.prototype.clearElement=function(A){A.innerHTML=" ";A.className="";};YAHOO.widget.Calendar.prototype.addRenderer=function(A,B){var D=this._parseDates(A);for(var C=0;C<D.length;++C){var E=D[C];if(E.length==2){if(E[0] instanceof Array){this._addRenderer(YAHOO.widget.Calendar.RANGE,E,B);}else{this._addRenderer(YAHOO.widget.Calendar.MONTH_DAY,E,B);}}else{if(E.length==3){this._addRenderer(YAHOO.widget.Calendar.DATE,E,B);}}}};YAHOO.widget.Calendar.prototype._addRenderer=function(B,C,A){var D=[B,C,A];this.renderStack.unshift(D);this._renderStack=this.renderStack.concat();};YAHOO.widget.Calendar.prototype.addMonthRenderer=function(B,A){this._addRenderer(YAHOO.widget.Calendar.MONTH,[B],A);};YAHOO.widget.Calendar.prototype.addWeekdayRenderer=function(B,A){this._addRenderer(YAHOO.widget.Calendar.WEEKDAY,[B],A);};YAHOO.widget.Calendar.prototype.clearAllBodyCellStyles=function(A){for(var B=0;B<this.cells.length;++B){YAHOO.util.Dom.removeClass(this.cells[B],A);}};YAHOO.widget.Calendar.prototype.setMonth=function(C){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;var B=this.cfg.getProperty(A);B.setMonth(parseInt(C,10));this.cfg.setProperty(A,B);};YAHOO.widget.Calendar.prototype.setYear=function(B){var A=YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;var C=this.cfg.getProperty(A);C.setFullYear(parseInt(B,10));this.cfg.setProperty(A,C);};YAHOO.widget.Calendar.prototype.getSelectedDates=function(){var C=[];var B=this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key);for(var E=0;E<B.length;++E){var D=B[E];var A=new Date(D[0],D[1]-1,D[2]);C.push(A);}C.sort(function(G,F){return G-F;});return C;};YAHOO.widget.Calendar.prototype.hide=function(){this.oDomContainer.style.display="none";};YAHOO.widget.Calendar.prototype.show=function(){this.oDomContainer.style.display="block";};YAHOO.widget.Calendar.prototype.browser=function(){var A=navigator.userAgent.toLowerCase();if(A.indexOf("opera")!=-1){return"opera";}else{if(A.indexOf("msie 7")!=-1){return"ie7";}else{if(A.indexOf("msie")!=-1){return"ie";}else{if(A.indexOf("safari")!=-1){return"safari";}else{if(A.indexOf("gecko")!=-1){return"gecko";}else{return false;}}}}}}();YAHOO.widget.Calendar.prototype.toString=function(){return"Calendar "+this.id;};YAHOO.widget.Calendar_Core=YAHOO.widget.Calendar;YAHOO.widget.Cal_Core=YAHOO.widget.Calendar;YAHOO.widget.CalendarGroup=function(C,A,B){if(arguments.length>0){this.init(C,A,B);}};YAHOO.widget.CalendarGroup.prototype.init=function(C,A,B){this.initEvents();this.initStyles();this.pages=[];this.id=C;this.containerId=A;this.oDomContainer=document.getElementById(A);YAHOO.util.Dom.addClass(this.oDomContainer,YAHOO.widget.CalendarGroup.CSS_CONTAINER);YAHOO.util.Dom.addClass(this.oDomContainer,YAHOO.widget.CalendarGroup.CSS_MULTI_UP);this.cfg=new YAHOO.util.Config(this);this.Options={};this.Locale={};this.setupConfig();if(B){this.cfg.applyConfig(B,true);}this.cfg.fireQueue();if(YAHOO.env.ua.opera){this.renderEvent.subscribe(this._fixWidth,this,true);}};YAHOO.widget.CalendarGroup.prototype.setupConfig=function(){var A=YAHOO.widget.CalendarGroup._DEFAULT_CONFIG;this.cfg.addProperty(A.PAGES.key,{value:A.PAGES.value,validator:this.cfg.checkNumber,handler:this.configPages});this.cfg.addProperty(A.PAGEDATE.key,{value:new Date(),handler:this.configPageDate});this.cfg.addProperty(A.SELECTED.key,{value:[],handler:this.configSelected});this.cfg.addProperty(A.TITLE.key,{value:A.TITLE.value,handler:this.configTitle});this.cfg.addProperty(A.CLOSE.key,{value:A.CLOSE.value,handler:this.configClose});this.cfg.addProperty(A.IFRAME.key,{value:A.IFRAME.value,handler:this.configIframe,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.MINDATE.key,{value:A.MINDATE.value,handler:this.delegateConfig});this.cfg.addProperty(A.MAXDATE.key,{value:A.MAXDATE.value,handler:this.delegateConfig});this.cfg.addProperty(A.MULTI_SELECT.key,{value:A.MULTI_SELECT.value,handler:this.delegateConfig,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.START_WEEKDAY.key,{value:A.START_WEEKDAY.value,handler:this.delegateConfig,validator:this.cfg.checkNumber});this.cfg.addProperty(A.SHOW_WEEKDAYS.key,{value:A.SHOW_WEEKDAYS.value,handler:this.delegateConfig,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.SHOW_WEEK_HEADER.key,{value:A.SHOW_WEEK_HEADER.value,handler:this.delegateConfig,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.SHOW_WEEK_FOOTER.key,{value:A.SHOW_WEEK_FOOTER.value,handler:this.delegateConfig,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.HIDE_BLANK_WEEKS.key,{value:A.HIDE_BLANK_WEEKS.value,handler:this.delegateConfig,validator:this.cfg.checkBoolean});this.cfg.addProperty(A.NAV_ARROW_LEFT.key,{value:A.NAV_ARROW_LEFT.value,handler:this.delegateConfig});this.cfg.addProperty(A.NAV_ARROW_RIGHT.key,{value:A.NAV_ARROW_RIGHT.value,handler:this.delegateConfig});this.cfg.addProperty(A.MONTHS_SHORT.key,{value:A.MONTHS_SHORT.value,handler:this.delegateConfig});this.cfg.addProperty(A.MONTHS_LONG.key,{value:A.MONTHS_LONG.value,handler:this.delegateConfig});
-this.cfg.addProperty(A.WEEKDAYS_1CHAR.key,{value:A.WEEKDAYS_1CHAR.value,handler:this.delegateConfig});this.cfg.addProperty(A.WEEKDAYS_SHORT.key,{value:A.WEEKDAYS_SHORT.value,handler:this.delegateConfig});this.cfg.addProperty(A.WEEKDAYS_MEDIUM.key,{value:A.WEEKDAYS_MEDIUM.value,handler:this.delegateConfig});this.cfg.addProperty(A.WEEKDAYS_LONG.key,{value:A.WEEKDAYS_LONG.value,handler:this.delegateConfig});this.cfg.addProperty(A.LOCALE_MONTHS.key,{value:A.LOCALE_MONTHS.value,handler:this.delegateConfig});this.cfg.addProperty(A.LOCALE_WEEKDAYS.key,{value:A.LOCALE_WEEKDAYS.value,handler:this.delegateConfig});this.cfg.addProperty(A.DATE_DELIMITER.key,{value:A.DATE_DELIMITER.value,handler:this.delegateConfig});this.cfg.addProperty(A.DATE_FIELD_DELIMITER.key,{value:A.DATE_FIELD_DELIMITER.value,handler:this.delegateConfig});this.cfg.addProperty(A.DATE_RANGE_DELIMITER.key,{value:A.DATE_RANGE_DELIMITER.value,handler:this.delegateConfig});this.cfg.addProperty(A.MY_MONTH_POSITION.key,{value:A.MY_MONTH_POSITION.value,handler:this.delegateConfig,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MY_YEAR_POSITION.key,{value:A.MY_YEAR_POSITION.value,handler:this.delegateConfig,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MD_MONTH_POSITION.key,{value:A.MD_MONTH_POSITION.value,handler:this.delegateConfig,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MD_DAY_POSITION.key,{value:A.MD_DAY_POSITION.value,handler:this.delegateConfig,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MDY_MONTH_POSITION.key,{value:A.MDY_MONTH_POSITION.value,handler:this.delegateConfig,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MDY_DAY_POSITION.key,{value:A.MDY_DAY_POSITION.value,handler:this.delegateConfig,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MDY_YEAR_POSITION.key,{value:A.MDY_YEAR_POSITION.value,handler:this.delegateConfig,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MY_LABEL_MONTH_POSITION.key,{value:A.MY_LABEL_MONTH_POSITION.value,handler:this.delegateConfig,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MY_LABEL_YEAR_POSITION.key,{value:A.MY_LABEL_YEAR_POSITION.value,handler:this.delegateConfig,validator:this.cfg.checkNumber});this.cfg.addProperty(A.MY_LABEL_MONTH_SUFFIX.key,{value:A.MY_LABEL_MONTH_SUFFIX.value,handler:this.delegateConfig});this.cfg.addProperty(A.MY_LABEL_YEAR_SUFFIX.key,{value:A.MY_LABEL_YEAR_SUFFIX.value,handler:this.delegateConfig});};YAHOO.widget.CalendarGroup.prototype.initEvents=function(){var C=this;var E="Event";var B=function(G,J,F){for(var I=0;I<C.pages.length;++I){var H=C.pages[I];H[this.type+E].subscribe(G,J,F);}};var A=function(F,I){for(var H=0;H<C.pages.length;++H){var G=C.pages[H];G[this.type+E].unsubscribe(F,I);}};var D=YAHOO.widget.Calendar._EVENT_TYPES;this.beforeSelectEvent=new YAHOO.util.CustomEvent(D.BEFORE_SELECT);this.beforeSelectEvent.subscribe=B;this.beforeSelectEvent.unsubscribe=A;this.selectEvent=new YAHOO.util.CustomEvent(D.SELECT);this.selectEvent.subscribe=B;this.selectEvent.unsubscribe=A;this.beforeDeselectEvent=new YAHOO.util.CustomEvent(D.BEFORE_DESELECT);this.beforeDeselectEvent.subscribe=B;this.beforeDeselectEvent.unsubscribe=A;this.deselectEvent=new YAHOO.util.CustomEvent(D.DESELECT);this.deselectEvent.subscribe=B;this.deselectEvent.unsubscribe=A;this.changePageEvent=new YAHOO.util.CustomEvent(D.CHANGE_PAGE);this.changePageEvent.subscribe=B;this.changePageEvent.unsubscribe=A;this.beforeRenderEvent=new YAHOO.util.CustomEvent(D.BEFORE_RENDER);this.beforeRenderEvent.subscribe=B;this.beforeRenderEvent.unsubscribe=A;this.renderEvent=new YAHOO.util.CustomEvent(D.RENDER);this.renderEvent.subscribe=B;this.renderEvent.unsubscribe=A;this.resetEvent=new YAHOO.util.CustomEvent(D.RESET);this.resetEvent.subscribe=B;this.resetEvent.unsubscribe=A;this.clearEvent=new YAHOO.util.CustomEvent(D.CLEAR);this.clearEvent.subscribe=B;this.clearEvent.unsubscribe=A;};YAHOO.widget.CalendarGroup.prototype.configPages=function(K,J,G){var E=J[0];var C=YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.PAGEDATE.key;var O="_";var L="groupcal";var N="first-of-type";var D="last-of-type";for(var B=0;B<E;++B){var M=this.id+O+B;var I=this.containerId+O+B;var H=this.cfg.getConfig();H.close=false;H.title=false;var A=this.constructChild(M,I,H);var F=A.cfg.getProperty(C);this._setMonthOnDate(F,F.getMonth()+B);A.cfg.setProperty(C,F);YAHOO.util.Dom.removeClass(A.oDomContainer,this.Style.CSS_SINGLE);YAHOO.util.Dom.addClass(A.oDomContainer,L);if(B===0){YAHOO.util.Dom.addClass(A.oDomContainer,N);}if(B==(E-1)){YAHOO.util.Dom.addClass(A.oDomContainer,D);}A.parent=this;A.index=B;this.pages[this.pages.length]=A;}};YAHOO.widget.CalendarGroup.prototype.configPageDate=function(H,G,E){var C=G[0];var F;var D=YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.PAGEDATE.key;for(var B=0;B<this.pages.length;++B){var A=this.pages[B];if(B===0){F=A._parsePageDate(C);A.cfg.setProperty(D,F);}else{var I=new Date(F);this._setMonthOnDate(I,I.getMonth()+B);A.cfg.setProperty(D,I);}}};YAHOO.widget.CalendarGroup.prototype.configSelected=function(C,A,E){var D=YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.SELECTED.key;this.delegateConfig(C,A,E);var B=(this.pages.length>0)?this.pages[0].cfg.getProperty(D):[];this.cfg.setProperty(D,B,true);};YAHOO.widget.CalendarGroup.prototype.delegateConfig=function(B,A,E){var F=A[0];var D;for(var C=0;C<this.pages.length;C++){D=this.pages[C];D.cfg.setProperty(B,F);}};YAHOO.widget.CalendarGroup.prototype.setChildFunction=function(D,B){var A=this.cfg.getProperty(YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.PAGES.key);for(var C=0;C<A;++C){this.pages[C][D]=B;}};YAHOO.widget.CalendarGroup.prototype.callChildFunction=function(F,B){var A=this.cfg.getProperty(YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.PAGES.key);for(var E=0;E<A;++E){var D=this.pages[E];if(D[F]){var C=D[F];C.call(D,B);}}};YAHOO.widget.CalendarGroup.prototype.constructChild=function(D,B,C){var A=document.getElementById(B);if(!A){A=document.createElement("div");A.id=B;this.oDomContainer.appendChild(A);
-}return new YAHOO.widget.Calendar(D,B,C);};YAHOO.widget.CalendarGroup.prototype.setMonth=function(E){E=parseInt(E,10);var F;var B=YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.PAGEDATE.key;for(var D=0;D<this.pages.length;++D){var C=this.pages[D];var A=C.cfg.getProperty(B);if(D===0){F=A.getFullYear();}else{A.setYear(F);}this._setMonthOnDate(A,E+D);C.cfg.setProperty(B,A);}};YAHOO.widget.CalendarGroup.prototype.setYear=function(C){var B=YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.PAGEDATE.key;C=parseInt(C,10);for(var E=0;E<this.pages.length;++E){var D=this.pages[E];var A=D.cfg.getProperty(B);if((A.getMonth()+1)==1&&E>0){C+=1;}D.setYear(C);}};YAHOO.widget.CalendarGroup.prototype.render=function(){this.renderHeader();for(var B=0;B<this.pages.length;++B){var A=this.pages[B];A.render();}this.renderFooter();};YAHOO.widget.CalendarGroup.prototype.select=function(A){for(var C=0;C<this.pages.length;++C){var B=this.pages[C];B.select(A);}return this.getSelectedDates();};YAHOO.widget.CalendarGroup.prototype.selectCell=function(A){for(var C=0;C<this.pages.length;++C){var B=this.pages[C];B.selectCell(A);}return this.getSelectedDates();};YAHOO.widget.CalendarGroup.prototype.deselect=function(A){for(var C=0;C<this.pages.length;++C){var B=this.pages[C];B.deselect(A);}return this.getSelectedDates();};YAHOO.widget.CalendarGroup.prototype.deselectAll=function(){for(var B=0;B<this.pages.length;++B){var A=this.pages[B];A.deselectAll();}return this.getSelectedDates();};YAHOO.widget.CalendarGroup.prototype.deselectCell=function(A){for(var C=0;C<this.pages.length;++C){var B=this.pages[C];B.deselectCell(A);}return this.getSelectedDates();};YAHOO.widget.CalendarGroup.prototype.reset=function(){for(var B=0;B<this.pages.length;++B){var A=this.pages[B];A.reset();}};YAHOO.widget.CalendarGroup.prototype.clear=function(){for(var B=0;B<this.pages.length;++B){var A=this.pages[B];A.clear();}};YAHOO.widget.CalendarGroup.prototype.nextMonth=function(){for(var B=0;B<this.pages.length;++B){var A=this.pages[B];A.nextMonth();}};YAHOO.widget.CalendarGroup.prototype.previousMonth=function(){for(var B=this.pages.length-1;B>=0;--B){var A=this.pages[B];A.previousMonth();}};YAHOO.widget.CalendarGroup.prototype.nextYear=function(){for(var B=0;B<this.pages.length;++B){var A=this.pages[B];A.nextYear();}};YAHOO.widget.CalendarGroup.prototype.previousYear=function(){for(var B=0;B<this.pages.length;++B){var A=this.pages[B];A.previousYear();}};YAHOO.widget.CalendarGroup.prototype.getSelectedDates=function(){var C=[];var B=this.cfg.getProperty(YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.SELECTED.key);for(var E=0;E<B.length;++E){var D=B[E];var A=new Date(D[0],D[1]-1,D[2]);C.push(A);}C.sort(function(G,F){return G-F;});return C;};YAHOO.widget.CalendarGroup.prototype.addRenderer=function(A,B){for(var D=0;D<this.pages.length;++D){var C=this.pages[D];C.addRenderer(A,B);}};YAHOO.widget.CalendarGroup.prototype.addMonthRenderer=function(D,A){for(var C=0;C<this.pages.length;++C){var B=this.pages[C];B.addMonthRenderer(D,A);}};YAHOO.widget.CalendarGroup.prototype.addWeekdayRenderer=function(B,A){for(var D=0;D<this.pages.length;++D){var C=this.pages[D];C.addWeekdayRenderer(B,A);}};YAHOO.widget.CalendarGroup.prototype.renderHeader=function(){};YAHOO.widget.CalendarGroup.prototype.renderFooter=function(){};YAHOO.widget.CalendarGroup.prototype.addMonths=function(A){this.callChildFunction("addMonths",A);};YAHOO.widget.CalendarGroup.prototype.subtractMonths=function(A){this.callChildFunction("subtractMonths",A);};YAHOO.widget.CalendarGroup.prototype.addYears=function(A){this.callChildFunction("addYears",A);};YAHOO.widget.CalendarGroup.prototype.subtractYears=function(A){this.callChildFunction("subtractYears",A);};YAHOO.widget.CalendarGroup.prototype.show=function(){this.oDomContainer.style.display="block";if(YAHOO.env.ua.opera){this._fixWidth();}};YAHOO.widget.CalendarGroup.prototype._setMonthOnDate=function(C,D){if(YAHOO.env.ua.webkit&&YAHOO.env.ua.webkit<420&&(D<0||D>11)){var B=YAHOO.widget.DateMath;var A=B.add(C,B.MONTH,D-C.getMonth());C.setTime(A.getTime());}else{C.setMonth(D);}};YAHOO.widget.CalendarGroup.prototype._fixWidth=function(){var B=this.oDomContainer.offsetWidth;var A=0;for(var D=0;D<this.pages.length;++D){var C=this.pages[D];A+=C.oDomContainer.offsetWidth;}if(A>0){this.oDomContainer.style.width=A+"px";}};YAHOO.widget.CalendarGroup.CSS_CONTAINER="yui-calcontainer";YAHOO.widget.CalendarGroup.CSS_MULTI_UP="multi";YAHOO.widget.CalendarGroup.CSS_2UPTITLE="title";YAHOO.widget.CalendarGroup.CSS_2UPCLOSE="close-icon";YAHOO.lang.augmentProto(YAHOO.widget.CalendarGroup,YAHOO.widget.Calendar,"buildDayLabel","buildMonthLabel","renderOutOfBoundsDate","renderRowHeader","renderRowFooter","renderCellDefault","styleCellDefault","renderCellStyleHighlight1","renderCellStyleHighlight2","renderCellStyleHighlight3","renderCellStyleHighlight4","renderCellStyleToday","renderCellStyleSelected","renderCellNotThisMonth","renderBodyCellRestricted","initStyles","configTitle","configClose","configIframe","createTitleBar","createCloseButton","removeTitleBar","removeCloseButton","hide","browser");YAHOO.widget.CalendarGroup._DEFAULT_CONFIG=YAHOO.widget.Calendar._DEFAULT_CONFIG;YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.PAGES={key:"pages",value:2};YAHOO.widget.CalendarGroup.prototype.toString=function(){return"CalendarGroup "+this.id;};YAHOO.widget.CalGrp=YAHOO.widget.CalendarGroup;YAHOO.widget.Calendar2up=function(C,A,B){this.init(C,A,B);};YAHOO.extend(YAHOO.widget.Calendar2up,YAHOO.widget.CalendarGroup);YAHOO.widget.Cal2up=YAHOO.widget.Calendar2up;YAHOO.register("calendar",YAHOO.widget.Calendar,{version:"2.3.1",build:"541"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/calendar/calendar-min.js b/Websites/bugs.webkit.org/js/yui/calendar/calendar-min.js
new file mode 100644
index 0000000..223cdcf
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/calendar/calendar-min.js
@@ -0,0 +1,18 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){YAHOO.util.Config=function(d){if(d){this.init(d);}};var b=YAHOO.lang,c=YAHOO.util.CustomEvent,a=YAHOO.util.Config;a.CONFIG_CHANGED_EVENT="configChanged";a.BOOLEAN_TYPE="boolean";a.prototype={owner:null,queueInProgress:false,config:null,initialConfig:null,eventQueue:null,configChangedEvent:null,init:function(d){this.owner=d;this.configChangedEvent=this.createEvent(a.CONFIG_CHANGED_EVENT);this.configChangedEvent.signature=c.LIST;this.queueInProgress=false;this.config={};this.initialConfig={};this.eventQueue=[];},checkBoolean:function(d){return(typeof d==a.BOOLEAN_TYPE);},checkNumber:function(d){return(!isNaN(d));},fireEvent:function(d,f){var e=this.config[d];if(e&&e.event){e.event.fire(f);}},addProperty:function(e,d){e=e.toLowerCase();this.config[e]=d;d.event=this.createEvent(e,{scope:this.owner});d.event.signature=c.LIST;d.key=e;if(d.handler){d.event.subscribe(d.handler,this.owner);}this.setProperty(e,d.value,true);if(!d.suppressEvent){this.queueProperty(e,d.value);}},getConfig:function(){var d={},f=this.config,g,e;for(g in f){if(b.hasOwnProperty(f,g)){e=f[g];if(e&&e.event){d[g]=e.value;}}}return d;},getProperty:function(d){var e=this.config[d.toLowerCase()];if(e&&e.event){return e.value;}else{return undefined;}},resetProperty:function(d){d=d.toLowerCase();var e=this.config[d];if(e&&e.event){if(d in this.initialConfig){this.setProperty(d,this.initialConfig[d]);return true;}}else{return false;}},setProperty:function(e,g,d){var f;e=e.toLowerCase();if(this.queueInProgress&&!d){this.queueProperty(e,g);return true;}else{f=this.config[e];if(f&&f.event){if(f.validator&&!f.validator(g)){return false;}else{f.value=g;if(!d){this.fireEvent(e,g);this.configChangedEvent.fire([e,g]);}return true;}}else{return false;}}},queueProperty:function(v,r){v=v.toLowerCase();var u=this.config[v],l=false,k,g,h,j,p,t,f,n,o,d,m,w,e;if(u&&u.event){if(!b.isUndefined(r)&&u.validator&&!u.validator(r)){return false;}else{if(!b.isUndefined(r)){u.value=r;}else{r=u.value;}l=false;k=this.eventQueue.length;for(m=0;m<k;m++){g=this.eventQueue[m];if(g){h=g[0];j=g[1];if(h==v){this.eventQueue[m]=null;this.eventQueue.push([v,(!b.isUndefined(r)?r:j)]);l=true;break;}}}if(!l&&!b.isUndefined(r)){this.eventQueue.push([v,r]);}}if(u.supercedes){p=u.supercedes.length;for(w=0;w<p;w++){t=u.supercedes[w];f=this.eventQueue.length;for(e=0;e<f;e++){n=this.eventQueue[e];if(n){o=n[0];d=n[1];if(o==t.toLowerCase()){this.eventQueue.push([o,d]);this.eventQueue[e]=null;break;}}}}}return true;}else{return false;}},refireEvent:function(d){d=d.toLowerCase();var e=this.config[d];if(e&&e.event&&!b.isUndefined(e.value)){if(this.queueInProgress){this.queueProperty(d);}else{this.fireEvent(d,e.value);}}},applyConfig:function(d,g){var f,e;if(g){e={};for(f in d){if(b.hasOwnProperty(d,f)){e[f.toLowerCase()]=d[f];}}this.initialConfig=e;}for(f in d){if(b.hasOwnProperty(d,f)){this.queueProperty(f,d[f]);}}},refresh:function(){var d;for(d in this.config){if(b.hasOwnProperty(this.config,d)){this.refireEvent(d);}}},fireQueue:function(){var e,h,d,g,f;this.queueInProgress=true;for(e=0;e<this.eventQueue.length;e++){h=this.eventQueue[e];if(h){d=h[0];g=h[1];f=this.config[d];f.value=g;this.eventQueue[e]=null;this.fireEvent(d,g);}}this.queueInProgress=false;this.eventQueue=[];},subscribeToConfigEvent:function(d,e,g,h){var f=this.config[d.toLowerCase()];if(f&&f.event){if(!a.alreadySubscribed(f.event,e,g)){f.event.subscribe(e,g,h);}return true;}else{return false;}},unsubscribeFromConfigEvent:function(d,e,g){var f=this.config[d.toLowerCase()];if(f&&f.event){return f.event.unsubscribe(e,g);}else{return false;}},toString:function(){var d="Config";if(this.owner){d+=" ["+this.owner.toString()+"]";}return d;},outputEventQueue:function(){var d="",g,e,f=this.eventQueue.length;for(e=0;e<f;e++){g=this.eventQueue[e];if(g){d+=g[0]+"="+g[1]+", ";}}return d;},destroy:function(){var e=this.config,d,f;for(d in e){if(b.hasOwnProperty(e,d)){f=e[d];f.event.unsubscribeAll();f.event=null;}}this.configChangedEvent.unsubscribeAll();this.configChangedEvent=null;this.owner=null;this.config=null;this.initialConfig=null;this.eventQueue=null;}};a.alreadySubscribed=function(e,h,j){var f=e.subscribers.length,d,g;if(f>0){g=f-1;do{d=e.subscribers[g];if(d&&d.obj==j&&d.fn==h){return true;}}while(g--);}return false;};YAHOO.lang.augmentProto(a,YAHOO.util.EventProvider);}());YAHOO.widget.DateMath={DAY:"D",WEEK:"W",YEAR:"Y",MONTH:"M",ONE_DAY_MS:1000*60*60*24,WEEK_ONE_JAN_DATE:1,add:function(a,e,c){var g=new Date(a.getTime());switch(e){case this.MONTH:var f=a.getMonth()+c;var b=0;if(f<0){while(f<0){f+=12;b-=1;}}else{if(f>11){while(f>11){f-=12;b+=1;}}}g.setMonth(f);g.setFullYear(a.getFullYear()+b);break;case this.DAY:this._addDays(g,c);break;case this.YEAR:g.setFullYear(a.getFullYear()+c);break;case this.WEEK:this._addDays(g,(c*7));break;}return g;},_addDays:function(e,c){if(YAHOO.env.ua.webkit&&YAHOO.env.ua.webkit<420){if(c<0){for(var b=-128;c<b;c-=b){e.setDate(e.getDate()+b);}}else{for(var a=96;c>a;c-=a){e.setDate(e.getDate()+a);}}}e.setDate(e.getDate()+c);},subtract:function(a,c,b){return this.add(a,c,(b*-1));},before:function(c,b){var a=b.getTime();if(c.getTime()<a){return true;}else{return false;}},after:function(c,b){var a=b.getTime();if(c.getTime()>a){return true;}else{return false;}},between:function(b,a,c){if(this.after(b,a)&&this.before(b,c)){return true;}else{return false;}},getJan1:function(a){return this.getDate(a,0,1);},getDayOffset:function(b,d){var c=this.getJan1(d);var a=Math.ceil((b.getTime()-c.getTime())/this.ONE_DAY_MS);return a;},getWeekNumber:function(d,b,g){b=b||0;g=g||this.WEEK_ONE_JAN_DATE;var h=this.clearTime(d),l,m;if(h.getDay()===b){l=h;}else{l=this.getFirstDayOfWeek(h,b);}var i=l.getFullYear();m=new Date(l.getTime()+6*this.ONE_DAY_MS);var f;if(i!==m.getFullYear()&&m.getDate()>=g){f=1;}else{var e=this.clearTime(this.getDate(i,0,g)),a=this.getFirstDayOfWeek(e,b);var j=Math.round((h.getTime()-a.getTime())/this.ONE_DAY_MS);var k=j%7;var c=(j-k)/7;f=c+1;}return f;},getFirstDayOfWeek:function(d,a){a=a||0;
+var b=d.getDay(),c=(b-a+7)%7;return this.subtract(d,this.DAY,c);},isYearOverlapWeek:function(a){var c=false;var b=this.add(a,this.DAY,6);if(b.getFullYear()!=a.getFullYear()){c=true;}return c;},isMonthOverlapWeek:function(a){var c=false;var b=this.add(a,this.DAY,6);if(b.getMonth()!=a.getMonth()){c=true;}return c;},findMonthStart:function(a){var b=this.getDate(a.getFullYear(),a.getMonth(),1);return b;},findMonthEnd:function(b){var d=this.findMonthStart(b);var c=this.add(d,this.MONTH,1);var a=this.subtract(c,this.DAY,1);return a;},clearTime:function(a){a.setHours(12,0,0,0);return a;},getDate:function(e,a,c){var b=null;if(YAHOO.lang.isUndefined(c)){c=1;}if(e>=100){b=new Date(e,a,c);}else{b=new Date();b.setFullYear(e);b.setMonth(a);b.setDate(c);b.setHours(0,0,0,0);}return b;}};(function(){var c=YAHOO.util.Dom,a=YAHOO.util.Event,e=YAHOO.lang,d=YAHOO.widget.DateMath;function f(i,g,h){this.init.apply(this,arguments);}f.IMG_ROOT=null;f.DATE="D";f.MONTH_DAY="MD";f.WEEKDAY="WD";f.RANGE="R";f.MONTH="M";f.DISPLAY_DAYS=42;f.STOP_RENDER="S";f.SHORT="short";f.LONG="long";f.MEDIUM="medium";f.ONE_CHAR="1char";f.DEFAULT_CONFIG={YEAR_OFFSET:{key:"year_offset",value:0,supercedes:["pagedate","selected","mindate","maxdate"]},TODAY:{key:"today",value:new Date(),supercedes:["pagedate"]},PAGEDATE:{key:"pagedate",value:null},SELECTED:{key:"selected",value:[]},TITLE:{key:"title",value:""},CLOSE:{key:"close",value:false},IFRAME:{key:"iframe",value:(YAHOO.env.ua.ie&&YAHOO.env.ua.ie<=6)?true:false},MINDATE:{key:"mindate",value:null},MAXDATE:{key:"maxdate",value:null},MULTI_SELECT:{key:"multi_select",value:false},OOM_SELECT:{key:"oom_select",value:false},START_WEEKDAY:{key:"start_weekday",value:0},SHOW_WEEKDAYS:{key:"show_weekdays",value:true},SHOW_WEEK_HEADER:{key:"show_week_header",value:false},SHOW_WEEK_FOOTER:{key:"show_week_footer",value:false},HIDE_BLANK_WEEKS:{key:"hide_blank_weeks",value:false},NAV_ARROW_LEFT:{key:"nav_arrow_left",value:null},NAV_ARROW_RIGHT:{key:"nav_arrow_right",value:null},MONTHS_SHORT:{key:"months_short",value:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]},MONTHS_LONG:{key:"months_long",value:["January","February","March","April","May","June","July","August","September","October","November","December"]},WEEKDAYS_1CHAR:{key:"weekdays_1char",value:["S","M","T","W","T","F","S"]},WEEKDAYS_SHORT:{key:"weekdays_short",value:["Su","Mo","Tu","We","Th","Fr","Sa"]},WEEKDAYS_MEDIUM:{key:"weekdays_medium",value:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},WEEKDAYS_LONG:{key:"weekdays_long",value:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},LOCALE_MONTHS:{key:"locale_months",value:"long"},LOCALE_WEEKDAYS:{key:"locale_weekdays",value:"short"},DATE_DELIMITER:{key:"date_delimiter",value:","},DATE_FIELD_DELIMITER:{key:"date_field_delimiter",value:"/"},DATE_RANGE_DELIMITER:{key:"date_range_delimiter",value:"-"},MY_MONTH_POSITION:{key:"my_month_position",value:1},MY_YEAR_POSITION:{key:"my_year_position",value:2},MD_MONTH_POSITION:{key:"md_month_position",value:1},MD_DAY_POSITION:{key:"md_day_position",value:2},MDY_MONTH_POSITION:{key:"mdy_month_position",value:1},MDY_DAY_POSITION:{key:"mdy_day_position",value:2},MDY_YEAR_POSITION:{key:"mdy_year_position",value:3},MY_LABEL_MONTH_POSITION:{key:"my_label_month_position",value:1},MY_LABEL_YEAR_POSITION:{key:"my_label_year_position",value:2},MY_LABEL_MONTH_SUFFIX:{key:"my_label_month_suffix",value:" "},MY_LABEL_YEAR_SUFFIX:{key:"my_label_year_suffix",value:""},NAV:{key:"navigator",value:null},STRINGS:{key:"strings",value:{previousMonth:"Previous Month",nextMonth:"Next Month",close:"Close"},supercedes:["close","title"]}};f._DEFAULT_CONFIG=f.DEFAULT_CONFIG;var b=f.DEFAULT_CONFIG;f._EVENT_TYPES={BEFORE_SELECT:"beforeSelect",SELECT:"select",BEFORE_DESELECT:"beforeDeselect",DESELECT:"deselect",CHANGE_PAGE:"changePage",BEFORE_RENDER:"beforeRender",RENDER:"render",BEFORE_DESTROY:"beforeDestroy",DESTROY:"destroy",RESET:"reset",CLEAR:"clear",BEFORE_HIDE:"beforeHide",HIDE:"hide",BEFORE_SHOW:"beforeShow",SHOW:"show",BEFORE_HIDE_NAV:"beforeHideNav",HIDE_NAV:"hideNav",BEFORE_SHOW_NAV:"beforeShowNav",SHOW_NAV:"showNav",BEFORE_RENDER_NAV:"beforeRenderNav",RENDER_NAV:"renderNav"};f.STYLES={CSS_ROW_HEADER:"calrowhead",CSS_ROW_FOOTER:"calrowfoot",CSS_CELL:"calcell",CSS_CELL_SELECTOR:"selector",CSS_CELL_SELECTED:"selected",CSS_CELL_SELECTABLE:"selectable",CSS_CELL_RESTRICTED:"restricted",CSS_CELL_TODAY:"today",CSS_CELL_OOM:"oom",CSS_CELL_OOB:"previous",CSS_HEADER:"calheader",CSS_HEADER_TEXT:"calhead",CSS_BODY:"calbody",CSS_WEEKDAY_CELL:"calweekdaycell",CSS_WEEKDAY_ROW:"calweekdayrow",CSS_FOOTER:"calfoot",CSS_CALENDAR:"yui-calendar",CSS_SINGLE:"single",CSS_CONTAINER:"yui-calcontainer",CSS_NAV_LEFT:"calnavleft",CSS_NAV_RIGHT:"calnavright",CSS_NAV:"calnav",CSS_CLOSE:"calclose",CSS_CELL_TOP:"calcelltop",CSS_CELL_LEFT:"calcellleft",CSS_CELL_RIGHT:"calcellright",CSS_CELL_BOTTOM:"calcellbottom",CSS_CELL_HOVER:"calcellhover",CSS_CELL_HIGHLIGHT1:"highlight1",CSS_CELL_HIGHLIGHT2:"highlight2",CSS_CELL_HIGHLIGHT3:"highlight3",CSS_CELL_HIGHLIGHT4:"highlight4",CSS_WITH_TITLE:"withtitle",CSS_FIXED_SIZE:"fixedsize",CSS_LINK_CLOSE:"link-close"};f._STYLES=f.STYLES;f.prototype={Config:null,parent:null,index:-1,cells:null,cellDates:null,id:null,containerId:null,oDomContainer:null,today:null,renderStack:null,_renderStack:null,oNavigator:null,_selectedDates:null,domEventMap:null,_parseArgs:function(h){var g={id:null,container:null,config:null};if(h&&h.length&&h.length>0){switch(h.length){case 1:g.id=null;g.container=h[0];g.config=null;break;case 2:if(e.isObject(h[1])&&!h[1].tagName&&!(h[1] instanceof String)){g.id=null;g.container=h[0];g.config=h[1];}else{g.id=h[0];g.container=h[1];g.config=null;}break;default:g.id=h[0];g.container=h[1];g.config=h[2];break;}}else{}return g;},init:function(j,h,i){var g=this._parseArgs(arguments);j=g.id;h=g.container;i=g.config;this.oDomContainer=c.get(h);this._oDoc=this.oDomContainer.ownerDocument;if(!this.oDomContainer.id){this.oDomContainer.id=c.generateId();
+}if(!j){j=this.oDomContainer.id+"_t";}this.id=j;this.containerId=this.oDomContainer.id;this.initEvents();this.cfg=new YAHOO.util.Config(this);this.Options={};this.Locale={};this.initStyles();c.addClass(this.oDomContainer,this.Style.CSS_CONTAINER);c.addClass(this.oDomContainer,this.Style.CSS_SINGLE);this.cellDates=[];this.cells=[];this.renderStack=[];this._renderStack=[];this.setupConfig();if(i){this.cfg.applyConfig(i,true);}this.cfg.fireQueue();this.today=this.cfg.getProperty("today");},configIframe:function(i,h,j){var g=h[0];if(!this.parent){if(c.inDocument(this.oDomContainer)){if(g){var k=c.getStyle(this.oDomContainer,"position");if(k=="absolute"||k=="relative"){if(!c.inDocument(this.iframe)){this.iframe=document.createElement("iframe");this.iframe.src="javascript:false;";c.setStyle(this.iframe,"opacity","0");if(YAHOO.env.ua.ie&&YAHOO.env.ua.ie<=6){c.addClass(this.iframe,this.Style.CSS_FIXED_SIZE);}this.oDomContainer.insertBefore(this.iframe,this.oDomContainer.firstChild);}}}else{if(this.iframe){if(this.iframe.parentNode){this.iframe.parentNode.removeChild(this.iframe);}this.iframe=null;}}}}},configTitle:function(h,g,i){var k=g[0];if(k){this.createTitleBar(k);}else{var j=this.cfg.getProperty(b.CLOSE.key);if(!j){this.removeTitleBar();}else{this.createTitleBar(" ");}}},configClose:function(h,g,i){var k=g[0],j=this.cfg.getProperty(b.TITLE.key);if(k){if(!j){this.createTitleBar(" ");}this.createCloseButton();}else{this.removeCloseButton();if(!j){this.removeTitleBar();}}},initEvents:function(){var g=f._EVENT_TYPES,i=YAHOO.util.CustomEvent,h=this;h.beforeSelectEvent=new i(g.BEFORE_SELECT);h.selectEvent=new i(g.SELECT);h.beforeDeselectEvent=new i(g.BEFORE_DESELECT);h.deselectEvent=new i(g.DESELECT);h.changePageEvent=new i(g.CHANGE_PAGE);h.beforeRenderEvent=new i(g.BEFORE_RENDER);h.renderEvent=new i(g.RENDER);h.beforeDestroyEvent=new i(g.BEFORE_DESTROY);h.destroyEvent=new i(g.DESTROY);h.resetEvent=new i(g.RESET);h.clearEvent=new i(g.CLEAR);h.beforeShowEvent=new i(g.BEFORE_SHOW);h.showEvent=new i(g.SHOW);h.beforeHideEvent=new i(g.BEFORE_HIDE);h.hideEvent=new i(g.HIDE);h.beforeShowNavEvent=new i(g.BEFORE_SHOW_NAV);h.showNavEvent=new i(g.SHOW_NAV);h.beforeHideNavEvent=new i(g.BEFORE_HIDE_NAV);h.hideNavEvent=new i(g.HIDE_NAV);h.beforeRenderNavEvent=new i(g.BEFORE_RENDER_NAV);h.renderNavEvent=new i(g.RENDER_NAV);h.beforeSelectEvent.subscribe(h.onBeforeSelect,this,true);h.selectEvent.subscribe(h.onSelect,this,true);h.beforeDeselectEvent.subscribe(h.onBeforeDeselect,this,true);h.deselectEvent.subscribe(h.onDeselect,this,true);h.changePageEvent.subscribe(h.onChangePage,this,true);h.renderEvent.subscribe(h.onRender,this,true);h.resetEvent.subscribe(h.onReset,this,true);h.clearEvent.subscribe(h.onClear,this,true);},doPreviousMonthNav:function(h,g){a.preventDefault(h);setTimeout(function(){g.previousMonth();var j=c.getElementsByClassName(g.Style.CSS_NAV_LEFT,"a",g.oDomContainer);if(j&&j[0]){try{j[0].focus();}catch(i){}}},0);},doNextMonthNav:function(h,g){a.preventDefault(h);setTimeout(function(){g.nextMonth();var j=c.getElementsByClassName(g.Style.CSS_NAV_RIGHT,"a",g.oDomContainer);if(j&&j[0]){try{j[0].focus();}catch(i){}}},0);},doSelectCell:function(m,g){var r,o,i,l;var n=a.getTarget(m),h=n.tagName.toLowerCase(),k=false;while(h!="td"&&!c.hasClass(n,g.Style.CSS_CELL_SELECTABLE)){if(!k&&h=="a"&&c.hasClass(n,g.Style.CSS_CELL_SELECTOR)){k=true;}n=n.parentNode;h=n.tagName.toLowerCase();if(n==this.oDomContainer||h=="html"){return;}}if(k){a.preventDefault(m);}r=n;if(c.hasClass(r,g.Style.CSS_CELL_SELECTABLE)){l=g.getIndexFromId(r.id);if(l>-1){o=g.cellDates[l];if(o){i=d.getDate(o[0],o[1]-1,o[2]);var q;if(g.Options.MULTI_SELECT){q=r.getElementsByTagName("a")[0];if(q){q.blur();}var j=g.cellDates[l];var p=g._indexOfSelectedFieldArray(j);if(p>-1){g.deselectCell(l);}else{g.selectCell(l);}}else{q=r.getElementsByTagName("a")[0];if(q){q.blur();}g.selectCell(l);}}}}},doCellMouseOver:function(i,h){var g;if(i){g=a.getTarget(i);}else{g=this;}while(g.tagName&&g.tagName.toLowerCase()!="td"){g=g.parentNode;if(!g.tagName||g.tagName.toLowerCase()=="html"){return;}}if(c.hasClass(g,h.Style.CSS_CELL_SELECTABLE)){c.addClass(g,h.Style.CSS_CELL_HOVER);}},doCellMouseOut:function(i,h){var g;if(i){g=a.getTarget(i);}else{g=this;}while(g.tagName&&g.tagName.toLowerCase()!="td"){g=g.parentNode;if(!g.tagName||g.tagName.toLowerCase()=="html"){return;}}if(c.hasClass(g,h.Style.CSS_CELL_SELECTABLE)){c.removeClass(g,h.Style.CSS_CELL_HOVER);}},setupConfig:function(){var g=this.cfg;g.addProperty(b.TODAY.key,{value:new Date(b.TODAY.value.getTime()),supercedes:b.TODAY.supercedes,handler:this.configToday,suppressEvent:true});g.addProperty(b.PAGEDATE.key,{value:b.PAGEDATE.value||new Date(b.TODAY.value.getTime()),handler:this.configPageDate});g.addProperty(b.SELECTED.key,{value:b.SELECTED.value.concat(),handler:this.configSelected});g.addProperty(b.TITLE.key,{value:b.TITLE.value,handler:this.configTitle});g.addProperty(b.CLOSE.key,{value:b.CLOSE.value,handler:this.configClose});g.addProperty(b.IFRAME.key,{value:b.IFRAME.value,handler:this.configIframe,validator:g.checkBoolean});g.addProperty(b.MINDATE.key,{value:b.MINDATE.value,handler:this.configMinDate});g.addProperty(b.MAXDATE.key,{value:b.MAXDATE.value,handler:this.configMaxDate});g.addProperty(b.MULTI_SELECT.key,{value:b.MULTI_SELECT.value,handler:this.configOptions,validator:g.checkBoolean});g.addProperty(b.OOM_SELECT.key,{value:b.OOM_SELECT.value,handler:this.configOptions,validator:g.checkBoolean});g.addProperty(b.START_WEEKDAY.key,{value:b.START_WEEKDAY.value,handler:this.configOptions,validator:g.checkNumber});g.addProperty(b.SHOW_WEEKDAYS.key,{value:b.SHOW_WEEKDAYS.value,handler:this.configOptions,validator:g.checkBoolean});g.addProperty(b.SHOW_WEEK_HEADER.key,{value:b.SHOW_WEEK_HEADER.value,handler:this.configOptions,validator:g.checkBoolean});g.addProperty(b.SHOW_WEEK_FOOTER.key,{value:b.SHOW_WEEK_FOOTER.value,handler:this.configOptions,validator:g.checkBoolean});g.addProperty(b.HIDE_BLANK_WEEKS.key,{value:b.HIDE_BLANK_WEEKS.value,handler:this.configOptions,validator:g.checkBoolean});
+g.addProperty(b.NAV_ARROW_LEFT.key,{value:b.NAV_ARROW_LEFT.value,handler:this.configOptions});g.addProperty(b.NAV_ARROW_RIGHT.key,{value:b.NAV_ARROW_RIGHT.value,handler:this.configOptions});g.addProperty(b.MONTHS_SHORT.key,{value:b.MONTHS_SHORT.value,handler:this.configLocale});g.addProperty(b.MONTHS_LONG.key,{value:b.MONTHS_LONG.value,handler:this.configLocale});g.addProperty(b.WEEKDAYS_1CHAR.key,{value:b.WEEKDAYS_1CHAR.value,handler:this.configLocale});g.addProperty(b.WEEKDAYS_SHORT.key,{value:b.WEEKDAYS_SHORT.value,handler:this.configLocale});g.addProperty(b.WEEKDAYS_MEDIUM.key,{value:b.WEEKDAYS_MEDIUM.value,handler:this.configLocale});g.addProperty(b.WEEKDAYS_LONG.key,{value:b.WEEKDAYS_LONG.value,handler:this.configLocale});var h=function(){g.refireEvent(b.LOCALE_MONTHS.key);g.refireEvent(b.LOCALE_WEEKDAYS.key);};g.subscribeToConfigEvent(b.START_WEEKDAY.key,h,this,true);g.subscribeToConfigEvent(b.MONTHS_SHORT.key,h,this,true);g.subscribeToConfigEvent(b.MONTHS_LONG.key,h,this,true);g.subscribeToConfigEvent(b.WEEKDAYS_1CHAR.key,h,this,true);g.subscribeToConfigEvent(b.WEEKDAYS_SHORT.key,h,this,true);g.subscribeToConfigEvent(b.WEEKDAYS_MEDIUM.key,h,this,true);g.subscribeToConfigEvent(b.WEEKDAYS_LONG.key,h,this,true);g.addProperty(b.LOCALE_MONTHS.key,{value:b.LOCALE_MONTHS.value,handler:this.configLocaleValues});g.addProperty(b.LOCALE_WEEKDAYS.key,{value:b.LOCALE_WEEKDAYS.value,handler:this.configLocaleValues});g.addProperty(b.YEAR_OFFSET.key,{value:b.YEAR_OFFSET.value,supercedes:b.YEAR_OFFSET.supercedes,handler:this.configLocale});g.addProperty(b.DATE_DELIMITER.key,{value:b.DATE_DELIMITER.value,handler:this.configLocale});g.addProperty(b.DATE_FIELD_DELIMITER.key,{value:b.DATE_FIELD_DELIMITER.value,handler:this.configLocale});g.addProperty(b.DATE_RANGE_DELIMITER.key,{value:b.DATE_RANGE_DELIMITER.value,handler:this.configLocale});g.addProperty(b.MY_MONTH_POSITION.key,{value:b.MY_MONTH_POSITION.value,handler:this.configLocale,validator:g.checkNumber});g.addProperty(b.MY_YEAR_POSITION.key,{value:b.MY_YEAR_POSITION.value,handler:this.configLocale,validator:g.checkNumber});g.addProperty(b.MD_MONTH_POSITION.key,{value:b.MD_MONTH_POSITION.value,handler:this.configLocale,validator:g.checkNumber});g.addProperty(b.MD_DAY_POSITION.key,{value:b.MD_DAY_POSITION.value,handler:this.configLocale,validator:g.checkNumber});g.addProperty(b.MDY_MONTH_POSITION.key,{value:b.MDY_MONTH_POSITION.value,handler:this.configLocale,validator:g.checkNumber});g.addProperty(b.MDY_DAY_POSITION.key,{value:b.MDY_DAY_POSITION.value,handler:this.configLocale,validator:g.checkNumber});g.addProperty(b.MDY_YEAR_POSITION.key,{value:b.MDY_YEAR_POSITION.value,handler:this.configLocale,validator:g.checkNumber});g.addProperty(b.MY_LABEL_MONTH_POSITION.key,{value:b.MY_LABEL_MONTH_POSITION.value,handler:this.configLocale,validator:g.checkNumber});g.addProperty(b.MY_LABEL_YEAR_POSITION.key,{value:b.MY_LABEL_YEAR_POSITION.value,handler:this.configLocale,validator:g.checkNumber});g.addProperty(b.MY_LABEL_MONTH_SUFFIX.key,{value:b.MY_LABEL_MONTH_SUFFIX.value,handler:this.configLocale});g.addProperty(b.MY_LABEL_YEAR_SUFFIX.key,{value:b.MY_LABEL_YEAR_SUFFIX.value,handler:this.configLocale});g.addProperty(b.NAV.key,{value:b.NAV.value,handler:this.configNavigator});g.addProperty(b.STRINGS.key,{value:b.STRINGS.value,handler:this.configStrings,validator:function(i){return e.isObject(i);},supercedes:b.STRINGS.supercedes});},configStrings:function(h,g,i){var j=e.merge(b.STRINGS.value,g[0]);this.cfg.setProperty(b.STRINGS.key,j,true);},configPageDate:function(h,g,i){this.cfg.setProperty(b.PAGEDATE.key,this._parsePageDate(g[0]),true);},configMinDate:function(h,g,i){var j=g[0];if(e.isString(j)){j=this._parseDate(j);this.cfg.setProperty(b.MINDATE.key,d.getDate(j[0],(j[1]-1),j[2]));}},configMaxDate:function(h,g,i){var j=g[0];if(e.isString(j)){j=this._parseDate(j);this.cfg.setProperty(b.MAXDATE.key,d.getDate(j[0],(j[1]-1),j[2]));}},configToday:function(i,h,j){var k=h[0];if(e.isString(k)){k=this._parseDate(k);}var g=d.clearTime(k);if(!this.cfg.initialConfig[b.PAGEDATE.key]){this.cfg.setProperty(b.PAGEDATE.key,g);}this.today=g;this.cfg.setProperty(b.TODAY.key,g,true);},configSelected:function(i,g,k){var h=g[0],j=b.SELECTED.key;if(h){if(e.isString(h)){this.cfg.setProperty(j,this._parseDates(h),true);}}if(!this._selectedDates){this._selectedDates=this.cfg.getProperty(j);}},configOptions:function(h,g,i){this.Options[h.toUpperCase()]=g[0];},configLocale:function(h,g,i){this.Locale[h.toUpperCase()]=g[0];this.cfg.refireEvent(b.LOCALE_MONTHS.key);this.cfg.refireEvent(b.LOCALE_WEEKDAYS.key);},configLocaleValues:function(j,i,k){j=j.toLowerCase();var m=i[0],h=this.cfg,n=this.Locale;switch(j){case b.LOCALE_MONTHS.key:switch(m){case f.SHORT:n.LOCALE_MONTHS=h.getProperty(b.MONTHS_SHORT.key).concat();break;case f.LONG:n.LOCALE_MONTHS=h.getProperty(b.MONTHS_LONG.key).concat();break;}break;case b.LOCALE_WEEKDAYS.key:switch(m){case f.ONE_CHAR:n.LOCALE_WEEKDAYS=h.getProperty(b.WEEKDAYS_1CHAR.key).concat();break;case f.SHORT:n.LOCALE_WEEKDAYS=h.getProperty(b.WEEKDAYS_SHORT.key).concat();break;case f.MEDIUM:n.LOCALE_WEEKDAYS=h.getProperty(b.WEEKDAYS_MEDIUM.key).concat();break;case f.LONG:n.LOCALE_WEEKDAYS=h.getProperty(b.WEEKDAYS_LONG.key).concat();break;}var l=h.getProperty(b.START_WEEKDAY.key);if(l>0){for(var g=0;g<l;++g){n.LOCALE_WEEKDAYS.push(n.LOCALE_WEEKDAYS.shift());}}break;}},configNavigator:function(h,g,i){var j=g[0];if(YAHOO.widget.CalendarNavigator&&(j===true||e.isObject(j))){if(!this.oNavigator){this.oNavigator=new YAHOO.widget.CalendarNavigator(this);this.beforeRenderEvent.subscribe(function(){if(!this.pages){this.oNavigator.erase();}},this,true);}}else{if(this.oNavigator){this.oNavigator.destroy();this.oNavigator=null;}}},initStyles:function(){var g=f.STYLES;this.Style={CSS_ROW_HEADER:g.CSS_ROW_HEADER,CSS_ROW_FOOTER:g.CSS_ROW_FOOTER,CSS_CELL:g.CSS_CELL,CSS_CELL_SELECTOR:g.CSS_CELL_SELECTOR,CSS_CELL_SELECTED:g.CSS_CELL_SELECTED,CSS_CELL_SELECTABLE:g.CSS_CELL_SELECTABLE,CSS_CELL_RESTRICTED:g.CSS_CELL_RESTRICTED,CSS_CELL_TODAY:g.CSS_CELL_TODAY,CSS_CELL_OOM:g.CSS_CELL_OOM,CSS_CELL_OOB:g.CSS_CELL_OOB,CSS_HEADER:g.CSS_HEADER,CSS_HEADER_TEXT:g.CSS_HEADER_TEXT,CSS_BODY:g.CSS_BODY,CSS_WEEKDAY_CELL:g.CSS_WEEKDAY_CELL,CSS_WEEKDAY_ROW:g.CSS_WEEKDAY_ROW,CSS_FOOTER:g.CSS_FOOTER,CSS_CALENDAR:g.CSS_CALENDAR,CSS_SINGLE:g.CSS_SINGLE,CSS_CONTAINER:g.CSS_CONTAINER,CSS_NAV_LEFT:g.CSS_NAV_LEFT,CSS_NAV_RIGHT:g.CSS_NAV_RIGHT,CSS_NAV:g.CSS_NAV,CSS_CLOSE:g.CSS_CLOSE,CSS_CELL_TOP:g.CSS_CELL_TOP,CSS_CELL_LEFT:g.CSS_CELL_LEFT,CSS_CELL_RIGHT:g.CSS_CELL_RIGHT,CSS_CELL_BOTTOM:g.CSS_CELL_BOTTOM,CSS_CELL_HOVER:g.CSS_CELL_HOVER,CSS_CELL_HIGHLIGHT1:g.CSS_CELL_HIGHLIGHT1,CSS_CELL_HIGHLIGHT2:g.CSS_CELL_HIGHLIGHT2,CSS_CELL_HIGHLIGHT3:g.CSS_CELL_HIGHLIGHT3,CSS_CELL_HIGHLIGHT4:g.CSS_CELL_HIGHLIGHT4,CSS_WITH_TITLE:g.CSS_WITH_TITLE,CSS_FIXED_SIZE:g.CSS_FIXED_SIZE,CSS_LINK_CLOSE:g.CSS_LINK_CLOSE};
+},buildMonthLabel:function(){return this._buildMonthLabel(this.cfg.getProperty(b.PAGEDATE.key));},_buildMonthLabel:function(g){var i=this.Locale.LOCALE_MONTHS[g.getMonth()]+this.Locale.MY_LABEL_MONTH_SUFFIX,h=(g.getFullYear()+this.Locale.YEAR_OFFSET)+this.Locale.MY_LABEL_YEAR_SUFFIX;if(this.Locale.MY_LABEL_MONTH_POSITION==2||this.Locale.MY_LABEL_YEAR_POSITION==1){return h+i;}else{return i+h;}},buildDayLabel:function(g){return g.getDate();},createTitleBar:function(g){var h=c.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE,"div",this.oDomContainer)[0]||document.createElement("div");h.className=YAHOO.widget.CalendarGroup.CSS_2UPTITLE;h.innerHTML=g;this.oDomContainer.insertBefore(h,this.oDomContainer.firstChild);c.addClass(this.oDomContainer,this.Style.CSS_WITH_TITLE);return h;},removeTitleBar:function(){var g=c.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE,"div",this.oDomContainer)[0]||null;if(g){a.purgeElement(g);this.oDomContainer.removeChild(g);}c.removeClass(this.oDomContainer,this.Style.CSS_WITH_TITLE);},createCloseButton:function(){var k=YAHOO.widget.CalendarGroup.CSS_2UPCLOSE,j=this.Style.CSS_LINK_CLOSE,m="us/my/bn/x_d.gif",l=c.getElementsByClassName(j,"a",this.oDomContainer)[0],g=this.cfg.getProperty(b.STRINGS.key),h=(g&&g.close)?g.close:"";if(!l){l=document.createElement("a");a.addListener(l,"click",function(o,n){n.hide();a.preventDefault(o);},this);}l.href="#";l.className=j;if(f.IMG_ROOT!==null){var i=c.getElementsByClassName(k,"img",l)[0]||document.createElement("img");i.src=f.IMG_ROOT+m;i.className=k;l.appendChild(i);}else{l.innerHTML='<span class="'+k+" "+this.Style.CSS_CLOSE+'">'+h+"</span>";}this.oDomContainer.appendChild(l);return l;},removeCloseButton:function(){var g=c.getElementsByClassName(this.Style.CSS_LINK_CLOSE,"a",this.oDomContainer)[0]||null;if(g){a.purgeElement(g);this.oDomContainer.removeChild(g);}},renderHeader:function(q){var p=7,o="us/tr/callt.gif",g="us/tr/calrt.gif",n=this.cfg,k=n.getProperty(b.PAGEDATE.key),l=n.getProperty(b.STRINGS.key),v=(l&&l.previousMonth)?l.previousMonth:"",h=(l&&l.nextMonth)?l.nextMonth:"",m;if(n.getProperty(b.SHOW_WEEK_HEADER.key)){p+=1;}if(n.getProperty(b.SHOW_WEEK_FOOTER.key)){p+=1;}q[q.length]="<thead>";q[q.length]="<tr>";q[q.length]='<th colspan="'+p+'" class="'+this.Style.CSS_HEADER_TEXT+'">';q[q.length]='<div class="'+this.Style.CSS_HEADER+'">';var x,u=false;if(this.parent){if(this.index===0){x=true;}if(this.index==(this.parent.cfg.getProperty("pages")-1)){u=true;}}else{x=true;u=true;}if(x){m=this._buildMonthLabel(d.subtract(k,d.MONTH,1));var r=n.getProperty(b.NAV_ARROW_LEFT.key);if(r===null&&f.IMG_ROOT!==null){r=f.IMG_ROOT+o;}var i=(r===null)?"":' style="background-image:url('+r+')"';q[q.length]='<a class="'+this.Style.CSS_NAV_LEFT+'"'+i+' href="#">'+v+" ("+m+")"+"</a>";}var w=this.buildMonthLabel();var s=this.parent||this;if(s.cfg.getProperty("navigator")){w='<a class="'+this.Style.CSS_NAV+'" href="#">'+w+"</a>";}q[q.length]=w;if(u){m=this._buildMonthLabel(d.add(k,d.MONTH,1));var t=n.getProperty(b.NAV_ARROW_RIGHT.key);if(t===null&&f.IMG_ROOT!==null){t=f.IMG_ROOT+g;}var j=(t===null)?"":' style="background-image:url('+t+')"';q[q.length]='<a class="'+this.Style.CSS_NAV_RIGHT+'"'+j+' href="#">'+h+" ("+m+")"+"</a>";}q[q.length]="</div>\n</th>\n</tr>";if(n.getProperty(b.SHOW_WEEKDAYS.key)){q=this.buildWeekdays(q);}q[q.length]="</thead>";return q;},buildWeekdays:function(h){h[h.length]='<tr class="'+this.Style.CSS_WEEKDAY_ROW+'">';if(this.cfg.getProperty(b.SHOW_WEEK_HEADER.key)){h[h.length]="<th> </th>";}for(var g=0;g<this.Locale.LOCALE_WEEKDAYS.length;++g){h[h.length]='<th class="'+this.Style.CSS_WEEKDAY_CELL+'">'+this.Locale.LOCALE_WEEKDAYS[g]+"</th>";}if(this.cfg.getProperty(b.SHOW_WEEK_FOOTER.key)){h[h.length]="<th> </th>";}h[h.length]="</tr>";return h;},renderBody:function(T,Q){var ao=this.cfg.getProperty(b.START_WEEKDAY.key);this.preMonthDays=T.getDay();if(ao>0){this.preMonthDays-=ao;}if(this.preMonthDays<0){this.preMonthDays+=7;}this.monthDays=d.findMonthEnd(T).getDate();this.postMonthDays=f.DISPLAY_DAYS-this.preMonthDays-this.monthDays;T=d.subtract(T,d.DAY,this.preMonthDays);var F,q,o="w",L="_cell",J="wd",Z="d",v,X,af=this.today,u=this.cfg,ae,D=af.getFullYear(),Y=af.getMonth(),k=af.getDate(),ad=u.getProperty(b.PAGEDATE.key),j=u.getProperty(b.HIDE_BLANK_WEEKS.key),P=u.getProperty(b.SHOW_WEEK_FOOTER.key),I=u.getProperty(b.SHOW_WEEK_HEADER.key),O=u.getProperty(b.OOM_SELECT.key),B=u.getProperty(b.MINDATE.key),H=u.getProperty(b.MAXDATE.key),A=this.Locale.YEAR_OFFSET;if(B){B=d.clearTime(B);}if(H){H=d.clearTime(H);}Q[Q.length]='<tbody class="m'+(ad.getMonth()+1)+" "+this.Style.CSS_BODY+'">';var am=0,w=document.createElement("div"),R=document.createElement("td");w.appendChild(R);var ac=this.parent||this;for(var ah=0;ah<6;ah++){F=d.getWeekNumber(T,ao);q=o+F;if(ah!==0&&j===true&&T.getMonth()!=ad.getMonth()){break;}else{Q[Q.length]='<tr class="'+q+'">';if(I){Q=this.renderRowHeader(F,Q);}for(var an=0;an<7;an++){v=[];this.clearElement(R);R.className=this.Style.CSS_CELL;R.id=this.id+L+am;if(T.getDate()==k&&T.getMonth()==Y&&T.getFullYear()==D){v[v.length]=ac.renderCellStyleToday;}var G=[T.getFullYear(),T.getMonth()+1,T.getDate()];this.cellDates[this.cellDates.length]=G;ae=T.getMonth()!=ad.getMonth();if(ae&&!O){v[v.length]=ac.renderCellNotThisMonth;}else{c.addClass(R,J+T.getDay());c.addClass(R,Z+T.getDate());var S=this.renderStack.concat();for(var ag=0,al=S.length;ag<al;++ag){X=null;var aa=S[ag],ap=aa[0],h,K,n;switch(ap){case f.DATE:h=aa[1][1];K=aa[1][2];n=aa[1][0];if(T.getMonth()+1==h&&T.getDate()==K&&T.getFullYear()==n){X=aa[2];this.renderStack.splice(ag,1);}break;case f.MONTH_DAY:h=aa[1][0];K=aa[1][1];if(T.getMonth()+1==h&&T.getDate()==K){X=aa[2];this.renderStack.splice(ag,1);}break;case f.RANGE:var N=aa[1][0],M=aa[1][1],U=N[1],z=N[2],E=N[0],ak=d.getDate(E,U-1,z),m=M[1],W=M[2],g=M[0],aj=d.getDate(g,m-1,W);if(T.getTime()>=ak.getTime()&&T.getTime()<=aj.getTime()){X=aa[2];if(T.getTime()==aj.getTime()){this.renderStack.splice(ag,1);
+}}break;case f.WEEKDAY:var y=aa[1][0];if(T.getDay()+1==y){X=aa[2];}break;case f.MONTH:h=aa[1][0];if(T.getMonth()+1==h){X=aa[2];}break;}if(X){v[v.length]=X;}}}if(this._indexOfSelectedFieldArray(G)>-1){v[v.length]=ac.renderCellStyleSelected;}if(ae){v[v.length]=ac.styleCellNotThisMonth;}if((B&&(T.getTime()<B.getTime()))||(H&&(T.getTime()>H.getTime()))){v[v.length]=ac.renderOutOfBoundsDate;}else{v[v.length]=ac.styleCellDefault;v[v.length]=ac.renderCellDefault;}for(var ab=0;ab<v.length;++ab){if(v[ab].call(ac,T,R)==f.STOP_RENDER){break;}}T.setTime(T.getTime()+d.ONE_DAY_MS);T=d.clearTime(T);if(am>=0&&am<=6){c.addClass(R,this.Style.CSS_CELL_TOP);}if((am%7)===0){c.addClass(R,this.Style.CSS_CELL_LEFT);}if(((am+1)%7)===0){c.addClass(R,this.Style.CSS_CELL_RIGHT);}var V=this.postMonthDays;if(j&&V>=7){var C=Math.floor(V/7);for(var ai=0;ai<C;++ai){V-=7;}}if(am>=((this.preMonthDays+V+this.monthDays)-7)){c.addClass(R,this.Style.CSS_CELL_BOTTOM);}Q[Q.length]=w.innerHTML;am++;}if(P){Q=this.renderRowFooter(F,Q);}Q[Q.length]="</tr>";}}Q[Q.length]="</tbody>";return Q;},renderFooter:function(g){return g;},render:function(){this.beforeRenderEvent.fire();var i=d.findMonthStart(this.cfg.getProperty(b.PAGEDATE.key));this.resetRenderers();this.cellDates.length=0;a.purgeElement(this.oDomContainer,true);var g=[],h;g[g.length]='<table cellSpacing="0" class="'+this.Style.CSS_CALENDAR+" y"+(i.getFullYear()+this.Locale.YEAR_OFFSET)+'" id="'+this.id+'">';g=this.renderHeader(g);g=this.renderBody(i,g);g=this.renderFooter(g);g[g.length]="</table>";this.oDomContainer.innerHTML=g.join("\n");this.applyListeners();h=((this._oDoc)&&this._oDoc.getElementById(this.id))||(this.id);this.cells=c.getElementsByClassName(this.Style.CSS_CELL,"td",h);this.cfg.refireEvent(b.TITLE.key);this.cfg.refireEvent(b.CLOSE.key);this.cfg.refireEvent(b.IFRAME.key);this.renderEvent.fire();},applyListeners:function(){var q=this.oDomContainer,h=this.parent||this,m="a",t="click";var n=c.getElementsByClassName(this.Style.CSS_NAV_LEFT,m,q),j=c.getElementsByClassName(this.Style.CSS_NAV_RIGHT,m,q);if(n&&n.length>0){this.linkLeft=n[0];a.addListener(this.linkLeft,t,this.doPreviousMonthNav,h,true);}if(j&&j.length>0){this.linkRight=j[0];a.addListener(this.linkRight,t,this.doNextMonthNav,h,true);}if(h.cfg.getProperty("navigator")!==null){this.applyNavListeners();}if(this.domEventMap){var k,g;for(var s in this.domEventMap){if(e.hasOwnProperty(this.domEventMap,s)){var o=this.domEventMap[s];if(!(o instanceof Array)){o=[o];}for(var l=0;l<o.length;l++){var r=o[l];g=c.getElementsByClassName(s,r.tag,this.oDomContainer);for(var p=0;p<g.length;p++){k=g[p];a.addListener(k,r.event,r.handler,r.scope,r.correct);}}}}}a.addListener(this.oDomContainer,"click",this.doSelectCell,this);a.addListener(this.oDomContainer,"mouseover",this.doCellMouseOver,this);a.addListener(this.oDomContainer,"mouseout",this.doCellMouseOut,this);},applyNavListeners:function(){var h=this.parent||this,i=this,g=c.getElementsByClassName(this.Style.CSS_NAV,"a",this.oDomContainer);if(g.length>0){a.addListener(g,"click",function(n,m){var l=a.getTarget(n);if(this===l||c.isAncestor(this,l)){a.preventDefault(n);}var j=h.oNavigator;if(j){var k=i.cfg.getProperty("pagedate");j.setYear(k.getFullYear()+i.Locale.YEAR_OFFSET);j.setMonth(k.getMonth());j.show();}});}},getDateByCellId:function(h){var g=this.getDateFieldsByCellId(h);return(g)?d.getDate(g[0],g[1]-1,g[2]):null;},getDateFieldsByCellId:function(g){g=this.getIndexFromId(g);return(g>-1)?this.cellDates[g]:null;},getCellIndex:function(j){var h=-1;if(j){var g=j.getMonth(),p=j.getFullYear(),o=j.getDate(),l=this.cellDates;for(var k=0;k<l.length;++k){var n=l[k];if(n[0]===p&&n[1]===g+1&&n[2]===o){h=k;break;}}}return h;},getIndexFromId:function(i){var h=-1,g=i.lastIndexOf("_cell");if(g>-1){h=parseInt(i.substring(g+5),10);}return h;},renderOutOfBoundsDate:function(h,g){c.addClass(g,this.Style.CSS_CELL_OOB);g.innerHTML=h.getDate();return f.STOP_RENDER;},renderRowHeader:function(h,g){g[g.length]='<th class="'+this.Style.CSS_ROW_HEADER+'">'+h+"</th>";return g;},renderRowFooter:function(h,g){g[g.length]='<th class="'+this.Style.CSS_ROW_FOOTER+'">'+h+"</th>";return g;},renderCellDefault:function(h,g){g.innerHTML='<a href="#" class="'+this.Style.CSS_CELL_SELECTOR+'">'+this.buildDayLabel(h)+"</a>";},styleCellDefault:function(h,g){c.addClass(g,this.Style.CSS_CELL_SELECTABLE);},renderCellStyleHighlight1:function(h,g){c.addClass(g,this.Style.CSS_CELL_HIGHLIGHT1);},renderCellStyleHighlight2:function(h,g){c.addClass(g,this.Style.CSS_CELL_HIGHLIGHT2);},renderCellStyleHighlight3:function(h,g){c.addClass(g,this.Style.CSS_CELL_HIGHLIGHT3);},renderCellStyleHighlight4:function(h,g){c.addClass(g,this.Style.CSS_CELL_HIGHLIGHT4);},renderCellStyleToday:function(h,g){c.addClass(g,this.Style.CSS_CELL_TODAY);},renderCellStyleSelected:function(h,g){c.addClass(g,this.Style.CSS_CELL_SELECTED);},renderCellNotThisMonth:function(h,g){this.styleCellNotThisMonth(h,g);g.innerHTML=h.getDate();return f.STOP_RENDER;},styleCellNotThisMonth:function(h,g){YAHOO.util.Dom.addClass(g,this.Style.CSS_CELL_OOM);},renderBodyCellRestricted:function(h,g){c.addClass(g,this.Style.CSS_CELL);c.addClass(g,this.Style.CSS_CELL_RESTRICTED);g.innerHTML=h.getDate();return f.STOP_RENDER;},addMonths:function(i){var h=b.PAGEDATE.key,j=this.cfg.getProperty(h),g=d.add(j,d.MONTH,i);this.cfg.setProperty(h,g);this.resetRenderers();this.changePageEvent.fire(j,g);},subtractMonths:function(g){this.addMonths(-1*g);},addYears:function(i){var h=b.PAGEDATE.key,j=this.cfg.getProperty(h),g=d.add(j,d.YEAR,i);this.cfg.setProperty(h,g);this.resetRenderers();this.changePageEvent.fire(j,g);},subtractYears:function(g){this.addYears(-1*g);},nextMonth:function(){this.addMonths(1);},previousMonth:function(){this.addMonths(-1);},nextYear:function(){this.addYears(1);},previousYear:function(){this.addYears(-1);},reset:function(){this.cfg.resetProperty(b.SELECTED.key);this.cfg.resetProperty(b.PAGEDATE.key);this.resetEvent.fire();},clear:function(){this.cfg.setProperty(b.SELECTED.key,[]);
+this.cfg.setProperty(b.PAGEDATE.key,new Date(this.today.getTime()));this.clearEvent.fire();},select:function(i){var l=this._toFieldArray(i),h=[],k=[],m=b.SELECTED.key;for(var g=0;g<l.length;++g){var j=l[g];if(!this.isDateOOB(this._toDate(j))){if(h.length===0){this.beforeSelectEvent.fire();k=this.cfg.getProperty(m);}h.push(j);if(this._indexOfSelectedFieldArray(j)==-1){k[k.length]=j;}}}if(h.length>0){if(this.parent){this.parent.cfg.setProperty(m,k);}else{this.cfg.setProperty(m,k);}this.selectEvent.fire(h);}return this.getSelectedDates();},selectCell:function(j){var h=this.cells[j],n=this.cellDates[j],m=this._toDate(n),i=c.hasClass(h,this.Style.CSS_CELL_SELECTABLE);if(i){this.beforeSelectEvent.fire();var l=b.SELECTED.key;var k=this.cfg.getProperty(l);var g=n.concat();if(this._indexOfSelectedFieldArray(g)==-1){k[k.length]=g;}if(this.parent){this.parent.cfg.setProperty(l,k);}else{this.cfg.setProperty(l,k);}this.renderCellStyleSelected(m,h);this.selectEvent.fire([g]);this.doCellMouseOut.call(h,null,this);}return this.getSelectedDates();},deselect:function(k){var g=this._toFieldArray(k),j=[],m=[],n=b.SELECTED.key;for(var h=0;h<g.length;++h){var l=g[h];if(!this.isDateOOB(this._toDate(l))){if(j.length===0){this.beforeDeselectEvent.fire();m=this.cfg.getProperty(n);}j.push(l);var i=this._indexOfSelectedFieldArray(l);if(i!=-1){m.splice(i,1);}}}if(j.length>0){if(this.parent){this.parent.cfg.setProperty(n,m);}else{this.cfg.setProperty(n,m);}this.deselectEvent.fire(j);}return this.getSelectedDates();},deselectCell:function(k){var h=this.cells[k],n=this.cellDates[k],i=this._indexOfSelectedFieldArray(n);var j=c.hasClass(h,this.Style.CSS_CELL_SELECTABLE);if(j){this.beforeDeselectEvent.fire();var l=this.cfg.getProperty(b.SELECTED.key),m=this._toDate(n),g=n.concat();if(i>-1){if((this.cfg.getProperty(b.PAGEDATE.key).getMonth()==m.getMonth()&&this.cfg.getProperty(b.PAGEDATE.key).getFullYear()==m.getFullYear())||this.cfg.getProperty(b.OOM_SELECT.key)){c.removeClass(h,this.Style.CSS_CELL_SELECTED);}l.splice(i,1);}if(this.parent){this.parent.cfg.setProperty(b.SELECTED.key,l);}else{this.cfg.setProperty(b.SELECTED.key,l);}this.deselectEvent.fire([g]);}return this.getSelectedDates();},deselectAll:function(){this.beforeDeselectEvent.fire();var j=b.SELECTED.key,g=this.cfg.getProperty(j),h=g.length,i=g.concat();if(this.parent){this.parent.cfg.setProperty(j,[]);}else{this.cfg.setProperty(j,[]);}if(h>0){this.deselectEvent.fire(i);}return this.getSelectedDates();},_toFieldArray:function(h){var g=[];if(h instanceof Date){g=[[h.getFullYear(),h.getMonth()+1,h.getDate()]];}else{if(e.isString(h)){g=this._parseDates(h);}else{if(e.isArray(h)){for(var j=0;j<h.length;++j){var k=h[j];g[g.length]=[k.getFullYear(),k.getMonth()+1,k.getDate()];}}}}return g;},toDate:function(g){return this._toDate(g);},_toDate:function(g){if(g instanceof Date){return g;}else{return d.getDate(g[0],g[1]-1,g[2]);}},_fieldArraysAreEqual:function(i,h){var g=false;if(i[0]==h[0]&&i[1]==h[1]&&i[2]==h[2]){g=true;}return g;},_indexOfSelectedFieldArray:function(k){var j=-1,g=this.cfg.getProperty(b.SELECTED.key);for(var i=0;i<g.length;++i){var h=g[i];if(k[0]==h[0]&&k[1]==h[1]&&k[2]==h[2]){j=i;break;}}return j;},isDateOOM:function(g){return(g.getMonth()!=this.cfg.getProperty(b.PAGEDATE.key).getMonth());},isDateOOB:function(i){var j=this.cfg.getProperty(b.MINDATE.key),k=this.cfg.getProperty(b.MAXDATE.key),h=d;if(j){j=h.clearTime(j);}if(k){k=h.clearTime(k);}var g=new Date(i.getTime());g=h.clearTime(g);return((j&&g.getTime()<j.getTime())||(k&&g.getTime()>k.getTime()));},_parsePageDate:function(g){var j;if(g){if(g instanceof Date){j=d.findMonthStart(g);}else{var k,i,h;h=g.split(this.cfg.getProperty(b.DATE_FIELD_DELIMITER.key));k=parseInt(h[this.cfg.getProperty(b.MY_MONTH_POSITION.key)-1],10)-1;i=parseInt(h[this.cfg.getProperty(b.MY_YEAR_POSITION.key)-1],10)-this.Locale.YEAR_OFFSET;j=d.getDate(i,k,1);}}else{j=d.getDate(this.today.getFullYear(),this.today.getMonth(),1);}return j;},onBeforeSelect:function(){if(this.cfg.getProperty(b.MULTI_SELECT.key)===false){if(this.parent){this.parent.callChildFunction("clearAllBodyCellStyles",this.Style.CSS_CELL_SELECTED);this.parent.deselectAll();}else{this.clearAllBodyCellStyles(this.Style.CSS_CELL_SELECTED);this.deselectAll();}}},onSelect:function(g){},onBeforeDeselect:function(){},onDeselect:function(g){},onChangePage:function(){this.render();},onRender:function(){},onReset:function(){this.render();},onClear:function(){this.render();},validate:function(){return true;},_parseDate:function(j){var k=j.split(this.Locale.DATE_FIELD_DELIMITER),g;if(k.length==2){g=[k[this.Locale.MD_MONTH_POSITION-1],k[this.Locale.MD_DAY_POSITION-1]];g.type=f.MONTH_DAY;}else{g=[k[this.Locale.MDY_YEAR_POSITION-1]-this.Locale.YEAR_OFFSET,k[this.Locale.MDY_MONTH_POSITION-1],k[this.Locale.MDY_DAY_POSITION-1]];g.type=f.DATE;}for(var h=0;h<g.length;h++){g[h]=parseInt(g[h],10);}return g;},_parseDates:function(h){var o=[],n=h.split(this.Locale.DATE_DELIMITER);for(var m=0;m<n.length;++m){var l=n[m];if(l.indexOf(this.Locale.DATE_RANGE_DELIMITER)!=-1){var g=l.split(this.Locale.DATE_RANGE_DELIMITER),k=this._parseDate(g[0]),p=this._parseDate(g[1]),j=this._parseRange(k,p);o=o.concat(j);}else{var i=this._parseDate(l);o.push(i);}}return o;},_parseRange:function(g,k){var h=d.add(d.getDate(g[0],g[1]-1,g[2]),d.DAY,1),j=d.getDate(k[0],k[1]-1,k[2]),i=[];i.push(g);while(h.getTime()<=j.getTime()){i.push([h.getFullYear(),h.getMonth()+1,h.getDate()]);h=d.add(h,d.DAY,1);}return i;},resetRenderers:function(){this.renderStack=this._renderStack.concat();},removeRenderers:function(){this._renderStack=[];this.renderStack=[];},clearElement:function(g){g.innerHTML=" ";g.className="";},addRenderer:function(g,h){var k=this._parseDates(g);for(var j=0;j<k.length;++j){var l=k[j];if(l.length==2){if(l[0] instanceof Array){this._addRenderer(f.RANGE,l,h);}else{this._addRenderer(f.MONTH_DAY,l,h);}}else{if(l.length==3){this._addRenderer(f.DATE,l,h);}}}},_addRenderer:function(h,i,g){var j=[h,i,g];
+this.renderStack.unshift(j);this._renderStack=this.renderStack.concat();},addMonthRenderer:function(h,g){this._addRenderer(f.MONTH,[h],g);},addWeekdayRenderer:function(h,g){this._addRenderer(f.WEEKDAY,[h],g);},clearAllBodyCellStyles:function(g){for(var h=0;h<this.cells.length;++h){c.removeClass(this.cells[h],g);}},setMonth:function(i){var g=b.PAGEDATE.key,h=this.cfg.getProperty(g);h.setMonth(parseInt(i,10));this.cfg.setProperty(g,h);},setYear:function(h){var g=b.PAGEDATE.key,i=this.cfg.getProperty(g);i.setFullYear(parseInt(h,10)-this.Locale.YEAR_OFFSET);this.cfg.setProperty(g,i);},getSelectedDates:function(){var i=[],h=this.cfg.getProperty(b.SELECTED.key);for(var k=0;k<h.length;++k){var j=h[k];var g=d.getDate(j[0],j[1]-1,j[2]);i.push(g);}i.sort(function(m,l){return m-l;});return i;},hide:function(){if(this.beforeHideEvent.fire()){this.oDomContainer.style.display="none";this.hideEvent.fire();}},show:function(){if(this.beforeShowEvent.fire()){this.oDomContainer.style.display="block";this.showEvent.fire();}},browser:(function(){var g=navigator.userAgent.toLowerCase();if(g.indexOf("opera")!=-1){return"opera";}else{if(g.indexOf("msie 7")!=-1){return"ie7";}else{if(g.indexOf("msie")!=-1){return"ie";}else{if(g.indexOf("safari")!=-1){return"safari";}else{if(g.indexOf("gecko")!=-1){return"gecko";}else{return false;}}}}}})(),toString:function(){return"Calendar "+this.id;},destroy:function(){if(this.beforeDestroyEvent.fire()){var g=this;if(g.navigator){g.navigator.destroy();}if(g.cfg){g.cfg.destroy();}a.purgeElement(g.oDomContainer,true);c.removeClass(g.oDomContainer,g.Style.CSS_WITH_TITLE);c.removeClass(g.oDomContainer,g.Style.CSS_CONTAINER);c.removeClass(g.oDomContainer,g.Style.CSS_SINGLE);g.oDomContainer.innerHTML="";g.oDomContainer=null;g.cells=null;this.destroyEvent.fire();}}};YAHOO.widget.Calendar=f;YAHOO.widget.Calendar_Core=YAHOO.widget.Calendar;YAHOO.widget.Cal_Core=YAHOO.widget.Calendar;})();(function(){var d=YAHOO.util.Dom,f=YAHOO.widget.DateMath,a=YAHOO.util.Event,e=YAHOO.lang,g=YAHOO.widget.Calendar;function b(j,h,i){if(arguments.length>0){this.init.apply(this,arguments);}}b.DEFAULT_CONFIG=b._DEFAULT_CONFIG=g.DEFAULT_CONFIG;b.DEFAULT_CONFIG.PAGES={key:"pages",value:2};var c=b.DEFAULT_CONFIG;b.prototype={init:function(k,i,j){var h=this._parseArgs(arguments);k=h.id;i=h.container;j=h.config;this.oDomContainer=d.get(i);if(!this.oDomContainer.id){this.oDomContainer.id=d.generateId();}if(!k){k=this.oDomContainer.id+"_t";}this.id=k;this.containerId=this.oDomContainer.id;this.initEvents();this.initStyles();this.pages=[];d.addClass(this.oDomContainer,b.CSS_CONTAINER);d.addClass(this.oDomContainer,b.CSS_MULTI_UP);this.cfg=new YAHOO.util.Config(this);this.Options={};this.Locale={};this.setupConfig();if(j){this.cfg.applyConfig(j,true);}this.cfg.fireQueue();},setupConfig:function(){var h=this.cfg;h.addProperty(c.PAGES.key,{value:c.PAGES.value,validator:h.checkNumber,handler:this.configPages});h.addProperty(c.YEAR_OFFSET.key,{value:c.YEAR_OFFSET.value,handler:this.delegateConfig,supercedes:c.YEAR_OFFSET.supercedes,suppressEvent:true});h.addProperty(c.TODAY.key,{value:new Date(c.TODAY.value.getTime()),supercedes:c.TODAY.supercedes,handler:this.configToday,suppressEvent:false});h.addProperty(c.PAGEDATE.key,{value:c.PAGEDATE.value||new Date(c.TODAY.value.getTime()),handler:this.configPageDate});h.addProperty(c.SELECTED.key,{value:[],handler:this.configSelected});h.addProperty(c.TITLE.key,{value:c.TITLE.value,handler:this.configTitle});h.addProperty(c.CLOSE.key,{value:c.CLOSE.value,handler:this.configClose});h.addProperty(c.IFRAME.key,{value:c.IFRAME.value,handler:this.configIframe,validator:h.checkBoolean});h.addProperty(c.MINDATE.key,{value:c.MINDATE.value,handler:this.delegateConfig});h.addProperty(c.MAXDATE.key,{value:c.MAXDATE.value,handler:this.delegateConfig});h.addProperty(c.MULTI_SELECT.key,{value:c.MULTI_SELECT.value,handler:this.delegateConfig,validator:h.checkBoolean});h.addProperty(c.OOM_SELECT.key,{value:c.OOM_SELECT.value,handler:this.delegateConfig,validator:h.checkBoolean});h.addProperty(c.START_WEEKDAY.key,{value:c.START_WEEKDAY.value,handler:this.delegateConfig,validator:h.checkNumber});h.addProperty(c.SHOW_WEEKDAYS.key,{value:c.SHOW_WEEKDAYS.value,handler:this.delegateConfig,validator:h.checkBoolean});h.addProperty(c.SHOW_WEEK_HEADER.key,{value:c.SHOW_WEEK_HEADER.value,handler:this.delegateConfig,validator:h.checkBoolean});h.addProperty(c.SHOW_WEEK_FOOTER.key,{value:c.SHOW_WEEK_FOOTER.value,handler:this.delegateConfig,validator:h.checkBoolean});h.addProperty(c.HIDE_BLANK_WEEKS.key,{value:c.HIDE_BLANK_WEEKS.value,handler:this.delegateConfig,validator:h.checkBoolean});h.addProperty(c.NAV_ARROW_LEFT.key,{value:c.NAV_ARROW_LEFT.value,handler:this.delegateConfig});h.addProperty(c.NAV_ARROW_RIGHT.key,{value:c.NAV_ARROW_RIGHT.value,handler:this.delegateConfig});h.addProperty(c.MONTHS_SHORT.key,{value:c.MONTHS_SHORT.value,handler:this.delegateConfig});h.addProperty(c.MONTHS_LONG.key,{value:c.MONTHS_LONG.value,handler:this.delegateConfig});h.addProperty(c.WEEKDAYS_1CHAR.key,{value:c.WEEKDAYS_1CHAR.value,handler:this.delegateConfig});h.addProperty(c.WEEKDAYS_SHORT.key,{value:c.WEEKDAYS_SHORT.value,handler:this.delegateConfig});h.addProperty(c.WEEKDAYS_MEDIUM.key,{value:c.WEEKDAYS_MEDIUM.value,handler:this.delegateConfig});h.addProperty(c.WEEKDAYS_LONG.key,{value:c.WEEKDAYS_LONG.value,handler:this.delegateConfig});h.addProperty(c.LOCALE_MONTHS.key,{value:c.LOCALE_MONTHS.value,handler:this.delegateConfig});h.addProperty(c.LOCALE_WEEKDAYS.key,{value:c.LOCALE_WEEKDAYS.value,handler:this.delegateConfig});h.addProperty(c.DATE_DELIMITER.key,{value:c.DATE_DELIMITER.value,handler:this.delegateConfig});h.addProperty(c.DATE_FIELD_DELIMITER.key,{value:c.DATE_FIELD_DELIMITER.value,handler:this.delegateConfig});h.addProperty(c.DATE_RANGE_DELIMITER.key,{value:c.DATE_RANGE_DELIMITER.value,handler:this.delegateConfig});h.addProperty(c.MY_MONTH_POSITION.key,{value:c.MY_MONTH_POSITION.value,handler:this.delegateConfig,validator:h.checkNumber});
+h.addProperty(c.MY_YEAR_POSITION.key,{value:c.MY_YEAR_POSITION.value,handler:this.delegateConfig,validator:h.checkNumber});h.addProperty(c.MD_MONTH_POSITION.key,{value:c.MD_MONTH_POSITION.value,handler:this.delegateConfig,validator:h.checkNumber});h.addProperty(c.MD_DAY_POSITION.key,{value:c.MD_DAY_POSITION.value,handler:this.delegateConfig,validator:h.checkNumber});h.addProperty(c.MDY_MONTH_POSITION.key,{value:c.MDY_MONTH_POSITION.value,handler:this.delegateConfig,validator:h.checkNumber});h.addProperty(c.MDY_DAY_POSITION.key,{value:c.MDY_DAY_POSITION.value,handler:this.delegateConfig,validator:h.checkNumber});h.addProperty(c.MDY_YEAR_POSITION.key,{value:c.MDY_YEAR_POSITION.value,handler:this.delegateConfig,validator:h.checkNumber});h.addProperty(c.MY_LABEL_MONTH_POSITION.key,{value:c.MY_LABEL_MONTH_POSITION.value,handler:this.delegateConfig,validator:h.checkNumber});h.addProperty(c.MY_LABEL_YEAR_POSITION.key,{value:c.MY_LABEL_YEAR_POSITION.value,handler:this.delegateConfig,validator:h.checkNumber});h.addProperty(c.MY_LABEL_MONTH_SUFFIX.key,{value:c.MY_LABEL_MONTH_SUFFIX.value,handler:this.delegateConfig});h.addProperty(c.MY_LABEL_YEAR_SUFFIX.key,{value:c.MY_LABEL_YEAR_SUFFIX.value,handler:this.delegateConfig});h.addProperty(c.NAV.key,{value:c.NAV.value,handler:this.configNavigator});h.addProperty(c.STRINGS.key,{value:c.STRINGS.value,handler:this.configStrings,validator:function(i){return e.isObject(i);},supercedes:c.STRINGS.supercedes});},initEvents:function(){var j=this,l="Event",m=YAHOO.util.CustomEvent;var i=function(o,s,n){for(var r=0;r<j.pages.length;++r){var q=j.pages[r];q[this.type+l].subscribe(o,s,n);}};var h=function(n,r){for(var q=0;q<j.pages.length;++q){var o=j.pages[q];o[this.type+l].unsubscribe(n,r);}};var k=g._EVENT_TYPES;j.beforeSelectEvent=new m(k.BEFORE_SELECT);j.beforeSelectEvent.subscribe=i;j.beforeSelectEvent.unsubscribe=h;j.selectEvent=new m(k.SELECT);j.selectEvent.subscribe=i;j.selectEvent.unsubscribe=h;j.beforeDeselectEvent=new m(k.BEFORE_DESELECT);j.beforeDeselectEvent.subscribe=i;j.beforeDeselectEvent.unsubscribe=h;j.deselectEvent=new m(k.DESELECT);j.deselectEvent.subscribe=i;j.deselectEvent.unsubscribe=h;j.changePageEvent=new m(k.CHANGE_PAGE);j.changePageEvent.subscribe=i;j.changePageEvent.unsubscribe=h;j.beforeRenderEvent=new m(k.BEFORE_RENDER);j.beforeRenderEvent.subscribe=i;j.beforeRenderEvent.unsubscribe=h;j.renderEvent=new m(k.RENDER);j.renderEvent.subscribe=i;j.renderEvent.unsubscribe=h;j.resetEvent=new m(k.RESET);j.resetEvent.subscribe=i;j.resetEvent.unsubscribe=h;j.clearEvent=new m(k.CLEAR);j.clearEvent.subscribe=i;j.clearEvent.unsubscribe=h;j.beforeShowEvent=new m(k.BEFORE_SHOW);j.showEvent=new m(k.SHOW);j.beforeHideEvent=new m(k.BEFORE_HIDE);j.hideEvent=new m(k.HIDE);j.beforeShowNavEvent=new m(k.BEFORE_SHOW_NAV);j.showNavEvent=new m(k.SHOW_NAV);j.beforeHideNavEvent=new m(k.BEFORE_HIDE_NAV);j.hideNavEvent=new m(k.HIDE_NAV);j.beforeRenderNavEvent=new m(k.BEFORE_RENDER_NAV);j.renderNavEvent=new m(k.RENDER_NAV);j.beforeDestroyEvent=new m(k.BEFORE_DESTROY);j.destroyEvent=new m(k.DESTROY);},configPages:function(u,s,n){var l=s[0],j=c.PAGEDATE.key,x="_",m,o=null,t="groupcal",w="first-of-type",k="last-of-type";for(var i=0;i<l;++i){var v=this.id+x+i,r=this.containerId+x+i,q=this.cfg.getConfig();q.close=false;q.title=false;q.navigator=null;if(i>0){m=new Date(o);this._setMonthOnDate(m,m.getMonth()+i);q.pageDate=m;}var h=this.constructChild(v,r,q);d.removeClass(h.oDomContainer,this.Style.CSS_SINGLE);d.addClass(h.oDomContainer,t);if(i===0){o=h.cfg.getProperty(j);d.addClass(h.oDomContainer,w);}if(i==(l-1)){d.addClass(h.oDomContainer,k);}h.parent=this;h.index=i;this.pages[this.pages.length]=h;}},configPageDate:function(o,n,l){var j=n[0],m;var k=c.PAGEDATE.key;for(var i=0;i<this.pages.length;++i){var h=this.pages[i];if(i===0){m=h._parsePageDate(j);h.cfg.setProperty(k,m);}else{var q=new Date(m);this._setMonthOnDate(q,q.getMonth()+i);h.cfg.setProperty(k,q);}}},configSelected:function(j,h,l){var k=c.SELECTED.key;this.delegateConfig(j,h,l);var i=(this.pages.length>0)?this.pages[0].cfg.getProperty(k):[];this.cfg.setProperty(k,i,true);},delegateConfig:function(i,h,l){var m=h[0];var k;for(var j=0;j<this.pages.length;j++){k=this.pages[j];k.cfg.setProperty(i,m);}},setChildFunction:function(k,i){var h=this.cfg.getProperty(c.PAGES.key);for(var j=0;j<h;++j){this.pages[j][k]=i;}},callChildFunction:function(m,i){var h=this.cfg.getProperty(c.PAGES.key);for(var l=0;l<h;++l){var k=this.pages[l];if(k[m]){var j=k[m];j.call(k,i);}}},constructChild:function(k,i,j){var h=document.getElementById(i);if(!h){h=document.createElement("div");h.id=i;this.oDomContainer.appendChild(h);}return new g(k,i,j);},setMonth:function(l){l=parseInt(l,10);var m;var i=c.PAGEDATE.key;for(var k=0;k<this.pages.length;++k){var j=this.pages[k];var h=j.cfg.getProperty(i);if(k===0){m=h.getFullYear();}else{h.setFullYear(m);}this._setMonthOnDate(h,l+k);j.cfg.setProperty(i,h);}},setYear:function(j){var i=c.PAGEDATE.key;j=parseInt(j,10);for(var l=0;l<this.pages.length;++l){var k=this.pages[l];var h=k.cfg.getProperty(i);if((h.getMonth()+1)==1&&l>0){j+=1;}k.setYear(j);}},render:function(){this.renderHeader();for(var i=0;i<this.pages.length;++i){var h=this.pages[i];h.render();}this.renderFooter();},select:function(h){for(var j=0;j<this.pages.length;++j){var i=this.pages[j];i.select(h);}return this.getSelectedDates();},selectCell:function(h){for(var j=0;j<this.pages.length;++j){var i=this.pages[j];i.selectCell(h);}return this.getSelectedDates();},deselect:function(h){for(var j=0;j<this.pages.length;++j){var i=this.pages[j];i.deselect(h);}return this.getSelectedDates();},deselectAll:function(){for(var i=0;i<this.pages.length;++i){var h=this.pages[i];h.deselectAll();}return this.getSelectedDates();},deselectCell:function(h){for(var j=0;j<this.pages.length;++j){var i=this.pages[j];i.deselectCell(h);}return this.getSelectedDates();},reset:function(){for(var i=0;i<this.pages.length;++i){var h=this.pages[i];h.reset();}},clear:function(){for(var i=0;
+i<this.pages.length;++i){var h=this.pages[i];h.clear();}this.cfg.setProperty(c.SELECTED.key,[]);this.cfg.setProperty(c.PAGEDATE.key,new Date(this.pages[0].today.getTime()));this.render();},nextMonth:function(){for(var i=0;i<this.pages.length;++i){var h=this.pages[i];h.nextMonth();}},previousMonth:function(){for(var i=this.pages.length-1;i>=0;--i){var h=this.pages[i];h.previousMonth();}},nextYear:function(){for(var i=0;i<this.pages.length;++i){var h=this.pages[i];h.nextYear();}},previousYear:function(){for(var i=0;i<this.pages.length;++i){var h=this.pages[i];h.previousYear();}},getSelectedDates:function(){var j=[];var i=this.cfg.getProperty(c.SELECTED.key);for(var l=0;l<i.length;++l){var k=i[l];var h=f.getDate(k[0],k[1]-1,k[2]);j.push(h);}j.sort(function(n,m){return n-m;});return j;},addRenderer:function(h,i){for(var k=0;k<this.pages.length;++k){var j=this.pages[k];j.addRenderer(h,i);}},addMonthRenderer:function(k,h){for(var j=0;j<this.pages.length;++j){var i=this.pages[j];i.addMonthRenderer(k,h);}},addWeekdayRenderer:function(i,h){for(var k=0;k<this.pages.length;++k){var j=this.pages[k];j.addWeekdayRenderer(i,h);}},removeRenderers:function(){this.callChildFunction("removeRenderers");},renderHeader:function(){},renderFooter:function(){},addMonths:function(h){this.callChildFunction("addMonths",h);},subtractMonths:function(h){this.callChildFunction("subtractMonths",h);},addYears:function(h){this.callChildFunction("addYears",h);},subtractYears:function(h){this.callChildFunction("subtractYears",h);},getCalendarPage:function(l){var o=null;if(l){var p=l.getFullYear(),k=l.getMonth();var j=this.pages;for(var n=0;n<j.length;++n){var h=j[n].cfg.getProperty("pagedate");if(h.getFullYear()===p&&h.getMonth()===k){o=j[n];break;}}}return o;},_setMonthOnDate:function(i,j){if(YAHOO.env.ua.webkit&&YAHOO.env.ua.webkit<420&&(j<0||j>11)){var h=f.add(i,f.MONTH,j-i.getMonth());i.setTime(h.getTime());}else{i.setMonth(j);}},_fixWidth:function(){var h=0;for(var j=0;j<this.pages.length;++j){var i=this.pages[j];h+=i.oDomContainer.offsetWidth;}if(h>0){this.oDomContainer.style.width=h+"px";}},toString:function(){return"CalendarGroup "+this.id;},destroy:function(){if(this.beforeDestroyEvent.fire()){var k=this;if(k.navigator){k.navigator.destroy();}if(k.cfg){k.cfg.destroy();}a.purgeElement(k.oDomContainer,true);d.removeClass(k.oDomContainer,b.CSS_CONTAINER);d.removeClass(k.oDomContainer,b.CSS_MULTI_UP);for(var j=0,h=k.pages.length;j<h;j++){k.pages[j].destroy();k.pages[j]=null;}k.oDomContainer.innerHTML="";k.oDomContainer=null;this.destroyEvent.fire();}}};b.CSS_CONTAINER="yui-calcontainer";b.CSS_MULTI_UP="multi";b.CSS_2UPTITLE="title";b.CSS_2UPCLOSE="close-icon";YAHOO.lang.augmentProto(b,g,"buildDayLabel","buildMonthLabel","renderOutOfBoundsDate","renderRowHeader","renderRowFooter","renderCellDefault","styleCellDefault","renderCellStyleHighlight1","renderCellStyleHighlight2","renderCellStyleHighlight3","renderCellStyleHighlight4","renderCellStyleToday","renderCellStyleSelected","renderCellNotThisMonth","styleCellNotThisMonth","renderBodyCellRestricted","initStyles","configTitle","configClose","configIframe","configStrings","configToday","configNavigator","createTitleBar","createCloseButton","removeTitleBar","removeCloseButton","hide","show","toDate","_toDate","_parseArgs","browser");YAHOO.widget.CalGrp=b;YAHOO.widget.CalendarGroup=b;YAHOO.widget.Calendar2up=function(j,h,i){this.init(j,h,i);};YAHOO.extend(YAHOO.widget.Calendar2up,b);YAHOO.widget.Cal2up=YAHOO.widget.Calendar2up;})();YAHOO.widget.CalendarNavigator=function(a){this.init(a);};(function(){var a=YAHOO.widget.CalendarNavigator;a.CLASSES={NAV:"yui-cal-nav",NAV_VISIBLE:"yui-cal-nav-visible",MASK:"yui-cal-nav-mask",YEAR:"yui-cal-nav-y",MONTH:"yui-cal-nav-m",BUTTONS:"yui-cal-nav-b",BUTTON:"yui-cal-nav-btn",ERROR:"yui-cal-nav-e",YEAR_CTRL:"yui-cal-nav-yc",MONTH_CTRL:"yui-cal-nav-mc",INVALID:"yui-invalid",DEFAULT:"yui-default"};a.DEFAULT_CONFIG={strings:{month:"Month",year:"Year",submit:"Okay",cancel:"Cancel",invalidYear:"Year needs to be a number"},monthFormat:YAHOO.widget.Calendar.LONG,initialFocus:"year"};a._DEFAULT_CFG=a.DEFAULT_CONFIG;a.ID_SUFFIX="_nav";a.MONTH_SUFFIX="_month";a.YEAR_SUFFIX="_year";a.ERROR_SUFFIX="_error";a.CANCEL_SUFFIX="_cancel";a.SUBMIT_SUFFIX="_submit";a.YR_MAX_DIGITS=4;a.YR_MINOR_INC=1;a.YR_MAJOR_INC=10;a.UPDATE_DELAY=50;a.YR_PATTERN=/^\d+$/;a.TRIM=/^\s*(.*?)\s*$/;})();YAHOO.widget.CalendarNavigator.prototype={id:null,cal:null,navEl:null,maskEl:null,yearEl:null,monthEl:null,errorEl:null,submitEl:null,cancelEl:null,firstCtrl:null,lastCtrl:null,_doc:null,_year:null,_month:0,__rendered:false,init:function(a){var c=a.oDomContainer;this.cal=a;this.id=c.id+YAHOO.widget.CalendarNavigator.ID_SUFFIX;this._doc=c.ownerDocument;var b=YAHOO.env.ua.ie;this.__isIEQuirks=(b&&((b<=6)||(this._doc.compatMode=="BackCompat")));},show:function(){var a=YAHOO.widget.CalendarNavigator.CLASSES;if(this.cal.beforeShowNavEvent.fire()){if(!this.__rendered){this.render();}this.clearErrors();this._updateMonthUI();this._updateYearUI();this._show(this.navEl,true);this.setInitialFocus();this.showMask();YAHOO.util.Dom.addClass(this.cal.oDomContainer,a.NAV_VISIBLE);this.cal.showNavEvent.fire();}},hide:function(){var a=YAHOO.widget.CalendarNavigator.CLASSES;if(this.cal.beforeHideNavEvent.fire()){this._show(this.navEl,false);this.hideMask();YAHOO.util.Dom.removeClass(this.cal.oDomContainer,a.NAV_VISIBLE);this.cal.hideNavEvent.fire();}},showMask:function(){this._show(this.maskEl,true);if(this.__isIEQuirks){this._syncMask();}},hideMask:function(){this._show(this.maskEl,false);},getMonth:function(){return this._month;},getYear:function(){return this._year;},setMonth:function(a){if(a>=0&&a<12){this._month=a;}this._updateMonthUI();},setYear:function(b){var a=YAHOO.widget.CalendarNavigator.YR_PATTERN;if(YAHOO.lang.isNumber(b)&&a.test(b+"")){this._year=b;}this._updateYearUI();},render:function(){this.cal.beforeRenderNavEvent.fire();if(!this.__rendered){this.createNav();this.createMask();this.applyListeners();
+this.__rendered=true;}this.cal.renderNavEvent.fire();},createNav:function(){var b=YAHOO.widget.CalendarNavigator;var c=this._doc;var e=c.createElement("div");e.className=b.CLASSES.NAV;var a=this.renderNavContents([]);e.innerHTML=a.join("");this.cal.oDomContainer.appendChild(e);this.navEl=e;this.yearEl=c.getElementById(this.id+b.YEAR_SUFFIX);this.monthEl=c.getElementById(this.id+b.MONTH_SUFFIX);this.errorEl=c.getElementById(this.id+b.ERROR_SUFFIX);this.submitEl=c.getElementById(this.id+b.SUBMIT_SUFFIX);this.cancelEl=c.getElementById(this.id+b.CANCEL_SUFFIX);if(YAHOO.env.ua.gecko&&this.yearEl&&this.yearEl.type=="text"){this.yearEl.setAttribute("autocomplete","off");}this._setFirstLastElements();},createMask:function(){var b=YAHOO.widget.CalendarNavigator.CLASSES;var a=this._doc.createElement("div");a.className=b.MASK;this.cal.oDomContainer.appendChild(a);this.maskEl=a;},_syncMask:function(){var b=this.cal.oDomContainer;if(b&&this.maskEl){var a=YAHOO.util.Dom.getRegion(b);YAHOO.util.Dom.setStyle(this.maskEl,"width",a.right-a.left+"px");YAHOO.util.Dom.setStyle(this.maskEl,"height",a.bottom-a.top+"px");}},renderNavContents:function(a){var c=YAHOO.widget.CalendarNavigator,d=c.CLASSES,b=a;b[b.length]='<div class="'+d.MONTH+'">';this.renderMonth(b);b[b.length]="</div>";b[b.length]='<div class="'+d.YEAR+'">';this.renderYear(b);b[b.length]="</div>";b[b.length]='<div class="'+d.BUTTONS+'">';this.renderButtons(b);b[b.length]="</div>";b[b.length]='<div class="'+d.ERROR+'" id="'+this.id+c.ERROR_SUFFIX+'"></div>';return b;},renderMonth:function(c){var f=YAHOO.widget.CalendarNavigator,g=f.CLASSES;var j=this.id+f.MONTH_SUFFIX,e=this.__getCfg("monthFormat"),a=this.cal.cfg.getProperty((e==YAHOO.widget.Calendar.SHORT)?"MONTHS_SHORT":"MONTHS_LONG"),d=c;if(a&&a.length>0){d[d.length]='<label for="'+j+'">';d[d.length]=this.__getCfg("month",true);d[d.length]="</label>";d[d.length]='<select name="'+j+'" id="'+j+'" class="'+g.MONTH_CTRL+'">';for(var b=0;b<a.length;b++){d[d.length]='<option value="'+b+'">';d[d.length]=a[b];d[d.length]="</option>";}d[d.length]="</select>";}return d;},renderYear:function(b){var d=YAHOO.widget.CalendarNavigator,e=d.CLASSES;var f=this.id+d.YEAR_SUFFIX,a=d.YR_MAX_DIGITS,c=b;c[c.length]='<label for="'+f+'">';c[c.length]=this.__getCfg("year",true);c[c.length]="</label>";c[c.length]='<input type="text" name="'+f+'" id="'+f+'" class="'+e.YEAR_CTRL+'" maxlength="'+a+'"/>';return c;},renderButtons:function(a){var c=YAHOO.widget.CalendarNavigator.CLASSES;var b=a;b[b.length]='<span class="'+c.BUTTON+" "+c.DEFAULT+'">';b[b.length]='<button type="button" id="'+this.id+"_submit"+'">';b[b.length]=this.__getCfg("submit",true);b[b.length]="</button>";b[b.length]="</span>";b[b.length]='<span class="'+c.BUTTON+'">';b[b.length]='<button type="button" id="'+this.id+"_cancel"+'">';b[b.length]=this.__getCfg("cancel",true);b[b.length]="</button>";b[b.length]="</span>";return b;},applyListeners:function(){var b=YAHOO.util.Event;function a(){if(this.validate()){this.setYear(this._getYearFromUI());}}function c(){this.setMonth(this._getMonthFromUI());}b.on(this.submitEl,"click",this.submit,this,true);b.on(this.cancelEl,"click",this.cancel,this,true);b.on(this.yearEl,"blur",a,this,true);b.on(this.monthEl,"change",c,this,true);if(this.__isIEQuirks){YAHOO.util.Event.on(this.cal.oDomContainer,"resize",this._syncMask,this,true);}this.applyKeyListeners();},purgeListeners:function(){var a=YAHOO.util.Event;a.removeListener(this.submitEl,"click",this.submit);a.removeListener(this.cancelEl,"click",this.cancel);a.removeListener(this.yearEl,"blur");a.removeListener(this.monthEl,"change");if(this.__isIEQuirks){a.removeListener(this.cal.oDomContainer,"resize",this._syncMask);}this.purgeKeyListeners();},applyKeyListeners:function(){var d=YAHOO.util.Event,a=YAHOO.env.ua;var c=(a.ie||a.webkit)?"keydown":"keypress";var b=(a.ie||a.opera||a.webkit)?"keydown":"keypress";d.on(this.yearEl,"keypress",this._handleEnterKey,this,true);d.on(this.yearEl,c,this._handleDirectionKeys,this,true);d.on(this.lastCtrl,b,this._handleTabKey,this,true);d.on(this.firstCtrl,b,this._handleShiftTabKey,this,true);},purgeKeyListeners:function(){var d=YAHOO.util.Event,a=YAHOO.env.ua;var c=(a.ie||a.webkit)?"keydown":"keypress";var b=(a.ie||a.opera||a.webkit)?"keydown":"keypress";d.removeListener(this.yearEl,"keypress",this._handleEnterKey);d.removeListener(this.yearEl,c,this._handleDirectionKeys);d.removeListener(this.lastCtrl,b,this._handleTabKey);d.removeListener(this.firstCtrl,b,this._handleShiftTabKey);},submit:function(){if(this.validate()){this.hide();this.setMonth(this._getMonthFromUI());this.setYear(this._getYearFromUI());var b=this.cal;var a=YAHOO.widget.CalendarNavigator.UPDATE_DELAY;if(a>0){var c=this;window.setTimeout(function(){c._update(b);},a);}else{this._update(b);}}},_update:function(b){var a=YAHOO.widget.DateMath.getDate(this.getYear()-b.cfg.getProperty("YEAR_OFFSET"),this.getMonth(),1);b.cfg.setProperty("pagedate",a);b.render();},cancel:function(){this.hide();},validate:function(){if(this._getYearFromUI()!==null){this.clearErrors();return true;}else{this.setYearError();this.setError(this.__getCfg("invalidYear",true));return false;}},setError:function(a){if(this.errorEl){this.errorEl.innerHTML=a;this._show(this.errorEl,true);}},clearError:function(){if(this.errorEl){this.errorEl.innerHTML="";this._show(this.errorEl,false);}},setYearError:function(){YAHOO.util.Dom.addClass(this.yearEl,YAHOO.widget.CalendarNavigator.CLASSES.INVALID);},clearYearError:function(){YAHOO.util.Dom.removeClass(this.yearEl,YAHOO.widget.CalendarNavigator.CLASSES.INVALID);},clearErrors:function(){this.clearError();this.clearYearError();},setInitialFocus:function(){var a=this.submitEl,c=this.__getCfg("initialFocus");if(c&&c.toLowerCase){c=c.toLowerCase();if(c=="year"){a=this.yearEl;try{this.yearEl.select();}catch(b){}}else{if(c=="month"){a=this.monthEl;}}}if(a&&YAHOO.lang.isFunction(a.focus)){try{a.focus();}catch(d){}}},erase:function(){if(this.__rendered){this.purgeListeners();
+this.yearEl=null;this.monthEl=null;this.errorEl=null;this.submitEl=null;this.cancelEl=null;this.firstCtrl=null;this.lastCtrl=null;if(this.navEl){this.navEl.innerHTML="";}var b=this.navEl.parentNode;if(b){b.removeChild(this.navEl);}this.navEl=null;var a=this.maskEl.parentNode;if(a){a.removeChild(this.maskEl);}this.maskEl=null;this.__rendered=false;}},destroy:function(){this.erase();this._doc=null;this.cal=null;this.id=null;},_show:function(b,a){if(b){YAHOO.util.Dom.setStyle(b,"display",(a)?"block":"none");}},_getMonthFromUI:function(){if(this.monthEl){return this.monthEl.selectedIndex;}else{return 0;}},_getYearFromUI:function(){var b=YAHOO.widget.CalendarNavigator;var a=null;if(this.yearEl){var c=this.yearEl.value;c=c.replace(b.TRIM,"$1");if(b.YR_PATTERN.test(c)){a=parseInt(c,10);}}return a;},_updateYearUI:function(){if(this.yearEl&&this._year!==null){this.yearEl.value=this._year;}},_updateMonthUI:function(){if(this.monthEl){this.monthEl.selectedIndex=this._month;}},_setFirstLastElements:function(){this.firstCtrl=this.monthEl;this.lastCtrl=this.cancelEl;if(this.__isMac){if(YAHOO.env.ua.webkit&&YAHOO.env.ua.webkit<420){this.firstCtrl=this.monthEl;this.lastCtrl=this.yearEl;}if(YAHOO.env.ua.gecko){this.firstCtrl=this.yearEl;this.lastCtrl=this.yearEl;}}},_handleEnterKey:function(b){var a=YAHOO.util.KeyListener.KEY;if(YAHOO.util.Event.getCharCode(b)==a.ENTER){YAHOO.util.Event.preventDefault(b);this.submit();}},_handleDirectionKeys:function(h){var g=YAHOO.util.Event,a=YAHOO.util.KeyListener.KEY,d=YAHOO.widget.CalendarNavigator;var f=(this.yearEl.value)?parseInt(this.yearEl.value,10):null;if(isFinite(f)){var b=false;switch(g.getCharCode(h)){case a.UP:this.yearEl.value=f+d.YR_MINOR_INC;b=true;break;case a.DOWN:this.yearEl.value=Math.max(f-d.YR_MINOR_INC,0);b=true;break;case a.PAGE_UP:this.yearEl.value=f+d.YR_MAJOR_INC;b=true;break;case a.PAGE_DOWN:this.yearEl.value=Math.max(f-d.YR_MAJOR_INC,0);b=true;break;default:break;}if(b){g.preventDefault(h);try{this.yearEl.select();}catch(c){}}}},_handleTabKey:function(d){var c=YAHOO.util.Event,a=YAHOO.util.KeyListener.KEY;if(c.getCharCode(d)==a.TAB&&!d.shiftKey){try{c.preventDefault(d);this.firstCtrl.focus();}catch(b){}}},_handleShiftTabKey:function(d){var c=YAHOO.util.Event,a=YAHOO.util.KeyListener.KEY;if(d.shiftKey&&c.getCharCode(d)==a.TAB){try{c.preventDefault(d);this.lastCtrl.focus();}catch(b){}}},__getCfg:function(d,b){var c=YAHOO.widget.CalendarNavigator.DEFAULT_CONFIG;var a=this.cal.cfg.getProperty("navigator");if(b){return(a!==true&&a.strings&&a.strings[d])?a.strings[d]:c.strings[d];}else{return(a!==true&&a[d])?a[d]:c[d];}},__isMac:(navigator.userAgent.toLowerCase().indexOf("macintosh")!=-1)};YAHOO.register("calendar",YAHOO.widget.Calendar,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/carousel/carousel-min.js b/Websites/bugs.webkit.org/js/yui/carousel/carousel-min.js
new file mode 100644
index 0000000..ed4062b
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/carousel/carousel-min.js
@@ -0,0 +1,12 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var Q="Carousel";YAHOO.widget.Carousel=function(u,t){YAHOO.widget.Carousel.superclass.constructor.call(this,u,t);};var V=YAHOO.widget.Carousel,g=YAHOO.util.Dom,e=YAHOO.util.Event,r=YAHOO.lang,U={},a=true,F="afterScroll",i="allItemsRemoved",d="beforeHide",K="beforePageChange",k="beforeScroll",Z="beforeShow",B="blur",Y="focus",c="hide",T="itemAdded",q="itemRemoved",R="itemReplaced",C="itemSelected",M="loadItems",J="navigationStateChange",j="pageChange",I="render",W="show",b="startAutoPlay",s="stopAutoPlay",L="uiUpdate";function H(t,u){var v;for(v in u){if(u.hasOwnProperty(v)){g.setStyle(t,v,u[v]);}}}function X(u,t){var v=document.createElement(u);t=t||{};if(t.className){g.addClass(v,t.className);}if(t.styles){H(v,t.styles);}if(t.parent){t.parent.appendChild(v);}if(t.id){v.setAttribute("id",t.id);}if(t.content){if(t.content.nodeName){v.appendChild(t.content);}else{v.innerHTML=t.content;}}return v;}function f(v,u,t){var x;if(!v){return 0;}function w(AA,z){var AB;if(z=="marginRight"&&(YAHOO.env.ua.webkit||(YAHOO.env.ua.ie&&YAHOO.env.ua.ie>=9))){AB=parseInt(g.getStyle(AA,"marginLeft"),10);}else{AB=parseInt(g.getStyle(AA,z),10);}return r.isNumber(AB)?AB:0;}function y(AA,z){var AB;if(z=="marginRight"&&YAHOO.env.ua.webkit){AB=parseFloat(g.getStyle(AA,"marginLeft"));}else{AB=parseFloat(g.getStyle(AA,z));}return r.isNumber(AB)?AB:0;}if(typeof t=="undefined"){t="int";}switch(u){case"height":x=v.offsetHeight;if(x>0){x+=w(v,"marginTop")+w(v,"marginBottom");}else{x=y(v,"height")+w(v,"marginTop")+w(v,"marginBottom")+w(v,"borderTopWidth")+w(v,"borderBottomWidth")+w(v,"paddingTop")+w(v,"paddingBottom");}break;case"width":x=v.offsetWidth;if(x>0){x+=w(v,"marginLeft")+w(v,"marginRight");}else{x=y(v,"width")+w(v,"marginLeft")+w(v,"marginRight")+w(v,"borderLeftWidth")+w(v,"borderRightWidth")+w(v,"paddingLeft")+w(v,"paddingRight");}break;default:if(t=="int"){x=w(v,u);}else{if(t=="float"){x=y(v,u);}else{x=g.getStyle(v,u);}}break;}return x;}function P(x){var w=this,y,v,u=0,t=false;if(w._itemAttrCache[x]){return w._itemAttrCache[x];}if(w._itemsTable.numItems===0){return 0;}v=w._findClosestSibling(-1);if(r.isUndefined(v)){return 0;}y=g.get(v.id);if(typeof x=="undefined"){t=w.get("isVertical");}else{t=x=="height";}if(t){u=f(y,"height");}else{u=f(y,"width");}if(u){w._itemAttrCache[x]=u;}return u;}function O(){var u=this,v,t;v=u.get("isVertical");t=P.call(u,v?"height":"width");return(t*u.get("revealAmount")/100);}function o(AC){var AI=this,AA=AI._cols,AG=AI._rows,y,z,AD,AE,x,w,AB,t,v,AF,AH={},u=AI._itemsTable;AD=AI.get("isVertical");z=P.call(AI,AD?"height":"width");v=O.call(AI);if(AG){y=this.getPageForItem(AC);if(AD){x=Math.floor(AC/AA);AF=x;AB=AF*z;AH.top=(AB+v)+"px";z=P.call(AI,"width");AE=AC%AA;AF=AE;t=AF*z;AH.left=t+"px";}else{AE=AC%AA;w=(y-1)*AA;AF=AE+w;t=AF*z;AH.left=(t+v)+"px";z=P.call(AI,"height");x=Math.floor(AC/AA);w=(y-1)*AG;AF=x-w;AB=AF*z;AH.top=AB+"px";}}else{if(AD){AH.left=0;AH.top=((AC*z)+v)+"px";}else{AH.top=0;AH.left=((AC*z)+v)+"px";}}return AH;}function D(u){var t=this.get("numVisible");return Math.floor(u/t)*t;}function l(x){var w=this,v=0,u=0,t=w.get("isVertical")?"height":"width";v=P.call(w,t);u=v*x;return u;}function h(t,u){u.scrollPageBackward();e.preventDefault(t);}function m(t,u){u.scrollPageForward();e.preventDefault(t);}function p(x,u){var AA=this,AC=AA.CLASSES,t,z=AA._firstItem,y=AA.get("numItems"),AB=AA.get("numVisible"),w=u,v=z+AB-1;if(w>=0&&w<y){if(!r.isUndefined(AA._itemsTable.items[w])){t=g.get(AA._itemsTable.items[w].id);if(t){g.removeClass(t,AC.SELECTED_ITEM);}}}if(r.isNumber(x)){x=parseInt(x,10);x=r.isNumber(x)?x:0;}else{x=z;}if(r.isUndefined(AA._itemsTable.items[x])){x=D.call(AA,x);AA.scrollTo(x);}if(!r.isUndefined(AA._itemsTable.items[x])){t=g.get(AA._itemsTable.items[x].id);if(t){g.addClass(t,AC.SELECTED_ITEM);}}if(x<z||x>v){x=D.call(AA,x);AA.scrollTo(x);}}function G(u){var v=this,t=v.get("navigation");if(r.isUndefined(t)){return;}if(r.isUndefined(u)){if(!r.isUndefined(t.prev)&&r.isArray(t.prev)&&!r.isUndefined(t.prev[0])){g.setStyle(t.prev[0],"visibility","visible");}if(!r.isUndefined(t.next)&&r.isArray(t.next)&&!r.isUndefined(t.next[0])){g.setStyle(t.next[0],"visibility","visible");}if(!r.isUndefined(v._pages)&&!r.isUndefined(v._pages.el)){g.setStyle(v._pages.el,"visibility","visible");}}else{if(!r.isUndefined(t.prev)&&r.isArray(t.prev)&&!r.isUndefined(t.prev[0])){g.setStyle(t.prev[0],"visibility","hidden");}if(!r.isUndefined(t.next)&&r.isArray(t.next)&&!r.isUndefined(t.next[0])){g.setStyle(t.next[0],"visibility","hidden");}if(!r.isUndefined(v._pages)&&!r.isUndefined(v._pages.el)){g.setStyle(v._pages.el,"visibility","hidden");}}}function n(){var v=false,y=this,u=y.CLASSES,x,t,w;if(!y._hasRendered){return;}t=y.get("navigation");w=y._firstItem+y.get("numVisible");if(t.prev){if(y.get("numItems")===0||y._firstItem===0){if(y.get("numItems")===0||!y.get("isCircular")){e.removeListener(t.prev,"click",h);g.addClass(t.prev,u.FIRST_NAV_DISABLED);for(x=0;x<y._navBtns.prev.length;x++){y._navBtns.prev[x].setAttribute("disabled","true");}y._prevEnabled=false;}else{v=!y._prevEnabled;}}else{v=!y._prevEnabled;}if(v){e.on(t.prev,"click",h,y);g.removeClass(t.prev,u.FIRST_NAV_DISABLED);for(x=0;x<y._navBtns.prev.length;x++){y._navBtns.prev[x].removeAttribute("disabled");}y._prevEnabled=true;}}v=false;if(t.next){if(w>=y.get("numItems")){if(!y.get("isCircular")){e.removeListener(t.next,"click",m);g.addClass(t.next,u.DISABLED);for(x=0;x<y._navBtns.next.length;x++){y._navBtns.next[x].setAttribute("disabled","true");}y._nextEnabled=false;}else{v=!y._nextEnabled;}}else{v=!y._nextEnabled;}if(v){e.on(t.next,"click",m,y);g.removeClass(t.next,u.DISABLED);for(x=0;x<y._navBtns.next.length;x++){y._navBtns.next[x].removeAttribute("disabled");}y._nextEnabled=true;}}y.fireEvent(J,{next:y._nextEnabled,prev:y._prevEnabled});}function S(v){var w=this,t,u;if(!w._hasRendered){return;}u=w.get("numVisible");if(!r.isNumber(v)){v=Math.floor(w.get("selectedItem")/u);}t=Math.ceil(w.get("numItems")/u);w._pages.num=t;
+w._pages.cur=v;if(t>w.CONFIG.MAX_PAGER_BUTTONS){w._updatePagerMenu();}else{w._updatePagerButtons();}}function N(t,u){switch(u){case"height":return f(t,"marginTop")+f(t,"marginBottom")+f(t,"paddingTop")+f(t,"paddingBottom")+f(t,"borderTopWidth")+f(t,"borderBottomWidth");case"width":return f(t,"marginLeft")+f(t,"marginRight")+f(t,"paddingLeft")+f(t,"paddingRight")+f(t,"borderLeftWidth")+f(t,"borderRightWidth");default:break;}return f(t,u);}function A(u){var t=this;if(!r.isObject(u)){return;}switch(u.ev){case T:t._syncUiForItemAdd(u);break;case q:t._syncUiForItemRemove(u);break;case R:t._syncUiForItemReplace(u);break;case M:t._syncUiForLazyLoading(u);break;}t.fireEvent(L);}function E(w,u){var y=this,x=y.get("currentPage"),v,t=y.get("numVisible");v=parseInt(y._firstItem/t,10);if(v!=x){y.setAttributeConfig("currentPage",{value:v});y.fireEvent(j,v);}if(y.get("selectOnScroll")){if(y.get("selectedItem")!=y._selectedItem){y.set("selectedItem",y._selectedItem);}}clearTimeout(y._autoPlayTimer);delete y._autoPlayTimer;if(y.isAutoPlayOn()){y.startAutoPlay();}y.fireEvent(F,{first:y._firstItem,last:u},y);}V.getById=function(t){return U[t]?U[t].object:false;};YAHOO.extend(V,YAHOO.util.Element,{_rows:null,_cols:null,_animObj:null,_carouselEl:null,_clipEl:null,_firstItem:0,_hasFocus:false,_hasRendered:false,_isAnimationInProgress:false,_isAutoPlayInProgress:false,_itemsTable:null,_navBtns:null,_navEl:null,_nextEnabled:true,_pages:null,_pagination:null,_prevEnabled:true,_recomputeSize:true,_itemAttrCache:null,CLASSES:{BUTTON:"yui-carousel-button",CAROUSEL:"yui-carousel",CAROUSEL_EL:"yui-carousel-element",CONTAINER:"yui-carousel-container",CONTENT:"yui-carousel-content",DISABLED:"yui-carousel-button-disabled",FIRST_NAV:" yui-carousel-first-button",FIRST_NAV_DISABLED:"yui-carousel-first-button-disabled",FIRST_PAGE:"yui-carousel-nav-first-page",FOCUSSED_BUTTON:"yui-carousel-button-focus",HORIZONTAL:"yui-carousel-horizontal",ITEM_LOADING:"yui-carousel-item-loading",MIN_WIDTH:"yui-carousel-min-width",NAVIGATION:"yui-carousel-nav",NEXT_NAV:" yui-carousel-next-button",NEXT_PAGE:"yui-carousel-next",NAV_CONTAINER:"yui-carousel-buttons",PAGER_ITEM:"yui-carousel-pager-item",PAGINATION:"yui-carousel-pagination",PAGE_FOCUS:"yui-carousel-nav-page-focus",PREV_PAGE:"yui-carousel-prev",ITEM:"yui-carousel-item",SELECTED_ITEM:"yui-carousel-item-selected",SELECTED_NAV:"yui-carousel-nav-page-selected",VERTICAL:"yui-carousel-vertical",MULTI_ROW:"yui-carousel-multi-row",ROW:"yui-carousel-row",VERTICAL_CONTAINER:"yui-carousel-vertical-container",VISIBLE:"yui-carousel-visible"},CONFIG:{FIRST_VISIBLE:0,HORZ_MIN_WIDTH:180,MAX_PAGER_BUTTONS:5,VERT_MIN_WIDTH:115,NUM_VISIBLE:3},STRINGS:{ITEM_LOADING_CONTENT:"Loading",NEXT_BUTTON_TEXT:"Next Page",PAGER_PREFIX_TEXT:"Go to page ",PREVIOUS_BUTTON_TEXT:"Previous Page"},addItem:function(AA,u){var z=this,w,v,t,AB=0,y,x=z.get("numItems");if(!AA){return false;}if(r.isString(AA)||AA.nodeName){v=AA.nodeName?AA.innerHTML:AA;}else{if(r.isObject(AA)){v=AA.content;}else{return false;}}w=z.CLASSES.ITEM+(AA.className?" "+AA.className:"");t=AA.id?AA.id:g.generateId();if(r.isUndefined(u)){z._itemsTable.items.push({item:v,className:w,id:t});y=z._itemsTable.items.length-1;}else{if(u<0||u>x){return false;}if(!z._itemsTable.items[u]){z._itemsTable.items[u]=undefined;AB=1;}z._itemsTable.items.splice(u,AB,{item:v,className:w,id:t});}z._itemsTable.numItems++;if(x<z._itemsTable.items.length){z.set("numItems",z._itemsTable.items.length);}z.fireEvent(T,{pos:u,ev:T,newPos:y});return true;},addItems:function(t){var u,w,v=true;if(!r.isArray(t)){return false;}a=false;for(u=0,w=t.length;u<w;u++){if(this.addItem(t[u][0],t[u][1])===false){v=false;}}a=true;this._syncUiItems();return v;},blur:function(){this._carouselEl.blur();this.fireEvent(B);},clearItems:function(){var t=this,u=t.get("numItems");while(u>0){if(!t.removeItem(0)){}if(t._itemsTable.numItems===0){t.set("numItems",0);break;}u--;}t.fireEvent(i);},focus:function(){var AC=this,x,y,z,w,AB,AD,u,v,t;if(!AC._hasRendered){return;}if(AC.isAnimating()){return;}t=AC.get("selectedItem");AD=AC.get("numVisible");u=AC.get("selectOnScroll");v=(t>=0)?AC.getItem(t):null;x=AC.get("firstVisible");AB=x+AD-1;z=(t<x||t>AB);y=(v&&v.id)?g.get(v.id):null;w=AC._itemsTable;if(!u&&z){y=(w&&w.items&&w.items[x])?g.get(w.items[x].id):null;}if(y){try{y.focus();}catch(AA){}}AC.fireEvent(Y);},hide:function(){var t=this;if(t.fireEvent(d)!==false){t.removeClass(t.CLASSES.VISIBLE);G.call(t,false);t.fireEvent(c);}},init:function(w,u){var x=this,t=w,y=false,v;if(!w){return;}x._hasRendered=false;x._navBtns={prev:[],next:[]};x._pages={el:null,num:0,cur:0};x._pagination={};x._itemAttrCache={};x._itemsTable={loading:{},numItems:0,items:[],size:0};if(r.isString(w)){w=g.get(w);}else{if(!w.nodeName){return;}}V.superclass.init.call(x,w,u);v=x.get("selectedItem");if(v>0){x.set("firstVisible",D.call(x,v));}if(w){if(!w.id){w.setAttribute("id",g.generateId());}y=x._parseCarousel(w);if(!y){x._createCarousel(t);}}else{w=x._createCarousel(t);}t=w.id;x.initEvents();if(y){x._parseCarouselItems();}if(v>0){p.call(x,v,0);}if(!u||typeof u.isVertical=="undefined"){x.set("isVertical",false);}x._parseCarouselNavigation(w);x._navEl=x._setupCarouselNavigation();U[t]={object:x};x._loadItems(Math.min(x.get("firstVisible")+x.get("numVisible"),x.get("numItems"))-1);},initAttributes:function(t){var u=this;t=t||{};V.superclass.initAttributes.call(u,t);u.setAttributeConfig("carouselEl",{validator:r.isString,value:t.carouselEl||"OL"});u.setAttributeConfig("carouselItemEl",{validator:r.isString,value:t.carouselItemEl||"LI"});u.setAttributeConfig("currentPage",{readOnly:true,value:0});u.setAttributeConfig("firstVisible",{method:u._setFirstVisible,validator:u._validateFirstVisible,value:t.firstVisible||u.CONFIG.FIRST_VISIBLE});u.setAttributeConfig("selectOnScroll",{validator:r.isBoolean,value:t.selectOnScroll||true});u.setAttributeConfig("numVisible",{setter:u._numVisibleSetter,method:u._setNumVisible,validator:u._validateNumVisible,value:t.numVisible||u.CONFIG.NUM_VISIBLE});
+u.setAttributeConfig("numItems",{method:u._setNumItems,validator:u._validateNumItems,value:u._itemsTable.numItems});u.setAttributeConfig("scrollIncrement",{validator:u._validateScrollIncrement,value:t.scrollIncrement||1});u.setAttributeConfig("selectedItem",{setter:u._selectedItemSetter,method:u._setSelectedItem,validator:r.isNumber,value:-1});u.setAttributeConfig("revealAmount",{method:u._setRevealAmount,validator:u._validateRevealAmount,value:t.revealAmount||0});u.setAttributeConfig("isCircular",{validator:r.isBoolean,value:t.isCircular||false});u.setAttributeConfig("isVertical",{method:u._setOrientation,validator:r.isBoolean,value:t.isVertical||false});u.setAttributeConfig("navigation",{method:u._setNavigation,validator:u._validateNavigation,value:t.navigation||{prev:null,next:null,page:null}});u.setAttributeConfig("animation",{validator:u._validateAnimation,value:t.animation||{speed:0,effect:null}});u.setAttributeConfig("autoPlay",{validator:r.isNumber,value:t.autoPlay||0});u.setAttributeConfig("autoPlayInterval",{validator:r.isNumber,value:t.autoPlayInterval||0});u.setAttributeConfig("numPages",{readOnly:true,getter:u._getNumPages});u.setAttributeConfig("lastVisible",{readOnly:true,getter:u._getLastVisible});},initEvents:function(){var v=this,u=v.CLASSES,t;v.on("keydown",v._keyboardEventHandler);v.on(F,n);v.on(T,A);v.on(q,A);v.on(R,A);v.on(C,v._focusHandler);v.on(M,A);v.on(i,function(w){v.scrollTo(0);n.call(v);S.call(v);});v.on(j,S,v);v.on(I,function(w){if(v.get("selectedItem")===null||v.get("selectedItem")<=0){v.set("selectedItem",v.get("firstVisible"));}n.call(v,w);S.call(v,w);v._setClipContainerSize();v.show();});v.on("selectedItemChange",function(w){p.call(v,w.newValue,w.prevValue);if(w.newValue>=0){v._updateTabIndex(v.getElementForItem(w.newValue));}v.fireEvent(C,w.newValue);});v.on(L,function(w){n.call(v,w);S.call(v,w);});v.on("firstVisibleChange",function(w){if(!v.get("selectOnScroll")){if(w.newValue>=0){v._updateTabIndex(v.getElementForItem(w.newValue));}}});v.on("click",function(w){if(v.isAutoPlayOn()){v.stopAutoPlay();}v._itemClickHandler(w);v._pagerClickHandler(w);});e.onFocus(v.get("element"),function(w,y){var x=e.getTarget(w);if(x&&x.nodeName.toUpperCase()=="A"&&g.getAncestorByClassName(x,u.NAVIGATION)){if(t){g.removeClass(t,u.PAGE_FOCUS);}t=x.parentNode;g.addClass(t,u.PAGE_FOCUS);}else{if(t){g.removeClass(t,u.PAGE_FOCUS);}}y._hasFocus=true;y._updateNavButtons(e.getTarget(w),true);},v);e.onBlur(v.get("element"),function(w,x){x._hasFocus=false;x._updateNavButtons(e.getTarget(w),false);},v);},isAnimating:function(){return this._isAnimationInProgress;},isAutoPlayOn:function(){return this._isAutoPlayInProgress;},getElementForItem:function(t){var u=this;if(t<0||t>=u.get("numItems")){return null;}if(u._itemsTable.items[t]){return g.get(u._itemsTable.items[t].id);}return null;},getElementForItems:function(){var v=this,u=[],t;for(t=0;t<v._itemsTable.numItems;t++){u.push(v.getElementForItem(t));}return u;},getItem:function(t){var u=this;if(t<0||t>=u.get("numItems")){return null;}if(u._itemsTable.items.length>t){if(!r.isUndefined(u._itemsTable.items[t])){return u._itemsTable.items[t];}}return null;},getItems:function(){return this._itemsTable.items;},getLoadingItems:function(){return this._itemsTable.loading;},getRows:function(){return this._rows;},getCols:function(){return this._cols;},getItemPositionById:function(y){var w=this,x=w.get("numItems"),u=0,t=w._itemsTable.items,v;while(u<x){v=t[u]||{};if(v.id==y){return u;}u++;}return -1;},getVisibleItems:function(){var v=this,t=v.get("firstVisible"),w=t+v.get("numVisible"),u=[];while(t<w){u.push(v.getElementForItem(t));t++;}return u;},removeItem:function(v){var x=this,t=x._itemsTable,w,u=x.get("numItems");if(v<0||v>=u){return false;}w=t.items.splice(v,1);if(w&&w.length==1){if(t.numItems){t.numItems--;}x.set("numItems",u-1);x.fireEvent(q,{item:w[0],pos:v,ev:q});return true;}return false;},replaceItem:function(AB,w){var AA=this,y,x,v,z=AA.get("numItems"),u,t=AB;if(!AB){return false;}if(r.isString(AB)||AB.nodeName){x=AB.nodeName?AB.innerHTML:AB;}else{if(r.isObject(AB)){x=AB.content;}else{return false;}}if(r.isUndefined(w)){return false;}else{if(w<0||w>=z){return false;}u=AA._itemsTable.items[w];if(!u){u=AA._itemsTable.loading[w];AA._itemsTable.items[w]=undefined;}v=u.id||g.generateId();AA._itemsTable.items.splice(w,1,{item:x,className:AA.CLASSES.ITEM+(AB.className?" "+AB.className:""),id:v});t=AA._itemsTable.items[w];}AA.fireEvent(R,{newItem:t,oldItem:u,pos:w,ev:R});return true;},replaceItems:function(t){var u,w,v=true;if(!r.isArray(t)){return false;}a=false;for(u=0,w=t.length;u<w;u++){if(this.replaceItem(t[u][0],t[u][1])===false){v=false;}}a=true;this._syncUiItems();return v;},render:function(u){var w=this,t=w.CLASSES,v=w._rows;w.addClass(t.CAROUSEL);if(!w._clipEl){w._clipEl=w._createCarouselClip();w._clipEl.appendChild(w._carouselEl);}if(u){w.appendChild(w._clipEl);w.appendTo(u);}else{if(!g.inDocument(w.get("element"))){return false;}w.appendChild(w._clipEl);}if(v){g.addClass(w._clipEl,t.MULTI_ROW);}if(w.get("isVertical")){w.addClass(t.VERTICAL);}else{w.addClass(t.HORIZONTAL);}if(w.get("numItems")<1){return false;}w._refreshUi();return true;},scrollBackward:function(){var t=this;t.scrollTo(t._firstItem-t.get("scrollIncrement"));},scrollForward:function(){var t=this;t.scrollTo(t._firstItem+t.get("scrollIncrement"));},scrollPageBackward:function(){var v=this,w=v.get("isVertical"),u=v._cols,x=v.get("firstVisible"),t=x-v.get("numVisible");if(t<0){if(u){t=x-u;}}v.scrollTo(t);},scrollPageForward:function(){var u=this,t=u._firstItem+u.get("numVisible");if(t>u.get("numItems")){t=0;}if(u.get("selectOnScroll")){u._selectedItem=u._getSelectedItem(t);}u.scrollTo(t);},scrollTo:function(AK,AH){var AG=this,v,AI,AA,AC,AL,AM,AN,AD,AB,w,AF,t,x,u,y,AE,z,AO,AJ=AG._itemsTable;if(AJ.numItems===0||AK==AG._firstItem||AG.isAnimating()){return;}AI=AG.get("animation");AA=AG.get("isCircular");AC=AG.get("isVertical");AB=AG._cols;w=AG._rows;AN=AG._firstItem;AF=AG.get("numItems");
+t=AG.get("numVisible");u=AG.get("currentPage");AO=function(){if(AG.isAutoPlayOn()){AG.stopAutoPlay();}};if(AK<0){if(AA){if(AF%t!==0){AK=AF+(AF%t)-t-1;}else{AK=AF+AK;}}else{AO.call(AG);return;}}else{if(AF>0&&AK>AF-1){if(AG.get("isCircular")){AK=AF-AK;}else{AO.call(AG);return;}}}if(isNaN(AK)){return;}AM=(AG._firstItem>AK)?"backward":"forward";AE=AN+t;AE=(AE>AF-1)?AF-1:AE;y=AG.fireEvent(k,{dir:AM,first:AN,last:AE});if(y===false){return;}AG.fireEvent(K,{page:u});AD=AK+t-1;AG._loadItems(AD>AF-1?AF-1:AD);AL=0-AK;if(w){if(AC){AL=parseInt(AL/AB,10);}else{AL=parseInt(AL/w,10);}}AG._firstItem=AK;AG.set("firstVisible",AK);if(!AH&&AG.get("selectOnScroll")){AG._selectedItem=AK;}AE=AK+t;AE=(AE>AF-1)?AF-1:AE;x=l.call(AG,AL);v=AI.speed>0;if(v){AG._animateAndSetCarouselOffset(x,AK,AE,AH);}else{AG._setCarouselOffset(x);E.call(AG,AK,AE);}},getPageForItem:function(t){return Math.ceil((t+1)/parseInt(this.get("numVisible"),10));},getFirstVisibleOnPage:function(t){return(t-1)*this.get("numVisible");},selectPreviousItem:function(){var v=this,u=0,t=v.get("selectedItem");if(t==v._firstItem){u=t-v.get("numVisible");v._selectedItem=v._getSelectedItem(t-1);v.scrollTo(u,true);}else{u=v.get("selectedItem")-v.get("scrollIncrement");v.set("selectedItem",v._getSelectedItem(u));}},selectNextItem:function(){var u=this,t=0;t=u.get("selectedItem")+u.get("scrollIncrement");u.set("selectedItem",u._getSelectedItem(t));},show:function(){var u=this,t=u.CLASSES;if(u.fireEvent(Z)!==false){u.addClass(t.VISIBLE);G.call(u);u.fireEvent(W);}},startAutoPlay:function(){var t=this,u;if(r.isUndefined(t._autoPlayTimer)){u=t.get("autoPlayInterval");if(u<=0){return;}t._isAutoPlayInProgress=true;t.fireEvent(b);t._autoPlayTimer=setTimeout(function(){t._autoScroll();},u);}},stopAutoPlay:function(){var t=this;if(!r.isUndefined(t._autoPlayTimer)){clearTimeout(t._autoPlayTimer);delete t._autoPlayTimer;t._isAutoPlayInProgress=false;t.fireEvent(s);}},updatePagination:function(){var AB=this,z=AB._pagination;if(!z.el){return false;}var y=AB.get("numItems"),AC=AB.get("numVisible"),w=AB.get("firstVisible")+1,x=AB.get("currentPage")+1,t=AB.get("numPages"),v={"numVisible":AC,"numPages":t,"numItems":y,"selectedItem":AB.get("selectedItem")+1,"currentPage":x,"firstVisible":w,"lastVisible":AB.get("lastVisible")+1},u=z.callback||{},AA=u.scope&&u.obj?u.obj:AB;z.el.innerHTML=r.isFunction(u.fn)?u.fn.apply(AA,[z.template,v]):YAHOO.lang.substitute(z.template,v);},registerPagination:function(u,w,t){var v=this;v._pagination.template=u;v._pagination.callback=t||{};if(!v._pagination.el){v._pagination.el=X("DIV",{className:v.CLASSES.PAGINATION});if(w=="before"){v._navEl.insertBefore(v._pagination.el,v._navEl.firstChild);}else{v._navEl.appendChild(v._pagination.el);}v.on("itemSelected",v.updatePagination);v.on("pageChange",v.updatePagination);}v.updatePagination();},toString:function(){return Q+(this.get?" (#"+this.get("id")+")":"");},_animateAndSetCarouselOffset:function(y,w,u){var x=this,v=x.get("animation"),t=null;if(x.get("isVertical")){t=new YAHOO.util.Motion(x._carouselEl,{top:{to:y}},v.speed,v.effect);}else{t=new YAHOO.util.Motion(x._carouselEl,{left:{to:y}},v.speed,v.effect);}x._isAnimationInProgress=true;t.onComplete.subscribe(x._animationCompleteHandler,{scope:x,item:w,last:u});t.animate();},_animationCompleteHandler:function(t,u,v){v.scope._isAnimationInProgress=false;E.call(v.scope,v.item,v.last);},_autoScroll:function(){var u=this,v=u._firstItem,t;if(v>=u.get("numItems")-1){if(u.get("isCircular")){t=0;}else{u.stopAutoPlay();}}else{t=v+u.get("numVisible");}u._selectedItem=u._getSelectedItem(t);u.scrollTo.call(u,t);},_createCarousel:function(u){var w=this,t=w.CLASSES,v=g.get(u);if(!v){v=X("DIV",{className:t.CAROUSEL,id:u});}if(!w._carouselEl){w._carouselEl=X(w.get("carouselEl"),{className:t.CAROUSEL_EL});}return v;},_createCarouselClip:function(){return X("DIV",{className:this.CLASSES.CONTENT});},_createCarouselItem:function(v){var t,u=this;return X(u.get("carouselItemEl"),{className:v.className,styles:{},content:v.content,id:v.id});},_getValidIndex:function(v){var y=this,t=y.get("isCircular"),w=y.get("numItems"),x=y.get("numVisible"),u=w-1;if(v<0){v=t?Math.ceil(w/x)*x+v:0;}else{if(v>u){v=t?0:u;}}return v;},_getSelectedItem:function(x){var w=this,t=w.get("isCircular"),v=w.get("numItems"),u=v-1;if(x<0){if(t){x=v+x;}else{x=w.get("selectedItem");}}else{if(x>u){if(t){x=x-v;}else{x=w.get("selectedItem");}}}return x;},_focusHandler:function(){var t=this;if(t._hasFocus){t.focus();}},_itemClickHandler:function(x){var AA=this,y=AA.get("carouselItemEl"),u=AA.get("element"),v,w,z=e.getTarget(x),t=z.tagName.toUpperCase();if(t==="INPUT"||t==="SELECT"||t==="TEXTAREA"){return;}while(z&&z!=u&&z.id!=AA._carouselEl){v=z.nodeName;if(v.toUpperCase()==y){break;}z=z.parentNode;}if((w=AA.getItemPositionById(z.id))>=0){AA.set("selectedItem",AA._getSelectedItem(w));AA.focus();}},_keyboardEventHandler:function(v){var x=this,u=e.getCharCode(v),w=e.getTarget(v),t=false;if(x.isAnimating()||w.tagName.toUpperCase()==="SELECT"){return;}switch(u){case 37:case 38:x.selectPreviousItem();t=true;break;case 39:case 40:x.selectNextItem();t=true;break;case 33:x.scrollPageBackward();t=true;break;case 34:x.scrollPageForward();t=true;break;}if(t){if(x.isAutoPlayOn()){x.stopAutoPlay();}e.preventDefault(v);}},_loadItems:function(v){var y=this,u=y.get("numItems"),w=y.get("numVisible"),x=y.get("revealAmount"),z=y._itemsTable.items.length,t=y.get("lastVisible");if(z>v&&v+1>=w){z=v%w||v==t?v-v%w:v-w+1;}if(x&&v<u-1){v++;}if(v>=z&&(!y.getItem(z)||!y.getItem(v))){y.fireEvent(M,{ev:M,first:z,last:v,num:v-z+1});}},_pagerChangeHandler:function(u){var x=this,w=e.getTarget(u),v=w.value,t;if(v){t=x.getFirstVisibleOnPage(v);x._selectedItem=t;x.scrollTo(t);x.focus();}},_pagerClickHandler:function(z){var AB=this,v=AB.CLASSES,w=e.getTarget(z),u=w.nodeName.toUpperCase(),t,y,x,AA;if(g.hasClass(w,v.PAGER_ITEM)||g.hasClass(w.parentNode,v.PAGER_ITEM)){if(u=="EM"){w=w.parentNode;}t=w.href;y=t.lastIndexOf("#");x=parseInt(t.substring(y+1),10);
+if(x!=-1){AA=AB.getFirstVisibleOnPage(x);AB._selectedItem=AA;AB.scrollTo(AA);AB.focus();}e.preventDefault(z);}},_parseCarousel:function(v){var y=this,z,t,u,x,w;t=y.CLASSES;u=y.get("carouselEl");x=false;for(z=v.firstChild;z;z=z.nextSibling){if(z.nodeType==1){w=z.nodeName;if(w.toUpperCase()==u){y._carouselEl=z;g.addClass(y._carouselEl,y.CLASSES.CAROUSEL_EL);x=true;}}}return x;},_parseCarouselItems:function(){var AA=this,AC=AA.CLASSES,x=0,AB,t,v,w,u,y=AA.get("firstVisible"),z=AA._carouselEl;AB=AA._rows;v=AA.get("carouselItemEl");for(t=z.firstChild;t;t=t.nextSibling){if(t.nodeType==1){u=t.nodeName;if(u.toUpperCase()==v){if(t.id){w=t.id;}else{w=g.generateId();t.setAttribute("id",w);g.addClass(t,AA.CLASSES.ITEM);}AA.addItem(t,y);y++;}}}},_parseCarouselNavigation:function(z){var AA=this,y,AB=AA.CLASSES,u,x,w,t,v=false;t=g.getElementsByClassName(AB.PREV_PAGE,"*",z);if(t.length>0){for(x in t){if(t.hasOwnProperty(x)){u=t[x];if(u.nodeName=="INPUT"||u.nodeName=="BUTTON"||u.nodeName=="A"){AA._navBtns.prev.push(u);}else{w=u.getElementsByTagName("INPUT");if(r.isArray(w)&&w.length>0){AA._navBtns.prev.push(w[0]);}else{w=u.getElementsByTagName("BUTTON");if(r.isArray(w)&&w.length>0){AA._navBtns.prev.push(w[0]);}}}}}y={prev:t};}t=g.getElementsByClassName(AB.NEXT_PAGE,"*",z);if(t.length>0){for(x in t){if(t.hasOwnProperty(x)){u=t[x];if(u.nodeName=="INPUT"||u.nodeName=="BUTTON"||u.nodeName=="A"){AA._navBtns.next.push(u);}else{w=u.getElementsByTagName("INPUT");if(r.isArray(w)&&w.length>0){AA._navBtns.next.push(w[0]);}else{w=u.getElementsByTagName("BUTTON");if(r.isArray(w)&&w.length>0){AA._navBtns.next.push(w[0]);}}}}}if(y){y.next=t;}else{y={next:t};}}if(y){AA.set("navigation",y);v=true;}return v;},_refreshUi:function(){var x=this,y=x.get("isVertical"),AA=x.get("firstVisible"),u,v,z,t,w;if(x._itemsTable.numItems<1){return;}w=P.call(x,y?"height":"width");v=x._itemsTable.items[AA].id;w=y?f(v,"width"):f(v,"height");g.setStyle(x._carouselEl,y?"width":"height",w+"px");x._hasRendered=true;x.fireEvent(I);},_setCarouselOffset:function(v){var t=this,u;u=t.get("isVertical")?"top":"left";g.setStyle(t._carouselEl,u,v+"px");},_setupCarouselNavigation:function(){var y=this,w,u,t,AA,x,z,v;t=y.CLASSES;x=g.getElementsByClassName(t.NAVIGATION,"DIV",y.get("element"));if(x.length===0){x=X("DIV",{className:t.NAVIGATION});y.insertBefore(x,g.getFirstChild(y.get("element")));}else{x=x[0];}y._pages.el=X("UL");x.appendChild(y._pages.el);AA=y.get("navigation");if(r.isString(AA.prev)||r.isArray(AA.prev)){if(r.isString(AA.prev)){AA.prev=[AA.prev];}for(w in AA.prev){if(AA.prev.hasOwnProperty(w)){y._navBtns.prev.push(g.get(AA.prev[w]));}}}else{v=X("SPAN",{className:t.BUTTON+t.FIRST_NAV});g.setStyle(v,"visibility","visible");w=g.generateId();v.innerHTML='<button type="button" '+'id="'+w+'" name="'+y.STRINGS.PREVIOUS_BUTTON_TEXT+'">'+y.STRINGS.PREVIOUS_BUTTON_TEXT+"</button>";x.appendChild(v);w=g.get(w);y._navBtns.prev=[w];u={prev:[v]};}if(r.isString(AA.next)||r.isArray(AA.next)){if(r.isString(AA.next)){AA.next=[AA.next];}for(w in AA.next){if(AA.next.hasOwnProperty(w)){y._navBtns.next.push(g.get(AA.next[w]));}}}else{z=X("SPAN",{className:t.BUTTON+t.NEXT_NAV});g.setStyle(z,"visibility","visible");w=g.generateId();z.innerHTML='<button type="button" '+'id="'+w+'" name="'+y.STRINGS.NEXT_BUTTON_TEXT+'">'+y.STRINGS.NEXT_BUTTON_TEXT+"</button>";x.appendChild(z);w=g.get(w);y._navBtns.next=[w];if(u){u.next=[z];}else{u={next:[z]};}}if(u){y.set("navigation",u);}return x;},_setClipContainerSize:function(t,v){var AB=this,z=AB.get("isVertical"),AD=AB._rows,x=AB._cols,AA=AB.get("revealAmount"),u=P.call(AB,"height"),w=P.call(AB,"width"),AC,y;AB._recomputeSize=(AC===0);if(AB._recomputeSize){AB._hasRendered=false;return;}t=t||AB._clipEl;if(AD){AC=u*AD;y=w*x;}else{v=v||AB.get("numVisible");if(z){AC=u*v;}else{y=w*v;}}AA=O.call(AB);if(z){AC+=(AA*2);}else{y+=(AA*2);}if(z){AC+=N(AB._carouselEl,"height");g.setStyle(t,"height",AC+"px");if(x){y+=N(AB._carouselEl,"width");g.setStyle(t,"width",y+(0)+"px");}}else{y+=N(AB._carouselEl,"width");g.setStyle(t,"width",y+"px");if(AD){AC+=N(AB._carouselEl,"height");g.setStyle(t,"height",AC+"px");}}if(t){AB._setContainerSize(t);}},_setContainerSize:function(u,v){var y=this,t=y.CONFIG,AB=y.CLASSES,x,AA,w,z;x=y.get("isVertical");AA=y._rows;w=y._cols;u=u||y._clipEl;v=v||(x?"height":"width");z=parseFloat(g.getStyle(u,v),10);z=r.isNumber(z)?z:0;if(x){z+=N(y._carouselEl,"height")+f(y._navEl,"height");}else{z+=N(y._carouselEl,"width");}if(!x){if(z<t.HORZ_MIN_WIDTH){z=t.HORZ_MIN_WIDTH;y.addClass(AB.MIN_WIDTH);}}y.setStyle(v,z+"px");if(x){z=P.call(y,"width");if(w){z=z*w;}g.setStyle(y._carouselEl,"width",z+"px");if(z<t.VERT_MIN_WIDTH){z=t.VERT_MIN_WIDTH;y.addClass(AB.MIN_WIDTH);}y.setStyle("width",z+"px");}else{z=P.call(y,"height");if(AA){z=z*AA;}g.setStyle(y._carouselEl,"height",z+"px");}},_setFirstVisible:function(u){var t=this;if(u>=0&&u<t.get("numItems")){t.scrollTo(u);}else{u=t.get("firstVisible");}return u;},_setNavigation:function(t){var u=this;if(t.prev){e.on(t.prev,"click",h,u);}if(t.next){e.on(t.next,"click",m,u);}},_setNumVisible:function(u){var t=this;t._setClipContainerSize(t._clipEl,u);},_numVisibleSetter:function(v){var u=this,t=v;if(r.isArray(v)){u._cols=v[0];u._rows=v[1];t=v[0]*v[1];}return t;},_selectedItemSetter:function(u){var t=this;return(u<t.get("numItems"))?u:0;},_setNumItems:function(v){var u=this,t=u._itemsTable.numItems;if(r.isArray(u._itemsTable.items)){if(u._itemsTable.items.length!=t){t=u._itemsTable.items.length;u._itemsTable.numItems=t;}}if(v<t){while(t>v){u.removeItem(t-1);t--;}}return v;},_setOrientation:function(v){var u=this,t=u.CLASSES;if(v){u.replaceClass(t.HORIZONTAL,t.VERTICAL);}else{u.replaceClass(t.VERTICAL,t.HORIZONTAL);}return v;},_setRevealAmount:function(u){var t=this;if(u>=0&&u<=100){u=parseInt(u,10);u=r.isNumber(u)?u:0;t._setClipContainerSize();}else{u=t.get("revealAmount");}return u;},_setSelectedItem:function(t){this._selectedItem=t;},_getNumPages:function(){return Math.ceil(parseInt(this.get("numItems"),10)/parseInt(this.get("numVisible"),10));
+},_getLastVisible:function(){var t=this;return t.get("currentPage")+1==t.get("numPages")?t.get("numItems")-1:t.get("firstVisible")+t.get("numVisible")-1;},_syncUiForItemAdd:function(w){var x,AC=this,z=AC._carouselEl,t,AD,v=AC._itemsTable,u,y,AA,AB;y=r.isUndefined(w.pos)?w.newPos||v.numItems-1:w.pos;if(!u){AD=v.items[y]||{};t=AC._createCarouselItem({className:AD.className,styles:AD.styles,content:AD.item,id:AD.id,pos:y});if(r.isUndefined(w.pos)){if(!r.isUndefined(v.loading[y])){u=v.loading[y];}if(u){z.replaceChild(t,u);delete v.loading[y];}else{z.appendChild(t);}}else{if(!r.isUndefined(v.items[w.pos+1])){AA=g.get(v.items[w.pos+1].id);}if(AA){z.insertBefore(t,AA);}else{}}}else{if(r.isUndefined(w.pos)){if(!g.isAncestor(AC._carouselEl,u)){z.appendChild(u);}}else{if(!g.isAncestor(z,u)){if(!r.isUndefined(v.items[w.pos+1])){z.insertBefore(u,g.get(v.items[w.pos+1].id));}}}}if(!AC._hasRendered){AC._refreshUi();}if(AC.get("selectedItem")<0){AC.set("selectedItem",AC.get("firstVisible"));}AC._syncUiItems();},_syncUiForItemReplace:function(z){var y=this,v=y._carouselEl,t=y._itemsTable,AA=z.pos,x=z.newItem,u=z.oldItem,w;w=y._createCarouselItem({className:x.className,styles:x.styles,content:x.item,id:u.id});if((u=g.get(u.id))){u.className=x.className;u.styles=x.styles;u.innerHTML=x.item;t.items[AA]=w;if(t.loading[AA]){t.numItems++;delete t.loading[AA];}}},_syncUiForItemRemove:function(y){var x=this,t=x._carouselEl,v,w,u,z;u=x.get("numItems");w=y.item;z=y.pos;if(w&&(v=g.get(w.id))){if(v&&g.isAncestor(t,v)){e.purgeElement(v,true);t.removeChild(v);}if(x.get("selectedItem")==z){z=z>=u?u-1:z;}}else{}x._syncUiItems();},_findClosestSibling:function(y){var x=this,u=x._itemsTable,t=u.items.length,v=y,w;while(v<t&&!w){w=u.items[++v];}return w;},_syncUiForLazyLoading:function(x){var AD=this,AA=AD._carouselEl,v=AD._itemsTable,z=v.items.length,AC=AD._findClosestSibling(x.last),AB=x.last,y=AB-AD.get("numVisible")+1,t,u;for(var w=y;w<=AB;w++){if(!v.loading[w]&&!v.items[w]){t=AD._createCarouselItem({className:AD.CLASSES.ITEM+" "+AD.CLASSES.ITEM_LOADING,content:AD.STRINGS.ITEM_LOADING_CONTENT,id:g.generateId()});if(t){if(AC){AC=g.get(AC.id);if(AC){AA.insertBefore(t,AC);}else{}}else{AA.appendChild(t);}}v.loading[w]=t;}}AD._syncUiItems();},_syncUiItems:function(){if(!a){return;}var x,AB=this,z=AB.get("numItems"),w,v=AB._itemsTable,y=v.items,t=v.loading,AC,AA,u=false;for(w=0;w<z;w++){AC=y[w]||t[w];if(AC&&AC.id){AA=o.call(AB,w);AC.styles=AC.styles||{};for(x in AA){if(AC.styles[x]!==AA[x]){u=true;AC.styles[x]=AA[x];}}if(u){H(g.get(AC.id),AA);}u=false;}}},_updateNavButtons:function(x,u){var v,t=this.CLASSES,y,w=x.parentNode;if(!w){return;}y=w.parentNode;if(x.nodeName.toUpperCase()=="BUTTON"&&g.hasClass(w,t.BUTTON)){if(u){if(y){v=g.getChildren(y);if(v){g.removeClass(v,t.FOCUSSED_BUTTON);}}g.addClass(w,t.FOCUSSED_BUTTON);}else{g.removeClass(w,t.FOCUSSED_BUTTON);}}},_updatePagerButtons:function(){if(!a){return;}var AB=this,z=AB.CLASSES,AA=AB._pages.cur,t,y,w,AC,u=AB.get("numVisible"),x=AB._pages.num,v=AB._pages.el;if(x===0||!v){return;}g.setStyle(v,"visibility","hidden");while(v.firstChild){v.removeChild(v.firstChild);}for(w=0;w<x;w++){t=document.createElement("LI");if(w===0){g.addClass(t,z.FIRST_PAGE);}if(w==AA){g.addClass(t,z.SELECTED_NAV);}y="<a class="+z.PAGER_ITEM+' href="#'+(w+1)+'" tabindex="0"><em>'+AB.STRINGS.PAGER_PREFIX_TEXT+" "+(w+1)+"</em></a>";t.innerHTML=y;v.appendChild(t);}g.setStyle(v,"visibility","visible");},_updatePagerMenu:function(){var AB=this,z=AB.CLASSES,AA=AB._pages.cur,u,x,AC,v=AB.get("numVisible"),y=AB._pages.num,w=AB._pages.el,t;if(y===0||!w){return;}t=document.createElement("SELECT");if(!t){return;}g.setStyle(w,"visibility","hidden");while(w.firstChild){w.removeChild(w.firstChild);}for(x=0;x<y;x++){u=document.createElement("OPTION");u.value=x+1;u.innerHTML=AB.STRINGS.PAGER_PREFIX_TEXT+" "+(x+1);if(x==AA){u.setAttribute("selected","selected");}t.appendChild(u);}u=document.createElement("FORM");if(!u){}else{u.appendChild(t);w.appendChild(u);}e.addListener(t,"change",AB._pagerChangeHandler,this,true);g.setStyle(w,"visibility","visible");},_updateTabIndex:function(t){var u=this;if(t){if(u._focusableItemEl){u._focusableItemEl.tabIndex=-1;}u._focusableItemEl=t;t.tabIndex=0;}},_validateAnimation:function(t){var u=true;if(r.isObject(t)){if(t.speed){u=u&&r.isNumber(t.speed);}if(t.effect){u=u&&r.isFunction(t.effect);}else{if(!r.isUndefined(YAHOO.util.Easing)){t.effect=YAHOO.util.Easing.easeOut;}}}else{u=false;}return u;},_validateFirstVisible:function(v){var u=this,t=u.get("numItems");if(r.isNumber(v)){if(t===0&&v==t){return true;}else{return(v>=0&&v<t);}}return false;},_validateNavigation:function(t){var u;if(!r.isObject(t)){return false;}if(t.prev){if(!r.isArray(t.prev)){return false;}for(u in t.prev){if(t.prev.hasOwnProperty(u)){if(!r.isString(t.prev[u].nodeName)){return false;}}}}if(t.next){if(!r.isArray(t.next)){return false;}for(u in t.next){if(t.next.hasOwnProperty(u)){if(!r.isString(t.next[u].nodeName)){return false;}}}}return true;},_validateNumItems:function(t){return r.isNumber(t)&&(t>=0);},_validateNumVisible:function(t){var u=false;if(r.isNumber(t)){u=t>0&&t<=this.get("numItems");}else{if(r.isArray(t)){if(r.isNumber(t[0])&&r.isNumber(t[1])){u=t[0]*t[1]>0&&t.length==2;}}}return u;},_validateRevealAmount:function(t){var u=false;if(r.isNumber(t)){u=t>=0&&t<100;}return u;},_validateScrollIncrement:function(t){var u=false;if(r.isNumber(t)){u=(t>0&&t<this.get("numItems"));}return u;}});})();YAHOO.register("carousel",YAHOO.widget.Carousel,{version:"2.9.0",build:"2800"});YAHOO.register("carousel",YAHOO.widget.Carousel,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/charts/charts-min.js b/Websites/bugs.webkit.org/js/yui/charts/charts-min.js
new file mode 100644
index 0000000..40553ab
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/charts/charts-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.widget.Chart=function(d,a,j,g){this._type=d;this._dataSource=j;var f={align:"",allowNetworking:"",allowScriptAccess:"",base:"",bgcolor:"",menu:"",name:"",quality:"",salign:"",scale:"",tabindex:"",wmode:""};var b={fixedAttributes:{allowScriptAccess:"always"},flashVars:{allowedDomain:document.location.hostname},backgroundColor:"#ffffff",host:this,version:9.045};for(var c in g){if(f.hasOwnProperty(c)){b.fixedAttributes[c]=g[c];}else{b[c]=g[c];}}this._id=b.id=b.id||YAHOO.util.Dom.generateId(null,"yuigen");this._swfURL=YAHOO.widget.Chart.SWFURL;this._containerID=a;this._attributes=b;this._swfEmbed=new YAHOO.widget.SWF(a,YAHOO.widget.Chart.SWFURL,b);this._swf=this._swfEmbed.swf;this._swfEmbed.subscribe("swfReady",this._eventHandler,this,true);try{this.createEvent("contentReady");}catch(h){}this.createEvent("itemMouseOverEvent");this.createEvent("itemMouseOutEvent");this.createEvent("itemClickEvent");this.createEvent("itemDoubleClickEvent");this.createEvent("itemDragStartEvent");this.createEvent("itemDragEvent");this.createEvent("itemDragEndEvent");};YAHOO.extend(YAHOO.widget.Chart,YAHOO.util.AttributeProvider,{_type:null,_pollingID:null,_pollingInterval:null,_dataTipFunction:null,_legendLabelFunction:null,_seriesFunctions:null,toString:function(){return"Chart "+this._id;},setStyle:function(a,b){b=YAHOO.lang.JSON.stringify(b);this._swf.setStyle(a,b);},setStyles:function(a){a=YAHOO.lang.JSON.stringify(a);this._swf.setStyles(a);},setSeriesStyles:function(b){for(var a=0;a<b.length;a++){b[a]=YAHOO.lang.JSON.stringify(b[a]);}this._swf.setSeriesStyles(b);},destroy:function(){if(this._dataSource!==null){if(this._pollingID!==null){this._dataSource.clearInterval(this._pollingID);this._pollingID=null;}}if(this._dataTipFunction){YAHOO.widget.Chart.removeProxyFunction(this._dataTipFunction);}if(this._legendLabelFunction){YAHOO.widget.Chart.removeProxyFunction(this._legendLabelFunction);}if(this._swf){var b=YAHOO.util.Dom.get(this._containerID);b.removeChild(this._swf);}var a=this._id;for(var c in this){if(YAHOO.lang.hasOwnProperty(this,c)){this[c]=null;}}},_initAttributes:function(a){this.setAttributeConfig("altText",{method:this._setAltText,getter:this._getAltText});this.setAttributeConfig("swfURL",{getter:this._getSWFURL});this.setAttributeConfig("request",{method:this._setRequest,getter:this._getRequest});this.setAttributeConfig("dataSource",{method:this._setDataSource,getter:this._getDataSource});this.setAttributeConfig("series",{method:this._setSeriesDefs,getter:this._getSeriesDefs});this.setAttributeConfig("categoryNames",{validator:YAHOO.lang.isArray,method:this._setCategoryNames,getter:this._getCategoryNames});this.setAttributeConfig("dataTipFunction",{method:this._setDataTipFunction,getter:this._getDataTipFunction});this.setAttributeConfig("legendLabelFunction",{method:this._setLegendLabelFunction,getter:this._getLegendLabelFunction});this.setAttributeConfig("polling",{method:this._setPolling,getter:this._getPolling});},_eventHandler:function(a){if(a.type=="swfReady"){this._swf=this._swfEmbed._swf;this._loadHandler();this.fireEvent("contentReady");}},_loadHandler:function(){if(!this._swf||!this._swf.setType){return;}this._swf.setType(this._type);if(this._attributes.style){var a=this._attributes.style;this.setStyles(a);}this._initialized=false;this._initAttributes(this._attributes);this.setAttributes(this._attributes,true);this._initialized=true;if(this._dataSource){this.set("dataSource",this._dataSource);}},refreshData:function(){if(!this._initialized){return;}if(this._dataSource!==null){if(this._pollingID!==null){this._dataSource.clearInterval(this._pollingID);this._pollingID=null;}if(this._pollingInterval>0){this._pollingID=this._dataSource.setInterval(this._pollingInterval,this._request,this._loadDataHandler,this);}this._dataSource.sendRequest(this._request,this._loadDataHandler,this);}},_loadDataHandler:function(d,c,m){if(this._swf){if(m){}else{var j;if(this._seriesFunctions){var k=this._seriesFunctions.length;for(j=0;j<k;j++){YAHOO.widget.Chart.removeProxyFunction(this._seriesFunctions[j]);}this._seriesFunctions=null;}this._seriesFunctions=[];var g=[];var f=0;var n=null;if(this._seriesDefs!==null){f=this._seriesDefs.length;for(j=0;j<f;j++){n=this._seriesDefs[j];var b={};for(var a in n){if(YAHOO.lang.hasOwnProperty(n,a)){if(a=="style"){if(n.style!==null){b.style=YAHOO.lang.JSON.stringify(n.style);}}else{if(a=="labelFunction"){if(n.labelFunction!==null){b.labelFunction=YAHOO.widget.Chart.getFunctionReference(n.labelFunction);this._seriesFunctions.push(b.labelFunction);}}else{if(a=="dataTipFunction"){if(n.dataTipFunction!==null){b.dataTipFunction=YAHOO.widget.Chart.getFunctionReference(n.dataTipFunction);this._seriesFunctions.push(b.dataTipFunction);}}else{if(a=="legendLabelFunction"){if(n.legendLabelFunction!==null){b.legendLabelFunction=YAHOO.widget.Chart.getFunctionReference(n.legendLabelFunction);this._seriesFunctions.push(b.legendLabelFunction);}}else{b[a]=n[a];}}}}}}g.push(b);}}if(f>0){for(j=0;j<f;j++){n=g[j];if(!n.type){n.type=this._type;}n.dataProvider=c.results;}}else{var h={type:this._type,dataProvider:c.results};g.push(h);}try{if(this._swf.setDataProvider){this._swf.setDataProvider(g);}}catch(l){this._swf.setDataProvider(g);}}}},_request:"",_getRequest:function(){return this._request;},_setRequest:function(a){this._request=a;this.refreshData();},_dataSource:null,_getDataSource:function(){return this._dataSource;},_setDataSource:function(a){this._dataSource=a;this.refreshData();},_seriesDefs:null,_getSeriesDefs:function(){return this._seriesDefs;},_setSeriesDefs:function(a){this._seriesDefs=a;this.refreshData();},_getCategoryNames:function(){return this._swf.getCategoryNames();},_setCategoryNames:function(a){this._swf.setCategoryNames(a);},_setDataTipFunction:function(a){if(this._dataTipFunction){YAHOO.widget.Chart.removeProxyFunction(this._dataTipFunction);}if(a){this._dataTipFunction=a=YAHOO.widget.Chart.getFunctionReference(a);}this._swf.setDataTipFunction(a);},_setLegendLabelFunction:function(a){if(this._legendLabelFunction){YAHOO.widget.Chart.removeProxyFunction(this._legendLabelFunction);
+}if(a){this._legendLabelFunction=a=YAHOO.widget.Chart.getFunctionReference(a);}this._swf.setLegendLabelFunction(a);},_getLegendLabelFunction:function(){return this._legendLabelFunction;},_getPolling:function(){return this._pollingInterval;},_setPolling:function(a){this._pollingInterval=a;this.refreshData();},_swfEmbed:null,_swfURL:null,_containerID:null,_swf:null,_id:null,_initialized:false,_attributes:null,set:function(a,b){this._attributes[a]=b;YAHOO.widget.Chart.superclass.set.call(this,a,b);},_getSWFURL:function(){return this._swfURL;},_getAltText:function(){return this._swf.getAltText();},_setAltText:function(a){this._swf.setAltText(a);}});YAHOO.widget.Chart.proxyFunctionCount=0;YAHOO.widget.Chart.createProxyFunction=function(c,b){var b=b||null;var a=YAHOO.widget.Chart.proxyFunctionCount;YAHOO.widget.Chart["proxyFunction"+a]=function(){return c.apply(b,arguments);};YAHOO.widget.Chart.proxyFunctionCount++;return"YAHOO.widget.Chart.proxyFunction"+a.toString();};YAHOO.widget.Chart.getFunctionReference=function(b){if(typeof b=="function"){b=YAHOO.widget.Chart.createProxyFunction(b);}else{if(b.func&&typeof b.func=="function"){var a=[b.func];if(b.scope&&typeof b.scope=="object"){a.push(b.scope);}b=YAHOO.widget.Chart.createProxyFunction.apply(this,a);}}return b;};YAHOO.widget.Chart.removeProxyFunction=function(a){if(!a||a.indexOf("YAHOO.widget.Chart.proxyFunction")<0){return;}a=a.substr(26);YAHOO.widget.Chart[a]=null;};YAHOO.widget.Chart.SWFURL="assets/charts.swf";YAHOO.widget.PieChart=function(a,c,b){YAHOO.widget.PieChart.superclass.constructor.call(this,"pie",a,c,b);};YAHOO.lang.extend(YAHOO.widget.PieChart,YAHOO.widget.Chart,{_initAttributes:function(a){YAHOO.widget.PieChart.superclass._initAttributes.call(this,a);this.setAttributeConfig("dataField",{validator:YAHOO.lang.isString,method:this._setDataField,getter:this._getDataField});this.setAttributeConfig("categoryField",{validator:YAHOO.lang.isString,method:this._setCategoryField,getter:this._getCategoryField});},_getDataField:function(){return this._swf.getDataField();},_setDataField:function(a){this._swf.setDataField(a);},_getCategoryField:function(){return this._swf.getCategoryField();},_setCategoryField:function(a){this._swf.setCategoryField(a);}});YAHOO.widget.CartesianChart=function(c,a,d,b){YAHOO.widget.CartesianChart.superclass.constructor.call(this,c,a,d,b);};YAHOO.lang.extend(YAHOO.widget.CartesianChart,YAHOO.widget.Chart,{_xAxisLabelFunctions:[],_yAxisLabelFunctions:[],destroy:function(){this._removeAxisFunctions(this._xAxisLabelFunctions);this._removeAxisFunctions(this._yAxisLabelFunctions);YAHOO.widget.CartesianChart.superclass.destroy.call(this);},_initAttributes:function(a){YAHOO.widget.CartesianChart.superclass._initAttributes.call(this,a);this.setAttributeConfig("xField",{validator:YAHOO.lang.isString,method:this._setXField,getter:this._getXField});this.setAttributeConfig("yField",{validator:YAHOO.lang.isString,method:this._setYField,getter:this._getYField});this.setAttributeConfig("xAxis",{method:this._setXAxis});this.setAttributeConfig("xAxes",{method:this._setXAxes});this.setAttributeConfig("yAxis",{method:this._setYAxis});this.setAttributeConfig("yAxes",{method:this._setYAxes});this.setAttributeConfig("constrainViewport",{method:this._setConstrainViewport});},_getXField:function(){return this._swf.getHorizontalField();},_setXField:function(a){this._swf.setHorizontalField(a);},_getYField:function(){return this._swf.getVerticalField();},_setYField:function(a){this._swf.setVerticalField(a);},_getClonedAxis:function(a){var b={};for(var c in a){if(c=="labelFunction"){if(a.labelFunction&&a.labelFunction!==null){b.labelFunction=YAHOO.widget.Chart.getFunctionReference(a.labelFunction);}}else{b[c]=a[c];}}return b;},_removeAxisFunctions:function(c){if(c&&c.length>0){var a=c.length;for(var b=0;b<a;b++){if(c[b]!==null){YAHOO.widget.Chart.removeProxyFunction(c[b]);}}c=[];}},_setXAxis:function(a){if(a.position!="bottom"&&a.position!="top"){a.position="bottom";}this._removeAxisFunctions(this._xAxisLabelFunctions);a=this._getClonedAxis(a);this._xAxisLabelFunctions.push(a.labelFunction);this._swf.setHorizontalAxis(a);},_setXAxes:function(c){this._removeAxisFunctions(this._xAxisLabelFunctions);var a=c.length;for(var b=0;b<a;b++){if(c[b].position=="left"){c[b].position="bottom";}c[b]=this._getClonedAxis(c[b]);if(c[b].labelFunction){this._xAxisLabelFunctions.push(c[b].labelFunction);}this._swf.setHorizontalAxis(c[b]);}},_setYAxis:function(a){this._removeAxisFunctions(this._yAxisLabelFunctions);a=this._getClonedAxis(a);this._yAxisLabelFunctions.push(a.labelFunction);this._swf.setVerticalAxis(a);},_setYAxes:function(c){this._removeAxisFunctions(this._yAxisLabelFunctions);var a=c.length;for(var b=0;b<a;b++){c[b]=this._getClonedAxis(c[b]);if(c[b].labelFunction){this._yAxisLabelFunctions.push(c[b].labelFunction);}this._swf.setVerticalAxis(c[b]);}},_setConstrainViewport:function(a){this._swf.setConstrainViewport(a);},setSeriesStylesByIndex:function(a,b){b=YAHOO.lang.JSON.stringify(b);if(this._swf&&this._swf.setSeriesStylesByIndex){this._swf.setSeriesStylesByIndex(a,b);}}});YAHOO.widget.LineChart=function(a,c,b){YAHOO.widget.LineChart.superclass.constructor.call(this,"line",a,c,b);};YAHOO.lang.extend(YAHOO.widget.LineChart,YAHOO.widget.CartesianChart);YAHOO.widget.ColumnChart=function(a,c,b){YAHOO.widget.ColumnChart.superclass.constructor.call(this,"column",a,c,b);};YAHOO.lang.extend(YAHOO.widget.ColumnChart,YAHOO.widget.CartesianChart);YAHOO.widget.BarChart=function(a,c,b){YAHOO.widget.BarChart.superclass.constructor.call(this,"bar",a,c,b);};YAHOO.lang.extend(YAHOO.widget.BarChart,YAHOO.widget.CartesianChart);YAHOO.widget.StackedColumnChart=function(a,c,b){YAHOO.widget.StackedColumnChart.superclass.constructor.call(this,"stackcolumn",a,c,b);};YAHOO.lang.extend(YAHOO.widget.StackedColumnChart,YAHOO.widget.CartesianChart);YAHOO.widget.StackedBarChart=function(a,c,b){YAHOO.widget.StackedBarChart.superclass.constructor.call(this,"stackbar",a,c,b);
+};YAHOO.lang.extend(YAHOO.widget.StackedBarChart,YAHOO.widget.CartesianChart);YAHOO.widget.Axis=function(){};YAHOO.widget.Axis.prototype={type:null,reverse:false,labelFunction:null,labelSpacing:2,title:null};YAHOO.widget.NumericAxis=function(){YAHOO.widget.NumericAxis.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.NumericAxis,YAHOO.widget.Axis,{type:"numeric",minimum:NaN,maximum:NaN,majorUnit:NaN,minorUnit:NaN,snapToUnits:true,stackingEnabled:false,alwaysShowZero:true,scale:"linear",roundMajorUnit:true,calculateByLabelSize:true,position:"left",adjustMaximumByMajorUnit:true,adjustMinimumByMajorUnit:true});YAHOO.widget.TimeAxis=function(){YAHOO.widget.TimeAxis.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.TimeAxis,YAHOO.widget.Axis,{type:"time",minimum:null,maximum:null,majorUnit:NaN,majorTimeUnit:null,minorUnit:NaN,minorTimeUnit:null,snapToUnits:true,stackingEnabled:false,calculateByLabelSize:true});YAHOO.widget.CategoryAxis=function(){YAHOO.widget.CategoryAxis.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.CategoryAxis,YAHOO.widget.Axis,{type:"category",categoryNames:null,calculateCategoryCount:false});YAHOO.widget.Series=function(){};YAHOO.widget.Series.prototype={type:null,displayName:null};YAHOO.widget.CartesianSeries=function(){YAHOO.widget.CartesianSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.CartesianSeries,YAHOO.widget.Series,{xField:null,yField:null,axis:"primary",showInLegend:true});YAHOO.widget.ColumnSeries=function(){YAHOO.widget.ColumnSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.ColumnSeries,YAHOO.widget.CartesianSeries,{type:"column"});YAHOO.widget.LineSeries=function(){YAHOO.widget.LineSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.LineSeries,YAHOO.widget.CartesianSeries,{type:"line"});YAHOO.widget.BarSeries=function(){YAHOO.widget.BarSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.BarSeries,YAHOO.widget.CartesianSeries,{type:"bar"});YAHOO.widget.PieSeries=function(){YAHOO.widget.PieSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.PieSeries,YAHOO.widget.Series,{type:"pie",dataField:null,categoryField:null,labelFunction:null});YAHOO.widget.StackedBarSeries=function(){YAHOO.widget.StackedBarSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.StackedBarSeries,YAHOO.widget.CartesianSeries,{type:"stackbar"});YAHOO.widget.StackedColumnSeries=function(){YAHOO.widget.StackedColumnSeries.superclass.constructor.call(this);};YAHOO.lang.extend(YAHOO.widget.StackedColumnSeries,YAHOO.widget.CartesianSeries,{type:"stackcolumn"});YAHOO.register("charts",YAHOO.widget.Chart,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/colorpicker/colorpicker-min.js b/Websites/bugs.webkit.org/js/yui/colorpicker/colorpicker-min.js
new file mode 100644
index 0000000..dadac2a
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/colorpicker/colorpicker-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.Color=function(){var a="0",b=YAHOO.lang.isArray,c=YAHOO.lang.isNumber;return{real2dec:function(d){return Math.min(255,Math.round(d*256));},hsv2rgb:function(l,y,w){if(b(l)){return this.hsv2rgb.call(this,l[0],l[1],l[2]);}var d,m,u,k=Math.floor((l/60)%6),n=(l/60)-k,j=w*(1-y),e=w*(1-n*y),x=w*(1-(1-n)*y),o;switch(k){case 0:d=w;m=x;u=j;break;case 1:d=e;m=w;u=j;break;case 2:d=j;m=w;u=x;break;case 3:d=j;m=e;u=w;break;case 4:d=x;m=j;u=w;break;case 5:d=w;m=j;u=e;break;}o=this.real2dec;return[o(d),o(m),o(u)];},rgb2hsv:function(d,j,k){if(b(d)){return this.rgb2hsv.apply(this,d);}d/=255;j/=255;k/=255;var i,n,e=Math.min(Math.min(d,j),k),l=Math.max(Math.max(d,j),k),m=l-e,f;switch(l){case e:i=0;break;case d:i=60*(j-k)/m;if(j<k){i+=360;}break;case j:i=(60*(k-d)/m)+120;break;case k:i=(60*(d-j)/m)+240;break;}n=(l===0)?0:1-(e/l);f=[Math.round(i),n,l];return f;},rgb2hex:function(h,e,d){if(b(h)){return this.rgb2hex.apply(this,h);}var i=this.dec2hex;return i(h)+i(e)+i(d);},dec2hex:function(d){d=parseInt(d,10)|0;d=(d>255||d<0)?0:d;return(a+d.toString(16)).slice(-2).toUpperCase();},hex2dec:function(d){return parseInt(d,16);},hex2rgb:function(d){var e=this.hex2dec;return[e(d.slice(0,2)),e(d.slice(2,4)),e(d.slice(4,6))];},websafe:function(h,e,d){if(b(h)){return this.websafe.apply(this,h);}var i=function(f){if(c(f)){f=Math.min(Math.max(0,f),255);var g,j;for(g=0;g<256;g=g+51){j=g+51;if(f>=g&&f<=j){return(f-g>25)?j:g;}}}return f;};return[i(h),i(e),i(d)];}};}();(function(){var k=0,g=YAHOO.util,d=YAHOO.lang,e=YAHOO.widget.Slider,c=g.Color,f=g.Dom,j=g.Event,a=d.substitute,i="yui-picker";function h(l,b){k=k+1;b=b||{};if(arguments.length===1&&!YAHOO.lang.isString(l)&&!l.nodeName){b=l;l=b.element||null;}if(!l&&!b.element){l=this._createHostElement(b);}h.superclass.constructor.call(this,l,b);this.initPicker();}YAHOO.extend(h,YAHOO.util.Element,{ID:{R:i+"-r",R_HEX:i+"-rhex",G:i+"-g",G_HEX:i+"-ghex",B:i+"-b",B_HEX:i+"-bhex",H:i+"-h",S:i+"-s",V:i+"-v",PICKER_BG:i+"-bg",PICKER_THUMB:i+"-thumb",HUE_BG:i+"-hue-bg",HUE_THUMB:i+"-hue-thumb",HEX:i+"-hex",SWATCH:i+"-swatch",WEBSAFE_SWATCH:i+"-websafe-swatch",CONTROLS:i+"-controls",RGB_CONTROLS:i+"-rgb-controls",HSV_CONTROLS:i+"-hsv-controls",HEX_CONTROLS:i+"-hex-controls",HEX_SUMMARY:i+"-hex-summary",CONTROLS_LABEL:i+"-controls-label"},TXT:{ILLEGAL_HEX:"Illegal hex value entered",SHOW_CONTROLS:"Show color details",HIDE_CONTROLS:"Hide color details",CURRENT_COLOR:"Currently selected color: {rgb}",CLOSEST_WEBSAFE:"Closest websafe color: {rgb}. Click to select.",R:"R",G:"G",B:"B",H:"H",S:"S",V:"V",HEX:"#",DEG:"\u00B0",PERCENT:"%"},IMAGE:{PICKER_THUMB:"../../build/colorpicker/assets/picker_thumb.png",HUE_THUMB:"../../build/colorpicker/assets/hue_thumb.png"},DEFAULT:{PICKER_SIZE:180},OPT:{HUE:"hue",SATURATION:"saturation",VALUE:"value",RED:"red",GREEN:"green",BLUE:"blue",HSV:"hsv",RGB:"rgb",WEBSAFE:"websafe",HEX:"hex",PICKER_SIZE:"pickersize",SHOW_CONTROLS:"showcontrols",SHOW_RGB_CONTROLS:"showrgbcontrols",SHOW_HSV_CONTROLS:"showhsvcontrols",SHOW_HEX_CONTROLS:"showhexcontrols",SHOW_HEX_SUMMARY:"showhexsummary",SHOW_WEBSAFE:"showwebsafe",CONTAINER:"container",IDS:"ids",ELEMENTS:"elements",TXT:"txt",IMAGES:"images",ANIMATE:"animate"},skipAnim:true,_createHostElement:function(){var b=document.createElement("div");if(this.CSS.BASE){b.className=this.CSS.BASE;}return b;},_updateHueSlider:function(){var b=this.get(this.OPT.PICKER_SIZE),l=this.get(this.OPT.HUE);l=b-Math.round(l/360*b);if(l===b){l=0;}this.hueSlider.setValue(l,this.skipAnim);},_updatePickerSlider:function(){var l=this.get(this.OPT.PICKER_SIZE),m=this.get(this.OPT.SATURATION),b=this.get(this.OPT.VALUE);m=Math.round(m*l/100);b=Math.round(l-(b*l/100));this.pickerSlider.setRegionValue(m,b,this.skipAnim);},_updateSliders:function(){this._updateHueSlider();this._updatePickerSlider();},setValue:function(l,b){b=(b)||false;this.set(this.OPT.RGB,l,b);this._updateSliders();},hueSlider:null,pickerSlider:null,_getH:function(){var b=this.get(this.OPT.PICKER_SIZE),l=(b-this.hueSlider.getValue())/b;l=Math.round(l*360);return(l===360)?0:l;},_getS:function(){return this.pickerSlider.getXValue()/this.get(this.OPT.PICKER_SIZE);},_getV:function(){var b=this.get(this.OPT.PICKER_SIZE);return(b-this.pickerSlider.getYValue())/b;},_updateSwatch:function(){var m=this.get(this.OPT.RGB),o=this.get(this.OPT.WEBSAFE),n=this.getElement(this.ID.SWATCH),l=m.join(","),b=this.get(this.OPT.TXT);f.setStyle(n,"background-color","rgb("+l+")");n.title=a(b.CURRENT_COLOR,{"rgb":"#"+this.get(this.OPT.HEX)});n=this.getElement(this.ID.WEBSAFE_SWATCH);l=o.join(",");f.setStyle(n,"background-color","rgb("+l+")");n.title=a(b.CLOSEST_WEBSAFE,{"rgb":"#"+c.rgb2hex(o)});},_getValuesFromSliders:function(){this.set(this.OPT.RGB,c.hsv2rgb(this._getH(),this._getS(),this._getV()));},_updateFormFields:function(){this.getElement(this.ID.H).value=this.get(this.OPT.HUE);this.getElement(this.ID.S).value=this.get(this.OPT.SATURATION);this.getElement(this.ID.V).value=this.get(this.OPT.VALUE);this.getElement(this.ID.R).value=this.get(this.OPT.RED);this.getElement(this.ID.R_HEX).innerHTML=c.dec2hex(this.get(this.OPT.RED));this.getElement(this.ID.G).value=this.get(this.OPT.GREEN);this.getElement(this.ID.G_HEX).innerHTML=c.dec2hex(this.get(this.OPT.GREEN));this.getElement(this.ID.B).value=this.get(this.OPT.BLUE);this.getElement(this.ID.B_HEX).innerHTML=c.dec2hex(this.get(this.OPT.BLUE));this.getElement(this.ID.HEX).value=this.get(this.OPT.HEX);},_onHueSliderChange:function(n){var l=this._getH(),b=c.hsv2rgb(l,1,1),m="rgb("+b.join(",")+")";this.set(this.OPT.HUE,l,true);f.setStyle(this.getElement(this.ID.PICKER_BG),"background-color",m);if(this.hueSlider.valueChangeSource!==e.SOURCE_SET_VALUE){this._getValuesFromSliders();}this._updateFormFields();this._updateSwatch();},_onPickerSliderChange:function(m){var l=this._getS(),b=this._getV();this.set(this.OPT.SATURATION,Math.round(l*100),true);this.set(this.OPT.VALUE,Math.round(b*100),true);if(this.pickerSlider.valueChangeSource!==e.SOURCE_SET_VALUE){this._getValuesFromSliders();
+}this._updateFormFields();this._updateSwatch();},_getCommand:function(b){var l=j.getCharCode(b);if(l===38){return 3;}else{if(l===13){return 6;}else{if(l===40){return 4;}else{if(l>=48&&l<=57){return 1;}else{if(l>=97&&l<=102){return 2;}else{if(l>=65&&l<=70){return 2;}else{if("8, 9, 13, 27, 37, 39".indexOf(l)>-1||b.ctrlKey||b.metaKey){return 5;}else{return 0;}}}}}}}},_useFieldValue:function(l,b,n){var m=b.value;if(n!==this.OPT.HEX){m=parseInt(m,10);}if(m!==this.get(n)){this.set(n,m);}},_rgbFieldKeypress:function(m,b,o){var n=this._getCommand(m),l=(m.shiftKey)?10:1;switch(n){case 6:this._useFieldValue.apply(this,arguments);break;case 3:this.set(o,Math.min(this.get(o)+l,255));this._updateFormFields();break;case 4:this.set(o,Math.max(this.get(o)-l,0));this._updateFormFields();break;default:}},_hexFieldKeypress:function(l,b,n){var m=this._getCommand(l);if(m===6){this._useFieldValue.apply(this,arguments);}},_hexOnly:function(l,b){var m=this._getCommand(l);switch(m){case 6:case 5:case 1:break;case 2:if(b!==true){break;}default:j.stopEvent(l);return false;}},_numbersOnly:function(b){return this._hexOnly(b,true);},getElement:function(b){return this.get(this.OPT.ELEMENTS)[this.get(this.OPT.IDS)[b]];},_createElements:function(){var n,m,q,o,l,b=this.get(this.OPT.IDS),r=this.get(this.OPT.TXT),t=this.get(this.OPT.IMAGES),s=function(p,v){var w=document.createElement(p);if(v){d.augmentObject(w,v,true);}return w;},u=function(p,v){var w=d.merge({autocomplete:"off",value:"0",size:3,maxlength:3},v);w.name=w.id;return new s(p,w);};l=this.get("element");n=new s("div",{id:b[this.ID.PICKER_BG],className:"yui-picker-bg",tabIndex:-1,hideFocus:true});m=new s("div",{id:b[this.ID.PICKER_THUMB],className:"yui-picker-thumb"});q=new s("img",{src:t.PICKER_THUMB});m.appendChild(q);n.appendChild(m);l.appendChild(n);n=new s("div",{id:b[this.ID.HUE_BG],className:"yui-picker-hue-bg",tabIndex:-1,hideFocus:true});m=new s("div",{id:b[this.ID.HUE_THUMB],className:"yui-picker-hue-thumb"});q=new s("img",{src:t.HUE_THUMB});m.appendChild(q);n.appendChild(m);l.appendChild(n);n=new s("div",{id:b[this.ID.CONTROLS],className:"yui-picker-controls"});l.appendChild(n);l=n;n=new s("div",{className:"hd"});m=new s("a",{id:b[this.ID.CONTROLS_LABEL],href:"#"});n.appendChild(m);l.appendChild(n);n=new s("div",{className:"bd"});l.appendChild(n);l=n;n=new s("ul",{id:b[this.ID.RGB_CONTROLS],className:"yui-picker-rgb-controls"});m=new s("li");m.appendChild(document.createTextNode(r.R+" "));o=new u("input",{id:b[this.ID.R],className:"yui-picker-r"});m.appendChild(o);n.appendChild(m);m=new s("li");m.appendChild(document.createTextNode(r.G+" "));o=new u("input",{id:b[this.ID.G],className:"yui-picker-g"});m.appendChild(o);n.appendChild(m);m=new s("li");m.appendChild(document.createTextNode(r.B+" "));o=new u("input",{id:b[this.ID.B],className:"yui-picker-b"});m.appendChild(o);n.appendChild(m);l.appendChild(n);n=new s("ul",{id:b[this.ID.HSV_CONTROLS],className:"yui-picker-hsv-controls"});m=new s("li");m.appendChild(document.createTextNode(r.H+" "));o=new u("input",{id:b[this.ID.H],className:"yui-picker-h"});m.appendChild(o);m.appendChild(document.createTextNode(" "+r.DEG));n.appendChild(m);m=new s("li");m.appendChild(document.createTextNode(r.S+" "));o=new u("input",{id:b[this.ID.S],className:"yui-picker-s"});m.appendChild(o);m.appendChild(document.createTextNode(" "+r.PERCENT));n.appendChild(m);m=new s("li");m.appendChild(document.createTextNode(r.V+" "));o=new u("input",{id:b[this.ID.V],className:"yui-picker-v"});m.appendChild(o);m.appendChild(document.createTextNode(" "+r.PERCENT));n.appendChild(m);l.appendChild(n);n=new s("ul",{id:b[this.ID.HEX_SUMMARY],className:"yui-picker-hex_summary"});m=new s("li",{id:b[this.ID.R_HEX]});n.appendChild(m);m=new s("li",{id:b[this.ID.G_HEX]});n.appendChild(m);m=new s("li",{id:b[this.ID.B_HEX]});n.appendChild(m);l.appendChild(n);n=new s("div",{id:b[this.ID.HEX_CONTROLS],className:"yui-picker-hex-controls"});n.appendChild(document.createTextNode(r.HEX+" "));m=new u("input",{id:b[this.ID.HEX],className:"yui-picker-hex",size:6,maxlength:6});n.appendChild(m);l.appendChild(n);l=this.get("element");n=new s("div",{id:b[this.ID.SWATCH],className:"yui-picker-swatch"});l.appendChild(n);n=new s("div",{id:b[this.ID.WEBSAFE_SWATCH],className:"yui-picker-websafe-swatch"});l.appendChild(n);},_attachRGBHSV:function(l,b){j.on(this.getElement(l),"keydown",function(n,m){m._rgbFieldKeypress(n,this,b);},this);j.on(this.getElement(l),"keypress",this._numbersOnly,this,true);j.on(this.getElement(l),"blur",function(n,m){m._useFieldValue(n,this,b);},this);},_updateRGB:function(){var b=[this.get(this.OPT.RED),this.get(this.OPT.GREEN),this.get(this.OPT.BLUE)];this.set(this.OPT.RGB,b);this._updateSliders();},_initElements:function(){var p=this.OPT,n=this.get(p.IDS),l=this.get(p.ELEMENTS),b,m,q;for(b in this.ID){if(d.hasOwnProperty(this.ID,b)){n[this.ID[b]]=n[b];}}m=f.get(n[this.ID.PICKER_BG]);if(!m){this._createElements();}else{}for(b in n){if(d.hasOwnProperty(n,b)){m=f.get(n[b]);q=f.generateId(m);n[b]=q;n[n[b]]=q;l[q]=m;}}},initPicker:function(){this._initSliders();this._bindUI();this.syncUI(true);},_initSliders:function(){var b=this.ID,l=this.get(this.OPT.PICKER_SIZE);this.hueSlider=e.getVertSlider(this.getElement(b.HUE_BG),this.getElement(b.HUE_THUMB),0,l);this.pickerSlider=e.getSliderRegion(this.getElement(b.PICKER_BG),this.getElement(b.PICKER_THUMB),0,l,0,l);this.set(this.OPT.ANIMATE,this.get(this.OPT.ANIMATE));},_bindUI:function(){var b=this.ID,l=this.OPT;this.hueSlider.subscribe("change",this._onHueSliderChange,this,true);this.pickerSlider.subscribe("change",this._onPickerSliderChange,this,true);j.on(this.getElement(b.WEBSAFE_SWATCH),"click",function(m){this.setValue(this.get(l.WEBSAFE));},this,true);j.on(this.getElement(b.CONTROLS_LABEL),"click",function(m){this.set(l.SHOW_CONTROLS,!this.get(l.SHOW_CONTROLS));j.preventDefault(m);},this,true);this._attachRGBHSV(b.R,l.RED);this._attachRGBHSV(b.G,l.GREEN);this._attachRGBHSV(b.B,l.BLUE);this._attachRGBHSV(b.H,l.HUE);
+this._attachRGBHSV(b.S,l.SATURATION);this._attachRGBHSV(b.V,l.VALUE);j.on(this.getElement(b.HEX),"keydown",function(n,m){m._hexFieldKeypress(n,this,l.HEX);},this);j.on(this.getElement(this.ID.HEX),"keypress",this._hexOnly,this,true);j.on(this.getElement(this.ID.HEX),"blur",function(n,m){m._useFieldValue(n,this,l.HEX);},this);},syncUI:function(b){this.skipAnim=b;this._updateRGB();this.skipAnim=false;},_updateRGBFromHSV:function(){var l=[this.get(this.OPT.HUE),this.get(this.OPT.SATURATION)/100,this.get(this.OPT.VALUE)/100],b=c.hsv2rgb(l);this.set(this.OPT.RGB,b);this._updateSliders();},_updateHex:function(){var o=this.get(this.OPT.HEX),b=o.length,p,n,m;if(b===3){p=o.split("");for(n=0;n<b;n=n+1){p[n]=p[n]+p[n];}o=p.join("");}if(o.length!==6){return false;}m=c.hex2rgb(o);this.setValue(m);},_hideShowEl:function(m,b){var l=(d.isString(m)?this.getElement(m):m);f.setStyle(l,"display",(b)?"":"none");},initAttributes:function(b){b=b||{};h.superclass.initAttributes.call(this,b);this.setAttributeConfig(this.OPT.PICKER_SIZE,{value:b.size||this.DEFAULT.PICKER_SIZE});this.setAttributeConfig(this.OPT.HUE,{value:b.hue||0,validator:d.isNumber});this.setAttributeConfig(this.OPT.SATURATION,{value:b.saturation||0,validator:d.isNumber});this.setAttributeConfig(this.OPT.VALUE,{value:d.isNumber(b.value)?b.value:100,validator:d.isNumber});this.setAttributeConfig(this.OPT.RED,{value:d.isNumber(b.red)?b.red:255,validator:d.isNumber});this.setAttributeConfig(this.OPT.GREEN,{value:d.isNumber(b.green)?b.green:255,validator:d.isNumber});this.setAttributeConfig(this.OPT.BLUE,{value:d.isNumber(b.blue)?b.blue:255,validator:d.isNumber});this.setAttributeConfig(this.OPT.HEX,{value:b.hex||"FFFFFF",validator:d.isString});this.setAttributeConfig(this.OPT.RGB,{value:b.rgb||[255,255,255],method:function(o){this.set(this.OPT.RED,o[0],true);this.set(this.OPT.GREEN,o[1],true);this.set(this.OPT.BLUE,o[2],true);var q=c.websafe(o),p=c.rgb2hex(o),n=c.rgb2hsv(o);this.set(this.OPT.WEBSAFE,q,true);this.set(this.OPT.HEX,p,true);if(n[1]){this.set(this.OPT.HUE,n[0],true);}this.set(this.OPT.SATURATION,Math.round(n[1]*100),true);this.set(this.OPT.VALUE,Math.round(n[2]*100),true);},readonly:true});this.setAttributeConfig(this.OPT.CONTAINER,{value:null,method:function(n){if(n){n.showEvent.subscribe(function(){this.pickerSlider.focus();},this,true);}}});this.setAttributeConfig(this.OPT.WEBSAFE,{value:b.websafe||[255,255,255]});var m=b.ids||d.merge({},this.ID),l;if(!b.ids&&k>1){for(l in m){if(d.hasOwnProperty(m,l)){m[l]=m[l]+k;}}}this.setAttributeConfig(this.OPT.IDS,{value:m,writeonce:true});this.setAttributeConfig(this.OPT.TXT,{value:b.txt||this.TXT,writeonce:true});this.setAttributeConfig(this.OPT.IMAGES,{value:b.images||this.IMAGE,writeonce:true});this.setAttributeConfig(this.OPT.ELEMENTS,{value:{},readonly:true});this.setAttributeConfig(this.OPT.SHOW_CONTROLS,{value:d.isBoolean(b.showcontrols)?b.showcontrols:true,method:function(n){var o=f.getElementsByClassName("bd","div",this.getElement(this.ID.CONTROLS))[0];this._hideShowEl(o,n);this.getElement(this.ID.CONTROLS_LABEL).innerHTML=(n)?this.get(this.OPT.TXT).HIDE_CONTROLS:this.get(this.OPT.TXT).SHOW_CONTROLS;}});this.setAttributeConfig(this.OPT.SHOW_RGB_CONTROLS,{value:d.isBoolean(b.showrgbcontrols)?b.showrgbcontrols:true,method:function(n){this._hideShowEl(this.ID.RGB_CONTROLS,n);}});this.setAttributeConfig(this.OPT.SHOW_HSV_CONTROLS,{value:d.isBoolean(b.showhsvcontrols)?b.showhsvcontrols:false,method:function(n){this._hideShowEl(this.ID.HSV_CONTROLS,n);if(n&&this.get(this.OPT.SHOW_HEX_SUMMARY)){this.set(this.OPT.SHOW_HEX_SUMMARY,false);}}});this.setAttributeConfig(this.OPT.SHOW_HEX_CONTROLS,{value:d.isBoolean(b.showhexcontrols)?b.showhexcontrols:false,method:function(n){this._hideShowEl(this.ID.HEX_CONTROLS,n);}});this.setAttributeConfig(this.OPT.SHOW_WEBSAFE,{value:d.isBoolean(b.showwebsafe)?b.showwebsafe:true,method:function(n){this._hideShowEl(this.ID.WEBSAFE_SWATCH,n);}});this.setAttributeConfig(this.OPT.SHOW_HEX_SUMMARY,{value:d.isBoolean(b.showhexsummary)?b.showhexsummary:true,method:function(n){this._hideShowEl(this.ID.HEX_SUMMARY,n);if(n&&this.get(this.OPT.SHOW_HSV_CONTROLS)){this.set(this.OPT.SHOW_HSV_CONTROLS,false);}}});this.setAttributeConfig(this.OPT.ANIMATE,{value:d.isBoolean(b.animate)?b.animate:true,method:function(n){if(this.pickerSlider){this.pickerSlider.animate=n;this.hueSlider.animate=n;}}});this.on(this.OPT.HUE+"Change",this._updateRGBFromHSV,this,true);this.on(this.OPT.SATURATION+"Change",this._updateRGBFromHSV,this,true);this.on(this.OPT.VALUE+"Change",this._updateRGBFromHSV,this,true);this.on(this.OPT.RED+"Change",this._updateRGB,this,true);this.on(this.OPT.GREEN+"Change",this._updateRGB,this,true);this.on(this.OPT.BLUE+"Change",this._updateRGB,this,true);this.on(this.OPT.HEX+"Change",this._updateHex,this,true);this._initElements();}});YAHOO.widget.ColorPicker=h;})();YAHOO.register("colorpicker",YAHOO.widget.ColorPicker,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/connection/connection-min.js b/Websites/bugs.webkit.org/js/yui/connection/connection-min.js
new file mode 100644
index 0000000..c0ec748
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/connection/connection-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.Connect={_msxml_progid:["Microsoft.XMLHTTP","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP"],_http_headers:{},_has_http_headers:false,_use_default_post_header:true,_default_post_header:"application/x-www-form-urlencoded; charset=UTF-8",_default_form_header:"application/x-www-form-urlencoded",_use_default_xhr_header:true,_default_xhr_header:"XMLHttpRequest",_has_default_headers:true,_isFormSubmit:false,_default_headers:{},_poll:{},_timeOut:{},_polling_interval:50,_transaction_id:0,startEvent:new YAHOO.util.CustomEvent("start"),completeEvent:new YAHOO.util.CustomEvent("complete"),successEvent:new YAHOO.util.CustomEvent("success"),failureEvent:new YAHOO.util.CustomEvent("failure"),abortEvent:new YAHOO.util.CustomEvent("abort"),_customEvents:{onStart:["startEvent","start"],onComplete:["completeEvent","complete"],onSuccess:["successEvent","success"],onFailure:["failureEvent","failure"],onUpload:["uploadEvent","upload"],onAbort:["abortEvent","abort"]},setProgId:function(a){this._msxml_progid.unshift(a);},setDefaultPostHeader:function(a){if(typeof a=="string"){this._default_post_header=a;this._use_default_post_header=true;}else{if(typeof a=="boolean"){this._use_default_post_header=a;}}},setDefaultXhrHeader:function(a){if(typeof a=="string"){this._default_xhr_header=a;}else{this._use_default_xhr_header=a;}},setPollingInterval:function(a){if(typeof a=="number"&&isFinite(a)){this._polling_interval=a;}},createXhrObject:function(g){var d,a,b;try{a=new XMLHttpRequest();d={conn:a,tId:g,xhr:true};}catch(c){for(b=0;b<this._msxml_progid.length;++b){try{a=new ActiveXObject(this._msxml_progid[b]);d={conn:a,tId:g,xhr:true};break;}catch(f){}}}finally{return d;}},getConnectionObject:function(a){var c,d=this._transaction_id;try{if(!a){c=this.createXhrObject(d);}else{c={tId:d};if(a==="xdr"){c.conn=this._transport;c.xdr=true;}else{if(a==="upload"){c.upload=true;}}}if(c){this._transaction_id++;}}catch(b){}return c;},asyncRequest:function(h,d,g,a){var b=g&&g.argument?g.argument:null,e=this,f,c;if(this._isFileUpload){c="upload";}else{if(g&&g.xdr){c="xdr";}}f=this.getConnectionObject(c);if(!f){return null;}else{if(g&&g.customevents){this.initCustomEvents(f,g);}if(this._isFormSubmit){if(this._isFileUpload){window.setTimeout(function(){e.uploadFile(f,g,d,a);},10);return f;}if(h.toUpperCase()=="GET"){if(this._sFormData.length!==0){d+=((d.indexOf("?")==-1)?"?":"&")+this._sFormData;}}else{if(h.toUpperCase()=="POST"){a=a?this._sFormData+"&"+a:this._sFormData;}}}if(h.toUpperCase()=="GET"&&(g&&g.cache===false)){d+=((d.indexOf("?")==-1)?"?":"&")+"rnd="+new Date().valueOf().toString();}if(this._use_default_xhr_header){if(!this._default_headers["X-Requested-With"]){this.initHeader("X-Requested-With",this._default_xhr_header,true);}}if((h.toUpperCase()==="POST"&&this._use_default_post_header)&&this._isFormSubmit===false){this.initHeader("Content-Type",this._default_post_header);}if(f.xdr){this.xdr(f,h,d,g,a);return f;}f.conn.open(h,d,true);if(this._has_default_headers||this._has_http_headers){this.setHeader(f);}this.handleReadyState(f,g);f.conn.send(a||"");if(this._isFormSubmit===true){this.resetFormState();}this.startEvent.fire(f,b);if(f.startEvent){f.startEvent.fire(f,b);}return f;}},initCustomEvents:function(a,c){var b;for(b in c.customevents){if(this._customEvents[b][0]){a[this._customEvents[b][0]]=new YAHOO.util.CustomEvent(this._customEvents[b][1],(c.scope)?c.scope:null);a[this._customEvents[b][0]].subscribe(c.customevents[b]);}}},handleReadyState:function(c,d){var b=this,a=(d&&d.argument)?d.argument:null;if(d&&d.timeout){this._timeOut[c.tId]=window.setTimeout(function(){b.abort(c,d,true);},d.timeout);}this._poll[c.tId]=window.setInterval(function(){if(c.conn&&c.conn.readyState===4){window.clearInterval(b._poll[c.tId]);delete b._poll[c.tId];if(d&&d.timeout){window.clearTimeout(b._timeOut[c.tId]);delete b._timeOut[c.tId];}b.completeEvent.fire(c,a);if(c.completeEvent){c.completeEvent.fire(c,a);}b.handleTransactionResponse(c,d);}},this._polling_interval);},handleTransactionResponse:function(b,j,d){var f,a,h=(j&&j.argument)?j.argument:null,c=(b.r&&b.r.statusText==="xdr:success")?true:false,i=(b.r&&b.r.statusText==="xdr:failure")?true:false,k=d;try{if((b.conn.status!==undefined&&b.conn.status!==0)||c){f=b.conn.status;}else{if(i&&!k){f=0;}else{f=13030;}}}catch(g){f=13030;}if((f>=200&&f<300)||f===1223||c){a=b.xdr?b.r:this.createResponseObject(b,h);if(j&&j.success){if(!j.scope){j.success(a);}else{j.success.apply(j.scope,[a]);}}this.successEvent.fire(a);if(b.successEvent){b.successEvent.fire(a);}}else{switch(f){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:a=this.createExceptionObject(b.tId,h,(d?d:false));if(j&&j.failure){if(!j.scope){j.failure(a);}else{j.failure.apply(j.scope,[a]);}}break;default:a=(b.xdr)?b.response:this.createResponseObject(b,h);if(j&&j.failure){if(!j.scope){j.failure(a);}else{j.failure.apply(j.scope,[a]);}}}this.failureEvent.fire(a);if(b.failureEvent){b.failureEvent.fire(a);}}this.releaseObject(b);a=null;},createResponseObject:function(a,h){var d={},k={},f,c,g,b;try{c=a.conn.getAllResponseHeaders();g=c.split("\n");for(f=0;f<g.length;f++){b=g[f].indexOf(":");if(b!=-1){k[g[f].substring(0,b)]=YAHOO.lang.trim(g[f].substring(b+2));}}}catch(j){}d.tId=a.tId;d.status=(a.conn.status==1223)?204:a.conn.status;d.statusText=(a.conn.status==1223)?"No Content":a.conn.statusText;d.getResponseHeader=k;d.getAllResponseHeaders=c;d.responseText=a.conn.responseText;d.responseXML=a.conn.responseXML;if(h){d.argument=h;}return d;},createExceptionObject:function(h,d,a){var f=0,g="communication failure",c=-1,b="transaction aborted",e={};e.tId=h;if(a){e.status=c;e.statusText=b;}else{e.status=f;e.statusText=g;}if(d){e.argument=d;}return e;},initHeader:function(a,d,c){var b=(c)?this._default_headers:this._http_headers;b[a]=d;if(c){this._has_default_headers=true;}else{this._has_http_headers=true;}},setHeader:function(a){var b;if(this._has_default_headers){for(b in this._default_headers){if(YAHOO.lang.hasOwnProperty(this._default_headers,b)){a.conn.setRequestHeader(b,this._default_headers[b]);
+}}}if(this._has_http_headers){for(b in this._http_headers){if(YAHOO.lang.hasOwnProperty(this._http_headers,b)){a.conn.setRequestHeader(b,this._http_headers[b]);}}this._http_headers={};this._has_http_headers=false;}},resetDefaultHeaders:function(){this._default_headers={};this._has_default_headers=false;},abort:function(e,g,a){var d,b=(g&&g.argument)?g.argument:null;e=e||{};if(e.conn){if(e.xhr){if(this.isCallInProgress(e)){e.conn.abort();window.clearInterval(this._poll[e.tId]);delete this._poll[e.tId];if(a){window.clearTimeout(this._timeOut[e.tId]);delete this._timeOut[e.tId];}d=true;}}else{if(e.xdr){e.conn.abort(e.tId);d=true;}}}else{if(e.upload){var c="yuiIO"+e.tId;var f=document.getElementById(c);if(f){YAHOO.util.Event.removeListener(f,"load");document.body.removeChild(f);if(a){window.clearTimeout(this._timeOut[e.tId]);delete this._timeOut[e.tId];}d=true;}}else{d=false;}}if(d===true){this.abortEvent.fire(e,b);if(e.abortEvent){e.abortEvent.fire(e,b);}this.handleTransactionResponse(e,g,true);}return d;},isCallInProgress:function(a){a=a||{};if(a.xhr&&a.conn){return a.conn.readyState!==4&&a.conn.readyState!==0;}else{if(a.xdr&&a.conn){return a.conn.isCallInProgress(a.tId);}else{if(a.upload===true){return document.getElementById("yuiIO"+a.tId)?true:false;}else{return false;}}}},releaseObject:function(a){if(a&&a.conn){a.conn=null;a=null;}}};(function(){var g=YAHOO.util.Connect,h={};function d(i){var j='<object id="YUIConnectionSwf" type="application/x-shockwave-flash" data="'+i+'" width="0" height="0">'+'<param name="movie" value="'+i+'">'+'<param name="allowScriptAccess" value="always">'+"</object>",k=document.createElement("div");document.body.appendChild(k);k.innerHTML=j;}function b(l,i,j,n,k){h[parseInt(l.tId)]={"o":l,"c":n};if(k){n.method=i;n.data=k;}l.conn.send(j,n,l.tId);}function e(i){d(i);g._transport=document.getElementById("YUIConnectionSwf");}function c(){g.xdrReadyEvent.fire();}function a(j,i){if(j){g.startEvent.fire(j,i.argument);if(j.startEvent){j.startEvent.fire(j,i.argument);}}}function f(j){var k=h[j.tId].o,i=h[j.tId].c;if(j.statusText==="xdr:start"){a(k,i);return;}j.responseText=decodeURI(j.responseText);k.r=j;if(i.argument){k.r.argument=i.argument;}this.handleTransactionResponse(k,i,j.statusText==="xdr:abort"?true:false);delete h[j.tId];}g.xdr=b;g.swf=d;g.transport=e;g.xdrReadyEvent=new YAHOO.util.CustomEvent("xdrReady");g.xdrReady=c;g.handleXdrResponse=f;})();(function(){var e=YAHOO.util.Connect,g=YAHOO.util.Event,a=document.documentMode?document.documentMode:false;e._isFileUpload=false;e._formNode=null;e._sFormData=null;e._submitElementValue=null;e.uploadEvent=new YAHOO.util.CustomEvent("upload");e._hasSubmitListener=function(){if(g){g.addListener(document,"click",function(k){var j=g.getTarget(k),i=j.nodeName.toLowerCase();if((i==="input"||i==="button")&&(j.type&&j.type.toLowerCase()=="submit")){e._submitElementValue=encodeURIComponent(j.name)+"="+encodeURIComponent(j.value);}});return true;}return false;}();function h(w,r,m){var v,l,u,s,z,t=false,p=[],y=0,o,q,n,x,k;this.resetFormState();if(typeof w=="string"){v=(document.getElementById(w)||document.forms[w]);}else{if(typeof w=="object"){v=w;}else{return;}}if(r){this.createFrame(m?m:null);this._isFormSubmit=true;this._isFileUpload=true;this._formNode=v;return;}for(o=0,q=v.elements.length;o<q;++o){l=v.elements[o];z=l.disabled;u=l.name;if(!z&&u){u=encodeURIComponent(u)+"=";s=encodeURIComponent(l.value);switch(l.type){case"select-one":if(l.selectedIndex>-1){k=l.options[l.selectedIndex];p[y++]=u+encodeURIComponent((k.attributes.value&&k.attributes.value.specified)?k.value:k.text);}break;case"select-multiple":if(l.selectedIndex>-1){for(n=l.selectedIndex,x=l.options.length;n<x;++n){k=l.options[n];if(k.selected){p[y++]=u+encodeURIComponent((k.attributes.value&&k.attributes.value.specified)?k.value:k.text);}}}break;case"radio":case"checkbox":if(l.checked){p[y++]=u+s;}break;case"file":case undefined:case"reset":case"button":break;case"submit":if(t===false){if(this._hasSubmitListener&&this._submitElementValue){p[y++]=this._submitElementValue;}t=true;}break;default:p[y++]=u+s;}}}this._isFormSubmit=true;this._sFormData=p.join("&");this.initHeader("Content-Type",this._default_form_header);return this._sFormData;}function d(){this._isFormSubmit=false;this._isFileUpload=false;this._formNode=null;this._sFormData="";}function c(i){var j="yuiIO"+this._transaction_id,l=(a===9)?true:false,k;if(YAHOO.env.ua.ie&&!l){k=document.createElement('<iframe id="'+j+'" name="'+j+'" />');if(typeof i=="boolean"){k.src="javascript:false";}}else{k=document.createElement("iframe");k.id=j;k.name=j;}k.style.position="absolute";k.style.top="-1000px";k.style.left="-1000px";document.body.appendChild(k);}function f(j){var m=[],k=j.split("&"),l,n;for(l=0;l<k.length;l++){n=k[l].indexOf("=");if(n!=-1){m[l]=document.createElement("input");m[l].type="hidden";m[l].name=decodeURIComponent(k[l].substring(0,n));m[l].value=decodeURIComponent(k[l].substring(n+1));this._formNode.appendChild(m[l]);}}return m;}function b(m,y,n,l){var t="yuiIO"+m.tId,u="multipart/form-data",w=document.getElementById(t),p=(a>=8)?true:false,z=this,v=(y&&y.argument)?y.argument:null,x,s,k,r,j,q;j={action:this._formNode.getAttribute("action"),method:this._formNode.getAttribute("method"),target:this._formNode.getAttribute("target")};this._formNode.setAttribute("action",n);this._formNode.setAttribute("method","POST");this._formNode.setAttribute("target",t);if(YAHOO.env.ua.ie&&!p){this._formNode.setAttribute("encoding",u);}else{this._formNode.setAttribute("enctype",u);}if(l){x=this.appendPostData(l);}this._formNode.submit();this.startEvent.fire(m,v);if(m.startEvent){m.startEvent.fire(m,v);}if(y&&y.timeout){this._timeOut[m.tId]=window.setTimeout(function(){z.abort(m,y,true);},y.timeout);}if(x&&x.length>0){for(s=0;s<x.length;s++){this._formNode.removeChild(x[s]);}}for(k in j){if(YAHOO.lang.hasOwnProperty(j,k)){if(j[k]){this._formNode.setAttribute(k,j[k]);}else{this._formNode.removeAttribute(k);}}}this.resetFormState();
+q=function(){var i,A,B;if(y&&y.timeout){window.clearTimeout(z._timeOut[m.tId]);delete z._timeOut[m.tId];}z.completeEvent.fire(m,v);if(m.completeEvent){m.completeEvent.fire(m,v);}r={tId:m.tId,argument:v};try{i=w.contentWindow.document.getElementsByTagName("body")[0];A=w.contentWindow.document.getElementsByTagName("pre")[0];if(i){if(A){B=A.textContent?A.textContent:A.innerText;}else{B=i.textContent?i.textContent:i.innerText;}}r.responseText=B;r.responseXML=w.contentWindow.document.XMLDocument?w.contentWindow.document.XMLDocument:w.contentWindow.document;}catch(o){}if(y&&y.upload){if(!y.scope){y.upload(r);}else{y.upload.apply(y.scope,[r]);}}z.uploadEvent.fire(r);if(m.uploadEvent){m.uploadEvent.fire(r);}g.removeListener(w,"load",q);setTimeout(function(){document.body.removeChild(w);z.releaseObject(m);},100);};g.addListener(w,"load",q);}e.setForm=h;e.resetFormState=d;e.createFrame=c;e.appendPostData=f;e.uploadFile=b;})();YAHOO.register("connection",YAHOO.util.Connect,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/connection/connection.swf b/Websites/bugs.webkit.org/js/yui/connection/connection.swf
new file mode 100644
index 0000000..c33a7fe
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/connection/connection.swf
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/connection/connection_core-min.js b/Websites/bugs.webkit.org/js/yui/connection/connection_core-min.js
new file mode 100644
index 0000000..c51420f
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/connection/connection_core-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.Connect={_msxml_progid:["Microsoft.XMLHTTP","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP"],_http_headers:{},_has_http_headers:false,_use_default_post_header:true,_default_post_header:"application/x-www-form-urlencoded; charset=UTF-8",_default_form_header:"application/x-www-form-urlencoded",_use_default_xhr_header:true,_default_xhr_header:"XMLHttpRequest",_has_default_headers:true,_isFormSubmit:false,_default_headers:{},_poll:{},_timeOut:{},_polling_interval:50,_transaction_id:0,startEvent:new YAHOO.util.CustomEvent("start"),completeEvent:new YAHOO.util.CustomEvent("complete"),successEvent:new YAHOO.util.CustomEvent("success"),failureEvent:new YAHOO.util.CustomEvent("failure"),abortEvent:new YAHOO.util.CustomEvent("abort"),_customEvents:{onStart:["startEvent","start"],onComplete:["completeEvent","complete"],onSuccess:["successEvent","success"],onFailure:["failureEvent","failure"],onUpload:["uploadEvent","upload"],onAbort:["abortEvent","abort"]},setProgId:function(A){this._msxml_progid.unshift(A);},setDefaultPostHeader:function(A){if(typeof A=="string"){this._default_post_header=A;this._use_default_post_header=true;}else{if(typeof A=="boolean"){this._use_default_post_header=A;}}},setDefaultXhrHeader:function(A){if(typeof A=="string"){this._default_xhr_header=A;}else{this._use_default_xhr_header=A;}},setPollingInterval:function(A){if(typeof A=="number"&&isFinite(A)){this._polling_interval=A;}},createXhrObject:function(F){var D,A,B;try{A=new XMLHttpRequest();D={conn:A,tId:F,xhr:true};}catch(C){for(B=0;B<this._msxml_progid.length;++B){try{A=new ActiveXObject(this._msxml_progid[B]);D={conn:A,tId:F,xhr:true};break;}catch(E){}}}finally{return D;}},getConnectionObject:function(A){var C,D=this._transaction_id;try{if(!A){C=this.createXhrObject(D);}else{C={tId:D};if(A==="xdr"){C.conn=this._transport;C.xdr=true;}else{if(A==="upload"){C.upload=true;}}}if(C){this._transaction_id++;}}catch(B){}return C;},asyncRequest:function(H,D,G,A){var B=G&&G.argument?G.argument:null,E=this,F,C;if(this._isFileUpload){C="upload";}else{if(G&&G.xdr){C="xdr";}}F=this.getConnectionObject(C);if(!F){return null;}else{if(G&&G.customevents){this.initCustomEvents(F,G);}if(this._isFormSubmit){if(this._isFileUpload){window.setTimeout(function(){E.uploadFile(F,G,D,A);},10);return F;}if(H.toUpperCase()=="GET"){if(this._sFormData.length!==0){D+=((D.indexOf("?")==-1)?"?":"&")+this._sFormData;}}else{if(H.toUpperCase()=="POST"){A=A?this._sFormData+"&"+A:this._sFormData;}}}if(H.toUpperCase()=="GET"&&(G&&G.cache===false)){D+=((D.indexOf("?")==-1)?"?":"&")+"rnd="+new Date().valueOf().toString();}if(this._use_default_xhr_header){if(!this._default_headers["X-Requested-With"]){this.initHeader("X-Requested-With",this._default_xhr_header,true);}}if((H.toUpperCase()==="POST"&&this._use_default_post_header)&&this._isFormSubmit===false){this.initHeader("Content-Type",this._default_post_header);}if(F.xdr){this.xdr(F,H,D,G,A);return F;}F.conn.open(H,D,true);if(this._has_default_headers||this._has_http_headers){this.setHeader(F);}this.handleReadyState(F,G);F.conn.send(A||"");if(this._isFormSubmit===true){this.resetFormState();}this.startEvent.fire(F,B);if(F.startEvent){F.startEvent.fire(F,B);}return F;}},initCustomEvents:function(A,C){var B;for(B in C.customevents){if(this._customEvents[B][0]){A[this._customEvents[B][0]]=new YAHOO.util.CustomEvent(this._customEvents[B][1],(C.scope)?C.scope:null);A[this._customEvents[B][0]].subscribe(C.customevents[B]);}}},handleReadyState:function(C,D){var B=this,A=(D&&D.argument)?D.argument:null;if(D&&D.timeout){this._timeOut[C.tId]=window.setTimeout(function(){B.abort(C,D,true);},D.timeout);}this._poll[C.tId]=window.setInterval(function(){if(C.conn&&C.conn.readyState===4){window.clearInterval(B._poll[C.tId]);delete B._poll[C.tId];if(D&&D.timeout){window.clearTimeout(B._timeOut[C.tId]);delete B._timeOut[C.tId];}B.completeEvent.fire(C,A);if(C.completeEvent){C.completeEvent.fire(C,A);}B.handleTransactionResponse(C,D);}},this._polling_interval);},handleTransactionResponse:function(B,I,D){var E,A,G=(I&&I.argument)?I.argument:null,C=(B.r&&B.r.statusText==="xdr:success")?true:false,H=(B.r&&B.r.statusText==="xdr:failure")?true:false,J=D;try{if((B.conn.status!==undefined&&B.conn.status!==0)||C){E=B.conn.status;}else{if(H&&!J){E=0;}else{E=13030;}}}catch(F){E=13030;}if((E>=200&&E<300)||E===1223||C){A=B.xdr?B.r:this.createResponseObject(B,G);if(I&&I.success){if(!I.scope){I.success(A);}else{I.success.apply(I.scope,[A]);}}this.successEvent.fire(A);if(B.successEvent){B.successEvent.fire(A);}}else{switch(E){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:A=this.createExceptionObject(B.tId,G,(D?D:false));if(I&&I.failure){if(!I.scope){I.failure(A);}else{I.failure.apply(I.scope,[A]);}}break;default:A=(B.xdr)?B.response:this.createResponseObject(B,G);if(I&&I.failure){if(!I.scope){I.failure(A);}else{I.failure.apply(I.scope,[A]);}}}this.failureEvent.fire(A);if(B.failureEvent){B.failureEvent.fire(A);}}this.releaseObject(B);A=null;},createResponseObject:function(A,G){var D={},I={},E,C,F,B;try{C=A.conn.getAllResponseHeaders();F=C.split("\n");for(E=0;E<F.length;E++){B=F[E].indexOf(":");if(B!=-1){I[F[E].substring(0,B)]=YAHOO.lang.trim(F[E].substring(B+2));}}}catch(H){}D.tId=A.tId;D.status=(A.conn.status==1223)?204:A.conn.status;D.statusText=(A.conn.status==1223)?"No Content":A.conn.statusText;D.getResponseHeader=I;D.getAllResponseHeaders=C;D.responseText=A.conn.responseText;D.responseXML=A.conn.responseXML;if(G){D.argument=G;}return D;},createExceptionObject:function(H,D,A){var F=0,G="communication failure",C=-1,B="transaction aborted",E={};E.tId=H;if(A){E.status=C;E.statusText=B;}else{E.status=F;E.statusText=G;}if(D){E.argument=D;}return E;},initHeader:function(A,D,C){var B=(C)?this._default_headers:this._http_headers;B[A]=D;if(C){this._has_default_headers=true;}else{this._has_http_headers=true;}},setHeader:function(A){var B;if(this._has_default_headers){for(B in this._default_headers){if(YAHOO.lang.hasOwnProperty(this._default_headers,B)){A.conn.setRequestHeader(B,this._default_headers[B]);
+}}}if(this._has_http_headers){for(B in this._http_headers){if(YAHOO.lang.hasOwnProperty(this._http_headers,B)){A.conn.setRequestHeader(B,this._http_headers[B]);}}this._http_headers={};this._has_http_headers=false;}},resetDefaultHeaders:function(){this._default_headers={};this._has_default_headers=false;},abort:function(E,G,A){var D,B=(G&&G.argument)?G.argument:null;E=E||{};if(E.conn){if(E.xhr){if(this.isCallInProgress(E)){E.conn.abort();window.clearInterval(this._poll[E.tId]);delete this._poll[E.tId];if(A){window.clearTimeout(this._timeOut[E.tId]);delete this._timeOut[E.tId];}D=true;}}else{if(E.xdr){E.conn.abort(E.tId);D=true;}}}else{if(E.upload){var C="yuiIO"+E.tId;var F=document.getElementById(C);if(F){YAHOO.util.Event.removeListener(F,"load");document.body.removeChild(F);if(A){window.clearTimeout(this._timeOut[E.tId]);delete this._timeOut[E.tId];}D=true;}}else{D=false;}}if(D===true){this.abortEvent.fire(E,B);if(E.abortEvent){E.abortEvent.fire(E,B);}this.handleTransactionResponse(E,G,true);}return D;},isCallInProgress:function(A){A=A||{};if(A.xhr&&A.conn){return A.conn.readyState!==4&&A.conn.readyState!==0;}else{if(A.xdr&&A.conn){return A.conn.isCallInProgress(A.tId);}else{if(A.upload===true){return document.getElementById("yuiIO"+A.tId)?true:false;}else{return false;}}}},releaseObject:function(A){if(A&&A.conn){A.conn=null;A=null;}}};YAHOO.register("connection_core",YAHOO.util.Connect,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/container/container-min.js b/Websites/bugs.webkit.org/js/yui/container/container-min.js
new file mode 100644
index 0000000..5be17aa
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/container/container-min.js
@@ -0,0 +1,19 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){YAHOO.util.Config=function(d){if(d){this.init(d);}};var b=YAHOO.lang,c=YAHOO.util.CustomEvent,a=YAHOO.util.Config;a.CONFIG_CHANGED_EVENT="configChanged";a.BOOLEAN_TYPE="boolean";a.prototype={owner:null,queueInProgress:false,config:null,initialConfig:null,eventQueue:null,configChangedEvent:null,init:function(d){this.owner=d;this.configChangedEvent=this.createEvent(a.CONFIG_CHANGED_EVENT);this.configChangedEvent.signature=c.LIST;this.queueInProgress=false;this.config={};this.initialConfig={};this.eventQueue=[];},checkBoolean:function(d){return(typeof d==a.BOOLEAN_TYPE);},checkNumber:function(d){return(!isNaN(d));},fireEvent:function(d,f){var e=this.config[d];if(e&&e.event){e.event.fire(f);}},addProperty:function(e,d){e=e.toLowerCase();this.config[e]=d;d.event=this.createEvent(e,{scope:this.owner});d.event.signature=c.LIST;d.key=e;if(d.handler){d.event.subscribe(d.handler,this.owner);}this.setProperty(e,d.value,true);if(!d.suppressEvent){this.queueProperty(e,d.value);}},getConfig:function(){var d={},f=this.config,g,e;for(g in f){if(b.hasOwnProperty(f,g)){e=f[g];if(e&&e.event){d[g]=e.value;}}}return d;},getProperty:function(d){var e=this.config[d.toLowerCase()];if(e&&e.event){return e.value;}else{return undefined;}},resetProperty:function(d){d=d.toLowerCase();var e=this.config[d];if(e&&e.event){if(d in this.initialConfig){this.setProperty(d,this.initialConfig[d]);return true;}}else{return false;}},setProperty:function(e,g,d){var f;e=e.toLowerCase();if(this.queueInProgress&&!d){this.queueProperty(e,g);return true;}else{f=this.config[e];if(f&&f.event){if(f.validator&&!f.validator(g)){return false;}else{f.value=g;if(!d){this.fireEvent(e,g);this.configChangedEvent.fire([e,g]);}return true;}}else{return false;}}},queueProperty:function(v,r){v=v.toLowerCase();var u=this.config[v],l=false,k,g,h,j,p,t,f,n,o,d,m,w,e;if(u&&u.event){if(!b.isUndefined(r)&&u.validator&&!u.validator(r)){return false;}else{if(!b.isUndefined(r)){u.value=r;}else{r=u.value;}l=false;k=this.eventQueue.length;for(m=0;m<k;m++){g=this.eventQueue[m];if(g){h=g[0];j=g[1];if(h==v){this.eventQueue[m]=null;this.eventQueue.push([v,(!b.isUndefined(r)?r:j)]);l=true;break;}}}if(!l&&!b.isUndefined(r)){this.eventQueue.push([v,r]);}}if(u.supercedes){p=u.supercedes.length;for(w=0;w<p;w++){t=u.supercedes[w];f=this.eventQueue.length;for(e=0;e<f;e++){n=this.eventQueue[e];if(n){o=n[0];d=n[1];if(o==t.toLowerCase()){this.eventQueue.push([o,d]);this.eventQueue[e]=null;break;}}}}}return true;}else{return false;}},refireEvent:function(d){d=d.toLowerCase();var e=this.config[d];if(e&&e.event&&!b.isUndefined(e.value)){if(this.queueInProgress){this.queueProperty(d);}else{this.fireEvent(d,e.value);}}},applyConfig:function(d,g){var f,e;if(g){e={};for(f in d){if(b.hasOwnProperty(d,f)){e[f.toLowerCase()]=d[f];}}this.initialConfig=e;}for(f in d){if(b.hasOwnProperty(d,f)){this.queueProperty(f,d[f]);}}},refresh:function(){var d;for(d in this.config){if(b.hasOwnProperty(this.config,d)){this.refireEvent(d);}}},fireQueue:function(){var e,h,d,g,f;this.queueInProgress=true;for(e=0;e<this.eventQueue.length;e++){h=this.eventQueue[e];if(h){d=h[0];g=h[1];f=this.config[d];f.value=g;this.eventQueue[e]=null;this.fireEvent(d,g);}}this.queueInProgress=false;this.eventQueue=[];},subscribeToConfigEvent:function(d,e,g,h){var f=this.config[d.toLowerCase()];if(f&&f.event){if(!a.alreadySubscribed(f.event,e,g)){f.event.subscribe(e,g,h);}return true;}else{return false;}},unsubscribeFromConfigEvent:function(d,e,g){var f=this.config[d.toLowerCase()];if(f&&f.event){return f.event.unsubscribe(e,g);}else{return false;}},toString:function(){var d="Config";if(this.owner){d+=" ["+this.owner.toString()+"]";}return d;},outputEventQueue:function(){var d="",g,e,f=this.eventQueue.length;for(e=0;e<f;e++){g=this.eventQueue[e];if(g){d+=g[0]+"="+g[1]+", ";}}return d;},destroy:function(){var e=this.config,d,f;for(d in e){if(b.hasOwnProperty(e,d)){f=e[d];f.event.unsubscribeAll();f.event=null;}}this.configChangedEvent.unsubscribeAll();this.configChangedEvent=null;this.owner=null;this.config=null;this.initialConfig=null;this.eventQueue=null;}};a.alreadySubscribed=function(e,h,j){var f=e.subscribers.length,d,g;if(f>0){g=f-1;do{d=e.subscribers[g];if(d&&d.obj==j&&d.fn==h){return true;}}while(g--);}return false;};YAHOO.lang.augmentProto(a,YAHOO.util.EventProvider);}());(function(){YAHOO.widget.Module=function(r,q){if(r){this.init(r,q);}else{}};var f=YAHOO.util.Dom,d=YAHOO.util.Config,n=YAHOO.util.Event,m=YAHOO.util.CustomEvent,g=YAHOO.widget.Module,i=YAHOO.env.ua,h,p,o,e,a={"BEFORE_INIT":"beforeInit","INIT":"init","APPEND":"append","BEFORE_RENDER":"beforeRender","RENDER":"render","CHANGE_HEADER":"changeHeader","CHANGE_BODY":"changeBody","CHANGE_FOOTER":"changeFooter","CHANGE_CONTENT":"changeContent","DESTROY":"destroy","BEFORE_SHOW":"beforeShow","SHOW":"show","BEFORE_HIDE":"beforeHide","HIDE":"hide"},j={"VISIBLE":{key:"visible",value:true,validator:YAHOO.lang.isBoolean},"EFFECT":{key:"effect",suppressEvent:true,supercedes:["visible"]},"MONITOR_RESIZE":{key:"monitorresize",value:true},"APPEND_TO_DOCUMENT_BODY":{key:"appendtodocumentbody",value:false}};g.IMG_ROOT=null;g.IMG_ROOT_SSL=null;g.CSS_MODULE="yui-module";g.CSS_HEADER="hd";g.CSS_BODY="bd";g.CSS_FOOTER="ft";g.RESIZE_MONITOR_SECURE_URL="javascript:false;";g.RESIZE_MONITOR_BUFFER=1;g.textResizeEvent=new m("textResize");g.forceDocumentRedraw=function(){var q=document.documentElement;if(q){q.className+=" ";q.className=YAHOO.lang.trim(q.className);}};function l(){if(!h){h=document.createElement("div");h.innerHTML=('<div class="'+g.CSS_HEADER+'"></div>'+'<div class="'+g.CSS_BODY+'"></div><div class="'+g.CSS_FOOTER+'"></div>');p=h.firstChild;o=p.nextSibling;e=o.nextSibling;}return h;}function k(){if(!p){l();}return(p.cloneNode(false));}function b(){if(!o){l();}return(o.cloneNode(false));}function c(){if(!e){l();}return(e.cloneNode(false));}g.prototype={constructor:g,element:null,header:null,body:null,footer:null,id:null,imageRoot:g.IMG_ROOT,initEvents:function(){var q=m.LIST;
+this.beforeInitEvent=this.createEvent(a.BEFORE_INIT);this.beforeInitEvent.signature=q;this.initEvent=this.createEvent(a.INIT);this.initEvent.signature=q;this.appendEvent=this.createEvent(a.APPEND);this.appendEvent.signature=q;this.beforeRenderEvent=this.createEvent(a.BEFORE_RENDER);this.beforeRenderEvent.signature=q;this.renderEvent=this.createEvent(a.RENDER);this.renderEvent.signature=q;this.changeHeaderEvent=this.createEvent(a.CHANGE_HEADER);this.changeHeaderEvent.signature=q;this.changeBodyEvent=this.createEvent(a.CHANGE_BODY);this.changeBodyEvent.signature=q;this.changeFooterEvent=this.createEvent(a.CHANGE_FOOTER);this.changeFooterEvent.signature=q;this.changeContentEvent=this.createEvent(a.CHANGE_CONTENT);this.changeContentEvent.signature=q;this.destroyEvent=this.createEvent(a.DESTROY);this.destroyEvent.signature=q;this.beforeShowEvent=this.createEvent(a.BEFORE_SHOW);this.beforeShowEvent.signature=q;this.showEvent=this.createEvent(a.SHOW);this.showEvent.signature=q;this.beforeHideEvent=this.createEvent(a.BEFORE_HIDE);this.beforeHideEvent.signature=q;this.hideEvent=this.createEvent(a.HIDE);this.hideEvent.signature=q;},platform:function(){var q=navigator.userAgent.toLowerCase();if(q.indexOf("windows")!=-1||q.indexOf("win32")!=-1){return"windows";}else{if(q.indexOf("macintosh")!=-1){return"mac";}else{return false;}}}(),browser:function(){var q=navigator.userAgent.toLowerCase();if(q.indexOf("opera")!=-1){return"opera";}else{if(q.indexOf("msie 7")!=-1){return"ie7";}else{if(q.indexOf("msie")!=-1){return"ie";}else{if(q.indexOf("safari")!=-1){return"safari";}else{if(q.indexOf("gecko")!=-1){return"gecko";}else{return false;}}}}}}(),isSecure:function(){if(window.location.href.toLowerCase().indexOf("https")===0){return true;}else{return false;}}(),initDefaultConfig:function(){this.cfg.addProperty(j.VISIBLE.key,{handler:this.configVisible,value:j.VISIBLE.value,validator:j.VISIBLE.validator});this.cfg.addProperty(j.EFFECT.key,{handler:this.configEffect,suppressEvent:j.EFFECT.suppressEvent,supercedes:j.EFFECT.supercedes});this.cfg.addProperty(j.MONITOR_RESIZE.key,{handler:this.configMonitorResize,value:j.MONITOR_RESIZE.value});this.cfg.addProperty(j.APPEND_TO_DOCUMENT_BODY.key,{value:j.APPEND_TO_DOCUMENT_BODY.value});},init:function(v,u){var s,w;this.initEvents();this.beforeInitEvent.fire(g);this.cfg=new d(this);if(this.isSecure){this.imageRoot=g.IMG_ROOT_SSL;}if(typeof v=="string"){s=v;v=document.getElementById(v);if(!v){v=(l()).cloneNode(false);v.id=s;}}this.id=f.generateId(v);this.element=v;w=this.element.firstChild;if(w){var r=false,q=false,t=false;do{if(1==w.nodeType){if(!r&&f.hasClass(w,g.CSS_HEADER)){this.header=w;r=true;}else{if(!q&&f.hasClass(w,g.CSS_BODY)){this.body=w;q=true;}else{if(!t&&f.hasClass(w,g.CSS_FOOTER)){this.footer=w;t=true;}}}}}while((w=w.nextSibling));}this.initDefaultConfig();f.addClass(this.element,g.CSS_MODULE);if(u){this.cfg.applyConfig(u,true);}if(!d.alreadySubscribed(this.renderEvent,this.cfg.fireQueue,this.cfg)){this.renderEvent.subscribe(this.cfg.fireQueue,this.cfg,true);}this.initEvent.fire(g);},initResizeMonitor:function(){var r=(i.gecko&&this.platform=="windows");if(r){var q=this;setTimeout(function(){q._initResizeMonitor();},0);}else{this._initResizeMonitor();}},_initResizeMonitor:function(){var q,s,u;function w(){g.textResizeEvent.fire();}if(!i.opera){s=f.get("_yuiResizeMonitor");var v=this._supportsCWResize();if(!s){s=document.createElement("iframe");if(this.isSecure&&g.RESIZE_MONITOR_SECURE_URL&&i.ie){s.src=g.RESIZE_MONITOR_SECURE_URL;}if(!v){u=["<html><head><script ",'type="text/javascript">',"window.onresize=function(){window.parent.","YAHOO.widget.Module.textResizeEvent.","fire();};<","/script></head>","<body></body></html>"].join("");s.src="data:text/html;charset=utf-8,"+encodeURIComponent(u);}s.id="_yuiResizeMonitor";s.title="Text Resize Monitor";s.tabIndex=-1;s.setAttribute("role","presentation");s.style.position="absolute";s.style.visibility="hidden";var r=document.body,t=r.firstChild;if(t){r.insertBefore(s,t);}else{r.appendChild(s);}s.style.backgroundColor="transparent";s.style.borderWidth="0";s.style.width="2em";s.style.height="2em";s.style.left="0";s.style.top=(-1*(s.offsetHeight+g.RESIZE_MONITOR_BUFFER))+"px";s.style.visibility="visible";if(i.webkit){q=s.contentWindow.document;q.open();q.close();}}if(s&&s.contentWindow){g.textResizeEvent.subscribe(this.onDomResize,this,true);if(!g.textResizeInitialized){if(v){if(!n.on(s.contentWindow,"resize",w)){n.on(s,"resize",w);}}g.textResizeInitialized=true;}this.resizeMonitor=s;}}},_supportsCWResize:function(){var q=true;if(i.gecko&&i.gecko<=1.8){q=false;}return q;},onDomResize:function(s,r){var q=-1*(this.resizeMonitor.offsetHeight+g.RESIZE_MONITOR_BUFFER);this.resizeMonitor.style.top=q+"px";this.resizeMonitor.style.left="0";},setHeader:function(r){var q=this.header||(this.header=k());if(r.nodeName){q.innerHTML="";q.appendChild(r);}else{q.innerHTML=r;}if(this._rendered){this._renderHeader();}this.changeHeaderEvent.fire(r);this.changeContentEvent.fire();},appendToHeader:function(r){var q=this.header||(this.header=k());q.appendChild(r);this.changeHeaderEvent.fire(r);this.changeContentEvent.fire();},setBody:function(r){var q=this.body||(this.body=b());if(r.nodeName){q.innerHTML="";q.appendChild(r);}else{q.innerHTML=r;}if(this._rendered){this._renderBody();}this.changeBodyEvent.fire(r);this.changeContentEvent.fire();},appendToBody:function(r){var q=this.body||(this.body=b());q.appendChild(r);this.changeBodyEvent.fire(r);this.changeContentEvent.fire();},setFooter:function(r){var q=this.footer||(this.footer=c());if(r.nodeName){q.innerHTML="";q.appendChild(r);}else{q.innerHTML=r;}if(this._rendered){this._renderFooter();}this.changeFooterEvent.fire(r);this.changeContentEvent.fire();},appendToFooter:function(r){var q=this.footer||(this.footer=c());q.appendChild(r);this.changeFooterEvent.fire(r);this.changeContentEvent.fire();},render:function(s,q){var t=this;function r(u){if(typeof u=="string"){u=document.getElementById(u);
+}if(u){t._addToParent(u,t.element);t.appendEvent.fire();}}this.beforeRenderEvent.fire();if(!q){q=this.element;}if(s){r(s);}else{if(!f.inDocument(this.element)){return false;}}this._renderHeader(q);this._renderBody(q);this._renderFooter(q);this._rendered=true;this.renderEvent.fire();return true;},_renderHeader:function(q){q=q||this.element;if(this.header&&!f.inDocument(this.header)){var r=q.firstChild;if(r){q.insertBefore(this.header,r);}else{q.appendChild(this.header);}}},_renderBody:function(q){q=q||this.element;if(this.body&&!f.inDocument(this.body)){if(this.footer&&f.isAncestor(q,this.footer)){q.insertBefore(this.body,this.footer);}else{q.appendChild(this.body);}}},_renderFooter:function(q){q=q||this.element;if(this.footer&&!f.inDocument(this.footer)){q.appendChild(this.footer);}},destroy:function(q){var r,s=!(q);if(this.element){n.purgeElement(this.element,s);r=this.element.parentNode;}if(r){r.removeChild(this.element);}this.element=null;this.header=null;this.body=null;this.footer=null;g.textResizeEvent.unsubscribe(this.onDomResize,this);this.cfg.destroy();this.cfg=null;this.destroyEvent.fire();},show:function(){this.cfg.setProperty("visible",true);},hide:function(){this.cfg.setProperty("visible",false);},configVisible:function(r,q,s){var t=q[0];if(t){if(this.beforeShowEvent.fire()){f.setStyle(this.element,"display","block");this.showEvent.fire();}}else{if(this.beforeHideEvent.fire()){f.setStyle(this.element,"display","none");this.hideEvent.fire();}}},configEffect:function(r,q,s){this._cachedEffects=(this.cacheEffects)?this._createEffects(q[0]):null;},cacheEffects:true,_createEffects:function(t){var q=null,u,r,s;if(t){if(t instanceof Array){q=[];u=t.length;for(r=0;r<u;r++){s=t[r];if(s.effect){q[q.length]=s.effect(this,s.duration);}}}else{if(t.effect){q=[t.effect(this,t.duration)];}}}return q;},configMonitorResize:function(s,r,t){var q=r[0];if(q){this.initResizeMonitor();}else{g.textResizeEvent.unsubscribe(this.onDomResize,this,true);this.resizeMonitor=null;}},_addToParent:function(q,r){if(!this.cfg.getProperty("appendtodocumentbody")&&q===document.body&&q.firstChild){q.insertBefore(r,q.firstChild);}else{q.appendChild(r);}},toString:function(){return"Module "+this.id;}};YAHOO.lang.augmentProto(g,YAHOO.util.EventProvider);}());(function(){YAHOO.widget.Overlay=function(p,o){YAHOO.widget.Overlay.superclass.constructor.call(this,p,o);};var i=YAHOO.lang,m=YAHOO.util.CustomEvent,g=YAHOO.widget.Module,n=YAHOO.util.Event,f=YAHOO.util.Dom,d=YAHOO.util.Config,k=YAHOO.env.ua,b=YAHOO.widget.Overlay,h="subscribe",e="unsubscribe",c="contained",j,a={"BEFORE_MOVE":"beforeMove","MOVE":"move"},l={"X":{key:"x",validator:i.isNumber,suppressEvent:true,supercedes:["iframe"]},"Y":{key:"y",validator:i.isNumber,suppressEvent:true,supercedes:["iframe"]},"XY":{key:"xy",suppressEvent:true,supercedes:["iframe"]},"CONTEXT":{key:"context",suppressEvent:true,supercedes:["iframe"]},"FIXED_CENTER":{key:"fixedcenter",value:false,supercedes:["iframe","visible"]},"WIDTH":{key:"width",suppressEvent:true,supercedes:["context","fixedcenter","iframe"]},"HEIGHT":{key:"height",suppressEvent:true,supercedes:["context","fixedcenter","iframe"]},"AUTO_FILL_HEIGHT":{key:"autofillheight",supercedes:["height"],value:"body"},"ZINDEX":{key:"zindex",value:null},"CONSTRAIN_TO_VIEWPORT":{key:"constraintoviewport",value:false,validator:i.isBoolean,supercedes:["iframe","x","y","xy"]},"IFRAME":{key:"iframe",value:(k.ie==6?true:false),validator:i.isBoolean,supercedes:["zindex"]},"PREVENT_CONTEXT_OVERLAP":{key:"preventcontextoverlap",value:false,validator:i.isBoolean,supercedes:["constraintoviewport"]}};b.IFRAME_SRC="javascript:false;";b.IFRAME_OFFSET=3;b.VIEWPORT_OFFSET=10;b.TOP_LEFT="tl";b.TOP_RIGHT="tr";b.BOTTOM_LEFT="bl";b.BOTTOM_RIGHT="br";b.PREVENT_OVERLAP_X={"tltr":true,"blbr":true,"brbl":true,"trtl":true};b.PREVENT_OVERLAP_Y={"trbr":true,"tlbl":true,"bltl":true,"brtr":true};b.CSS_OVERLAY="yui-overlay";b.CSS_HIDDEN="yui-overlay-hidden";b.CSS_IFRAME="yui-overlay-iframe";b.STD_MOD_RE=/^\s*?(body|footer|header)\s*?$/i;b.windowScrollEvent=new m("windowScroll");b.windowResizeEvent=new m("windowResize");b.windowScrollHandler=function(p){var o=n.getTarget(p);if(!o||o===window||o===window.document){if(k.ie){if(!window.scrollEnd){window.scrollEnd=-1;}clearTimeout(window.scrollEnd);window.scrollEnd=setTimeout(function(){b.windowScrollEvent.fire();},1);}else{b.windowScrollEvent.fire();}}};b.windowResizeHandler=function(o){if(k.ie){if(!window.resizeEnd){window.resizeEnd=-1;}clearTimeout(window.resizeEnd);window.resizeEnd=setTimeout(function(){b.windowResizeEvent.fire();},100);}else{b.windowResizeEvent.fire();}};b._initialized=null;if(b._initialized===null){n.on(window,"scroll",b.windowScrollHandler);n.on(window,"resize",b.windowResizeHandler);b._initialized=true;}b._TRIGGER_MAP={"windowScroll":b.windowScrollEvent,"windowResize":b.windowResizeEvent,"textResize":g.textResizeEvent};YAHOO.extend(b,g,{CONTEXT_TRIGGERS:[],init:function(p,o){b.superclass.init.call(this,p);this.beforeInitEvent.fire(b);f.addClass(this.element,b.CSS_OVERLAY);if(o){this.cfg.applyConfig(o,true);}if(this.platform=="mac"&&k.gecko){if(!d.alreadySubscribed(this.showEvent,this.showMacGeckoScrollbars,this)){this.showEvent.subscribe(this.showMacGeckoScrollbars,this,true);}if(!d.alreadySubscribed(this.hideEvent,this.hideMacGeckoScrollbars,this)){this.hideEvent.subscribe(this.hideMacGeckoScrollbars,this,true);}}this.initEvent.fire(b);},initEvents:function(){b.superclass.initEvents.call(this);var o=m.LIST;this.beforeMoveEvent=this.createEvent(a.BEFORE_MOVE);this.beforeMoveEvent.signature=o;this.moveEvent=this.createEvent(a.MOVE);this.moveEvent.signature=o;},initDefaultConfig:function(){b.superclass.initDefaultConfig.call(this);var o=this.cfg;o.addProperty(l.X.key,{handler:this.configX,validator:l.X.validator,suppressEvent:l.X.suppressEvent,supercedes:l.X.supercedes});o.addProperty(l.Y.key,{handler:this.configY,validator:l.Y.validator,suppressEvent:l.Y.suppressEvent,supercedes:l.Y.supercedes});
+o.addProperty(l.XY.key,{handler:this.configXY,suppressEvent:l.XY.suppressEvent,supercedes:l.XY.supercedes});o.addProperty(l.CONTEXT.key,{handler:this.configContext,suppressEvent:l.CONTEXT.suppressEvent,supercedes:l.CONTEXT.supercedes});o.addProperty(l.FIXED_CENTER.key,{handler:this.configFixedCenter,value:l.FIXED_CENTER.value,validator:l.FIXED_CENTER.validator,supercedes:l.FIXED_CENTER.supercedes});o.addProperty(l.WIDTH.key,{handler:this.configWidth,suppressEvent:l.WIDTH.suppressEvent,supercedes:l.WIDTH.supercedes});o.addProperty(l.HEIGHT.key,{handler:this.configHeight,suppressEvent:l.HEIGHT.suppressEvent,supercedes:l.HEIGHT.supercedes});o.addProperty(l.AUTO_FILL_HEIGHT.key,{handler:this.configAutoFillHeight,value:l.AUTO_FILL_HEIGHT.value,validator:this._validateAutoFill,supercedes:l.AUTO_FILL_HEIGHT.supercedes});o.addProperty(l.ZINDEX.key,{handler:this.configzIndex,value:l.ZINDEX.value});o.addProperty(l.CONSTRAIN_TO_VIEWPORT.key,{handler:this.configConstrainToViewport,value:l.CONSTRAIN_TO_VIEWPORT.value,validator:l.CONSTRAIN_TO_VIEWPORT.validator,supercedes:l.CONSTRAIN_TO_VIEWPORT.supercedes});o.addProperty(l.IFRAME.key,{handler:this.configIframe,value:l.IFRAME.value,validator:l.IFRAME.validator,supercedes:l.IFRAME.supercedes});o.addProperty(l.PREVENT_CONTEXT_OVERLAP.key,{value:l.PREVENT_CONTEXT_OVERLAP.value,validator:l.PREVENT_CONTEXT_OVERLAP.validator,supercedes:l.PREVENT_CONTEXT_OVERLAP.supercedes});},moveTo:function(o,p){this.cfg.setProperty("xy",[o,p]);},hideMacGeckoScrollbars:function(){f.replaceClass(this.element,"show-scrollbars","hide-scrollbars");},showMacGeckoScrollbars:function(){f.replaceClass(this.element,"hide-scrollbars","show-scrollbars");},_setDomVisibility:function(o){f.setStyle(this.element,"visibility",(o)?"visible":"hidden");var p=b.CSS_HIDDEN;if(o){f.removeClass(this.element,p);}else{f.addClass(this.element,p);}},configVisible:function(x,w,t){var p=w[0],B=f.getStyle(this.element,"visibility"),o=this._cachedEffects||this._createEffects(this.cfg.getProperty("effect")),A=(this.platform=="mac"&&k.gecko),y=d.alreadySubscribed,q,v,s,r,u,z;if(B=="inherit"){v=this.element.parentNode;while(v.nodeType!=9&&v.nodeType!=11){B=f.getStyle(v,"visibility");if(B!="inherit"){break;}v=v.parentNode;}if(B=="inherit"){B="visible";}}if(p){if(A){this.showMacGeckoScrollbars();}if(o){if(p){if(B!="visible"||B===""||this._fadingOut){if(this.beforeShowEvent.fire()){z=o.length;for(s=0;s<z;s++){q=o[s];if(s===0&&!y(q.animateInCompleteEvent,this.showEvent.fire,this.showEvent)){q.animateInCompleteEvent.subscribe(this.showEvent.fire,this.showEvent,true);}q.animateIn();}}}}}else{if(B!="visible"||B===""){if(this.beforeShowEvent.fire()){this._setDomVisibility(true);this.cfg.refireEvent("iframe");this.showEvent.fire();}}else{this._setDomVisibility(true);}}}else{if(A){this.hideMacGeckoScrollbars();}if(o){if(B=="visible"||this._fadingIn){if(this.beforeHideEvent.fire()){z=o.length;for(r=0;r<z;r++){u=o[r];if(r===0&&!y(u.animateOutCompleteEvent,this.hideEvent.fire,this.hideEvent)){u.animateOutCompleteEvent.subscribe(this.hideEvent.fire,this.hideEvent,true);}u.animateOut();}}}else{if(B===""){this._setDomVisibility(false);}}}else{if(B=="visible"||B===""){if(this.beforeHideEvent.fire()){this._setDomVisibility(false);this.hideEvent.fire();}}else{this._setDomVisibility(false);}}}},doCenterOnDOMEvent:function(){var o=this.cfg,p=o.getProperty("fixedcenter");if(o.getProperty("visible")){if(p&&(p!==c||this.fitsInViewport())){this.center();}}},fitsInViewport:function(){var s=b.VIEWPORT_OFFSET,q=this.element,t=q.offsetWidth,r=q.offsetHeight,o=f.getViewportWidth(),p=f.getViewportHeight();return((t+s<o)&&(r+s<p));},configFixedCenter:function(s,q,t){var u=q[0],p=d.alreadySubscribed,r=b.windowResizeEvent,o=b.windowScrollEvent;if(u){this.center();if(!p(this.beforeShowEvent,this.center)){this.beforeShowEvent.subscribe(this.center);}if(!p(r,this.doCenterOnDOMEvent,this)){r.subscribe(this.doCenterOnDOMEvent,this,true);}if(!p(o,this.doCenterOnDOMEvent,this)){o.subscribe(this.doCenterOnDOMEvent,this,true);}}else{this.beforeShowEvent.unsubscribe(this.center);r.unsubscribe(this.doCenterOnDOMEvent,this);o.unsubscribe(this.doCenterOnDOMEvent,this);}},configHeight:function(r,p,s){var o=p[0],q=this.element;f.setStyle(q,"height",o);this.cfg.refireEvent("iframe");},configAutoFillHeight:function(t,s,p){var v=s[0],q=this.cfg,u="autofillheight",w="height",r=q.getProperty(u),o=this._autoFillOnHeightChange;q.unsubscribeFromConfigEvent(w,o);g.textResizeEvent.unsubscribe(o);this.changeContentEvent.unsubscribe(o);if(r&&v!==r&&this[r]){f.setStyle(this[r],w,"");}if(v){v=i.trim(v.toLowerCase());q.subscribeToConfigEvent(w,o,this[v],this);g.textResizeEvent.subscribe(o,this[v],this);this.changeContentEvent.subscribe(o,this[v],this);q.setProperty(u,v,true);}},configWidth:function(r,o,s){var q=o[0],p=this.element;f.setStyle(p,"width",q);this.cfg.refireEvent("iframe");},configzIndex:function(q,o,r){var s=o[0],p=this.element;if(!s){s=f.getStyle(p,"zIndex");if(!s||isNaN(s)){s=0;}}if(this.iframe||this.cfg.getProperty("iframe")===true){if(s<=0){s=1;}}f.setStyle(p,"zIndex",s);this.cfg.setProperty("zIndex",s,true);if(this.iframe){this.stackIframe();}},configXY:function(q,p,r){var t=p[0],o=t[0],s=t[1];this.cfg.setProperty("x",o);this.cfg.setProperty("y",s);this.beforeMoveEvent.fire([o,s]);o=this.cfg.getProperty("x");s=this.cfg.getProperty("y");this.cfg.refireEvent("iframe");this.moveEvent.fire([o,s]);},configX:function(q,p,r){var o=p[0],s=this.cfg.getProperty("y");this.cfg.setProperty("x",o,true);this.cfg.setProperty("y",s,true);this.beforeMoveEvent.fire([o,s]);o=this.cfg.getProperty("x");s=this.cfg.getProperty("y");f.setX(this.element,o,true);this.cfg.setProperty("xy",[o,s],true);this.cfg.refireEvent("iframe");this.moveEvent.fire([o,s]);},configY:function(q,p,r){var o=this.cfg.getProperty("x"),s=p[0];this.cfg.setProperty("x",o,true);this.cfg.setProperty("y",s,true);this.beforeMoveEvent.fire([o,s]);o=this.cfg.getProperty("x");s=this.cfg.getProperty("y");f.setY(this.element,s,true);
+this.cfg.setProperty("xy",[o,s],true);this.cfg.refireEvent("iframe");this.moveEvent.fire([o,s]);},showIframe:function(){var p=this.iframe,o;if(p){o=this.element.parentNode;if(o!=p.parentNode){this._addToParent(o,p);}p.style.display="block";}},hideIframe:function(){if(this.iframe){this.iframe.style.display="none";}},syncIframe:function(){var o=this.iframe,q=this.element,s=b.IFRAME_OFFSET,p=(s*2),r;if(o){o.style.width=(q.offsetWidth+p+"px");o.style.height=(q.offsetHeight+p+"px");r=this.cfg.getProperty("xy");if(!i.isArray(r)||(isNaN(r[0])||isNaN(r[1]))){this.syncPosition();r=this.cfg.getProperty("xy");}f.setXY(o,[(r[0]-s),(r[1]-s)]);}},stackIframe:function(){if(this.iframe){var o=f.getStyle(this.element,"zIndex");if(!YAHOO.lang.isUndefined(o)&&!isNaN(o)){f.setStyle(this.iframe,"zIndex",(o-1));}}},configIframe:function(r,q,s){var o=q[0];function t(){var v=this.iframe,w=this.element,x;if(!v){if(!j){j=document.createElement("iframe");if(this.isSecure){j.src=b.IFRAME_SRC;}if(k.ie){j.style.filter="alpha(opacity=0)";j.frameBorder=0;}else{j.style.opacity="0";}j.style.position="absolute";j.style.border="none";j.style.margin="0";j.style.padding="0";j.style.display="none";j.tabIndex=-1;j.className=b.CSS_IFRAME;}v=j.cloneNode(false);v.id=this.id+"_f";x=w.parentNode;var u=x||document.body;this._addToParent(u,v);this.iframe=v;}this.showIframe();this.syncIframe();this.stackIframe();if(!this._hasIframeEventListeners){this.showEvent.subscribe(this.showIframe);this.hideEvent.subscribe(this.hideIframe);this.changeContentEvent.subscribe(this.syncIframe);this._hasIframeEventListeners=true;}}function p(){t.call(this);this.beforeShowEvent.unsubscribe(p);this._iframeDeferred=false;}if(o){if(this.cfg.getProperty("visible")){t.call(this);}else{if(!this._iframeDeferred){this.beforeShowEvent.subscribe(p);this._iframeDeferred=true;}}}else{this.hideIframe();if(this._hasIframeEventListeners){this.showEvent.unsubscribe(this.showIframe);this.hideEvent.unsubscribe(this.hideIframe);this.changeContentEvent.unsubscribe(this.syncIframe);this._hasIframeEventListeners=false;}}},_primeXYFromDOM:function(){if(YAHOO.lang.isUndefined(this.cfg.getProperty("xy"))){this.syncPosition();this.cfg.refireEvent("xy");this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);}},configConstrainToViewport:function(p,o,q){var r=o[0];if(r){if(!d.alreadySubscribed(this.beforeMoveEvent,this.enforceConstraints,this)){this.beforeMoveEvent.subscribe(this.enforceConstraints,this,true);}if(!d.alreadySubscribed(this.beforeShowEvent,this._primeXYFromDOM)){this.beforeShowEvent.subscribe(this._primeXYFromDOM);}}else{this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);this.beforeMoveEvent.unsubscribe(this.enforceConstraints,this);}},configContext:function(u,t,q){var x=t[0],r,o,v,s,p,w=this.CONTEXT_TRIGGERS;if(x){r=x[0];o=x[1];v=x[2];s=x[3];p=x[4];if(w&&w.length>0){s=(s||[]).concat(w);}if(r){if(typeof r=="string"){this.cfg.setProperty("context",[document.getElementById(r),o,v,s,p],true);}if(o&&v){this.align(o,v,p);}if(this._contextTriggers){this._processTriggers(this._contextTriggers,e,this._alignOnTrigger);}if(s){this._processTriggers(s,h,this._alignOnTrigger);this._contextTriggers=s;}}}},_alignOnTrigger:function(p,o){this.align();},_findTriggerCE:function(o){var p=null;if(o instanceof m){p=o;}else{if(b._TRIGGER_MAP[o]){p=b._TRIGGER_MAP[o];}}return p;},_processTriggers:function(s,v,r){var q,u;for(var p=0,o=s.length;p<o;++p){q=s[p];u=this._findTriggerCE(q);if(u){u[v](r,this,true);}else{this[v](q,r);}}},align:function(p,w,s){var v=this.cfg.getProperty("context"),t=this,o,q,u;function r(z,A){var y=null,x=null;switch(p){case b.TOP_LEFT:y=A;x=z;break;case b.TOP_RIGHT:y=A-q.offsetWidth;x=z;break;case b.BOTTOM_LEFT:y=A;x=z-q.offsetHeight;break;case b.BOTTOM_RIGHT:y=A-q.offsetWidth;x=z-q.offsetHeight;break;}if(y!==null&&x!==null){if(s){y+=s[0];x+=s[1];}t.moveTo(y,x);}}if(v){o=v[0];q=this.element;t=this;if(!p){p=v[1];}if(!w){w=v[2];}if(!s&&v[4]){s=v[4];}if(q&&o){u=f.getRegion(o);switch(w){case b.TOP_LEFT:r(u.top,u.left);break;case b.TOP_RIGHT:r(u.top,u.right);break;case b.BOTTOM_LEFT:r(u.bottom,u.left);break;case b.BOTTOM_RIGHT:r(u.bottom,u.right);break;}}}},enforceConstraints:function(p,o,q){var s=o[0];var r=this.getConstrainedXY(s[0],s[1]);this.cfg.setProperty("x",r[0],true);this.cfg.setProperty("y",r[1],true);this.cfg.setProperty("xy",r,true);},_getConstrainedPos:function(y,p){var t=this.element,r=b.VIEWPORT_OFFSET,A=(y=="x"),z=(A)?t.offsetWidth:t.offsetHeight,s=(A)?f.getViewportWidth():f.getViewportHeight(),D=(A)?f.getDocumentScrollLeft():f.getDocumentScrollTop(),C=(A)?b.PREVENT_OVERLAP_X:b.PREVENT_OVERLAP_Y,o=this.cfg.getProperty("context"),u=(z+r<s),w=this.cfg.getProperty("preventcontextoverlap")&&o&&C[(o[1]+o[2])],v=D+r,B=D+s-z-r,q=p;if(p<v||p>B){if(w){q=this._preventOverlap(y,o[0],z,s,D);}else{if(u){if(p<v){q=v;}else{if(p>B){q=B;}}}else{q=v;}}}return q;},_preventOverlap:function(y,w,z,u,C){var A=(y=="x"),t=b.VIEWPORT_OFFSET,s=this,q=((A)?f.getX(w):f.getY(w))-C,o=(A)?w.offsetWidth:w.offsetHeight,p=q-t,r=(u-(q+o))-t,D=false,v=function(){var x;if((s.cfg.getProperty(y)-C)>q){x=(q-z);}else{x=(q+o);}s.cfg.setProperty(y,(x+C),true);return x;},B=function(){var E=((s.cfg.getProperty(y)-C)>q)?r:p,x;if(z>E){if(D){v();}else{v();D=true;x=B();}}return x;};B();return this.cfg.getProperty(y);},getConstrainedX:function(o){return this._getConstrainedPos("x",o);},getConstrainedY:function(o){return this._getConstrainedPos("y",o);},getConstrainedXY:function(o,p){return[this.getConstrainedX(o),this.getConstrainedY(p)];},center:function(){var r=b.VIEWPORT_OFFSET,s=this.element.offsetWidth,q=this.element.offsetHeight,p=f.getViewportWidth(),t=f.getViewportHeight(),o,u;if(s<p){o=(p/2)-(s/2)+f.getDocumentScrollLeft();}else{o=r+f.getDocumentScrollLeft();}if(q<t){u=(t/2)-(q/2)+f.getDocumentScrollTop();}else{u=r+f.getDocumentScrollTop();}this.cfg.setProperty("xy",[parseInt(o,10),parseInt(u,10)]);this.cfg.refireEvent("iframe");if(k.webkit){this.forceContainerRedraw();}},syncPosition:function(){var o=f.getXY(this.element);
+this.cfg.setProperty("x",o[0],true);this.cfg.setProperty("y",o[1],true);this.cfg.setProperty("xy",o,true);},onDomResize:function(q,p){var o=this;b.superclass.onDomResize.call(this,q,p);setTimeout(function(){o.syncPosition();o.cfg.refireEvent("iframe");o.cfg.refireEvent("context");},0);},_getComputedHeight:(function(){if(document.defaultView&&document.defaultView.getComputedStyle){return function(p){var o=null;if(p.ownerDocument&&p.ownerDocument.defaultView){var q=p.ownerDocument.defaultView.getComputedStyle(p,"");if(q){o=parseInt(q.height,10);}}return(i.isNumber(o))?o:null;};}else{return function(p){var o=null;if(p.style.pixelHeight){o=p.style.pixelHeight;}return(i.isNumber(o))?o:null;};}})(),_validateAutoFillHeight:function(o){return(!o)||(i.isString(o)&&b.STD_MOD_RE.test(o));},_autoFillOnHeightChange:function(r,p,q){var o=this.cfg.getProperty("height");if((o&&o!=="auto")||(o===0)){this.fillHeight(q);}},_getPreciseHeight:function(p){var o=p.offsetHeight;if(p.getBoundingClientRect){var q=p.getBoundingClientRect();o=q.bottom-q.top;}return o;},fillHeight:function(r){if(r){var p=this.innerElement||this.element,o=[this.header,this.body,this.footer],v,w=0,x=0,t=0,q=false;for(var u=0,s=o.length;u<s;u++){v=o[u];if(v){if(r!==v){x+=this._getPreciseHeight(v);}else{q=true;}}}if(q){if(k.ie||k.opera){f.setStyle(r,"height",0+"px");}w=this._getComputedHeight(p);if(w===null){f.addClass(p,"yui-override-padding");w=p.clientHeight;f.removeClass(p,"yui-override-padding");}t=Math.max(w-x,0);f.setStyle(r,"height",t+"px");if(r.offsetHeight!=t){t=Math.max(t-(r.offsetHeight-t),0);}f.setStyle(r,"height",t+"px");}}},bringToTop:function(){var s=[],r=this.element;function v(z,y){var B=f.getStyle(z,"zIndex"),A=f.getStyle(y,"zIndex"),x=(!B||isNaN(B))?0:parseInt(B,10),w=(!A||isNaN(A))?0:parseInt(A,10);if(x>w){return -1;}else{if(x<w){return 1;}else{return 0;}}}function q(y){var x=f.hasClass(y,b.CSS_OVERLAY),w=YAHOO.widget.Panel;if(x&&!f.isAncestor(r,y)){if(w&&f.hasClass(y,w.CSS_PANEL)){s[s.length]=y.parentNode;}else{s[s.length]=y;}}}f.getElementsBy(q,"div",document.body);s.sort(v);var o=s[0],u;if(o){u=f.getStyle(o,"zIndex");if(!isNaN(u)){var t=false;if(o!=r){t=true;}else{if(s.length>1){var p=f.getStyle(s[1],"zIndex");if(!isNaN(p)&&(u==p)){t=true;}}}if(t){this.cfg.setProperty("zindex",(parseInt(u,10)+2));}}}},destroy:function(o){if(this.iframe){this.iframe.parentNode.removeChild(this.iframe);}this.iframe=null;b.windowResizeEvent.unsubscribe(this.doCenterOnDOMEvent,this);b.windowScrollEvent.unsubscribe(this.doCenterOnDOMEvent,this);g.textResizeEvent.unsubscribe(this._autoFillOnHeightChange);if(this._contextTriggers){this._processTriggers(this._contextTriggers,e,this._alignOnTrigger);}b.superclass.destroy.call(this,o);},forceContainerRedraw:function(){var o=this;f.addClass(o.element,"yui-force-redraw");setTimeout(function(){f.removeClass(o.element,"yui-force-redraw");},0);},toString:function(){return"Overlay "+this.id;}});}());(function(){YAHOO.widget.OverlayManager=function(g){this.init(g);};var d=YAHOO.widget.Overlay,c=YAHOO.util.Event,e=YAHOO.util.Dom,b=YAHOO.util.Config,f=YAHOO.util.CustomEvent,a=YAHOO.widget.OverlayManager;a.CSS_FOCUSED="focused";a.prototype={constructor:a,overlays:null,initDefaultConfig:function(){this.cfg.addProperty("overlays",{suppressEvent:true});this.cfg.addProperty("focusevent",{value:"mousedown"});},init:function(i){this.cfg=new b(this);this.initDefaultConfig();if(i){this.cfg.applyConfig(i,true);}this.cfg.fireQueue();var h=null;this.getActive=function(){return h;};this.focus=function(j){var k=this.find(j);if(k){k.focus();}};this.remove=function(k){var m=this.find(k),j;if(m){if(h==m){h=null;}var l=(m.element===null&&m.cfg===null)?true:false;if(!l){j=e.getStyle(m.element,"zIndex");m.cfg.setProperty("zIndex",-1000,true);}this.overlays.sort(this.compareZIndexDesc);this.overlays=this.overlays.slice(0,(this.overlays.length-1));m.hideEvent.unsubscribe(m.blur);m.destroyEvent.unsubscribe(this._onOverlayDestroy,m);m.focusEvent.unsubscribe(this._onOverlayFocusHandler,m);m.blurEvent.unsubscribe(this._onOverlayBlurHandler,m);if(!l){c.removeListener(m.element,this.cfg.getProperty("focusevent"),this._onOverlayElementFocus);m.cfg.setProperty("zIndex",j,true);m.cfg.setProperty("manager",null);}if(m.focusEvent._managed){m.focusEvent=null;}if(m.blurEvent._managed){m.blurEvent=null;}if(m.focus._managed){m.focus=null;}if(m.blur._managed){m.blur=null;}}};this.blurAll=function(){var k=this.overlays.length,j;if(k>0){j=k-1;do{this.overlays[j].blur();}while(j--);}};this._manageBlur=function(j){var k=false;if(h==j){e.removeClass(h.element,a.CSS_FOCUSED);h=null;k=true;}return k;};this._manageFocus=function(j){var k=false;if(h!=j){if(h){h.blur();}h=j;this.bringToTop(h);e.addClass(h.element,a.CSS_FOCUSED);k=true;}return k;};var g=this.cfg.getProperty("overlays");if(!this.overlays){this.overlays=[];}if(g){this.register(g);this.overlays.sort(this.compareZIndexDesc);}},_onOverlayElementFocus:function(i){var g=c.getTarget(i),h=this.close;if(h&&(g==h||e.isAncestor(h,g))){this.blur();}else{this.focus();}},_onOverlayDestroy:function(h,g,i){this.remove(i);},_onOverlayFocusHandler:function(h,g,i){this._manageFocus(i);},_onOverlayBlurHandler:function(h,g,i){this._manageBlur(i);},_bindFocus:function(g){var h=this;if(!g.focusEvent){g.focusEvent=g.createEvent("focus");g.focusEvent.signature=f.LIST;g.focusEvent._managed=true;}else{g.focusEvent.subscribe(h._onOverlayFocusHandler,g,h);}if(!g.focus){c.on(g.element,h.cfg.getProperty("focusevent"),h._onOverlayElementFocus,null,g);g.focus=function(){if(h._manageFocus(this)){if(this.cfg.getProperty("visible")&&this.focusFirst){this.focusFirst();}this.focusEvent.fire();}};g.focus._managed=true;}},_bindBlur:function(g){var h=this;if(!g.blurEvent){g.blurEvent=g.createEvent("blur");g.blurEvent.signature=f.LIST;g.focusEvent._managed=true;}else{g.blurEvent.subscribe(h._onOverlayBlurHandler,g,h);}if(!g.blur){g.blur=function(){if(h._manageBlur(this)){this.blurEvent.fire();}};g.blur._managed=true;}g.hideEvent.subscribe(g.blur);
+},_bindDestroy:function(g){var h=this;g.destroyEvent.subscribe(h._onOverlayDestroy,g,h);},_syncZIndex:function(g){var h=e.getStyle(g.element,"zIndex");if(!isNaN(h)){g.cfg.setProperty("zIndex",parseInt(h,10));}else{g.cfg.setProperty("zIndex",0);}},register:function(g){var k=false,h,j;if(g instanceof d){g.cfg.addProperty("manager",{value:this});this._bindFocus(g);this._bindBlur(g);this._bindDestroy(g);this._syncZIndex(g);this.overlays.push(g);this.bringToTop(g);k=true;}else{if(g instanceof Array){for(h=0,j=g.length;h<j;h++){k=this.register(g[h])||k;}}}return k;},bringToTop:function(m){var i=this.find(m),l,g,j;if(i){j=this.overlays;j.sort(this.compareZIndexDesc);g=j[0];if(g){l=e.getStyle(g.element,"zIndex");if(!isNaN(l)){var k=false;if(g!==i){k=true;}else{if(j.length>1){var h=e.getStyle(j[1].element,"zIndex");if(!isNaN(h)&&(l==h)){k=true;}}}if(k){i.cfg.setProperty("zindex",(parseInt(l,10)+2));}}j.sort(this.compareZIndexDesc);}}},find:function(g){var l=g instanceof d,j=this.overlays,p=j.length,k=null,m,h;if(l||typeof g=="string"){for(h=p-1;h>=0;h--){m=j[h];if((l&&(m===g))||(m.id==g)){k=m;break;}}}return k;},compareZIndexDesc:function(j,i){var h=(j.cfg)?j.cfg.getProperty("zIndex"):null,g=(i.cfg)?i.cfg.getProperty("zIndex"):null;if(h===null&&g===null){return 0;}else{if(h===null){return 1;}else{if(g===null){return -1;}else{if(h>g){return -1;}else{if(h<g){return 1;}else{return 0;}}}}}},showAll:function(){var h=this.overlays,j=h.length,g;for(g=j-1;g>=0;g--){h[g].show();}},hideAll:function(){var h=this.overlays,j=h.length,g;for(g=j-1;g>=0;g--){h[g].hide();}},toString:function(){return"OverlayManager";}};}());(function(){YAHOO.widget.Tooltip=function(p,o){YAHOO.widget.Tooltip.superclass.constructor.call(this,p,o);};var e=YAHOO.lang,n=YAHOO.util.Event,m=YAHOO.util.CustomEvent,c=YAHOO.util.Dom,j=YAHOO.widget.Tooltip,h=YAHOO.env.ua,g=(h.ie&&(h.ie<=6||document.compatMode=="BackCompat")),f,i={"PREVENT_OVERLAP":{key:"preventoverlap",value:true,validator:e.isBoolean,supercedes:["x","y","xy"]},"SHOW_DELAY":{key:"showdelay",value:200,validator:e.isNumber},"AUTO_DISMISS_DELAY":{key:"autodismissdelay",value:5000,validator:e.isNumber},"HIDE_DELAY":{key:"hidedelay",value:250,validator:e.isNumber},"TEXT":{key:"text",suppressEvent:true},"CONTAINER":{key:"container"},"DISABLED":{key:"disabled",value:false,suppressEvent:true},"XY_OFFSET":{key:"xyoffset",value:[0,25],suppressEvent:true}},a={"CONTEXT_MOUSE_OVER":"contextMouseOver","CONTEXT_MOUSE_OUT":"contextMouseOut","CONTEXT_TRIGGER":"contextTrigger"};j.CSS_TOOLTIP="yui-tt";function k(q,o){var p=this.cfg,r=p.getProperty("width");if(r==o){p.setProperty("width",q);}}function d(p,o){if("_originalWidth" in this){k.call(this,this._originalWidth,this._forcedWidth);}var q=document.body,u=this.cfg,t=u.getProperty("width"),r,s;if((!t||t=="auto")&&(u.getProperty("container")!=q||u.getProperty("x")>=c.getViewportWidth()||u.getProperty("y")>=c.getViewportHeight())){s=this.element.cloneNode(true);s.style.visibility="hidden";s.style.top="0px";s.style.left="0px";q.appendChild(s);r=(s.offsetWidth+"px");q.removeChild(s);s=null;u.setProperty("width",r);u.refireEvent("xy");this._originalWidth=t||"";this._forcedWidth=r;}}function b(p,o,q){this.render(q);}function l(){n.onDOMReady(b,this.cfg.getProperty("container"),this);}YAHOO.extend(j,YAHOO.widget.Overlay,{init:function(p,o){j.superclass.init.call(this,p);this.beforeInitEvent.fire(j);c.addClass(this.element,j.CSS_TOOLTIP);if(o){this.cfg.applyConfig(o,true);}this.cfg.queueProperty("visible",false);this.cfg.queueProperty("constraintoviewport",true);this.setBody("");this.subscribe("changeContent",d);this.subscribe("init",l);this.subscribe("render",this.onRender);this.initEvent.fire(j);},initEvents:function(){j.superclass.initEvents.call(this);var o=m.LIST;this.contextMouseOverEvent=this.createEvent(a.CONTEXT_MOUSE_OVER);this.contextMouseOverEvent.signature=o;this.contextMouseOutEvent=this.createEvent(a.CONTEXT_MOUSE_OUT);this.contextMouseOutEvent.signature=o;this.contextTriggerEvent=this.createEvent(a.CONTEXT_TRIGGER);this.contextTriggerEvent.signature=o;},initDefaultConfig:function(){j.superclass.initDefaultConfig.call(this);this.cfg.addProperty(i.PREVENT_OVERLAP.key,{value:i.PREVENT_OVERLAP.value,validator:i.PREVENT_OVERLAP.validator,supercedes:i.PREVENT_OVERLAP.supercedes});this.cfg.addProperty(i.SHOW_DELAY.key,{handler:this.configShowDelay,value:200,validator:i.SHOW_DELAY.validator});this.cfg.addProperty(i.AUTO_DISMISS_DELAY.key,{handler:this.configAutoDismissDelay,value:i.AUTO_DISMISS_DELAY.value,validator:i.AUTO_DISMISS_DELAY.validator});this.cfg.addProperty(i.HIDE_DELAY.key,{handler:this.configHideDelay,value:i.HIDE_DELAY.value,validator:i.HIDE_DELAY.validator});this.cfg.addProperty(i.TEXT.key,{handler:this.configText,suppressEvent:i.TEXT.suppressEvent});this.cfg.addProperty(i.CONTAINER.key,{handler:this.configContainer,value:document.body});this.cfg.addProperty(i.DISABLED.key,{handler:this.configContainer,value:i.DISABLED.value,supressEvent:i.DISABLED.suppressEvent});this.cfg.addProperty(i.XY_OFFSET.key,{value:i.XY_OFFSET.value.concat(),supressEvent:i.XY_OFFSET.suppressEvent});},configText:function(p,o,q){var r=o[0];if(r){this.setBody(r);}},configContainer:function(q,p,r){var o=p[0];if(typeof o=="string"){this.cfg.setProperty("container",document.getElementById(o),true);}},_removeEventListeners:function(){var r=this._context,o,q,p;if(r){o=r.length;if(o>0){p=o-1;do{q=r[p];n.removeListener(q,"mouseover",this.onContextMouseOver);n.removeListener(q,"mousemove",this.onContextMouseMove);n.removeListener(q,"mouseout",this.onContextMouseOut);}while(p--);}}},configContext:function(t,p,u){var s=p[0],v,o,r,q;if(s){if(!(s instanceof Array)){if(typeof s=="string"){this.cfg.setProperty("context",[document.getElementById(s)],true);}else{this.cfg.setProperty("context",[s],true);}s=this.cfg.getProperty("context");}this._removeEventListeners();this._context=s;v=this._context;if(v){o=v.length;if(o>0){q=o-1;do{r=v[q];n.on(r,"mouseover",this.onContextMouseOver,this);
+n.on(r,"mousemove",this.onContextMouseMove,this);n.on(r,"mouseout",this.onContextMouseOut,this);}while(q--);}}}},onContextMouseMove:function(p,o){o.pageX=n.getPageX(p);o.pageY=n.getPageY(p);},onContextMouseOver:function(q,p){var o=this;if(o.title){p._tempTitle=o.title;o.title="";}if(p.fireEvent("contextMouseOver",o,q)!==false&&!p.cfg.getProperty("disabled")){if(p.hideProcId){clearTimeout(p.hideProcId);p.hideProcId=null;}n.on(o,"mousemove",p.onContextMouseMove,p);p.showProcId=p.doShow(q,o);}},onContextMouseOut:function(q,p){var o=this;if(p._tempTitle){o.title=p._tempTitle;p._tempTitle=null;}if(p.showProcId){clearTimeout(p.showProcId);p.showProcId=null;}if(p.hideProcId){clearTimeout(p.hideProcId);p.hideProcId=null;}p.fireEvent("contextMouseOut",o,q);p.hideProcId=setTimeout(function(){p.hide();},p.cfg.getProperty("hidedelay"));},doShow:function(r,o){var t=this.cfg.getProperty("xyoffset"),p=t[0],s=t[1],q=this;if(h.opera&&o.tagName&&o.tagName.toUpperCase()=="A"){s+=12;}return setTimeout(function(){var u=q.cfg.getProperty("text");if(q._tempTitle&&(u===""||YAHOO.lang.isUndefined(u)||YAHOO.lang.isNull(u))){q.setBody(q._tempTitle);}else{q.cfg.refireEvent("text");}q.moveTo(q.pageX+p,q.pageY+s);if(q.cfg.getProperty("preventoverlap")){q.preventOverlap(q.pageX,q.pageY);}n.removeListener(o,"mousemove",q.onContextMouseMove);q.contextTriggerEvent.fire(o);q.show();q.hideProcId=q.doHide();},this.cfg.getProperty("showdelay"));},doHide:function(){var o=this;return setTimeout(function(){o.hide();},this.cfg.getProperty("autodismissdelay"));},preventOverlap:function(s,r){var o=this.element.offsetHeight,q=new YAHOO.util.Point(s,r),p=c.getRegion(this.element);p.top-=5;p.left-=5;p.right+=5;p.bottom+=5;if(p.contains(q)){this.cfg.setProperty("y",(r-o-5));}},onRender:function(s,r){function t(){var w=this.element,v=this.underlay;if(v){v.style.width=(w.offsetWidth+6)+"px";v.style.height=(w.offsetHeight+1)+"px";}}function p(){c.addClass(this.underlay,"yui-tt-shadow-visible");if(h.ie){this.forceUnderlayRedraw();}}function o(){c.removeClass(this.underlay,"yui-tt-shadow-visible");}function u(){var x=this.underlay,w,v,z,y;if(!x){w=this.element;v=YAHOO.widget.Module;z=h.ie;y=this;if(!f){f=document.createElement("div");f.className="yui-tt-shadow";}x=f.cloneNode(false);w.appendChild(x);this.underlay=x;this._shadow=this.underlay;p.call(this);this.subscribe("beforeShow",p);this.subscribe("hide",o);if(g){window.setTimeout(function(){t.call(y);},0);this.cfg.subscribeToConfigEvent("width",t);this.cfg.subscribeToConfigEvent("height",t);this.subscribe("changeContent",t);v.textResizeEvent.subscribe(t,this,true);this.subscribe("destroy",function(){v.textResizeEvent.unsubscribe(t,this);});}}}function q(){u.call(this);this.unsubscribe("beforeShow",q);}if(this.cfg.getProperty("visible")){u.call(this);}else{this.subscribe("beforeShow",q);}},forceUnderlayRedraw:function(){var o=this;c.addClass(o.underlay,"yui-force-redraw");setTimeout(function(){c.removeClass(o.underlay,"yui-force-redraw");},0);},destroy:function(){this._removeEventListeners();j.superclass.destroy.call(this);},toString:function(){return"Tooltip "+this.id;}});}());(function(){YAHOO.widget.Panel=function(v,u){YAHOO.widget.Panel.superclass.constructor.call(this,v,u);};var s=null;var e=YAHOO.lang,f=YAHOO.util,a=f.Dom,t=f.Event,m=f.CustomEvent,k=YAHOO.util.KeyListener,i=f.Config,h=YAHOO.widget.Overlay,o=YAHOO.widget.Panel,l=YAHOO.env.ua,p=(l.ie&&(l.ie<=6||document.compatMode=="BackCompat")),g,q,c,d={"BEFORE_SHOW_MASK":"beforeShowMask","BEFORE_HIDE_MASK":"beforeHideMask","SHOW_MASK":"showMask","HIDE_MASK":"hideMask","DRAG":"drag"},n={"CLOSE":{key:"close",value:true,validator:e.isBoolean,supercedes:["visible"]},"DRAGGABLE":{key:"draggable",value:(f.DD?true:false),validator:e.isBoolean,supercedes:["visible"]},"DRAG_ONLY":{key:"dragonly",value:false,validator:e.isBoolean,supercedes:["draggable"]},"UNDERLAY":{key:"underlay",value:"shadow",supercedes:["visible"]},"MODAL":{key:"modal",value:false,validator:e.isBoolean,supercedes:["visible","zindex"]},"KEY_LISTENERS":{key:"keylisteners",suppressEvent:true,supercedes:["visible"]},"STRINGS":{key:"strings",supercedes:["close"],validator:e.isObject,value:{close:"Close"}}};o.CSS_PANEL="yui-panel";o.CSS_PANEL_CONTAINER="yui-panel-container";o.FOCUSABLE=["a","button","select","textarea","input","iframe"];function j(v,u){if(!this.header&&this.cfg.getProperty("draggable")){this.setHeader(" ");}}function r(v,u,w){var z=w[0],x=w[1],y=this.cfg,A=y.getProperty("width");if(A==x){y.setProperty("width",z);}this.unsubscribe("hide",r,w);}function b(v,u){var y,x,w;if(p){y=this.cfg;x=y.getProperty("width");if(!x||x=="auto"){w=(this.element.offsetWidth+"px");y.setProperty("width",w);this.subscribe("hide",r,[(x||""),w]);}}}YAHOO.extend(o,h,{init:function(v,u){o.superclass.init.call(this,v);this.beforeInitEvent.fire(o);a.addClass(this.element,o.CSS_PANEL);this.buildWrapper();if(u){this.cfg.applyConfig(u,true);}this.subscribe("showMask",this._addFocusHandlers);this.subscribe("hideMask",this._removeFocusHandlers);this.subscribe("beforeRender",j);this.subscribe("render",function(){this.setFirstLastFocusable();this.subscribe("changeContent",this.setFirstLastFocusable);});this.subscribe("show",this._focusOnShow);this.initEvent.fire(o);},_onElementFocus:function(z){if(s===this){var y=t.getTarget(z),x=document.documentElement,v=(y!==x&&y!==window);if(v&&y!==this.element&&y!==this.mask&&!a.isAncestor(this.element,y)){try{this._focusFirstModal();}catch(w){try{if(v&&y!==document.body){y.blur();}}catch(u){}}}}},_focusFirstModal:function(){var u=this.firstElement;if(u){u.focus();}else{if(this._modalFocus){this._modalFocus.focus();}else{this.innerElement.focus();}}},_addFocusHandlers:function(v,u){if(!this.firstElement){if(l.webkit||l.opera){if(!this._modalFocus){this._createHiddenFocusElement();}}else{this.innerElement.tabIndex=0;}}this._setTabLoop(this.firstElement,this.lastElement);t.onFocus(document.documentElement,this._onElementFocus,this,true);s=this;},_createHiddenFocusElement:function(){var u=document.createElement("button");
+u.style.height="1px";u.style.width="1px";u.style.position="absolute";u.style.left="-10000em";u.style.opacity=0;u.tabIndex=-1;this.innerElement.appendChild(u);this._modalFocus=u;},_removeFocusHandlers:function(v,u){t.removeFocusListener(document.documentElement,this._onElementFocus,this);if(s==this){s=null;}},_focusOnShow:function(v,u,w){if(u&&u[1]){t.stopEvent(u[1]);}if(!this.focusFirst(v,u,w)){if(this.cfg.getProperty("modal")){this._focusFirstModal();}}},focusFirst:function(w,u,z){var v=this.firstElement,y=false;if(u&&u[1]){t.stopEvent(u[1]);}if(v){try{v.focus();y=true;}catch(x){}}return y;},focusLast:function(w,u,z){var v=this.lastElement,y=false;if(u&&u[1]){t.stopEvent(u[1]);}if(v){try{v.focus();y=true;}catch(x){}}return y;},_setTabLoop:function(u,v){this.setTabLoop(u,v);},setTabLoop:function(x,z){var v=this.preventBackTab,w=this.preventTabOut,u=this.showEvent,y=this.hideEvent;if(v){v.disable();u.unsubscribe(v.enable,v);y.unsubscribe(v.disable,v);v=this.preventBackTab=null;}if(w){w.disable();u.unsubscribe(w.enable,w);y.unsubscribe(w.disable,w);w=this.preventTabOut=null;}if(x){this.preventBackTab=new k(x,{shift:true,keys:9},{fn:this.focusLast,scope:this,correctScope:true});v=this.preventBackTab;u.subscribe(v.enable,v,true);y.subscribe(v.disable,v,true);}if(z){this.preventTabOut=new k(z,{shift:false,keys:9},{fn:this.focusFirst,scope:this,correctScope:true});w=this.preventTabOut;u.subscribe(w.enable,w,true);y.subscribe(w.disable,w,true);}},getFocusableElements:function(v){v=v||this.innerElement;var x={},u=this;for(var w=0;w<o.FOCUSABLE.length;w++){x[o.FOCUSABLE[w]]=true;}return a.getElementsBy(function(y){return u._testIfFocusable(y,x);},null,v);},_testIfFocusable:function(u,v){if(u.focus&&u.type!=="hidden"&&!u.disabled&&v[u.tagName.toLowerCase()]){return true;}return false;},setFirstLastFocusable:function(){this.firstElement=null;this.lastElement=null;var u=this.getFocusableElements();this.focusableElements=u;if(u.length>0){this.firstElement=u[0];this.lastElement=u[u.length-1];}if(this.cfg.getProperty("modal")){this._setTabLoop(this.firstElement,this.lastElement);}},initEvents:function(){o.superclass.initEvents.call(this);var u=m.LIST;this.showMaskEvent=this.createEvent(d.SHOW_MASK);this.showMaskEvent.signature=u;this.beforeShowMaskEvent=this.createEvent(d.BEFORE_SHOW_MASK);this.beforeShowMaskEvent.signature=u;this.hideMaskEvent=this.createEvent(d.HIDE_MASK);this.hideMaskEvent.signature=u;this.beforeHideMaskEvent=this.createEvent(d.BEFORE_HIDE_MASK);this.beforeHideMaskEvent.signature=u;this.dragEvent=this.createEvent(d.DRAG);this.dragEvent.signature=u;},initDefaultConfig:function(){o.superclass.initDefaultConfig.call(this);this.cfg.addProperty(n.CLOSE.key,{handler:this.configClose,value:n.CLOSE.value,validator:n.CLOSE.validator,supercedes:n.CLOSE.supercedes});this.cfg.addProperty(n.DRAGGABLE.key,{handler:this.configDraggable,value:(f.DD)?true:false,validator:n.DRAGGABLE.validator,supercedes:n.DRAGGABLE.supercedes});this.cfg.addProperty(n.DRAG_ONLY.key,{value:n.DRAG_ONLY.value,validator:n.DRAG_ONLY.validator,supercedes:n.DRAG_ONLY.supercedes});this.cfg.addProperty(n.UNDERLAY.key,{handler:this.configUnderlay,value:n.UNDERLAY.value,supercedes:n.UNDERLAY.supercedes});this.cfg.addProperty(n.MODAL.key,{handler:this.configModal,value:n.MODAL.value,validator:n.MODAL.validator,supercedes:n.MODAL.supercedes});this.cfg.addProperty(n.KEY_LISTENERS.key,{handler:this.configKeyListeners,suppressEvent:n.KEY_LISTENERS.suppressEvent,supercedes:n.KEY_LISTENERS.supercedes});this.cfg.addProperty(n.STRINGS.key,{value:n.STRINGS.value,handler:this.configStrings,validator:n.STRINGS.validator,supercedes:n.STRINGS.supercedes});},configClose:function(y,v,z){var A=v[0],x=this.close,u=this.cfg.getProperty("strings"),w;if(A){if(!x){if(!c){c=document.createElement("a");c.className="container-close";c.href="#";}x=c.cloneNode(true);w=this.innerElement.firstChild;if(w){this.innerElement.insertBefore(x,w);}else{this.innerElement.appendChild(x);}x.innerHTML=(u&&u.close)?u.close:" ";t.on(x,"click",this._doClose,this,true);this.close=x;}else{x.style.display="block";}}else{if(x){x.style.display="none";}}},_doClose:function(u){t.preventDefault(u);this.hide();},configDraggable:function(v,u,w){var x=u[0];if(x){if(!f.DD){this.cfg.setProperty("draggable",false);return;}if(this.header){a.setStyle(this.header,"cursor","move");this.registerDragDrop();}this.subscribe("beforeShow",b);}else{if(this.dd){this.dd.unreg();}if(this.header){a.setStyle(this.header,"cursor","auto");}this.unsubscribe("beforeShow",b);}},configUnderlay:function(D,C,z){var B=(this.platform=="mac"&&l.gecko),E=C[0].toLowerCase(),v=this.underlay,w=this.element;function x(){var F=false;if(!v){if(!q){q=document.createElement("div");q.className="underlay";}v=q.cloneNode(false);this.element.appendChild(v);this.underlay=v;if(p){this.sizeUnderlay();this.cfg.subscribeToConfigEvent("width",this.sizeUnderlay);this.cfg.subscribeToConfigEvent("height",this.sizeUnderlay);this.changeContentEvent.subscribe(this.sizeUnderlay);YAHOO.widget.Module.textResizeEvent.subscribe(this.sizeUnderlay,this,true);}if(l.webkit&&l.webkit<420){this.changeContentEvent.subscribe(this.forceUnderlayRedraw);}F=true;}}function A(){var F=x.call(this);if(!F&&p){this.sizeUnderlay();}this._underlayDeferred=false;this.beforeShowEvent.unsubscribe(A);}function y(){if(this._underlayDeferred){this.beforeShowEvent.unsubscribe(A);this._underlayDeferred=false;}if(v){this.cfg.unsubscribeFromConfigEvent("width",this.sizeUnderlay);this.cfg.unsubscribeFromConfigEvent("height",this.sizeUnderlay);this.changeContentEvent.unsubscribe(this.sizeUnderlay);this.changeContentEvent.unsubscribe(this.forceUnderlayRedraw);YAHOO.widget.Module.textResizeEvent.unsubscribe(this.sizeUnderlay,this,true);this.element.removeChild(v);this.underlay=null;}}switch(E){case"shadow":a.removeClass(w,"matte");a.addClass(w,"shadow");break;case"matte":if(!B){y.call(this);}a.removeClass(w,"shadow");a.addClass(w,"matte");break;default:if(!B){y.call(this);
+}a.removeClass(w,"shadow");a.removeClass(w,"matte");break;}if((E=="shadow")||(B&&!v)){if(this.cfg.getProperty("visible")){var u=x.call(this);if(!u&&p){this.sizeUnderlay();}}else{if(!this._underlayDeferred){this.beforeShowEvent.subscribe(A);this._underlayDeferred=true;}}}},configModal:function(v,u,x){var w=u[0];if(w){if(!this._hasModalityEventListeners){this.subscribe("beforeShow",this.buildMask);this.subscribe("beforeShow",this.bringToTop);this.subscribe("beforeShow",this.showMask);this.subscribe("hide",this.hideMask);h.windowResizeEvent.subscribe(this.sizeMask,this,true);this._hasModalityEventListeners=true;}}else{if(this._hasModalityEventListeners){if(this.cfg.getProperty("visible")){this.hideMask();this.removeMask();}this.unsubscribe("beforeShow",this.buildMask);this.unsubscribe("beforeShow",this.bringToTop);this.unsubscribe("beforeShow",this.showMask);this.unsubscribe("hide",this.hideMask);h.windowResizeEvent.unsubscribe(this.sizeMask,this);this._hasModalityEventListeners=false;}}},removeMask:function(){var v=this.mask,u;if(v){this.hideMask();u=v.parentNode;if(u){u.removeChild(v);}this.mask=null;}},configKeyListeners:function(x,u,A){var w=u[0],z,y,v;if(w){if(w instanceof Array){y=w.length;for(v=0;v<y;v++){z=w[v];if(!i.alreadySubscribed(this.showEvent,z.enable,z)){this.showEvent.subscribe(z.enable,z,true);}if(!i.alreadySubscribed(this.hideEvent,z.disable,z)){this.hideEvent.subscribe(z.disable,z,true);this.destroyEvent.subscribe(z.disable,z,true);}}}else{if(!i.alreadySubscribed(this.showEvent,w.enable,w)){this.showEvent.subscribe(w.enable,w,true);}if(!i.alreadySubscribed(this.hideEvent,w.disable,w)){this.hideEvent.subscribe(w.disable,w,true);this.destroyEvent.subscribe(w.disable,w,true);}}}},configStrings:function(v,u,w){var x=e.merge(n.STRINGS.value,u[0]);this.cfg.setProperty(n.STRINGS.key,x,true);},configHeight:function(x,v,y){var u=v[0],w=this.innerElement;a.setStyle(w,"height",u);this.cfg.refireEvent("iframe");},_autoFillOnHeightChange:function(x,v,w){o.superclass._autoFillOnHeightChange.apply(this,arguments);if(p){var u=this;setTimeout(function(){u.sizeUnderlay();},0);}},configWidth:function(x,u,y){var w=u[0],v=this.innerElement;a.setStyle(v,"width",w);this.cfg.refireEvent("iframe");},configzIndex:function(v,u,x){o.superclass.configzIndex.call(this,v,u,x);if(this.mask||this.cfg.getProperty("modal")===true){var w=a.getStyle(this.element,"zIndex");if(!w||isNaN(w)){w=0;}if(w===0){this.cfg.setProperty("zIndex",1);}else{this.stackMask();}}},buildWrapper:function(){var w=this.element.parentNode,u=this.element,v=document.createElement("div");v.className=o.CSS_PANEL_CONTAINER;v.id=u.id+"_c";if(w){w.insertBefore(v,u);}v.appendChild(u);this.element=v;this.innerElement=u;a.setStyle(this.innerElement,"visibility","inherit");},sizeUnderlay:function(){var v=this.underlay,u;if(v){u=this.element;v.style.width=u.offsetWidth+"px";v.style.height=u.offsetHeight+"px";}},registerDragDrop:function(){var v=this;if(this.header){if(!f.DD){return;}var u=(this.cfg.getProperty("dragonly")===true);this.dd=new f.DD(this.element.id,this.id,{dragOnly:u});if(!this.header.id){this.header.id=this.id+"_h";}this.dd.startDrag=function(){var x,z,w,C,B,A;if(YAHOO.env.ua.ie==6){a.addClass(v.element,"drag");}if(v.cfg.getProperty("constraintoviewport")){var y=h.VIEWPORT_OFFSET;x=v.element.offsetHeight;z=v.element.offsetWidth;w=a.getViewportWidth();C=a.getViewportHeight();B=a.getDocumentScrollLeft();A=a.getDocumentScrollTop();if(x+y<C){this.minY=A+y;this.maxY=A+C-x-y;}else{this.minY=A+y;this.maxY=A+y;}if(z+y<w){this.minX=B+y;this.maxX=B+w-z-y;}else{this.minX=B+y;this.maxX=B+y;}this.constrainX=true;this.constrainY=true;}else{this.constrainX=false;this.constrainY=false;}v.dragEvent.fire("startDrag",arguments);};this.dd.onDrag=function(){v.syncPosition();v.cfg.refireEvent("iframe");if(this.platform=="mac"&&YAHOO.env.ua.gecko){this.showMacGeckoScrollbars();}v.dragEvent.fire("onDrag",arguments);};this.dd.endDrag=function(){if(YAHOO.env.ua.ie==6){a.removeClass(v.element,"drag");}v.dragEvent.fire("endDrag",arguments);v.moveEvent.fire(v.cfg.getProperty("xy"));};this.dd.setHandleElId(this.header.id);this.dd.addInvalidHandleType("INPUT");this.dd.addInvalidHandleType("SELECT");this.dd.addInvalidHandleType("TEXTAREA");}},buildMask:function(){var u=this.mask;if(!u){if(!g){g=document.createElement("div");g.className="mask";g.innerHTML=" ";}u=g.cloneNode(true);u.id=this.id+"_mask";document.body.insertBefore(u,document.body.firstChild);this.mask=u;if(YAHOO.env.ua.gecko&&this.platform=="mac"){a.addClass(this.mask,"block-scrollbars");}this.stackMask();}},hideMask:function(){if(this.cfg.getProperty("modal")&&this.mask&&this.beforeHideMaskEvent.fire()){this.mask.style.display="none";a.removeClass(document.body,"masked");this.hideMaskEvent.fire();}},showMask:function(){if(this.cfg.getProperty("modal")&&this.mask&&this.beforeShowMaskEvent.fire()){a.addClass(document.body,"masked");this.sizeMask();this.mask.style.display="block";this.showMaskEvent.fire();}},sizeMask:function(){if(this.mask){var v=this.mask,w=a.getViewportWidth(),u=a.getViewportHeight();if(v.offsetHeight>u){v.style.height=u+"px";}if(v.offsetWidth>w){v.style.width=w+"px";}v.style.height=a.getDocumentHeight()+"px";v.style.width=a.getDocumentWidth()+"px";}},stackMask:function(){if(this.mask){var u=a.getStyle(this.element,"zIndex");if(!YAHOO.lang.isUndefined(u)&&!isNaN(u)){a.setStyle(this.mask,"zIndex",u-1);}}},render:function(u){return o.superclass.render.call(this,u,this.innerElement);},_renderHeader:function(u){u=u||this.innerElement;o.superclass._renderHeader.call(this,u);},_renderBody:function(u){u=u||this.innerElement;o.superclass._renderBody.call(this,u);},_renderFooter:function(u){u=u||this.innerElement;o.superclass._renderFooter.call(this,u);},destroy:function(u){h.windowResizeEvent.unsubscribe(this.sizeMask,this);this.removeMask();if(this.close){t.purgeElement(this.close);}o.superclass.destroy.call(this,u);},forceUnderlayRedraw:function(){var v=this.underlay;a.addClass(v,"yui-force-redraw");
+setTimeout(function(){a.removeClass(v,"yui-force-redraw");},0);},toString:function(){return"Panel "+this.id;}});}());(function(){YAHOO.widget.Dialog=function(j,i){YAHOO.widget.Dialog.superclass.constructor.call(this,j,i);};var b=YAHOO.util.Event,g=YAHOO.util.CustomEvent,e=YAHOO.util.Dom,a=YAHOO.widget.Dialog,f=YAHOO.lang,h={"BEFORE_SUBMIT":"beforeSubmit","SUBMIT":"submit","MANUAL_SUBMIT":"manualSubmit","ASYNC_SUBMIT":"asyncSubmit","FORM_SUBMIT":"formSubmit","CANCEL":"cancel"},c={"POST_METHOD":{key:"postmethod",value:"async"},"POST_DATA":{key:"postdata",value:null},"BUTTONS":{key:"buttons",value:"none",supercedes:["visible"]},"HIDEAFTERSUBMIT":{key:"hideaftersubmit",value:true}};a.CSS_DIALOG="yui-dialog";function d(){var m=this._aButtons,k,l,j;if(f.isArray(m)){k=m.length;if(k>0){j=k-1;do{l=m[j];if(YAHOO.widget.Button&&l instanceof YAHOO.widget.Button){l.destroy();}else{if(l.tagName.toUpperCase()=="BUTTON"){b.purgeElement(l);b.purgeElement(l,false);}}}while(j--);}}}YAHOO.extend(a,YAHOO.widget.Panel,{form:null,initDefaultConfig:function(){a.superclass.initDefaultConfig.call(this);this.callback={success:null,failure:null,argument:null};this.cfg.addProperty(c.POST_METHOD.key,{handler:this.configPostMethod,value:c.POST_METHOD.value,validator:function(i){if(i!="form"&&i!="async"&&i!="none"&&i!="manual"){return false;}else{return true;}}});this.cfg.addProperty(c.POST_DATA.key,{value:c.POST_DATA.value});this.cfg.addProperty(c.HIDEAFTERSUBMIT.key,{value:c.HIDEAFTERSUBMIT.value});this.cfg.addProperty(c.BUTTONS.key,{handler:this.configButtons,value:c.BUTTONS.value,supercedes:c.BUTTONS.supercedes});},initEvents:function(){a.superclass.initEvents.call(this);var i=g.LIST;this.beforeSubmitEvent=this.createEvent(h.BEFORE_SUBMIT);this.beforeSubmitEvent.signature=i;this.submitEvent=this.createEvent(h.SUBMIT);this.submitEvent.signature=i;this.manualSubmitEvent=this.createEvent(h.MANUAL_SUBMIT);this.manualSubmitEvent.signature=i;this.asyncSubmitEvent=this.createEvent(h.ASYNC_SUBMIT);this.asyncSubmitEvent.signature=i;this.formSubmitEvent=this.createEvent(h.FORM_SUBMIT);this.formSubmitEvent.signature=i;this.cancelEvent=this.createEvent(h.CANCEL);this.cancelEvent.signature=i;},init:function(j,i){a.superclass.init.call(this,j);this.beforeInitEvent.fire(a);e.addClass(this.element,a.CSS_DIALOG);this.cfg.setProperty("visible",false);if(i){this.cfg.applyConfig(i,true);}this.beforeHideEvent.subscribe(this.blurButtons,this,true);this.subscribe("changeBody",this.registerForm);this.initEvent.fire(a);},doSubmit:function(){var q=YAHOO.util.Connect,r=this.form,l=false,o=false,s,n,m,j;switch(this.cfg.getProperty("postmethod")){case"async":s=r.elements;n=s.length;if(n>0){m=n-1;do{if(s[m].type=="file"){l=true;break;}}while(m--);}if(l&&YAHOO.env.ua.ie&&this.isSecure){o=true;}j=this._getFormAttributes(r);q.setForm(r,l,o);var k=this.cfg.getProperty("postdata");var p=q.asyncRequest(j.method,j.action,this.callback,k);this.asyncSubmitEvent.fire(p);break;case"form":r.submit();this.formSubmitEvent.fire();break;case"none":case"manual":this.manualSubmitEvent.fire();break;}},_getFormAttributes:function(k){var i={method:null,action:null};if(k){if(k.getAttributeNode){var j=k.getAttributeNode("action");var l=k.getAttributeNode("method");if(j){i.action=j.value;}if(l){i.method=l.value;}}else{i.action=k.getAttribute("action");i.method=k.getAttribute("method");}}i.method=(f.isString(i.method)?i.method:"POST").toUpperCase();i.action=f.isString(i.action)?i.action:"";return i;},registerForm:function(){var i=this.element.getElementsByTagName("form")[0];if(this.form){if(this.form==i&&e.isAncestor(this.element,this.form)){return;}else{b.purgeElement(this.form);this.form=null;}}if(!i){i=document.createElement("form");i.name="frm_"+this.id;this.body.appendChild(i);}if(i){this.form=i;b.on(i,"submit",this._submitHandler,this,true);}},_submitHandler:function(i){b.stopEvent(i);this.submit();this.form.blur();},setTabLoop:function(i,j){i=i||this.firstButton;j=j||this.lastButton;a.superclass.setTabLoop.call(this,i,j);},_setTabLoop:function(i,j){i=i||this.firstButton;j=this.lastButton||j;this.setTabLoop(i,j);},setFirstLastFocusable:function(){a.superclass.setFirstLastFocusable.call(this);var k,j,m,n=this.focusableElements;this.firstFormElement=null;this.lastFormElement=null;if(this.form&&n&&n.length>0){j=n.length;for(k=0;k<j;++k){m=n[k];if(this.form===m.form){this.firstFormElement=m;break;}}for(k=j-1;k>=0;--k){m=n[k];if(this.form===m.form){this.lastFormElement=m;break;}}}},configClose:function(j,i,k){a.superclass.configClose.apply(this,arguments);},_doClose:function(i){b.preventDefault(i);this.cancel();},configButtons:function(t,s,n){var o=YAHOO.widget.Button,v=s[0],l=this.innerElement,u,q,k,r,p,j,m;d.call(this);this._aButtons=null;if(f.isArray(v)){p=document.createElement("span");p.className="button-group";r=v.length;this._aButtons=[];this.defaultHtmlButton=null;for(m=0;m<r;m++){u=v[m];if(o){k=new o({label:u.text,type:u.type});k.appendTo(p);q=k.get("element");if(u.isDefault){k.addClass("default");this.defaultHtmlButton=q;}if(f.isFunction(u.handler)){k.set("onclick",{fn:u.handler,obj:this,scope:this});}else{if(f.isObject(u.handler)&&f.isFunction(u.handler.fn)){k.set("onclick",{fn:u.handler.fn,obj:((!f.isUndefined(u.handler.obj))?u.handler.obj:this),scope:(u.handler.scope||this)});}}this._aButtons[this._aButtons.length]=k;}else{q=document.createElement("button");q.setAttribute("type","button");if(u.isDefault){q.className="default";this.defaultHtmlButton=q;}q.innerHTML=u.text;if(f.isFunction(u.handler)){b.on(q,"click",u.handler,this,true);}else{if(f.isObject(u.handler)&&f.isFunction(u.handler.fn)){b.on(q,"click",u.handler.fn,((!f.isUndefined(u.handler.obj))?u.handler.obj:this),(u.handler.scope||this));}}p.appendChild(q);this._aButtons[this._aButtons.length]=q;}u.htmlButton=q;if(m===0){this.firstButton=q;}if(m==(r-1)){this.lastButton=q;}}this.setFooter(p);j=this.footer;if(e.inDocument(this.element)&&!e.isAncestor(l,j)){l.appendChild(j);}this.buttonSpan=p;}else{p=this.buttonSpan;
+j=this.footer;if(p&&j){j.removeChild(p);this.buttonSpan=null;this.firstButton=null;this.lastButton=null;this.defaultHtmlButton=null;}}this.changeContentEvent.fire();},getButtons:function(){return this._aButtons||null;},focusFirst:function(k,i,n){var j=this.firstFormElement,m=false;if(i&&i[1]){b.stopEvent(i[1]);if(i[0]===9&&this.firstElement){j=this.firstElement;}}if(j){try{j.focus();m=true;}catch(l){}}else{if(this.defaultHtmlButton){m=this.focusDefaultButton();}else{m=this.focusFirstButton();}}return m;},focusLast:function(k,i,n){var o=this.cfg.getProperty("buttons"),j=this.lastFormElement,m=false;if(i&&i[1]){b.stopEvent(i[1]);if(i[0]===9&&this.lastElement){j=this.lastElement;}}if(o&&f.isArray(o)){m=this.focusLastButton();}else{if(j){try{j.focus();m=true;}catch(l){}}}return m;},_getButton:function(j){var i=YAHOO.widget.Button;if(i&&j&&j.nodeName&&j.id){j=i.getButton(j.id)||j;}return j;},focusDefaultButton:function(){var i=this._getButton(this.defaultHtmlButton),k=false;if(i){try{i.focus();k=true;}catch(j){}}return k;},blurButtons:function(){var o=this.cfg.getProperty("buttons"),l,n,k,j;if(o&&f.isArray(o)){l=o.length;if(l>0){j=(l-1);do{n=o[j];if(n){k=this._getButton(n.htmlButton);if(k){try{k.blur();}catch(m){}}}}while(j--);}}},focusFirstButton:function(){var m=this.cfg.getProperty("buttons"),k,i,l=false;if(m&&f.isArray(m)){k=m[0];if(k){i=this._getButton(k.htmlButton);if(i){try{i.focus();l=true;}catch(j){}}}}return l;},focusLastButton:function(){var n=this.cfg.getProperty("buttons"),j,l,i,m=false;if(n&&f.isArray(n)){j=n.length;if(j>0){l=n[(j-1)];if(l){i=this._getButton(l.htmlButton);if(i){try{i.focus();m=true;}catch(k){}}}}}return m;},configPostMethod:function(j,i,k){this.registerForm();},validate:function(){return true;},submit:function(){if(this.validate()){if(this.beforeSubmitEvent.fire()){this.doSubmit();this.submitEvent.fire();if(this.cfg.getProperty("hideaftersubmit")){this.hide();}return true;}else{return false;}}else{return false;}},cancel:function(){this.cancelEvent.fire();this.hide();},getData:function(){var A=this.form,k,t,w,m,u,r,q,j,x,l,y,B,p,C,o,z,v;function s(n){var i=n.tagName.toUpperCase();return((i=="INPUT"||i=="TEXTAREA"||i=="SELECT")&&n.name==m);}if(A){k=A.elements;t=k.length;w={};for(z=0;z<t;z++){m=k[z].name;u=e.getElementsBy(s,"*",A);r=u.length;if(r>0){if(r==1){u=u[0];q=u.type;j=u.tagName.toUpperCase();switch(j){case"INPUT":if(q=="checkbox"){w[m]=u.checked;}else{if(q!="radio"){w[m]=u.value;}}break;case"TEXTAREA":w[m]=u.value;break;case"SELECT":x=u.options;l=x.length;y=[];for(v=0;v<l;v++){B=x[v];if(B.selected){o=B.attributes.value;y[y.length]=(o&&o.specified)?B.value:B.text;}}w[m]=y;break;}}else{q=u[0].type;switch(q){case"radio":for(v=0;v<r;v++){p=u[v];if(p.checked){w[m]=p.value;break;}}break;case"checkbox":y=[];for(v=0;v<r;v++){C=u[v];if(C.checked){y[y.length]=C.value;}}w[m]=y;break;}}}}}return w;},destroy:function(i){d.call(this);this._aButtons=null;var j=this.element.getElementsByTagName("form"),k;if(j.length>0){k=j[0];if(k){b.purgeElement(k);if(k.parentNode){k.parentNode.removeChild(k);}this.form=null;}}a.superclass.destroy.call(this,i);},toString:function(){return"Dialog "+this.id;}});}());(function(){YAHOO.widget.SimpleDialog=function(e,d){YAHOO.widget.SimpleDialog.superclass.constructor.call(this,e,d);};var c=YAHOO.util.Dom,b=YAHOO.widget.SimpleDialog,a={"ICON":{key:"icon",value:"none",suppressEvent:true},"TEXT":{key:"text",value:"",suppressEvent:true,supercedes:["icon"]}};b.ICON_BLOCK="blckicon";b.ICON_ALARM="alrticon";b.ICON_HELP="hlpicon";b.ICON_INFO="infoicon";b.ICON_WARN="warnicon";b.ICON_TIP="tipicon";b.ICON_CSS_CLASSNAME="yui-icon";b.CSS_SIMPLEDIALOG="yui-simple-dialog";YAHOO.extend(b,YAHOO.widget.Dialog,{initDefaultConfig:function(){b.superclass.initDefaultConfig.call(this);this.cfg.addProperty(a.ICON.key,{handler:this.configIcon,value:a.ICON.value,suppressEvent:a.ICON.suppressEvent});this.cfg.addProperty(a.TEXT.key,{handler:this.configText,value:a.TEXT.value,suppressEvent:a.TEXT.suppressEvent,supercedes:a.TEXT.supercedes});},init:function(e,d){b.superclass.init.call(this,e);this.beforeInitEvent.fire(b);c.addClass(this.element,b.CSS_SIMPLEDIALOG);this.cfg.queueProperty("postmethod","manual");if(d){this.cfg.applyConfig(d,true);}this.beforeRenderEvent.subscribe(function(){if(!this.body){this.setBody("");}},this,true);this.initEvent.fire(b);},registerForm:function(){b.superclass.registerForm.call(this);var e=this.form.ownerDocument,d=e.createElement("input");d.type="hidden";d.name=this.id;d.value="";this.form.appendChild(d);},configIcon:function(k,j,h){var d=j[0],e=this.body,f=b.ICON_CSS_CLASSNAME,l,i,g;if(d&&d!="none"){l=c.getElementsByClassName(f,"*",e);if(l.length===1){i=l[0];g=i.parentNode;if(g){g.removeChild(i);i=null;}}if(d.indexOf(".")==-1){i=document.createElement("span");i.className=(f+" "+d);i.innerHTML=" ";}else{i=document.createElement("img");i.src=(this.imageRoot+d);i.className=f;}if(i){e.insertBefore(i,e.firstChild);}}},configText:function(e,d,f){var g=d[0];if(g){this.setBody(g);this.cfg.refireEvent("icon");}},toString:function(){return"SimpleDialog "+this.id;}});}());(function(){YAHOO.widget.ContainerEffect=function(e,h,g,d,f){if(!f){f=YAHOO.util.Anim;}this.overlay=e;this.attrIn=h;this.attrOut=g;this.targetElement=d||e.element;this.animClass=f;};var b=YAHOO.util.Dom,c=YAHOO.util.CustomEvent,a=YAHOO.widget.ContainerEffect;a.FADE=function(d,f){var g=YAHOO.util.Easing,i={attributes:{opacity:{from:0,to:1}},duration:f,method:g.easeIn},e={attributes:{opacity:{to:0}},duration:f,method:g.easeOut},h=new a(d,i,e,d.element);h.handleUnderlayStart=function(){var k=this.overlay.underlay;if(k&&YAHOO.env.ua.ie){var j=(k.filters&&k.filters.length>0);if(j){b.addClass(d.element,"yui-effect-fade");}}};h.handleUnderlayComplete=function(){var j=this.overlay.underlay;if(j&&YAHOO.env.ua.ie){b.removeClass(d.element,"yui-effect-fade");}};h.handleStartAnimateIn=function(k,j,l){l.overlay._fadingIn=true;b.addClass(l.overlay.element,"hide-select");if(!l.overlay.underlay){l.overlay.cfg.refireEvent("underlay");
+}l.handleUnderlayStart();l.overlay._setDomVisibility(true);b.setStyle(l.overlay.element,"opacity",0);};h.handleCompleteAnimateIn=function(k,j,l){l.overlay._fadingIn=false;b.removeClass(l.overlay.element,"hide-select");if(l.overlay.element.style.filter){l.overlay.element.style.filter=null;}l.handleUnderlayComplete();l.overlay.cfg.refireEvent("iframe");l.animateInCompleteEvent.fire();};h.handleStartAnimateOut=function(k,j,l){l.overlay._fadingOut=true;b.addClass(l.overlay.element,"hide-select");l.handleUnderlayStart();};h.handleCompleteAnimateOut=function(k,j,l){l.overlay._fadingOut=false;b.removeClass(l.overlay.element,"hide-select");if(l.overlay.element.style.filter){l.overlay.element.style.filter=null;}l.overlay._setDomVisibility(false);b.setStyle(l.overlay.element,"opacity",1);l.handleUnderlayComplete();l.overlay.cfg.refireEvent("iframe");l.animateOutCompleteEvent.fire();};h.init();return h;};a.SLIDE=function(f,d){var i=YAHOO.util.Easing,l=f.cfg.getProperty("x")||b.getX(f.element),k=f.cfg.getProperty("y")||b.getY(f.element),m=b.getClientWidth(),h=f.element.offsetWidth,j={attributes:{points:{to:[l,k]}},duration:d,method:i.easeIn},e={attributes:{points:{to:[(m+25),k]}},duration:d,method:i.easeOut},g=new a(f,j,e,f.element,YAHOO.util.Motion);g.handleStartAnimateIn=function(o,n,p){p.overlay.element.style.left=((-25)-h)+"px";p.overlay.element.style.top=k+"px";};g.handleTweenAnimateIn=function(q,p,r){var s=b.getXY(r.overlay.element),o=s[0],n=s[1];if(b.getStyle(r.overlay.element,"visibility")=="hidden"&&o<l){r.overlay._setDomVisibility(true);}r.overlay.cfg.setProperty("xy",[o,n],true);r.overlay.cfg.refireEvent("iframe");};g.handleCompleteAnimateIn=function(o,n,p){p.overlay.cfg.setProperty("xy",[l,k],true);p.startX=l;p.startY=k;p.overlay.cfg.refireEvent("iframe");p.animateInCompleteEvent.fire();};g.handleStartAnimateOut=function(o,n,r){var p=b.getViewportWidth(),s=b.getXY(r.overlay.element),q=s[1];r.animOut.attributes.points.to=[(p+25),q];};g.handleTweenAnimateOut=function(p,o,q){var s=b.getXY(q.overlay.element),n=s[0],r=s[1];q.overlay.cfg.setProperty("xy",[n,r],true);q.overlay.cfg.refireEvent("iframe");};g.handleCompleteAnimateOut=function(o,n,p){p.overlay._setDomVisibility(false);p.overlay.cfg.setProperty("xy",[l,k]);p.animateOutCompleteEvent.fire();};g.init();return g;};a.prototype={init:function(){this.beforeAnimateInEvent=this.createEvent("beforeAnimateIn");this.beforeAnimateInEvent.signature=c.LIST;this.beforeAnimateOutEvent=this.createEvent("beforeAnimateOut");this.beforeAnimateOutEvent.signature=c.LIST;this.animateInCompleteEvent=this.createEvent("animateInComplete");this.animateInCompleteEvent.signature=c.LIST;this.animateOutCompleteEvent=this.createEvent("animateOutComplete");this.animateOutCompleteEvent.signature=c.LIST;this.animIn=new this.animClass(this.targetElement,this.attrIn.attributes,this.attrIn.duration,this.attrIn.method);this.animIn.onStart.subscribe(this.handleStartAnimateIn,this);this.animIn.onTween.subscribe(this.handleTweenAnimateIn,this);this.animIn.onComplete.subscribe(this.handleCompleteAnimateIn,this);this.animOut=new this.animClass(this.targetElement,this.attrOut.attributes,this.attrOut.duration,this.attrOut.method);this.animOut.onStart.subscribe(this.handleStartAnimateOut,this);this.animOut.onTween.subscribe(this.handleTweenAnimateOut,this);this.animOut.onComplete.subscribe(this.handleCompleteAnimateOut,this);},animateIn:function(){this._stopAnims(this.lastFrameOnStop);this.beforeAnimateInEvent.fire();this.animIn.animate();},animateOut:function(){this._stopAnims(this.lastFrameOnStop);this.beforeAnimateOutEvent.fire();this.animOut.animate();},lastFrameOnStop:true,_stopAnims:function(d){if(this.animOut&&this.animOut.isAnimated()){this.animOut.stop(d);}if(this.animIn&&this.animIn.isAnimated()){this.animIn.stop(d);}},handleStartAnimateIn:function(e,d,f){},handleTweenAnimateIn:function(e,d,f){},handleCompleteAnimateIn:function(e,d,f){},handleStartAnimateOut:function(e,d,f){},handleTweenAnimateOut:function(e,d,f){},handleCompleteAnimateOut:function(e,d,f){},toString:function(){var d="ContainerEffect";if(this.overlay){d+=" ["+this.overlay.toString()+"]";}return d;}};YAHOO.lang.augmentProto(a,YAHOO.util.EventProvider);})();YAHOO.register("container",YAHOO.widget.Module,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/container/container_core-min.js b/Websites/bugs.webkit.org/js/yui/container/container_core-min.js
new file mode 100644
index 0000000..d1e3b7a
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/container/container_core-min.js
@@ -0,0 +1,14 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){YAHOO.util.Config=function(d){if(d){this.init(d);}};var b=YAHOO.lang,c=YAHOO.util.CustomEvent,a=YAHOO.util.Config;a.CONFIG_CHANGED_EVENT="configChanged";a.BOOLEAN_TYPE="boolean";a.prototype={owner:null,queueInProgress:false,config:null,initialConfig:null,eventQueue:null,configChangedEvent:null,init:function(d){this.owner=d;this.configChangedEvent=this.createEvent(a.CONFIG_CHANGED_EVENT);this.configChangedEvent.signature=c.LIST;this.queueInProgress=false;this.config={};this.initialConfig={};this.eventQueue=[];},checkBoolean:function(d){return(typeof d==a.BOOLEAN_TYPE);},checkNumber:function(d){return(!isNaN(d));},fireEvent:function(d,f){var e=this.config[d];if(e&&e.event){e.event.fire(f);}},addProperty:function(e,d){e=e.toLowerCase();this.config[e]=d;d.event=this.createEvent(e,{scope:this.owner});d.event.signature=c.LIST;d.key=e;if(d.handler){d.event.subscribe(d.handler,this.owner);}this.setProperty(e,d.value,true);if(!d.suppressEvent){this.queueProperty(e,d.value);}},getConfig:function(){var d={},f=this.config,g,e;for(g in f){if(b.hasOwnProperty(f,g)){e=f[g];if(e&&e.event){d[g]=e.value;}}}return d;},getProperty:function(d){var e=this.config[d.toLowerCase()];if(e&&e.event){return e.value;}else{return undefined;}},resetProperty:function(d){d=d.toLowerCase();var e=this.config[d];if(e&&e.event){if(d in this.initialConfig){this.setProperty(d,this.initialConfig[d]);return true;}}else{return false;}},setProperty:function(e,g,d){var f;e=e.toLowerCase();if(this.queueInProgress&&!d){this.queueProperty(e,g);return true;}else{f=this.config[e];if(f&&f.event){if(f.validator&&!f.validator(g)){return false;}else{f.value=g;if(!d){this.fireEvent(e,g);this.configChangedEvent.fire([e,g]);}return true;}}else{return false;}}},queueProperty:function(v,r){v=v.toLowerCase();var u=this.config[v],l=false,k,g,h,j,p,t,f,n,o,d,m,w,e;if(u&&u.event){if(!b.isUndefined(r)&&u.validator&&!u.validator(r)){return false;}else{if(!b.isUndefined(r)){u.value=r;}else{r=u.value;}l=false;k=this.eventQueue.length;for(m=0;m<k;m++){g=this.eventQueue[m];if(g){h=g[0];j=g[1];if(h==v){this.eventQueue[m]=null;this.eventQueue.push([v,(!b.isUndefined(r)?r:j)]);l=true;break;}}}if(!l&&!b.isUndefined(r)){this.eventQueue.push([v,r]);}}if(u.supercedes){p=u.supercedes.length;for(w=0;w<p;w++){t=u.supercedes[w];f=this.eventQueue.length;for(e=0;e<f;e++){n=this.eventQueue[e];if(n){o=n[0];d=n[1];if(o==t.toLowerCase()){this.eventQueue.push([o,d]);this.eventQueue[e]=null;break;}}}}}return true;}else{return false;}},refireEvent:function(d){d=d.toLowerCase();var e=this.config[d];if(e&&e.event&&!b.isUndefined(e.value)){if(this.queueInProgress){this.queueProperty(d);}else{this.fireEvent(d,e.value);}}},applyConfig:function(d,g){var f,e;if(g){e={};for(f in d){if(b.hasOwnProperty(d,f)){e[f.toLowerCase()]=d[f];}}this.initialConfig=e;}for(f in d){if(b.hasOwnProperty(d,f)){this.queueProperty(f,d[f]);}}},refresh:function(){var d;for(d in this.config){if(b.hasOwnProperty(this.config,d)){this.refireEvent(d);}}},fireQueue:function(){var e,h,d,g,f;this.queueInProgress=true;for(e=0;e<this.eventQueue.length;e++){h=this.eventQueue[e];if(h){d=h[0];g=h[1];f=this.config[d];f.value=g;this.eventQueue[e]=null;this.fireEvent(d,g);}}this.queueInProgress=false;this.eventQueue=[];},subscribeToConfigEvent:function(d,e,g,h){var f=this.config[d.toLowerCase()];if(f&&f.event){if(!a.alreadySubscribed(f.event,e,g)){f.event.subscribe(e,g,h);}return true;}else{return false;}},unsubscribeFromConfigEvent:function(d,e,g){var f=this.config[d.toLowerCase()];if(f&&f.event){return f.event.unsubscribe(e,g);}else{return false;}},toString:function(){var d="Config";if(this.owner){d+=" ["+this.owner.toString()+"]";}return d;},outputEventQueue:function(){var d="",g,e,f=this.eventQueue.length;for(e=0;e<f;e++){g=this.eventQueue[e];if(g){d+=g[0]+"="+g[1]+", ";}}return d;},destroy:function(){var e=this.config,d,f;for(d in e){if(b.hasOwnProperty(e,d)){f=e[d];f.event.unsubscribeAll();f.event=null;}}this.configChangedEvent.unsubscribeAll();this.configChangedEvent=null;this.owner=null;this.config=null;this.initialConfig=null;this.eventQueue=null;}};a.alreadySubscribed=function(e,h,j){var f=e.subscribers.length,d,g;if(f>0){g=f-1;do{d=e.subscribers[g];if(d&&d.obj==j&&d.fn==h){return true;}}while(g--);}return false;};YAHOO.lang.augmentProto(a,YAHOO.util.EventProvider);}());(function(){YAHOO.widget.Module=function(r,q){if(r){this.init(r,q);}else{}};var f=YAHOO.util.Dom,d=YAHOO.util.Config,n=YAHOO.util.Event,m=YAHOO.util.CustomEvent,g=YAHOO.widget.Module,i=YAHOO.env.ua,h,p,o,e,a={"BEFORE_INIT":"beforeInit","INIT":"init","APPEND":"append","BEFORE_RENDER":"beforeRender","RENDER":"render","CHANGE_HEADER":"changeHeader","CHANGE_BODY":"changeBody","CHANGE_FOOTER":"changeFooter","CHANGE_CONTENT":"changeContent","DESTROY":"destroy","BEFORE_SHOW":"beforeShow","SHOW":"show","BEFORE_HIDE":"beforeHide","HIDE":"hide"},j={"VISIBLE":{key:"visible",value:true,validator:YAHOO.lang.isBoolean},"EFFECT":{key:"effect",suppressEvent:true,supercedes:["visible"]},"MONITOR_RESIZE":{key:"monitorresize",value:true},"APPEND_TO_DOCUMENT_BODY":{key:"appendtodocumentbody",value:false}};g.IMG_ROOT=null;g.IMG_ROOT_SSL=null;g.CSS_MODULE="yui-module";g.CSS_HEADER="hd";g.CSS_BODY="bd";g.CSS_FOOTER="ft";g.RESIZE_MONITOR_SECURE_URL="javascript:false;";g.RESIZE_MONITOR_BUFFER=1;g.textResizeEvent=new m("textResize");g.forceDocumentRedraw=function(){var q=document.documentElement;if(q){q.className+=" ";q.className=YAHOO.lang.trim(q.className);}};function l(){if(!h){h=document.createElement("div");h.innerHTML=('<div class="'+g.CSS_HEADER+'"></div>'+'<div class="'+g.CSS_BODY+'"></div><div class="'+g.CSS_FOOTER+'"></div>');p=h.firstChild;o=p.nextSibling;e=o.nextSibling;}return h;}function k(){if(!p){l();}return(p.cloneNode(false));}function b(){if(!o){l();}return(o.cloneNode(false));}function c(){if(!e){l();}return(e.cloneNode(false));}g.prototype={constructor:g,element:null,header:null,body:null,footer:null,id:null,imageRoot:g.IMG_ROOT,initEvents:function(){var q=m.LIST;
+this.beforeInitEvent=this.createEvent(a.BEFORE_INIT);this.beforeInitEvent.signature=q;this.initEvent=this.createEvent(a.INIT);this.initEvent.signature=q;this.appendEvent=this.createEvent(a.APPEND);this.appendEvent.signature=q;this.beforeRenderEvent=this.createEvent(a.BEFORE_RENDER);this.beforeRenderEvent.signature=q;this.renderEvent=this.createEvent(a.RENDER);this.renderEvent.signature=q;this.changeHeaderEvent=this.createEvent(a.CHANGE_HEADER);this.changeHeaderEvent.signature=q;this.changeBodyEvent=this.createEvent(a.CHANGE_BODY);this.changeBodyEvent.signature=q;this.changeFooterEvent=this.createEvent(a.CHANGE_FOOTER);this.changeFooterEvent.signature=q;this.changeContentEvent=this.createEvent(a.CHANGE_CONTENT);this.changeContentEvent.signature=q;this.destroyEvent=this.createEvent(a.DESTROY);this.destroyEvent.signature=q;this.beforeShowEvent=this.createEvent(a.BEFORE_SHOW);this.beforeShowEvent.signature=q;this.showEvent=this.createEvent(a.SHOW);this.showEvent.signature=q;this.beforeHideEvent=this.createEvent(a.BEFORE_HIDE);this.beforeHideEvent.signature=q;this.hideEvent=this.createEvent(a.HIDE);this.hideEvent.signature=q;},platform:function(){var q=navigator.userAgent.toLowerCase();if(q.indexOf("windows")!=-1||q.indexOf("win32")!=-1){return"windows";}else{if(q.indexOf("macintosh")!=-1){return"mac";}else{return false;}}}(),browser:function(){var q=navigator.userAgent.toLowerCase();if(q.indexOf("opera")!=-1){return"opera";}else{if(q.indexOf("msie 7")!=-1){return"ie7";}else{if(q.indexOf("msie")!=-1){return"ie";}else{if(q.indexOf("safari")!=-1){return"safari";}else{if(q.indexOf("gecko")!=-1){return"gecko";}else{return false;}}}}}}(),isSecure:function(){if(window.location.href.toLowerCase().indexOf("https")===0){return true;}else{return false;}}(),initDefaultConfig:function(){this.cfg.addProperty(j.VISIBLE.key,{handler:this.configVisible,value:j.VISIBLE.value,validator:j.VISIBLE.validator});this.cfg.addProperty(j.EFFECT.key,{handler:this.configEffect,suppressEvent:j.EFFECT.suppressEvent,supercedes:j.EFFECT.supercedes});this.cfg.addProperty(j.MONITOR_RESIZE.key,{handler:this.configMonitorResize,value:j.MONITOR_RESIZE.value});this.cfg.addProperty(j.APPEND_TO_DOCUMENT_BODY.key,{value:j.APPEND_TO_DOCUMENT_BODY.value});},init:function(v,u){var s,w;this.initEvents();this.beforeInitEvent.fire(g);this.cfg=new d(this);if(this.isSecure){this.imageRoot=g.IMG_ROOT_SSL;}if(typeof v=="string"){s=v;v=document.getElementById(v);if(!v){v=(l()).cloneNode(false);v.id=s;}}this.id=f.generateId(v);this.element=v;w=this.element.firstChild;if(w){var r=false,q=false,t=false;do{if(1==w.nodeType){if(!r&&f.hasClass(w,g.CSS_HEADER)){this.header=w;r=true;}else{if(!q&&f.hasClass(w,g.CSS_BODY)){this.body=w;q=true;}else{if(!t&&f.hasClass(w,g.CSS_FOOTER)){this.footer=w;t=true;}}}}}while((w=w.nextSibling));}this.initDefaultConfig();f.addClass(this.element,g.CSS_MODULE);if(u){this.cfg.applyConfig(u,true);}if(!d.alreadySubscribed(this.renderEvent,this.cfg.fireQueue,this.cfg)){this.renderEvent.subscribe(this.cfg.fireQueue,this.cfg,true);}this.initEvent.fire(g);},initResizeMonitor:function(){var r=(i.gecko&&this.platform=="windows");if(r){var q=this;setTimeout(function(){q._initResizeMonitor();},0);}else{this._initResizeMonitor();}},_initResizeMonitor:function(){var q,s,u;function w(){g.textResizeEvent.fire();}if(!i.opera){s=f.get("_yuiResizeMonitor");var v=this._supportsCWResize();if(!s){s=document.createElement("iframe");if(this.isSecure&&g.RESIZE_MONITOR_SECURE_URL&&i.ie){s.src=g.RESIZE_MONITOR_SECURE_URL;}if(!v){u=["<html><head><script ",'type="text/javascript">',"window.onresize=function(){window.parent.","YAHOO.widget.Module.textResizeEvent.","fire();};<","/script></head>","<body></body></html>"].join("");s.src="data:text/html;charset=utf-8,"+encodeURIComponent(u);}s.id="_yuiResizeMonitor";s.title="Text Resize Monitor";s.tabIndex=-1;s.setAttribute("role","presentation");s.style.position="absolute";s.style.visibility="hidden";var r=document.body,t=r.firstChild;if(t){r.insertBefore(s,t);}else{r.appendChild(s);}s.style.backgroundColor="transparent";s.style.borderWidth="0";s.style.width="2em";s.style.height="2em";s.style.left="0";s.style.top=(-1*(s.offsetHeight+g.RESIZE_MONITOR_BUFFER))+"px";s.style.visibility="visible";if(i.webkit){q=s.contentWindow.document;q.open();q.close();}}if(s&&s.contentWindow){g.textResizeEvent.subscribe(this.onDomResize,this,true);if(!g.textResizeInitialized){if(v){if(!n.on(s.contentWindow,"resize",w)){n.on(s,"resize",w);}}g.textResizeInitialized=true;}this.resizeMonitor=s;}}},_supportsCWResize:function(){var q=true;if(i.gecko&&i.gecko<=1.8){q=false;}return q;},onDomResize:function(s,r){var q=-1*(this.resizeMonitor.offsetHeight+g.RESIZE_MONITOR_BUFFER);this.resizeMonitor.style.top=q+"px";this.resizeMonitor.style.left="0";},setHeader:function(r){var q=this.header||(this.header=k());if(r.nodeName){q.innerHTML="";q.appendChild(r);}else{q.innerHTML=r;}if(this._rendered){this._renderHeader();}this.changeHeaderEvent.fire(r);this.changeContentEvent.fire();},appendToHeader:function(r){var q=this.header||(this.header=k());q.appendChild(r);this.changeHeaderEvent.fire(r);this.changeContentEvent.fire();},setBody:function(r){var q=this.body||(this.body=b());if(r.nodeName){q.innerHTML="";q.appendChild(r);}else{q.innerHTML=r;}if(this._rendered){this._renderBody();}this.changeBodyEvent.fire(r);this.changeContentEvent.fire();},appendToBody:function(r){var q=this.body||(this.body=b());q.appendChild(r);this.changeBodyEvent.fire(r);this.changeContentEvent.fire();},setFooter:function(r){var q=this.footer||(this.footer=c());if(r.nodeName){q.innerHTML="";q.appendChild(r);}else{q.innerHTML=r;}if(this._rendered){this._renderFooter();}this.changeFooterEvent.fire(r);this.changeContentEvent.fire();},appendToFooter:function(r){var q=this.footer||(this.footer=c());q.appendChild(r);this.changeFooterEvent.fire(r);this.changeContentEvent.fire();},render:function(s,q){var t=this;function r(u){if(typeof u=="string"){u=document.getElementById(u);
+}if(u){t._addToParent(u,t.element);t.appendEvent.fire();}}this.beforeRenderEvent.fire();if(!q){q=this.element;}if(s){r(s);}else{if(!f.inDocument(this.element)){return false;}}this._renderHeader(q);this._renderBody(q);this._renderFooter(q);this._rendered=true;this.renderEvent.fire();return true;},_renderHeader:function(q){q=q||this.element;if(this.header&&!f.inDocument(this.header)){var r=q.firstChild;if(r){q.insertBefore(this.header,r);}else{q.appendChild(this.header);}}},_renderBody:function(q){q=q||this.element;if(this.body&&!f.inDocument(this.body)){if(this.footer&&f.isAncestor(q,this.footer)){q.insertBefore(this.body,this.footer);}else{q.appendChild(this.body);}}},_renderFooter:function(q){q=q||this.element;if(this.footer&&!f.inDocument(this.footer)){q.appendChild(this.footer);}},destroy:function(q){var r,s=!(q);if(this.element){n.purgeElement(this.element,s);r=this.element.parentNode;}if(r){r.removeChild(this.element);}this.element=null;this.header=null;this.body=null;this.footer=null;g.textResizeEvent.unsubscribe(this.onDomResize,this);this.cfg.destroy();this.cfg=null;this.destroyEvent.fire();},show:function(){this.cfg.setProperty("visible",true);},hide:function(){this.cfg.setProperty("visible",false);},configVisible:function(r,q,s){var t=q[0];if(t){if(this.beforeShowEvent.fire()){f.setStyle(this.element,"display","block");this.showEvent.fire();}}else{if(this.beforeHideEvent.fire()){f.setStyle(this.element,"display","none");this.hideEvent.fire();}}},configEffect:function(r,q,s){this._cachedEffects=(this.cacheEffects)?this._createEffects(q[0]):null;},cacheEffects:true,_createEffects:function(t){var q=null,u,r,s;if(t){if(t instanceof Array){q=[];u=t.length;for(r=0;r<u;r++){s=t[r];if(s.effect){q[q.length]=s.effect(this,s.duration);}}}else{if(t.effect){q=[t.effect(this,t.duration)];}}}return q;},configMonitorResize:function(s,r,t){var q=r[0];if(q){this.initResizeMonitor();}else{g.textResizeEvent.unsubscribe(this.onDomResize,this,true);this.resizeMonitor=null;}},_addToParent:function(q,r){if(!this.cfg.getProperty("appendtodocumentbody")&&q===document.body&&q.firstChild){q.insertBefore(r,q.firstChild);}else{q.appendChild(r);}},toString:function(){return"Module "+this.id;}};YAHOO.lang.augmentProto(g,YAHOO.util.EventProvider);}());(function(){YAHOO.widget.Overlay=function(p,o){YAHOO.widget.Overlay.superclass.constructor.call(this,p,o);};var i=YAHOO.lang,m=YAHOO.util.CustomEvent,g=YAHOO.widget.Module,n=YAHOO.util.Event,f=YAHOO.util.Dom,d=YAHOO.util.Config,k=YAHOO.env.ua,b=YAHOO.widget.Overlay,h="subscribe",e="unsubscribe",c="contained",j,a={"BEFORE_MOVE":"beforeMove","MOVE":"move"},l={"X":{key:"x",validator:i.isNumber,suppressEvent:true,supercedes:["iframe"]},"Y":{key:"y",validator:i.isNumber,suppressEvent:true,supercedes:["iframe"]},"XY":{key:"xy",suppressEvent:true,supercedes:["iframe"]},"CONTEXT":{key:"context",suppressEvent:true,supercedes:["iframe"]},"FIXED_CENTER":{key:"fixedcenter",value:false,supercedes:["iframe","visible"]},"WIDTH":{key:"width",suppressEvent:true,supercedes:["context","fixedcenter","iframe"]},"HEIGHT":{key:"height",suppressEvent:true,supercedes:["context","fixedcenter","iframe"]},"AUTO_FILL_HEIGHT":{key:"autofillheight",supercedes:["height"],value:"body"},"ZINDEX":{key:"zindex",value:null},"CONSTRAIN_TO_VIEWPORT":{key:"constraintoviewport",value:false,validator:i.isBoolean,supercedes:["iframe","x","y","xy"]},"IFRAME":{key:"iframe",value:(k.ie==6?true:false),validator:i.isBoolean,supercedes:["zindex"]},"PREVENT_CONTEXT_OVERLAP":{key:"preventcontextoverlap",value:false,validator:i.isBoolean,supercedes:["constraintoviewport"]}};b.IFRAME_SRC="javascript:false;";b.IFRAME_OFFSET=3;b.VIEWPORT_OFFSET=10;b.TOP_LEFT="tl";b.TOP_RIGHT="tr";b.BOTTOM_LEFT="bl";b.BOTTOM_RIGHT="br";b.PREVENT_OVERLAP_X={"tltr":true,"blbr":true,"brbl":true,"trtl":true};b.PREVENT_OVERLAP_Y={"trbr":true,"tlbl":true,"bltl":true,"brtr":true};b.CSS_OVERLAY="yui-overlay";b.CSS_HIDDEN="yui-overlay-hidden";b.CSS_IFRAME="yui-overlay-iframe";b.STD_MOD_RE=/^\s*?(body|footer|header)\s*?$/i;b.windowScrollEvent=new m("windowScroll");b.windowResizeEvent=new m("windowResize");b.windowScrollHandler=function(p){var o=n.getTarget(p);if(!o||o===window||o===window.document){if(k.ie){if(!window.scrollEnd){window.scrollEnd=-1;}clearTimeout(window.scrollEnd);window.scrollEnd=setTimeout(function(){b.windowScrollEvent.fire();},1);}else{b.windowScrollEvent.fire();}}};b.windowResizeHandler=function(o){if(k.ie){if(!window.resizeEnd){window.resizeEnd=-1;}clearTimeout(window.resizeEnd);window.resizeEnd=setTimeout(function(){b.windowResizeEvent.fire();},100);}else{b.windowResizeEvent.fire();}};b._initialized=null;if(b._initialized===null){n.on(window,"scroll",b.windowScrollHandler);n.on(window,"resize",b.windowResizeHandler);b._initialized=true;}b._TRIGGER_MAP={"windowScroll":b.windowScrollEvent,"windowResize":b.windowResizeEvent,"textResize":g.textResizeEvent};YAHOO.extend(b,g,{CONTEXT_TRIGGERS:[],init:function(p,o){b.superclass.init.call(this,p);this.beforeInitEvent.fire(b);f.addClass(this.element,b.CSS_OVERLAY);if(o){this.cfg.applyConfig(o,true);}if(this.platform=="mac"&&k.gecko){if(!d.alreadySubscribed(this.showEvent,this.showMacGeckoScrollbars,this)){this.showEvent.subscribe(this.showMacGeckoScrollbars,this,true);}if(!d.alreadySubscribed(this.hideEvent,this.hideMacGeckoScrollbars,this)){this.hideEvent.subscribe(this.hideMacGeckoScrollbars,this,true);}}this.initEvent.fire(b);},initEvents:function(){b.superclass.initEvents.call(this);var o=m.LIST;this.beforeMoveEvent=this.createEvent(a.BEFORE_MOVE);this.beforeMoveEvent.signature=o;this.moveEvent=this.createEvent(a.MOVE);this.moveEvent.signature=o;},initDefaultConfig:function(){b.superclass.initDefaultConfig.call(this);var o=this.cfg;o.addProperty(l.X.key,{handler:this.configX,validator:l.X.validator,suppressEvent:l.X.suppressEvent,supercedes:l.X.supercedes});o.addProperty(l.Y.key,{handler:this.configY,validator:l.Y.validator,suppressEvent:l.Y.suppressEvent,supercedes:l.Y.supercedes});
+o.addProperty(l.XY.key,{handler:this.configXY,suppressEvent:l.XY.suppressEvent,supercedes:l.XY.supercedes});o.addProperty(l.CONTEXT.key,{handler:this.configContext,suppressEvent:l.CONTEXT.suppressEvent,supercedes:l.CONTEXT.supercedes});o.addProperty(l.FIXED_CENTER.key,{handler:this.configFixedCenter,value:l.FIXED_CENTER.value,validator:l.FIXED_CENTER.validator,supercedes:l.FIXED_CENTER.supercedes});o.addProperty(l.WIDTH.key,{handler:this.configWidth,suppressEvent:l.WIDTH.suppressEvent,supercedes:l.WIDTH.supercedes});o.addProperty(l.HEIGHT.key,{handler:this.configHeight,suppressEvent:l.HEIGHT.suppressEvent,supercedes:l.HEIGHT.supercedes});o.addProperty(l.AUTO_FILL_HEIGHT.key,{handler:this.configAutoFillHeight,value:l.AUTO_FILL_HEIGHT.value,validator:this._validateAutoFill,supercedes:l.AUTO_FILL_HEIGHT.supercedes});o.addProperty(l.ZINDEX.key,{handler:this.configzIndex,value:l.ZINDEX.value});o.addProperty(l.CONSTRAIN_TO_VIEWPORT.key,{handler:this.configConstrainToViewport,value:l.CONSTRAIN_TO_VIEWPORT.value,validator:l.CONSTRAIN_TO_VIEWPORT.validator,supercedes:l.CONSTRAIN_TO_VIEWPORT.supercedes});o.addProperty(l.IFRAME.key,{handler:this.configIframe,value:l.IFRAME.value,validator:l.IFRAME.validator,supercedes:l.IFRAME.supercedes});o.addProperty(l.PREVENT_CONTEXT_OVERLAP.key,{value:l.PREVENT_CONTEXT_OVERLAP.value,validator:l.PREVENT_CONTEXT_OVERLAP.validator,supercedes:l.PREVENT_CONTEXT_OVERLAP.supercedes});},moveTo:function(o,p){this.cfg.setProperty("xy",[o,p]);},hideMacGeckoScrollbars:function(){f.replaceClass(this.element,"show-scrollbars","hide-scrollbars");},showMacGeckoScrollbars:function(){f.replaceClass(this.element,"hide-scrollbars","show-scrollbars");},_setDomVisibility:function(o){f.setStyle(this.element,"visibility",(o)?"visible":"hidden");var p=b.CSS_HIDDEN;if(o){f.removeClass(this.element,p);}else{f.addClass(this.element,p);}},configVisible:function(x,w,t){var p=w[0],B=f.getStyle(this.element,"visibility"),o=this._cachedEffects||this._createEffects(this.cfg.getProperty("effect")),A=(this.platform=="mac"&&k.gecko),y=d.alreadySubscribed,q,v,s,r,u,z;if(B=="inherit"){v=this.element.parentNode;while(v.nodeType!=9&&v.nodeType!=11){B=f.getStyle(v,"visibility");if(B!="inherit"){break;}v=v.parentNode;}if(B=="inherit"){B="visible";}}if(p){if(A){this.showMacGeckoScrollbars();}if(o){if(p){if(B!="visible"||B===""||this._fadingOut){if(this.beforeShowEvent.fire()){z=o.length;for(s=0;s<z;s++){q=o[s];if(s===0&&!y(q.animateInCompleteEvent,this.showEvent.fire,this.showEvent)){q.animateInCompleteEvent.subscribe(this.showEvent.fire,this.showEvent,true);}q.animateIn();}}}}}else{if(B!="visible"||B===""){if(this.beforeShowEvent.fire()){this._setDomVisibility(true);this.cfg.refireEvent("iframe");this.showEvent.fire();}}else{this._setDomVisibility(true);}}}else{if(A){this.hideMacGeckoScrollbars();}if(o){if(B=="visible"||this._fadingIn){if(this.beforeHideEvent.fire()){z=o.length;for(r=0;r<z;r++){u=o[r];if(r===0&&!y(u.animateOutCompleteEvent,this.hideEvent.fire,this.hideEvent)){u.animateOutCompleteEvent.subscribe(this.hideEvent.fire,this.hideEvent,true);}u.animateOut();}}}else{if(B===""){this._setDomVisibility(false);}}}else{if(B=="visible"||B===""){if(this.beforeHideEvent.fire()){this._setDomVisibility(false);this.hideEvent.fire();}}else{this._setDomVisibility(false);}}}},doCenterOnDOMEvent:function(){var o=this.cfg,p=o.getProperty("fixedcenter");if(o.getProperty("visible")){if(p&&(p!==c||this.fitsInViewport())){this.center();}}},fitsInViewport:function(){var s=b.VIEWPORT_OFFSET,q=this.element,t=q.offsetWidth,r=q.offsetHeight,o=f.getViewportWidth(),p=f.getViewportHeight();return((t+s<o)&&(r+s<p));},configFixedCenter:function(s,q,t){var u=q[0],p=d.alreadySubscribed,r=b.windowResizeEvent,o=b.windowScrollEvent;if(u){this.center();if(!p(this.beforeShowEvent,this.center)){this.beforeShowEvent.subscribe(this.center);}if(!p(r,this.doCenterOnDOMEvent,this)){r.subscribe(this.doCenterOnDOMEvent,this,true);}if(!p(o,this.doCenterOnDOMEvent,this)){o.subscribe(this.doCenterOnDOMEvent,this,true);}}else{this.beforeShowEvent.unsubscribe(this.center);r.unsubscribe(this.doCenterOnDOMEvent,this);o.unsubscribe(this.doCenterOnDOMEvent,this);}},configHeight:function(r,p,s){var o=p[0],q=this.element;f.setStyle(q,"height",o);this.cfg.refireEvent("iframe");},configAutoFillHeight:function(t,s,p){var v=s[0],q=this.cfg,u="autofillheight",w="height",r=q.getProperty(u),o=this._autoFillOnHeightChange;q.unsubscribeFromConfigEvent(w,o);g.textResizeEvent.unsubscribe(o);this.changeContentEvent.unsubscribe(o);if(r&&v!==r&&this[r]){f.setStyle(this[r],w,"");}if(v){v=i.trim(v.toLowerCase());q.subscribeToConfigEvent(w,o,this[v],this);g.textResizeEvent.subscribe(o,this[v],this);this.changeContentEvent.subscribe(o,this[v],this);q.setProperty(u,v,true);}},configWidth:function(r,o,s){var q=o[0],p=this.element;f.setStyle(p,"width",q);this.cfg.refireEvent("iframe");},configzIndex:function(q,o,r){var s=o[0],p=this.element;if(!s){s=f.getStyle(p,"zIndex");if(!s||isNaN(s)){s=0;}}if(this.iframe||this.cfg.getProperty("iframe")===true){if(s<=0){s=1;}}f.setStyle(p,"zIndex",s);this.cfg.setProperty("zIndex",s,true);if(this.iframe){this.stackIframe();}},configXY:function(q,p,r){var t=p[0],o=t[0],s=t[1];this.cfg.setProperty("x",o);this.cfg.setProperty("y",s);this.beforeMoveEvent.fire([o,s]);o=this.cfg.getProperty("x");s=this.cfg.getProperty("y");this.cfg.refireEvent("iframe");this.moveEvent.fire([o,s]);},configX:function(q,p,r){var o=p[0],s=this.cfg.getProperty("y");this.cfg.setProperty("x",o,true);this.cfg.setProperty("y",s,true);this.beforeMoveEvent.fire([o,s]);o=this.cfg.getProperty("x");s=this.cfg.getProperty("y");f.setX(this.element,o,true);this.cfg.setProperty("xy",[o,s],true);this.cfg.refireEvent("iframe");this.moveEvent.fire([o,s]);},configY:function(q,p,r){var o=this.cfg.getProperty("x"),s=p[0];this.cfg.setProperty("x",o,true);this.cfg.setProperty("y",s,true);this.beforeMoveEvent.fire([o,s]);o=this.cfg.getProperty("x");s=this.cfg.getProperty("y");f.setY(this.element,s,true);
+this.cfg.setProperty("xy",[o,s],true);this.cfg.refireEvent("iframe");this.moveEvent.fire([o,s]);},showIframe:function(){var p=this.iframe,o;if(p){o=this.element.parentNode;if(o!=p.parentNode){this._addToParent(o,p);}p.style.display="block";}},hideIframe:function(){if(this.iframe){this.iframe.style.display="none";}},syncIframe:function(){var o=this.iframe,q=this.element,s=b.IFRAME_OFFSET,p=(s*2),r;if(o){o.style.width=(q.offsetWidth+p+"px");o.style.height=(q.offsetHeight+p+"px");r=this.cfg.getProperty("xy");if(!i.isArray(r)||(isNaN(r[0])||isNaN(r[1]))){this.syncPosition();r=this.cfg.getProperty("xy");}f.setXY(o,[(r[0]-s),(r[1]-s)]);}},stackIframe:function(){if(this.iframe){var o=f.getStyle(this.element,"zIndex");if(!YAHOO.lang.isUndefined(o)&&!isNaN(o)){f.setStyle(this.iframe,"zIndex",(o-1));}}},configIframe:function(r,q,s){var o=q[0];function t(){var v=this.iframe,w=this.element,x;if(!v){if(!j){j=document.createElement("iframe");if(this.isSecure){j.src=b.IFRAME_SRC;}if(k.ie){j.style.filter="alpha(opacity=0)";j.frameBorder=0;}else{j.style.opacity="0";}j.style.position="absolute";j.style.border="none";j.style.margin="0";j.style.padding="0";j.style.display="none";j.tabIndex=-1;j.className=b.CSS_IFRAME;}v=j.cloneNode(false);v.id=this.id+"_f";x=w.parentNode;var u=x||document.body;this._addToParent(u,v);this.iframe=v;}this.showIframe();this.syncIframe();this.stackIframe();if(!this._hasIframeEventListeners){this.showEvent.subscribe(this.showIframe);this.hideEvent.subscribe(this.hideIframe);this.changeContentEvent.subscribe(this.syncIframe);this._hasIframeEventListeners=true;}}function p(){t.call(this);this.beforeShowEvent.unsubscribe(p);this._iframeDeferred=false;}if(o){if(this.cfg.getProperty("visible")){t.call(this);}else{if(!this._iframeDeferred){this.beforeShowEvent.subscribe(p);this._iframeDeferred=true;}}}else{this.hideIframe();if(this._hasIframeEventListeners){this.showEvent.unsubscribe(this.showIframe);this.hideEvent.unsubscribe(this.hideIframe);this.changeContentEvent.unsubscribe(this.syncIframe);this._hasIframeEventListeners=false;}}},_primeXYFromDOM:function(){if(YAHOO.lang.isUndefined(this.cfg.getProperty("xy"))){this.syncPosition();this.cfg.refireEvent("xy");this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);}},configConstrainToViewport:function(p,o,q){var r=o[0];if(r){if(!d.alreadySubscribed(this.beforeMoveEvent,this.enforceConstraints,this)){this.beforeMoveEvent.subscribe(this.enforceConstraints,this,true);}if(!d.alreadySubscribed(this.beforeShowEvent,this._primeXYFromDOM)){this.beforeShowEvent.subscribe(this._primeXYFromDOM);}}else{this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);this.beforeMoveEvent.unsubscribe(this.enforceConstraints,this);}},configContext:function(u,t,q){var x=t[0],r,o,v,s,p,w=this.CONTEXT_TRIGGERS;if(x){r=x[0];o=x[1];v=x[2];s=x[3];p=x[4];if(w&&w.length>0){s=(s||[]).concat(w);}if(r){if(typeof r=="string"){this.cfg.setProperty("context",[document.getElementById(r),o,v,s,p],true);}if(o&&v){this.align(o,v,p);}if(this._contextTriggers){this._processTriggers(this._contextTriggers,e,this._alignOnTrigger);}if(s){this._processTriggers(s,h,this._alignOnTrigger);this._contextTriggers=s;}}}},_alignOnTrigger:function(p,o){this.align();},_findTriggerCE:function(o){var p=null;if(o instanceof m){p=o;}else{if(b._TRIGGER_MAP[o]){p=b._TRIGGER_MAP[o];}}return p;},_processTriggers:function(s,v,r){var q,u;for(var p=0,o=s.length;p<o;++p){q=s[p];u=this._findTriggerCE(q);if(u){u[v](r,this,true);}else{this[v](q,r);}}},align:function(p,w,s){var v=this.cfg.getProperty("context"),t=this,o,q,u;function r(z,A){var y=null,x=null;switch(p){case b.TOP_LEFT:y=A;x=z;break;case b.TOP_RIGHT:y=A-q.offsetWidth;x=z;break;case b.BOTTOM_LEFT:y=A;x=z-q.offsetHeight;break;case b.BOTTOM_RIGHT:y=A-q.offsetWidth;x=z-q.offsetHeight;break;}if(y!==null&&x!==null){if(s){y+=s[0];x+=s[1];}t.moveTo(y,x);}}if(v){o=v[0];q=this.element;t=this;if(!p){p=v[1];}if(!w){w=v[2];}if(!s&&v[4]){s=v[4];}if(q&&o){u=f.getRegion(o);switch(w){case b.TOP_LEFT:r(u.top,u.left);break;case b.TOP_RIGHT:r(u.top,u.right);break;case b.BOTTOM_LEFT:r(u.bottom,u.left);break;case b.BOTTOM_RIGHT:r(u.bottom,u.right);break;}}}},enforceConstraints:function(p,o,q){var s=o[0];var r=this.getConstrainedXY(s[0],s[1]);this.cfg.setProperty("x",r[0],true);this.cfg.setProperty("y",r[1],true);this.cfg.setProperty("xy",r,true);},_getConstrainedPos:function(y,p){var t=this.element,r=b.VIEWPORT_OFFSET,A=(y=="x"),z=(A)?t.offsetWidth:t.offsetHeight,s=(A)?f.getViewportWidth():f.getViewportHeight(),D=(A)?f.getDocumentScrollLeft():f.getDocumentScrollTop(),C=(A)?b.PREVENT_OVERLAP_X:b.PREVENT_OVERLAP_Y,o=this.cfg.getProperty("context"),u=(z+r<s),w=this.cfg.getProperty("preventcontextoverlap")&&o&&C[(o[1]+o[2])],v=D+r,B=D+s-z-r,q=p;if(p<v||p>B){if(w){q=this._preventOverlap(y,o[0],z,s,D);}else{if(u){if(p<v){q=v;}else{if(p>B){q=B;}}}else{q=v;}}}return q;},_preventOverlap:function(y,w,z,u,C){var A=(y=="x"),t=b.VIEWPORT_OFFSET,s=this,q=((A)?f.getX(w):f.getY(w))-C,o=(A)?w.offsetWidth:w.offsetHeight,p=q-t,r=(u-(q+o))-t,D=false,v=function(){var x;if((s.cfg.getProperty(y)-C)>q){x=(q-z);}else{x=(q+o);}s.cfg.setProperty(y,(x+C),true);return x;},B=function(){var E=((s.cfg.getProperty(y)-C)>q)?r:p,x;if(z>E){if(D){v();}else{v();D=true;x=B();}}return x;};B();return this.cfg.getProperty(y);},getConstrainedX:function(o){return this._getConstrainedPos("x",o);},getConstrainedY:function(o){return this._getConstrainedPos("y",o);},getConstrainedXY:function(o,p){return[this.getConstrainedX(o),this.getConstrainedY(p)];},center:function(){var r=b.VIEWPORT_OFFSET,s=this.element.offsetWidth,q=this.element.offsetHeight,p=f.getViewportWidth(),t=f.getViewportHeight(),o,u;if(s<p){o=(p/2)-(s/2)+f.getDocumentScrollLeft();}else{o=r+f.getDocumentScrollLeft();}if(q<t){u=(t/2)-(q/2)+f.getDocumentScrollTop();}else{u=r+f.getDocumentScrollTop();}this.cfg.setProperty("xy",[parseInt(o,10),parseInt(u,10)]);this.cfg.refireEvent("iframe");if(k.webkit){this.forceContainerRedraw();}},syncPosition:function(){var o=f.getXY(this.element);
+this.cfg.setProperty("x",o[0],true);this.cfg.setProperty("y",o[1],true);this.cfg.setProperty("xy",o,true);},onDomResize:function(q,p){var o=this;b.superclass.onDomResize.call(this,q,p);setTimeout(function(){o.syncPosition();o.cfg.refireEvent("iframe");o.cfg.refireEvent("context");},0);},_getComputedHeight:(function(){if(document.defaultView&&document.defaultView.getComputedStyle){return function(p){var o=null;if(p.ownerDocument&&p.ownerDocument.defaultView){var q=p.ownerDocument.defaultView.getComputedStyle(p,"");if(q){o=parseInt(q.height,10);}}return(i.isNumber(o))?o:null;};}else{return function(p){var o=null;if(p.style.pixelHeight){o=p.style.pixelHeight;}return(i.isNumber(o))?o:null;};}})(),_validateAutoFillHeight:function(o){return(!o)||(i.isString(o)&&b.STD_MOD_RE.test(o));},_autoFillOnHeightChange:function(r,p,q){var o=this.cfg.getProperty("height");if((o&&o!=="auto")||(o===0)){this.fillHeight(q);}},_getPreciseHeight:function(p){var o=p.offsetHeight;if(p.getBoundingClientRect){var q=p.getBoundingClientRect();o=q.bottom-q.top;}return o;},fillHeight:function(r){if(r){var p=this.innerElement||this.element,o=[this.header,this.body,this.footer],v,w=0,x=0,t=0,q=false;for(var u=0,s=o.length;u<s;u++){v=o[u];if(v){if(r!==v){x+=this._getPreciseHeight(v);}else{q=true;}}}if(q){if(k.ie||k.opera){f.setStyle(r,"height",0+"px");}w=this._getComputedHeight(p);if(w===null){f.addClass(p,"yui-override-padding");w=p.clientHeight;f.removeClass(p,"yui-override-padding");}t=Math.max(w-x,0);f.setStyle(r,"height",t+"px");if(r.offsetHeight!=t){t=Math.max(t-(r.offsetHeight-t),0);}f.setStyle(r,"height",t+"px");}}},bringToTop:function(){var s=[],r=this.element;function v(z,y){var B=f.getStyle(z,"zIndex"),A=f.getStyle(y,"zIndex"),x=(!B||isNaN(B))?0:parseInt(B,10),w=(!A||isNaN(A))?0:parseInt(A,10);if(x>w){return -1;}else{if(x<w){return 1;}else{return 0;}}}function q(y){var x=f.hasClass(y,b.CSS_OVERLAY),w=YAHOO.widget.Panel;if(x&&!f.isAncestor(r,y)){if(w&&f.hasClass(y,w.CSS_PANEL)){s[s.length]=y.parentNode;}else{s[s.length]=y;}}}f.getElementsBy(q,"div",document.body);s.sort(v);var o=s[0],u;if(o){u=f.getStyle(o,"zIndex");if(!isNaN(u)){var t=false;if(o!=r){t=true;}else{if(s.length>1){var p=f.getStyle(s[1],"zIndex");if(!isNaN(p)&&(u==p)){t=true;}}}if(t){this.cfg.setProperty("zindex",(parseInt(u,10)+2));}}}},destroy:function(o){if(this.iframe){this.iframe.parentNode.removeChild(this.iframe);}this.iframe=null;b.windowResizeEvent.unsubscribe(this.doCenterOnDOMEvent,this);b.windowScrollEvent.unsubscribe(this.doCenterOnDOMEvent,this);g.textResizeEvent.unsubscribe(this._autoFillOnHeightChange);if(this._contextTriggers){this._processTriggers(this._contextTriggers,e,this._alignOnTrigger);}b.superclass.destroy.call(this,o);},forceContainerRedraw:function(){var o=this;f.addClass(o.element,"yui-force-redraw");setTimeout(function(){f.removeClass(o.element,"yui-force-redraw");},0);},toString:function(){return"Overlay "+this.id;}});}());(function(){YAHOO.widget.OverlayManager=function(g){this.init(g);};var d=YAHOO.widget.Overlay,c=YAHOO.util.Event,e=YAHOO.util.Dom,b=YAHOO.util.Config,f=YAHOO.util.CustomEvent,a=YAHOO.widget.OverlayManager;a.CSS_FOCUSED="focused";a.prototype={constructor:a,overlays:null,initDefaultConfig:function(){this.cfg.addProperty("overlays",{suppressEvent:true});this.cfg.addProperty("focusevent",{value:"mousedown"});},init:function(i){this.cfg=new b(this);this.initDefaultConfig();if(i){this.cfg.applyConfig(i,true);}this.cfg.fireQueue();var h=null;this.getActive=function(){return h;};this.focus=function(j){var k=this.find(j);if(k){k.focus();}};this.remove=function(k){var m=this.find(k),j;if(m){if(h==m){h=null;}var l=(m.element===null&&m.cfg===null)?true:false;if(!l){j=e.getStyle(m.element,"zIndex");m.cfg.setProperty("zIndex",-1000,true);}this.overlays.sort(this.compareZIndexDesc);this.overlays=this.overlays.slice(0,(this.overlays.length-1));m.hideEvent.unsubscribe(m.blur);m.destroyEvent.unsubscribe(this._onOverlayDestroy,m);m.focusEvent.unsubscribe(this._onOverlayFocusHandler,m);m.blurEvent.unsubscribe(this._onOverlayBlurHandler,m);if(!l){c.removeListener(m.element,this.cfg.getProperty("focusevent"),this._onOverlayElementFocus);m.cfg.setProperty("zIndex",j,true);m.cfg.setProperty("manager",null);}if(m.focusEvent._managed){m.focusEvent=null;}if(m.blurEvent._managed){m.blurEvent=null;}if(m.focus._managed){m.focus=null;}if(m.blur._managed){m.blur=null;}}};this.blurAll=function(){var k=this.overlays.length,j;if(k>0){j=k-1;do{this.overlays[j].blur();}while(j--);}};this._manageBlur=function(j){var k=false;if(h==j){e.removeClass(h.element,a.CSS_FOCUSED);h=null;k=true;}return k;};this._manageFocus=function(j){var k=false;if(h!=j){if(h){h.blur();}h=j;this.bringToTop(h);e.addClass(h.element,a.CSS_FOCUSED);k=true;}return k;};var g=this.cfg.getProperty("overlays");if(!this.overlays){this.overlays=[];}if(g){this.register(g);this.overlays.sort(this.compareZIndexDesc);}},_onOverlayElementFocus:function(i){var g=c.getTarget(i),h=this.close;if(h&&(g==h||e.isAncestor(h,g))){this.blur();}else{this.focus();}},_onOverlayDestroy:function(h,g,i){this.remove(i);},_onOverlayFocusHandler:function(h,g,i){this._manageFocus(i);},_onOverlayBlurHandler:function(h,g,i){this._manageBlur(i);},_bindFocus:function(g){var h=this;if(!g.focusEvent){g.focusEvent=g.createEvent("focus");g.focusEvent.signature=f.LIST;g.focusEvent._managed=true;}else{g.focusEvent.subscribe(h._onOverlayFocusHandler,g,h);}if(!g.focus){c.on(g.element,h.cfg.getProperty("focusevent"),h._onOverlayElementFocus,null,g);g.focus=function(){if(h._manageFocus(this)){if(this.cfg.getProperty("visible")&&this.focusFirst){this.focusFirst();}this.focusEvent.fire();}};g.focus._managed=true;}},_bindBlur:function(g){var h=this;if(!g.blurEvent){g.blurEvent=g.createEvent("blur");g.blurEvent.signature=f.LIST;g.focusEvent._managed=true;}else{g.blurEvent.subscribe(h._onOverlayBlurHandler,g,h);}if(!g.blur){g.blur=function(){if(h._manageBlur(this)){this.blurEvent.fire();}};g.blur._managed=true;}g.hideEvent.subscribe(g.blur);
+},_bindDestroy:function(g){var h=this;g.destroyEvent.subscribe(h._onOverlayDestroy,g,h);},_syncZIndex:function(g){var h=e.getStyle(g.element,"zIndex");if(!isNaN(h)){g.cfg.setProperty("zIndex",parseInt(h,10));}else{g.cfg.setProperty("zIndex",0);}},register:function(g){var k=false,h,j;if(g instanceof d){g.cfg.addProperty("manager",{value:this});this._bindFocus(g);this._bindBlur(g);this._bindDestroy(g);this._syncZIndex(g);this.overlays.push(g);this.bringToTop(g);k=true;}else{if(g instanceof Array){for(h=0,j=g.length;h<j;h++){k=this.register(g[h])||k;}}}return k;},bringToTop:function(m){var i=this.find(m),l,g,j;if(i){j=this.overlays;j.sort(this.compareZIndexDesc);g=j[0];if(g){l=e.getStyle(g.element,"zIndex");if(!isNaN(l)){var k=false;if(g!==i){k=true;}else{if(j.length>1){var h=e.getStyle(j[1].element,"zIndex");if(!isNaN(h)&&(l==h)){k=true;}}}if(k){i.cfg.setProperty("zindex",(parseInt(l,10)+2));}}j.sort(this.compareZIndexDesc);}}},find:function(g){var l=g instanceof d,j=this.overlays,p=j.length,k=null,m,h;if(l||typeof g=="string"){for(h=p-1;h>=0;h--){m=j[h];if((l&&(m===g))||(m.id==g)){k=m;break;}}}return k;},compareZIndexDesc:function(j,i){var h=(j.cfg)?j.cfg.getProperty("zIndex"):null,g=(i.cfg)?i.cfg.getProperty("zIndex"):null;if(h===null&&g===null){return 0;}else{if(h===null){return 1;}else{if(g===null){return -1;}else{if(h>g){return -1;}else{if(h<g){return 1;}else{return 0;}}}}}},showAll:function(){var h=this.overlays,j=h.length,g;for(g=j-1;g>=0;g--){h[g].show();}},hideAll:function(){var h=this.overlays,j=h.length,g;for(g=j-1;g>=0;g--){h[g].hide();}},toString:function(){return"OverlayManager";}};}());(function(){YAHOO.widget.ContainerEffect=function(e,h,g,d,f){if(!f){f=YAHOO.util.Anim;}this.overlay=e;this.attrIn=h;this.attrOut=g;this.targetElement=d||e.element;this.animClass=f;};var b=YAHOO.util.Dom,c=YAHOO.util.CustomEvent,a=YAHOO.widget.ContainerEffect;a.FADE=function(d,f){var g=YAHOO.util.Easing,i={attributes:{opacity:{from:0,to:1}},duration:f,method:g.easeIn},e={attributes:{opacity:{to:0}},duration:f,method:g.easeOut},h=new a(d,i,e,d.element);h.handleUnderlayStart=function(){var k=this.overlay.underlay;if(k&&YAHOO.env.ua.ie){var j=(k.filters&&k.filters.length>0);if(j){b.addClass(d.element,"yui-effect-fade");}}};h.handleUnderlayComplete=function(){var j=this.overlay.underlay;if(j&&YAHOO.env.ua.ie){b.removeClass(d.element,"yui-effect-fade");}};h.handleStartAnimateIn=function(k,j,l){l.overlay._fadingIn=true;b.addClass(l.overlay.element,"hide-select");if(!l.overlay.underlay){l.overlay.cfg.refireEvent("underlay");}l.handleUnderlayStart();l.overlay._setDomVisibility(true);b.setStyle(l.overlay.element,"opacity",0);};h.handleCompleteAnimateIn=function(k,j,l){l.overlay._fadingIn=false;b.removeClass(l.overlay.element,"hide-select");if(l.overlay.element.style.filter){l.overlay.element.style.filter=null;}l.handleUnderlayComplete();l.overlay.cfg.refireEvent("iframe");l.animateInCompleteEvent.fire();};h.handleStartAnimateOut=function(k,j,l){l.overlay._fadingOut=true;b.addClass(l.overlay.element,"hide-select");l.handleUnderlayStart();};h.handleCompleteAnimateOut=function(k,j,l){l.overlay._fadingOut=false;b.removeClass(l.overlay.element,"hide-select");if(l.overlay.element.style.filter){l.overlay.element.style.filter=null;}l.overlay._setDomVisibility(false);b.setStyle(l.overlay.element,"opacity",1);l.handleUnderlayComplete();l.overlay.cfg.refireEvent("iframe");l.animateOutCompleteEvent.fire();};h.init();return h;};a.SLIDE=function(f,d){var i=YAHOO.util.Easing,l=f.cfg.getProperty("x")||b.getX(f.element),k=f.cfg.getProperty("y")||b.getY(f.element),m=b.getClientWidth(),h=f.element.offsetWidth,j={attributes:{points:{to:[l,k]}},duration:d,method:i.easeIn},e={attributes:{points:{to:[(m+25),k]}},duration:d,method:i.easeOut},g=new a(f,j,e,f.element,YAHOO.util.Motion);g.handleStartAnimateIn=function(o,n,p){p.overlay.element.style.left=((-25)-h)+"px";p.overlay.element.style.top=k+"px";};g.handleTweenAnimateIn=function(q,p,r){var s=b.getXY(r.overlay.element),o=s[0],n=s[1];if(b.getStyle(r.overlay.element,"visibility")=="hidden"&&o<l){r.overlay._setDomVisibility(true);}r.overlay.cfg.setProperty("xy",[o,n],true);r.overlay.cfg.refireEvent("iframe");};g.handleCompleteAnimateIn=function(o,n,p){p.overlay.cfg.setProperty("xy",[l,k],true);p.startX=l;p.startY=k;p.overlay.cfg.refireEvent("iframe");p.animateInCompleteEvent.fire();};g.handleStartAnimateOut=function(o,n,r){var p=b.getViewportWidth(),s=b.getXY(r.overlay.element),q=s[1];r.animOut.attributes.points.to=[(p+25),q];};g.handleTweenAnimateOut=function(p,o,q){var s=b.getXY(q.overlay.element),n=s[0],r=s[1];q.overlay.cfg.setProperty("xy",[n,r],true);q.overlay.cfg.refireEvent("iframe");};g.handleCompleteAnimateOut=function(o,n,p){p.overlay._setDomVisibility(false);p.overlay.cfg.setProperty("xy",[l,k]);p.animateOutCompleteEvent.fire();};g.init();return g;};a.prototype={init:function(){this.beforeAnimateInEvent=this.createEvent("beforeAnimateIn");this.beforeAnimateInEvent.signature=c.LIST;this.beforeAnimateOutEvent=this.createEvent("beforeAnimateOut");this.beforeAnimateOutEvent.signature=c.LIST;this.animateInCompleteEvent=this.createEvent("animateInComplete");this.animateInCompleteEvent.signature=c.LIST;this.animateOutCompleteEvent=this.createEvent("animateOutComplete");this.animateOutCompleteEvent.signature=c.LIST;this.animIn=new this.animClass(this.targetElement,this.attrIn.attributes,this.attrIn.duration,this.attrIn.method);this.animIn.onStart.subscribe(this.handleStartAnimateIn,this);this.animIn.onTween.subscribe(this.handleTweenAnimateIn,this);this.animIn.onComplete.subscribe(this.handleCompleteAnimateIn,this);this.animOut=new this.animClass(this.targetElement,this.attrOut.attributes,this.attrOut.duration,this.attrOut.method);this.animOut.onStart.subscribe(this.handleStartAnimateOut,this);this.animOut.onTween.subscribe(this.handleTweenAnimateOut,this);this.animOut.onComplete.subscribe(this.handleCompleteAnimateOut,this);},animateIn:function(){this._stopAnims(this.lastFrameOnStop);
+this.beforeAnimateInEvent.fire();this.animIn.animate();},animateOut:function(){this._stopAnims(this.lastFrameOnStop);this.beforeAnimateOutEvent.fire();this.animOut.animate();},lastFrameOnStop:true,_stopAnims:function(d){if(this.animOut&&this.animOut.isAnimated()){this.animOut.stop(d);}if(this.animIn&&this.animIn.isAnimated()){this.animIn.stop(d);}},handleStartAnimateIn:function(e,d,f){},handleTweenAnimateIn:function(e,d,f){},handleCompleteAnimateIn:function(e,d,f){},handleStartAnimateOut:function(e,d,f){},handleTweenAnimateOut:function(e,d,f){},handleCompleteAnimateOut:function(e,d,f){},toString:function(){var d="ContainerEffect";if(this.overlay){d+=" ["+this.overlay.toString()+"]";}return d;}};YAHOO.lang.augmentProto(a,YAHOO.util.EventProvider);})();YAHOO.register("containercore",YAHOO.widget.Module,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/cookie/cookie-min.js b/Websites/bugs.webkit.org/js/yui/cookie/cookie-min.js
new file mode 100644
index 0000000..290c4241
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/cookie/cookie-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.namespace("util");YAHOO.util.Cookie={_createCookieString:function(B,D,C,A){var F=YAHOO.lang,E=encodeURIComponent(B)+"="+(C?encodeURIComponent(D):D);if(F.isObject(A)){if(A.expires instanceof Date){E+="; expires="+A.expires.toUTCString();}if(F.isString(A.path)&&A.path!==""){E+="; path="+A.path;}if(F.isString(A.domain)&&A.domain!==""){E+="; domain="+A.domain;}if(A.secure===true){E+="; secure";}}return E;},_createCookieHashString:function(B){var D=YAHOO.lang;if(!D.isObject(B)){throw new TypeError("Cookie._createCookieHashString(): Argument must be an object.");}var C=[];for(var A in B){if(D.hasOwnProperty(B,A)&&!D.isFunction(B[A])&&!D.isUndefined(B[A])){C.push(encodeURIComponent(A)+"="+encodeURIComponent(String(B[A])));}}return C.join("&");},_parseCookieHash:function(E){var D=E.split("&"),F=null,C={};if(E.length>0){for(var B=0,A=D.length;B<A;B++){F=D[B].split("=");C[decodeURIComponent(F[0])]=decodeURIComponent(F[1]);}}return C;},_parseCookieString:function(J,A){var K={};if(YAHOO.lang.isString(J)&&J.length>0){var B=(A===false?function(L){return L;}:decodeURIComponent);var H=J.split(/;\s/g),I=null,C=null,E=null;for(var D=0,F=H.length;D<F;D++){E=H[D].match(/([^=]+)=/i);if(E instanceof Array){try{I=decodeURIComponent(E[1]);C=B(H[D].substring(E[1].length+1));}catch(G){}}else{I=decodeURIComponent(H[D]);C="";}K[I]=C;}}return K;},exists:function(A){if(!YAHOO.lang.isString(A)||A===""){throw new TypeError("Cookie.exists(): Cookie name must be a non-empty string.");}var B=this._parseCookieString(document.cookie,true);return B.hasOwnProperty(A);},get:function(B,A){var E=YAHOO.lang,C;if(E.isFunction(A)){C=A;A={};}else{if(E.isObject(A)){C=A.converter;}else{A={};}}var D=this._parseCookieString(document.cookie,!A.raw);if(!E.isString(B)||B===""){throw new TypeError("Cookie.get(): Cookie name must be a non-empty string.");}if(E.isUndefined(D[B])){return null;}if(!E.isFunction(C)){return D[B];}else{return C(D[B]);}},getSub:function(A,C,B){var E=YAHOO.lang,D=this.getSubs(A);if(D!==null){if(!E.isString(C)||C===""){throw new TypeError("Cookie.getSub(): Subcookie name must be a non-empty string.");}if(E.isUndefined(D[C])){return null;}if(!E.isFunction(B)){return D[C];}else{return B(D[C]);}}else{return null;}},getSubs:function(B){var A=YAHOO.lang.isString;if(!A(B)||B===""){throw new TypeError("Cookie.getSubs(): Cookie name must be a non-empty string.");}var C=this._parseCookieString(document.cookie,false);if(A(C[B])){return this._parseCookieHash(C[B]);}return null;},remove:function(B,A){if(!YAHOO.lang.isString(B)||B===""){throw new TypeError("Cookie.remove(): Cookie name must be a non-empty string.");}A=YAHOO.lang.merge(A||{},{expires:new Date(0)});return this.set(B,"",A);},removeSub:function(B,E,A){var F=YAHOO.lang;A=A||{};if(!F.isString(B)||B===""){throw new TypeError("Cookie.removeSub(): Cookie name must be a non-empty string.");}if(!F.isString(E)||E===""){throw new TypeError("Cookie.removeSub(): Subcookie name must be a non-empty string.");}var D=this.getSubs(B);if(F.isObject(D)&&F.hasOwnProperty(D,E)){delete D[E];if(!A.removeIfEmpty){return this.setSubs(B,D,A);}else{for(var C in D){if(F.hasOwnProperty(D,C)&&!F.isFunction(D[C])&&!F.isUndefined(D[C])){return this.setSubs(B,D,A);}}return this.remove(B,A);}}else{return"";}},set:function(B,C,A){var E=YAHOO.lang;A=A||{};if(!E.isString(B)){throw new TypeError("Cookie.set(): Cookie name must be a string.");}if(E.isUndefined(C)){throw new TypeError("Cookie.set(): Value cannot be undefined.");}var D=this._createCookieString(B,C,!A.raw,A);document.cookie=D;return D;},setSub:function(B,D,C,A){var F=YAHOO.lang;if(!F.isString(B)||B===""){throw new TypeError("Cookie.setSub(): Cookie name must be a non-empty string.");}if(!F.isString(D)||D===""){throw new TypeError("Cookie.setSub(): Subcookie name must be a non-empty string.");}if(F.isUndefined(C)){throw new TypeError("Cookie.setSub(): Subcookie value cannot be undefined.");}var E=this.getSubs(B);if(!F.isObject(E)){E={};}E[D]=C;return this.setSubs(B,E,A);},setSubs:function(B,C,A){var E=YAHOO.lang;if(!E.isString(B)){throw new TypeError("Cookie.setSubs(): Cookie name must be a string.");}if(!E.isObject(C)){throw new TypeError("Cookie.setSubs(): Cookie value must be an object.");}var D=this._createCookieString(B,this._createCookieHashString(C),false,A);document.cookie=D;return D;}};YAHOO.register("cookie",YAHOO.util.Cookie,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/datasource/datasource-min.js b/Websites/bugs.webkit.org/js/yui/datasource/datasource-min.js
new file mode 100644
index 0000000..d2a3da3
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/datasource/datasource-min.js
@@ -0,0 +1,12 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var lang=YAHOO.lang,util=YAHOO.util,Ev=util.Event;util.DataSourceBase=function(oLiveData,oConfigs){if(oLiveData===null||oLiveData===undefined){return;}this.liveData=oLiveData;this._oQueue={interval:null,conn:null,requests:[]};this.responseSchema={};if(oConfigs&&(oConfigs.constructor==Object)){for(var sConfig in oConfigs){if(sConfig){this[sConfig]=oConfigs[sConfig];}}}var maxCacheEntries=this.maxCacheEntries;if(!lang.isNumber(maxCacheEntries)||(maxCacheEntries<0)){maxCacheEntries=0;}this._aIntervals=[];this.createEvent("cacheRequestEvent");this.createEvent("cacheResponseEvent");this.createEvent("requestEvent");this.createEvent("responseEvent");this.createEvent("responseParseEvent");this.createEvent("responseCacheEvent");this.createEvent("dataErrorEvent");this.createEvent("cacheFlushEvent");var DS=util.DataSourceBase;this._sName="DataSource instance"+DS._nIndex;DS._nIndex++;};var DS=util.DataSourceBase;lang.augmentObject(DS,{TYPE_UNKNOWN:-1,TYPE_JSARRAY:0,TYPE_JSFUNCTION:1,TYPE_XHR:2,TYPE_JSON:3,TYPE_XML:4,TYPE_TEXT:5,TYPE_HTMLTABLE:6,TYPE_SCRIPTNODE:7,TYPE_LOCAL:8,ERROR_DATAINVALID:"Invalid data",ERROR_DATANULL:"Null data",_nIndex:0,_nTransactionId:0,_cloneObject:function(o){if(!lang.isValue(o)){return o;}var copy={};if(Object.prototype.toString.apply(o)==="[object RegExp]"){copy=o;}else{if(lang.isFunction(o)){copy=o;}else{if(lang.isArray(o)){var array=[];for(var i=0,len=o.length;i<len;i++){array[i]=DS._cloneObject(o[i]);}copy=array;}else{if(lang.isObject(o)){for(var x in o){if(lang.hasOwnProperty(o,x)){if(lang.isValue(o[x])&&lang.isObject(o[x])||lang.isArray(o[x])){copy[x]=DS._cloneObject(o[x]);}else{copy[x]=o[x];}}}}else{copy=o;}}}}return copy;},_getLocationValue:function(field,context){var locator=field.locator||field.key||field,xmldoc=context.ownerDocument||context,result,res,value=null;try{if(!lang.isUndefined(xmldoc.evaluate)){result=xmldoc.evaluate(locator,context,xmldoc.createNSResolver(!context.ownerDocument?context.documentElement:context.ownerDocument.documentElement),0,null);while(res=result.iterateNext()){value=res.textContent;}}else{xmldoc.setProperty("SelectionLanguage","XPath");result=context.selectNodes(locator)[0];value=result.value||result.text||null;}return value;}catch(e){}},issueCallback:function(callback,params,error,scope){if(lang.isFunction(callback)){callback.apply(scope,params);}else{if(lang.isObject(callback)){scope=callback.scope||scope||window;var callbackFunc=callback.success;if(error){callbackFunc=callback.failure;}if(callbackFunc){callbackFunc.apply(scope,params.concat([callback.argument]));}}}},parseString:function(oData){if(!lang.isValue(oData)){return null;}var string=oData+"";if(lang.isString(string)){return string;}else{return null;}},parseNumber:function(oData){if(!lang.isValue(oData)||(oData==="")){return null;}var number=oData*1;if(lang.isNumber(number)){return number;}else{return null;}},convertNumber:function(oData){return DS.parseNumber(oData);},parseDate:function(oData){var date=null;if(lang.isValue(oData)&&!(oData instanceof Date)){date=new Date(oData);}else{return oData;}if(date instanceof Date){return date;}else{return null;}},convertDate:function(oData){return DS.parseDate(oData);}});DS.Parser={string:DS.parseString,number:DS.parseNumber,date:DS.parseDate};DS.prototype={_sName:null,_aCache:null,_oQueue:null,_aIntervals:null,maxCacheEntries:0,liveData:null,dataType:DS.TYPE_UNKNOWN,responseType:DS.TYPE_UNKNOWN,responseSchema:null,useXPath:false,cloneBeforeCaching:false,toString:function(){return this._sName;},getCachedResponse:function(oRequest,oCallback,oCaller){var aCache=this._aCache;if(this.maxCacheEntries>0){if(!aCache){this._aCache=[];}else{var nCacheLength=aCache.length;if(nCacheLength>0){var oResponse=null;this.fireEvent("cacheRequestEvent",{request:oRequest,callback:oCallback,caller:oCaller});for(var i=nCacheLength-1;i>=0;i--){var oCacheElem=aCache[i];if(this.isCacheHit(oRequest,oCacheElem.request)){oResponse=oCacheElem.response;this.fireEvent("cacheResponseEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});if(i<nCacheLength-1){aCache.splice(i,1);this.addToCache(oRequest,oResponse);}oResponse.cached=true;break;}}return oResponse;}}}else{if(aCache){this._aCache=null;}}return null;},isCacheHit:function(oRequest,oCachedRequest){return(oRequest===oCachedRequest);},addToCache:function(oRequest,oResponse){var aCache=this._aCache;if(!aCache){return;}while(aCache.length>=this.maxCacheEntries){aCache.shift();}oResponse=(this.cloneBeforeCaching)?DS._cloneObject(oResponse):oResponse;var oCacheElem={request:oRequest,response:oResponse};aCache[aCache.length]=oCacheElem;this.fireEvent("responseCacheEvent",{request:oRequest,response:oResponse});},flushCache:function(){if(this._aCache){this._aCache=[];this.fireEvent("cacheFlushEvent");}},setInterval:function(nMsec,oRequest,oCallback,oCaller){if(lang.isNumber(nMsec)&&(nMsec>=0)){var oSelf=this;var nId=setInterval(function(){oSelf.makeConnection(oRequest,oCallback,oCaller);},nMsec);this._aIntervals.push(nId);return nId;}else{}},clearInterval:function(nId){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){if(tracker[i]===nId){tracker.splice(i,1);clearInterval(nId);}}},clearAllIntervals:function(){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){clearInterval(tracker[i]);}tracker=[];},sendRequest:function(oRequest,oCallback,oCaller){var oCachedResponse=this.getCachedResponse(oRequest,oCallback,oCaller);if(oCachedResponse){DS.issueCallback(oCallback,[oRequest,oCachedResponse],false,oCaller);return null;}return this.makeConnection(oRequest,oCallback,oCaller);},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=this.liveData;this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;},handleResponse:function(oRequest,oRawResponse,oCallback,oCaller,tId){this.fireEvent("responseEvent",{tId:tId,request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller});
+var xhr=(this.dataType==DS.TYPE_XHR)?true:false;var oParsedResponse=null;var oFullResponse=oRawResponse;if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oRawResponse&&oRawResponse.getResponseHeader)?oRawResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}else{if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse&&oRawResponse.nodeType&&(oRawResponse.nodeType===9||oRawResponse.nodeType===1||oRawResponse.nodeType===11)){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}}switch(this.responseType){case DS.TYPE_JSARRAY:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var arrayEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,arrayEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e1){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseArrayData(oRequest,oFullResponse);break;case DS.TYPE_JSON:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var objEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,objEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseJSONData(oRequest,oFullResponse);break;case DS.TYPE_HTMLTABLE:if(xhr&&oRawResponse.responseText){var el=document.createElement("div");el.innerHTML=oRawResponse.responseText;oFullResponse=el.getElementsByTagName("table")[0];}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseHTMLTableData(oRequest,oFullResponse);break;case DS.TYPE_XML:if(xhr&&oRawResponse.responseXML){oFullResponse=oRawResponse.responseXML;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseXMLData(oRequest,oFullResponse);break;case DS.TYPE_TEXT:if(xhr&&lang.isString(oRawResponse.responseText)){oFullResponse=oRawResponse.responseText;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseTextData(oRequest,oFullResponse);break;default:oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseData(oRequest,oFullResponse);break;}oParsedResponse=oParsedResponse||{};if(!oParsedResponse.results){oParsedResponse.results=[];}if(!oParsedResponse.meta){oParsedResponse.meta={};}if(!oParsedResponse.error){oParsedResponse=this.doBeforeCallback(oRequest,oFullResponse,oParsedResponse,oCallback);this.fireEvent("responseParseEvent",{request:oRequest,response:oParsedResponse,callback:oCallback,caller:oCaller});this.addToCache(oRequest,oParsedResponse);}else{oParsedResponse.error=true;this.fireEvent("dataErrorEvent",{request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});}oParsedResponse.tId=tId;DS.issueCallback(oCallback,[oRequest,oParsedResponse],oParsedResponse.error,oCaller);},doBeforeParseData:function(oRequest,oFullResponse,oCallback){return oFullResponse;},doBeforeCallback:function(oRequest,oFullResponse,oParsedResponse,oCallback){return oParsedResponse;},parseData:function(oRequest,oFullResponse){if(lang.isValue(oFullResponse)){var oParsedResponse={results:oFullResponse,meta:{}};return oParsedResponse;}return null;},parseArrayData:function(oRequest,oFullResponse){if(lang.isArray(oFullResponse)){var results=[],i,j,rec,field,data;if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(i=fields.length-1;i>=0;--i){if(typeof fields[i]!=="object"){fields[i]={key:fields[i]};}}var parsers={},p;for(i=fields.length-1;i>=0;--i){p=(typeof fields[i].parser==="function"?fields[i].parser:DS.Parser[fields[i].parser+""])||fields[i].converter;if(p){parsers[fields[i].key]=p;}}var arrType=lang.isArray(oFullResponse[0]);for(i=oFullResponse.length-1;i>-1;i--){var oResult={};rec=oFullResponse[i];if(typeof rec==="object"){for(j=fields.length-1;j>-1;j--){field=fields[j];data=arrType?rec[j]:rec[field.key];if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;}}else{if(lang.isString(rec)){for(j=fields.length-1;j>-1;j--){field=fields[j];data=rec;if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;
+}}}results[i]=oResult;}}else{results=oFullResponse;}var oParsedResponse={results:results};return oParsedResponse;}return null;},parseTextData:function(oRequest,oFullResponse){if(lang.isString(oFullResponse)){if(lang.isString(this.responseSchema.recordDelim)&&lang.isString(this.responseSchema.fieldDelim)){var oParsedResponse={results:[]};var recDelim=this.responseSchema.recordDelim;var fieldDelim=this.responseSchema.fieldDelim;if(oFullResponse.length>0){var newLength=oFullResponse.length-recDelim.length;if(oFullResponse.substr(newLength)==recDelim){oFullResponse=oFullResponse.substr(0,newLength);}if(oFullResponse.length>0){var recordsarray=oFullResponse.split(recDelim);for(var i=0,len=recordsarray.length,recIdx=0;i<len;++i){var bError=false,sRecord=recordsarray[i];if(lang.isString(sRecord)&&(sRecord.length>0)){var fielddataarray=recordsarray[i].split(fieldDelim);var oResult={};if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(var j=fields.length-1;j>-1;j--){try{var data=fielddataarray[j];if(lang.isString(data)){if(data.charAt(0)=='"'){data=data.substr(1);}if(data.charAt(data.length-1)=='"'){data=data.substr(0,data.length-1);}var field=fields[j];var key=(lang.isValue(field.key))?field.key:field;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}else{bError=true;}}catch(e){bError=true;}}}else{oResult=fielddataarray;}if(!bError){oParsedResponse.results[recIdx++]=oResult;}}}}}return oParsedResponse;}}return null;},parseXMLResult:function(result){var oResult={},schema=this.responseSchema;try{for(var m=schema.fields.length-1;m>=0;m--){var field=schema.fields[m];var key=(lang.isValue(field.key))?field.key:field;var data=null;if(this.useXPath){data=YAHOO.util.DataSource._getLocationValue(field,result);}else{var xmlAttr=result.attributes.getNamedItem(key);if(xmlAttr){data=xmlAttr.value;}else{var xmlNode=result.getElementsByTagName(key);if(xmlNode&&xmlNode.item(0)){var item=xmlNode.item(0);data=(item)?((item.text)?item.text:(item.textContent)?item.textContent:null):null;if(!data){var datapieces=[];for(var j=0,len=item.childNodes.length;j<len;j++){if(item.childNodes[j].nodeValue){datapieces[datapieces.length]=item.childNodes[j].nodeValue;}}if(datapieces.length>0){data=datapieces.join("");}}}}}if(data===null){data="";}if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}}catch(e){}return oResult;},parseXMLData:function(oRequest,oFullResponse){var bError=false,schema=this.responseSchema,oParsedResponse={meta:{}},xmlList=null,metaNode=schema.metaNode,metaLocators=schema.metaFields||{},i,k,loc,v;try{if(this.useXPath){for(k in metaLocators){oParsedResponse.meta[k]=YAHOO.util.DataSource._getLocationValue(metaLocators[k],oFullResponse);}}else{metaNode=metaNode?oFullResponse.getElementsByTagName(metaNode)[0]:oFullResponse;if(metaNode){for(k in metaLocators){if(lang.hasOwnProperty(metaLocators,k)){loc=metaLocators[k];v=metaNode.getElementsByTagName(loc)[0];if(v){v=v.firstChild.nodeValue;}else{v=metaNode.attributes.getNamedItem(loc);if(v){v=v.value;}}if(lang.isValue(v)){oParsedResponse.meta[k]=v;}}}}}xmlList=(schema.resultNode)?oFullResponse.getElementsByTagName(schema.resultNode):null;}catch(e){}if(!xmlList||!lang.isArray(schema.fields)){bError=true;}else{oParsedResponse.results=[];for(i=xmlList.length-1;i>=0;--i){var oResult=this.parseXMLResult(xmlList.item(i));oParsedResponse.results[i]=oResult;}}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;},parseJSONData:function(oRequest,oFullResponse){var oParsedResponse={results:[],meta:{}};if(lang.isObject(oFullResponse)&&this.responseSchema.resultsList){var schema=this.responseSchema,fields=schema.fields,resultsList=oFullResponse,results=[],metaFields=schema.metaFields||{},fieldParsers=[],fieldPaths=[],simpleFields=[],bError=false,i,len,j,v,key,parser,path;var buildPath=function(needle){var path=null,keys=[],i=0;if(needle){needle=needle.replace(/\[(['"])(.*?)\1\]/g,function(x,$1,$2){keys[i]=$2;return".@"+(i++);}).replace(/\[(\d+)\]/g,function(x,$1){keys[i]=parseInt($1,10)|0;return".@"+(i++);}).replace(/^\./,"");if(!/[^\w\.\$@]/.test(needle)){path=needle.split(".");for(i=path.length-1;i>=0;--i){if(path[i].charAt(0)==="@"){path[i]=keys[parseInt(path[i].substr(1),10)];}}}else{}}return path;};var walkPath=function(path,origin){var v=origin,i=0,len=path.length;for(;i<len&&v;++i){v=v[path[i]];}return v;};path=buildPath(schema.resultsList);if(path){resultsList=walkPath(path,oFullResponse);if(resultsList===undefined){bError=true;}}else{bError=true;}if(!resultsList){resultsList=[];}if(!lang.isArray(resultsList)){resultsList=[resultsList];}if(!bError){if(schema.fields){var field;for(i=0,len=fields.length;i<len;i++){field=fields[i];key=field.key||field;parser=((typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""])||field.converter;path=buildPath(key);if(parser){fieldParsers[fieldParsers.length]={key:key,parser:parser};}if(path){if(path.length>1){fieldPaths[fieldPaths.length]={key:key,path:path};}else{simpleFields[simpleFields.length]={key:key,path:path[0]};}}else{}}for(i=resultsList.length-1;i>=0;--i){var r=resultsList[i],rec={};if(r){for(j=simpleFields.length-1;j>=0;--j){rec[simpleFields[j].key]=(r[simpleFields[j].path]!==undefined)?r[simpleFields[j].path]:r[j];}for(j=fieldPaths.length-1;j>=0;--j){rec[fieldPaths[j].key]=walkPath(fieldPaths[j].path,r);}for(j=fieldParsers.length-1;j>=0;--j){var p=fieldParsers[j].key;rec[p]=fieldParsers[j].parser.call(this,rec[p]);if(rec[p]===undefined){rec[p]=null;}}}results[i]=rec;}}else{results=resultsList;}for(key in metaFields){if(lang.hasOwnProperty(metaFields,key)){path=buildPath(metaFields[key]);
+if(path){v=walkPath(path,oFullResponse);oParsedResponse.meta[key]=v;}}}}else{oParsedResponse.error=true;}oParsedResponse.results=results;}else{oParsedResponse.error=true;}return oParsedResponse;},parseHTMLTableData:function(oRequest,oFullResponse){var bError=false;var elTable=oFullResponse;var fields=this.responseSchema.fields;var oParsedResponse={results:[]};if(lang.isArray(fields)){for(var i=0;i<elTable.tBodies.length;i++){var elTbody=elTable.tBodies[i];for(var j=elTbody.rows.length-1;j>-1;j--){var elRow=elTbody.rows[j];var oResult={};for(var k=fields.length-1;k>-1;k--){var field=fields[k];var key=(lang.isValue(field.key))?field.key:field;var data=elRow.cells[k].innerHTML;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}oParsedResponse.results[j]=oResult;}}}else{bError=true;}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;}};lang.augmentProto(DS,util.EventProvider);util.LocalDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_LOCAL;if(oLiveData){if(YAHOO.lang.isArray(oLiveData)){this.responseType=DS.TYPE_JSARRAY;}else{if(oLiveData.nodeType&&oLiveData.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oLiveData.nodeName&&(oLiveData.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;oLiveData=oLiveData.cloneNode(true);}else{if(YAHOO.lang.isString(oLiveData)){this.responseType=DS.TYPE_TEXT;}else{if(YAHOO.lang.isObject(oLiveData)){this.responseType=DS.TYPE_JSON;}}}}}}else{oLiveData=[];this.responseType=DS.TYPE_JSARRAY;}util.LocalDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.LocalDataSource,DS);lang.augmentObject(util.LocalDataSource,DS);util.FunctionDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_JSFUNCTION;oLiveData=oLiveData||function(){};util.FunctionDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.FunctionDataSource,DS,{scope:null,makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=(this.scope)?this.liveData.call(this.scope,oRequest,this,oCallback):this.liveData(oRequest,oCallback);if(this.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse&&oRawResponse.nodeType&&oRawResponse.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;}});lang.augmentObject(util.FunctionDataSource,DS);util.ScriptNodeDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_SCRIPTNODE;oLiveData=oLiveData||"";util.ScriptNodeDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.ScriptNodeDataSource,DS,{getUtility:util.Get,asyncMode:"allowAll",scriptCallbackParam:"callback",generateRequestCallback:function(id){return"&"+this.scriptCallbackParam+"=YAHOO.util.ScriptNodeDataSource.callbacks["+id+"]";},doBeforeGetScriptNode:function(sUri){return sUri;},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});if(util.ScriptNodeDataSource._nPending===0){util.ScriptNodeDataSource.callbacks=[];util.ScriptNodeDataSource._nId=0;}var id=util.ScriptNodeDataSource._nId;util.ScriptNodeDataSource._nId++;var oSelf=this;util.ScriptNodeDataSource.callbacks[id]=function(oRawResponse){if((oSelf.asyncMode!=="ignoreStaleResponses")||(id===util.ScriptNodeDataSource.callbacks.length-1)){if(oSelf.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){oSelf.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse.nodeType&&oRawResponse.nodeType==9){oSelf.responseType=DS.TYPE_XML;}else{if(oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){oSelf.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){oSelf.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){oSelf.responseType=DS.TYPE_TEXT;}}}}}}oSelf.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);}else{}delete util.ScriptNodeDataSource.callbacks[id];};util.ScriptNodeDataSource._nPending++;var sUri=this.liveData+oRequest+this.generateRequestCallback(id);sUri=this.doBeforeGetScriptNode(sUri);this.getUtility.script(sUri,{autopurge:true,onsuccess:util.ScriptNodeDataSource._bumpPendingDown,onfail:util.ScriptNodeDataSource._bumpPendingDown});return tId;}});lang.augmentObject(util.ScriptNodeDataSource,DS);lang.augmentObject(util.ScriptNodeDataSource,{_nId:0,_nPending:0,callbacks:[]});util.XHRDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_XHR;this.connMgr=this.connMgr||util.Connect;oLiveData=oLiveData||"";util.XHRDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.XHRDataSource,DS,{connMgr:null,connXhrMode:"allowAll",connMethodPost:false,connTimeout:0,makeConnection:function(oRequest,oCallback,oCaller){var oRawResponse=null;var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oSelf=this;var oConnMgr=this.connMgr;var oQueue=this._oQueue;var _xhrSuccess=function(oResponse){if(oResponse&&(this.connXhrMode=="ignoreStaleResponses")&&(oResponse.tId!=oQueue.conn.tId)){return null;}else{if(!oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:null,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);return null;
+}else{if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oResponse.getResponseHeader)?oResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}}this.handleResponse(oRequest,oResponse,oCallback,oCaller,tId);}}};var _xhrFailure=function(oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATAINVALID});if(lang.isString(this.liveData)&&lang.isString(oRequest)&&(this.liveData.lastIndexOf("?")!==this.liveData.length-1)&&(oRequest.indexOf("?")!==0)){}oResponse=oResponse||{};oResponse.error=true;DS.issueCallback(oCallback,[oRequest,oResponse],true,oCaller);return null;};var _xhrCallback={success:_xhrSuccess,failure:_xhrFailure,scope:this};if(lang.isNumber(this.connTimeout)){_xhrCallback.timeout=this.connTimeout;}if(this.connXhrMode=="cancelStaleRequests"){if(oQueue.conn){if(oConnMgr.abort){oConnMgr.abort(oQueue.conn);oQueue.conn=null;}else{}}}if(oConnMgr&&oConnMgr.asyncRequest){var sLiveData=this.liveData;var isPost=this.connMethodPost;var sMethod=(isPost)?"POST":"GET";var sUri=(isPost||!lang.isValue(oRequest))?sLiveData:sLiveData+oRequest;var sRequest=(isPost)?oRequest:null;if(this.connXhrMode!="queueRequests"){oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}else{if(oQueue.conn){var allRequests=oQueue.requests;allRequests.push({request:oRequest,callback:_xhrCallback});if(!oQueue.interval){oQueue.interval=setInterval(function(){if(oConnMgr.isCallInProgress(oQueue.conn)){return;}else{if(allRequests.length>0){sUri=(isPost||!lang.isValue(allRequests[0].request))?sLiveData:sLiveData+allRequests[0].request;sRequest=(isPost)?allRequests[0].request:null;oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,allRequests[0].callback,sRequest);allRequests.shift();}else{clearInterval(oQueue.interval);oQueue.interval=null;}}},50);}}else{oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}}}else{DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);}return tId;}});lang.augmentObject(util.XHRDataSource,DS);util.DataSource=function(oLiveData,oConfigs){oConfigs=oConfigs||{};var dataType=oConfigs.dataType;if(dataType){if(dataType==DS.TYPE_LOCAL){return new util.LocalDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_XHR){return new util.XHRDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_SCRIPTNODE){return new util.ScriptNodeDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_JSFUNCTION){return new util.FunctionDataSource(oLiveData,oConfigs);}}}}}if(YAHOO.lang.isString(oLiveData)){return new util.XHRDataSource(oLiveData,oConfigs);}else{if(YAHOO.lang.isFunction(oLiveData)){return new util.FunctionDataSource(oLiveData,oConfigs);}else{return new util.LocalDataSource(oLiveData,oConfigs);}}};lang.augmentObject(util.DataSource,DS);})();YAHOO.util.Number={format:function(e,k){if(e===""||e===null||!isFinite(e)){return"";}e=+e;k=YAHOO.lang.merge(YAHOO.util.Number.format.defaults,(k||{}));var j=e+"",l=Math.abs(e),b=k.decimalPlaces||0,r=k.thousandsSeparator,f=k.negativeFormat||("-"+k.format),q,p,g,h;if(f.indexOf("#")>-1){f=f.replace(/#/,k.format);}if(b<0){q=l-(l%1)+"";g=q.length+b;if(g>0){q=Number("."+q).toFixed(g).slice(2)+new Array(q.length-g+1).join("0");}else{q="0";}}else{var a=l+"";if(b>0||a.indexOf(".")>0){var d=Math.pow(10,b);q=Math.round(l*d)/d+"";var c=q.indexOf("."),m,o;if(c<0){m=b;o=(Math.pow(10,m)+"").substring(1);if(b>0){q=q+"."+o;}}else{m=b-(q.length-c-1);o=(Math.pow(10,m)+"").substring(1);q=q+o;}}else{q=l.toFixed(b)+"";}}p=q.split(/\D/);if(l>=1000){g=p[0].length%3||3;p[0]=p[0].slice(0,g)+p[0].slice(g).replace(/(\d{3})/g,r+"$1");}return YAHOO.util.Number.format._applyFormat((e<0?f:k.format),p.join(k.decimalSeparator),k);}};YAHOO.util.Number.format.defaults={format:"{prefix}{number}{suffix}",negativeFormat:null,decimalSeparator:".",decimalPlaces:null,thousandsSeparator:""};YAHOO.util.Number.format._applyFormat=function(a,b,c){return a.replace(/\{(\w+)\}/g,function(d,e){return e==="number"?b:e in c?c[e]:"";});};(function(){var a=function(c,e,d){if(typeof d==="undefined"){d=10;}for(;parseInt(c,10)<d&&d>1;d/=10){c=e.toString()+c;}return c.toString();};var b={formats:{a:function(e,c){return c.a[e.getDay()];},A:function(e,c){return c.A[e.getDay()];},b:function(e,c){return c.b[e.getMonth()];},B:function(e,c){return c.B[e.getMonth()];},C:function(c){return a(parseInt(c.getFullYear()/100,10),0);},d:["getDate","0"],e:["getDate"," "],g:function(c){return a(parseInt(b.formats.G(c)%100,10),0);},G:function(f){var g=f.getFullYear();var e=parseInt(b.formats.V(f),10);var c=parseInt(b.formats.W(f),10);if(c>e){g++;}else{if(c===0&&e>=52){g--;}}return g;},H:["getHours","0"],I:function(e){var c=e.getHours()%12;return a(c===0?12:c,0);},j:function(h){var g=new Date(""+h.getFullYear()+"/1/1 GMT");var e=new Date(""+h.getFullYear()+"/"+(h.getMonth()+1)+"/"+h.getDate()+" GMT");var c=e-g;var f=parseInt(c/60000/60/24,10)+1;return a(f,0,100);},k:["getHours"," "],l:function(e){var c=e.getHours()%12;return a(c===0?12:c," ");},m:function(c){return a(c.getMonth()+1,0);},M:["getMinutes","0"],p:function(e,c){return c.p[e.getHours()>=12?1:0];},P:function(e,c){return c.P[e.getHours()>=12?1:0];},s:function(e,c){return parseInt(e.getTime()/1000,10);},S:["getSeconds","0"],u:function(c){var e=c.getDay();return e===0?7:e;},U:function(g){var c=parseInt(b.formats.j(g),10);var f=6-g.getDay();var e=parseInt((c+f)/7,10);return a(e,0);},V:function(g){var f=parseInt(b.formats.W(g),10);var c=(new Date(""+g.getFullYear()+"/1/1")).getDay();var e=f+(c>4||c<=1?0:1);if(e===53&&(new Date(""+g.getFullYear()+"/12/31")).getDay()<4){e=1;}else{if(e===0){e=b.formats.V(new Date(""+(g.getFullYear()-1)+"/12/31"));}}return a(e,0);},w:"getDay",W:function(g){var c=parseInt(b.formats.j(g),10);var f=7-b.formats.u(g);var e=parseInt((c+f)/7,10);
+return a(e,0,10);},y:function(c){return a(c.getFullYear()%100,0);},Y:"getFullYear",z:function(f){var e=f.getTimezoneOffset();var c=a(parseInt(Math.abs(e/60),10),0);var g=a(Math.abs(e%60),0);return(e>0?"-":"+")+c+g;},Z:function(c){var e=c.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/,"$2").replace(/[a-z ]/g,"");if(e.length>4){e=b.formats.z(c);}return e;},"%":function(c){return"%";}},aggregates:{c:"locale",D:"%m/%d/%y",F:"%Y-%m-%d",h:"%b",n:"\n",r:"locale",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"},format:function(g,f,d){f=f||{};if(!(g instanceof Date)){return YAHOO.lang.isValue(g)?g:"";}var h=f.format||"%m/%d/%Y";if(h==="YYYY/MM/DD"){h="%Y/%m/%d";}else{if(h==="DD/MM/YYYY"){h="%d/%m/%Y";}else{if(h==="MM/DD/YYYY"){h="%m/%d/%Y";}}}d=d||"en";if(!(d in YAHOO.util.DateLocale)){if(d.replace(/-[a-zA-Z]+$/,"") in YAHOO.util.DateLocale){d=d.replace(/-[a-zA-Z]+$/,"");}else{d="en";}}var j=YAHOO.util.DateLocale[d];var c=function(l,k){var m=b.aggregates[k];return(m==="locale"?j[k]:m);};var e=function(l,k){var m=b.formats[k];if(typeof m==="string"){return g[m]();}else{if(typeof m==="function"){return m.call(g,g,j);}else{if(typeof m==="object"&&typeof m[0]==="string"){return a(g[m[0]](),m[1]);}else{return k;}}}};while(h.match(/%[cDFhnrRtTxX]/)){h=h.replace(/%([cDFhnrRtTxX])/g,c);}var i=h.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g,e);c=e=undefined;return i;}};YAHOO.namespace("YAHOO.util");YAHOO.util.Date=b;YAHOO.util.DateLocale={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],r:"%I:%M:%S %p",x:"%d/%m/%y",X:"%T"};YAHOO.util.DateLocale["en"]=YAHOO.lang.merge(YAHOO.util.DateLocale,{});YAHOO.util.DateLocale["en-US"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{c:"%a %d %b %Y %I:%M:%S %p %Z",x:"%m/%d/%Y",X:"%I:%M:%S %p"});YAHOO.util.DateLocale["en-GB"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{r:"%l:%M:%S %P %Z"});YAHOO.util.DateLocale["en-AU"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"]);})();YAHOO.register("datasource",YAHOO.util.DataSource,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/datatable/datatable-min.js b/Websites/bugs.webkit.org/js/yui/datatable/datatable-min.js
new file mode 100644
index 0000000..f603091
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/datatable/datatable-min.js
@@ -0,0 +1,33 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.Chain=function(){this.q=[].slice.call(arguments);this.createEvent("end");};YAHOO.util.Chain.prototype={id:0,run:function(){var g=this.q[0],d;if(!g){this.fireEvent("end");return this;}else{if(this.id){return this;}}d=g.method||g;if(typeof d==="function"){var f=g.scope||{},b=g.argument||[],a=g.timeout||0,e=this;if(!(b instanceof Array)){b=[b];}if(a<0){this.id=a;if(g.until){for(;!g.until();){d.apply(f,b);}}else{if(g.iterations){for(;g.iterations-->0;){d.apply(f,b);}}else{d.apply(f,b);}}this.q.shift();this.id=0;return this.run();}else{if(g.until){if(g.until()){this.q.shift();return this.run();}}else{if(!g.iterations||!--g.iterations){this.q.shift();}}this.id=setTimeout(function(){d.apply(f,b);if(e.id){e.id=0;e.run();}},a);}}return this;},add:function(a){this.q.push(a);return this;},pause:function(){if(this.id>0){clearTimeout(this.id);}this.id=0;return this;},stop:function(){this.pause();this.q=[];return this;}};YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);(function(){var a=YAHOO.util.Event,c=YAHOO.lang,b=[],d=function(h,e,f){var g;if(!h||h===f){g=false;}else{g=YAHOO.util.Selector.test(h,e)?h:d(h.parentNode,e,f);}return g;};c.augmentObject(a,{_createDelegate:function(f,e,g,h){return function(i){var j=this,n=a.getTarget(i),l=e,p=(j.nodeType===9),q,k,o,m;if(c.isFunction(e)){q=e(n);}else{if(c.isString(e)){if(!p){o=j.id;if(!o){o=a.generateId(j);}m=("#"+o+" ");l=(m+e).replace(/,/gi,(","+m));}if(YAHOO.util.Selector.test(n,l)){q=n;}else{if(YAHOO.util.Selector.test(n,((l.replace(/,/gi," *,"))+" *"))){q=d(n,l,j);}}}}if(q){k=q;if(h){if(h===true){k=g;}else{k=h;}}return f.call(k,i,q,j,g);}};},delegate:function(f,j,l,g,h,i){var e=j,k,m;if(c.isString(g)&&!YAHOO.util.Selector){return false;}if(j=="mouseenter"||j=="mouseleave"){if(!a._createMouseDelegate){return false;}e=a._getType(j);k=a._createMouseDelegate(l,h,i);m=a._createDelegate(function(p,o,n){return k.call(o,p,n);},g,h,i);}else{m=a._createDelegate(l,g,h,i);}b.push([f,e,l,m]);return a.on(f,e,m);},removeDelegate:function(f,j,i){var k=j,h=false,g,e;if(j=="mouseenter"||j=="mouseleave"){k=a._getType(j);}g=a._getCacheIndex(b,f,k,i);if(g>=0){e=b[g];}if(f&&e){h=a.removeListener(e[0],e[1],e[3]);if(h){delete b[g][2];delete b[g][3];b.splice(g,1);}}return h;}});}());(function(){var b=YAHOO.util.Event,g=YAHOO.lang,e=b.addListener,f=b.removeListener,c=b.getListeners,d=[],h={mouseenter:"mouseover",mouseleave:"mouseout"},a=function(n,m,l){var j=b._getCacheIndex(d,n,m,l),i,k;if(j>=0){i=d[j];}if(n&&i){k=f.call(b,i[0],m,i[3]);if(k){delete d[j][2];delete d[j][3];d.splice(j,1);}}return k;};g.augmentObject(b._specialTypes,h);g.augmentObject(b,{_createMouseDelegate:function(i,j,k){return function(q,m){var p=this,l=b.getRelatedTarget(q),o,n;if(p!=l&&!YAHOO.util.Dom.isAncestor(p,l)){o=p;if(k){if(k===true){o=j;}else{o=k;}}n=[q,j];if(m){n.splice(1,0,p,m);}return i.apply(o,n);}};},addListener:function(m,l,k,n,o){var i,j;if(h[l]){i=b._createMouseDelegate(k,n,o);i.mouseDelegate=true;d.push([m,l,k,i]);j=e.call(b,m,l,i);}else{j=e.apply(b,arguments);}return j;},removeListener:function(l,k,j){var i;if(h[k]){i=a.apply(b,arguments);}else{i=f.apply(b,arguments);}return i;},getListeners:function(p,o){var n=[],r,m=(o==="mouseover"||o==="mouseout"),q,k,j;if(o&&(m||h[o])){r=c.call(b,p,this._getType(o));if(r){for(k=r.length-1;k>-1;k--){j=r[k];q=j.fn.mouseDelegate;if((h[o]&&q)||(m&&!q)){n.push(j);}}}}else{n=c.apply(b,arguments);}return(n&&n.length)?n:null;}},true);b.on=b.addListener;}());YAHOO.register("event-mouseenter",YAHOO.util.Event,{version:"2.9.0",build:"2800"});var Y=YAHOO,Y_DOM=YAHOO.util.Dom,EMPTY_ARRAY=[],Y_UA=Y.env.ua,Y_Lang=Y.lang,Y_DOC=document,Y_DOCUMENT_ELEMENT=Y_DOC.documentElement,Y_DOM_inDoc=Y_DOM.inDocument,Y_mix=Y_Lang.augmentObject,Y_guid=Y_DOM.generateId,Y_getDoc=function(a){var b=Y_DOC;if(a){b=(a.nodeType===9)?a:a.ownerDocument||a.document||Y_DOC;}return b;},Y_Array=function(g,d){var c,b,h=d||0;try{return Array.prototype.slice.call(g,h);}catch(f){b=[];c=g.length;for(;h<c;h++){b.push(g[h]);}return b;}},Y_DOM_allById=function(f,a){a=a||Y_DOC;var b=[],c=[],d,e;if(a.querySelectorAll){c=a.querySelectorAll('[id="'+f+'"]');}else{if(a.all){b=a.all(f);if(b){if(b.nodeName){if(b.id===f){c.push(b);b=EMPTY_ARRAY;}else{b=[b];}}if(b.length){for(d=0;e=b[d++];){if(e.id===f||(e.attributes&&e.attributes.id&&e.attributes.id.value===f)){c.push(e);}}}}}else{c=[Y_getDoc(a).getElementById(f)];}}return c;};var COMPARE_DOCUMENT_POSITION="compareDocumentPosition",OWNER_DOCUMENT="ownerDocument",Selector={_foundCache:[],useNative:true,_compare:("sourceIndex" in Y_DOCUMENT_ELEMENT)?function(f,e){var d=f.sourceIndex,c=e.sourceIndex;if(d===c){return 0;}else{if(d>c){return 1;}}return -1;}:(Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION]?function(b,a){if(b[COMPARE_DOCUMENT_POSITION](a)&4){return -1;}else{return 1;}}:function(e,d){var c,a,b;if(e&&d){c=e[OWNER_DOCUMENT].createRange();c.setStart(e,0);a=d[OWNER_DOCUMENT].createRange();a.setStart(d,0);b=c.compareBoundaryPoints(1,a);}return b;}),_sort:function(a){if(a){a=Y_Array(a,0,true);if(a.sort){a.sort(Selector._compare);}}return a;},_deDupe:function(a){var b=[],c,d;for(c=0;(d=a[c++]);){if(!d._found){b[b.length]=d;d._found=true;}}for(c=0;(d=b[c++]);){d._found=null;d.removeAttribute("_found");}return b;},query:function(b,j,k,a){if(typeof j=="string"){j=Y_DOM.get(j);if(!j){return(k)?null:[];}}else{j=j||Y_DOC;}var f=[],c=(Selector.useNative&&Y_DOC.querySelector&&!a),e=[[b,j]],g,l,d,h=(c)?Selector._nativeQuery:Selector._bruteQuery;if(b&&h){if(!a&&(!c||j.tagName)){e=Selector._splitQueries(b,j);}for(d=0;(g=e[d++]);){l=h(g[0],g[1],k);if(!k){l=Y_Array(l,0,true);}if(l){f=f.concat(l);}}if(e.length>1){f=Selector._sort(Selector._deDupe(f));}}Y.log("query: "+b+" returning: "+f.length,"info","Selector");return(k)?(f[0]||null):f;},_splitQueries:function(c,f){var b=c.split(","),d=[],g="",e,a;if(f){if(f.tagName){f.id=f.id||Y_guid();g='[id="'+f.id+'"] ';}for(e=0,a=b.length;e<a;++e){c=g+b[e];d.push([c,f]);}}return d;},_nativeQuery:function(a,b,c){if(Y_UA.webkit&&a.indexOf(":checked")>-1&&(Selector.pseudos&&Selector.pseudos.checked)){return Selector.query(a,b,c,true);
+}try{return b["querySelector"+(c?"":"All")](a);}catch(d){return Selector.query(a,b,c,true);}},filter:function(b,a){var c=[],d,e;if(b&&a){for(d=0;(e=b[d++]);){if(Selector.test(e,a)){c[c.length]=e;}}}else{Y.log("invalid filter input (nodes: "+b+", selector: "+a+")","warn","Selector");}return c;},test:function(c,d,k){var g=false,b=d.split(","),a=false,l,o,h,n,f,e,m;if(c&&c.tagName){if(!k&&!Y_DOM_inDoc(c)){l=c.parentNode;if(l){k=l;}else{n=c[OWNER_DOCUMENT].createDocumentFragment();n.appendChild(c);k=n;a=true;}}k=k||c[OWNER_DOCUMENT];if(!c.id){c.id=Y_guid();}for(f=0;(m=b[f++]);){m+='[id="'+c.id+'"]';h=Selector.query(m,k);for(e=0;o=h[e++];){if(o===c){g=true;break;}}if(g){break;}}if(a){n.removeChild(c);}}return g;}};YAHOO.util.Selector=Selector;var PARENT_NODE="parentNode",TAG_NAME="tagName",ATTRIBUTES="attributes",COMBINATOR="combinator",PSEUDOS="pseudos",SelectorCSS2={_reRegExpTokens:/([\^\$\?\[\]\*\+\-\.\(\)\|\\])/,SORT_RESULTS:true,_children:function(e,a){var b=e.children,d,c=[],f,g;if(e.children&&a&&e.children.tags){c=e.children.tags(a);}else{if((!b&&e[TAG_NAME])||(b&&a)){f=b||e.childNodes;b=[];for(d=0;(g=f[d++]);){if(g.tagName){if(!a||a===g.tagName){b.push(g);}}}}}return b||[];},_re:{attr:/(\[[^\]]*\])/g,esc:/\\[:\[\]\(\)#\.\'\>+~"]/gi,pseudos:/(\([^\)]*\))/g},shorthand:{"\\#(-?[_a-z]+[-\\w\\uE000]*)":"[id=$1]","\\.(-?[_a-z]+[-\\w\\uE000]*)":"[className~=$1]"},operators:{"":function(b,a){return !!b.getAttribute(a);},"~=":"(?:^|\\s+){val}(?:\\s+|$)","|=":"^{val}(?:-|$)"},pseudos:{"first-child":function(a){return Selector._children(a[PARENT_NODE])[0]===a;}},_bruteQuery:function(f,j,l){var g=[],a=[],i=Selector._tokenize(f),e=i[i.length-1],k=Y_getDoc(j),c,b,h,d;if(e){b=e.id;h=e.className;d=e.tagName||"*";if(j.getElementsByTagName){if(b&&(j.all||(j.nodeType===9||Y_DOM_inDoc(j)))){a=Y_DOM_allById(b,j);}else{if(h){a=j.getElementsByClassName(h);}else{a=j.getElementsByTagName(d);}}}else{c=j.firstChild;while(c){if(c.tagName){a.push(c);}c=c.nextSilbing||c.firstChild;}}if(a.length){g=Selector._filterNodes(a,i,l);}}return g;},_filterNodes:function(l,f,h){var r=0,q,s=f.length,k=s-1,e=[],o=l[0],v=o,t=Selector.getters,d,p,c,g,a,m,b,u;for(r=0;(v=o=l[r++]);){k=s-1;g=null;testLoop:while(v&&v.tagName){c=f[k];b=c.tests;q=b.length;if(q&&!a){while((u=b[--q])){d=u[1];if(t[u[0]]){m=t[u[0]](v,u[0]);}else{m=v[u[0]];if(m===undefined&&v.getAttribute){m=v.getAttribute(u[0]);}}if((d==="="&&m!==u[2])||(typeof d!=="string"&&d.test&&!d.test(m))||(!d.test&&typeof d==="function"&&!d(v,u[0],u[2]))){if((v=v[g])){while(v&&(!v.tagName||(c.tagName&&c.tagName!==v.tagName))){v=v[g];}}continue testLoop;}}}k--;if(!a&&(p=c.combinator)){g=p.axis;v=v[g];while(v&&!v.tagName){v=v[g];}if(p.direct){g=null;}}else{e.push(o);if(h){return e;}break;}}}o=v=null;return e;},combinators:{" ":{axis:"parentNode"},">":{axis:"parentNode",direct:true},"+":{axis:"previousSibling",direct:true}},_parsers:[{name:ATTRIBUTES,re:/^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,fn:function(d,e){var c=d[2]||"",a=Selector.operators,b=(d[3])?d[3].replace(/\\/g,""):"",f;if((d[1]==="id"&&c==="=")||(d[1]==="className"&&Y_DOCUMENT_ELEMENT.getElementsByClassName&&(c==="~="||c==="="))){e.prefilter=d[1];d[3]=b;e[d[1]]=(d[1]==="id")?d[3]:b;}if(c in a){f=a[c];if(typeof f==="string"){d[3]=b.replace(Selector._reRegExpTokens,"\\$1");f=new RegExp(f.replace("{val}",d[3]));}d[2]=f;}if(!e.last||e.prefilter!==d[1]){return d.slice(1);}}},{name:TAG_NAME,re:/^((?:-?[_a-z]+[\w-]*)|\*)/i,fn:function(b,c){var a=b[1].toUpperCase();c.tagName=a;if(a!=="*"&&(!c.last||c.prefilter)){return[TAG_NAME,"=",a];}if(!c.prefilter){c.prefilter="tagName";}}},{name:COMBINATOR,re:/^\s*([>+~]|\s)\s*/,fn:function(a,b){}},{name:PSEUDOS,re:/^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,fn:function(a,b){var c=Selector[PSEUDOS][a[1]];if(c){if(a[2]){a[2]=a[2].replace(/\\/g,"");}return[a[2],c];}else{return false;}}}],_getToken:function(a){return{tagName:null,id:null,className:null,attributes:{},combinator:null,tests:[]};},_tokenize:function(c){c=c||"";c=Selector._replaceShorthand(Y_Lang.trim(c));var b=Selector._getToken(),h=c,g=[],j=false,e,f,d,a;outer:do{j=false;for(d=0;(a=Selector._parsers[d++]);){if((e=a.re.exec(c))){if(a.name!==COMBINATOR){b.selector=c;}c=c.replace(e[0],"");if(!c.length){b.last=true;}if(Selector._attrFilters[e[1]]){e[1]=Selector._attrFilters[e[1]];}f=a.fn(e,b);if(f===false){j=false;break outer;}else{if(f){b.tests.push(f);}}if(!c.length||a.name===COMBINATOR){g.push(b);b=Selector._getToken(b);if(a.name===COMBINATOR){b.combinator=Selector.combinators[e[1]];}}j=true;}}}while(j&&c.length);if(!j||c.length){Y.log("query: "+h+" contains unsupported token in: "+c,"warn","Selector");g=[];}return g;},_replaceShorthand:function(b){var d=Selector.shorthand,c=b.match(Selector._re.esc),e,h,g,f,a;if(c){b=b.replace(Selector._re.esc,"\uE000");}e=b.match(Selector._re.attr);h=b.match(Selector._re.pseudos);if(e){b=b.replace(Selector._re.attr,"\uE001");}if(h){b=b.replace(Selector._re.pseudos,"\uE002");}for(g in d){if(d.hasOwnProperty(g)){b=b.replace(new RegExp(g,"gi"),d[g]);}}if(e){for(f=0,a=e.length;f<a;++f){b=b.replace(/\uE001/,e[f]);}}if(h){for(f=0,a=h.length;f<a;++f){b=b.replace(/\uE002/,h[f]);}}b=b.replace(/\[/g,"\uE003");b=b.replace(/\]/g,"\uE004");b=b.replace(/\(/g,"\uE005");b=b.replace(/\)/g,"\uE006");if(c){for(f=0,a=c.length;f<a;++f){b=b.replace("\uE000",c[f]);}}return b;},_attrFilters:{"class":"className","for":"htmlFor"},getters:{href:function(b,a){return Y_DOM.getAttribute(b,a);}}};Y_mix(Selector,SelectorCSS2,true);Selector.getters.src=Selector.getters.rel=Selector.getters.href;if(Selector.useNative&&Y_DOC.querySelector){Selector.shorthand["\\.([^\\s\\\\(\\[:]*)"]="[class~=$1]";}Selector._reNth=/^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;Selector._getNth=function(d,o,q,h){Selector._reNth.test(o);var m=parseInt(RegExp.$1,10),c=RegExp.$2,j=RegExp.$3,k=parseInt(RegExp.$4,10)||0,p=[],l=Selector._children(d.parentNode,q),f;if(j){m=2;f="+";c="n";k=(j==="odd")?1:0;}else{if(isNaN(m)){m=(c)?1:0;
+}}if(m===0){if(h){k=l.length-k+1;}if(l[k-1]===d){return true;}else{return false;}}else{if(m<0){h=!!h;m=Math.abs(m);}}if(!h){for(var e=k-1,g=l.length;e<g;e+=m){if(e>=0&&l[e]===d){return true;}}}else{for(var e=l.length-k,g=l.length;e>=0;e-=m){if(e<g&&l[e]===d){return true;}}}return false;};Y_mix(Selector.pseudos,{"root":function(a){return a===a.ownerDocument.documentElement;},"nth-child":function(a,b){return Selector._getNth(a,b);},"nth-last-child":function(a,b){return Selector._getNth(a,b,null,true);},"nth-of-type":function(a,b){return Selector._getNth(a,b,a.tagName);},"nth-last-of-type":function(a,b){return Selector._getNth(a,b,a.tagName,true);},"last-child":function(b){var a=Selector._children(b.parentNode);return a[a.length-1]===b;},"first-of-type":function(a){return Selector._children(a.parentNode,a.tagName)[0]===a;},"last-of-type":function(b){var a=Selector._children(b.parentNode,b.tagName);return a[a.length-1]===b;},"only-child":function(b){var a=Selector._children(b.parentNode);return a.length===1&&a[0]===b;},"only-of-type":function(b){var a=Selector._children(b.parentNode,b.tagName);return a.length===1&&a[0]===b;},"empty":function(a){return a.childNodes.length===0;},"not":function(a,b){return !Selector.test(a,b);},"contains":function(a,b){var c=a.innerText||a.textContent||"";return c.indexOf(b)>-1;},"checked":function(a){return(a.checked===true||a.selected===true);},enabled:function(a){return(a.disabled!==undefined&&!a.disabled);},disabled:function(a){return(a.disabled);}});Y_mix(Selector.operators,{"^=":"^{val}","!=":function(b,a,c){return b[a]!==c;},"$=":"{val}$","*=":"{val}"});Selector.combinators["~"]={axis:"previousSibling"};YAHOO.register("selector",YAHOO.util.Selector,{version:"2.9.0",build:"2800"});var Dom=YAHOO.util.Dom;YAHOO.widget.ColumnSet=function(a){this._sId=Dom.generateId(null,"yui-cs");a=YAHOO.widget.DataTable._cloneObject(a);this._init(a);YAHOO.widget.ColumnSet._nCount++;};YAHOO.widget.ColumnSet._nCount=0;YAHOO.widget.ColumnSet.prototype={_sId:null,_aDefinitions:null,tree:null,flat:null,keys:null,headers:null,_init:function(j){var k=[];var a=[];var g=[];var e=[];var c=-1;var b=function(m,s){c++;if(!k[c]){k[c]=[];}for(var o=0;o<m.length;o++){var i=m[o];var q=new YAHOO.widget.Column(i);i.yuiColumnId=q._sId;a.push(q);if(s){q._oParent=s;}if(YAHOO.lang.isArray(i.children)){q.children=i.children;var r=0;var p=function(v){var w=v.children;for(var u=0;u<w.length;u++){if(YAHOO.lang.isArray(w[u].children)){p(w[u]);}else{r++;}}};p(i);q._nColspan=r;var t=i.children;for(var n=0;n<t.length;n++){var l=t[n];if(q.className&&(l.className===undefined)){l.className=q.className;}if(q.editor&&(l.editor===undefined)){l.editor=q.editor;}if(q.editorOptions&&(l.editorOptions===undefined)){l.editorOptions=q.editorOptions;}if(q.formatter&&(l.formatter===undefined)){l.formatter=q.formatter;}if(q.resizeable&&(l.resizeable===undefined)){l.resizeable=q.resizeable;}if(q.sortable&&(l.sortable===undefined)){l.sortable=q.sortable;}if(q.hidden){l.hidden=true;}if(q.width&&(l.width===undefined)){l.width=q.width;}if(q.minWidth&&(l.minWidth===undefined)){l.minWidth=q.minWidth;}if(q.maxAutoWidth&&(l.maxAutoWidth===undefined)){l.maxAutoWidth=q.maxAutoWidth;}if(q.type&&(l.type===undefined)){l.type=q.type;}if(q.type&&!q.formatter){q.formatter=q.type;}if(q.text&&!YAHOO.lang.isValue(q.label)){q.label=q.text;}if(q.parser){}if(q.sortOptions&&((q.sortOptions.ascFunction)||(q.sortOptions.descFunction))){}}if(!k[c+1]){k[c+1]=[];}b(t,q);}else{q._nKeyIndex=g.length;q._nColspan=1;g.push(q);}k[c].push(q);}c--;};if(YAHOO.lang.isArray(j)){b(j);this._aDefinitions=j;}else{return null;}var f;var d=function(l){var n=1;var q;var o;var r=function(t,p){p=p||1;for(var u=0;u<t.length;u++){var m=t[u];if(YAHOO.lang.isArray(m.children)){p++;r(m.children,p);p--;}else{if(p>n){n=p;}}}};for(var i=0;i<l.length;i++){q=l[i];r(q);for(var s=0;s<q.length;s++){o=q[s];if(!YAHOO.lang.isArray(o.children)){o._nRowspan=n;}else{o._nRowspan=1;}}n=1;}};d(k);for(f=0;f<k[0].length;f++){k[0][f]._nTreeIndex=f;}var h=function(l,m){e[l].push(m.getSanitizedKey());if(m._oParent){h(l,m._oParent);}};for(f=0;f<g.length;f++){e[f]=[];h(f,g[f]);e[f]=e[f].reverse();}this.tree=k;this.flat=a;this.keys=g;this.headers=e;},getId:function(){return this._sId;},toString:function(){return"ColumnSet instance "+this._sId;},getDefinitions:function(){var a=this._aDefinitions;var b=function(e,g){for(var d=0;d<e.length;d++){var f=e[d];var i=g.getColumnById(f.yuiColumnId);if(i){var h=i.getDefinition();for(var c in h){if(YAHOO.lang.hasOwnProperty(h,c)){f[c]=h[c];}}}if(YAHOO.lang.isArray(f.children)){b(f.children,g);}}};b(a,this);this._aDefinitions=a;return a;},getColumnById:function(c){if(YAHOO.lang.isString(c)){var a=this.flat;for(var b=a.length-1;b>-1;b--){if(a[b]._sId===c){return a[b];}}}return null;},getColumn:function(c){if(YAHOO.lang.isNumber(c)&&this.keys[c]){return this.keys[c];}else{if(YAHOO.lang.isString(c)){var a=this.flat;var d=[];for(var b=0;b<a.length;b++){if(a[b].key===c){d.push(a[b]);}}if(d.length===1){return d[0];}else{if(d.length>1){return d;}}}}return null;},getDescendants:function(d){var b=this;var c=[];var a;var e=function(f){c.push(f);if(f.children){for(a=0;a<f.children.length;a++){e(b.getColumn(f.children[a].key));}}};e(d);return c;}};YAHOO.widget.Column=function(b){this._sId=Dom.generateId(null,"yui-col");if(b&&YAHOO.lang.isObject(b)){for(var a in b){if(a){this[a]=b[a];}}}if(!YAHOO.lang.isValue(this.key)){this.key=Dom.generateId(null,"yui-dt-col");}if(!YAHOO.lang.isValue(this.field)){this.field=this.key;}YAHOO.widget.Column._nCount++;if(this.width&&!YAHOO.lang.isNumber(this.width)){this.width=null;}if(this.editor&&YAHOO.lang.isString(this.editor)){this.editor=new YAHOO.widget.CellEditor(this.editor,this.editorOptions);}};YAHOO.lang.augmentObject(YAHOO.widget.Column,{_nCount:0,formatCheckbox:function(b,a,c,d){YAHOO.widget.DataTable.formatCheckbox(b,a,c,d);},formatCurrency:function(b,a,c,d){YAHOO.widget.DataTable.formatCurrency(b,a,c,d);},formatDate:function(b,a,c,d){YAHOO.widget.DataTable.formatDate(b,a,c,d);
+},formatEmail:function(b,a,c,d){YAHOO.widget.DataTable.formatEmail(b,a,c,d);},formatLink:function(b,a,c,d){YAHOO.widget.DataTable.formatLink(b,a,c,d);},formatNumber:function(b,a,c,d){YAHOO.widget.DataTable.formatNumber(b,a,c,d);},formatSelect:function(b,a,c,d){YAHOO.widget.DataTable.formatDropdown(b,a,c,d);}});YAHOO.widget.Column.prototype={_sId:null,_nKeyIndex:null,_nTreeIndex:null,_nColspan:1,_nRowspan:1,_oParent:null,_elTh:null,_elThLiner:null,_elThLabel:null,_elResizer:null,_nWidth:null,_dd:null,_ddResizer:null,key:null,field:null,label:null,abbr:null,children:null,width:null,minWidth:null,maxAutoWidth:null,hidden:false,selected:false,className:null,formatter:null,currencyOptions:null,dateOptions:null,dropdownOptions:null,editor:null,resizeable:false,sortable:false,sortOptions:null,getId:function(){return this._sId;},toString:function(){return"Column instance "+this._sId;},getDefinition:function(){var a={};a.abbr=this.abbr;a.className=this.className;a.editor=this.editor;a.editorOptions=this.editorOptions;a.field=this.field;a.formatter=this.formatter;a.hidden=this.hidden;a.key=this.key;a.label=this.label;a.minWidth=this.minWidth;a.maxAutoWidth=this.maxAutoWidth;a.resizeable=this.resizeable;a.selected=this.selected;a.sortable=this.sortable;a.sortOptions=this.sortOptions;a.width=this.width;a._calculatedWidth=this._calculatedWidth;return a;},getKey:function(){return this.key;},getField:function(){return this.field;},getSanitizedKey:function(){return this.getKey().replace(/[^\w\-]/g,"");},getKeyIndex:function(){return this._nKeyIndex;},getTreeIndex:function(){return this._nTreeIndex;},getParent:function(){return this._oParent;},getColspan:function(){return this._nColspan;},getColSpan:function(){return this.getColspan();},getRowspan:function(){return this._nRowspan;},getThEl:function(){return this._elTh;},getThLinerEl:function(){return this._elThLiner;},getResizerEl:function(){return this._elResizer;},getColEl:function(){return this.getThEl();},getIndex:function(){return this.getKeyIndex();},format:function(){}};YAHOO.util.Sort={compare:function(d,c,e){if((d===null)||(typeof d=="undefined")){if((c===null)||(typeof c=="undefined")){return 0;}else{return 1;}}else{if((c===null)||(typeof c=="undefined")){return -1;}}if(d.constructor==String){d=d.toLowerCase();}if(c.constructor==String){c=c.toLowerCase();}if(d<c){return(e)?1:-1;}else{if(d>c){return(e)?-1:1;}else{return 0;}}}};YAHOO.widget.ColumnDD=function(d,a,c,b){if(d&&a&&c&&b){this.datatable=d;this.table=d.getTableEl();this.column=a;this.headCell=c;this.pointer=b;this.newIndex=null;this.init(c);this.initFrame();this.invalidHandleTypes={};this.setPadding(10,0,(this.datatable.getTheadEl().offsetHeight+10),0);YAHOO.util.Event.on(window,"resize",function(){this.initConstraints();},this,true);}else{}};if(YAHOO.util.DDProxy){YAHOO.extend(YAHOO.widget.ColumnDD,YAHOO.util.DDProxy,{initConstraints:function(){var g=YAHOO.util.Dom.getRegion(this.table),d=this.getEl(),f=YAHOO.util.Dom.getXY(d),c=parseInt(YAHOO.util.Dom.getStyle(d,"width"),10),a=parseInt(YAHOO.util.Dom.getStyle(d,"height"),10),e=((f[0]-g.left)+15),b=((g.right-f[0]-c)+15);this.setXConstraint(e,b);this.setYConstraint(10,10);},_resizeProxy:function(){YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this,arguments);var a=this.getDragEl(),b=this.getEl();YAHOO.util.Dom.setStyle(this.pointer,"height",(this.table.parentNode.offsetHeight+10)+"px");YAHOO.util.Dom.setStyle(this.pointer,"display","block");var c=YAHOO.util.Dom.getXY(b);YAHOO.util.Dom.setXY(this.pointer,[c[0],(c[1]-5)]);YAHOO.util.Dom.setStyle(a,"height",this.datatable.getContainerEl().offsetHeight+"px");YAHOO.util.Dom.setStyle(a,"width",(parseInt(YAHOO.util.Dom.getStyle(a,"width"),10)+4)+"px");YAHOO.util.Dom.setXY(this.dragEl,c);},onMouseDown:function(){this.initConstraints();this.resetConstraints();},clickValidator:function(b){if(!this.column.hidden){var a=YAHOO.util.Event.getTarget(b);return(this.isValidHandleChild(a)&&(this.id==this.handleElId||this.DDM.handleWasClicked(a,this.id)));}},onDragOver:function(h,a){var f=this.datatable.getColumn(a);if(f){var c=f.getTreeIndex();while((c===null)&&f.getParent()){f=f.getParent();c=f.getTreeIndex();}if(c!==null){var b=f.getThEl();var k=c;var d=YAHOO.util.Event.getPageX(h),i=YAHOO.util.Dom.getX(b),j=i+((YAHOO.util.Dom.get(b).offsetWidth)/2),e=this.column.getTreeIndex();if(d<j){YAHOO.util.Dom.setX(this.pointer,i);}else{var g=parseInt(b.offsetWidth,10);YAHOO.util.Dom.setX(this.pointer,(i+g));k++;}if(c>e){k--;}if(k<0){k=0;}else{if(k>this.datatable.getColumnSet().tree[0].length){k=this.datatable.getColumnSet().tree[0].length;}}this.newIndex=k;}}},onDragDrop:function(){this.datatable.reorderColumn(this.column,this.newIndex);},endDrag:function(){this.newIndex=null;YAHOO.util.Dom.setStyle(this.pointer,"display","none");}});}YAHOO.util.ColumnResizer=function(e,c,d,a,b){if(e&&c&&d&&a){this.datatable=e;this.column=c;this.headCell=d;this.headCellLiner=c.getThLinerEl();this.resizerLiner=d.firstChild;this.init(a,a,{dragOnly:true,dragElId:b.id});this.initFrame();this.resetResizerEl();this.setPadding(0,1,0,0);}else{}};if(YAHOO.util.DD){YAHOO.extend(YAHOO.util.ColumnResizer,YAHOO.util.DDProxy,{resetResizerEl:function(){var a=YAHOO.util.Dom.get(this.handleElId).style;a.left="auto";a.right=0;a.top="auto";a.bottom=0;a.height=this.headCell.offsetHeight+"px";},onMouseUp:function(h){var f=this.datatable.getColumnSet().keys,b;for(var c=0,a=f.length;c<a;c++){b=f[c];if(b._ddResizer){b._ddResizer.resetResizerEl();}}this.resetResizerEl();var d=this.headCellLiner;var g=d.offsetWidth-(parseInt(YAHOO.util.Dom.getStyle(d,"paddingLeft"),10)|0)-(parseInt(YAHOO.util.Dom.getStyle(d,"paddingRight"),10)|0);this.datatable.fireEvent("columnResizeEvent",{column:this.column,target:this.headCell,width:g});},onMouseDown:function(a){this.startWidth=this.headCellLiner.offsetWidth;this.startX=YAHOO.util.Event.getXY(a)[0];this.nLinerPadding=(parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0)+(parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
+},clickValidator:function(b){if(!this.column.hidden){var a=YAHOO.util.Event.getTarget(b);return(this.isValidHandleChild(a)&&(this.id==this.handleElId||this.DDM.handleWasClicked(a,this.id)));}},startDrag:function(){var e=this.datatable.getColumnSet().keys,d=this.column.getKeyIndex(),b;for(var c=0,a=e.length;c<a;c++){b=e[c];if(b._ddResizer){YAHOO.util.Dom.get(b._ddResizer.handleElId).style.height="1em";}}},onDrag:function(c){var d=YAHOO.util.Event.getXY(c)[0];if(d>YAHOO.util.Dom.getX(this.headCellLiner)){var a=d-this.startX;var b=this.startWidth+a-this.nLinerPadding;if(b>0){this.datatable.setColumnWidth(this.column,b);}}}});}(function(){var g=YAHOO.lang,a=YAHOO.util,e=YAHOO.widget,c=a.Dom,f=a.Event,d=e.DataTable;YAHOO.widget.RecordSet=function(h){this._init(h);};var b=e.RecordSet;b._nCount=0;b.prototype={_sId:null,_init:function(h){this._sId=c.generateId(null,"yui-rs");e.RecordSet._nCount++;this._records=[];this._initEvents();if(h){if(g.isArray(h)){this.addRecords(h);}else{if(g.isObject(h)){this.addRecord(h);}}}},_initEvents:function(){this.createEvent("recordAddEvent");this.createEvent("recordsAddEvent");this.createEvent("recordSetEvent");this.createEvent("recordsSetEvent");this.createEvent("recordUpdateEvent");this.createEvent("recordDeleteEvent");this.createEvent("recordsDeleteEvent");this.createEvent("resetEvent");this.createEvent("recordValueUpdateEvent");},_addRecord:function(j,h){var i=new YAHOO.widget.Record(j);if(YAHOO.lang.isNumber(h)&&(h>-1)){this._records.splice(h,0,i);}else{this._records[this._records.length]=i;}return i;},_setRecord:function(i,h){if(!g.isNumber(h)||h<0){h=this._records.length;}return(this._records[h]=new e.Record(i));},_deleteRecord:function(i,h){if(!g.isNumber(h)||(h<0)){h=1;}this._records.splice(i,h);},getId:function(){return this._sId;},toString:function(){return"RecordSet instance "+this._sId;},getLength:function(){return this._records.length;},getRecord:function(h){var j;if(h instanceof e.Record){for(j=0;j<this._records.length;j++){if(this._records[j]&&(this._records[j]._sId===h._sId)){return h;}}}else{if(g.isNumber(h)){if((h>-1)&&(h<this.getLength())){return this._records[h];}}else{if(g.isString(h)){for(j=0;j<this._records.length;j++){if(this._records[j]&&(this._records[j]._sId===h)){return this._records[j];}}}}}return null;},getRecords:function(i,h){if(!g.isNumber(i)){return this._records;}if(!g.isNumber(h)){return this._records.slice(i);}return this._records.slice(i,i+h);},hasRecords:function(j,h){var l=this.getRecords(j,h);for(var k=0;k<h;++k){if(typeof l[k]==="undefined"){return false;}}return true;},getRecordIndex:function(j){if(j){for(var h=this._records.length-1;h>-1;h--){if(this._records[h]&&j.getId()===this._records[h].getId()){return h;}}}return null;},addRecord:function(j,h){if(g.isObject(j)){var i=this._addRecord(j,h);this.fireEvent("recordAddEvent",{record:i,data:j});return i;}else{return null;}},addRecords:function(m,l){if(g.isArray(m)){var p=[],j,n,h;l=g.isNumber(l)?l:this._records.length;j=l;for(n=0,h=m.length;n<h;++n){if(g.isObject(m[n])){var k=this._addRecord(m[n],j++);p.push(k);}}this.fireEvent("recordsAddEvent",{records:p,data:m});return p;}else{if(g.isObject(m)){var o=this._addRecord(m);this.fireEvent("recordsAddEvent",{records:[o],data:m});return o;}else{return null;}}},setRecord:function(j,h){if(g.isObject(j)){var i=this._setRecord(j,h);this.fireEvent("recordSetEvent",{record:i,data:j});return i;}else{return null;}},setRecords:function(o,n){var r=e.Record,k=g.isArray(o)?o:[o],q=[],p=0,h=k.length,m=0;n=parseInt(n,10)|0;for(;p<h;++p){if(typeof k[p]==="object"&&k[p]){q[m++]=this._records[n+p]=new r(k[p]);}}this.fireEvent("recordsSetEvent",{records:q,data:o});this.fireEvent("recordsSet",{records:q,data:o});if(k.length&&!q.length){}return q;},updateRecord:function(h,l){var j=this.getRecord(h);if(j&&g.isObject(l)){var k={};for(var i in j._oData){if(g.hasOwnProperty(j._oData,i)){k[i]=j._oData[i];}}j._oData=l;this.fireEvent("recordUpdateEvent",{record:j,newData:l,oldData:k});return j;}else{return null;}},updateKey:function(h,i,j){this.updateRecordValue(h,i,j);},updateRecordValue:function(h,k,n){var j=this.getRecord(h);if(j){var m=null;var l=j._oData[k];if(l&&g.isObject(l)){m={};for(var i in l){if(g.hasOwnProperty(l,i)){m[i]=l[i];}}}else{m=l;}j._oData[k]=n;this.fireEvent("keyUpdateEvent",{record:j,key:k,newData:n,oldData:m});this.fireEvent("recordValueUpdateEvent",{record:j,key:k,newData:n,oldData:m});}else{}},replaceRecords:function(h){this.reset();return this.addRecords(h);},sortRecords:function(h,j,i){return this._records.sort(function(l,k){return h(l,k,j,i);});},reverseRecords:function(){return this._records.reverse();},deleteRecord:function(h){if(g.isNumber(h)&&(h>-1)&&(h<this.getLength())){var i=this.getRecord(h).getData();this._deleteRecord(h);this.fireEvent("recordDeleteEvent",{data:i,index:h});return i;}else{return null;}},deleteRecords:function(k,h){if(!g.isNumber(h)){h=1;}if(g.isNumber(k)&&(k>-1)&&(k<this.getLength())){var m=this.getRecords(k,h);var j=[],n=[];for(var l=0;l<m.length;l++){j[j.length]=m[l];n[n.length]=m[l].getData();}this._deleteRecord(k,h);this.fireEvent("recordsDeleteEvent",{data:j,deletedData:n,index:k});return j;}else{return null;}},reset:function(){this._records=[];this.fireEvent("resetEvent");}};g.augmentProto(b,a.EventProvider);YAHOO.widget.Record=function(h){this._nCount=e.Record._nCount;this._sId=c.generateId(null,"yui-rec");e.Record._nCount++;this._oData={};if(g.isObject(h)){for(var i in h){if(g.hasOwnProperty(h,i)){this._oData[i]=h[i];}}}};YAHOO.widget.Record._nCount=0;YAHOO.widget.Record.prototype={_nCount:null,_sId:null,_oData:null,getCount:function(){return this._nCount;},getId:function(){return this._sId;},getData:function(h){if(g.isString(h)){return this._oData[h];}else{return this._oData;}},setData:function(h,i){this._oData[h]=i;}};})();(function(){var h=YAHOO.lang,a=YAHOO.util,e=YAHOO.widget,b=YAHOO.env.ua,c=a.Dom,g=a.Event,f=a.DataSourceBase;YAHOO.widget.DataTable=function(i,m,o,k){var l=e.DataTable;
+if(k&&k.scrollable){return new YAHOO.widget.ScrollingDataTable(i,m,o,k);}this._nIndex=l._nCount;this._sId=c.generateId(null,"yui-dt");this._oChainRender=new YAHOO.util.Chain();this._oChainRender.subscribe("end",this._onRenderChainEnd,this,true);this._initConfigs(k);this._initDataSource(o);if(!this._oDataSource){return;}this._initColumnSet(m);if(!this._oColumnSet){return;}this._initRecordSet();if(!this._oRecordSet){}l.superclass.constructor.call(this,i,this.configs);var q=this._initDomElements(i);if(!q){return;}this.showTableMessage(this.get("MSG_LOADING"),l.CLASS_LOADING);this._initEvents();l._nCount++;l._nCurrentCount++;var n={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,scope:this,argument:this.getState()};var p=this.get("initialLoad");if(p===true){this._oDataSource.sendRequest(this.get("initialRequest"),n);}else{if(p===false){this.showTableMessage(this.get("MSG_EMPTY"),l.CLASS_EMPTY);}else{var j=p||{};n.argument=j.argument||{};this._oDataSource.sendRequest(j.request,n);}}};var d=e.DataTable;h.augmentObject(d,{CLASS_DATATABLE:"yui-dt",CLASS_LINER:"yui-dt-liner",CLASS_LABEL:"yui-dt-label",CLASS_MESSAGE:"yui-dt-message",CLASS_MASK:"yui-dt-mask",CLASS_DATA:"yui-dt-data",CLASS_COLTARGET:"yui-dt-coltarget",CLASS_RESIZER:"yui-dt-resizer",CLASS_RESIZERLINER:"yui-dt-resizerliner",CLASS_RESIZERPROXY:"yui-dt-resizerproxy",CLASS_EDITOR:"yui-dt-editor",CLASS_EDITOR_SHIM:"yui-dt-editor-shim",CLASS_PAGINATOR:"yui-dt-paginator",CLASS_PAGE:"yui-dt-page",CLASS_DEFAULT:"yui-dt-default",CLASS_PREVIOUS:"yui-dt-previous",CLASS_NEXT:"yui-dt-next",CLASS_FIRST:"yui-dt-first",CLASS_LAST:"yui-dt-last",CLASS_REC:"yui-dt-rec",CLASS_EVEN:"yui-dt-even",CLASS_ODD:"yui-dt-odd",CLASS_SELECTED:"yui-dt-selected",CLASS_HIGHLIGHTED:"yui-dt-highlighted",CLASS_HIDDEN:"yui-dt-hidden",CLASS_DISABLED:"yui-dt-disabled",CLASS_EMPTY:"yui-dt-empty",CLASS_LOADING:"yui-dt-loading",CLASS_ERROR:"yui-dt-error",CLASS_EDITABLE:"yui-dt-editable",CLASS_DRAGGABLE:"yui-dt-draggable",CLASS_RESIZEABLE:"yui-dt-resizeable",CLASS_SCROLLABLE:"yui-dt-scrollable",CLASS_SORTABLE:"yui-dt-sortable",CLASS_ASC:"yui-dt-asc",CLASS_DESC:"yui-dt-desc",CLASS_BUTTON:"yui-dt-button",CLASS_CHECKBOX:"yui-dt-checkbox",CLASS_DROPDOWN:"yui-dt-dropdown",CLASS_RADIO:"yui-dt-radio",_nCount:0,_nCurrentCount:0,_elDynStyleNode:null,_bDynStylesFallback:(b.ie)?true:false,_oDynStyles:{},_cloneObject:function(m){if(!h.isValue(m)){return m;}var p={};if(m instanceof YAHOO.widget.BaseCellEditor){p=m;}else{if(Object.prototype.toString.apply(m)==="[object RegExp]"){p=m;}else{if(h.isFunction(m)){p=m;}else{if(h.isArray(m)){var n=[];for(var l=0,k=m.length;l<k;l++){n[l]=d._cloneObject(m[l]);}p=n;}else{if(h.isObject(m)){for(var j in m){if(h.hasOwnProperty(m,j)){if(h.isValue(m[j])&&h.isObject(m[j])||h.isArray(m[j])){p[j]=d._cloneObject(m[j]);}else{p[j]=m[j];}}}}else{p=m;}}}}}return p;},formatButton:function(i,j,k,n,m){var l=h.isValue(n)?n:"Click";i.innerHTML='<button type="button" class="'+d.CLASS_BUTTON+'">'+l+"</button>";},formatCheckbox:function(i,j,k,n,m){var l=n;l=(l)?' checked="checked"':"";i.innerHTML='<input type="checkbox"'+l+' class="'+d.CLASS_CHECKBOX+'" />';},formatCurrency:function(j,k,l,n,m){var i=m||this;j.innerHTML=a.Number.format(n,l.currencyOptions||i.get("currencyOptions"));},formatDate:function(j,l,m,o,n){var i=n||this,k=m.dateOptions||i.get("dateOptions");j.innerHTML=a.Date.format(o,k,k.locale);},formatDropdown:function(l,u,q,j,t){var s=t||this,r=(h.isValue(j))?j:u.getData(q.field),v=(h.isArray(q.dropdownOptions))?q.dropdownOptions:null,k,p=l.getElementsByTagName("select");if(p.length===0){k=document.createElement("select");k.className=d.CLASS_DROPDOWN;k=l.appendChild(k);g.addListener(k,"change",s._onDropdownChange,s);}k=p[0];if(k){k.innerHTML="";if(v){for(var n=0;n<v.length;n++){var o=v[n];var m=document.createElement("option");m.value=(h.isValue(o.value))?o.value:o;m.innerHTML=(h.isValue(o.text))?o.text:(h.isValue(o.label))?o.label:o;m=k.appendChild(m);if(m.value==r){m.selected=true;}}}else{k.innerHTML='<option selected value="'+r+'">'+r+"</option>";}}else{l.innerHTML=h.isValue(j)?j:"";}},formatEmail:function(i,j,k,m,l){if(h.isString(m)){m=h.escapeHTML(m);i.innerHTML='<a href="mailto:'+m+'">'+m+"</a>";}else{i.innerHTML=h.isValue(m)?h.escapeHTML(m.toString()):"";}},formatLink:function(i,j,k,m,l){if(h.isString(m)){m=h.escapeHTML(m);i.innerHTML='<a href="'+m+'">'+m+"</a>";}else{i.innerHTML=h.isValue(m)?h.escapeHTML(m.toString()):"";}},formatNumber:function(j,k,l,n,m){var i=m||this;j.innerHTML=a.Number.format(n,l.numberOptions||i.get("numberOptions"));},formatRadio:function(j,k,l,o,n){var i=n||this,m=o;m=(m)?' checked="checked"':"";j.innerHTML='<input type="radio"'+m+' name="'+i.getId()+"-col-"+l.getSanitizedKey()+'"'+' class="'+d.CLASS_RADIO+'" />';},formatText:function(i,j,l,n,m){var k=(h.isValue(n))?n:"";i.innerHTML=h.escapeHTML(k.toString());},formatTextarea:function(j,k,m,o,n){var l=(h.isValue(o))?h.escapeHTML(o.toString()):"",i="<textarea>"+l+"</textarea>";j.innerHTML=i;},formatTextbox:function(j,k,m,o,n){var l=(h.isValue(o))?h.escapeHTML(o.toString()):"",i='<input type="text" value="'+l+'" />';j.innerHTML=i;},formatDefault:function(i,j,k,m,l){i.innerHTML=(h.isValue(m)&&m!=="")?m.toString():" ";},validateNumber:function(j){var i=j*1;if(h.isNumber(i)){return i;}else{return undefined;}}});d.Formatter={button:d.formatButton,checkbox:d.formatCheckbox,currency:d.formatCurrency,"date":d.formatDate,dropdown:d.formatDropdown,email:d.formatEmail,link:d.formatLink,"number":d.formatNumber,radio:d.formatRadio,text:d.formatText,textarea:d.formatTextarea,textbox:d.formatTextbox,defaultFormatter:d.formatDefault};h.extend(d,a.Element,{initAttributes:function(i){i=i||{};d.superclass.initAttributes.call(this,i);this.setAttributeConfig("summary",{value:"",validator:h.isString,method:function(j){if(this._elTable){this._elTable.summary=j;}}});this.setAttributeConfig("selectionMode",{value:"standard",validator:h.isString});this.setAttributeConfig("sortedBy",{value:null,validator:function(j){if(j){return(h.isObject(j)&&j.key);
+}else{return(j===null);}},method:function(k){var r=this.get("sortedBy");this._configs.sortedBy.value=k;var j,o,m,q;if(this._elThead){if(r&&r.key&&r.dir){j=this._oColumnSet.getColumn(r.key);o=j.getKeyIndex();var u=j.getThEl();c.removeClass(u,r.dir);this.formatTheadCell(j.getThLinerEl().firstChild,j,k);}if(k){m=(k.column)?k.column:this._oColumnSet.getColumn(k.key);q=m.getKeyIndex();var v=m.getThEl();if(k.dir&&((k.dir=="asc")||(k.dir=="desc"))){var p=(k.dir=="desc")?d.CLASS_DESC:d.CLASS_ASC;c.addClass(v,p);}else{var l=k.dir||d.CLASS_ASC;c.addClass(v,l);}this.formatTheadCell(m.getThLinerEl().firstChild,m,k);}}if(this._elTbody){this._elTbody.style.display="none";var s=this._elTbody.rows,t;for(var n=s.length-1;n>-1;n--){t=s[n].childNodes;if(t[o]){c.removeClass(t[o],r.dir);}if(t[q]){c.addClass(t[q],k.dir);}}this._elTbody.style.display="";}this._clearTrTemplateEl();}});this.setAttributeConfig("paginator",{value:null,validator:function(j){return j===null||j instanceof e.Paginator;},method:function(){this._updatePaginator.apply(this,arguments);}});this.setAttributeConfig("caption",{value:null,validator:h.isString,method:function(j){this._initCaptionEl(j);}});this.setAttributeConfig("draggableColumns",{value:false,validator:h.isBoolean,method:function(j){if(this._elThead){if(j){this._initDraggableColumns();}else{this._destroyDraggableColumns();}}}});this.setAttributeConfig("renderLoopSize",{value:0,validator:h.isNumber});this.setAttributeConfig("sortFunction",{value:function(k,j,o,n){var m=YAHOO.util.Sort.compare,l=m(k.getData(n),j.getData(n),o);if(l===0){return m(k.getCount(),j.getCount(),o);}else{return l;}}});this.setAttributeConfig("formatRow",{value:null,validator:h.isFunction});this.setAttributeConfig("generateRequest",{value:function(k,n){k=k||{pagination:null,sortedBy:null};var m=encodeURIComponent((k.sortedBy)?k.sortedBy.key:n.getColumnSet().keys[0].getKey());var j=(k.sortedBy&&k.sortedBy.dir===YAHOO.widget.DataTable.CLASS_DESC)?"desc":"asc";var o=(k.pagination)?k.pagination.recordOffset:0;var l=(k.pagination)?k.pagination.rowsPerPage:null;return"sort="+m+"&dir="+j+"&startIndex="+o+((l!==null)?"&results="+l:"");},validator:h.isFunction});this.setAttributeConfig("initialRequest",{value:null});this.setAttributeConfig("initialLoad",{value:true});this.setAttributeConfig("dynamicData",{value:false,validator:h.isBoolean});this.setAttributeConfig("MSG_EMPTY",{value:"No records found.",validator:h.isString});this.setAttributeConfig("MSG_LOADING",{value:"Loading...",validator:h.isString});this.setAttributeConfig("MSG_ERROR",{value:"Data error.",validator:h.isString});this.setAttributeConfig("MSG_SORTASC",{value:"Click to sort ascending",validator:h.isString,method:function(k){if(this._elThead){for(var l=0,m=this.getColumnSet().keys,j=m.length;l<j;l++){if(m[l].sortable&&this.getColumnSortDir(m[l])===d.CLASS_ASC){m[l]._elThLabel.firstChild.title=k;}}}}});this.setAttributeConfig("MSG_SORTDESC",{value:"Click to sort descending",validator:h.isString,method:function(k){if(this._elThead){for(var l=0,m=this.getColumnSet().keys,j=m.length;l<j;l++){if(m[l].sortable&&this.getColumnSortDir(m[l])===d.CLASS_DESC){m[l]._elThLabel.firstChild.title=k;}}}}});this.setAttributeConfig("currencySymbol",{value:"$",validator:h.isString});this.setAttributeConfig("currencyOptions",{value:{prefix:this.get("currencySymbol"),decimalPlaces:2,decimalSeparator:".",thousandsSeparator:","}});this.setAttributeConfig("dateOptions",{value:{format:"%m/%d/%Y",locale:"en"}});this.setAttributeConfig("numberOptions",{value:{decimalPlaces:0,thousandsSeparator:","}});},_bInit:true,_nIndex:null,_nTrCount:0,_nTdCount:0,_sId:null,_oChainRender:null,_elContainer:null,_elMask:null,_elTable:null,_elCaption:null,_elColgroup:null,_elThead:null,_elTbody:null,_elMsgTbody:null,_elMsgTr:null,_elMsgTd:null,_elColumnDragTarget:null,_elColumnResizerProxy:null,_oDataSource:null,_oColumnSet:null,_oRecordSet:null,_oCellEditor:null,_sFirstTrId:null,_sLastTrId:null,_elTrTemplate:null,_aDynFunctions:[],_disabled:false,clearTextSelection:function(){var i;if(window.getSelection){i=window.getSelection();}else{if(document.getSelection){i=document.getSelection();}else{if(document.selection){i=document.selection;}}}if(i){if(i.empty){i.empty();}else{if(i.removeAllRanges){i.removeAllRanges();}else{if(i.collapse){i.collapse();}}}}},_focusEl:function(i){i=i||this._elTbody;setTimeout(function(){try{i.focus();}catch(j){}},0);},_repaintGecko:(b.gecko)?function(j){j=j||this._elContainer;var i=j.parentNode;var k=j.nextSibling;i.insertBefore(i.removeChild(j),k);}:function(){},_repaintOpera:(b.opera)?function(){if(b.opera){document.documentElement.className+=" ";document.documentElement.className=YAHOO.lang.trim(document.documentElement.className);}}:function(){},_repaintWebkit:(b.webkit)?function(j){j=j||this._elContainer;var i=j.parentNode;var k=j.nextSibling;i.insertBefore(i.removeChild(j),k);}:function(){},_initConfigs:function(i){if(!i||!h.isObject(i)){i={};}this.configs=i;},_initColumnSet:function(n){var m,k,j;if(this._oColumnSet){for(k=0,j=this._oColumnSet.keys.length;k<j;k++){m=this._oColumnSet.keys[k];d._oDynStyles["."+this.getId()+"-col-"+m.getSanitizedKey()+" ."+d.CLASS_LINER]=undefined;if(m.editor&&m.editor.unsubscribeAll){m.editor.unsubscribeAll();}}this._oColumnSet=null;this._clearTrTemplateEl();}if(h.isArray(n)){this._oColumnSet=new YAHOO.widget.ColumnSet(n);}else{if(n instanceof YAHOO.widget.ColumnSet){this._oColumnSet=n;}}var l=this._oColumnSet.keys;for(k=0,j=l.length;k<j;k++){m=l[k];if(m.editor&&m.editor.subscribe){m.editor.subscribe("showEvent",this._onEditorShowEvent,this,true);m.editor.subscribe("keydownEvent",this._onEditorKeydownEvent,this,true);m.editor.subscribe("revertEvent",this._onEditorRevertEvent,this,true);m.editor.subscribe("saveEvent",this._onEditorSaveEvent,this,true);m.editor.subscribe("cancelEvent",this._onEditorCancelEvent,this,true);m.editor.subscribe("blurEvent",this._onEditorBlurEvent,this,true);m.editor.subscribe("blockEvent",this._onEditorBlockEvent,this,true);
+m.editor.subscribe("unblockEvent",this._onEditorUnblockEvent,this,true);}}},_initDataSource:function(j){this._oDataSource=null;if(j&&(h.isFunction(j.sendRequest))){this._oDataSource=j;}else{var k=null;var o=this._elContainer;var l=0;if(o.hasChildNodes()){var n=o.childNodes;for(l=0;l<n.length;l++){if(n[l].nodeName&&n[l].nodeName.toLowerCase()=="table"){k=n[l];break;}}if(k){var m=[];for(;l<this._oColumnSet.keys.length;l++){m.push({key:this._oColumnSet.keys[l].key});}this._oDataSource=new f(k);this._oDataSource.responseType=f.TYPE_HTMLTABLE;this._oDataSource.responseSchema={fields:m};}}}},_initRecordSet:function(){if(this._oRecordSet){this._oRecordSet.reset();}else{this._oRecordSet=new YAHOO.widget.RecordSet();}},_initDomElements:function(i){this._initContainerEl(i);this._initTableEl(this._elContainer);this._initColgroupEl(this._elTable);this._initTheadEl(this._elTable);this._initMsgTbodyEl(this._elTable);this._initTbodyEl(this._elTable);if(!this._elContainer||!this._elTable||!this._elColgroup||!this._elThead||!this._elTbody||!this._elMsgTbody){return false;}else{return true;}},_destroyContainerEl:function(m){var k=this._oColumnSet.keys,l,j;c.removeClass(m,d.CLASS_DATATABLE);g.purgeElement(m);g.purgeElement(this._elThead,true);g.purgeElement(this._elTbody);g.purgeElement(this._elMsgTbody);l=m.getElementsByTagName("select");if(l.length){g.detachListener(l,"change");}for(j=k.length-1;j>=0;--j){if(k[j].editor){g.purgeElement(k[j].editor._elContainer);}}m.innerHTML="";this._elContainer=null;this._elColgroup=null;this._elThead=null;this._elTbody=null;},_initContainerEl:function(j){j=c.get(j);if(j&&j.nodeName&&(j.nodeName.toLowerCase()=="div")){this._destroyContainerEl(j);c.addClass(j,d.CLASS_DATATABLE);g.addListener(j,"focus",this._onTableFocus,this);g.addListener(j,"dblclick",this._onTableDblclick,this);this._elContainer=j;var i=document.createElement("div");i.className=d.CLASS_MASK;i.style.display="none";this._elMask=j.appendChild(i);}},_destroyTableEl:function(){var i=this._elTable;if(i){g.purgeElement(i,true);i.parentNode.removeChild(i);this._elCaption=null;this._elColgroup=null;this._elThead=null;this._elTbody=null;}},_initCaptionEl:function(i){if(this._elTable&&i){if(!this._elCaption){this._elCaption=this._elTable.createCaption();}this._elCaption.innerHTML=i;}else{if(this._elCaption){this._elCaption.parentNode.removeChild(this._elCaption);}}},_initTableEl:function(i){if(i){this._destroyTableEl();this._elTable=i.appendChild(document.createElement("table"));this._elTable.summary=this.get("summary");if(this.get("caption")){this._initCaptionEl(this.get("caption"));}g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"thead ."+d.CLASS_LABEL,this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"thead ."+d.CLASS_LABEL,this);g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"tbody.yui-dt-data>tr>td",this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"tbody.yui-dt-data>tr>td",this);g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"tbody.yui-dt-message>tr>td",this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"tbody.yui-dt-message>tr>td",this);}},_destroyColgroupEl:function(){var i=this._elColgroup;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elColgroup=null;}},_initColgroupEl:function(s){if(s){this._destroyColgroupEl();var l=this._aColIds||[],r=this._oColumnSet.keys,m=0,p=l.length,j,o,q=document.createDocumentFragment(),n=document.createElement("col");for(m=0,p=r.length;m<p;m++){o=r[m];j=q.appendChild(n.cloneNode(false));}var k=s.insertBefore(document.createElement("colgroup"),s.firstChild);k.appendChild(q);this._elColgroup=k;}},_insertColgroupColEl:function(i){if(h.isNumber(i)&&this._elColgroup){var j=this._elColgroup.childNodes[i]||null;this._elColgroup.insertBefore(document.createElement("col"),j);}},_removeColgroupColEl:function(i){if(h.isNumber(i)&&this._elColgroup&&this._elColgroup.childNodes[i]){this._elColgroup.removeChild(this._elColgroup.childNodes[i]);}},_reorderColgroupColEl:function(l,k){if(h.isArray(l)&&h.isNumber(k)&&this._elColgroup&&(this._elColgroup.childNodes.length>l[l.length-1])){var j,n=[];for(j=l.length-1;j>-1;j--){n.push(this._elColgroup.removeChild(this._elColgroup.childNodes[l[j]]));}var m=this._elColgroup.childNodes[k]||null;for(j=n.length-1;j>-1;j--){this._elColgroup.insertBefore(n[j],m);}}},_destroyTheadEl:function(){var j=this._elThead;if(j){var i=j.parentNode;g.purgeElement(j,true);this._destroyColumnHelpers();i.removeChild(j);this._elThead=null;}},_initTheadEl:function(v){v=v||this._elTable;if(v){this._destroyTheadEl();var q=(this._elColgroup)?v.insertBefore(document.createElement("thead"),this._elColgroup.nextSibling):v.appendChild(document.createElement("thead"));g.addListener(q,"focus",this._onTheadFocus,this);g.addListener(q,"keydown",this._onTheadKeydown,this);g.addListener(q,"mousedown",this._onTableMousedown,this);g.addListener(q,"mouseup",this._onTableMouseup,this);g.addListener(q,"click",this._onTheadClick,this);var x=this._oColumnSet,t,r,p,n;var w=x.tree;var o;for(r=0;r<w.length;r++){var m=q.appendChild(document.createElement("tr"));for(p=0;p<w[r].length;p++){t=w[r][p];o=m.appendChild(document.createElement("th"));this._initThEl(o,t);}if(r===0){c.addClass(m,d.CLASS_FIRST);}if(r===(w.length-1)){c.addClass(m,d.CLASS_LAST);}}var k=x.headers[0]||[];for(r=0;r<k.length;r++){c.addClass(c.get(this.getId()+"-th-"+k[r]),d.CLASS_FIRST);}var s=x.headers[x.headers.length-1]||[];for(r=0;r<s.length;r++){c.addClass(c.get(this.getId()+"-th-"+s[r]),d.CLASS_LAST);}if(b.webkit&&b.webkit<420){var u=this;setTimeout(function(){q.style.display="";},0);q.style.display="none";}this._elThead=q;this._initColumnHelpers();}},_initThEl:function(m,l){m.id=this.getId()+"-th-"+l.getSanitizedKey();m.innerHTML="";m.rowSpan=l.getRowspan();m.colSpan=l.getColspan();l._elTh=m;var i=m.appendChild(document.createElement("div"));i.id=m.id+"-liner";i.className=d.CLASS_LINER;l._elThLiner=i;var j=i.appendChild(document.createElement("span"));
+j.className=d.CLASS_LABEL;if(l.abbr){m.abbr=l.abbr;}if(l.hidden){this._clearMinWidth(l);}m.className=this._getColumnClassNames(l);if(l.width){var k=(l.minWidth&&(l.width<l.minWidth))?l.minWidth:l.width;if(d._bDynStylesFallback){m.firstChild.style.overflow="hidden";m.firstChild.style.width=k+"px";}else{this._setColumnWidthDynStyles(l,k+"px","hidden");}}this.formatTheadCell(j,l,this.get("sortedBy"));l._elThLabel=j;},formatTheadCell:function(i,m,k){var q=m.getKey();var p=h.isValue(m.label)?m.label:q;if(m.sortable){var n=this.getColumnSortDir(m,k);var j=(n===d.CLASS_DESC);if(k&&(m.key===k.key)){j=!(k.dir===d.CLASS_DESC);}var l=this.getId()+"-href-"+m.getSanitizedKey();var o=(j)?this.get("MSG_SORTDESC"):this.get("MSG_SORTASC");i.innerHTML='<a href="'+l+'" title="'+o+'" class="'+d.CLASS_SORTABLE+'">'+p+"</a>";}else{i.innerHTML=p;}},_destroyDraggableColumns:function(){var l,m;for(var k=0,j=this._oColumnSet.tree[0].length;k<j;k++){l=this._oColumnSet.tree[0][k];if(l._dd){l._dd=l._dd.unreg();c.removeClass(l.getThEl(),d.CLASS_DRAGGABLE);}}this._destroyColumnDragTargetEl();},_initDraggableColumns:function(){this._destroyDraggableColumns();if(a.DD){var m,n,k;for(var l=0,j=this._oColumnSet.tree[0].length;l<j;l++){m=this._oColumnSet.tree[0][l];n=m.getThEl();c.addClass(n,d.CLASS_DRAGGABLE);k=this._initColumnDragTargetEl();m._dd=new YAHOO.widget.ColumnDD(this,m,n,k);}}else{}},_destroyColumnDragTargetEl:function(){if(this._elColumnDragTarget){var i=this._elColumnDragTarget;YAHOO.util.Event.purgeElement(i);i.parentNode.removeChild(i);this._elColumnDragTarget=null;}},_initColumnDragTargetEl:function(){if(!this._elColumnDragTarget){var i=document.createElement("div");i.id=this.getId()+"-coltarget";i.className=d.CLASS_COLTARGET;i.style.display="none";document.body.insertBefore(i,document.body.firstChild);this._elColumnDragTarget=i;}return this._elColumnDragTarget;},_destroyResizeableColumns:function(){var k=this._oColumnSet.keys;for(var l=0,j=k.length;l<j;l++){if(k[l]._ddResizer){k[l]._ddResizer=k[l]._ddResizer.unreg();c.removeClass(k[l].getThEl(),d.CLASS_RESIZEABLE);}}this._destroyColumnResizerProxyEl();},_initResizeableColumns:function(){this._destroyResizeableColumns();if(a.DD){var p,k,n,q,j,r,m;for(var l=0,o=this._oColumnSet.keys.length;l<o;l++){p=this._oColumnSet.keys[l];if(p.resizeable){k=p.getThEl();c.addClass(k,d.CLASS_RESIZEABLE);n=p.getThLinerEl();q=k.appendChild(document.createElement("div"));q.className=d.CLASS_RESIZERLINER;q.appendChild(n);j=q.appendChild(document.createElement("div"));j.id=k.id+"-resizer";j.className=d.CLASS_RESIZER;p._elResizer=j;r=this._initColumnResizerProxyEl();p._ddResizer=new YAHOO.util.ColumnResizer(this,p,k,j,r);m=function(i){g.stopPropagation(i);};g.addListener(j,"click",m);}}}else{}},_destroyColumnResizerProxyEl:function(){if(this._elColumnResizerProxy){var i=this._elColumnResizerProxy;YAHOO.util.Event.purgeElement(i);i.parentNode.removeChild(i);this._elColumnResizerProxy=null;}},_initColumnResizerProxyEl:function(){if(!this._elColumnResizerProxy){var i=document.createElement("div");i.id=this.getId()+"-colresizerproxy";i.className=d.CLASS_RESIZERPROXY;document.body.insertBefore(i,document.body.firstChild);this._elColumnResizerProxy=i;}return this._elColumnResizerProxy;},_destroyColumnHelpers:function(){this._destroyDraggableColumns();this._destroyResizeableColumns();},_initColumnHelpers:function(){if(this.get("draggableColumns")){this._initDraggableColumns();}this._initResizeableColumns();},_destroyTbodyEl:function(){var i=this._elTbody;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elTbody=null;}},_initTbodyEl:function(j){if(j){this._destroyTbodyEl();var i=j.appendChild(document.createElement("tbody"));i.tabIndex=0;i.className=d.CLASS_DATA;g.addListener(i,"focus",this._onTbodyFocus,this);g.addListener(i,"mousedown",this._onTableMousedown,this);g.addListener(i,"mouseup",this._onTableMouseup,this);g.addListener(i,"keydown",this._onTbodyKeydown,this);g.addListener(i,"click",this._onTbodyClick,this);if(b.ie){i.hideFocus=true;}this._elTbody=i;}},_destroyMsgTbodyEl:function(){var i=this._elMsgTbody;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elTbody=null;}},_initMsgTbodyEl:function(l){if(l){var k=document.createElement("tbody");k.className=d.CLASS_MESSAGE;var j=k.appendChild(document.createElement("tr"));j.className=d.CLASS_FIRST+" "+d.CLASS_LAST;this._elMsgTr=j;var m=j.appendChild(document.createElement("td"));m.colSpan=this._oColumnSet.keys.length||1;m.className=d.CLASS_FIRST+" "+d.CLASS_LAST;this._elMsgTd=m;k=l.insertBefore(k,this._elTbody);var i=m.appendChild(document.createElement("div"));i.className=d.CLASS_LINER;this._elMsgTbody=k;g.addListener(k,"focus",this._onTbodyFocus,this);g.addListener(k,"mousedown",this._onTableMousedown,this);g.addListener(k,"mouseup",this._onTableMouseup,this);g.addListener(k,"keydown",this._onTbodyKeydown,this);g.addListener(k,"click",this._onTbodyClick,this);}},_initEvents:function(){this._initColumnSort();YAHOO.util.Event.addListener(document,"click",this._onDocumentClick,this);this.subscribe("paginatorChange",function(){this._handlePaginatorChange.apply(this,arguments);});this.subscribe("initEvent",function(){this.renderPaginator();});this._initCellEditing();},_initColumnSort:function(){this.subscribe("theadCellClickEvent",this.onEventSortColumn);var i=this.get("sortedBy");if(i){if(i.dir=="desc"){this._configs.sortedBy.value.dir=d.CLASS_DESC;}else{if(i.dir=="asc"){this._configs.sortedBy.value.dir=d.CLASS_ASC;}}}},_initCellEditing:function(){this.subscribe("editorBlurEvent",function(){this.onEditorBlurEvent.apply(this,arguments);});this.subscribe("editorBlockEvent",function(){this.onEditorBlockEvent.apply(this,arguments);});this.subscribe("editorUnblockEvent",function(){this.onEditorUnblockEvent.apply(this,arguments);});},_getColumnClassNames:function(l,k){var i;if(h.isString(l.className)){i=[l.className];}else{if(h.isArray(l.className)){i=l.className;}else{i=[];}}i[i.length]=this.getId()+"-col-"+l.getSanitizedKey();
+i[i.length]="yui-dt-col-"+l.getSanitizedKey();var j=this.get("sortedBy")||{};if(l.key===j.key){i[i.length]=j.dir||"";}if(l.hidden){i[i.length]=d.CLASS_HIDDEN;}if(l.selected){i[i.length]=d.CLASS_SELECTED;}if(l.sortable){i[i.length]=d.CLASS_SORTABLE;}if(l.resizeable){i[i.length]=d.CLASS_RESIZEABLE;}if(l.editor){i[i.length]=d.CLASS_EDITABLE;}if(k){i=i.concat(k);}return i.join(" ");},_clearTrTemplateEl:function(){this._elTrTemplate=null;},_getTrTemplateEl:function(u,o){if(this._elTrTemplate){return this._elTrTemplate;}else{var q=document,s=q.createElement("tr"),l=q.createElement("td"),k=q.createElement("div");l.appendChild(k);var t=document.createDocumentFragment(),r=this._oColumnSet.keys,n;var p;for(var m=0,j=r.length;m<j;m++){n=l.cloneNode(true);n=this._formatTdEl(r[m],n,m,(m===j-1));t.appendChild(n);}s.appendChild(t);s.className=d.CLASS_REC;this._elTrTemplate=s;return s;}},_formatTdEl:function(n,p,q,m){var t=this._oColumnSet;var i=t.headers,k=i[q],o="",v;for(var l=0,u=k.length;l<u;l++){v=this._sId+"-th-"+k[l]+" ";o+=v;}p.headers=o;var s=[];if(q===0){s[s.length]=d.CLASS_FIRST;}if(m){s[s.length]=d.CLASS_LAST;}p.className=this._getColumnClassNames(n,s);p.firstChild.className=d.CLASS_LINER;if(n.width&&d._bDynStylesFallback){var r=(n.minWidth&&(n.width<n.minWidth))?n.minWidth:n.width;p.firstChild.style.overflow="hidden";p.firstChild.style.width=r+"px";}return p;},_addTrEl:function(k){var j=this._getTrTemplateEl();var i=j.cloneNode(true);return this._updateTrEl(i,k);},_updateTrEl:function(q,r){var p=this.get("formatRow")?this.get("formatRow").call(this,q,r):true;if(p){q.style.display="none";var o=q.childNodes,m;for(var l=0,n=o.length;l<n;++l){m=o[l];this.formatCell(o[l].firstChild,r,this._oColumnSet.keys[l]);}q.style.display="";}var j=q.id,k=r.getId();if(this._sFirstTrId===j){this._sFirstTrId=k;}if(this._sLastTrId===j){this._sLastTrId=k;}q.id=k;return q;},_deleteTrEl:function(i){var j;if(!h.isNumber(i)){j=c.get(i).sectionRowIndex;}else{j=i;}if(h.isNumber(j)&&(j>-2)&&(j<this._elTbody.rows.length)){return this._elTbody.removeChild(this._elTbody.rows[i]);}else{return null;}},_unsetFirstRow:function(){if(this._sFirstTrId){c.removeClass(this._sFirstTrId,d.CLASS_FIRST);this._sFirstTrId=null;}},_setFirstRow:function(){this._unsetFirstRow();var i=this.getFirstTrEl();if(i){c.addClass(i,d.CLASS_FIRST);this._sFirstTrId=i.id;}},_unsetLastRow:function(){if(this._sLastTrId){c.removeClass(this._sLastTrId,d.CLASS_LAST);this._sLastTrId=null;}},_setLastRow:function(){this._unsetLastRow();var i=this.getLastTrEl();if(i){c.addClass(i,d.CLASS_LAST);this._sLastTrId=i.id;}},_setRowStripes:function(t,l){var m=this._elTbody.rows,q=0,s=m.length,p=[],r=0,n=[],j=0;if((t!==null)&&(t!==undefined)){var o=this.getTrEl(t);if(o){q=o.sectionRowIndex;if(h.isNumber(l)&&(l>1)){s=q+l;}}}for(var k=q;k<s;k++){if(k%2){p[r++]=m[k];}else{n[j++]=m[k];}}if(p.length){c.replaceClass(p,d.CLASS_EVEN,d.CLASS_ODD);}if(n.length){c.replaceClass(n,d.CLASS_ODD,d.CLASS_EVEN);}},_setSelections:function(){var l=this.getSelectedRows();var n=this.getSelectedCells();if((l.length>0)||(n.length>0)){var m=this._oColumnSet,k;for(var j=0;j<l.length;j++){k=c.get(l[j]);if(k){c.addClass(k,d.CLASS_SELECTED);}}for(j=0;j<n.length;j++){k=c.get(n[j].recordId);if(k){c.addClass(k.childNodes[m.getColumn(n[j].columnKey).getKeyIndex()],d.CLASS_SELECTED);}}}},_onRenderChainEnd:function(){this.hideTableMessage();if(this._elTbody.rows.length===0){this.showTableMessage(this.get("MSG_EMPTY"),d.CLASS_EMPTY);}var i=this;setTimeout(function(){if((i instanceof d)&&i._sId){if(i._bInit){i._bInit=false;i.fireEvent("initEvent");}i.fireEvent("renderEvent");i.fireEvent("refreshEvent");i.validateColumnWidths();i.fireEvent("postRenderEvent");}},0);},_onDocumentClick:function(l,j){var m=g.getTarget(l);var i=m.nodeName.toLowerCase();if(!c.isAncestor(j._elContainer,m)){j.fireEvent("tableBlurEvent");if(j._oCellEditor){if(j._oCellEditor.getContainerEl){var k=j._oCellEditor.getContainerEl();if(!c.isAncestor(k,m)&&(k.id!==m.id)){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor});}}else{if(j._oCellEditor.isActive){if(!c.isAncestor(j._oCellEditor.container,m)&&(j._oCellEditor.container.id!==m.id)){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor});}}}}}},_onTableFocus:function(j,i){i.fireEvent("tableFocusEvent");},_onTheadFocus:function(j,i){i.fireEvent("theadFocusEvent");i.fireEvent("tableFocusEvent");},_onTbodyFocus:function(j,i){i.fireEvent("tbodyFocusEvent");i.fireEvent("tableFocusEvent");},_onTableMouseover:function(n,m,i,k){var o=m;var j=o.nodeName&&o.nodeName.toLowerCase();var l=true;while(o&&(j!="table")){switch(j){case"body":return;case"a":break;case"td":l=k.fireEvent("cellMouseoverEvent",{target:o,event:n});break;case"span":if(c.hasClass(o,d.CLASS_LABEL)){l=k.fireEvent("theadLabelMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerLabelMouseoverEvent",{target:o,event:n});}break;case"th":l=k.fireEvent("theadCellMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerCellMouseoverEvent",{target:o,event:n});break;case"tr":if(o.parentNode.nodeName.toLowerCase()=="thead"){l=k.fireEvent("theadRowMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerRowMouseoverEvent",{target:o,event:n});}else{l=k.fireEvent("rowMouseoverEvent",{target:o,event:n});}break;default:break;}if(l===false){return;}else{o=o.parentNode;if(o){j=o.nodeName.toLowerCase();}}}k.fireEvent("tableMouseoverEvent",{target:(o||k._elContainer),event:n});},_onTableMouseout:function(n,m,i,k){var o=m;var j=o.nodeName&&o.nodeName.toLowerCase();var l=true;while(o&&(j!="table")){switch(j){case"body":return;case"a":break;case"td":l=k.fireEvent("cellMouseoutEvent",{target:o,event:n});break;case"span":if(c.hasClass(o,d.CLASS_LABEL)){l=k.fireEvent("theadLabelMouseoutEvent",{target:o,event:n});l=k.fireEvent("headerLabelMouseoutEvent",{target:o,event:n});}break;case"th":l=k.fireEvent("theadCellMouseoutEvent",{target:o,event:n});l=k.fireEvent("headerCellMouseoutEvent",{target:o,event:n});break;case"tr":if(o.parentNode.nodeName.toLowerCase()=="thead"){l=k.fireEvent("theadRowMouseoutEvent",{target:o,event:n});
+l=k.fireEvent("headerRowMouseoutEvent",{target:o,event:n});}else{l=k.fireEvent("rowMouseoutEvent",{target:o,event:n});}break;default:break;}if(l===false){return;}else{o=o.parentNode;if(o){j=o.nodeName.toLowerCase();}}}k.fireEvent("tableMouseoutEvent",{target:(o||k._elContainer),event:n});},_onTableMousedown:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"a":break;case"td":k=j.fireEvent("cellMousedownEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelMousedownEvent",{target:m,event:l});k=j.fireEvent("headerLabelMousedownEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellMousedownEvent",{target:m,event:l});k=j.fireEvent("headerCellMousedownEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowMousedownEvent",{target:m,event:l});k=j.fireEvent("headerRowMousedownEvent",{target:m,event:l});}else{k=j.fireEvent("rowMousedownEvent",{target:m,event:l});}break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableMousedownEvent",{target:(m||j._elContainer),event:l});},_onTableMouseup:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"a":break;case"td":k=j.fireEvent("cellMouseupEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelMouseupEvent",{target:m,event:l});k=j.fireEvent("headerLabelMouseupEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellMouseupEvent",{target:m,event:l});k=j.fireEvent("headerCellMouseupEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowMouseupEvent",{target:m,event:l});k=j.fireEvent("headerRowMouseupEvent",{target:m,event:l});}else{k=j.fireEvent("rowMouseupEvent",{target:m,event:l});}break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableMouseupEvent",{target:(m||j._elContainer),event:l});},_onTableDblclick:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"td":k=j.fireEvent("cellDblclickEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelDblclickEvent",{target:m,event:l});k=j.fireEvent("headerLabelDblclickEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellDblclickEvent",{target:m,event:l});k=j.fireEvent("headerCellDblclickEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowDblclickEvent",{target:m,event:l});k=j.fireEvent("headerRowDblclickEvent",{target:m,event:l});}else{k=j.fireEvent("rowDblclickEvent",{target:m,event:l});}break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableDblclickEvent",{target:(m||j._elContainer),event:l});},_onTheadKeydown:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":case"textarea":break;case"thead":k=j.fireEvent("theadKeyEvent",{target:m,event:l});break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableKeyEvent",{target:(m||j._elContainer),event:l});},_onTbodyKeydown:function(m,k){var j=k.get("selectionMode");if(j=="standard"){k._handleStandardSelectionByKey(m);}else{if(j=="single"){k._handleSingleSelectionByKey(m);}else{if(j=="cellblock"){k._handleCellBlockSelectionByKey(m);}else{if(j=="cellrange"){k._handleCellRangeSelectionByKey(m);}else{if(j=="singlecell"){k._handleSingleCellSelectionByKey(m);}}}}}if(k._oCellEditor){if(k._oCellEditor.fireEvent){k._oCellEditor.fireEvent("blurEvent",{editor:k._oCellEditor});}else{if(k._oCellEditor.isActive){k.fireEvent("editorBlurEvent",{editor:k._oCellEditor});}}}var n=g.getTarget(m);var i=n.nodeName&&n.nodeName.toLowerCase();var l=true;while(n&&(i!="table")){switch(i){case"body":return;case"tbody":l=k.fireEvent("tbodyKeyEvent",{target:n,event:m});break;default:break;}if(l===false){return;}else{n=n.parentNode;if(n){i=n.nodeName.toLowerCase();}}}k.fireEvent("tableKeyEvent",{target:(n||k._elContainer),event:m});},_onTheadClick:function(l,j){if(j._oCellEditor){if(j._oCellEditor.fireEvent){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor});}else{if(j._oCellEditor.isActive){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor});}}}var m=g.getTarget(l),i=m.nodeName&&m.nodeName.toLowerCase(),k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":var n=m.type.toLowerCase();if(n=="checkbox"){k=j.fireEvent("theadCheckboxClickEvent",{target:m,event:l});}else{if(n=="radio"){k=j.fireEvent("theadRadioClickEvent",{target:m,event:l});}else{if((n=="button")||(n=="image")||(n=="submit")||(n=="reset")){if(!m.disabled){k=j.fireEvent("theadButtonClickEvent",{target:m,event:l});}else{k=false;}}else{if(m.disabled){k=false;}}}}break;case"a":k=j.fireEvent("theadLinkClickEvent",{target:m,event:l});break;case"button":if(!m.disabled){k=j.fireEvent("theadButtonClickEvent",{target:m,event:l});}else{k=false;}break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelClickEvent",{target:m,event:l});k=j.fireEvent("headerLabelClickEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellClickEvent",{target:m,event:l});k=j.fireEvent("headerCellClickEvent",{target:m,event:l});break;case"tr":k=j.fireEvent("theadRowClickEvent",{target:m,event:l});k=j.fireEvent("headerRowClickEvent",{target:m,event:l});break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableClickEvent",{target:(m||j._elContainer),event:l});},_onTbodyClick:function(l,j){if(j._oCellEditor){if(j._oCellEditor.fireEvent){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor});
+}else{if(j._oCellEditor.isActive){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor});}}}var m=g.getTarget(l),i=m.nodeName&&m.nodeName.toLowerCase(),k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":var n=m.type.toLowerCase();if(n=="checkbox"){k=j.fireEvent("checkboxClickEvent",{target:m,event:l});}else{if(n=="radio"){k=j.fireEvent("radioClickEvent",{target:m,event:l});}else{if((n=="button")||(n=="image")||(n=="submit")||(n=="reset")){if(!m.disabled){k=j.fireEvent("buttonClickEvent",{target:m,event:l});}else{k=false;}}else{if(m.disabled){k=false;}}}}break;case"a":k=j.fireEvent("linkClickEvent",{target:m,event:l});break;case"button":if(!m.disabled){k=j.fireEvent("buttonClickEvent",{target:m,event:l});}else{k=false;}break;case"td":k=j.fireEvent("cellClickEvent",{target:m,event:l});break;case"tr":k=j.fireEvent("rowClickEvent",{target:m,event:l});break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableClickEvent",{target:(m||j._elContainer),event:l});},_onDropdownChange:function(j,i){var k=g.getTarget(j);i.fireEvent("dropdownChangeEvent",{event:j,target:k});},configs:null,getId:function(){return this._sId;},toString:function(){return"DataTable instance "+this._sId;},getDataSource:function(){return this._oDataSource;},getColumnSet:function(){return this._oColumnSet;},getRecordSet:function(){return this._oRecordSet;},getState:function(){return{totalRecords:this.get("paginator")?this.get("paginator").get("totalRecords"):this._oRecordSet.getLength(),pagination:this.get("paginator")?this.get("paginator").getState():null,sortedBy:this.get("sortedBy"),selectedRows:this.getSelectedRows(),selectedCells:this.getSelectedCells()};},getContainerEl:function(){return this._elContainer;},getTableEl:function(){return this._elTable;},getTheadEl:function(){return this._elThead;},getTbodyEl:function(){return this._elTbody;},getMsgTbodyEl:function(){return this._elMsgTbody;},getMsgTdEl:function(){return this._elMsgTd;},getTrEl:function(k){if(k instanceof YAHOO.widget.Record){return document.getElementById(k.getId());}else{if(h.isNumber(k)){var j=c.getElementsByClassName(d.CLASS_REC,"tr",this._elTbody);return j&&j[k]?j[k]:null;}else{if(k){var i=(h.isString(k))?document.getElementById(k):k;if(i&&i.ownerDocument==document){if(i.nodeName.toLowerCase()!="tr"){i=c.getAncestorByTagName(i,"tr");}return i;}}}}return null;},getFirstTrEl:function(){var k=this._elTbody.rows,j=0;while(k[j]){if(this.getRecord(k[j])){return k[j];}j++;}return null;},getLastTrEl:function(){var k=this._elTbody.rows,j=k.length-1;while(j>-1){if(this.getRecord(k[j])){return k[j];}j--;}return null;},getNextTrEl:function(l,i){var j=this.getTrIndex(l);if(j!==null){var k=this._elTbody.rows;if(i){while(j<k.length-1){l=k[j+1];if(this.getRecord(l)){return l;}j++;}}else{if(j<k.length-1){return k[j+1];}}}return null;},getPreviousTrEl:function(l,i){var j=this.getTrIndex(l);if(j!==null){var k=this._elTbody.rows;if(i){while(j>0){l=k[j-1];if(this.getRecord(l)){return l;}j--;}}else{if(j>0){return k[j-1];}}}return null;},getCellIndex:function(k){k=this.getTdEl(k);if(k){if(b.ie>0){var l=0,n=k.parentNode,m=n.childNodes,j=m.length;for(;l<j;l++){if(m[l]==k){return l;}}}else{return k.cellIndex;}}},getTdLinerEl:function(i){var j=this.getTdEl(i);return j.firstChild||null;},getTdEl:function(i){var n;var l=c.get(i);if(l&&(l.ownerDocument==document)){if(l.nodeName.toLowerCase()!="td"){n=c.getAncestorByTagName(l,"td");}else{n=l;}if(n&&((n.parentNode.parentNode==this._elTbody)||(n.parentNode.parentNode===null)||(n.parentNode.parentNode.nodeType===11))){return n;}}else{if(i){var m,k;if(h.isString(i.columnKey)&&h.isString(i.recordId)){m=this.getRecord(i.recordId);var o=this.getColumn(i.columnKey);if(o){k=o.getKeyIndex();}}if(i.record&&i.column&&i.column.getKeyIndex){m=i.record;k=i.column.getKeyIndex();}var j=this.getTrEl(m);if((k!==null)&&j&&j.cells&&j.cells.length>0){return j.cells[k]||null;}}}return null;},getFirstTdEl:function(j){var i=h.isValue(j)?this.getTrEl(j):this.getFirstTrEl();if(i){if(i.cells&&i.cells.length>0){return i.cells[0];}else{if(i.childNodes&&i.childNodes.length>0){return i.childNodes[0];}}}return null;},getLastTdEl:function(j){var i=h.isValue(j)?this.getTrEl(j):this.getLastTrEl();if(i){if(i.cells&&i.cells.length>0){return i.cells[i.cells.length-1];}else{if(i.childNodes&&i.childNodes.length>0){return i.childNodes[i.childNodes.length-1];}}}return null;},getNextTdEl:function(i){var m=this.getTdEl(i);if(m){var k=this.getCellIndex(m);var j=this.getTrEl(m);if(j.cells&&(j.cells.length)>0&&(k<j.cells.length-1)){return j.cells[k+1];}else{if(j.childNodes&&(j.childNodes.length)>0&&(k<j.childNodes.length-1)){return j.childNodes[k+1];}else{var l=this.getNextTrEl(j);if(l){return l.cells[0];}}}}return null;},getPreviousTdEl:function(i){var m=this.getTdEl(i);if(m){var k=this.getCellIndex(m);var j=this.getTrEl(m);if(k>0){if(j.cells&&j.cells.length>0){return j.cells[k-1];}else{if(j.childNodes&&j.childNodes.length>0){return j.childNodes[k-1];}}}else{var l=this.getPreviousTrEl(j);if(l){return this.getLastTdEl(l);}}}return null;},getAboveTdEl:function(j,i){var m=this.getTdEl(j);if(m){var l=this.getPreviousTrEl(m,i);if(l){var k=this.getCellIndex(m);if(l.cells&&l.cells.length>0){return l.cells[k]?l.cells[k]:null;}else{if(l.childNodes&&l.childNodes.length>0){return l.childNodes[k]?l.childNodes[k]:null;}}}}return null;},getBelowTdEl:function(j,i){var m=this.getTdEl(j);if(m){var l=this.getNextTrEl(m,i);if(l){var k=this.getCellIndex(m);if(l.cells&&l.cells.length>0){return l.cells[k]?l.cells[k]:null;}else{if(l.childNodes&&l.childNodes.length>0){return l.childNodes[k]?l.childNodes[k]:null;}}}}return null;},getThLinerEl:function(j){var i=this.getColumn(j);return(i)?i.getThLinerEl():null;},getThEl:function(k){var l;if(k instanceof YAHOO.widget.Column){var j=k;l=j.getThEl();if(l){return l;}}else{var i=c.get(k);if(i&&(i.ownerDocument==document)){if(i.nodeName.toLowerCase()!="th"){l=c.getAncestorByTagName(i,"th");
+}else{l=i;}return l;}}return null;},getTrIndex:function(m){var i=this.getRecord(m),k=this.getRecordIndex(i),l;if(i){l=this.getTrEl(i);if(l){return l.sectionRowIndex;}else{var j=this.get("paginator");if(j){return j.get("recordOffset")+k;}else{return k;}}}return null;},load:function(i){i=i||{};(i.datasource||this._oDataSource).sendRequest(i.request||this.get("initialRequest"),i.callback||{success:this.onDataReturnInitializeTable,failure:this.onDataReturnInitializeTable,scope:this,argument:this.getState()});},initializeTable:function(){this._bInit=true;this._oRecordSet.reset();var i=this.get("paginator");if(i){i.set("totalRecords",0);}this._unselectAllTrEls();this._unselectAllTdEls();this._aSelections=null;this._oAnchorRecord=null;this._oAnchorCell=null;this.set("sortedBy",null);},_runRenderChain:function(){this._oChainRender.run();},_getViewRecords:function(){var i=this.get("paginator");if(i){return this._oRecordSet.getRecords(i.getStartIndex(),i.getRowsPerPage());}else{return this._oRecordSet.getRecords();}},render:function(){this._oChainRender.stop();this.fireEvent("beforeRenderEvent");var r,p,o,s,l=this._getViewRecords();var m=this._elTbody,q=this.get("renderLoopSize"),t=l.length;if(t>0){m.style.display="none";while(m.lastChild){m.removeChild(m.lastChild);}m.style.display="";this._oChainRender.add({method:function(u){if((this instanceof d)&&this._sId){var k=u.nCurrentRecord,w=((u.nCurrentRecord+u.nLoopLength)>t)?t:(u.nCurrentRecord+u.nLoopLength),j,v;m.style.display="none";for(;k<w;k++){j=c.get(l[k].getId());j=j||this._addTrEl(l[k]);v=m.childNodes[k]||null;m.insertBefore(j,v);}m.style.display="";u.nCurrentRecord=k;}},scope:this,iterations:(q>0)?Math.ceil(t/q):1,argument:{nCurrentRecord:0,nLoopLength:(q>0)?q:t},timeout:(q>0)?0:-1});this._oChainRender.add({method:function(i){if((this instanceof d)&&this._sId){while(m.rows.length>t){m.removeChild(m.lastChild);}this._setFirstRow();this._setLastRow();this._setRowStripes();this._setSelections();}},scope:this,timeout:(q>0)?0:-1});}else{var n=m.rows.length;if(n>0){this._oChainRender.add({method:function(k){if((this instanceof d)&&this._sId){var j=k.nCurrent,v=k.nLoopLength,u=(j-v<0)?0:j-v;m.style.display="none";for(;j>u;j--){m.deleteRow(-1);}m.style.display="";k.nCurrent=j;}},scope:this,iterations:(q>0)?Math.ceil(n/q):1,argument:{nCurrent:n,nLoopLength:(q>0)?q:n},timeout:(q>0)?0:-1});}}this._runRenderChain();},disable:function(){this._disabled=true;var i=this._elTable;var j=this._elMask;j.style.width=i.offsetWidth+"px";j.style.height=i.offsetHeight+"px";j.style.left=i.offsetLeft+"px";j.style.display="";this.fireEvent("disableEvent");},undisable:function(){this._disabled=false;this._elMask.style.display="none";this.fireEvent("undisableEvent");},isDisabled:function(){return this._disabled;},destroy:function(){var k=this.toString();this._oChainRender.stop();this._destroyColumnHelpers();var m;for(var l=0,j=this._oColumnSet.flat.length;l<j;l++){m=this._oColumnSet.flat[l].editor;if(m&&m.destroy){m.destroy();this._oColumnSet.flat[l].editor=null;}}this._destroyPaginator();this._oRecordSet.unsubscribeAll();this.unsubscribeAll();g.removeListener(document,"click",this._onDocumentClick);this._destroyContainerEl(this._elContainer);for(var n in this){if(h.hasOwnProperty(this,n)){this[n]=null;}}d._nCurrentCount--;if(d._nCurrentCount<1){if(d._elDynStyleNode){document.getElementsByTagName("head")[0].removeChild(d._elDynStyleNode);d._elDynStyleNode=null;}}},showTableMessage:function(j,i){var k=this._elMsgTd;if(h.isString(j)){k.firstChild.innerHTML=j;}if(h.isString(i)){k.className=i;}this._elMsgTbody.style.display="";this.fireEvent("tableMsgShowEvent",{html:j,className:i});},hideTableMessage:function(){if(this._elMsgTbody.style.display!="none"){this._elMsgTbody.style.display="none";this._elMsgTbody.parentNode.style.width="";this.fireEvent("tableMsgHideEvent");}},focus:function(){this.focusTbodyEl();},focusTheadEl:function(){this._focusEl(this._elThead);},focusTbodyEl:function(){this._focusEl(this._elTbody);},onShow:function(){this.validateColumnWidths();for(var m=this._oColumnSet.keys,l=0,j=m.length,k;l<j;l++){k=m[l];if(k._ddResizer){k._ddResizer.resetResizerEl();}}},getRecordIndex:function(l){var k;if(!h.isNumber(l)){if(l instanceof YAHOO.widget.Record){return this._oRecordSet.getRecordIndex(l);}else{var j=this.getTrEl(l);if(j){k=j.sectionRowIndex;}}}else{k=l;}if(h.isNumber(k)){var i=this.get("paginator");if(i){return i.get("recordOffset")+k;}else{return k;}}return null;},getRecord:function(k){var j=this._oRecordSet.getRecord(k);if(!j){var i=this.getTrEl(k);if(i){j=this._oRecordSet.getRecord(i.id);}}if(j instanceof YAHOO.widget.Record){return this._oRecordSet.getRecord(j);}else{return null;}},getColumn:function(m){var o=this._oColumnSet.getColumn(m);if(!o){var n=this.getTdEl(m);if(n){o=this._oColumnSet.getColumn(this.getCellIndex(n));}else{n=this.getThEl(m);if(n){var k=this._oColumnSet.flat;for(var l=0,j=k.length;l<j;l++){if(k[l].getThEl().id===n.id){o=k[l];}}}}}if(!o){}return o;},getColumnById:function(i){return this._oColumnSet.getColumnById(i);},getColumnSortDir:function(k,l){if(k.sortOptions&&k.sortOptions.defaultDir){if(k.sortOptions.defaultDir=="asc"){k.sortOptions.defaultDir=d.CLASS_ASC;}else{if(k.sortOptions.defaultDir=="desc"){k.sortOptions.defaultDir=d.CLASS_DESC;}}}var j=(k.sortOptions&&k.sortOptions.defaultDir)?k.sortOptions.defaultDir:d.CLASS_ASC;var i=false;l=l||this.get("sortedBy");if(l&&(l.key===k.key)){i=true;if(l.dir){j=(l.dir===d.CLASS_ASC)?d.CLASS_DESC:d.CLASS_ASC;}else{j=(j===d.CLASS_ASC)?d.CLASS_DESC:d.CLASS_ASC;}}return j;},doBeforeSortColumn:function(j,i){this.showTableMessage(this.get("MSG_LOADING"),d.CLASS_LOADING);return true;},sortColumn:function(m,j){if(m&&(m instanceof YAHOO.widget.Column)){if(!m.sortable){c.addClass(this.getThEl(m),d.CLASS_SORTABLE);}if(j&&(j!==d.CLASS_ASC)&&(j!==d.CLASS_DESC)){j=null;}var n=j||this.getColumnSortDir(m);var l=this.get("sortedBy")||{};var t=(l.key===m.key)?true:false;var p=this.doBeforeSortColumn(m,n);
+if(p){if(this.get("dynamicData")){var s=this.getState();if(s.pagination){s.pagination.recordOffset=0;}s.sortedBy={key:m.key,dir:n};var k=this.get("generateRequest")(s,this);this.unselectAllRows();this.unselectAllCells();var r={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,argument:s,scope:this};this._oDataSource.sendRequest(k,r);}else{var i=(m.sortOptions&&h.isFunction(m.sortOptions.sortFunction))?m.sortOptions.sortFunction:null;if(!t||j||i){i=i||this.get("sortFunction");var q=(m.sortOptions&&m.sortOptions.field)?m.sortOptions.field:m.field;this._oRecordSet.sortRecords(i,((n==d.CLASS_DESC)?true:false),q);}else{this._oRecordSet.reverseRecords();}var o=this.get("paginator");if(o){o.setPage(1,true);}this.render();this.set("sortedBy",{key:m.key,dir:n,column:m});}this.fireEvent("columnSortEvent",{column:m,dir:n});return;}}},setColumnWidth:function(j,i){if(!(j instanceof YAHOO.widget.Column)){j=this.getColumn(j);}if(j){if(h.isNumber(i)){i=(i>j.minWidth)?i:j.minWidth;j.width=i;this._setColumnWidth(j,i+"px");this.fireEvent("columnSetWidthEvent",{column:j,width:i});}else{if(i===null){j.width=i;this._setColumnWidth(j,"auto");this.validateColumnWidths(j);this.fireEvent("columnUnsetWidthEvent",{column:j});}}this._clearTrTemplateEl();}else{}},_setColumnWidth:function(j,i,k){if(j&&(j.getKeyIndex()!==null)){k=k||(((i==="")||(i==="auto"))?"visible":"hidden");if(!d._bDynStylesFallback){this._setColumnWidthDynStyles(j,i,k);}else{this._setColumnWidthDynFunction(j,i,k);}}else{}},_setColumnWidthDynStyles:function(m,l,n){var j=d._elDynStyleNode,k;if(!j){j=document.createElement("style");j.type="text/css";j=document.getElementsByTagName("head").item(0).appendChild(j);d._elDynStyleNode=j;}if(j){var i="."+this.getId()+"-col-"+m.getSanitizedKey()+" ."+d.CLASS_LINER;if(this._elTbody){this._elTbody.style.display="none";}k=d._oDynStyles[i];if(!k){if(j.styleSheet&&j.styleSheet.addRule){j.styleSheet.addRule(i,"overflow:"+n);j.styleSheet.addRule(i,"width:"+l);k=j.styleSheet.rules[j.styleSheet.rules.length-1];d._oDynStyles[i]=k;}else{if(j.sheet&&j.sheet.insertRule){j.sheet.insertRule(i+" {overflow:"+n+";width:"+l+";}",j.sheet.cssRules.length);k=j.sheet.cssRules[j.sheet.cssRules.length-1];d._oDynStyles[i]=k;}}}else{k.style.overflow=n;k.style.width=l;}if(this._elTbody){this._elTbody.style.display="";}}if(!k){d._bDynStylesFallback=true;this._setColumnWidthDynFunction(m,l);}},_setColumnWidthDynFunction:function(r,m,s){if(m=="auto"){m="";}var l=this._elTbody?this._elTbody.rows.length:0;if(!this._aDynFunctions[l]){var q,p,o;var t=["var colIdx=oColumn.getKeyIndex();","oColumn.getThLinerEl().style.overflow="];for(q=l-1,p=2;q>=0;--q){t[p++]="this._elTbody.rows[";t[p++]=q;t[p++]="].cells[colIdx].firstChild.style.overflow=";}t[p]="sOverflow;";t[p+1]="oColumn.getThLinerEl().style.width=";for(q=l-1,o=p+2;q>=0;--q){t[o++]="this._elTbody.rows[";t[o++]=q;t[o++]="].cells[colIdx].firstChild.style.width=";}t[o]="sWidth;";this._aDynFunctions[l]=new Function("oColumn","sWidth","sOverflow",t.join(""));}var n=this._aDynFunctions[l];if(n){n.call(this,r,m,s);}},validateColumnWidths:function(o){var l=this._elColgroup;var q=l.cloneNode(true);var p=false;var n=this._oColumnSet.keys;var k;if(o&&!o.hidden&&!o.width&&(o.getKeyIndex()!==null)){k=o.getThLinerEl();if((o.minWidth>0)&&(k.offsetWidth<o.minWidth)){q.childNodes[o.getKeyIndex()].style.width=o.minWidth+(parseInt(c.getStyle(k,"paddingLeft"),10)|0)+(parseInt(c.getStyle(k,"paddingRight"),10)|0)+"px";p=true;}else{if((o.maxAutoWidth>0)&&(k.offsetWidth>o.maxAutoWidth)){this._setColumnWidth(o,o.maxAutoWidth+"px","hidden");}}}else{for(var m=0,j=n.length;m<j;m++){o=n[m];if(!o.hidden&&!o.width){k=o.getThLinerEl();if((o.minWidth>0)&&(k.offsetWidth<o.minWidth)){q.childNodes[m].style.width=o.minWidth+(parseInt(c.getStyle(k,"paddingLeft"),10)|0)+(parseInt(c.getStyle(k,"paddingRight"),10)|0)+"px";p=true;}else{if((o.maxAutoWidth>0)&&(k.offsetWidth>o.maxAutoWidth)){this._setColumnWidth(o,o.maxAutoWidth+"px","hidden");}}}}}if(p){l.parentNode.replaceChild(q,l);this._elColgroup=q;}},_clearMinWidth:function(i){if(i.getKeyIndex()!==null){this._elColgroup.childNodes[i.getKeyIndex()].style.width="";}},_restoreMinWidth:function(i){if(i.minWidth&&(i.getKeyIndex()!==null)){this._elColgroup.childNodes[i.getKeyIndex()].style.width=i.minWidth+"px";}},hideColumn:function(r){if(!(r instanceof YAHOO.widget.Column)){r=this.getColumn(r);}if(r&&!r.hidden&&r.getTreeIndex()!==null){var o=this.getTbodyEl().rows;var n=o.length;var m=this._oColumnSet.getDescendants(r);for(var q=0,s=m.length;q<s;q++){var t=m[q];t.hidden=true;c.addClass(t.getThEl(),d.CLASS_HIDDEN);var k=t.getKeyIndex();if(k!==null){this._clearMinWidth(r);for(var p=0;p<n;p++){c.addClass(o[p].cells[k],d.CLASS_HIDDEN);}}this.fireEvent("columnHideEvent",{column:t});}this._repaintOpera();this._clearTrTemplateEl();}else{}},showColumn:function(r){if(!(r instanceof YAHOO.widget.Column)){r=this.getColumn(r);}if(r&&r.hidden&&(r.getTreeIndex()!==null)){var o=this.getTbodyEl().rows;var n=o.length;var m=this._oColumnSet.getDescendants(r);for(var q=0,s=m.length;q<s;q++){var t=m[q];t.hidden=false;c.removeClass(t.getThEl(),d.CLASS_HIDDEN);var k=t.getKeyIndex();if(k!==null){this._restoreMinWidth(r);for(var p=0;p<n;p++){c.removeClass(o[p].cells[k],d.CLASS_HIDDEN);}}this.fireEvent("columnShowEvent",{column:t});}this._clearTrTemplateEl();}else{}},removeColumn:function(p){if(!(p instanceof YAHOO.widget.Column)){p=this.getColumn(p);}if(p){var m=p.getTreeIndex();if(m!==null){var o,r,q=p.getKeyIndex();if(q===null){var u=[];var j=this._oColumnSet.getDescendants(p);for(o=0,r=j.length;o<r;o++){var s=j[o].getKeyIndex();if(s!==null){u[u.length]=s;}}if(u.length>0){q=u;}}else{q=[q];}if(q!==null){q.sort(function(v,i){return YAHOO.util.Sort.compare(v,i);});this._destroyTheadEl();var k=this._oColumnSet.getDefinitions();p=k.splice(m,1)[0];this._initColumnSet(k);this._initTheadEl();for(o=q.length-1;o>-1;o--){this._removeColgroupColEl(q[o]);}var t=this._elTbody.rows;if(t.length>0){var n=this.get("renderLoopSize"),l=t.length;
+this._oChainRender.add({method:function(y){if((this instanceof d)&&this._sId){var x=y.nCurrentRow,v=n>0?Math.min(x+n,t.length):t.length,z=y.aIndexes,w;for(;x<v;++x){for(w=z.length-1;w>-1;w--){t[x].removeChild(t[x].childNodes[z[w]]);}}y.nCurrentRow=x;}},iterations:(n>0)?Math.ceil(l/n):1,argument:{nCurrentRow:0,aIndexes:q},scope:this,timeout:(n>0)?0:-1});this._runRenderChain();}this.fireEvent("columnRemoveEvent",{column:p});return p;}}}},insertColumn:function(r,s){if(r instanceof YAHOO.widget.Column){r=r.getDefinition();}else{if(r.constructor!==Object){return;}}var x=this._oColumnSet;if(!h.isValue(s)||!h.isNumber(s)){s=x.tree[0].length;}this._destroyTheadEl();var z=this._oColumnSet.getDefinitions();z.splice(s,0,r);this._initColumnSet(z);this._initTheadEl();x=this._oColumnSet;var n=x.tree[0][s];var p,t,w=[];var l=x.getDescendants(n);for(p=0,t=l.length;p<t;p++){var u=l[p].getKeyIndex();if(u!==null){w[w.length]=u;}}if(w.length>0){var y=w.sort(function(A,i){return YAHOO.util.Sort.compare(A,i);})[0];for(p=w.length-1;p>-1;p--){this._insertColgroupColEl(w[p]);}var v=this._elTbody.rows;if(v.length>0){var o=this.get("renderLoopSize"),m=v.length;var k=[],q;for(p=0,t=w.length;p<t;p++){var j=w[p];q=this._getTrTemplateEl().childNodes[p].cloneNode(true);q=this._formatTdEl(this._oColumnSet.keys[j],q,j,(j===this._oColumnSet.keys.length-1));k[j]=q;}this._oChainRender.add({method:function(D){if((this instanceof d)&&this._sId){var C=D.nCurrentRow,B,F=D.descKeyIndexes,A=o>0?Math.min(C+o,v.length):v.length,E;for(;C<A;++C){E=v[C].childNodes[y]||null;for(B=F.length-1;B>-1;B--){v[C].insertBefore(D.aTdTemplates[F[B]].cloneNode(true),E);}}D.nCurrentRow=C;}},iterations:(o>0)?Math.ceil(m/o):1,argument:{nCurrentRow:0,aTdTemplates:k,descKeyIndexes:w},scope:this,timeout:(o>0)?0:-1});this._runRenderChain();}this.fireEvent("columnInsertEvent",{column:r,index:s});return n;}},reorderColumn:function(q,r){if(!(q instanceof YAHOO.widget.Column)){q=this.getColumn(q);}if(q&&YAHOO.lang.isNumber(r)){var z=q.getTreeIndex();if((z!==null)&&(z!==r)){var p,s,l=q.getKeyIndex(),k,v=[],t;if(l===null){k=this._oColumnSet.getDescendants(q);for(p=0,s=k.length;p<s;p++){t=k[p].getKeyIndex();if(t!==null){v[v.length]=t;}}if(v.length>0){l=v;}}else{l=[l];}if(l!==null){l.sort(function(A,i){return YAHOO.util.Sort.compare(A,i);});this._destroyTheadEl();var w=this._oColumnSet.getDefinitions();var j=w.splice(z,1)[0];w.splice(r,0,j);this._initColumnSet(w);this._initTheadEl();var n=this._oColumnSet.tree[0][r];var y=n.getKeyIndex();if(y===null){v=[];k=this._oColumnSet.getDescendants(n);for(p=0,s=k.length;p<s;p++){t=k[p].getKeyIndex();if(t!==null){v[v.length]=t;}}if(v.length>0){y=v;}}else{y=[y];}var x=y.sort(function(A,i){return YAHOO.util.Sort.compare(A,i);})[0];this._reorderColgroupColEl(l,x);var u=this._elTbody.rows;if(u.length>0){var o=this.get("renderLoopSize"),m=u.length;this._oChainRender.add({method:function(D){if((this instanceof d)&&this._sId){var C=D.nCurrentRow,B,F,E,A=o>0?Math.min(C+o,u.length):u.length,H=D.aIndexes,G;for(;C<A;++C){F=[];G=u[C];for(B=H.length-1;B>-1;B--){F.push(G.removeChild(G.childNodes[H[B]]));}E=G.childNodes[x]||null;for(B=F.length-1;B>-1;B--){G.insertBefore(F[B],E);}}D.nCurrentRow=C;}},iterations:(o>0)?Math.ceil(m/o):1,argument:{nCurrentRow:0,aIndexes:l},scope:this,timeout:(o>0)?0:-1});this._runRenderChain();}this.fireEvent("columnReorderEvent",{column:n,oldIndex:z});return n;}}}},selectColumn:function(k){k=this.getColumn(k);if(k&&!k.selected){if(k.getKeyIndex()!==null){k.selected=true;var l=k.getThEl();c.addClass(l,d.CLASS_SELECTED);var j=this.getTbodyEl().rows;var i=this._oChainRender;i.add({method:function(m){if((this instanceof d)&&this._sId&&j[m.rowIndex]&&j[m.rowIndex].cells[m.cellIndex]){c.addClass(j[m.rowIndex].cells[m.cellIndex],d.CLASS_SELECTED);}m.rowIndex++;},scope:this,iterations:j.length,argument:{rowIndex:0,cellIndex:k.getKeyIndex()}});this._clearTrTemplateEl();this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnSelectEvent",{column:k});}else{}}},unselectColumn:function(k){k=this.getColumn(k);if(k&&k.selected){if(k.getKeyIndex()!==null){k.selected=false;var l=k.getThEl();c.removeClass(l,d.CLASS_SELECTED);var j=this.getTbodyEl().rows;var i=this._oChainRender;i.add({method:function(m){if((this instanceof d)&&this._sId&&j[m.rowIndex]&&j[m.rowIndex].cells[m.cellIndex]){c.removeClass(j[m.rowIndex].cells[m.cellIndex],d.CLASS_SELECTED);}m.rowIndex++;},scope:this,iterations:j.length,argument:{rowIndex:0,cellIndex:k.getKeyIndex()}});this._clearTrTemplateEl();this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnUnselectEvent",{column:k});}else{}}},getSelectedColumns:function(n){var k=[];var l=this._oColumnSet.keys;for(var m=0,j=l.length;m<j;m++){if(l[m].selected){k[k.length]=l[m];}}return k;},highlightColumn:function(i){var l=this.getColumn(i);if(l&&(l.getKeyIndex()!==null)){var m=l.getThEl();c.addClass(m,d.CLASS_HIGHLIGHTED);var k=this.getTbodyEl().rows;var j=this._oChainRender;j.add({method:function(n){if((this instanceof d)&&this._sId&&k[n.rowIndex]&&k[n.rowIndex].cells[n.cellIndex]){c.addClass(k[n.rowIndex].cells[n.cellIndex],d.CLASS_HIGHLIGHTED);}n.rowIndex++;},scope:this,iterations:k.length,argument:{rowIndex:0,cellIndex:l.getKeyIndex()},timeout:-1});this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnHighlightEvent",{column:l});}else{}},unhighlightColumn:function(i){var l=this.getColumn(i);if(l&&(l.getKeyIndex()!==null)){var m=l.getThEl();c.removeClass(m,d.CLASS_HIGHLIGHTED);var k=this.getTbodyEl().rows;var j=this._oChainRender;j.add({method:function(n){if((this instanceof d)&&this._sId&&k[n.rowIndex]&&k[n.rowIndex].cells[n.cellIndex]){c.removeClass(k[n.rowIndex].cells[n.cellIndex],d.CLASS_HIGHLIGHTED);}n.rowIndex++;},scope:this,iterations:k.length,argument:{rowIndex:0,cellIndex:l.getKeyIndex()},timeout:-1});this._elTbody.style.display="none";
+this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnUnhighlightEvent",{column:l});}else{}},addRow:function(o,k){if(h.isNumber(k)&&(k<0||k>this._oRecordSet.getLength())){return;}if(o&&h.isObject(o)){var m=this._oRecordSet.addRecord(o,k);if(m){var i;var j=this.get("paginator");if(j){var n=j.get("totalRecords");if(n!==e.Paginator.VALUE_UNLIMITED){j.set("totalRecords",n+1);}i=this.getRecordIndex(m);var l=(j.getPageRecords())[1];if(i<=l){this.render();}this.fireEvent("rowAddEvent",{record:m});return;}else{i=this.getRecordIndex(m);if(h.isNumber(i)){this._oChainRender.add({method:function(r){if((this instanceof d)&&this._sId){var s=r.record;var p=r.recIndex;var t=this._addTrEl(s);if(t){var q=(this._elTbody.rows[p])?this._elTbody.rows[p]:null;this._elTbody.insertBefore(t,q);if(p===0){this._setFirstRow();}if(q===null){this._setLastRow();}this._setRowStripes();this.hideTableMessage();this.fireEvent("rowAddEvent",{record:s});}}},argument:{record:m,recIndex:i},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}}},addRows:function(k,n){if(h.isNumber(n)&&(n<0||n>this._oRecordSet.getLength())){return;}if(h.isArray(k)){var o=this._oRecordSet.addRecords(k,n);if(o){var s=this.getRecordIndex(o[0]);var r=this.get("paginator");if(r){var p=r.get("totalRecords");if(p!==e.Paginator.VALUE_UNLIMITED){r.set("totalRecords",p+o.length);}var q=(r.getPageRecords())[1];if(s<=q){this.render();}this.fireEvent("rowsAddEvent",{records:o});return;}else{var m=this.get("renderLoopSize");var j=s+k.length;var i=(j-s);var l=(s>=this._elTbody.rows.length);this._oChainRender.add({method:function(x){if((this instanceof d)&&this._sId){var y=x.aRecords,w=x.nCurrentRow,v=x.nCurrentRecord,t=m>0?Math.min(w+m,j):j,z=document.createDocumentFragment(),u=(this._elTbody.rows[w])?this._elTbody.rows[w]:null;for(;w<t;w++,v++){z.appendChild(this._addTrEl(y[v]));}this._elTbody.insertBefore(z,u);x.nCurrentRow=w;x.nCurrentRecord=v;}},iterations:(m>0)?Math.ceil(j/m):1,argument:{nCurrentRow:s,nCurrentRecord:0,aRecords:o},scope:this,timeout:(m>0)?0:-1});this._oChainRender.add({method:function(u){var t=u.recIndex;if(t===0){this._setFirstRow();}if(u.isLast){this._setLastRow();}this._setRowStripes();this.fireEvent("rowsAddEvent",{records:o});},argument:{recIndex:s,isLast:l},scope:this,timeout:-1});this._runRenderChain();this.hideTableMessage();return;}}}},updateRow:function(u,k){var r=u;if(!h.isNumber(r)){r=this.getRecordIndex(u);}if(h.isNumber(r)&&(r>=0)){var s=this._oRecordSet,q=s.getRecord(r);if(q){var o=this._oRecordSet.setRecord(k,r),j=this.getTrEl(q),p=q?q.getData():null;if(o){var t=this._aSelections||[],n=0,l=q.getId(),m=o.getId();for(;n<t.length;n++){if((t[n]===l)){t[n]=m;}else{if(t[n].recordId===l){t[n].recordId=m;}}}if(this._oAnchorRecord&&this._oAnchorRecord.getId()===l){this._oAnchorRecord=o;}if(this._oAnchorCell&&this._oAnchorCell.record.getId()===l){this._oAnchorCell.record=o;}this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){var v=this.get("paginator");if(v){var i=(v.getPageRecords())[0],w=(v.getPageRecords())[1];if((r>=i)||(r<=w)){this.render();}}else{if(j){this._updateTrEl(j,o);}else{this.getTbodyEl().appendChild(this._addTrEl(o));}}this.fireEvent("rowUpdateEvent",{record:o,oldData:p});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}return;},updateRows:function(A,m){if(h.isArray(m)){var s=A,l=this._oRecordSet,o=l.getLength();if(!h.isNumber(A)){s=this.getRecordIndex(A);}if(h.isNumber(s)&&(s>=0)&&(s<l.getLength())){var E=s+m.length,B=l.getRecords(s,m.length),G=l.setRecords(m,s);if(G){var t=this._aSelections||[],D=0,C,u,x,z,y=this._oAnchorRecord?this._oAnchorRecord.getId():null,n=this._oAnchorCell?this._oAnchorCell.record.getId():null;for(;D<B.length;D++){z=B[D].getId();u=G[D];x=u.getId();for(C=0;C<t.length;C++){if((t[C]===z)){t[C]=x;}else{if(t[C].recordId===z){t[C].recordId=x;}}}if(y&&y===z){this._oAnchorRecord=u;}if(n&&n===z){this._oAnchorCell.record=u;}}var F=this.get("paginator");if(F){var r=(F.getPageRecords())[0],p=(F.getPageRecords())[1];if((s>=r)||(E<=p)){this.render();}this.fireEvent("rowsAddEvent",{newRecords:G,oldRecords:B});return;}else{var k=this.get("renderLoopSize"),v=m.length,w=(E>=o),q=(E>o);this._oChainRender.add({method:function(K){if((this instanceof d)&&this._sId){var L=K.aRecords,J=K.nCurrentRow,I=K.nDataPointer,H=k>0?Math.min(J+k,s+L.length):s+L.length;for(;J<H;J++,I++){if(q&&(J>=o)){this._elTbody.appendChild(this._addTrEl(L[I]));}else{this._updateTrEl(this._elTbody.rows[J],L[I]);}}K.nCurrentRow=J;K.nDataPointer=I;}},iterations:(k>0)?Math.ceil(v/k):1,argument:{nCurrentRow:s,aRecords:G,nDataPointer:0,isAdding:q},scope:this,timeout:(k>0)?0:-1});this._oChainRender.add({method:function(j){var i=j.recIndex;if(i===0){this._setFirstRow();}if(j.isLast){this._setLastRow();}this._setRowStripes();this.fireEvent("rowsAddEvent",{newRecords:G,oldRecords:B});},argument:{recIndex:s,isLast:w},scope:this,timeout:-1});this._runRenderChain();this.hideTableMessage();return;}}}}},deleteRow:function(s){var k=(h.isNumber(s))?s:this.getRecordIndex(s);if(h.isNumber(k)){var t=this.getRecord(k);if(t){var m=this.getTrIndex(k);var p=t.getId();var r=this._aSelections||[];for(var n=r.length-1;n>-1;n--){if((h.isString(r[n])&&(r[n]===p))||(h.isObject(r[n])&&(r[n].recordId===p))){r.splice(n,1);}}var l=this._oRecordSet.deleteRecord(k);if(l){var q=this.get("paginator");if(q){var o=q.get("totalRecords"),i=q.getPageRecords();if(o!==e.Paginator.VALUE_UNLIMITED){q.set("totalRecords",o-1);}if(!i||k<=i[1]){this.render();}this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){this.fireEvent("rowDeleteEvent",{recordIndex:k,oldData:l,trElIndex:m});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();}else{if(h.isNumber(m)){this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){var j=(k===this._oRecordSet.getLength());this._deleteTrEl(m);if(this._elTbody.rows.length>0){if(m===0){this._setFirstRow();
+}if(j){this._setLastRow();}if(m!=this._elTbody.rows.length){this._setRowStripes(m);}}this.fireEvent("rowDeleteEvent",{recordIndex:k,oldData:l,trElIndex:m});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}}}return null;},deleteRows:function(y,s){var l=(h.isNumber(y))?y:this.getRecordIndex(y);if(h.isNumber(l)){var z=this.getRecord(l);if(z){var m=this.getTrIndex(l);var u=z.getId();var x=this._aSelections||[];for(var q=x.length-1;q>-1;q--){if((h.isString(x[q])&&(x[q]===u))||(h.isObject(x[q])&&(x[q].recordId===u))){x.splice(q,1);}}var n=l;var w=l;if(s&&h.isNumber(s)){n=(s>0)?l+s-1:l;w=(s>0)?l:l+s+1;s=(s>0)?s:s*-1;if(w<0){w=0;s=n-w+1;}}else{s=1;}var p=this._oRecordSet.deleteRecords(w,s);if(p){var v=this.get("paginator"),r=this.get("renderLoopSize");if(v){var t=v.get("totalRecords"),k=v.getPageRecords();if(t!==e.Paginator.VALUE_UNLIMITED){v.set("totalRecords",t-p.length);}if(!k||w<=k[1]){this.render();}this._oChainRender.add({method:function(j){if((this instanceof d)&&this._sId){this.fireEvent("rowsDeleteEvent",{recordIndex:w,oldData:p,count:s});}},scope:this,timeout:(r>0)?0:-1});this._runRenderChain();return;}else{if(h.isNumber(m)){var o=w;var i=s;this._oChainRender.add({method:function(B){if((this instanceof d)&&this._sId){var A=B.nCurrentRow,j=(r>0)?(Math.max(A-r,o)-1):o-1;for(;A>j;--A){this._deleteTrEl(A);}B.nCurrentRow=A;}},iterations:(r>0)?Math.ceil(s/r):1,argument:{nCurrentRow:n},scope:this,timeout:(r>0)?0:-1});this._oChainRender.add({method:function(){if(this._elTbody.rows.length>0){this._setFirstRow();this._setLastRow();this._setRowStripes();}this.fireEvent("rowsDeleteEvent",{recordIndex:w,oldData:p,count:s});},scope:this,timeout:-1});this._runRenderChain();return;}}}}}return null;},formatCell:function(j,l,m){if(!l){l=this.getRecord(j);}if(!m){m=this.getColumn(this.getCellIndex(j.parentNode));}if(l&&m){var i=m.field;var n=l.getData(i);var k=typeof m.formatter==="function"?m.formatter:d.Formatter[m.formatter+""]||d.Formatter.defaultFormatter;if(k){k.call(this,j,l,m,n);}else{j.innerHTML=n;}this.fireEvent("cellFormatEvent",{record:l,column:m,key:m.key,el:j});}else{}},updateCell:function(k,m,o,j){m=(m instanceof YAHOO.widget.Column)?m:this.getColumn(m);if(m&&m.getField()&&(k instanceof YAHOO.widget.Record)){var l=m.getField(),n=k.getData(l);this._oRecordSet.updateRecordValue(k,l,o);var i=this.getTdEl({record:k,column:m});if(i){this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){this.formatCell(i.firstChild,k,m);this.fireEvent("cellUpdateEvent",{record:k,column:m,oldData:n});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});if(!j){this._runRenderChain();}}else{this.fireEvent("cellUpdateEvent",{record:k,column:m,oldData:n});}}},_updatePaginator:function(j){var i=this.get("paginator");if(i&&j!==i){i.unsubscribe("changeRequest",this.onPaginatorChangeRequest,this,true);}if(j){j.subscribe("changeRequest",this.onPaginatorChangeRequest,this,true);}},_handlePaginatorChange:function(l){if(l.prevValue===l.newValue){return;}var n=l.newValue,m=l.prevValue,k=this._defaultPaginatorContainers();if(m){if(m.getContainerNodes()[0]==k[0]){m.set("containers",[]);}m.destroy();if(k[0]){if(n&&!n.getContainerNodes().length){n.set("containers",k);}else{for(var j=k.length-1;j>=0;--j){if(k[j]){k[j].parentNode.removeChild(k[j]);}}}}}if(!this._bInit){this.render();}if(n){this.renderPaginator();}},_defaultPaginatorContainers:function(l){var j=this._sId+"-paginator0",k=this._sId+"-paginator1",i=c.get(j),m=c.get(k);if(l&&(!i||!m)){if(!i){i=document.createElement("div");i.id=j;c.addClass(i,d.CLASS_PAGINATOR);this._elContainer.insertBefore(i,this._elContainer.firstChild);}if(!m){m=document.createElement("div");m.id=k;c.addClass(m,d.CLASS_PAGINATOR);this._elContainer.appendChild(m);}}return[i,m];},_destroyPaginator:function(){var i=this.get("paginator");if(i){i.destroy();}},renderPaginator:function(){var i=this.get("paginator");if(!i){return;}if(!i.getContainerNodes().length){i.set("containers",this._defaultPaginatorContainers(true));}i.render();},doBeforePaginatorChange:function(i){this.showTableMessage(this.get("MSG_LOADING"),d.CLASS_LOADING);return true;},onPaginatorChangeRequest:function(l){var j=this.doBeforePaginatorChange(l);if(j){if(this.get("dynamicData")){var i=this.getState();i.pagination=l;var k=this.get("generateRequest")(i,this);this.unselectAllRows();this.unselectAllCells();var m={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,argument:i,scope:this};this._oDataSource.sendRequest(k,m);}else{l.paginator.setStartIndex(l.recordOffset,true);l.paginator.setRowsPerPage(l.rowsPerPage,true);this.render();}}else{}},_elLastHighlightedTd:null,_aSelections:null,_oAnchorRecord:null,_oAnchorCell:null,_unselectAllTrEls:function(){var i=c.getElementsByClassName(d.CLASS_SELECTED,"tr",this._elTbody);c.removeClass(i,d.CLASS_SELECTED);},_getSelectionTrigger:function(){var l=this.get("selectionMode");var k={};var o,i,j,n,m;if((l=="cellblock")||(l=="cellrange")||(l=="singlecell")){o=this.getLastSelectedCell();if(!o){return null;}else{i=this.getRecord(o.recordId);j=this.getRecordIndex(i);n=this.getTrEl(i);m=this.getTrIndex(n);if(m===null){return null;}else{k.record=i;k.recordIndex=j;k.el=this.getTdEl(o);k.trIndex=m;k.column=this.getColumn(o.columnKey);k.colKeyIndex=k.column.getKeyIndex();k.cell=o;return k;}}}else{i=this.getLastSelectedRecord();if(!i){return null;}else{i=this.getRecord(i);j=this.getRecordIndex(i);n=this.getTrEl(i);m=this.getTrIndex(n);if(m===null){return null;}else{k.record=i;k.recordIndex=j;k.el=n;k.trIndex=m;return k;}}}},_getSelectionAnchor:function(k){var j=this.get("selectionMode");var l={};var m,o,i;if((j=="cellblock")||(j=="cellrange")||(j=="singlecell")){var n=this._oAnchorCell;if(!n){if(k){n=this._oAnchorCell=k.cell;}else{return null;}}m=this._oAnchorCell.record;o=this._oRecordSet.getRecordIndex(m);i=this.getTrIndex(m);if(i===null){if(o<this.getRecordIndex(this.getFirstTrEl())){i=0;}else{i=this.getRecordIndex(this.getLastTrEl());
+}}l.record=m;l.recordIndex=o;l.trIndex=i;l.column=this._oAnchorCell.column;l.colKeyIndex=l.column.getKeyIndex();l.cell=n;return l;}else{m=this._oAnchorRecord;if(!m){if(k){m=this._oAnchorRecord=k.record;}else{return null;}}o=this.getRecordIndex(m);i=this.getTrIndex(m);if(i===null){if(o<this.getRecordIndex(this.getFirstTrEl())){i=0;}else{i=this.getRecordIndex(this.getLastTrEl());}}l.record=m;l.recordIndex=o;l.trIndex=i;return l;}},_handleStandardSelectionByMouse:function(k){var j=k.target;var m=this.getTrEl(j);if(m){var p=k.event;var s=p.shiftKey;var o=p.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&p.metaKey);var r=this.getRecord(m);var l=this._oRecordSet.getRecordIndex(r);var q=this._getSelectionAnchor();var n;if(s&&o){if(q){if(this.isSelected(q.record)){if(q.recordIndex<l){for(n=q.recordIndex+1;n<=l;n++){if(!this.isSelected(n)){this.selectRow(n);}}}else{for(n=q.recordIndex-1;n>=l;n--){if(!this.isSelected(n)){this.selectRow(n);}}}}else{if(q.recordIndex<l){for(n=q.recordIndex+1;n<=l-1;n++){if(this.isSelected(n)){this.unselectRow(n);}}}else{for(n=l+1;n<=q.recordIndex-1;n++){if(this.isSelected(n)){this.unselectRow(n);}}}this.selectRow(r);}}else{this._oAnchorRecord=r;if(this.isSelected(r)){this.unselectRow(r);}else{this.selectRow(r);}}}else{if(s){this.unselectAllRows();if(q){if(q.recordIndex<l){for(n=q.recordIndex;n<=l;n++){this.selectRow(n);}}else{for(n=q.recordIndex;n>=l;n--){this.selectRow(n);}}}else{this._oAnchorRecord=r;this.selectRow(r);}}else{if(o){this._oAnchorRecord=r;if(this.isSelected(r)){this.unselectRow(r);}else{this.selectRow(r);}}else{this._handleSingleSelectionByMouse(k);return;}}}}},_handleStandardSelectionByKey:function(m){var i=g.getCharCode(m);if((i==38)||(i==40)){var k=m.shiftKey;var j=this._getSelectionTrigger();if(!j){return null;}g.stopEvent(m);var l=this._getSelectionAnchor(j);if(k){if((i==40)&&(l.recordIndex<=j.trIndex)){this.selectRow(this.getNextTrEl(j.el));}else{if((i==38)&&(l.recordIndex>=j.trIndex)){this.selectRow(this.getPreviousTrEl(j.el));}else{this.unselectRow(j.el);}}}else{this._handleSingleSelectionByKey(m);}}},_handleSingleSelectionByMouse:function(k){var l=k.target;var j=this.getTrEl(l);if(j){var i=this.getRecord(j);this._oAnchorRecord=i;this.unselectAllRows();this.selectRow(i);}},_handleSingleSelectionByKey:function(l){var i=g.getCharCode(l);if((i==38)||(i==40)){var j=this._getSelectionTrigger();if(!j){return null;}g.stopEvent(l);var k;if(i==38){k=this.getPreviousTrEl(j.el);if(k===null){k=this.getFirstTrEl();}}else{if(i==40){k=this.getNextTrEl(j.el);if(k===null){k=this.getLastTrEl();}}}this.unselectAllRows();this.selectRow(k);this._oAnchorRecord=this.getRecord(k);}},_handleCellBlockSelectionByMouse:function(A){var B=A.target;var l=this.getTdEl(B);if(l){var z=A.event;var q=z.shiftKey;var m=z.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&z.metaKey);var s=this.getTrEl(l);var r=this.getTrIndex(s);var v=this.getColumn(l);var w=v.getKeyIndex();var u=this.getRecord(s);var D=this._oRecordSet.getRecordIndex(u);var p={record:u,column:v};var t=this._getSelectionAnchor();var o=this.getTbodyEl().rows;var n,k,C,y,x;if(q&&m){if(t){if(this.isSelected(t.cell)){if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex+1;y<=w;y++){this.selectCell(s.cells[y]);}}else{if(w<t.colKeyIndex){for(y=w;y<t.colKeyIndex;y++){this.selectCell(s.cells[y]);}}}}else{if(t.recordIndex<D){n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=t.trIndex;y<=r;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x]);}}}else{n=Math.min(t.trIndex,w);k=Math.max(t.trIndex,w);for(y=t.trIndex;y>=r;y--){for(x=k;x>=n;x--){this.selectCell(o[y].cells[x]);}}}}}else{if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex+1;y<w;y++){this.unselectCell(s.cells[y]);}}else{if(w<t.colKeyIndex){for(y=w+1;y<t.colKeyIndex;y++){this.unselectCell(s.cells[y]);}}}}if(t.recordIndex<D){for(y=t.trIndex;y<=r;y++){C=o[y];for(x=0;x<C.cells.length;x++){if(C.sectionRowIndex===t.trIndex){if(x>t.colKeyIndex){this.unselectCell(C.cells[x]);}}else{if(C.sectionRowIndex===r){if(x<w){this.unselectCell(C.cells[x]);}}else{this.unselectCell(C.cells[x]);}}}}}else{for(y=r;y<=t.trIndex;y++){C=o[y];for(x=0;x<C.cells.length;x++){if(C.sectionRowIndex==r){if(x>w){this.unselectCell(C.cells[x]);}}else{if(C.sectionRowIndex==t.trIndex){if(x<t.colKeyIndex){this.unselectCell(C.cells[x]);}}else{this.unselectCell(C.cells[x]);}}}}}this.selectCell(l);}}else{this._oAnchorCell=p;if(this.isSelected(p)){this.unselectCell(p);}else{this.selectCell(p);}}}else{if(q){this.unselectAllCells();if(t){if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex;y<=w;y++){this.selectCell(s.cells[y]);}}else{if(w<t.colKeyIndex){for(y=w;y<=t.colKeyIndex;y++){this.selectCell(s.cells[y]);}}}}else{if(t.recordIndex<D){n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=t.trIndex;y<=r;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x]);}}}else{n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=r;y<=t.trIndex;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x]);}}}}}else{this._oAnchorCell=p;this.selectCell(p);}}else{if(m){this._oAnchorCell=p;if(this.isSelected(p)){this.unselectCell(p);}else{this.selectCell(p);}}else{this._handleSingleCellSelectionByMouse(A);}}}}},_handleCellBlockSelectionByKey:function(o){var j=g.getCharCode(o);var t=o.shiftKey;if((j==9)||!t){this._handleSingleCellSelectionByKey(o);return;}if((j>36)&&(j<41)){var u=this._getSelectionTrigger();if(!u){return null;}g.stopEvent(o);var r=this._getSelectionAnchor(u);var k,s,l,q,m;var p=this.getTbodyEl().rows;var n=u.el.parentNode;if(j==40){if(r.recordIndex<=u.recordIndex){m=this.getNextTrEl(u.el);if(m){s=r.colKeyIndex;l=u.colKeyIndex;if(s>l){for(k=s;k>=l;k--){q=m.cells[k];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=m.cells[k];this.selectCell(q);}}}}else{s=Math.min(r.colKeyIndex,u.colKeyIndex);l=Math.max(r.colKeyIndex,u.colKeyIndex);for(k=s;k<=l;k++){this.unselectCell(n.cells[k]);}}}else{if(j==38){if(r.recordIndex>=u.recordIndex){m=this.getPreviousTrEl(u.el);
+if(m){s=r.colKeyIndex;l=u.colKeyIndex;if(s>l){for(k=s;k>=l;k--){q=m.cells[k];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=m.cells[k];this.selectCell(q);}}}}else{s=Math.min(r.colKeyIndex,u.colKeyIndex);l=Math.max(r.colKeyIndex,u.colKeyIndex);for(k=s;k<=l;k++){this.unselectCell(n.cells[k]);}}}else{if(j==39){if(r.colKeyIndex<=u.colKeyIndex){if(u.colKeyIndex<n.cells.length-1){s=r.trIndex;l=u.trIndex;if(s>l){for(k=s;k>=l;k--){q=p[k].cells[u.colKeyIndex+1];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=p[k].cells[u.colKeyIndex+1];this.selectCell(q);}}}}else{s=Math.min(r.trIndex,u.trIndex);l=Math.max(r.trIndex,u.trIndex);for(k=s;k<=l;k++){this.unselectCell(p[k].cells[u.colKeyIndex]);}}}else{if(j==37){if(r.colKeyIndex>=u.colKeyIndex){if(u.colKeyIndex>0){s=r.trIndex;l=u.trIndex;if(s>l){for(k=s;k>=l;k--){q=p[k].cells[u.colKeyIndex-1];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=p[k].cells[u.colKeyIndex-1];this.selectCell(q);}}}}else{s=Math.min(r.trIndex,u.trIndex);l=Math.max(r.trIndex,u.trIndex);for(k=s;k<=l;k++){this.unselectCell(p[k].cells[u.colKeyIndex]);}}}}}}}},_handleCellRangeSelectionByMouse:function(y){var z=y.target;var k=this.getTdEl(z);if(k){var x=y.event;var o=x.shiftKey;var l=x.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&x.metaKey);var q=this.getTrEl(k);var p=this.getTrIndex(q);var t=this.getColumn(k);var u=t.getKeyIndex();var s=this.getRecord(q);var B=this._oRecordSet.getRecordIndex(s);var n={record:s,column:t};var r=this._getSelectionAnchor();var m=this.getTbodyEl().rows;var A,w,v;if(o&&l){if(r){if(this.isSelected(r.cell)){if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex+1;w<=u;w++){this.selectCell(q.cells[w]);}}else{if(u<r.colKeyIndex){for(w=u;w<r.colKeyIndex;w++){this.selectCell(q.cells[w]);}}}}else{if(r.recordIndex<B){for(w=r.colKeyIndex+1;w<q.cells.length;w++){this.selectCell(q.cells[w]);}for(w=r.trIndex+1;w<p;w++){for(v=0;v<m[w].cells.length;v++){this.selectCell(m[w].cells[v]);}}for(w=0;w<=u;w++){this.selectCell(q.cells[w]);}}else{for(w=u;w<q.cells.length;w++){this.selectCell(q.cells[w]);}for(w=p+1;w<r.trIndex;w++){for(v=0;v<m[w].cells.length;v++){this.selectCell(m[w].cells[v]);}}for(w=0;w<r.colKeyIndex;w++){this.selectCell(q.cells[w]);}}}}else{if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex+1;w<u;w++){this.unselectCell(q.cells[w]);}}else{if(u<r.colKeyIndex){for(w=u+1;w<r.colKeyIndex;w++){this.unselectCell(q.cells[w]);}}}}if(r.recordIndex<B){for(w=r.trIndex;w<=p;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex===r.trIndex){if(v>r.colKeyIndex){this.unselectCell(A.cells[v]);}}else{if(A.sectionRowIndex===p){if(v<u){this.unselectCell(A.cells[v]);}}else{this.unselectCell(A.cells[v]);}}}}}else{for(w=p;w<=r.trIndex;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==p){if(v>u){this.unselectCell(A.cells[v]);}}else{if(A.sectionRowIndex==r.trIndex){if(v<r.colKeyIndex){this.unselectCell(A.cells[v]);}}else{this.unselectCell(A.cells[v]);}}}}}this.selectCell(k);}}else{this._oAnchorCell=n;if(this.isSelected(n)){this.unselectCell(n);}else{this.selectCell(n);}}}else{if(o){this.unselectAllCells();if(r){if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex;w<=u;w++){this.selectCell(q.cells[w]);}}else{if(u<r.colKeyIndex){for(w=u;w<=r.colKeyIndex;w++){this.selectCell(q.cells[w]);}}}}else{if(r.recordIndex<B){for(w=r.trIndex;w<=p;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==r.trIndex){if(v>=r.colKeyIndex){this.selectCell(A.cells[v]);}}else{if(A.sectionRowIndex==p){if(v<=u){this.selectCell(A.cells[v]);}}else{this.selectCell(A.cells[v]);}}}}}else{for(w=p;w<=r.trIndex;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==p){if(v>=u){this.selectCell(A.cells[v]);}}else{if(A.sectionRowIndex==r.trIndex){if(v<=r.colKeyIndex){this.selectCell(A.cells[v]);}}else{this.selectCell(A.cells[v]);}}}}}}}else{this._oAnchorCell=n;this.selectCell(n);}}else{if(l){this._oAnchorCell=n;if(this.isSelected(n)){this.unselectCell(n);}else{this.selectCell(n);}}else{this._handleSingleCellSelectionByMouse(y);}}}}},_handleCellRangeSelectionByKey:function(n){var j=g.getCharCode(n);var r=n.shiftKey;if((j==9)||!r){this._handleSingleCellSelectionByKey(n);return;}if((j>36)&&(j<41)){var s=this._getSelectionTrigger();if(!s){return null;}g.stopEvent(n);var q=this._getSelectionAnchor(s);var k,l,p;var o=this.getTbodyEl().rows;var m=s.el.parentNode;if(j==40){l=this.getNextTrEl(s.el);if(q.recordIndex<=s.recordIndex){for(k=s.colKeyIndex+1;k<m.cells.length;k++){p=m.cells[k];this.selectCell(p);}if(l){for(k=0;k<=s.colKeyIndex;k++){p=l.cells[k];this.selectCell(p);}}}else{for(k=s.colKeyIndex;k<m.cells.length;k++){this.unselectCell(m.cells[k]);}if(l){for(k=0;k<s.colKeyIndex;k++){this.unselectCell(l.cells[k]);}}}}else{if(j==38){l=this.getPreviousTrEl(s.el);if(q.recordIndex>=s.recordIndex){for(k=s.colKeyIndex-1;k>-1;k--){p=m.cells[k];this.selectCell(p);}if(l){for(k=m.cells.length-1;k>=s.colKeyIndex;k--){p=l.cells[k];this.selectCell(p);}}}else{for(k=s.colKeyIndex;k>-1;k--){this.unselectCell(m.cells[k]);}if(l){for(k=m.cells.length-1;k>s.colKeyIndex;k--){this.unselectCell(l.cells[k]);}}}}else{if(j==39){l=this.getNextTrEl(s.el);if(q.recordIndex<s.recordIndex){if(s.colKeyIndex<m.cells.length-1){p=m.cells[s.colKeyIndex+1];this.selectCell(p);}else{if(l){p=l.cells[0];this.selectCell(p);}}}else{if(q.recordIndex>s.recordIndex){this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex<m.cells.length-1){}else{}}else{if(q.colKeyIndex<=s.colKeyIndex){if(s.colKeyIndex<m.cells.length-1){p=m.cells[s.colKeyIndex+1];this.selectCell(p);}else{if(s.trIndex<o.length-1){p=l.cells[0];this.selectCell(p);}}}else{this.unselectCell(m.cells[s.colKeyIndex]);}}}}else{if(j==37){l=this.getPreviousTrEl(s.el);if(q.recordIndex<s.recordIndex){this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex>0){}else{}}else{if(q.recordIndex>s.recordIndex){if(s.colKeyIndex>0){p=m.cells[s.colKeyIndex-1];this.selectCell(p);}else{if(s.trIndex>0){p=l.cells[l.cells.length-1];this.selectCell(p);
+}}}else{if(q.colKeyIndex>=s.colKeyIndex){if(s.colKeyIndex>0){p=m.cells[s.colKeyIndex-1];this.selectCell(p);}else{if(s.trIndex>0){p=l.cells[l.cells.length-1];this.selectCell(p);}}}else{this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex>0){}else{}}}}}}}}}},_handleSingleCellSelectionByMouse:function(n){var o=n.target;var k=this.getTdEl(o);if(k){var j=this.getTrEl(k);var i=this.getRecord(j);var m=this.getColumn(k);var l={record:i,column:m};this._oAnchorCell=l;this.unselectAllCells();this.selectCell(l);}},_handleSingleCellSelectionByKey:function(m){var i=g.getCharCode(m);if((i==9)||((i>36)&&(i<41))){var k=m.shiftKey;var j=this._getSelectionTrigger();if(!j){return null;}var l;if(i==40){l=this.getBelowTdEl(j.el);if(l===null){l=j.el;}}else{if(i==38){l=this.getAboveTdEl(j.el);if(l===null){l=j.el;}}else{if((i==39)||(!k&&(i==9))){l=this.getNextTdEl(j.el);if(l===null){return;}}else{if((i==37)||(k&&(i==9))){l=this.getPreviousTdEl(j.el);if(l===null){return;}}}}}g.stopEvent(m);this.unselectAllCells();this.selectCell(l);this._oAnchorCell={record:this.getRecord(l),column:this.getColumn(l)};}},getSelectedTrEls:function(){return c.getElementsByClassName(d.CLASS_SELECTED,"tr",this._elTbody);},selectRow:function(p){var o,i;if(p instanceof YAHOO.widget.Record){o=this._oRecordSet.getRecord(p);i=this.getTrEl(o);}else{if(h.isNumber(p)){o=this.getRecord(p);i=this.getTrEl(o);}else{i=this.getTrEl(p);o=this.getRecord(i);}}if(o){var n=this._aSelections||[];var m=o.getId();var l=-1;if(n.indexOf){l=n.indexOf(m);}else{for(var k=n.length-1;k>-1;k--){if(n[k]===m){l=k;break;}}}if(l>-1){n.splice(l,1);}n.push(m);this._aSelections=n;if(!this._oAnchorRecord){this._oAnchorRecord=o;}if(i){c.addClass(i,d.CLASS_SELECTED);}this.fireEvent("rowSelectEvent",{record:o,el:i});}else{}},unselectRow:function(p){var i=this.getTrEl(p);var o;if(p instanceof YAHOO.widget.Record){o=this._oRecordSet.getRecord(p);}else{if(h.isNumber(p)){o=this.getRecord(p);}else{o=this.getRecord(i);}}if(o){var n=this._aSelections||[];var m=o.getId();var l=-1;if(n.indexOf){l=n.indexOf(m);}else{for(var k=n.length-1;k>-1;k--){if(n[k]===m){l=k;break;}}}if(l>-1){n.splice(l,1);this._aSelections=n;c.removeClass(i,d.CLASS_SELECTED);this.fireEvent("rowUnselectEvent",{record:o,el:i});return;}}},unselectAllRows:function(){var k=this._aSelections||[],m,l=[];for(var i=k.length-1;i>-1;i--){if(h.isString(k[i])){m=k.splice(i,1);l[l.length]=this.getRecord(h.isArray(m)?m[0]:m);}}this._aSelections=k;this._unselectAllTrEls();this.fireEvent("unselectAllRowsEvent",{records:l});},_unselectAllTdEls:function(){var i=c.getElementsByClassName(d.CLASS_SELECTED,"td",this._elTbody);c.removeClass(i,d.CLASS_SELECTED);},getSelectedTdEls:function(){return c.getElementsByClassName(d.CLASS_SELECTED,"td",this._elTbody);},selectCell:function(i){var p=this.getTdEl(i);if(p){var o=this.getRecord(p);var q=this.getColumn(this.getCellIndex(p));var m=q.getKey();if(o&&m){var n=this._aSelections||[];var l=o.getId();for(var k=n.length-1;k>-1;k--){if((n[k].recordId===l)&&(n[k].columnKey===m)){n.splice(k,1);break;}}n.push({recordId:l,columnKey:m});this._aSelections=n;if(!this._oAnchorCell){this._oAnchorCell={record:o,column:q};}c.addClass(p,d.CLASS_SELECTED);this.fireEvent("cellSelectEvent",{record:o,column:q,key:m,el:p});return;}}},unselectCell:function(i){var o=this.getTdEl(i);if(o){var n=this.getRecord(o);var p=this.getColumn(this.getCellIndex(o));var l=p.getKey();if(n&&l){var m=this._aSelections||[];var q=n.getId();for(var k=m.length-1;k>-1;k--){if((m[k].recordId===q)&&(m[k].columnKey===l)){m.splice(k,1);this._aSelections=m;c.removeClass(o,d.CLASS_SELECTED);this.fireEvent("cellUnselectEvent",{record:n,column:p,key:l,el:o});return;}}}}},unselectAllCells:function(){var k=this._aSelections||[];for(var i=k.length-1;i>-1;i--){if(h.isObject(k[i])){k.splice(i,1);}}this._aSelections=k;this._unselectAllTdEls();this.fireEvent("unselectAllCellsEvent");},isSelected:function(p){if(p&&(p.ownerDocument==document)){return(c.hasClass(this.getTdEl(p),d.CLASS_SELECTED)||c.hasClass(this.getTrEl(p),d.CLASS_SELECTED));}else{var n,k,i;var m=this._aSelections;if(m&&m.length>0){if(p instanceof YAHOO.widget.Record){n=p;}else{if(h.isNumber(p)){n=this.getRecord(p);}}if(n){k=n.getId();if(m.indexOf){if(m.indexOf(k)>-1){return true;}}else{for(i=m.length-1;i>-1;i--){if(m[i]===k){return true;}}}}else{if(p.record&&p.column){k=p.record.getId();var l=p.column.getKey();for(i=m.length-1;i>-1;i--){if((m[i].recordId===k)&&(m[i].columnKey===l)){return true;}}}}}}return false;},getSelectedRows:function(){var i=[];var l=this._aSelections||[];for(var k=0;k<l.length;k++){if(h.isString(l[k])){i.push(l[k]);}}return i;},getSelectedCells:function(){var k=[];var l=this._aSelections||[];for(var i=0;i<l.length;i++){if(l[i]&&h.isObject(l[i])){k.push(l[i]);}}return k;},getLastSelectedRecord:function(){var k=this._aSelections;if(k&&k.length>0){for(var j=k.length-1;j>-1;j--){if(h.isString(k[j])){return k[j];}}}},getLastSelectedCell:function(){var k=this._aSelections;if(k&&k.length>0){for(var j=k.length-1;j>-1;j--){if(k[j].recordId&&k[j].columnKey){return k[j];}}}},highlightRow:function(k){var i=this.getTrEl(k);if(i){var j=this.getRecord(i);c.addClass(i,d.CLASS_HIGHLIGHTED);this.fireEvent("rowHighlightEvent",{record:j,el:i});return;}},unhighlightRow:function(k){var i=this.getTrEl(k);if(i){var j=this.getRecord(i);c.removeClass(i,d.CLASS_HIGHLIGHTED);this.fireEvent("rowUnhighlightEvent",{record:j,el:i});return;}},highlightCell:function(i){var l=this.getTdEl(i);if(l){if(this._elLastHighlightedTd){this.unhighlightCell(this._elLastHighlightedTd);}var k=this.getRecord(l);var m=this.getColumn(this.getCellIndex(l));var j=m.getKey();c.addClass(l,d.CLASS_HIGHLIGHTED);this._elLastHighlightedTd=l;this.fireEvent("cellHighlightEvent",{record:k,column:m,key:j,el:l});return;}},unhighlightCell:function(i){var k=this.getTdEl(i);if(k){var j=this.getRecord(k);c.removeClass(k,d.CLASS_HIGHLIGHTED);this._elLastHighlightedTd=null;this.fireEvent("cellUnhighlightEvent",{record:j,column:this.getColumn(this.getCellIndex(k)),key:this.getColumn(this.getCellIndex(k)).getKey(),el:k});
+return;}},addCellEditor:function(j,i){j.editor=i;j.editor.subscribe("showEvent",this._onEditorShowEvent,this,true);j.editor.subscribe("keydownEvent",this._onEditorKeydownEvent,this,true);j.editor.subscribe("revertEvent",this._onEditorRevertEvent,this,true);j.editor.subscribe("saveEvent",this._onEditorSaveEvent,this,true);j.editor.subscribe("cancelEvent",this._onEditorCancelEvent,this,true);j.editor.subscribe("blurEvent",this._onEditorBlurEvent,this,true);j.editor.subscribe("blockEvent",this._onEditorBlockEvent,this,true);j.editor.subscribe("unblockEvent",this._onEditorUnblockEvent,this,true);},getCellEditor:function(){return this._oCellEditor;},showCellEditor:function(p,q,l){p=this.getTdEl(p);if(p){l=this.getColumn(p);if(l&&l.editor){var j=this._oCellEditor;if(j){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{if(j.isActive){this.cancelCellEditor();}}}if(l.editor instanceof YAHOO.widget.BaseCellEditor){j=l.editor;var n=j.attach(this,p);if(n){j.render();j.move();n=this.doBeforeShowCellEditor(j);if(n){j.show();this._oCellEditor=j;}}}else{if(!q||!(q instanceof YAHOO.widget.Record)){q=this.getRecord(p);}if(!l||!(l instanceof YAHOO.widget.Column)){l=this.getColumn(p);}if(q&&l){if(!this._oCellEditor||this._oCellEditor.container){this._initCellEditorEl();}j=this._oCellEditor;j.cell=p;j.record=q;j.column=l;j.validator=(l.editorOptions&&h.isFunction(l.editorOptions.validator))?l.editorOptions.validator:null;j.value=q.getData(l.key);j.defaultValue=null;var k=j.container;var o=c.getX(p);var m=c.getY(p);if(isNaN(o)||isNaN(m)){o=p.offsetLeft+c.getX(this._elTbody.parentNode)-this._elTbody.scrollLeft;m=p.offsetTop+c.getY(this._elTbody.parentNode)-this._elTbody.scrollTop+this._elThead.offsetHeight;}k.style.left=o+"px";k.style.top=m+"px";this.doBeforeShowCellEditor(this._oCellEditor);k.style.display="";g.addListener(k,"keydown",function(s,r){if((s.keyCode==27)){r.cancelCellEditor();r.focusTbodyEl();}else{r.fireEvent("editorKeydownEvent",{editor:r._oCellEditor,event:s});}},this);var i;if(h.isString(l.editor)){switch(l.editor){case"checkbox":i=d.editCheckbox;break;case"date":i=d.editDate;break;case"dropdown":i=d.editDropdown;break;case"radio":i=d.editRadio;break;case"textarea":i=d.editTextarea;break;case"textbox":i=d.editTextbox;break;default:i=null;}}else{if(h.isFunction(l.editor)){i=l.editor;}}if(i){i(this._oCellEditor,this);if(!l.editorOptions||!l.editorOptions.disableBtns){this.showCellEditorBtns(k);}j.isActive=true;this.fireEvent("editorShowEvent",{editor:j});return;}}}}}},_initCellEditorEl:function(){var i=document.createElement("div");i.id=this._sId+"-celleditor";i.style.display="none";i.tabIndex=0;c.addClass(i,d.CLASS_EDITOR);var k=c.getFirstChild(document.body);if(k){i=c.insertBefore(i,k);}else{i=document.body.appendChild(i);}var j={};j.container=i;j.value=null;j.isActive=false;this._oCellEditor=j;},doBeforeShowCellEditor:function(i){return true;},saveCellEditor:function(){if(this._oCellEditor){if(this._oCellEditor.save){this._oCellEditor.save();}else{if(this._oCellEditor.isActive){var i=this._oCellEditor.value;var j=this._oCellEditor.record.getData(this._oCellEditor.column.key);if(this._oCellEditor.validator){i=this._oCellEditor.value=this._oCellEditor.validator.call(this,i,j,this._oCellEditor);if(i===null){this.resetCellEditor();this.fireEvent("editorRevertEvent",{editor:this._oCellEditor,oldData:j,newData:i});return;}}this._oRecordSet.updateRecordValue(this._oCellEditor.record,this._oCellEditor.column.key,this._oCellEditor.value);this.formatCell(this._oCellEditor.cell.firstChild,this._oCellEditor.record,this._oCellEditor.column);this._oChainRender.add({method:function(){this.validateColumnWidths();},scope:this});this._oChainRender.run();this.resetCellEditor();this.fireEvent("editorSaveEvent",{editor:this._oCellEditor,oldData:j,newData:i});}}}},cancelCellEditor:function(){if(this._oCellEditor){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{if(this._oCellEditor.isActive){this.resetCellEditor();this.fireEvent("editorCancelEvent",{editor:this._oCellEditor});}}}},destroyCellEditor:function(){if(this._oCellEditor){this._oCellEditor.destroy();this._oCellEditor=null;}},_onEditorShowEvent:function(i){this.fireEvent("editorShowEvent",i);},_onEditorKeydownEvent:function(i){this.fireEvent("editorKeydownEvent",i);},_onEditorRevertEvent:function(i){this.fireEvent("editorRevertEvent",i);},_onEditorSaveEvent:function(i){this.fireEvent("editorSaveEvent",i);},_onEditorCancelEvent:function(i){this.fireEvent("editorCancelEvent",i);},_onEditorBlurEvent:function(i){this.fireEvent("editorBlurEvent",i);},_onEditorBlockEvent:function(i){this.fireEvent("editorBlockEvent",i);},_onEditorUnblockEvent:function(i){this.fireEvent("editorUnblockEvent",i);},onEditorBlurEvent:function(i){if(i.editor.disableBtns){if(i.editor.save){i.editor.save();}}else{if(i.editor.cancel){i.editor.cancel();}}},onEditorBlockEvent:function(i){this.disable();},onEditorUnblockEvent:function(i){this.undisable();},doBeforeLoadData:function(i,j,k){return true;},onEventSortColumn:function(k){var i=k.event;var m=k.target;var j=this.getThEl(m)||this.getTdEl(m);if(j){var l=this.getColumn(j);if(l.sortable){g.stopEvent(i);this.sortColumn(l);}}else{}},onEventSelectColumn:function(i){this.selectColumn(i.target);},onEventHighlightColumn:function(i){this.highlightColumn(i.target);},onEventUnhighlightColumn:function(i){this.unhighlightColumn(i.target);},onEventSelectRow:function(j){var i=this.get("selectionMode");if(i=="single"){this._handleSingleSelectionByMouse(j);}else{this._handleStandardSelectionByMouse(j);}},onEventSelectCell:function(j){var i=this.get("selectionMode");if(i=="cellblock"){this._handleCellBlockSelectionByMouse(j);}else{if(i=="cellrange"){this._handleCellRangeSelectionByMouse(j);}else{this._handleSingleCellSelectionByMouse(j);}}},onEventHighlightRow:function(i){this.highlightRow(i.target);},onEventUnhighlightRow:function(i){this.unhighlightRow(i.target);},onEventHighlightCell:function(i){this.highlightCell(i.target);
+},onEventUnhighlightCell:function(i){this.unhighlightCell(i.target);},onEventFormatCell:function(i){var l=i.target;var j=this.getTdEl(l);if(j){var k=this.getColumn(this.getCellIndex(j));this.formatCell(j.firstChild,this.getRecord(j),k);}else{}},onEventShowCellEditor:function(i){if(!this.isDisabled()){this.showCellEditor(i.target);}},onEventSaveCellEditor:function(i){if(this._oCellEditor){if(this._oCellEditor.save){this._oCellEditor.save();}else{this.saveCellEditor();}}},onEventCancelCellEditor:function(i){if(this._oCellEditor){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{this.cancelCellEditor();}}},onDataReturnInitializeTable:function(i,j,k){if((this instanceof d)&&this._sId){this.initializeTable();this.onDataReturnSetRows(i,j,k);}},onDataReturnReplaceRows:function(m,l,n){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:m,response:l,payload:n});var j=this.doBeforeLoadData(m,l,n),k=this.get("paginator"),i=0;if(j&&l&&!l.error&&h.isArray(l.results)){this._oRecordSet.reset();if(this.get("dynamicData")){if(n&&n.pagination&&h.isNumber(n.pagination.recordOffset)){i=n.pagination.recordOffset;}else{if(k){i=k.getStartIndex();}}}this._oRecordSet.setRecords(l.results,i|0);this._handleDataReturnPayload(m,l,n);this.render();}else{if(j&&l.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnAppendRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.addRows(k.results);this._handleDataReturnPayload(j,k,l);}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnInsertRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.addRows(k.results,(l?l.insertIndex:0));this._handleDataReturnPayload(j,k,l);}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnUpdateRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.updateRows((l?l.updateIndex:0),k.results);this._handleDataReturnPayload(j,k,l);}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnSetRows:function(m,l,n){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:m,response:l,payload:n});var j=this.doBeforeLoadData(m,l,n),k=this.get("paginator"),i=0;if(j&&l&&!l.error&&h.isArray(l.results)){if(this.get("dynamicData")){if(n&&n.pagination&&h.isNumber(n.pagination.recordOffset)){i=n.pagination.recordOffset;}else{if(k){i=k.getStartIndex();}}this._oRecordSet.reset();}this._oRecordSet.setRecords(l.results,i|0);this._handleDataReturnPayload(m,l,n);this.render();}else{if(j&&l.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}else{}},handleDataReturnPayload:function(j,i,k){return k||{};},_handleDataReturnPayload:function(k,j,l){l=this.handleDataReturnPayload(k,j,l);if(l){var i=this.get("paginator");if(i){if(this.get("dynamicData")){if(e.Paginator.isNumeric(l.totalRecords)){i.set("totalRecords",l.totalRecords);}}else{i.set("totalRecords",this._oRecordSet.getLength());}if(h.isObject(l.pagination)){i.set("rowsPerPage",l.pagination.rowsPerPage);i.set("recordOffset",l.pagination.recordOffset);}}if(l.sortedBy){this.set("sortedBy",l.sortedBy);}else{if(l.sorting){this.set("sortedBy",l.sorting);}}}},showCellEditorBtns:function(k){var l=k.appendChild(document.createElement("div"));c.addClass(l,d.CLASS_BUTTON);var j=l.appendChild(document.createElement("button"));c.addClass(j,d.CLASS_DEFAULT);j.innerHTML="OK";g.addListener(j,"click",function(n,m){m.onEventSaveCellEditor(n,m);m.focusTbodyEl();},this,true);var i=l.appendChild(document.createElement("button"));i.innerHTML="Cancel";g.addListener(i,"click",function(n,m){m.onEventCancelCellEditor(n,m);m.focusTbodyEl();},this,true);},resetCellEditor:function(){var i=this._oCellEditor.container;i.style.display="none";g.purgeElement(i,true);i.innerHTML="";this._oCellEditor.value=null;this._oCellEditor.isActive=false;},getBody:function(){return this.getTbodyEl();},getCell:function(i){return this.getTdEl(i);},getRow:function(i){return this.getTrEl(i);},refreshView:function(){this.render();},select:function(k){if(!h.isArray(k)){k=[k];}for(var j=0;j<k.length;j++){this.selectRow(k[j]);}},onEventEditCell:function(i){this.onEventShowCellEditor(i);},_syncColWidths:function(){this.validateColumnWidths();}});d.prototype.onDataReturnSetRecords=d.prototype.onDataReturnSetRows;d.prototype.onPaginatorChange=d.prototype.onPaginatorChangeRequest;d.editCheckbox=function(){};d.editDate=function(){};d.editDropdown=function(){};d.editRadio=function(){};d.editTextarea=function(){};d.editTextbox=function(){};})();(function(){var c=YAHOO.lang,f=YAHOO.util,e=YAHOO.widget,a=YAHOO.env.ua,d=f.Dom,j=f.Event,i=f.DataSourceBase,g=e.DataTable,b=e.Paginator;e.ScrollingDataTable=function(n,m,k,l){l=l||{};if(l.scrollable){l.scrollable=false;}this._init();e.ScrollingDataTable.superclass.constructor.call(this,n,m,k,l);this.subscribe("columnShowEvent",this._onColumnChange);};var h=e.ScrollingDataTable;c.augmentObject(h,{CLASS_HEADER:"yui-dt-hd",CLASS_BODY:"yui-dt-bd"});c.extend(h,g,{_elHdContainer:null,_elHdTable:null,_elBdContainer:null,_elBdThead:null,_elTmpContainer:null,_elTmpTable:null,_bScrollbarX:null,initAttributes:function(k){k=k||{};h.superclass.initAttributes.call(this,k);this.setAttributeConfig("width",{value:null,validator:c.isString,method:function(l){if(this._elHdContainer&&this._elBdContainer){this._elHdContainer.style.width=l;this._elBdContainer.style.width=l;this._syncScrollX();this._syncScrollOverhang();}}});this.setAttributeConfig("height",{value:null,validator:c.isString,method:function(l){if(this._elHdContainer&&this._elBdContainer){this._elBdContainer.style.height=l;
+this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();}}});this.setAttributeConfig("COLOR_COLUMNFILLER",{value:"#F2F2F2",validator:c.isString,method:function(l){if(this._elHdContainer){this._elHdContainer.style.backgroundColor=l;}}});},_init:function(){this._elHdContainer=null;this._elHdTable=null;this._elBdContainer=null;this._elBdThead=null;this._elTmpContainer=null;this._elTmpTable=null;},_initDomElements:function(k){this._initContainerEl(k);if(this._elContainer&&this._elHdContainer&&this._elBdContainer){this._initTableEl();if(this._elHdTable&&this._elTable){this._initColgroupEl(this._elHdTable);this._initTheadEl(this._elHdTable,this._elTable);this._initTbodyEl(this._elTable);this._initMsgTbodyEl(this._elTable);}}if(!this._elContainer||!this._elTable||!this._elColgroup||!this._elThead||!this._elTbody||!this._elMsgTbody||!this._elHdTable||!this._elBdThead){return false;}else{return true;}},_destroyContainerEl:function(k){d.removeClass(k,g.CLASS_SCROLLABLE);h.superclass._destroyContainerEl.call(this,k);this._elHdContainer=null;this._elBdContainer=null;},_initContainerEl:function(l){h.superclass._initContainerEl.call(this,l);if(this._elContainer){l=this._elContainer;d.addClass(l,g.CLASS_SCROLLABLE);var k=document.createElement("div");k.style.width=this.get("width")||"";k.style.backgroundColor=this.get("COLOR_COLUMNFILLER");d.addClass(k,h.CLASS_HEADER);this._elHdContainer=k;l.appendChild(k);var m=document.createElement("div");m.style.width=this.get("width")||"";m.style.height=this.get("height")||"";d.addClass(m,h.CLASS_BODY);j.addListener(m,"scroll",this._onScroll,this);this._elBdContainer=m;l.appendChild(m);}},_initCaptionEl:function(k){},_destroyHdTableEl:function(){var k=this._elHdTable;if(k){j.purgeElement(k,true);k.parentNode.removeChild(k);this._elBdThead=null;}},_initTableEl:function(){if(this._elHdContainer){this._destroyHdTableEl();this._elHdTable=this._elHdContainer.appendChild(document.createElement("table"));j.delegate(this._elHdTable,"mouseenter",this._onTableMouseover,"thead ."+g.CLASS_LABEL,this);j.delegate(this._elHdTable,"mouseleave",this._onTableMouseout,"thead ."+g.CLASS_LABEL,this);}h.superclass._initTableEl.call(this,this._elBdContainer);},_initTheadEl:function(l,k){l=l||this._elHdTable;k=k||this._elTable;this._initBdTheadEl(k);h.superclass._initTheadEl.call(this,l);},_initThEl:function(l,k){h.superclass._initThEl.call(this,l,k);l.id=this.getId()+"-fixedth-"+k.getSanitizedKey();},_destroyBdTheadEl:function(){var k=this._elBdThead;if(k){var l=k.parentNode;j.purgeElement(k,true);l.removeChild(k);this._elBdThead=null;this._destroyColumnHelpers();}},_initBdTheadEl:function(t){if(t){this._destroyBdTheadEl();var p=t.insertBefore(document.createElement("thead"),t.firstChild);var v=this._oColumnSet,u=v.tree,o,l,s,q,n,m,r;for(q=0,m=u.length;q<m;q++){l=p.appendChild(document.createElement("tr"));for(n=0,r=u[q].length;n<r;n++){s=u[q][n];o=l.appendChild(document.createElement("th"));this._initBdThEl(o,s,q,n);}}this._elBdThead=p;}},_initBdThEl:function(n,m){n.id=this.getId()+"-th-"+m.getSanitizedKey();n.rowSpan=m.getRowspan();n.colSpan=m.getColspan();if(m.abbr){n.abbr=m.abbr;}var l=m.getKey();var k=c.isValue(m.label)?m.label:l;n.innerHTML=k;},_initTbodyEl:function(k){h.superclass._initTbodyEl.call(this,k);k.style.marginTop=(this._elTbody.offsetTop>0)?"-"+this._elTbody.offsetTop+"px":0;},_focusEl:function(l){l=l||this._elTbody;var k=this;this._storeScrollPositions();setTimeout(function(){setTimeout(function(){try{l.focus();k._restoreScrollPositions();}catch(m){}},0);},0);},_runRenderChain:function(){this._storeScrollPositions();this._oChainRender.run();},_storeScrollPositions:function(){this._nScrollTop=this._elBdContainer.scrollTop;this._nScrollLeft=this._elBdContainer.scrollLeft;},clearScrollPositions:function(){this._nScrollTop=0;this._nScrollLeft=0;},_restoreScrollPositions:function(){if(this._nScrollTop){this._elBdContainer.scrollTop=this._nScrollTop;this._nScrollTop=null;}if(this._nScrollLeft){this._elBdContainer.scrollLeft=this._nScrollLeft;this._elHdContainer.scrollLeft=this._nScrollLeft;this._nScrollLeft=null;}},_validateColumnWidth:function(n,k){if(!n.width&&!n.hidden){var p=n.getThEl();if(n._calculatedWidth){this._setColumnWidth(n,"auto","visible");}if(p.offsetWidth!==k.offsetWidth){var m=(p.offsetWidth>k.offsetWidth)?n.getThLinerEl():k.firstChild;var l=Math.max(0,(m.offsetWidth-(parseInt(d.getStyle(m,"paddingLeft"),10)|0)-(parseInt(d.getStyle(m,"paddingRight"),10)|0)),n.minWidth);var o="visible";if((n.maxAutoWidth>0)&&(l>n.maxAutoWidth)){l=n.maxAutoWidth;o="hidden";}this._elTbody.style.display="none";this._setColumnWidth(n,l+"px",o);n._calculatedWidth=l;this._elTbody.style.display="";}}},validateColumnWidths:function(s){var u=this._oColumnSet.keys,w=u.length,l=this.getFirstTrEl();if(a.ie){this._setOverhangValue(1);}if(u&&l&&(l.childNodes.length===w)){var m=this.get("width");if(m){this._elHdContainer.style.width="";this._elBdContainer.style.width="";}this._elContainer.style.width="";if(s&&c.isNumber(s.getKeyIndex())){this._validateColumnWidth(s,l.childNodes[s.getKeyIndex()]);}else{var t,k=[],o,q,r;for(q=0;q<w;q++){s=u[q];if(!s.width&&!s.hidden&&s._calculatedWidth){k[k.length]=s;}}this._elTbody.style.display="none";for(q=0,r=k.length;q<r;q++){this._setColumnWidth(k[q],"auto","visible");}this._elTbody.style.display="";k=[];for(q=0;q<w;q++){s=u[q];t=l.childNodes[q];if(!s.width&&!s.hidden){var n=s.getThEl();if(n.offsetWidth!==t.offsetWidth){var v=(n.offsetWidth>t.offsetWidth)?s.getThLinerEl():t.firstChild;var p=Math.max(0,(v.offsetWidth-(parseInt(d.getStyle(v,"paddingLeft"),10)|0)-(parseInt(d.getStyle(v,"paddingRight"),10)|0)),s.minWidth);var x="visible";if((s.maxAutoWidth>0)&&(p>s.maxAutoWidth)){p=s.maxAutoWidth;x="hidden";}k[k.length]=[s,p,x];}}}this._elTbody.style.display="none";for(q=0,r=k.length;q<r;q++){o=k[q];this._setColumnWidth(o[0],o[1]+"px",o[2]);o[0]._calculatedWidth=o[1];}this._elTbody.style.display="";}if(m){this._elHdContainer.style.width=m;this._elBdContainer.style.width=m;
+}}this._syncScroll();this._restoreScrollPositions();},_syncScroll:function(){this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();if(a.opera){this._elHdContainer.scrollLeft=this._elBdContainer.scrollLeft;if(!this.get("width")){document.body.style+="";}}},_syncScrollY:function(){var k=this._elTbody,l=this._elBdContainer;if(!this.get("width")){this._elContainer.style.width=(l.scrollHeight>l.clientHeight)?(k.parentNode.clientWidth+19)+"px":(k.parentNode.clientWidth+2)+"px";}},_syncScrollX:function(){var k=this._elTbody,l=this._elBdContainer;if(!this.get("height")&&(a.ie)){l.style.height=(l.scrollWidth>l.offsetWidth)?(k.parentNode.offsetHeight+18)+"px":k.parentNode.offsetHeight+"px";}if(this._elTbody.rows.length===0){this._elMsgTbody.parentNode.style.width=this.getTheadEl().parentNode.offsetWidth+"px";}else{this._elMsgTbody.parentNode.style.width="";}},_syncScrollOverhang:function(){var l=this._elBdContainer,k=1;if((l.scrollHeight>l.clientHeight)&&(l.scrollWidth>l.clientWidth)){k=18;}this._setOverhangValue(k);},_setOverhangValue:function(n){var p=this._oColumnSet.headers[this._oColumnSet.headers.length-1]||[],l=p.length,k=this._sId+"-fixedth-",o=n+"px solid "+this.get("COLOR_COLUMNFILLER");this._elThead.style.display="none";for(var m=0;m<l;m++){d.get(k+p[m]).style.borderRight=o;}this._elThead.style.display="";},getHdContainerEl:function(){return this._elHdContainer;},getBdContainerEl:function(){return this._elBdContainer;},getHdTableEl:function(){return this._elHdTable;},getBdTableEl:function(){return this._elTable;},disable:function(){var k=this._elMask;k.style.width=this._elBdContainer.offsetWidth+"px";k.style.height=this._elHdContainer.offsetHeight+this._elBdContainer.offsetHeight+"px";k.style.display="";this.fireEvent("disableEvent");},removeColumn:function(m){var k=this._elHdContainer.scrollLeft;var l=this._elBdContainer.scrollLeft;m=h.superclass.removeColumn.call(this,m);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=l;return m;},insertColumn:function(n,l){var k=this._elHdContainer.scrollLeft;var m=this._elBdContainer.scrollLeft;var o=h.superclass.insertColumn.call(this,n,l);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=m;return o;},reorderColumn:function(n,l){var k=this._elHdContainer.scrollLeft;var m=this._elBdContainer.scrollLeft;var o=h.superclass.reorderColumn.call(this,n,l);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=m;return o;},setColumnWidth:function(l,k){l=this.getColumn(l);if(l){this._storeScrollPositions();if(c.isNumber(k)){k=(k>l.minWidth)?k:l.minWidth;l.width=k;this._setColumnWidth(l,k+"px");this._syncScroll();this.fireEvent("columnSetWidthEvent",{column:l,width:k});}else{if(k===null){l.width=k;this._setColumnWidth(l,"auto");this.validateColumnWidths(l);this.fireEvent("columnUnsetWidthEvent",{column:l});}}this._clearTrTemplateEl();}else{}},scrollTo:function(m){var l=this.getTdEl(m);if(l){this.clearScrollPositions();this.getBdContainerEl().scrollLeft=l.offsetLeft;this.getBdContainerEl().scrollTop=l.parentNode.offsetTop;}else{var k=this.getTrEl(m);if(k){this.clearScrollPositions();this.getBdContainerEl().scrollTop=k.offsetTop;}}},showTableMessage:function(o,k){var p=this._elMsgTd;if(c.isString(o)){p.firstChild.innerHTML=o;}if(c.isString(k)){d.addClass(p.firstChild,k);}var n=this.getTheadEl();var l=n.parentNode;var m=l.offsetWidth;this._elMsgTbody.parentNode.style.width=this.getTheadEl().parentNode.offsetWidth+"px";this._elMsgTbody.style.display="";this.fireEvent("tableMsgShowEvent",{html:o,className:k});},_onColumnChange:function(k){var l=(k.column)?k.column:(k.editor)?k.editor.column:null;this._storeScrollPositions();this.validateColumnWidths(l);},_onScroll:function(m,l){l._elHdContainer.scrollLeft=l._elBdContainer.scrollLeft;if(l._oCellEditor&&l._oCellEditor.isActive){l.fireEvent("editorBlurEvent",{editor:l._oCellEditor});l.cancelCellEditor();}var n=j.getTarget(m);var k=n.nodeName.toLowerCase();l.fireEvent("tableScrollEvent",{event:m,target:n});},_onTheadKeydown:function(n,l){if(j.getCharCode(n)===9){setTimeout(function(){if((l instanceof h)&&l._sId){l._elBdContainer.scrollLeft=l._elHdContainer.scrollLeft;}},0);}var o=j.getTarget(n);var k=o.nodeName.toLowerCase();var m=true;while(o&&(k!="table")){switch(k){case"body":return;case"input":case"textarea":break;case"thead":m=l.fireEvent("theadKeyEvent",{target:o,event:n});break;default:break;}if(m===false){return;}else{o=o.parentNode;if(o){k=o.nodeName.toLowerCase();}}}l.fireEvent("tableKeyEvent",{target:(o||l._elContainer),event:n});}});})();(function(){var c=YAHOO.lang,f=YAHOO.util,e=YAHOO.widget,b=YAHOO.env.ua,d=f.Dom,i=f.Event,h=e.DataTable;e.BaseCellEditor=function(k,j){this._sId=this._sId||d.generateId(null,"yui-ceditor");YAHOO.widget.BaseCellEditor._nCount++;this._sType=k;this._initConfigs(j);this._initEvents();this._needsRender=true;};var a=e.BaseCellEditor;c.augmentObject(a,{_nCount:0,CLASS_CELLEDITOR:"yui-ceditor"});a.prototype={_sId:null,_sType:null,_oDataTable:null,_oColumn:null,_oRecord:null,_elTd:null,_elContainer:null,_elCancelBtn:null,_elSaveBtn:null,_initConfigs:function(k){if(k&&YAHOO.lang.isObject(k)){for(var j in k){if(j){this[j]=k[j];}}}},_initEvents:function(){this.createEvent("showEvent");this.createEvent("keydownEvent");this.createEvent("invalidDataEvent");this.createEvent("revertEvent");this.createEvent("saveEvent");this.createEvent("cancelEvent");this.createEvent("blurEvent");this.createEvent("blockEvent");this.createEvent("unblockEvent");},_initContainerEl:function(){if(this._elContainer){YAHOO.util.Event.purgeElement(this._elContainer,true);this._elContainer.innerHTML="";}var j=document.createElement("div");j.id=this.getId()+"-container";j.style.display="none";j.tabIndex=0;this.className=c.isArray(this.className)?this.className:this.className?[this.className]:[];this.className[this.className.length]=h.CLASS_EDITOR;j.className=this.className.join(" ");document.body.insertBefore(j,document.body.firstChild);this._elContainer=j;},_initShimEl:function(){if(this.useIFrame){if(!this._elIFrame){var j=document.createElement("iframe");
+j.src="javascript:false";j.frameBorder=0;j.scrolling="no";j.style.display="none";j.className=h.CLASS_EDITOR_SHIM;j.tabIndex=-1;j.role="presentation";j.title="Presentational iframe shim";document.body.insertBefore(j,document.body.firstChild);this._elIFrame=j;}}},_hide:function(){this.getContainerEl().style.display="none";if(this._elIFrame){this._elIFrame.style.display="none";}this.isActive=false;this.getDataTable()._oCellEditor=null;},asyncSubmitter:null,value:null,defaultValue:null,validator:null,resetInvalidData:true,isActive:false,LABEL_SAVE:"Save",LABEL_CANCEL:"Cancel",disableBtns:false,useIFrame:false,className:null,toString:function(){return"CellEditor instance "+this._sId;},getId:function(){return this._sId;},getDataTable:function(){return this._oDataTable;},getColumn:function(){return this._oColumn;},getRecord:function(){return this._oRecord;},getTdEl:function(){return this._elTd;},getContainerEl:function(){return this._elContainer;},destroy:function(){this.unsubscribeAll();var k=this.getColumn();if(k){k.editor=null;}var j=this.getContainerEl();if(j){i.purgeElement(j,true);j.parentNode.removeChild(j);}},render:function(){if(!this._needsRender){return;}this._initContainerEl();this._initShimEl();i.addListener(this.getContainerEl(),"keydown",function(l,j){if((l.keyCode==27)){var k=i.getTarget(l);if(k.nodeName&&k.nodeName.toLowerCase()==="select"){k.blur();}j.cancel();}j.fireEvent("keydownEvent",{editor:j,event:l});},this);this.renderForm();if(!this.disableBtns){this.renderBtns();}this.doAfterRender();this._needsRender=false;},renderBtns:function(){var l=this.getContainerEl().appendChild(document.createElement("div"));l.className=h.CLASS_BUTTON;var k=l.appendChild(document.createElement("button"));k.className=h.CLASS_DEFAULT;k.innerHTML=this.LABEL_SAVE;i.addListener(k,"click",function(m){this.save();},this,true);this._elSaveBtn=k;var j=l.appendChild(document.createElement("button"));j.innerHTML=this.LABEL_CANCEL;i.addListener(j,"click",function(m){this.cancel();},this,true);this._elCancelBtn=j;},attach:function(n,l){if(n instanceof YAHOO.widget.DataTable){this._oDataTable=n;l=n.getTdEl(l);if(l){this._elTd=l;var m=n.getColumn(l);if(m){this._oColumn=m;var j=n.getRecord(l);if(j){this._oRecord=j;var k=j.getData(this.getColumn().getField());this.value=(k!==undefined)?k:this.defaultValue;return true;}}}}return false;},move:function(){var m=this.getContainerEl(),l=this.getTdEl(),j=d.getX(l),n=d.getY(l);if(isNaN(j)||isNaN(n)){var k=this.getDataTable().getTbodyEl();j=l.offsetLeft+d.getX(k.parentNode)-k.scrollLeft;n=l.offsetTop+d.getY(k.parentNode)-k.scrollTop+this.getDataTable().getTheadEl().offsetHeight;}m.style.left=j+"px";m.style.top=n+"px";if(this._elIFrame){this._elIFrame.style.left=j+"px";this._elIFrame.style.top=n+"px";}},show:function(){var k=this.getContainerEl(),j=this._elIFrame;this.resetForm();this.isActive=true;k.style.display="";if(j){j.style.width=k.offsetWidth+"px";j.style.height=k.offsetHeight+"px";j.style.display="";}this.focus();this.fireEvent("showEvent",{editor:this});},block:function(){this.fireEvent("blockEvent",{editor:this});},unblock:function(){this.fireEvent("unblockEvent",{editor:this});},save:function(){var k=this.getInputValue();var l=k;if(this.validator){l=this.validator.call(this.getDataTable(),k,this.value,this);if(l===undefined){if(this.resetInvalidData){this.resetForm();}this.fireEvent("invalidDataEvent",{editor:this,oldData:this.value,newData:k});return;}}var m=this;var j=function(o,n){var p=m.value;if(o){m.value=n;m.getDataTable().updateCell(m.getRecord(),m.getColumn(),n);m._hide();m.fireEvent("saveEvent",{editor:m,oldData:p,newData:m.value});}else{m.resetForm();m.fireEvent("revertEvent",{editor:m,oldData:p,newData:n});}m.unblock();};this.block();if(c.isFunction(this.asyncSubmitter)){this.asyncSubmitter.call(this,j,l);}else{j(true,l);}},cancel:function(){if(this.isActive){this._hide();this.fireEvent("cancelEvent",{editor:this});}else{}},renderForm:function(){},doAfterRender:function(){},handleDisabledBtns:function(){},resetForm:function(){},focus:function(){},getInputValue:function(){}};c.augmentProto(a,f.EventProvider);e.CheckboxCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-checkboxceditor");YAHOO.widget.BaseCellEditor._nCount++;e.CheckboxCellEditor.superclass.constructor.call(this,j.type||"checkbox",j);};c.extend(e.CheckboxCellEditor,a,{checkboxOptions:null,checkboxes:null,value:null,renderForm:function(){if(c.isArray(this.checkboxOptions)){var n,o,q,l,m,k;for(m=0,k=this.checkboxOptions.length;m<k;m++){n=this.checkboxOptions[m];o=c.isValue(n.value)?n.value:n;q=this.getId()+"-chk"+m;this.getContainerEl().innerHTML+='<input type="checkbox"'+' id="'+q+'"'+' value="'+o+'" />';l=this.getContainerEl().appendChild(document.createElement("label"));l.htmlFor=q;l.innerHTML=c.isValue(n.label)?n.label:n;}var p=[];for(m=0;m<k;m++){p[p.length]=this.getContainerEl().childNodes[m*2];}this.checkboxes=p;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){i.addListener(this.getContainerEl(),"click",function(j){if(i.getTarget(j).tagName.toLowerCase()==="input"){this.save();}},this,true);},resetForm:function(){var p=c.isArray(this.value)?this.value:[this.value];for(var o=0,n=this.checkboxes.length;o<n;o++){this.checkboxes[o].checked=false;for(var m=0,l=p.length;m<l;m++){if(this.checkboxes[o].value==p[m]){this.checkboxes[o].checked=true;}}}},focus:function(){this.checkboxes[0].focus();},getInputValue:function(){var k=[];for(var m=0,l=this.checkboxes.length;m<l;m++){if(this.checkboxes[m].checked){k[k.length]=this.checkboxes[m].value;}}return k;}});c.augmentObject(e.CheckboxCellEditor,a);e.DateCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-dateceditor");YAHOO.widget.BaseCellEditor._nCount++;e.DateCellEditor.superclass.constructor.call(this,j.type||"date",j);};c.extend(e.DateCellEditor,a,{calendar:null,calendarOptions:null,defaultValue:new Date(),renderForm:function(){if(YAHOO.widget.Calendar){var k=this.getContainerEl().appendChild(document.createElement("div"));
+k.id=this.getId()+"-dateContainer";var l=new YAHOO.widget.Calendar(this.getId()+"-date",k.id,this.calendarOptions);l.render();k.style.cssFloat="none";l.hideEvent.subscribe(function(){this.cancel();},this,true);if(b.ie){var j=this.getContainerEl().appendChild(document.createElement("div"));j.style.clear="both";}this.calendar=l;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){this.calendar.selectEvent.subscribe(function(j){this.save();},this,true);},resetForm:function(){var j=this.value||(new Date());this.calendar.select(j);this.calendar.cfg.setProperty("pagedate",j,false);this.calendar.render();this.calendar.show();},focus:function(){},getInputValue:function(){return this.calendar.getSelectedDates()[0];}});c.augmentObject(e.DateCellEditor,a);e.DropdownCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-dropdownceditor");YAHOO.widget.BaseCellEditor._nCount++;e.DropdownCellEditor.superclass.constructor.call(this,j.type||"dropdown",j);};c.extend(e.DropdownCellEditor,a,{dropdownOptions:null,dropdown:null,multiple:false,size:null,renderForm:function(){var n=this.getContainerEl().appendChild(document.createElement("select"));n.style.zoom=1;if(this.multiple){n.multiple="multiple";}if(c.isNumber(this.size)){n.size=this.size;}this.dropdown=n;if(c.isArray(this.dropdownOptions)){var o,m;for(var l=0,k=this.dropdownOptions.length;l<k;l++){o=this.dropdownOptions[l];m=document.createElement("option");m.value=(c.isValue(o.value))?o.value:o;m.innerHTML=(c.isValue(o.label))?o.label:o;m=n.appendChild(m);}if(this.disableBtns){this.handleDisabledBtns();}}},handleDisabledBtns:function(){if(this.multiple){i.addListener(this.dropdown,"blur",function(j){this.save();},this,true);}else{if(!b.ie){i.addListener(this.dropdown,"change",function(j){this.save();},this,true);}else{i.addListener(this.dropdown,"blur",function(j){this.save();},this,true);i.addListener(this.dropdown,"click",function(j){this.save();},this,true);}}},resetForm:function(){var s=this.dropdown.options,p=0,o=s.length;if(c.isArray(this.value)){var l=this.value,k=0,r=l.length,q={};for(;p<o;p++){s[p].selected=false;q[s[p].value]=s[p];}for(;k<r;k++){if(q[l[k]]){q[l[k]].selected=true;}}}else{for(;p<o;p++){if(this.value==s[p].value){s[p].selected=true;}}}},focus:function(){this.getDataTable()._focusEl(this.dropdown);},getInputValue:function(){var n=this.dropdown.options;if(this.multiple){var k=[],m=0,l=n.length;for(;m<l;m++){if(n[m].selected){k.push(n[m].value);}}return k;}else{return n[n.selectedIndex].value;}}});c.augmentObject(e.DropdownCellEditor,a);e.RadioCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-radioceditor");YAHOO.widget.BaseCellEditor._nCount++;e.RadioCellEditor.superclass.constructor.call(this,j.type||"radio",j);};c.extend(e.RadioCellEditor,a,{radios:null,radioOptions:null,renderForm:function(){if(c.isArray(this.radioOptions)){var k,l,r,o;for(var n=0,p=this.radioOptions.length;n<p;n++){k=this.radioOptions[n];l=c.isValue(k.value)?k.value:k;r=this.getId()+"-radio"+n;this.getContainerEl().innerHTML+='<input type="radio"'+' name="'+this.getId()+'"'+' value="'+l+'"'+' id="'+r+'" />';o=this.getContainerEl().appendChild(document.createElement("label"));o.htmlFor=r;o.innerHTML=(c.isValue(k.label))?k.label:k;}var q=[],s;for(var m=0;m<p;m++){s=this.getContainerEl().childNodes[m*2];q[q.length]=s;}this.radios=q;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){i.addListener(this.getContainerEl(),"click",function(j){if(i.getTarget(j).tagName.toLowerCase()==="input"){this.save();}},this,true);},resetForm:function(){for(var m=0,l=this.radios.length;m<l;m++){var k=this.radios[m];if(this.value==k.value){k.checked=true;return;}}},focus:function(){for(var l=0,k=this.radios.length;l<k;l++){if(this.radios[l].checked){this.radios[l].focus();return;}}},getInputValue:function(){for(var l=0,k=this.radios.length;l<k;l++){if(this.radios[l].checked){return this.radios[l].value;}}}});c.augmentObject(e.RadioCellEditor,a);e.TextareaCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-textareaceditor");YAHOO.widget.BaseCellEditor._nCount++;e.TextareaCellEditor.superclass.constructor.call(this,j.type||"textarea",j);};c.extend(e.TextareaCellEditor,a,{textarea:null,renderForm:function(){var j=this.getContainerEl().appendChild(document.createElement("textarea"));this.textarea=j;if(this.disableBtns){this.handleDisabledBtns();}},handleDisabledBtns:function(){i.addListener(this.textarea,"blur",function(j){this.save();},this,true);},move:function(){this.textarea.style.width=this.getTdEl().offsetWidth+"px";this.textarea.style.height="3em";YAHOO.widget.TextareaCellEditor.superclass.move.call(this);},resetForm:function(){this.textarea.value=this.value;},focus:function(){this.getDataTable()._focusEl(this.textarea);this.textarea.select();},getInputValue:function(){return this.textarea.value;}});c.augmentObject(e.TextareaCellEditor,a);e.TextboxCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-textboxceditor");YAHOO.widget.BaseCellEditor._nCount++;e.TextboxCellEditor.superclass.constructor.call(this,j.type||"textbox",j);};c.extend(e.TextboxCellEditor,a,{textbox:null,renderForm:function(){var j;if(b.webkit>420){j=this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));}else{j=this.getContainerEl().appendChild(document.createElement("input"));}j.type="text";this.textbox=j;i.addListener(j,"keypress",function(k){if((k.keyCode===13)){YAHOO.util.Event.preventDefault(k);this.save();}},this,true);if(this.disableBtns){this.handleDisabledBtns();}},move:function(){this.textbox.style.width=this.getTdEl().offsetWidth+"px";e.TextboxCellEditor.superclass.move.call(this);},resetForm:function(){this.textbox.value=c.isValue(this.value)?this.value.toString():"";},focus:function(){this.getDataTable()._focusEl(this.textbox);this.textbox.select();},getInputValue:function(){return this.textbox.value;
+}});c.augmentObject(e.TextboxCellEditor,a);h.Editors={checkbox:e.CheckboxCellEditor,"date":e.DateCellEditor,dropdown:e.DropdownCellEditor,radio:e.RadioCellEditor,textarea:e.TextareaCellEditor,textbox:e.TextboxCellEditor};e.CellEditor=function(k,j){if(k&&h.Editors[k]){c.augmentObject(a,h.Editors[k]);return new h.Editors[k](j);}else{return new a(null,j);}};var g=e.CellEditor;c.augmentObject(g,a);})();YAHOO.register("datatable",YAHOO.widget.DataTable,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/datemath/datemath-min.js b/Websites/bugs.webkit.org/js/yui/datemath/datemath-min.js
new file mode 100644
index 0000000..3445b28
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/datemath/datemath-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.widget.DateMath={DAY:"D",WEEK:"W",YEAR:"Y",MONTH:"M",ONE_DAY_MS:1000*60*60*24,WEEK_ONE_JAN_DATE:1,add:function(A,D,C){var F=new Date(A.getTime());switch(D){case this.MONTH:var E=A.getMonth()+C;var B=0;if(E<0){while(E<0){E+=12;B-=1;}}else{if(E>11){while(E>11){E-=12;B+=1;}}}F.setMonth(E);F.setFullYear(A.getFullYear()+B);break;case this.DAY:this._addDays(F,C);break;case this.YEAR:F.setFullYear(A.getFullYear()+C);break;case this.WEEK:this._addDays(F,(C*7));break;}return F;},_addDays:function(D,C){if(YAHOO.env.ua.webkit&&YAHOO.env.ua.webkit<420){if(C<0){for(var B=-128;C<B;C-=B){D.setDate(D.getDate()+B);}}else{for(var A=96;C>A;C-=A){D.setDate(D.getDate()+A);}}}D.setDate(D.getDate()+C);},subtract:function(A,C,B){return this.add(A,C,(B*-1));},before:function(C,B){var A=B.getTime();if(C.getTime()<A){return true;}else{return false;}},after:function(C,B){var A=B.getTime();if(C.getTime()>A){return true;}else{return false;}},between:function(B,A,C){if(this.after(B,A)&&this.before(B,C)){return true;}else{return false;}},getJan1:function(A){return this.getDate(A,0,1);},getDayOffset:function(B,D){var C=this.getJan1(D);var A=Math.ceil((B.getTime()-C.getTime())/this.ONE_DAY_MS);return A;},getWeekNumber:function(D,B,G){B=B||0;G=G||this.WEEK_ONE_JAN_DATE;var H=this.clearTime(D),L,M;if(H.getDay()===B){L=H;}else{L=this.getFirstDayOfWeek(H,B);}var I=L.getFullYear();M=new Date(L.getTime()+6*this.ONE_DAY_MS);var F;if(I!==M.getFullYear()&&M.getDate()>=G){F=1;}else{var E=this.clearTime(this.getDate(I,0,G)),A=this.getFirstDayOfWeek(E,B);var J=Math.round((H.getTime()-A.getTime())/this.ONE_DAY_MS);var K=J%7;var C=(J-K)/7;F=C+1;}return F;},getFirstDayOfWeek:function(D,A){A=A||0;var B=D.getDay(),C=(B-A+7)%7;return this.subtract(D,this.DAY,C);},isYearOverlapWeek:function(A){var C=false;var B=this.add(A,this.DAY,6);if(B.getFullYear()!=A.getFullYear()){C=true;}return C;},isMonthOverlapWeek:function(A){var C=false;var B=this.add(A,this.DAY,6);if(B.getMonth()!=A.getMonth()){C=true;}return C;},findMonthStart:function(A){var B=this.getDate(A.getFullYear(),A.getMonth(),1);return B;},findMonthEnd:function(B){var D=this.findMonthStart(B);var C=this.add(D,this.MONTH,1);var A=this.subtract(C,this.DAY,1);return A;},clearTime:function(A){A.setHours(12,0,0,0);return A;},getDate:function(D,A,C){var B=null;if(YAHOO.lang.isUndefined(C)){C=1;}if(D>=100){B=new Date(D,A,C);}else{B=new Date();B.setFullYear(D);B.setMonth(A);B.setDate(C);B.setHours(0,0,0,0);}return B;}};YAHOO.register("datemath",YAHOO.widget.DateMath,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/dom/dom-min.js b/Websites/bugs.webkit.org/js/yui/dom/dom-min.js
new file mode 100644
index 0000000..194f1e8
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/dom/dom-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){YAHOO.env._id_counter=YAHOO.env._id_counter||0;var e=YAHOO.util,k=YAHOO.lang,L=YAHOO.env.ua,a=YAHOO.lang.trim,B={},F={},m=/^t(?:able|d|h)$/i,w=/color$/i,j=window.document,v=j.documentElement,C="ownerDocument",M="defaultView",U="documentElement",S="compatMode",z="offsetLeft",o="offsetTop",T="offsetParent",x="parentNode",K="nodeType",c="tagName",n="scrollLeft",H="scrollTop",p="getBoundingClientRect",V="getComputedStyle",y="currentStyle",l="CSS1Compat",A="BackCompat",E="class",f="className",i="",b=" ",R="(?:^|\\s)",J="(?= |$)",t="g",O="position",D="fixed",u="relative",I="left",N="top",Q="medium",P="borderLeftWidth",q="borderTopWidth",d=L.opera,h=L.webkit,g=L.gecko,s=L.ie;e.Dom={CUSTOM_ATTRIBUTES:(!v.hasAttribute)?{"for":"htmlFor","class":f}:{"htmlFor":"for","className":E},DOT_ATTRIBUTES:{checked:true},get:function(aa){var ac,X,ab,Z,W,G,Y=null;if(aa){if(typeof aa=="string"||typeof aa=="number"){ac=aa+"";aa=j.getElementById(aa);G=(aa)?aa.attributes:null;if(aa&&G&&G.id&&G.id.value===ac){return aa;}else{if(aa&&j.all){aa=null;X=j.all[ac];if(X&&X.length){for(Z=0,W=X.length;Z<W;++Z){if(X[Z].id===ac){return X[Z];}}}}}}else{if(e.Element&&aa instanceof e.Element){aa=aa.get("element");}else{if(!aa.nodeType&&"length" in aa){ab=[];for(Z=0,W=aa.length;Z<W;++Z){ab[ab.length]=e.Dom.get(aa[Z]);}aa=ab;}}}Y=aa;}return Y;},getComputedStyle:function(G,W){if(window[V]){return G[C][M][V](G,null)[W];}else{if(G[y]){return e.Dom.IE_ComputedStyle.get(G,W);}}},getStyle:function(G,W){return e.Dom.batch(G,e.Dom._getStyle,W);},_getStyle:function(){if(window[V]){return function(G,Y){Y=(Y==="float")?Y="cssFloat":e.Dom._toCamel(Y);var X=G.style[Y],W;if(!X){W=G[C][M][V](G,null);if(W){X=W[Y];}}return X;};}else{if(v[y]){return function(G,Y){var X;switch(Y){case"opacity":X=100;try{X=G.filters["DXImageTransform.Microsoft.Alpha"].opacity;}catch(Z){try{X=G.filters("alpha").opacity;}catch(W){}}return X/100;case"float":Y="styleFloat";default:Y=e.Dom._toCamel(Y);X=G[y]?G[y][Y]:null;return(G.style[Y]||X);}};}}}(),setStyle:function(G,W,X){e.Dom.batch(G,e.Dom._setStyle,{prop:W,val:X});},_setStyle:function(){if(!window.getComputedStyle&&j.documentElement.currentStyle){return function(W,G){var X=e.Dom._toCamel(G.prop),Y=G.val;if(W){switch(X){case"opacity":if(Y===""||Y===null||Y===1){W.style.removeAttribute("filter");}else{if(k.isString(W.style.filter)){W.style.filter="alpha(opacity="+Y*100+")";if(!W[y]||!W[y].hasLayout){W.style.zoom=1;}}}break;case"float":X="styleFloat";default:W.style[X]=Y;}}else{}};}else{return function(W,G){var X=e.Dom._toCamel(G.prop),Y=G.val;if(W){if(X=="float"){X="cssFloat";}W.style[X]=Y;}else{}};}}(),getXY:function(G){return e.Dom.batch(G,e.Dom._getXY);},_canPosition:function(G){return(e.Dom._getStyle(G,"display")!=="none"&&e.Dom._inDoc(G));},_getXY:function(W){var X,G,Z,ab,Y,aa,ac=Math.round,ad=false;if(e.Dom._canPosition(W)){Z=W[p]();ab=W[C];X=e.Dom.getDocumentScrollLeft(ab);G=e.Dom.getDocumentScrollTop(ab);ad=[Z[I],Z[N]];if(Y||aa){ad[0]-=aa;ad[1]-=Y;}if((G||X)){ad[0]+=X;ad[1]+=G;}ad[0]=ac(ad[0]);ad[1]=ac(ad[1]);}else{}return ad;},getX:function(G){var W=function(X){return e.Dom.getXY(X)[0];};return e.Dom.batch(G,W,e.Dom,true);},getY:function(G){var W=function(X){return e.Dom.getXY(X)[1];};return e.Dom.batch(G,W,e.Dom,true);},setXY:function(G,X,W){e.Dom.batch(G,e.Dom._setXY,{pos:X,noRetry:W});},_setXY:function(G,Z){var aa=e.Dom._getStyle(G,O),Y=e.Dom.setStyle,ad=Z.pos,W=Z.noRetry,ab=[parseInt(e.Dom.getComputedStyle(G,I),10),parseInt(e.Dom.getComputedStyle(G,N),10)],ac,X;ac=e.Dom._getXY(G);if(!ad||ac===false){return false;}if(aa=="static"){aa=u;Y(G,O,aa);}if(isNaN(ab[0])){ab[0]=(aa==u)?0:G[z];}if(isNaN(ab[1])){ab[1]=(aa==u)?0:G[o];}if(ad[0]!==null){Y(G,I,ad[0]-ac[0]+ab[0]+"px");}if(ad[1]!==null){Y(G,N,ad[1]-ac[1]+ab[1]+"px");}if(!W){X=e.Dom._getXY(G);if((ad[0]!==null&&X[0]!=ad[0])||(ad[1]!==null&&X[1]!=ad[1])){e.Dom._setXY(G,{pos:ad,noRetry:true});}}},setX:function(W,G){e.Dom.setXY(W,[G,null]);},setY:function(G,W){e.Dom.setXY(G,[null,W]);},getRegion:function(G){var W=function(X){var Y=false;if(e.Dom._canPosition(X)){Y=e.Region.getRegion(X);}else{}return Y;};return e.Dom.batch(G,W,e.Dom,true);},getClientWidth:function(){return e.Dom.getViewportWidth();},getClientHeight:function(){return e.Dom.getViewportHeight();},getElementsByClassName:function(ab,af,ac,ae,X,ad){af=af||"*";ac=(ac)?e.Dom.get(ac):null||j;if(!ac){return[];}var W=[],G=ac.getElementsByTagName(af),Z=e.Dom.hasClass;for(var Y=0,aa=G.length;Y<aa;++Y){if(Z(G[Y],ab)){W[W.length]=G[Y];}}if(ae){e.Dom.batch(W,ae,X,ad);}return W;},hasClass:function(W,G){return e.Dom.batch(W,e.Dom._hasClass,G);},_hasClass:function(X,W){var G=false,Y;if(X&&W){Y=e.Dom._getAttribute(X,f)||i;if(Y){Y=Y.replace(/\s+/g,b);}if(W.exec){G=W.test(Y);}else{G=W&&(b+Y+b).indexOf(b+W+b)>-1;}}else{}return G;},addClass:function(W,G){return e.Dom.batch(W,e.Dom._addClass,G);},_addClass:function(X,W){var G=false,Y;if(X&&W){Y=e.Dom._getAttribute(X,f)||i;if(!e.Dom._hasClass(X,W)){e.Dom.setAttribute(X,f,a(Y+b+W));G=true;}}else{}return G;},removeClass:function(W,G){return e.Dom.batch(W,e.Dom._removeClass,G);},_removeClass:function(Y,X){var W=false,aa,Z,G;if(Y&&X){aa=e.Dom._getAttribute(Y,f)||i;e.Dom.setAttribute(Y,f,aa.replace(e.Dom._getClassRegex(X),i));Z=e.Dom._getAttribute(Y,f);if(aa!==Z){e.Dom.setAttribute(Y,f,a(Z));W=true;if(e.Dom._getAttribute(Y,f)===""){G=(Y.hasAttribute&&Y.hasAttribute(E))?E:f;Y.removeAttribute(G);}}}else{}return W;},replaceClass:function(X,W,G){return e.Dom.batch(X,e.Dom._replaceClass,{from:W,to:G});},_replaceClass:function(Y,X){var W,ab,aa,G=false,Z;if(Y&&X){ab=X.from;aa=X.to;if(!aa){G=false;}else{if(!ab){G=e.Dom._addClass(Y,X.to);}else{if(ab!==aa){Z=e.Dom._getAttribute(Y,f)||i;W=(b+Z.replace(e.Dom._getClassRegex(ab),b+aa).replace(/\s+/g,b)).split(e.Dom._getClassRegex(aa));W.splice(1,0,b+aa);e.Dom.setAttribute(Y,f,a(W.join(i)));G=true;}}}}else{}return G;},generateId:function(G,X){X=X||"yui-gen";var W=function(Y){if(Y&&Y.id){return Y.id;}var Z=X+YAHOO.env._id_counter++;
+if(Y){if(Y[C]&&Y[C].getElementById(Z)){return e.Dom.generateId(Y,Z+X);}Y.id=Z;}return Z;};return e.Dom.batch(G,W,e.Dom,true)||W.apply(e.Dom,arguments);},isAncestor:function(W,X){W=e.Dom.get(W);X=e.Dom.get(X);var G=false;if((W&&X)&&(W[K]&&X[K])){if(W.contains&&W!==X){G=W.contains(X);}else{if(W.compareDocumentPosition){G=!!(W.compareDocumentPosition(X)&16);}}}else{}return G;},inDocument:function(G,W){return e.Dom._inDoc(e.Dom.get(G),W);},_inDoc:function(W,X){var G=false;if(W&&W[c]){X=X||W[C];G=e.Dom.isAncestor(X[U],W);}else{}return G;},getElementsBy:function(W,af,ab,ad,X,ac,ae){af=af||"*";ab=(ab)?e.Dom.get(ab):null||j;var aa=(ae)?null:[],G;if(ab){G=ab.getElementsByTagName(af);for(var Y=0,Z=G.length;Y<Z;++Y){if(W(G[Y])){if(ae){aa=G[Y];break;}else{aa[aa.length]=G[Y];}}}if(ad){e.Dom.batch(aa,ad,X,ac);}}return aa;},getElementBy:function(X,G,W){return e.Dom.getElementsBy(X,G,W,null,null,null,true);},batch:function(X,ab,aa,Z){var Y=[],W=(Z)?aa:null;X=(X&&(X[c]||X.item))?X:e.Dom.get(X);if(X&&ab){if(X[c]||X.length===undefined){return ab.call(W,X,aa);}for(var G=0;G<X.length;++G){Y[Y.length]=ab.call(W||X[G],X[G],aa);}}else{return false;}return Y;},getDocumentHeight:function(){var W=(j[S]!=l||h)?j.body.scrollHeight:v.scrollHeight,G=Math.max(W,e.Dom.getViewportHeight());return G;},getDocumentWidth:function(){var W=(j[S]!=l||h)?j.body.scrollWidth:v.scrollWidth,G=Math.max(W,e.Dom.getViewportWidth());return G;},getViewportHeight:function(){var G=self.innerHeight,W=j[S];if((W||s)&&!d){G=(W==l)?v.clientHeight:j.body.clientHeight;}return G;},getViewportWidth:function(){var G=self.innerWidth,W=j[S];if(W||s){G=(W==l)?v.clientWidth:j.body.clientWidth;}return G;},getAncestorBy:function(G,W){while((G=G[x])){if(e.Dom._testElement(G,W)){return G;}}return null;},getAncestorByClassName:function(W,G){W=e.Dom.get(W);if(!W){return null;}var X=function(Y){return e.Dom.hasClass(Y,G);};return e.Dom.getAncestorBy(W,X);},getAncestorByTagName:function(W,G){W=e.Dom.get(W);if(!W){return null;}var X=function(Y){return Y[c]&&Y[c].toUpperCase()==G.toUpperCase();};return e.Dom.getAncestorBy(W,X);},getPreviousSiblingBy:function(G,W){while(G){G=G.previousSibling;if(e.Dom._testElement(G,W)){return G;}}return null;},getPreviousSibling:function(G){G=e.Dom.get(G);if(!G){return null;}return e.Dom.getPreviousSiblingBy(G);},getNextSiblingBy:function(G,W){while(G){G=G.nextSibling;if(e.Dom._testElement(G,W)){return G;}}return null;},getNextSibling:function(G){G=e.Dom.get(G);if(!G){return null;}return e.Dom.getNextSiblingBy(G);},getFirstChildBy:function(G,X){var W=(e.Dom._testElement(G.firstChild,X))?G.firstChild:null;return W||e.Dom.getNextSiblingBy(G.firstChild,X);},getFirstChild:function(G,W){G=e.Dom.get(G);if(!G){return null;}return e.Dom.getFirstChildBy(G);},getLastChildBy:function(G,X){if(!G){return null;}var W=(e.Dom._testElement(G.lastChild,X))?G.lastChild:null;return W||e.Dom.getPreviousSiblingBy(G.lastChild,X);},getLastChild:function(G){G=e.Dom.get(G);return e.Dom.getLastChildBy(G);},getChildrenBy:function(W,Y){var X=e.Dom.getFirstChildBy(W,Y),G=X?[X]:[];e.Dom.getNextSiblingBy(X,function(Z){if(!Y||Y(Z)){G[G.length]=Z;}return false;});return G;},getChildren:function(G){G=e.Dom.get(G);if(!G){}return e.Dom.getChildrenBy(G);},getDocumentScrollLeft:function(G){G=G||j;return Math.max(G[U].scrollLeft,G.body.scrollLeft);},getDocumentScrollTop:function(G){G=G||j;return Math.max(G[U].scrollTop,G.body.scrollTop);},insertBefore:function(W,G){W=e.Dom.get(W);G=e.Dom.get(G);if(!W||!G||!G[x]){return null;}return G[x].insertBefore(W,G);},insertAfter:function(W,G){W=e.Dom.get(W);G=e.Dom.get(G);if(!W||!G||!G[x]){return null;}if(G.nextSibling){return G[x].insertBefore(W,G.nextSibling);}else{return G[x].appendChild(W);}},getClientRegion:function(){var X=e.Dom.getDocumentScrollTop(),W=e.Dom.getDocumentScrollLeft(),Y=e.Dom.getViewportWidth()+W,G=e.Dom.getViewportHeight()+X;return new e.Region(X,Y,G,W);},setAttribute:function(W,G,X){e.Dom.batch(W,e.Dom._setAttribute,{attr:G,val:X});},_setAttribute:function(X,W){var G=e.Dom._toCamel(W.attr),Y=W.val;if(X&&X.setAttribute){if(e.Dom.DOT_ATTRIBUTES[G]&&X.tagName&&X.tagName!="BUTTON"){X[G]=Y;}else{G=e.Dom.CUSTOM_ATTRIBUTES[G]||G;X.setAttribute(G,Y);}}else{}},getAttribute:function(W,G){return e.Dom.batch(W,e.Dom._getAttribute,G);},_getAttribute:function(W,G){var X;G=e.Dom.CUSTOM_ATTRIBUTES[G]||G;if(e.Dom.DOT_ATTRIBUTES[G]){X=W[G];}else{if(W&&"getAttribute" in W){if(/^(?:href|src)$/.test(G)){X=W.getAttribute(G,2);}else{X=W.getAttribute(G);}}else{}}return X;},_toCamel:function(W){var X=B;function G(Y,Z){return Z.toUpperCase();}return X[W]||(X[W]=W.indexOf("-")===-1?W:W.replace(/-([a-z])/gi,G));},_getClassRegex:function(W){var G;if(W!==undefined){if(W.exec){G=W;}else{G=F[W];if(!G){W=W.replace(e.Dom._patterns.CLASS_RE_TOKENS,"\\$1");W=W.replace(/\s+/g,b);G=F[W]=new RegExp(R+W+J,t);}}}return G;},_patterns:{ROOT_TAG:/^body|html$/i,CLASS_RE_TOKENS:/([\.\(\)\^\$\*\+\?\|\[\]\{\}\\])/g},_testElement:function(G,W){return G&&G[K]==1&&(!W||W(G));},_calcBorders:function(X,Y){var W=parseInt(e.Dom[V](X,q),10)||0,G=parseInt(e.Dom[V](X,P),10)||0;if(g){if(m.test(X[c])){W=0;G=0;}}Y[0]+=G;Y[1]+=W;return Y;}};var r=e.Dom[V];if(L.opera){e.Dom[V]=function(W,G){var X=r(W,G);if(w.test(G)){X=e.Dom.Color.toRGB(X);}return X;};}if(L.webkit){e.Dom[V]=function(W,G){var X=r(W,G);if(X==="rgba(0, 0, 0, 0)"){X="transparent";}return X;};}if(L.ie&&L.ie>=8){e.Dom.DOT_ATTRIBUTES.type=true;}})();YAHOO.util.Region=function(d,e,a,c){this.top=d;this.y=d;this[1]=d;this.right=e;this.bottom=a;this.left=c;this.x=c;this[0]=c;this.width=this.right-this.left;this.height=this.bottom-this.top;};YAHOO.util.Region.prototype.contains=function(a){return(a.left>=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(f){var d=Math.max(this.top,f.top),e=Math.min(this.right,f.right),a=Math.min(this.bottom,f.bottom),c=Math.max(this.left,f.left);
+if(a>=d&&e>=c){return new YAHOO.util.Region(d,e,a,c);}else{return null;}};YAHOO.util.Region.prototype.union=function(f){var d=Math.min(this.top,f.top),e=Math.max(this.right,f.right),a=Math.max(this.bottom,f.bottom),c=Math.min(this.left,f.left);return new YAHOO.util.Region(d,e,a,c);};YAHOO.util.Region.prototype.toString=function(){return("Region {"+"top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+", height: "+this.height+", width: "+this.width+"}");};YAHOO.util.Region.getRegion=function(e){var g=YAHOO.util.Dom.getXY(e),d=g[1],f=g[0]+e.offsetWidth,a=g[1]+e.offsetHeight,c=g[0];return new YAHOO.util.Region(d,f,a,c);};YAHOO.util.Point=function(a,b){if(YAHOO.lang.isArray(a)){b=a[1];a=a[0];}YAHOO.util.Point.superclass.constructor.call(this,b,a,b,a);};YAHOO.extend(YAHOO.util.Point,YAHOO.util.Region);(function(){var b=YAHOO.util,a="clientTop",f="clientLeft",j="parentNode",k="right",w="hasLayout",i="px",u="opacity",l="auto",d="borderLeftWidth",g="borderTopWidth",p="borderRightWidth",v="borderBottomWidth",s="visible",q="transparent",n="height",e="width",h="style",t="currentStyle",r=/^width|height$/,o=/^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i,m={get:function(x,z){var y="",A=x[t][z];if(z===u){y=b.Dom.getStyle(x,u);}else{if(!A||(A.indexOf&&A.indexOf(i)>-1)){y=A;}else{if(b.Dom.IE_COMPUTED[z]){y=b.Dom.IE_COMPUTED[z](x,z);}else{if(o.test(A)){y=b.Dom.IE.ComputedStyle.getPixel(x,z);}else{y=A;}}}}return y;},getOffset:function(z,E){var B=z[t][E],x=E.charAt(0).toUpperCase()+E.substr(1),C="offset"+x,y="pixel"+x,A="",D;if(B==l){D=z[C];if(D===undefined){A=0;}A=D;if(r.test(E)){z[h][E]=D;if(z[C]>D){A=D-(z[C]-D);}z[h][E]=l;}}else{if(!z[h][y]&&!z[h][E]){z[h][E]=B;}A=z[h][y];}return A+i;},getBorderWidth:function(x,z){var y=null;if(!x[t][w]){x[h].zoom=1;}switch(z){case g:y=x[a];break;case v:y=x.offsetHeight-x.clientHeight-x[a];break;case d:y=x[f];break;case p:y=x.offsetWidth-x.clientWidth-x[f];break;}return y+i;},getPixel:function(y,x){var A=null,B=y[t][k],z=y[t][x];y[h][k]=z;A=y[h].pixelRight;y[h][k]=B;return A+i;},getMargin:function(y,x){var z;if(y[t][x]==l){z=0+i;}else{z=b.Dom.IE.ComputedStyle.getPixel(y,x);}return z;},getVisibility:function(y,x){var z;while((z=y[t])&&z[x]=="inherit"){y=y[j];}return(z)?z[x]:s;},getColor:function(y,x){return b.Dom.Color.toRGB(y[t][x])||q;},getBorderColor:function(y,x){var z=y[t],A=z[x]||z.color;return b.Dom.Color.toRGB(b.Dom.Color.toHex(A));}},c={};c.top=c.right=c.bottom=c.left=c[e]=c[n]=m.getOffset;c.color=m.getColor;c[g]=c[p]=c[v]=c[d]=m.getBorderWidth;c.marginTop=c.marginRight=c.marginBottom=c.marginLeft=m.getMargin;c.visibility=m.getVisibility;c.borderColor=c.borderTopColor=c.borderRightColor=c.borderBottomColor=c.borderLeftColor=m.getBorderColor;b.Dom.IE_COMPUTED=c;b.Dom.IE_ComputedStyle=m;})();(function(){var c="toString",a=parseInt,b=RegExp,d=YAHOO.util;d.Dom.Color={KEYWORDS:{black:"000",silver:"c0c0c0",gray:"808080",white:"fff",maroon:"800000",red:"f00",purple:"800080",fuchsia:"f0f",green:"008000",lime:"0f0",olive:"808000",yellow:"ff0",navy:"000080",blue:"00f",teal:"008080",aqua:"0ff"},re_RGB:/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,re_hex:/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,re_hex3:/([0-9A-F])/gi,toRGB:function(e){if(!d.Dom.Color.re_RGB.test(e)){e=d.Dom.Color.toHex(e);}if(d.Dom.Color.re_hex.exec(e)){e="rgb("+[a(b.$1,16),a(b.$2,16),a(b.$3,16)].join(", ")+")";}return e;},toHex:function(f){f=d.Dom.Color.KEYWORDS[f]||f;if(d.Dom.Color.re_RGB.exec(f)){f=[Number(b.$1).toString(16),Number(b.$2).toString(16),Number(b.$3).toString(16)];for(var e=0;e<f.length;e++){if(f[e].length<2){f[e]="0"+f[e];}}f=f.join("");}if(f.length<6){f=f.replace(d.Dom.Color.re_hex3,"$1$1");}if(f!=="transparent"&&f.indexOf("#")<0){f="#"+f;}return f.toUpperCase();}};}());YAHOO.register("dom",YAHOO.util.Dom,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/dragdrop/dragdrop-min.js b/Websites/bugs.webkit.org/js/yui/dragdrop/dragdrop-min.js
new file mode 100644
index 0000000..29b507f
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/dragdrop/dragdrop-min.js
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+if(!YAHOO.util.DragDropMgr){YAHOO.util.DragDropMgr=function(){var A=YAHOO.util.Event,B=YAHOO.util.Dom;return{useShim:false,_shimActive:false,_shimState:false,_debugShim:false,_createShim:function(){var C=document.createElement("div");C.id="yui-ddm-shim";if(document.body.firstChild){document.body.insertBefore(C,document.body.firstChild);}else{document.body.appendChild(C);}C.style.display="none";C.style.backgroundColor="red";C.style.position="absolute";C.style.zIndex="99999";B.setStyle(C,"opacity","0");this._shim=C;A.on(C,"mouseup",this.handleMouseUp,this,true);A.on(C,"mousemove",this.handleMouseMove,this,true);A.on(window,"scroll",this._sizeShim,this,true);},_sizeShim:function(){if(this._shimActive){var C=this._shim;C.style.height=B.getDocumentHeight()+"px";C.style.width=B.getDocumentWidth()+"px";C.style.top="0";C.style.left="0";}},_activateShim:function(){if(this.useShim){if(!this._shim){this._createShim();}this._shimActive=true;var C=this._shim,D="0";if(this._debugShim){D=".5";}B.setStyle(C,"opacity",D);this._sizeShim();C.style.display="block";}},_deactivateShim:function(){this._shim.style.display="none";this._shimActive=false;},_shim:null,ids:{},handleIds:{},dragCurrent:null,dragOvers:{},deltaX:0,deltaY:0,preventDefault:true,stopPropagation:true,initialized:false,locked:false,interactionInfo:null,init:function(){this.initialized=true;},POINT:0,INTERSECT:1,STRICT_INTERSECT:2,mode:0,_execOnAll:function(E,D){for(var F in this.ids){for(var C in this.ids[F]){var G=this.ids[F][C];if(!this.isTypeOfDD(G)){continue;}G[E].apply(G,D);}}},_onLoad:function(){this.init();A.on(document,"mouseup",this.handleMouseUp,this,true);A.on(document,"mousemove",this.handleMouseMove,this,true);A.on(window,"unload",this._onUnload,this,true);A.on(window,"resize",this._onResize,this,true);},_onResize:function(C){this._execOnAll("resetConstraints",[]);},lock:function(){this.locked=true;},unlock:function(){this.locked=false;},isLocked:function(){return this.locked;},locationCache:{},useCache:true,clickPixelThresh:3,clickTimeThresh:1000,dragThreshMet:false,clickTimeout:null,startX:0,startY:0,fromTimeout:false,regDragDrop:function(D,C){if(!this.initialized){this.init();}if(!this.ids[C]){this.ids[C]={};}this.ids[C][D.id]=D;},removeDDFromGroup:function(E,C){if(!this.ids[C]){this.ids[C]={};}var D=this.ids[C];if(D&&D[E.id]){delete D[E.id];}},_remove:function(E){for(var D in E.groups){if(D){var C=this.ids[D];if(C&&C[E.id]){delete C[E.id];}}}delete this.handleIds[E.id];},regHandle:function(D,C){if(!this.handleIds[D]){this.handleIds[D]={};}this.handleIds[D][C]=C;},isDragDrop:function(C){return(this.getDDById(C))?true:false;},getRelated:function(H,D){var G=[];for(var F in H.groups){for(var E in this.ids[F]){var C=this.ids[F][E];if(!this.isTypeOfDD(C)){continue;}if(!D||C.isTarget){G[G.length]=C;}}}return G;},isLegalTarget:function(G,F){var D=this.getRelated(G,true);for(var E=0,C=D.length;E<C;++E){if(D[E].id==F.id){return true;}}return false;},isTypeOfDD:function(C){return(C&&C.__ygDragDrop);},isHandle:function(D,C){return(this.handleIds[D]&&this.handleIds[D][C]);},getDDById:function(D){for(var C in this.ids){if(this.ids[C][D]){return this.ids[C][D];}}return null;},handleMouseDown:function(E,D){this.currentTarget=YAHOO.util.Event.getTarget(E);this.dragCurrent=D;var C=D.getEl();this.startX=YAHOO.util.Event.getPageX(E);this.startY=YAHOO.util.Event.getPageY(E);this.deltaX=this.startX-C.offsetLeft;this.deltaY=this.startY-C.offsetTop;this.dragThreshMet=false;this.clickTimeout=setTimeout(function(){var F=YAHOO.util.DDM;F.startDrag(F.startX,F.startY);F.fromTimeout=true;},this.clickTimeThresh);},startDrag:function(C,E){if(this.dragCurrent&&this.dragCurrent.useShim){this._shimState=this.useShim;this.useShim=true;}this._activateShim();clearTimeout(this.clickTimeout);var D=this.dragCurrent;if(D&&D.events.b4StartDrag){D.b4StartDrag(C,E);D.fireEvent("b4StartDragEvent",{x:C,y:E});}if(D&&D.events.startDrag){D.startDrag(C,E);D.fireEvent("startDragEvent",{x:C,y:E});}this.dragThreshMet=true;},handleMouseUp:function(C){if(this.dragCurrent){clearTimeout(this.clickTimeout);if(this.dragThreshMet){if(this.fromTimeout){this.fromTimeout=false;this.handleMouseMove(C);}this.fromTimeout=false;this.fireEvents(C,true);}else{}this.stopDrag(C);this.stopEvent(C);}},stopEvent:function(C){if(this.stopPropagation){YAHOO.util.Event.stopPropagation(C);}if(this.preventDefault){YAHOO.util.Event.preventDefault(C);}},stopDrag:function(E,D){var C=this.dragCurrent;if(C&&!D){if(this.dragThreshMet){if(C.events.b4EndDrag){C.b4EndDrag(E);C.fireEvent("b4EndDragEvent",{e:E});}if(C.events.endDrag){C.endDrag(E);C.fireEvent("endDragEvent",{e:E});}}if(C.events.mouseUp){C.onMouseUp(E);C.fireEvent("mouseUpEvent",{e:E});}}if(this._shimActive){this._deactivateShim();if(this.dragCurrent&&this.dragCurrent.useShim){this.useShim=this._shimState;this._shimState=false;}}this.dragCurrent=null;this.dragOvers={};},handleMouseMove:function(F){var C=this.dragCurrent;if(C){if(YAHOO.env.ua.ie&&(YAHOO.env.ua.ie<9)&&!F.button){this.stopEvent(F);return this.handleMouseUp(F);}else{if(F.clientX<0||F.clientY<0){}}if(!this.dragThreshMet){var E=Math.abs(this.startX-YAHOO.util.Event.getPageX(F));var D=Math.abs(this.startY-YAHOO.util.Event.getPageY(F));if(E>this.clickPixelThresh||D>this.clickPixelThresh){this.startDrag(this.startX,this.startY);}}if(this.dragThreshMet){if(C&&C.events.b4Drag){C.b4Drag(F);C.fireEvent("b4DragEvent",{e:F});}if(C&&C.events.drag){C.onDrag(F);C.fireEvent("dragEvent",{e:F});}if(C){this.fireEvents(F,false);}}this.stopEvent(F);}},fireEvents:function(W,M){var c=this.dragCurrent;if(!c||c.isLocked()||c.dragOnly){return;}var O=YAHOO.util.Event.getPageX(W),N=YAHOO.util.Event.getPageY(W),Q=new YAHOO.util.Point(O,N),K=c.getTargetCoord(Q.x,Q.y),F=c.getDragEl(),E=["out","over","drop","enter"],V=new YAHOO.util.Region(K.y,K.x+F.offsetWidth,K.y+F.offsetHeight,K.x),I=[],D={},L={},R=[],d={outEvts:[],overEvts:[],dropEvts:[],enterEvts:[]};for(var T in this.dragOvers){var f=this.dragOvers[T];if(!this.isTypeOfDD(f)){continue;
+}if(!this.isOverTarget(Q,f,this.mode,V)){d.outEvts.push(f);}I[T]=true;delete this.dragOvers[T];}for(var S in c.groups){if("string"!=typeof S){continue;}for(T in this.ids[S]){var G=this.ids[S][T];if(!this.isTypeOfDD(G)){continue;}if(G.isTarget&&!G.isLocked()&&G!=c){if(this.isOverTarget(Q,G,this.mode,V)){D[S]=true;if(M){d.dropEvts.push(G);}else{if(!I[G.id]){d.enterEvts.push(G);}else{d.overEvts.push(G);}this.dragOvers[G.id]=G;}}}}}this.interactionInfo={out:d.outEvts,enter:d.enterEvts,over:d.overEvts,drop:d.dropEvts,point:Q,draggedRegion:V,sourceRegion:this.locationCache[c.id],validDrop:M};for(var C in D){R.push(C);}if(M&&!d.dropEvts.length){this.interactionInfo.validDrop=false;if(c.events.invalidDrop){c.onInvalidDrop(W);c.fireEvent("invalidDropEvent",{e:W});}}for(T=0;T<E.length;T++){var Z=null;if(d[E[T]+"Evts"]){Z=d[E[T]+"Evts"];}if(Z&&Z.length){var H=E[T].charAt(0).toUpperCase()+E[T].substr(1),Y="onDrag"+H,J="b4Drag"+H,P="drag"+H+"Event",X="drag"+H;if(this.mode){if(c.events[J]){c[J](W,Z,R);L[Y]=c.fireEvent(J+"Event",{event:W,info:Z,group:R});}if(c.events[X]&&(L[Y]!==false)){c[Y](W,Z,R);c.fireEvent(P,{event:W,info:Z,group:R});}}else{for(var a=0,U=Z.length;a<U;++a){if(c.events[J]){c[J](W,Z[a].id,R[0]);L[Y]=c.fireEvent(J+"Event",{event:W,info:Z[a].id,group:R[0]});}if(c.events[X]&&(L[Y]!==false)){c[Y](W,Z[a].id,R[0]);c.fireEvent(P,{event:W,info:Z[a].id,group:R[0]});}}}}}},getBestMatch:function(E){var G=null;var D=E.length;if(D==1){G=E[0];}else{for(var F=0;F<D;++F){var C=E[F];if(this.mode==this.INTERSECT&&C.cursorIsOver){G=C;break;}else{if(!G||!G.overlap||(C.overlap&&G.overlap.getArea()<C.overlap.getArea())){G=C;}}}}return G;},refreshCache:function(D){var F=D||this.ids;for(var C in F){if("string"!=typeof C){continue;}for(var E in this.ids[C]){var G=this.ids[C][E];if(this.isTypeOfDD(G)){var H=this.getLocation(G);if(H){this.locationCache[G.id]=H;}else{delete this.locationCache[G.id];}}}}},verifyEl:function(D){try{if(D){var C=D.offsetParent;if(C){return true;}}}catch(E){}return false;},getLocation:function(H){if(!this.isTypeOfDD(H)){return null;}var F=H.getEl(),K,E,D,M,L,N,C,J,G;try{K=YAHOO.util.Dom.getXY(F);}catch(I){}if(!K){return null;}E=K[0];D=E+F.offsetWidth;M=K[1];L=M+F.offsetHeight;N=M-H.padding[0];C=D+H.padding[1];J=L+H.padding[2];G=E-H.padding[3];return new YAHOO.util.Region(N,C,J,G);},isOverTarget:function(K,C,E,F){var G=this.locationCache[C.id];if(!G||!this.useCache){G=this.getLocation(C);this.locationCache[C.id]=G;}if(!G){return false;}C.cursorIsOver=G.contains(K);var J=this.dragCurrent;if(!J||(!E&&!J.constrainX&&!J.constrainY)){return C.cursorIsOver;}C.overlap=null;if(!F){var H=J.getTargetCoord(K.x,K.y);var D=J.getDragEl();F=new YAHOO.util.Region(H.y,H.x+D.offsetWidth,H.y+D.offsetHeight,H.x);}var I=F.intersect(G);if(I){C.overlap=I;return(E)?true:C.cursorIsOver;}else{return false;}},_onUnload:function(D,C){this.unregAll();},unregAll:function(){if(this.dragCurrent){this.stopDrag();this.dragCurrent=null;}this._execOnAll("unreg",[]);this.ids={};},elementCache:{},getElWrapper:function(D){var C=this.elementCache[D];if(!C||!C.el){C=this.elementCache[D]=new this.ElementWrapper(YAHOO.util.Dom.get(D));}return C;},getElement:function(C){return YAHOO.util.Dom.get(C);},getCss:function(D){var C=YAHOO.util.Dom.get(D);return(C)?C.style:null;},ElementWrapper:function(C){this.el=C||null;this.id=this.el&&C.id;this.css=this.el&&C.style;},getPosX:function(C){return YAHOO.util.Dom.getX(C);},getPosY:function(C){return YAHOO.util.Dom.getY(C);},swapNode:function(E,C){if(E.swapNode){E.swapNode(C);}else{var F=C.parentNode;var D=C.nextSibling;if(D==E){F.insertBefore(E,C);}else{if(C==E.nextSibling){F.insertBefore(C,E);}else{E.parentNode.replaceChild(C,E);F.insertBefore(E,D);}}}},getScroll:function(){var E,C,F=document.documentElement,D=document.body;if(F&&(F.scrollTop||F.scrollLeft)){E=F.scrollTop;C=F.scrollLeft;}else{if(D){E=D.scrollTop;C=D.scrollLeft;}else{}}return{top:E,left:C};},getStyle:function(D,C){return YAHOO.util.Dom.getStyle(D,C);},getScrollTop:function(){return this.getScroll().top;},getScrollLeft:function(){return this.getScroll().left;},moveToEl:function(C,E){var D=YAHOO.util.Dom.getXY(E);YAHOO.util.Dom.setXY(C,D);},getClientHeight:function(){return YAHOO.util.Dom.getViewportHeight();},getClientWidth:function(){return YAHOO.util.Dom.getViewportWidth();},numericSort:function(D,C){return(D-C);},_timeoutCount:0,_addListeners:function(){var C=YAHOO.util.DDM;if(YAHOO.util.Event&&document){C._onLoad();}else{if(C._timeoutCount>2000){}else{setTimeout(C._addListeners,10);if(document&&document.body){C._timeoutCount+=1;}}}},handleWasClicked:function(C,E){if(this.isHandle(E,C.id)){return true;}else{var D=C.parentNode;while(D){if(this.isHandle(E,D.id)){return true;}else{D=D.parentNode;}}}return false;}};}();YAHOO.util.DDM=YAHOO.util.DragDropMgr;YAHOO.util.DDM._addListeners();}(function(){var A=YAHOO.util.Event;var B=YAHOO.util.Dom;YAHOO.util.DragDrop=function(E,C,D){if(E){this.init(E,C,D);}};YAHOO.util.DragDrop.prototype={events:null,on:function(){this.subscribe.apply(this,arguments);},id:null,config:null,dragElId:null,handleElId:null,invalidHandleTypes:null,invalidHandleIds:null,invalidHandleClasses:null,startPageX:0,startPageY:0,groups:null,locked:false,lock:function(){this.locked=true;},unlock:function(){this.locked=false;},isTarget:true,padding:null,dragOnly:false,useShim:false,_domRef:null,__ygDragDrop:true,constrainX:false,constrainY:false,minX:0,maxX:0,minY:0,maxY:0,deltaX:0,deltaY:0,maintainOffset:false,xTicks:null,yTicks:null,primaryButtonOnly:true,available:false,hasOuterHandles:false,cursorIsOver:false,overlap:null,b4StartDrag:function(C,D){},startDrag:function(C,D){},b4Drag:function(C){},onDrag:function(C){},onDragEnter:function(C,D){},b4DragOver:function(C){},onDragOver:function(C,D){},b4DragOut:function(C){},onDragOut:function(C,D){},b4DragDrop:function(C){},onDragDrop:function(C,D){},onInvalidDrop:function(C){},b4EndDrag:function(C){},endDrag:function(C){},b4MouseDown:function(C){},onMouseDown:function(C){},onMouseUp:function(C){},onAvailable:function(){},getEl:function(){if(!this._domRef){this._domRef=B.get(this.id);
+}return this._domRef;},getDragEl:function(){return B.get(this.dragElId);},init:function(F,C,D){this.initTarget(F,C,D);A.on(this._domRef||this.id,"mousedown",this.handleMouseDown,this,true);for(var E in this.events){this.createEvent(E+"Event");}},initTarget:function(E,C,D){this.config=D||{};this.events={};this.DDM=YAHOO.util.DDM;this.groups={};if(typeof E!=="string"){this._domRef=E;E=B.generateId(E);}this.id=E;this.addToGroup((C)?C:"default");this.handleElId=E;A.onAvailable(E,this.handleOnAvailable,this,true);this.setDragElId(E);this.invalidHandleTypes={A:"A"};this.invalidHandleIds={};this.invalidHandleClasses=[];this.applyConfig();},applyConfig:function(){this.events={mouseDown:true,b4MouseDown:true,mouseUp:true,b4StartDrag:true,startDrag:true,b4EndDrag:true,endDrag:true,drag:true,b4Drag:true,invalidDrop:true,b4DragOut:true,dragOut:true,dragEnter:true,b4DragOver:true,dragOver:true,b4DragDrop:true,dragDrop:true};if(this.config.events){for(var C in this.config.events){if(this.config.events[C]===false){this.events[C]=false;}}}this.padding=this.config.padding||[0,0,0,0];this.isTarget=(this.config.isTarget!==false);this.maintainOffset=(this.config.maintainOffset);this.primaryButtonOnly=(this.config.primaryButtonOnly!==false);this.dragOnly=((this.config.dragOnly===true)?true:false);this.useShim=((this.config.useShim===true)?true:false);},handleOnAvailable:function(){this.available=true;this.resetConstraints();this.onAvailable();},setPadding:function(E,C,F,D){if(!C&&0!==C){this.padding=[E,E,E,E];}else{if(!F&&0!==F){this.padding=[E,C,E,C];}else{this.padding=[E,C,F,D];}}},setInitPosition:function(F,E){var G=this.getEl();if(!this.DDM.verifyEl(G)){if(G&&G.style&&(G.style.display=="none")){}else{}return;}var D=F||0;var C=E||0;var H=B.getXY(G);this.initPageX=H[0]-D;this.initPageY=H[1]-C;this.lastPageX=H[0];this.lastPageY=H[1];this.setStartPosition(H);},setStartPosition:function(D){var C=D||B.getXY(this.getEl());this.deltaSetXY=null;this.startPageX=C[0];this.startPageY=C[1];},addToGroup:function(C){this.groups[C]=true;this.DDM.regDragDrop(this,C);},removeFromGroup:function(C){if(this.groups[C]){delete this.groups[C];}this.DDM.removeDDFromGroup(this,C);},setDragElId:function(C){this.dragElId=C;},setHandleElId:function(C){if(typeof C!=="string"){C=B.generateId(C);}this.handleElId=C;this.DDM.regHandle(this.id,C);},setOuterHandleElId:function(C){if(typeof C!=="string"){C=B.generateId(C);}A.on(C,"mousedown",this.handleMouseDown,this,true);this.setHandleElId(C);this.hasOuterHandles=true;},unreg:function(){A.removeListener(this.id,"mousedown",this.handleMouseDown);this._domRef=null;this.DDM._remove(this);},isLocked:function(){return(this.DDM.isLocked()||this.locked);},handleMouseDown:function(J,I){var D=J.which||J.button;if(this.primaryButtonOnly&&D>1){return;}if(this.isLocked()){return;}var C=this.b4MouseDown(J),F=true;if(this.events.b4MouseDown){F=this.fireEvent("b4MouseDownEvent",J);}var E=this.onMouseDown(J),H=true;if(this.events.mouseDown){if(E===false){H=false;}else{H=this.fireEvent("mouseDownEvent",J);}}if((C===false)||(E===false)||(F===false)||(H===false)){return;}this.DDM.refreshCache(this.groups);var G=new YAHOO.util.Point(A.getPageX(J),A.getPageY(J));if(!this.hasOuterHandles&&!this.DDM.isOverTarget(G,this)){}else{if(this.clickValidator(J)){this.setStartPosition();this.DDM.handleMouseDown(J,this);this.DDM.stopEvent(J);}else{}}},clickValidator:function(D){var C=YAHOO.util.Event.getTarget(D);return(this.isValidHandleChild(C)&&(this.id==this.handleElId||this.DDM.handleWasClicked(C,this.id)));},getTargetCoord:function(E,D){var C=E-this.deltaX;var F=D-this.deltaY;if(this.constrainX){if(C<this.minX){C=this.minX;}if(C>this.maxX){C=this.maxX;}}if(this.constrainY){if(F<this.minY){F=this.minY;}if(F>this.maxY){F=this.maxY;}}C=this.getTick(C,this.xTicks);F=this.getTick(F,this.yTicks);return{x:C,y:F};},addInvalidHandleType:function(C){var D=C.toUpperCase();this.invalidHandleTypes[D]=D;},addInvalidHandleId:function(C){if(typeof C!=="string"){C=B.generateId(C);}this.invalidHandleIds[C]=C;},addInvalidHandleClass:function(C){this.invalidHandleClasses.push(C);},removeInvalidHandleType:function(C){var D=C.toUpperCase();delete this.invalidHandleTypes[D];},removeInvalidHandleId:function(C){if(typeof C!=="string"){C=B.generateId(C);}delete this.invalidHandleIds[C];},removeInvalidHandleClass:function(D){for(var E=0,C=this.invalidHandleClasses.length;E<C;++E){if(this.invalidHandleClasses[E]==D){delete this.invalidHandleClasses[E];}}},isValidHandleChild:function(F){var E=true;var H;try{H=F.nodeName.toUpperCase();}catch(G){H=F.nodeName;}E=E&&!this.invalidHandleTypes[H];E=E&&!this.invalidHandleIds[F.id];for(var D=0,C=this.invalidHandleClasses.length;E&&D<C;++D){E=!B.hasClass(F,this.invalidHandleClasses[D]);}return E;},setXTicks:function(F,C){this.xTicks=[];this.xTickSize=C;var E={};for(var D=this.initPageX;D>=this.minX;D=D-C){if(!E[D]){this.xTicks[this.xTicks.length]=D;E[D]=true;}}for(D=this.initPageX;D<=this.maxX;D=D+C){if(!E[D]){this.xTicks[this.xTicks.length]=D;E[D]=true;}}this.xTicks.sort(this.DDM.numericSort);},setYTicks:function(F,C){this.yTicks=[];this.yTickSize=C;var E={};for(var D=this.initPageY;D>=this.minY;D=D-C){if(!E[D]){this.yTicks[this.yTicks.length]=D;E[D]=true;}}for(D=this.initPageY;D<=this.maxY;D=D+C){if(!E[D]){this.yTicks[this.yTicks.length]=D;E[D]=true;}}this.yTicks.sort(this.DDM.numericSort);},setXConstraint:function(E,D,C){this.leftConstraint=parseInt(E,10);this.rightConstraint=parseInt(D,10);this.minX=this.initPageX-this.leftConstraint;this.maxX=this.initPageX+this.rightConstraint;if(C){this.setXTicks(this.initPageX,C);}this.constrainX=true;},clearConstraints:function(){this.constrainX=false;this.constrainY=false;this.clearTicks();},clearTicks:function(){this.xTicks=null;this.yTicks=null;this.xTickSize=0;this.yTickSize=0;},setYConstraint:function(C,E,D){this.topConstraint=parseInt(C,10);this.bottomConstraint=parseInt(E,10);this.minY=this.initPageY-this.topConstraint;this.maxY=this.initPageY+this.bottomConstraint;
+if(D){this.setYTicks(this.initPageY,D);}this.constrainY=true;},resetConstraints:function(){if(this.initPageX||this.initPageX===0){var D=(this.maintainOffset)?this.lastPageX-this.initPageX:0;var C=(this.maintainOffset)?this.lastPageY-this.initPageY:0;this.setInitPosition(D,C);}else{this.setInitPosition();}if(this.constrainX){this.setXConstraint(this.leftConstraint,this.rightConstraint,this.xTickSize);}if(this.constrainY){this.setYConstraint(this.topConstraint,this.bottomConstraint,this.yTickSize);}},getTick:function(I,F){if(!F){return I;}else{if(F[0]>=I){return F[0];}else{for(var D=0,C=F.length;D<C;++D){var E=D+1;if(F[E]&&F[E]>=I){var H=I-F[D];var G=F[E]-I;return(G>H)?F[D]:F[E];}}return F[F.length-1];}}},toString:function(){return("DragDrop "+this.id);}};YAHOO.augment(YAHOO.util.DragDrop,YAHOO.util.EventProvider);})();YAHOO.util.DD=function(C,A,B){if(C){this.init(C,A,B);}};YAHOO.extend(YAHOO.util.DD,YAHOO.util.DragDrop,{scroll:true,autoOffset:function(C,B){var A=C-this.startPageX;var D=B-this.startPageY;this.setDelta(A,D);},setDelta:function(B,A){this.deltaX=B;this.deltaY=A;},setDragElPos:function(C,B){var A=this.getDragEl();this.alignElWithMouse(A,C,B);},alignElWithMouse:function(C,G,F){var E=this.getTargetCoord(G,F);if(!this.deltaSetXY){var H=[E.x,E.y];YAHOO.util.Dom.setXY(C,H);var D=parseInt(YAHOO.util.Dom.getStyle(C,"left"),10);var B=parseInt(YAHOO.util.Dom.getStyle(C,"top"),10);this.deltaSetXY=[D-E.x,B-E.y];}else{YAHOO.util.Dom.setStyle(C,"left",(E.x+this.deltaSetXY[0])+"px");YAHOO.util.Dom.setStyle(C,"top",(E.y+this.deltaSetXY[1])+"px");}this.cachePosition(E.x,E.y);var A=this;setTimeout(function(){A.autoScroll.call(A,E.x,E.y,C.offsetHeight,C.offsetWidth);},0);},cachePosition:function(B,A){if(B){this.lastPageX=B;this.lastPageY=A;}else{var C=YAHOO.util.Dom.getXY(this.getEl());this.lastPageX=C[0];this.lastPageY=C[1];}},autoScroll:function(J,I,E,K){if(this.scroll){var L=this.DDM.getClientHeight();var B=this.DDM.getClientWidth();var N=this.DDM.getScrollTop();var D=this.DDM.getScrollLeft();var H=E+I;var M=K+J;var G=(L+N-I-this.deltaY);var F=(B+D-J-this.deltaX);var C=40;var A=(document.all)?80:30;if(H>L&&G<C){window.scrollTo(D,N+A);}if(I<N&&N>0&&I-N<C){window.scrollTo(D,N-A);}if(M>B&&F<C){window.scrollTo(D+A,N);}if(J<D&&D>0&&J-D<C){window.scrollTo(D-A,N);}}},applyConfig:function(){YAHOO.util.DD.superclass.applyConfig.call(this);this.scroll=(this.config.scroll!==false);},b4MouseDown:function(A){this.setStartPosition();this.autoOffset(YAHOO.util.Event.getPageX(A),YAHOO.util.Event.getPageY(A));},b4Drag:function(A){this.setDragElPos(YAHOO.util.Event.getPageX(A),YAHOO.util.Event.getPageY(A));},toString:function(){return("DD "+this.id);}});YAHOO.util.DDProxy=function(C,A,B){if(C){this.init(C,A,B);this.initFrame();}};YAHOO.util.DDProxy.dragElId="ygddfdiv";YAHOO.extend(YAHOO.util.DDProxy,YAHOO.util.DD,{resizeFrame:true,centerFrame:false,createFrame:function(){var B=this,A=document.body;if(!A||!A.firstChild){setTimeout(function(){B.createFrame();},50);return;}var F=this.getDragEl(),E=YAHOO.util.Dom;if(!F){F=document.createElement("div");F.id=this.dragElId;var D=F.style;D.position="absolute";D.visibility="hidden";D.cursor="move";D.border="2px solid #aaa";D.zIndex=999;D.height="25px";D.width="25px";var C=document.createElement("div");E.setStyle(C,"height","100%");E.setStyle(C,"width","100%");E.setStyle(C,"background-color","#ccc");E.setStyle(C,"opacity","0");F.appendChild(C);A.insertBefore(F,A.firstChild);}},initFrame:function(){this.createFrame();},applyConfig:function(){YAHOO.util.DDProxy.superclass.applyConfig.call(this);this.resizeFrame=(this.config.resizeFrame!==false);this.centerFrame=(this.config.centerFrame);this.setDragElId(this.config.dragElId||YAHOO.util.DDProxy.dragElId);},showFrame:function(E,D){var C=this.getEl();var A=this.getDragEl();var B=A.style;this._resizeProxy();if(this.centerFrame){this.setDelta(Math.round(parseInt(B.width,10)/2),Math.round(parseInt(B.height,10)/2));}this.setDragElPos(E,D);YAHOO.util.Dom.setStyle(A,"visibility","visible");},_resizeProxy:function(){if(this.resizeFrame){var H=YAHOO.util.Dom;var B=this.getEl();var C=this.getDragEl();var G=parseInt(H.getStyle(C,"borderTopWidth"),10);var I=parseInt(H.getStyle(C,"borderRightWidth"),10);var F=parseInt(H.getStyle(C,"borderBottomWidth"),10);var D=parseInt(H.getStyle(C,"borderLeftWidth"),10);if(isNaN(G)){G=0;}if(isNaN(I)){I=0;}if(isNaN(F)){F=0;}if(isNaN(D)){D=0;}var E=Math.max(0,B.offsetWidth-I-D);var A=Math.max(0,B.offsetHeight-G-F);H.setStyle(C,"width",E+"px");H.setStyle(C,"height",A+"px");}},b4MouseDown:function(B){this.setStartPosition();var A=YAHOO.util.Event.getPageX(B);var C=YAHOO.util.Event.getPageY(B);this.autoOffset(A,C);},b4StartDrag:function(A,B){this.showFrame(A,B);},b4EndDrag:function(A){YAHOO.util.Dom.setStyle(this.getDragEl(),"visibility","hidden");},endDrag:function(D){var C=YAHOO.util.Dom;var B=this.getEl();var A=this.getDragEl();C.setStyle(A,"visibility","");C.setStyle(B,"visibility","hidden");YAHOO.util.DDM.moveToEl(B,A);C.setStyle(A,"visibility","hidden");C.setStyle(B,"visibility","");},toString:function(){return("DDProxy "+this.id);}});YAHOO.util.DDTarget=function(C,A,B){if(C){this.initTarget(C,A,B);}};YAHOO.extend(YAHOO.util.DDTarget,YAHOO.util.DragDrop,{toString:function(){return("DDTarget "+this.id);}});YAHOO.register("dragdrop",YAHOO.util.DragDropMgr,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/element-delegate/element-delegate-min.js b/Websites/bugs.webkit.org/js/yui/element-delegate/element-delegate-min.js
new file mode 100644
index 0000000..b8d0a66
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/element-delegate/element-delegate-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var A=YAHOO.util.Event,B=[],C={mouseenter:true,mouseleave:true};YAHOO.lang.augmentObject(YAHOO.util.Element.prototype,{delegate:function(J,L,F,H,I){if(YAHOO.lang.isString(F)&&!YAHOO.util.Selector){return false;}if(!A._createDelegate){return false;}var E=A._getType(J),G=this.get("element"),M,K,D=function(N){return M.call(G,N);};if(C[J]){if(!A._createMouseDelegate){return false;}K=A._createMouseDelegate(L,H,I);M=A._createDelegate(function(P,O,N){return K.call(O,P,N);},F,H,I);}else{M=A._createDelegate(L,F,H,I);}B.push([G,E,L,D]);return this.on(E,D);},removeDelegate:function(H,G){var I=A._getType(H),E=A._getCacheIndex(B,this.get("element"),I,G),F,D;if(E>=0){D=B[E];}if(D){F=this.removeListener(D[1],D[3]);if(F){delete B[E][2];delete B[E][3];B.splice(E,1);}}return F;}});}());YAHOO.register("element-delegate",YAHOO.util.Element,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/element/element-min.js b/Websites/bugs.webkit.org/js/yui/element/element-min.js
new file mode 100644
index 0000000..6a46421
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/element/element-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.Attribute=function(b,a){if(a){this.owner=a;this.configure(b,true);}};YAHOO.util.Attribute.INVALID_VALUE={};YAHOO.util.Attribute.prototype={name:undefined,value:null,owner:null,readOnly:false,writeOnce:false,_initialConfig:null,_written:false,method:null,setter:null,getter:null,validator:null,getValue:function(){var a=this.value;if(this.getter){a=this.getter.call(this.owner,this.name,a);}return a;},setValue:function(f,b){var e,a=this.owner,c=this.name,g=YAHOO.util.Attribute.INVALID_VALUE,d={type:c,prevValue:this.getValue(),newValue:f};if(this.readOnly||(this.writeOnce&&this._written)){return false;}if(this.validator&&!this.validator.call(a,f)){return false;}if(!b){e=a.fireBeforeChangeEvent(d);if(e===false){return false;}}if(this.setter){f=this.setter.call(a,f,this.name);if(f===undefined){}if(f===g){return false;}}if(this.method){if(this.method.call(a,f,this.name)===g){return false;}}this.value=f;this._written=true;d.type=c;if(!b){this.owner.fireChangeEvent(d);}return true;},configure:function(b,c){b=b||{};if(c){this._written=false;}this._initialConfig=this._initialConfig||{};for(var a in b){if(b.hasOwnProperty(a)){this[a]=b[a];if(c){this._initialConfig[a]=b[a];}}}},resetValue:function(){return this.setValue(this._initialConfig.value);},resetConfig:function(){this.configure(this._initialConfig,true);},refresh:function(a){this.setValue(this.value,a);}};(function(){var a=YAHOO.util.Lang;YAHOO.util.AttributeProvider=function(){};YAHOO.util.AttributeProvider.prototype={_configs:null,get:function(c){this._configs=this._configs||{};var b=this._configs[c];if(!b||!this._configs.hasOwnProperty(c)){return null;}return b.getValue();},set:function(d,e,b){this._configs=this._configs||{};var c=this._configs[d];if(!c){return false;}return c.setValue(e,b);},getAttributeKeys:function(){this._configs=this._configs;var c=[],b;for(b in this._configs){if(a.hasOwnProperty(this._configs,b)&&!a.isUndefined(this._configs[b])){c[c.length]=b;}}return c;},setAttributes:function(d,b){for(var c in d){if(a.hasOwnProperty(d,c)){this.set(c,d[c],b);}}},resetValue:function(c,b){this._configs=this._configs||{};if(this._configs[c]){this.set(c,this._configs[c]._initialConfig.value,b);return true;}return false;},refresh:function(e,c){this._configs=this._configs||{};var f=this._configs;e=((a.isString(e))?[e]:e)||this.getAttributeKeys();for(var d=0,b=e.length;d<b;++d){if(f.hasOwnProperty(e[d])){this._configs[e[d]].refresh(c);}}},register:function(b,c){this.setAttributeConfig(b,c);},getAttributeConfig:function(c){this._configs=this._configs||{};var b=this._configs[c]||{};var d={};for(c in b){if(a.hasOwnProperty(b,c)){d[c]=b[c];}}return d;},setAttributeConfig:function(b,c,d){this._configs=this._configs||{};c=c||{};if(!this._configs[b]){c.name=b;this._configs[b]=this.createAttribute(c);}else{this._configs[b].configure(c,d);}},configureAttribute:function(b,c,d){this.setAttributeConfig(b,c,d);},resetAttributeConfig:function(b){this._configs=this._configs||{};this._configs[b].resetConfig();},subscribe:function(b,c){this._events=this._events||{};if(!(b in this._events)){this._events[b]=this.createEvent(b);}YAHOO.util.EventProvider.prototype.subscribe.apply(this,arguments);},on:function(){this.subscribe.apply(this,arguments);},addListener:function(){this.subscribe.apply(this,arguments);},fireBeforeChangeEvent:function(c){var b="before";b+=c.type.charAt(0).toUpperCase()+c.type.substr(1)+"Change";c.type=b;return this.fireEvent(c.type,c);},fireChangeEvent:function(b){b.type+="Change";return this.fireEvent(b.type,b);},createAttribute:function(b){return new YAHOO.util.Attribute(b,this);}};YAHOO.augment(YAHOO.util.AttributeProvider,YAHOO.util.EventProvider);})();(function(){var b=YAHOO.util.Dom,d=YAHOO.util.AttributeProvider,c={mouseenter:true,mouseleave:true};var a=function(e,f){this.init.apply(this,arguments);};a.DOM_EVENTS={"click":true,"dblclick":true,"keydown":true,"keypress":true,"keyup":true,"mousedown":true,"mousemove":true,"mouseout":true,"mouseover":true,"mouseup":true,"mouseenter":true,"mouseleave":true,"focus":true,"blur":true,"submit":true,"change":true};a.prototype={DOM_EVENTS:null,DEFAULT_HTML_SETTER:function(g,e){var f=this.get("element");if(f){f[e]=g;}return g;},DEFAULT_HTML_GETTER:function(e){var f=this.get("element"),g;if(f){g=f[e];}return g;},appendChild:function(e){e=e.get?e.get("element"):e;return this.get("element").appendChild(e);},getElementsByTagName:function(e){return this.get("element").getElementsByTagName(e);},hasChildNodes:function(){return this.get("element").hasChildNodes();},insertBefore:function(e,f){e=e.get?e.get("element"):e;f=(f&&f.get)?f.get("element"):f;return this.get("element").insertBefore(e,f);},removeChild:function(e){e=e.get?e.get("element"):e;return this.get("element").removeChild(e);},replaceChild:function(e,f){e=e.get?e.get("element"):e;f=f.get?f.get("element"):f;return this.get("element").replaceChild(e,f);},initAttributes:function(e){},addListener:function(j,i,k,h){h=h||this;var e=YAHOO.util.Event,g=this.get("element")||this.get("id"),f=this;if(c[j]&&!e._createMouseDelegate){return false;}if(!this._events[j]){if(g&&this.DOM_EVENTS[j]){e.on(g,j,function(m,l){if(m.srcElement&&!m.target){m.target=m.srcElement;}if((m.toElement&&!m.relatedTarget)||(m.fromElement&&!m.relatedTarget)){m.relatedTarget=e.getRelatedTarget(m);}if(!m.currentTarget){m.currentTarget=g;}f.fireEvent(j,m,l);},k,h);}this.createEvent(j,{scope:this});}return YAHOO.util.EventProvider.prototype.subscribe.apply(this,arguments);},on:function(){return this.addListener.apply(this,arguments);},subscribe:function(){return this.addListener.apply(this,arguments);},removeListener:function(f,e){return this.unsubscribe.apply(this,arguments);},addClass:function(e){b.addClass(this.get("element"),e);},getElementsByClassName:function(f,e){return b.getElementsByClassName(f,e,this.get("element"));},hasClass:function(e){return b.hasClass(this.get("element"),e);},removeClass:function(e){return b.removeClass(this.get("element"),e);},replaceClass:function(f,e){return b.replaceClass(this.get("element"),f,e);
+},setStyle:function(f,e){return b.setStyle(this.get("element"),f,e);},getStyle:function(e){return b.getStyle(this.get("element"),e);},fireQueue:function(){var f=this._queue;for(var g=0,e=f.length;g<e;++g){this[f[g][0]].apply(this,f[g][1]);}},appendTo:function(f,g){f=(f.get)?f.get("element"):b.get(f);this.fireEvent("beforeAppendTo",{type:"beforeAppendTo",target:f});g=(g&&g.get)?g.get("element"):b.get(g);var e=this.get("element");if(!e){return false;}if(!f){return false;}if(e.parent!=f){if(g){f.insertBefore(e,g);}else{f.appendChild(e);}}this.fireEvent("appendTo",{type:"appendTo",target:f});return e;},get:function(e){var g=this._configs||{},f=g.element;if(f&&!g[e]&&!YAHOO.lang.isUndefined(f.value[e])){this._setHTMLAttrConfig(e);}return d.prototype.get.call(this,e);},setAttributes:function(l,h){var f={},j=this._configOrder;for(var k=0,e=j.length;k<e;++k){if(l[j[k]]!==undefined){f[j[k]]=true;this.set(j[k],l[j[k]],h);}}for(var g in l){if(l.hasOwnProperty(g)&&!f[g]){this.set(g,l[g],h);}}},set:function(f,h,e){var g=this.get("element");if(!g){this._queue[this._queue.length]=["set",arguments];if(this._configs[f]){this._configs[f].value=h;}return;}if(!this._configs[f]&&!YAHOO.lang.isUndefined(g[f])){this._setHTMLAttrConfig(f);}return d.prototype.set.apply(this,arguments);},setAttributeConfig:function(e,f,g){this._configOrder.push(e);d.prototype.setAttributeConfig.apply(this,arguments);},createEvent:function(f,e){this._events[f]=true;return d.prototype.createEvent.apply(this,arguments);},init:function(f,e){this._initElement(f,e);},destroy:function(){var e=this.get("element");YAHOO.util.Event.purgeElement(e,true);this.unsubscribeAll();if(e&&e.parentNode){e.parentNode.removeChild(e);}this._queue=[];this._events={};this._configs={};this._configOrder=[];},_initElement:function(g,f){this._queue=this._queue||[];this._events=this._events||{};this._configs=this._configs||{};this._configOrder=[];f=f||{};f.element=f.element||g||null;var i=false;var e=a.DOM_EVENTS;this.DOM_EVENTS=this.DOM_EVENTS||{};for(var h in e){if(e.hasOwnProperty(h)){this.DOM_EVENTS[h]=e[h];}}if(typeof f.element==="string"){this._setHTMLAttrConfig("id",{value:f.element});}if(b.get(f.element)){i=true;this._initHTMLElement(f);this._initContent(f);}YAHOO.util.Event.onAvailable(f.element,function(){if(!i){this._initHTMLElement(f);}this.fireEvent("available",{type:"available",target:b.get(f.element)});},this,true);YAHOO.util.Event.onContentReady(f.element,function(){if(!i){this._initContent(f);}this.fireEvent("contentReady",{type:"contentReady",target:b.get(f.element)});},this,true);},_initHTMLElement:function(e){this.setAttributeConfig("element",{value:b.get(e.element),readOnly:true});},_initContent:function(e){this.initAttributes(e);this.setAttributes(e,true);this.fireQueue();},_setHTMLAttrConfig:function(e,g){var f=this.get("element");g=g||{};g.name=e;g.setter=g.setter||this.DEFAULT_HTML_SETTER;g.getter=g.getter||this.DEFAULT_HTML_GETTER;g.value=g.value||f[e];this._configs[e]=new YAHOO.util.Attribute(g,this);}};YAHOO.augment(a,d);YAHOO.util.Element=a;})();YAHOO.register("element",YAHOO.util.Element,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/event-delegate/event-delegate-min.js b/Websites/bugs.webkit.org/js/yui/event-delegate/event-delegate-min.js
new file mode 100644
index 0000000..5163f7a
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/event-delegate/event-delegate-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var A=YAHOO.util.Event,C=YAHOO.lang,B=[],D=function(H,E,F){var G;if(!H||H===F){G=false;}else{G=YAHOO.util.Selector.test(H,E)?H:D(H.parentNode,E,F);}return G;};C.augmentObject(A,{_createDelegate:function(F,E,G,H){return function(I){var J=this,N=A.getTarget(I),L=E,P=(J.nodeType===9),Q,K,O,M;if(C.isFunction(E)){Q=E(N);}else{if(C.isString(E)){if(!P){O=J.id;if(!O){O=A.generateId(J);}M=("#"+O+" ");L=(M+E).replace(/,/gi,(","+M));}if(YAHOO.util.Selector.test(N,L)){Q=N;}else{if(YAHOO.util.Selector.test(N,((L.replace(/,/gi," *,"))+" *"))){Q=D(N,L,J);}}}}if(Q){K=Q;if(H){if(H===true){K=G;}else{K=H;}}return F.call(K,I,Q,J,G);}};},delegate:function(F,J,L,G,H,I){var E=J,K,M;if(C.isString(G)&&!YAHOO.util.Selector){return false;}if(J=="mouseenter"||J=="mouseleave"){if(!A._createMouseDelegate){return false;}E=A._getType(J);K=A._createMouseDelegate(L,H,I);M=A._createDelegate(function(P,O,N){return K.call(O,P,N);},G,H,I);}else{M=A._createDelegate(L,G,H,I);}B.push([F,E,L,M]);return A.on(F,E,M);},removeDelegate:function(F,J,I){var K=J,H=false,G,E;if(J=="mouseenter"||J=="mouseleave"){K=A._getType(J);}G=A._getCacheIndex(B,F,K,I);if(G>=0){E=B[G];}if(F&&E){H=A.removeListener(E[0],E[1],E[3]);if(H){delete B[G][2];delete B[G][3];B.splice(G,1);}}return H;}});}());YAHOO.register("event-delegate",YAHOO.util.Event,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/event-mouseenter/event-mouseenter-min.js b/Websites/bugs.webkit.org/js/yui/event-mouseenter/event-mouseenter-min.js
new file mode 100644
index 0000000..4a4e951
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/event-mouseenter/event-mouseenter-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var b=YAHOO.util.Event,g=YAHOO.lang,e=b.addListener,f=b.removeListener,c=b.getListeners,d=[],h={mouseenter:"mouseover",mouseleave:"mouseout"},a=function(n,m,l){var j=b._getCacheIndex(d,n,m,l),i,k;if(j>=0){i=d[j];}if(n&&i){k=f.call(b,i[0],m,i[3]);if(k){delete d[j][2];delete d[j][3];d.splice(j,1);}}return k;};g.augmentObject(b._specialTypes,h);g.augmentObject(b,{_createMouseDelegate:function(i,j,k){return function(q,m){var p=this,l=b.getRelatedTarget(q),o,n;if(p!=l&&!YAHOO.util.Dom.isAncestor(p,l)){o=p;if(k){if(k===true){o=j;}else{o=k;}}n=[q,j];if(m){n.splice(1,0,p,m);}return i.apply(o,n);}};},addListener:function(m,l,k,n,o){var i,j;if(h[l]){i=b._createMouseDelegate(k,n,o);i.mouseDelegate=true;d.push([m,l,k,i]);j=e.call(b,m,l,i);}else{j=e.apply(b,arguments);}return j;},removeListener:function(l,k,j){var i;if(h[k]){i=a.apply(b,arguments);}else{i=f.apply(b,arguments);}return i;},getListeners:function(p,o){var n=[],r,m=(o==="mouseover"||o==="mouseout"),q,k,j;if(o&&(m||h[o])){r=c.call(b,p,this._getType(o));if(r){for(k=r.length-1;k>-1;k--){j=r[k];q=j.fn.mouseDelegate;if((h[o]&&q)||(m&&!q)){n.push(j);}}}}else{n=c.apply(b,arguments);}return(n&&n.length)?n:null;}},true);b.on=b.addListener;}());YAHOO.register("event-mouseenter",YAHOO.util.Event,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/event-simulate/event-simulate-min.js b/Websites/bugs.webkit.org/js/yui/event-simulate/event-simulate-min.js
new file mode 100644
index 0000000..63b9710
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/event-simulate/event-simulate-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.UserAction={simulateKeyEvent:function(f,j,e,c,l,b,a,k,h,n,m){f=YAHOO.util.Dom.get(f);if(!f){throw new Error("simulateKeyEvent(): Invalid target.");}if(YAHOO.lang.isString(j)){j=j.toLowerCase();switch(j){case"keyup":case"keydown":case"keypress":break;case"textevent":j="keypress";break;default:throw new Error("simulateKeyEvent(): Event type '"+j+"' not supported.");}}else{throw new Error("simulateKeyEvent(): Event type must be a string.");}if(!YAHOO.lang.isBoolean(e)){e=true;}if(!YAHOO.lang.isBoolean(c)){c=true;}if(!YAHOO.lang.isObject(l)){l=window;}if(!YAHOO.lang.isBoolean(b)){b=false;}if(!YAHOO.lang.isBoolean(a)){a=false;}if(!YAHOO.lang.isBoolean(k)){k=false;}if(!YAHOO.lang.isBoolean(h)){h=false;}if(!YAHOO.lang.isNumber(n)){n=0;}if(!YAHOO.lang.isNumber(m)){m=0;}var i=null;if(YAHOO.lang.isFunction(document.createEvent)){try{i=document.createEvent("KeyEvents");i.initKeyEvent(j,e,c,l,b,a,k,h,n,m);}catch(g){try{i=document.createEvent("Events");}catch(d){i=document.createEvent("UIEvents");}finally{i.initEvent(j,e,c);i.view=l;i.altKey=a;i.ctrlKey=b;i.shiftKey=k;i.metaKey=h;i.keyCode=n;i.charCode=m;}}f.dispatchEvent(i);}else{if(YAHOO.lang.isObject(document.createEventObject)){i=document.createEventObject();i.bubbles=e;i.cancelable=c;i.view=l;i.ctrlKey=b;i.altKey=a;i.shiftKey=k;i.metaKey=h;i.keyCode=(m>0)?m:n;f.fireEvent("on"+j,i);}else{throw new Error("simulateKeyEvent(): No event simulation framework present.");}}},simulateMouseEvent:function(k,p,h,e,q,j,g,f,d,b,c,a,o,m,i,l){k=YAHOO.util.Dom.get(k);if(!k){throw new Error("simulateMouseEvent(): Invalid target.");}l=l||null;if(YAHOO.lang.isString(p)){p=p.toLowerCase();switch(p){case"mouseover":case"mouseout":case"mousedown":case"mouseup":case"click":case"dblclick":case"mousemove":break;default:throw new Error("simulateMouseEvent(): Event type '"+p+"' not supported.");}}else{throw new Error("simulateMouseEvent(): Event type must be a string.");}if(!YAHOO.lang.isBoolean(h)){h=true;}if(!YAHOO.lang.isBoolean(e)){e=(p!="mousemove");}if(!YAHOO.lang.isObject(q)){q=window;}if(!YAHOO.lang.isNumber(j)){j=1;}if(!YAHOO.lang.isNumber(g)){g=0;}if(!YAHOO.lang.isNumber(f)){f=0;}if(!YAHOO.lang.isNumber(d)){d=0;}if(!YAHOO.lang.isNumber(b)){b=0;}if(!YAHOO.lang.isBoolean(c)){c=false;}if(!YAHOO.lang.isBoolean(a)){a=false;}if(!YAHOO.lang.isBoolean(o)){o=false;}if(!YAHOO.lang.isBoolean(m)){m=false;}if(!YAHOO.lang.isNumber(i)){i=0;}var n=null;if(YAHOO.lang.isFunction(document.createEvent)){n=document.createEvent("MouseEvents");if(n.initMouseEvent){n.initMouseEvent(p,h,e,q,j,g,f,d,b,c,a,o,m,i,l);}else{n=document.createEvent("UIEvents");n.initEvent(p,h,e);n.view=q;n.detail=j;n.screenX=g;n.screenY=f;n.clientX=d;n.clientY=b;n.ctrlKey=c;n.altKey=a;n.metaKey=m;n.shiftKey=o;n.button=i;n.relatedTarget=l;}if(l&&!n.relatedTarget){if(p=="mouseout"){n.toElement=l;}else{if(p=="mouseover"){n.fromElement=l;}}}k.dispatchEvent(n);}else{if(YAHOO.lang.isObject(document.createEventObject)){n=document.createEventObject();n.bubbles=h;n.cancelable=e;n.view=q;n.detail=j;n.screenX=g;n.screenY=f;n.clientX=d;n.clientY=b;n.ctrlKey=c;n.altKey=a;n.metaKey=m;n.shiftKey=o;switch(i){case 0:n.button=1;break;case 1:n.button=4;break;case 2:break;default:n.button=0;}n.relatedTarget=l;k.fireEvent("on"+p,n);}else{throw new Error("simulateMouseEvent(): No event simulation framework present.");}}},fireMouseEvent:function(c,b,a){a=a||{};this.simulateMouseEvent(c,b,a.bubbles,a.cancelable,a.view,a.detail,a.screenX,a.screenY,a.clientX,a.clientY,a.ctrlKey,a.altKey,a.shiftKey,a.metaKey,a.button,a.relatedTarget);},click:function(b,a){this.fireMouseEvent(b,"click",a);},dblclick:function(b,a){this.fireMouseEvent(b,"dblclick",a);},mousedown:function(b,a){this.fireMouseEvent(b,"mousedown",a);},mousemove:function(b,a){this.fireMouseEvent(b,"mousemove",a);},mouseout:function(b,a){this.fireMouseEvent(b,"mouseout",a);},mouseover:function(b,a){this.fireMouseEvent(b,"mouseover",a);},mouseup:function(b,a){this.fireMouseEvent(b,"mouseup",a);},fireKeyEvent:function(b,c,a){a=a||{};this.simulateKeyEvent(c,b,a.bubbles,a.cancelable,a.view,a.ctrlKey,a.altKey,a.shiftKey,a.metaKey,a.keyCode,a.charCode);},keydown:function(b,a){this.fireKeyEvent("keydown",b,a);},keypress:function(b,a){this.fireKeyEvent("keypress",b,a);},keyup:function(b,a){this.fireKeyEvent("keyup",b,a);}};YAHOO.register("event-simulate",YAHOO.util.UserAction,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/event/event-min.js b/Websites/bugs.webkit.org/js/yui/event/event-min.js
new file mode 100644
index 0000000..f4a0f25
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/event/event-min.js
@@ -0,0 +1,11 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.CustomEvent=function(d,c,b,a,e){this.type=d;this.scope=c||window;this.silent=b;this.fireOnce=e;this.fired=false;this.firedWith=null;this.signature=a||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var f="_YUICEOnSubscribe";if(d!==f){this.subscribeEvent=new YAHOO.util.CustomEvent(f,this,true);}this.lastError=null;};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(b,c,d){if(!b){throw new Error("Invalid callback for subscriber to '"+this.type+"'");}if(this.subscribeEvent){this.subscribeEvent.fire(b,c,d);}var a=new YAHOO.util.Subscriber(b,c,d);if(this.fireOnce&&this.fired){this.notify(a,this.firedWith);}else{this.subscribers.push(a);}},unsubscribe:function(d,f){if(!d){return this.unsubscribeAll();}var e=false;for(var b=0,a=this.subscribers.length;b<a;++b){var c=this.subscribers[b];if(c&&c.contains(d,f)){this._delete(b);e=true;}}return e;},fire:function(){this.lastError=null;var h=[],a=this.subscribers.length;var d=[].slice.call(arguments,0),c=true,f,b=false;if(this.fireOnce){if(this.fired){return true;}else{this.firedWith=d;}}this.fired=true;if(!a&&this.silent){return true;}if(!this.silent){}var e=this.subscribers.slice();for(f=0;f<a;++f){var g=e[f];if(!g||!g.fn){b=true;}else{c=this.notify(g,d);if(false===c){if(!this.silent){}break;}}}return(c!==false);},notify:function(g,c){var b,i=null,f=g.getScope(this.scope),a=YAHOO.util.Event.throwErrors;if(!this.silent){}if(this.signature==YAHOO.util.CustomEvent.FLAT){if(c.length>0){i=c[0];}try{b=g.fn.call(f,i,g.obj);}catch(h){this.lastError=h;if(a){throw h;}}}else{try{b=g.fn.call(f,this.type,c,g.obj);}catch(d){this.lastError=d;if(a){throw d;}}}return b;},unsubscribeAll:function(){var a=this.subscribers.length,b;for(b=a-1;b>-1;b--){this._delete(b);}this.subscribers=[];return a;},_delete:function(a){var b=this.subscribers[a];if(b){delete b.fn;delete b.obj;}this.subscribers.splice(a,1);},toString:function(){return"CustomEvent: "+"'"+this.type+"', "+"context: "+this.scope;}};YAHOO.util.Subscriber=function(a,b,c){this.fn=a;this.obj=YAHOO.lang.isUndefined(b)?null:b;this.overrideContext=c;};YAHOO.util.Subscriber.prototype.getScope=function(a){if(this.overrideContext){if(this.overrideContext===true){return this.obj;}else{return this.overrideContext;}}return a;};YAHOO.util.Subscriber.prototype.contains=function(a,b){if(b){return(this.fn==a&&this.obj==b);}else{return(this.fn==a);}};YAHOO.util.Subscriber.prototype.toString=function(){return"Subscriber { obj: "+this.obj+", overrideContext: "+(this.overrideContext||"no")+" }";};if(!YAHOO.util.Event){YAHOO.util.Event=function(){var g=false,h=[],j=[],a=0,e=[],b=0,c={63232:38,63233:40,63234:37,63235:39,63276:33,63277:34,25:9},d=YAHOO.env.ua.ie,f="focusin",i="focusout";return{POLL_RETRYS:500,POLL_INTERVAL:40,EL:0,TYPE:1,FN:2,WFN:3,UNLOAD_OBJ:3,ADJ_SCOPE:4,OBJ:5,OVERRIDE:6,CAPTURE:7,lastError:null,isSafari:YAHOO.env.ua.webkit,webkit:YAHOO.env.ua.webkit,isIE:d,_interval:null,_dri:null,_specialTypes:{focusin:(d?"focusin":"focus"),focusout:(d?"focusout":"blur")},DOMReady:false,throwErrors:false,startInterval:function(){if(!this._interval){this._interval=YAHOO.lang.later(this.POLL_INTERVAL,this,this._tryPreloadAttach,null,true);}},onAvailable:function(q,m,o,p,n){var k=(YAHOO.lang.isString(q))?[q]:q;for(var l=0;l<k.length;l=l+1){e.push({id:k[l],fn:m,obj:o,overrideContext:p,checkReady:n});}a=this.POLL_RETRYS;this.startInterval();},onContentReady:function(n,k,l,m){this.onAvailable(n,k,l,m,true);},onDOMReady:function(){this.DOMReadyEvent.subscribe.apply(this.DOMReadyEvent,arguments);},_addListener:function(m,k,v,p,t,y){if(!v||!v.call){return false;}if(this._isValidCollection(m)){var w=true;for(var q=0,s=m.length;q<s;++q){w=this.on(m[q],k,v,p,t)&&w;}return w;}else{if(YAHOO.lang.isString(m)){var o=this.getEl(m);if(o){m=o;}else{this.onAvailable(m,function(){YAHOO.util.Event._addListener(m,k,v,p,t,y);});return true;}}}if(!m){return false;}if("unload"==k&&p!==this){j[j.length]=[m,k,v,p,t];return true;}var l=m;if(t){if(t===true){l=p;}else{l=t;}}var n=function(z){return v.call(l,YAHOO.util.Event.getEvent(z,m),p);};var x=[m,k,v,n,l,p,t,y];var r=h.length;h[r]=x;try{this._simpleAdd(m,k,n,y);}catch(u){this.lastError=u;this.removeListener(m,k,v);return false;}return true;},_getType:function(k){return this._specialTypes[k]||k;},addListener:function(m,p,l,n,o){var k=((p==f||p==i)&&!YAHOO.env.ua.ie)?true:false;return this._addListener(m,this._getType(p),l,n,o,k);},addFocusListener:function(l,k,m,n){return this.on(l,f,k,m,n);},removeFocusListener:function(l,k){return this.removeListener(l,f,k);},addBlurListener:function(l,k,m,n){return this.on(l,i,k,m,n);},removeBlurListener:function(l,k){return this.removeListener(l,i,k);},removeListener:function(l,k,r){var m,p,u;k=this._getType(k);if(typeof l=="string"){l=this.getEl(l);}else{if(this._isValidCollection(l)){var s=true;for(m=l.length-1;m>-1;m--){s=(this.removeListener(l[m],k,r)&&s);}return s;}}if(!r||!r.call){return this.purgeElement(l,false,k);}if("unload"==k){for(m=j.length-1;m>-1;m--){u=j[m];if(u&&u[0]==l&&u[1]==k&&u[2]==r){j.splice(m,1);return true;}}return false;}var n=null;var o=arguments[3];if("undefined"===typeof o){o=this._getCacheIndex(h,l,k,r);}if(o>=0){n=h[o];}if(!l||!n){return false;}var t=n[this.CAPTURE]===true?true:false;try{this._simpleRemove(l,k,n[this.WFN],t);}catch(q){this.lastError=q;return false;}delete h[o][this.WFN];delete h[o][this.FN];h.splice(o,1);return true;},getTarget:function(m,l){var k=m.target||m.srcElement;return this.resolveTextNode(k);},resolveTextNode:function(l){try{if(l&&3==l.nodeType){return l.parentNode;}}catch(k){return null;}return l;},getPageX:function(l){var k=l.pageX;if(!k&&0!==k){k=l.clientX||0;if(this.isIE){k+=this._getScrollLeft();}}return k;},getPageY:function(k){var l=k.pageY;if(!l&&0!==l){l=k.clientY||0;if(this.isIE){l+=this._getScrollTop();}}return l;},getXY:function(k){return[this.getPageX(k),this.getPageY(k)];},getRelatedTarget:function(l){var k=l.relatedTarget;
+if(!k){if(l.type=="mouseout"){k=l.toElement;}else{if(l.type=="mouseover"){k=l.fromElement;}}}return this.resolveTextNode(k);},getTime:function(m){if(!m.time){var l=new Date().getTime();try{m.time=l;}catch(k){this.lastError=k;return l;}}return m.time;},stopEvent:function(k){this.stopPropagation(k);this.preventDefault(k);},stopPropagation:function(k){if(k.stopPropagation){k.stopPropagation();}else{k.cancelBubble=true;}},preventDefault:function(k){if(k.preventDefault){k.preventDefault();}else{k.returnValue=false;}},getEvent:function(m,k){var l=m||window.event;if(!l){var n=this.getEvent.caller;while(n){l=n.arguments[0];if(l&&Event==l.constructor){break;}n=n.caller;}}return l;},getCharCode:function(l){var k=l.keyCode||l.charCode||0;if(YAHOO.env.ua.webkit&&(k in c)){k=c[k];}return k;},_getCacheIndex:function(n,q,r,p){for(var o=0,m=n.length;o<m;o=o+1){var k=n[o];if(k&&k[this.FN]==p&&k[this.EL]==q&&k[this.TYPE]==r){return o;}}return -1;},generateId:function(k){var l=k.id;if(!l){l="yuievtautoid-"+b;++b;k.id=l;}return l;},_isValidCollection:function(l){try{return(l&&typeof l!=="string"&&l.length&&!l.tagName&&!l.alert&&typeof l[0]!=="undefined");}catch(k){return false;}},elCache:{},getEl:function(k){return(typeof k==="string")?document.getElementById(k):k;},clearCache:function(){},DOMReadyEvent:new YAHOO.util.CustomEvent("DOMReady",YAHOO,0,0,1),_load:function(l){if(!g){g=true;var k=YAHOO.util.Event;k._ready();k._tryPreloadAttach();}},_ready:function(l){var k=YAHOO.util.Event;if(!k.DOMReady){k.DOMReady=true;k.DOMReadyEvent.fire();k._simpleRemove(document,"DOMContentLoaded",k._ready);}},_tryPreloadAttach:function(){if(e.length===0){a=0;if(this._interval){this._interval.cancel();this._interval=null;}return;}if(this.locked){return;}if(this.isIE){if(!this.DOMReady){this.startInterval();return;}}this.locked=true;var q=!g;if(!q){q=(a>0&&e.length>0);}var p=[];var r=function(t,u){var s=t;if(u.overrideContext){if(u.overrideContext===true){s=u.obj;}else{s=u.overrideContext;}}u.fn.call(s,u.obj);};var l,k,o,n,m=[];for(l=0,k=e.length;l<k;l=l+1){o=e[l];if(o){n=this.getEl(o.id);if(n){if(o.checkReady){if(g||n.nextSibling||!q){m.push(o);e[l]=null;}}else{r(n,o);e[l]=null;}}else{p.push(o);}}}for(l=0,k=m.length;l<k;l=l+1){o=m[l];r(this.getEl(o.id),o);}a--;if(q){for(l=e.length-1;l>-1;l--){o=e[l];if(!o||!o.id){e.splice(l,1);}}this.startInterval();}else{if(this._interval){this._interval.cancel();this._interval=null;}}this.locked=false;},purgeElement:function(p,q,s){var n=(YAHOO.lang.isString(p))?this.getEl(p):p;var r=this.getListeners(n,s),o,k;if(r){for(o=r.length-1;o>-1;o--){var m=r[o];this.removeListener(n,m.type,m.fn);}}if(q&&n&&n.childNodes){for(o=0,k=n.childNodes.length;o<k;++o){this.purgeElement(n.childNodes[o],q,s);}}},getListeners:function(n,k){var q=[],m;if(!k){m=[h,j];}else{if(k==="unload"){m=[j];}else{k=this._getType(k);m=[h];}}var s=(YAHOO.lang.isString(n))?this.getEl(n):n;for(var p=0;p<m.length;p=p+1){var u=m[p];if(u){for(var r=0,t=u.length;r<t;++r){var o=u[r];if(o&&o[this.EL]===s&&(!k||k===o[this.TYPE])){q.push({type:o[this.TYPE],fn:o[this.FN],obj:o[this.OBJ],adjust:o[this.OVERRIDE],scope:o[this.ADJ_SCOPE],index:r});}}}}return(q.length)?q:null;},_unload:function(s){var m=YAHOO.util.Event,p,o,n,r,q,t=j.slice(),k;for(p=0,r=j.length;p<r;++p){n=t[p];if(n){try{k=window;if(n[m.ADJ_SCOPE]){if(n[m.ADJ_SCOPE]===true){k=n[m.UNLOAD_OBJ];}else{k=n[m.ADJ_SCOPE];}}n[m.FN].call(k,m.getEvent(s,n[m.EL]),n[m.UNLOAD_OBJ]);}catch(w){}t[p]=null;}}n=null;k=null;j=null;if(h){for(o=h.length-1;o>-1;o--){n=h[o];if(n){try{m.removeListener(n[m.EL],n[m.TYPE],n[m.FN],o);}catch(v){}}}n=null;}try{m._simpleRemove(window,"unload",m._unload);m._simpleRemove(window,"load",m._load);}catch(u){}},_getScrollLeft:function(){return this._getScroll()[1];},_getScrollTop:function(){return this._getScroll()[0];},_getScroll:function(){var k=document.documentElement,l=document.body;if(k&&(k.scrollTop||k.scrollLeft)){return[k.scrollTop,k.scrollLeft];}else{if(l){return[l.scrollTop,l.scrollLeft];}else{return[0,0];}}},regCE:function(){},_simpleAdd:function(){if(window.addEventListener){return function(m,n,l,k){m.addEventListener(n,l,(k));};}else{if(window.attachEvent){return function(m,n,l,k){m.attachEvent("on"+n,l);};}else{return function(){};}}}(),_simpleRemove:function(){if(window.removeEventListener){return function(m,n,l,k){m.removeEventListener(n,l,(k));};}else{if(window.detachEvent){return function(l,m,k){l.detachEvent("on"+m,k);};}else{return function(){};}}}()};}();(function(){var a=YAHOO.util.Event;a.on=a.addListener;a.onFocus=a.addFocusListener;a.onBlur=a.addBlurListener;
+/*! DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller/Diego Perini */
+if(a.isIE){if(self!==self.top){document.onreadystatechange=function(){if(document.readyState=="complete"){document.onreadystatechange=null;a._ready();}};}else{YAHOO.util.Event.onDOMReady(YAHOO.util.Event._tryPreloadAttach,YAHOO.util.Event,true);var b=document.createElement("p");a._dri=setInterval(function(){try{b.doScroll("left");clearInterval(a._dri);a._dri=null;a._ready();b=null;}catch(c){}},a.POLL_INTERVAL);}}else{if(a.webkit&&a.webkit<525){a._dri=setInterval(function(){var c=document.readyState;if("loaded"==c||"complete"==c){clearInterval(a._dri);a._dri=null;a._ready();}},a.POLL_INTERVAL);}else{a._simpleAdd(document,"DOMContentLoaded",a._ready);}}a._simpleAdd(window,"load",a._load);a._simpleAdd(window,"unload",a._unload);a._tryPreloadAttach();})();}YAHOO.util.EventProvider=function(){};YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(a,c,f,e){this.__yui_events=this.__yui_events||{};var d=this.__yui_events[a];if(d){d.subscribe(c,f,e);}else{this.__yui_subscribers=this.__yui_subscribers||{};var b=this.__yui_subscribers;if(!b[a]){b[a]=[];}b[a].push({fn:c,obj:f,overrideContext:e});}},unsubscribe:function(c,e,g){this.__yui_events=this.__yui_events||{};var a=this.__yui_events;if(c){var f=a[c];if(f){return f.unsubscribe(e,g);}}else{var b=true;for(var d in a){if(YAHOO.lang.hasOwnProperty(a,d)){b=b&&a[d].unsubscribe(e,g);
+}}return b;}return false;},unsubscribeAll:function(a){return this.unsubscribe(a);},createEvent:function(b,g){this.__yui_events=this.__yui_events||{};var e=g||{},d=this.__yui_events,f;if(d[b]){}else{f=new YAHOO.util.CustomEvent(b,e.scope||this,e.silent,YAHOO.util.CustomEvent.FLAT,e.fireOnce);d[b]=f;if(e.onSubscribeCallback){f.subscribeEvent.subscribe(e.onSubscribeCallback);}this.__yui_subscribers=this.__yui_subscribers||{};var a=this.__yui_subscribers[b];if(a){for(var c=0;c<a.length;++c){f.subscribe(a[c].fn,a[c].obj,a[c].overrideContext);}}}return d[b];},fireEvent:function(b){this.__yui_events=this.__yui_events||{};var d=this.__yui_events[b];if(!d){return null;}var a=[];for(var c=1;c<arguments.length;++c){a.push(arguments[c]);}return d.fire.apply(d,a);},hasEvent:function(a){if(this.__yui_events){if(this.__yui_events[a]){return true;}}return false;}};(function(){var a=YAHOO.util.Event,c=YAHOO.lang;YAHOO.util.KeyListener=function(d,i,e,f){if(!d){}else{if(!i){}else{if(!e){}}}if(!f){f=YAHOO.util.KeyListener.KEYDOWN;}var g=new YAHOO.util.CustomEvent("keyPressed");this.enabledEvent=new YAHOO.util.CustomEvent("enabled");this.disabledEvent=new YAHOO.util.CustomEvent("disabled");if(c.isString(d)){d=document.getElementById(d);}if(c.isFunction(e)){g.subscribe(e);}else{g.subscribe(e.fn,e.scope,e.correctScope);}function h(o,n){if(!i.shift){i.shift=false;}if(!i.alt){i.alt=false;}if(!i.ctrl){i.ctrl=false;}if(o.shiftKey==i.shift&&o.altKey==i.alt&&o.ctrlKey==i.ctrl){var j,m=i.keys,l;if(YAHOO.lang.isArray(m)){for(var k=0;k<m.length;k++){j=m[k];l=a.getCharCode(o);if(j==l){g.fire(l,o);break;}}}else{l=a.getCharCode(o);if(m==l){g.fire(l,o);}}}}this.enable=function(){if(!this.enabled){a.on(d,f,h);this.enabledEvent.fire(i);}this.enabled=true;};this.disable=function(){if(this.enabled){a.removeListener(d,f,h);this.disabledEvent.fire(i);}this.enabled=false;};this.toString=function(){return"KeyListener ["+i.keys+"] "+d.tagName+(d.id?"["+d.id+"]":"");};};var b=YAHOO.util.KeyListener;b.KEYDOWN="keydown";b.KEYUP="keyup";b.KEY={ALT:18,BACK_SPACE:8,CAPS_LOCK:20,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,META:224,NUM_LOCK:144,PAGE_DOWN:34,PAGE_UP:33,PAUSE:19,PRINTSCREEN:44,RIGHT:39,SCROLL_LOCK:145,SHIFT:16,SPACE:32,TAB:9,UP:38};})();YAHOO.register("event",YAHOO.util.Event,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/fonts/fonts-min.css b/Websites/bugs.webkit.org/js/yui/fonts/fonts-min.css
new file mode 100644
index 0000000..0eb29e0
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/fonts/fonts-min.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small}select,input,textarea,button{font:99% arial,helvetica,clean,sans-serif}table{font-size:inherit;font:100%}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%}
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/fonts/fonts.css b/Websites/bugs.webkit.org/js/yui/fonts/fonts.css
new file mode 100644
index 0000000..b170cd7
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/fonts/fonts.css
@@ -0,0 +1,55 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+/**
+ * YUI Fonts
+ * @module fonts
+ * @namespace yui-
+ * @requires
+ */
+
+/**
+ * Percents could work for IE, but for backCompat purposes, we are using keywords.
+ * x-small is for IE6/7 quirks mode.
+ */
+body {
+ font:13px/1.231 arial,helvetica,clean,sans-serif;
+ /* for IE6/7 */
+ *font-size:small;
+ /* for IE Quirks Mode */
+ *font:x-small;
+}
+
+/**
+ * Nudge down to get to 13px equivalent for these form elements
+ */
+select,
+input,
+textarea,
+button {
+ font:99% arial,helvetica,clean,sans-serif;
+}
+
+/**
+ * To help tables remember to inherit
+ */
+table {
+ font-size:inherit;
+ font:100%;
+}
+
+/**
+ * Bump up IE to get to 13px equivalent for these fixed-width elements
+ */
+pre,
+code,
+kbd,
+samp,
+tt {
+ font-family:monospace;
+ *font-size:108%;
+ line-height:100%;
+}
diff --git a/Websites/bugs.webkit.org/js/yui/get/get-min.js b/Websites/bugs.webkit.org/js/yui/get/get-min.js
new file mode 100644
index 0000000..1fdcc49
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/get/get-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.Get=function(){var m={},k=0,r=0,l=false,n=YAHOO.env.ua,s=YAHOO.lang,q,d,e,i=function(x,t,y){var u=y||window,z=u.document,A=z.createElement(x),v;for(v in t){if(t.hasOwnProperty(v)){A.setAttribute(v,t[v]);}}return A;},h=function(u,v,t){var w={id:"yui__dyn_"+(r++),type:"text/css",rel:"stylesheet",href:u};if(t){s.augmentObject(w,t);}return i("link",w,v);},p=function(u,v,t){var w={id:"yui__dyn_"+(r++),type:"text/javascript",src:u};if(t){s.augmentObject(w,t);}return i("script",w,v);},a=function(t,u){return{tId:t.tId,win:t.win,data:t.data,nodes:t.nodes,msg:u,purge:function(){d(this.tId);}};},b=function(t,w){var u=m[w],v=(s.isString(t))?u.win.document.getElementById(t):t;if(!v){q(w,"target node not found: "+t);}return v;},c=function(w){var u=m[w],v,t;u.finished=true;if(u.aborted){v="transaction "+w+" was aborted";q(w,v);return;}if(u.onSuccess){t=u.scope||u.win;u.onSuccess.call(t,a(u));}},o=function(v){var u=m[v],t;if(u.onTimeout){t=u.scope||u;u.onTimeout.call(t,a(u));}},f=function(v,A){var u=m[v],D=u.win,C=D.document,B=C.getElementsByTagName("head")[0],x,y,t,E,z;if(u.timer){u.timer.cancel();}if(u.aborted){y="transaction "+v+" was aborted";q(v,y);return;}if(A){u.url.shift();if(u.varName){u.varName.shift();}}else{u.url=(s.isString(u.url))?[u.url]:u.url;if(u.varName){u.varName=(s.isString(u.varName))?[u.varName]:u.varName;}}if(u.url.length===0){if(u.type==="script"&&n.webkit&&n.webkit<420&&!u.finalpass&&!u.varName){z=p(null,u.win,u.attributes);z.innerHTML='YAHOO.util.Get._finalize("'+v+'");';u.nodes.push(z);B.appendChild(z);}else{c(v);}return;}t=u.url[0];if(!t){u.url.shift();return f(v);}if(u.timeout){u.timer=s.later(u.timeout,u,o,v);}if(u.type==="script"){x=p(t,D,u.attributes);}else{x=h(t,D,u.attributes);}e(u.type,x,v,t,D,u.url.length);u.nodes.push(x);if(u.insertBefore){E=b(u.insertBefore,v);if(E){E.parentNode.insertBefore(x,E);}}else{B.appendChild(x);}if((n.webkit||n.gecko)&&u.type==="css"){f(v,t);}},j=function(){if(l){return;}l=true;var t,u;for(t in m){if(m.hasOwnProperty(t)){u=m[t];if(u.autopurge&&u.finished){d(u.tId);delete m[t];}}}l=false;},g=function(u,t,v){var x="q"+(k++),w;v=v||{};if(k%YAHOO.util.Get.PURGE_THRESH===0){j();}m[x]=s.merge(v,{tId:x,type:u,url:t,finished:false,aborted:false,nodes:[]});w=m[x];w.win=w.win||window;w.scope=w.scope||w.win;w.autopurge=("autopurge" in w)?w.autopurge:(u==="script")?true:false;w.attributes=w.attributes||{};w.attributes.charset=v.charset||w.attributes.charset||"utf-8";s.later(0,w,f,x);return{tId:x};};e=function(H,z,x,u,D,E,G){var F=G||f,B,t,I,v,J,A,C,y;if(n.ie){z.onreadystatechange=function(){B=this.readyState;if("loaded"===B||"complete"===B){z.onreadystatechange=null;F(x,u);}};}else{if(n.webkit){if(H==="script"){if(n.webkit>=420){z.addEventListener("load",function(){F(x,u);});}else{t=m[x];if(t.varName){v=YAHOO.util.Get.POLL_FREQ;t.maxattempts=YAHOO.util.Get.TIMEOUT/v;t.attempts=0;t._cache=t.varName[0].split(".");t.timer=s.later(v,t,function(w){I=this._cache;A=I.length;J=this.win;for(C=0;C<A;C=C+1){J=J[I[C]];if(!J){this.attempts++;if(this.attempts++>this.maxattempts){y="Over retry limit, giving up";t.timer.cancel();q(x,y);}else{}return;}}t.timer.cancel();F(x,u);},null,true);}else{s.later(YAHOO.util.Get.POLL_FREQ,null,F,[x,u]);}}}}else{z.onload=function(){F(x,u);};}}};q=function(w,v){var u=m[w],t;if(u.onFailure){t=u.scope||u.win;u.onFailure.call(t,a(u,v));}};d=function(z){if(m[z]){var t=m[z],u=t.nodes,x=u.length,C=t.win.document,A=C.getElementsByTagName("head")[0],v,y,w,B;if(t.insertBefore){v=b(t.insertBefore,z);if(v){A=v.parentNode;}}for(y=0;y<x;y=y+1){w=u[y];if(w.clearAttributes){w.clearAttributes();}else{for(B in w){if(w.hasOwnProperty(B)){delete w[B];}}}A.removeChild(w);}t.nodes=[];}};return{POLL_FREQ:10,PURGE_THRESH:20,TIMEOUT:2000,_finalize:function(t){s.later(0,null,c,t);},abort:function(u){var v=(s.isString(u))?u:u.tId,t=m[v];if(t){t.aborted=true;}},script:function(t,u){return g("script",t,u);},css:function(t,u){return g("css",t,u);}};}();YAHOO.register("get",YAHOO.util.Get,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/grids/grids-min.css b/Websites/bugs.webkit.org/js/yui/grids/grids-min.css
new file mode 100644
index 0000000..3a172de
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/grids/grids-min.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+body{text-align:center}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em}#doc2{width:73.076em;*width:71.25em}#doc3{margin:auto 10px;width:auto}#doc4{width:74.923em;*width:73.05em}.yui-b{position:relative}.yui-b{_position:static}#yui-main .yui-b{position:static}#yui-main,.yui-g .yui-u .yui-g{width:100%}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0}#yui-main .yui-b{float:none;width:auto}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%}.yui-gc div.first,.yui-gd .yui-u{width:66%}.yui-gd div.first{width:32%}.yui-ge div.first,.yui-gf .yui-u{width:74.2%}.yui-ge .yui-u,.yui-gf div.first{width:24%}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0}.yui-g .yui-g .yui-u{width:48.1%}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%}.yui-g .yui-gb .yui-u{_margin-left:1.0%}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0}.yui-gb .yui-gb .yui-u{_margin-left:.7%}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0}.yui-gb .yui-gd div.first{width:32%}.yui-g .yui-gd div.first{_width:29.9%}.yui-ge .yui-g{width:24%}.yui-gf .yui-g{width:74.2%}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%}.yui-ge div.first .yui-gd .yui-u{width:65%}.yui-ge div.first .yui-gd div.first{width:32%}#hd:after,#bd:after,#ft:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:"";display:block;clear:both}#hd,#bd,#ft,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1}
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/grids/grids.css b/Websites/bugs.webkit.org/js/yui/grids/grids.css
new file mode 100644
index 0000000..c5407c8
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/grids/grids.css
@@ -0,0 +1,465 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+/**
+ * YUI Grids
+ * @module grids
+ * @namespace yui-
+ * @requires reset, fonts
+ */
+
+/**
+ * Note: Throughout this file, the *property (star-property) filter is used
+ * to give a value to IE < 8 that other browsers do not see. The _property (underscore-property)
+ * is only seen by IE < 7, so the combo of *prop and _prop can differentiate between IE6 and IE7.
+ *
+ * More information on these filters and related validation errors:
+ * http://tech.groups.yahoo.com/group/ydn-javascript/message/40059
+ */
+
+/**
+ * Section: General Rules
+ */
+
+body {
+ text-align: center;
+}
+
+/**
+ * Section: Page Width Rules (#doc, #doc2, #doc3, #doc4)
+ */
+
+#doc,#doc2,#doc3,#doc4,
+.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7 {
+ margin: auto;
+ text-align: left;
+ width: 57.69em;
+ *width: 56.25em;
+}
+
+/* 950 Centered (doc2) */
+#doc2 {
+ width: 73.076em;
+ *width: 71.25em;
+}
+
+/* 100% (doc3) */
+#doc3 {
+/**
+ * Left and Right margins are not a structural part of Grids. Without them
+ * Grids works fine, but content bleeds to the very edge of the document, which
+ * often impairs readability and usability. They are provided because they
+ * prevent the content from "bleeding" into the browser's chrome.
+ */
+ margin: auto 10px;
+ width: auto;
+}
+
+/* 974 Centered (doc4) */
+#doc4 {
+ width: 74.923em;
+ *width: 73.05em;
+}
+
+/**
+ * Section: Preset Template Rules (.yui-t[1-6])
+ */
+
+
+.yui-b {
+ /* to preserve source-order independence for Gecko */
+ position: relative;
+}
+
+.yui-b {
+ /* to preserve source-order independence for IE */
+ _position: static;
+}
+
+#yui-main .yui-b {
+ /* to preserve source-order independence for Gecko */
+ position: static;
+}
+
+#yui-main,
+.yui-g .yui-u .yui-g {
+ width: 100%;
+}
+
+.yui-t1 #yui-main,
+.yui-t2 #yui-main,
+.yui-t3 #yui-main {
+ float: right;
+ /* IE: preserve layout at narrow widths */
+ margin-left: -25em;
+}
+
+.yui-t4 #yui-main,
+.yui-t5 #yui-main,
+.yui-t6 #yui-main {
+ float: left;
+ /* IE: preserve layout at narrow widths */
+ margin-right: -25em;
+}
+
+/**
+ * For Specific Template Presets
+ */
+
+.yui-t1 .yui-b {
+ float: left;
+ width: 12.30769em;
+ *width: 12.00em;
+}
+
+.yui-t1 #yui-main .yui-b {
+ margin-left: 13.30769em;
+ *margin-left: 13.05em;
+}
+
+.yui-t2 .yui-b {
+ float: left;
+ width: 13.8461em;
+ *width: 13.50em;
+}
+
+.yui-t2 #yui-main .yui-b {
+ margin-left: 14.8461em;
+ *margin-left: 14.55em;
+}
+
+.yui-t3 .yui-b {
+ float: left;
+ width: 23.0769em;
+ *width: 22.50em;
+}
+
+.yui-t3 #yui-main .yui-b {
+ margin-left: 24.0769em;
+ *margin-left: 23.62em;
+}
+
+.yui-t4 .yui-b {
+ float: right;
+ width: 13.8456em;
+ *width: 13.50em;
+}
+
+.yui-t4 #yui-main .yui-b {
+ margin-right: 14.8456em;
+ *margin-right: 14.55em;
+}
+
+.yui-t5 .yui-b {
+ float: right;
+ width: 18.4615em;
+ *width: 18.00em;
+}
+
+.yui-t5 #yui-main .yui-b {
+ margin-right: 19.4615em;
+ *margin-right: 19.125em;
+}
+
+.yui-t6 .yui-b {
+ float: right;
+ width: 23.0769em;
+ *width: 22.50em;
+}
+
+.yui-t6 #yui-main .yui-b {
+ margin-right: 24.0769em;
+ *margin-right: 23.62em;
+}
+
+.yui-t7 #yui-main .yui-b {
+ display: block;
+ margin: 0 0 1em 0;
+}
+
+#yui-main .yui-b {
+ float: none;
+ width: auto;
+}
+
+/**
+ * Section: Grids and Nesting Grids
+ */
+
+/* Children generally take half the available space */
+.yui-gb .yui-u,
+.yui-g .yui-gb .yui-u,
+.yui-gb .yui-g,
+.yui-gb .yui-gb,
+.yui-gb .yui-gc,
+.yui-gb .yui-gd,
+.yui-gb .yui-ge,
+.yui-gb .yui-gf,
+.yui-gc .yui-u,
+.yui-gc .yui-g,
+.yui-gd .yui-u {
+ float: left;
+}
+
+/* Float units (and sub grids) to the right */
+.yui-g .yui-u,
+.yui-g .yui-g,
+.yui-g .yui-gb,
+.yui-g .yui-gc,
+.yui-g .yui-gd,
+.yui-g .yui-ge,
+.yui-g .yui-gf,
+.yui-gc .yui-u,
+.yui-gd .yui-g,
+.yui-g .yui-gc .yui-u,
+.yui-ge .yui-u,
+.yui-ge .yui-g,
+.yui-gf .yui-g,
+.yui-gf .yui-u {
+ float: right;
+}
+
+/*Float units (and sub grids) to the left */
+.yui-g div.first,
+.yui-gb div.first,
+.yui-gc div.first,
+.yui-gd div.first,
+.yui-ge div.first,
+.yui-gf div.first,
+.yui-g .yui-gc div.first,
+.yui-g .yui-ge div.first,
+.yui-gc div.first div.first {
+ float: left;
+}
+
+.yui-g .yui-u,
+.yui-g .yui-g,
+.yui-g .yui-gb,
+.yui-g .yui-gc,
+.yui-g .yui-gd,
+.yui-g .yui-ge,
+.yui-g .yui-gf {
+ width: 49.1%;
+}
+
+.yui-gb .yui-u,
+.yui-g .yui-gb .yui-u,
+.yui-gb .yui-g,
+.yui-gb .yui-gb,
+.yui-gb .yui-gc,
+.yui-gb .yui-gd,
+.yui-gb .yui-ge,
+.yui-gb .yui-gf,
+.yui-gc .yui-u,
+.yui-gc .yui-g,
+.yui-gd .yui-u {
+ width: 32%;
+ margin-left: 1.99%;
+}
+
+/* Give IE some extra breathing room for 1/3-based rounding issues */
+.yui-gb .yui-u {
+ *margin-left: 1.9%;
+ *width: 31.9%;
+}
+
+.yui-gc div.first,
+ .yui-gd .yui-u {
+ width: 66%;
+}
+
+.yui-gd div.first {
+ width: 32%;
+}
+
+.yui-ge div.first,
+ .yui-gf .yui-u {
+ width: 74.2%;
+}
+
+.yui-ge .yui-u,
+ .yui-gf div.first {
+ width: 24%;
+}
+
+.yui-g .yui-gb div.first,
+.yui-gb div.first,
+.yui-gc div.first,
+.yui-gd div.first {
+ margin-left: 0;
+}
+
+/**
+ * Section: Deep Nesting
+ */
+
+.yui-g .yui-g .yui-u,
+.yui-gb .yui-g .yui-u,
+.yui-gc .yui-g .yui-u,
+.yui-gd .yui-g .yui-u,
+.yui-ge .yui-g .yui-u,
+.yui-gf .yui-g .yui-u {
+ width: 49%;
+ *width: 48.1%;
+ *margin-left: 0;
+}
+
+.yui-g .yui-g .yui-u {
+ width: 48.1%;
+}
+
+/* YUILibrary bug #1927599 from 1.14 to 2.6.0*/
+.yui-g .yui-gb div.first,
+ .yui-gb .yui-gb div.first {
+ *margin-right: 0;
+ *width: 32%;
+ _width: 31.7%;
+}
+
+.yui-g .yui-gc div.first,
+ .yui-gd .yui-g {
+ width: 66%;
+}
+
+.yui-gb .yui-g div.first {
+ *margin-right: 4%;
+ _margin-right: 1.3%;
+}
+
+.yui-gb .yui-gc div.first,
+ .yui-gb .yui-gd div.first {
+ *margin-right: 0;
+}
+
+.yui-gb .yui-gb .yui-u,
+ .yui-gb .yui-gc .yui-u {
+ *margin-left: 1.8%;
+ _margin-left: 4%;
+}
+
+.yui-g .yui-gb .yui-u {
+ _margin-left: 1.0%;
+}
+
+.yui-gb .yui-gd .yui-u {
+ *width: 66%;
+ _width: 61.2%;
+}
+
+.yui-gb .yui-gd div.first {
+ *width: 31%;
+ _width: 29.5%;
+}
+
+.yui-g .yui-gc .yui-u,
+ .yui-gb .yui-gc .yui-u {
+ width: 32%;
+ _float: right;
+ margin-right: 0;
+ _margin-left: 0;
+}
+
+.yui-gb .yui-gc div.first {
+ width: 66%;
+ *float: left;
+ *margin-left: 0;
+}
+
+.yui-gb .yui-ge .yui-u,
+ .yui-gb .yui-gf .yui-u {
+ margin: 0;
+}
+
+.yui-gb .yui-gb .yui-u {
+ _margin-left: .7%;
+}
+
+.yui-gb .yui-g div.first,
+ .yui-gb .yui-gb div.first {
+ *margin-left: 0;
+}
+
+.yui-gc .yui-g .yui-u,
+ .yui-gd .yui-g .yui-u {
+ *width: 48.1%;
+ *margin-left: 0;
+}
+
+.yui-gb .yui-gd div.first {
+ width: 32%;
+}
+
+.yui-g .yui-gd div.first {
+ _width: 29.9%;
+}
+
+.yui-ge .yui-g {
+ width: 24%;
+}
+
+.yui-gf .yui-g {
+ width: 74.2%;
+}
+
+.yui-gb .yui-ge div.yui-u,
+ .yui-gb .yui-gf div.yui-u {
+ float: right;
+}
+
+.yui-gb .yui-ge div.first,
+ .yui-gb .yui-gf div.first {
+ float: left;
+}
+
+/* Width Accommodation for Nested Contexts */
+.yui-gb .yui-ge .yui-u,
+ .yui-gb .yui-gf div.first {
+ *width: 24%;
+ _width: 20%;
+}
+
+/* Width Accommodation for Nested Contexts */
+.yui-gb .yui-ge div.first,
+ .yui-gb .yui-gf .yui-u {
+ *width: 73.5%;
+ _width: 65.5%;
+}
+
+/* Patch for GD within GE */
+.yui-ge div.first .yui-gd .yui-u {
+ width: 65%;
+}
+
+.yui-ge div.first .yui-gd div.first {
+ width: 32%;
+}
+
+/* @group Clearing */
+#hd:after,
+#bd:after,
+#ft:after,
+.yui-g:after,
+.yui-gb:after,
+.yui-gc:after,
+.yui-gd:after,
+.yui-ge:after,
+.yui-gf:after {
+ content: "";
+ display: block;
+ clear: both;
+}
+
+#hd,
+#bd,
+#ft,
+.yui-g,
+.yui-gb,
+.yui-gc,
+.yui-gd,
+.yui-ge,
+.yui-gf {
+ zoom: 1;
+}
diff --git a/Websites/bugs.webkit.org/js/yui/history/history-min.js b/Websites/bugs.webkit.org/js/yui/history/history-min.js
new file mode 100644
index 0000000..424256c
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/history/history-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.History=(function(){var d=null;var m=null;var g=false;var e=[];var c=[];function k(){var o,n;n=self.location.href;o=n.indexOf("#");return o>=0?n.substr(o+1):null;}function b(){var o,p,q=[],n=[];for(o in e){if(YAHOO.lang.hasOwnProperty(e,o)){p=e[o];q.push(o+"="+p.initialState);n.push(o+"="+p.currentState);}}m.value=q.join("&")+"|"+n.join("&");}function j(n){var s,t,o,q,r,v,u,p;if(!n){for(o in e){if(YAHOO.lang.hasOwnProperty(e,o)){q=e[o];q.currentState=q.initialState;q.onStateChange(i(q.currentState));}}return;}r=[];v=n.split("&");for(s=0,t=v.length;s<t;s++){u=v[s].split("=");if(u.length===2){o=u[0];p=u[1];r[o]=p;}}for(o in e){if(YAHOO.lang.hasOwnProperty(e,o)){q=e[o];p=r[o];if(!p||q.currentState!==p){q.currentState=typeof p==="undefined"?q.initialState:p;q.onStateChange(i(q.currentState));}}}}function l(q){var n,p;n='<html><body><div id="state">'+YAHOO.lang.escapeHTML(q)+"</div></body></html>";try{p=d.contentWindow.document;p.open();p.write(n);p.close();return true;}catch(o){return false;}}function h(){var q,n,p,o;if(!d.contentWindow||!d.contentWindow.document){setTimeout(h,10);return;}q=d.contentWindow.document;n=q.getElementById("state");p=n?n.innerText:null;o=k();setInterval(function(){var w,s,t,u,v,r;q=d.contentWindow.document;n=q.getElementById("state");w=n?n.innerText:null;v=k();if(w!==p){p=w;j(p);if(!p){s=[];for(t in e){if(YAHOO.lang.hasOwnProperty(e,t)){u=e[t];s.push(t+"="+u.initialState);}}v=s.join("&");}else{v=p;}self.location.hash=v;o=v;b();}else{if(v!==o){o=v;l(v);}}},50);g=true;YAHOO.util.History.onLoadEvent.fire();}function f(){var u,w,s,y,o,q,x,r,v,p,n,t;s=m.value.split("|");if(s.length>1){x=s[0].split("&");for(u=0,w=x.length;u<w;u++){y=x[u].split("=");if(y.length===2){o=y[0];r=y[1];q=YAHOO.lang.hasOwnProperty(e,o)&&e[o];if(q){q.initialState=r;}}}v=s[1].split("&");for(u=0,w=v.length;u<w;u++){y=v[u].split("=");if(y.length>=2){o=y[0];p=y[1];q=YAHOO.lang.hasOwnProperty(e,o)&&e[o];if(q){q.currentState=p;}}}}if(s.length>2){c=s[2].split(",");}if(YAHOO.env.ua.ie){if(typeof document.documentMode==="undefined"||document.documentMode<8){h();}else{YAHOO.util.Event.on(top,"hashchange",function(){var z=k();j(z);b();});g=true;YAHOO.util.History.onLoadEvent.fire();}}else{t=k();setInterval(function(){var B,z,A;z=k();if(z!==t){t=z;j(t);b();}},50);g=true;YAHOO.util.History.onLoadEvent.fire();}}function i(n){return decodeURIComponent(n.replace(/\+/g," "));}function a(n){return encodeURIComponent(n).replace(/%20/g,"+");}return{onLoadEvent:new YAHOO.util.CustomEvent("onLoad"),onReady:function(n,o,p){if(g){setTimeout(function(){var q=window;if(p){if(p===true){q=o;}else{q=p;}}n.call(q,"onLoad",[],o);},0);}else{YAHOO.util.History.onLoadEvent.subscribe(n,o,p);}},register:function(p,n,r,s,t){var q,o;if(typeof p!=="string"||YAHOO.lang.trim(p)===""||typeof n!=="string"||typeof r!=="function"){throw new Error("Missing or invalid argument");}if(YAHOO.lang.hasOwnProperty(e,p)){return;}if(g){throw new Error("All modules must be registered before calling YAHOO.util.History.initialize");}p=a(p);n=a(n);q=null;if(t===true){q=s;}else{q=t;}o=function(u){return r.call(q,u,s);};e[p]={name:p,initialState:n,currentState:n,onStateChange:o};},initialize:function(n,o){if(g){return;}if(YAHOO.env.ua.opera&&typeof history.navigationMode!=="undefined"){history.navigationMode="compatible";}if(typeof n==="string"){n=document.getElementById(n);}if(!n||n.tagName.toUpperCase()!=="TEXTAREA"&&(n.tagName.toUpperCase()!=="INPUT"||n.type!=="hidden"&&n.type!=="text")){throw new Error("Missing or invalid argument");}m=n;if(YAHOO.env.ua.ie&&(typeof document.documentMode==="undefined"||document.documentMode<8)){if(typeof o==="string"){o=document.getElementById(o);}if(!o||o.tagName.toUpperCase()!=="IFRAME"){throw new Error("Missing or invalid argument");}d=o;}YAHOO.util.Event.onDOMReady(f);},navigate:function(o,p){var n;if(typeof o!=="string"||typeof p!=="string"){throw new Error("Missing or invalid argument");}n={};n[o]=p;return YAHOO.util.History.multiNavigate(n);},multiNavigate:function(o){var n,p,r,q,s;if(typeof o!=="object"){throw new Error("Missing or invalid argument");}if(!g){throw new Error("The Browser History Manager is not initialized");}for(p in o){if(!YAHOO.lang.hasOwnProperty(e,a(p))){throw new Error("The following module has not been registered: "+p);}}n=[];for(p in e){if(YAHOO.lang.hasOwnProperty(e,p)){r=e[p];if(YAHOO.lang.hasOwnProperty(o,p)){q=o[i(p)];}else{q=i(r.currentState);}p=a(p);q=a(q);n.push(p+"="+q);}}s=n.join("&");if(YAHOO.env.ua.ie&&(typeof document.documentMode==="undefined"||document.documentMode<8)){return l(s);}else{self.location.hash=s;return true;}},getCurrentState:function(n){var o;if(typeof n!=="string"){throw new Error("Missing or invalid argument");}if(!g){throw new Error("The Browser History Manager is not initialized");}o=YAHOO.lang.hasOwnProperty(e,n)&&e[n];if(!o){throw new Error("No such registered module: "+n);}return i(o.currentState);},getBookmarkedState:function(s){var r,o,n,u,p,t,q;if(typeof s!=="string"){throw new Error("Missing or invalid argument");}n=self.location.href.indexOf("#");if(n>=0){u=self.location.href.substr(n+1);p=u.split("&");for(r=0,o=p.length;r<o;r++){t=p[r].split("=");if(t.length===2){q=t[0];if(q===s){return i(t[1]);}}}}return null;},getQueryStringParameter:function(s,p){var q,o,n,u,t,r;p=p||self.location.href;n=p.indexOf("?");u=n>=0?p.substr(n+1):p;n=u.lastIndexOf("#");u=n>=0?u.substr(0,n):u;t=u.split("&");for(q=0,o=t.length;q<o;q++){r=t[q].split("=");if(r.length>=2){if(r[0]===s){return i(r[1]);}}}return null;}};})();YAHOO.register("history",YAHOO.util.History,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/imagecropper/imagecropper-min.js b/Websites/bugs.webkit.org/js/yui/imagecropper/imagecropper-min.js
new file mode 100644
index 0000000..10cbba6
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/imagecropper/imagecropper-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var C=YAHOO.util.Dom,A=YAHOO.util.Event,D=YAHOO.lang;var B=function(F,E){var G={element:F,attributes:E||{}};B.superclass.constructor.call(this,G.element,G.attributes);};B._instances={};B.getCropperById=function(E){if(B._instances[E]){return B._instances[E];}return false;};YAHOO.extend(B,YAHOO.util.Element,{CSS_MAIN:"yui-crop",CSS_MASK:"yui-crop-mask",CSS_RESIZE_MASK:"yui-crop-resize-mask",_image:null,_active:null,_resize:null,_resizeEl:null,_resizeMaskEl:null,_wrap:null,_mask:null,_createWrap:function(){this._wrap=document.createElement("div");this._wrap.id=this.get("element").id+"_wrap";this._wrap.className=this.CSS_MAIN;var F=this.get("element");this._wrap.style.width=F.width?F.width+"px":C.getStyle(F,"width");this._wrap.style.height=F.height?F.height+"px":C.getStyle(F,"height");var E=this.get("element").parentNode;E.replaceChild(this._wrap,this.get("element"));this._wrap.appendChild(this.get("element"));A.on(this._wrap,"mouseover",this._handleMouseOver,this,true);A.on(this._wrap,"mouseout",this._handleMouseOut,this,true);A.on(this._wrap,"click",function(G){A.stopEvent(G);},this,true);},_createMask:function(){this._mask=document.createElement("div");this._mask.className=this.CSS_MASK;this._wrap.appendChild(this._mask);},_createResize:function(){this._resizeEl=document.createElement("div");this._resizeEl.className=YAHOO.util.Resize.prototype.CSS_RESIZE;this._resizeEl.style.position="absolute";this._resizeEl.innerHTML='<div class="'+this.CSS_RESIZE_MASK+'"></div>';this._resizeMaskEl=this._resizeEl.firstChild;this._wrap.appendChild(this._resizeEl);this._resizeEl.style.top=this.get("initialXY")[1]+"px";this._resizeEl.style.left=this.get("initialXY")[0]+"px";this._resizeMaskEl.style.height=Math.floor(this.get("initHeight"))+"px";this._resizeMaskEl.style.width=Math.floor(this.get("initWidth"))+"px";this._resize=new YAHOO.util.Resize(this._resizeEl,{knobHandles:true,handles:"all",draggable:true,status:this.get("status"),minWidth:this.get("minWidth"),minHeight:this.get("minHeight"),ratio:this.get("ratio"),autoRatio:this.get("autoRatio"),height:this.get("initHeight"),width:this.get("initWidth")});this._setBackgroundImage(this.get("element").getAttribute("src",2));this._setBackgroundPosition(-(this.get("initialXY")[0]),-(this.get("initialXY")[1]));this._resize.on("startResize",this._handleStartResizeEvent,this,true);this._resize.on("endResize",this._handleEndResizeEvent,this,true);this._resize.on("dragEvent",this._handleDragEvent,this,true);this._resize.on("beforeResize",this._handleBeforeResizeEvent,this,true);this._resize.on("resize",this._handleResizeEvent,this,true);this._resize.dd.on("b4StartDragEvent",this._handleB4DragEvent,this,true);},_handleMouseOver:function(F){var E="keydown";if(YAHOO.env.ua.gecko||YAHOO.env.ua.opera){E="keypress";}if(!this._active){this._active=true;if(this.get("useKeys")){A.on(document,E,this._handleKeyPress,this,true);}}},_handleMouseOut:function(F){var E="keydown";if(YAHOO.env.ua.gecko||YAHOO.env.ua.opera){E="keypress";}this._active=false;if(this.get("useKeys")){A.removeListener(document,E,this._handleKeyPress);}},_moveEl:function(G,J){var H=0,E=0,I=this._setConstraints(),F=true;switch(G){case"down":H=-(J);if((I.bottom-J)<0){F=false;this._resizeEl.style.top=(I.top+I.bottom)+"px";}break;case"up":H=(J);if((I.top-J)<0){F=false;this._resizeEl.style.top="0px";}break;case"right":E=-(J);if((I.right-J)<0){F=false;this._resizeEl.style.left=(I.left+I.right)+"px";}break;case"left":E=J;if((I.left-J)<0){F=false;this._resizeEl.style.left="0px";}break;}if(F){this._resizeEl.style.left=(parseInt(this._resizeEl.style.left,10)-E)+"px";this._resizeEl.style.top=(parseInt(this._resizeEl.style.top,10)-H)+"px";this.fireEvent("moveEvent",{target:"keypress"});}else{this._setConstraints();}this._syncBackgroundPosition();},_handleKeyPress:function(G){var E=A.getCharCode(G),F=false,H=((G.shiftKey)?this.get("shiftKeyTick"):this.get("keyTick"));switch(E){case 37:this._moveEl("left",H);F=true;break;case 38:this._moveEl("up",H);F=true;break;case 39:this._moveEl("right",H);F=true;break;case 40:this._moveEl("down",H);F=true;break;default:}if(F){A.preventDefault(G);}},_handleB4DragEvent:function(){this._setConstraints();},_handleDragEvent:function(){this._syncBackgroundPosition();this.fireEvent("dragEvent",arguments);this.fireEvent("moveEvent",{target:"dragevent"});},_handleBeforeResizeEvent:function(F){var I=C.getRegion(this.get("element")),J=this._resize._cache,H=this._resize._currentHandle,G=0,E=0;if(F.top&&(F.top<I.top)){G=(J.height+J.top)-I.top;C.setY(this._resize.getWrapEl(),I.top);this._resize.getWrapEl().style.height=G+"px";this._resize._cache.height=G;this._resize._cache.top=I.top;this._syncBackgroundPosition();return false;}if(F.left&&(F.left<I.left)){E=(J.width+J.left)-I.left;C.setX(this._resize.getWrapEl(),I.left);this._resize._cache.left=I.left;this._resize.getWrapEl().style.width=E+"px";this._resize._cache.width=E;this._syncBackgroundPosition();return false;}if(H!="tl"&&H!="l"&&H!="bl"){if(J.left&&F.width&&((J.left+F.width)>I.right)){E=(I.right-J.left);C.setX(this._resize.getWrapEl(),(I.right-E));this._resize.getWrapEl().style.width=E+"px";this._resize._cache.left=(I.right-E);this._resize._cache.width=E;this._syncBackgroundPosition();return false;}}if(H!="t"&&H!="tr"&&H!="tl"){if(J.top&&F.height&&((J.top+F.height)>I.bottom)){G=(I.bottom-J.top);C.setY(this._resize.getWrapEl(),(I.bottom-G));this._resize.getWrapEl().style.height=G+"px";this._resize._cache.height=G;this._resize._cache.top=(I.bottom-G);this._syncBackgroundPosition();return false;}}},_handleResizeMaskEl:function(){var E=this._resize._cache;this._resizeMaskEl.style.height=Math.floor(E.height)+"px";this._resizeMaskEl.style.width=Math.floor(E.width)+"px";},_handleResizeEvent:function(E){this._setConstraints(true);this._syncBackgroundPosition();this.fireEvent("resizeEvent",arguments);this.fireEvent("moveEvent",{target:"resizeevent"});},_syncBackgroundPosition:function(){this._handleResizeMaskEl();this._setBackgroundPosition(-(parseInt(this._resizeEl.style.left,10)),-(parseInt(this._resizeEl.style.top,10)));
+},_setBackgroundPosition:function(F,H){var J=parseInt(C.getStyle(this._resize.get("element"),"borderLeftWidth"),10);var G=parseInt(C.getStyle(this._resize.get("element"),"borderTopWidth"),10);if(isNaN(J)){J=0;}if(isNaN(G)){G=0;}var E=this._resize.getWrapEl().firstChild;var I=(F-J)+"px "+(H-G)+"px";this._resizeMaskEl.style.backgroundPosition=I;},_setBackgroundImage:function(F){var E=this._resize.getWrapEl().firstChild;this._image=F;E.style.backgroundImage="url("+F+"#)";},_handleEndResizeEvent:function(){this._setConstraints(true);},_handleStartResizeEvent:function(){this._setConstraints(true);var I=this._resize._cache.height,F=this._resize._cache.width,H=parseInt(this._resize.getWrapEl().style.top,10),E=parseInt(this._resize.getWrapEl().style.left,10),G=0,J=0;switch(this._resize._currentHandle){case"b":G=(I+this._resize.dd.bottomConstraint);break;case"l":J=(F+this._resize.dd.leftConstraint);break;case"r":G=(I+H);J=(F+this._resize.dd.rightConstraint);break;case"br":G=(I+this._resize.dd.bottomConstraint);J=(F+this._resize.dd.rightConstraint);break;case"tr":G=(I+H);J=(F+this._resize.dd.rightConstraint);break;}if(G){}if(J){}this.fireEvent("startResizeEvent",arguments);},_setConstraints:function(J){var H=this._resize;H.dd.resetConstraints();var N=parseInt(H.get("height"),10),F=parseInt(H.get("width"),10);if(J){N=H._cache.height;F=H._cache.width;}var L=C.getRegion(this.get("element"));var G=H.getWrapEl();var O=C.getXY(G);var I=O[0]-L.left;var M=L.right-O[0]-F;var K=O[1]-L.top;var E=L.bottom-O[1]-N;if(K<0){K=0;}H.dd.setXConstraint(I,M);H.dd.setYConstraint(K,E);return{top:K,right:M,bottom:E,left:I};},getCropCoords:function(){var E={top:parseInt(this._resize.getWrapEl().style.top,10),left:parseInt(this._resize.getWrapEl().style.left,10),height:this._resize._cache.height,width:this._resize._cache.width,image:this._image};return E;},reset:function(){this._resize.resize(null,this.get("initHeight"),this.get("initWidth"),0,0,true);this._resizeEl.style.top=this.get("initialXY")[1]+"px";this._resizeEl.style.left=this.get("initialXY")[0]+"px";this._syncBackgroundPosition();return this;},getEl:function(){return this.get("element");},getResizeEl:function(){return this._resizeEl;},getWrapEl:function(){return this._wrap;},getMaskEl:function(){return this._mask;},getResizeMaskEl:function(){return this._resizeMaskEl;},getResizeObject:function(){return this._resize;},init:function(G,E){B.superclass.init.call(this,G,E);var H=G;if(!D.isString(H)){if(H.tagName&&(H.tagName.toLowerCase()=="img")){H=C.generateId(H);}else{return false;}}else{var F=C.get(H);if(F.tagName&&F.tagName.toLowerCase()=="img"){}else{return false;}}B._instances[H]=this;this._createWrap();this._createMask();this._createResize();this._setConstraints();},initAttributes:function(E){B.superclass.initAttributes.call(this,E);this.setAttributeConfig("initialXY",{validator:YAHOO.lang.isArray,value:E.initialXY||[10,10]});this.setAttributeConfig("keyTick",{validator:YAHOO.lang.isNumber,value:E.keyTick||1});this.setAttributeConfig("shiftKeyTick",{validator:YAHOO.lang.isNumber,value:E.shiftKeyTick||10});this.setAttributeConfig("useKeys",{validator:YAHOO.lang.isBoolean,value:((E.useKeys===false)?false:true)});this.setAttributeConfig("status",{validator:YAHOO.lang.isBoolean,value:((E.status===false)?false:true),method:function(F){if(this._resize){this._resize.set("status",F);}}});this.setAttributeConfig("minHeight",{validator:YAHOO.lang.isNumber,value:E.minHeight||50,method:function(F){if(this._resize){this._resize.set("minHeight",F);}}});this.setAttributeConfig("minWidth",{validator:YAHOO.lang.isNumber,value:E.minWidth||50,method:function(F){if(this._resize){this._resize.set("minWidth",F);}}});this.setAttributeConfig("ratio",{validator:YAHOO.lang.isBoolean,value:E.ratio||false,method:function(F){if(this._resize){this._resize.set("ratio",F);}}});this.setAttributeConfig("autoRatio",{validator:YAHOO.lang.isBoolean,value:((E.autoRatio===false)?false:true),method:function(F){if(this._resize){this._resize.set("autoRatio",F);}}});this.setAttributeConfig("initHeight",{writeOnce:true,validator:YAHOO.lang.isNumber,value:E.initHeight||(this.get("element").height/4)});this.setAttributeConfig("initWidth",{validator:YAHOO.lang.isNumber,writeOnce:true,value:E.initWidth||(this.get("element").width/4)});},destroy:function(){this._resize.destroy();this._resizeEl.parentNode.removeChild(this._resizeEl);this._mask.parentNode.removeChild(this._mask);A.purgeElement(this._wrap);this._wrap.parentNode.replaceChild(this.get("element"),this._wrap);for(var E in this){if(D.hasOwnProperty(this,E)){this[E]=null;}}},toString:function(){if(this.get){return"ImageCropper (#"+this.get("id")+")";}return"Image Cropper";}});YAHOO.widget.ImageCropper=B;})();YAHOO.register("imagecropper",YAHOO.widget.ImageCropper,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/imageloader/imageloader-min.js b/Websites/bugs.webkit.org/js/yui/imageloader/imageloader-min.js
new file mode 100644
index 0000000..555633c
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/imageloader/imageloader-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+if(typeof(YAHOO.util.ImageLoader)=="undefined"){YAHOO.util.ImageLoader={};}YAHOO.util.ImageLoader.group=function(A,B,C){this.name="unnamed";this._imgObjs={};this.timeoutLen=C;this._timeout=null;this._triggers=[];this._customTriggers=[];this.foldConditional=false;this.className=null;this._classImageEls=null;if(YAHOO.util.Event.DOMReady){this._onloadTasks();}else{YAHOO.util.Event.onDOMReady(this._onloadTasks,this,true);}this.addTrigger(A,B);};YAHOO.util.ImageLoader.group.prototype.addTrigger=function(B,C){if(!B||!C){return;}var A=function(){this.fetch();};this._triggers.push([B,C,A]);YAHOO.util.Event.addListener(B,C,A,this,true);};YAHOO.util.ImageLoader.group.prototype.addCustomTrigger=function(B){if(!B||!B instanceof YAHOO.util.CustomEvent){return;}var A=function(){this.fetch();};this._customTriggers.push([B,A]);B.subscribe(A,this,true);};YAHOO.util.ImageLoader.group.prototype._onloadTasks=function(){if(this.timeoutLen&&typeof(this.timeoutLen)=="number"&&this.timeoutLen>0){this._timeout=setTimeout(this._getFetchTimeout(),this.timeoutLen*1000);}if(this.foldConditional){this._foldCheck();}};YAHOO.util.ImageLoader.group.prototype._getFetchTimeout=function(){var A=this;return function(){A.fetch();};};YAHOO.util.ImageLoader.group.prototype.registerBgImage=function(B,A){this._imgObjs[B]=new YAHOO.util.ImageLoader.bgImgObj(B,A);return this._imgObjs[B];};YAHOO.util.ImageLoader.group.prototype.registerSrcImage=function(D,B,C,A){this._imgObjs[D]=new YAHOO.util.ImageLoader.srcImgObj(D,B,C,A);return this._imgObjs[D];};YAHOO.util.ImageLoader.group.prototype.registerPngBgImage=function(C,B,A){this._imgObjs[C]=new YAHOO.util.ImageLoader.pngBgImgObj(C,B,A);return this._imgObjs[C];};YAHOO.util.ImageLoader.group.prototype.fetch=function(){var B,A,C;clearTimeout(this._timeout);for(B=0,A=this._triggers.length;B<A;B++){YAHOO.util.Event.removeListener(this._triggers[B][0],this._triggers[B][1],this._triggers[B][2]);}for(B=0,A=this._customTriggers.length;B<A;B++){this._customTriggers[B][0].unsubscribe(this._customTriggers[B][1],this);}this._fetchByClass();for(C in this._imgObjs){if(YAHOO.lang.hasOwnProperty(this._imgObjs,C)){this._imgObjs[C].fetch();}}};YAHOO.util.ImageLoader.group.prototype._foldCheck=function(){var C=(document.compatMode!="CSS1Compat")?document.body.scrollTop:document.documentElement.scrollTop,D=YAHOO.util.Dom.getViewportHeight(),A=C+D,E=(document.compatMode!="CSS1Compat")?document.body.scrollLeft:document.documentElement.scrollLeft,G=YAHOO.util.Dom.getViewportWidth(),I=E+G,B,J,F,H;for(B in this._imgObjs){if(YAHOO.lang.hasOwnProperty(this._imgObjs,B)){J=YAHOO.util.Dom.getXY(this._imgObjs[B].domId);if(J[1]<A&&J[0]<I){this._imgObjs[B].fetch();}}}if(this.className){this._classImageEls=YAHOO.util.Dom.getElementsByClassName(this.className);for(F=0,H=this._classImageEls.length;F<H;F++){J=YAHOO.util.Dom.getXY(this._classImageEls[F]);if(J[1]<A&&J[0]<I){YAHOO.util.Dom.removeClass(this._classImageEls[F],this.className);}}}};YAHOO.util.ImageLoader.group.prototype._fetchByClass=function(){if(!this.className){return;}if(this._classImageEls===null){this._classImageEls=YAHOO.util.Dom.getElementsByClassName(this.className);}YAHOO.util.Dom.removeClass(this._classImageEls,this.className);};YAHOO.util.ImageLoader.imgObj=function(B,A){this.domId=B;this.url=A;this.width=null;this.height=null;this.setVisible=false;this._fetched=false;};YAHOO.util.ImageLoader.imgObj.prototype.fetch=function(){if(this._fetched){return;}var A=document.getElementById(this.domId);if(!A){return;}this._applyUrl(A);if(this.setVisible){A.style.visibility="visible";}if(this.width){A.width=this.width;}if(this.height){A.height=this.height;}this._fetched=true;};YAHOO.util.ImageLoader.imgObj.prototype._applyUrl=function(A){};YAHOO.util.ImageLoader.bgImgObj=function(B,A){YAHOO.util.ImageLoader.bgImgObj.superclass.constructor.call(this,B,A);};YAHOO.lang.extend(YAHOO.util.ImageLoader.bgImgObj,YAHOO.util.ImageLoader.imgObj);YAHOO.util.ImageLoader.bgImgObj.prototype._applyUrl=function(A){A.style.backgroundImage="url('"+this.url+"')";};YAHOO.util.ImageLoader.srcImgObj=function(D,B,C,A){YAHOO.util.ImageLoader.srcImgObj.superclass.constructor.call(this,D,B);this.width=C;this.height=A;};YAHOO.lang.extend(YAHOO.util.ImageLoader.srcImgObj,YAHOO.util.ImageLoader.imgObj);YAHOO.util.ImageLoader.srcImgObj.prototype._applyUrl=function(A){A.src=this.url;};YAHOO.util.ImageLoader.pngBgImgObj=function(C,B,A){YAHOO.util.ImageLoader.pngBgImgObj.superclass.constructor.call(this,C,B);this.props=A||{};};YAHOO.lang.extend(YAHOO.util.ImageLoader.pngBgImgObj,YAHOO.util.ImageLoader.imgObj);YAHOO.util.ImageLoader.pngBgImgObj.prototype._applyUrl=function(B){if(YAHOO.env.ua.ie&&YAHOO.env.ua.ie<=6){var C=(YAHOO.lang.isUndefined(this.props.sizingMethod))?"scale":this.props.sizingMethod,A=(YAHOO.lang.isUndefined(this.props.enabled))?"true":this.props.enabled;B.style.filter='progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+this.url+'", sizingMethod="'+C+'", enabled="'+A+'")';}else{B.style.backgroundImage="url('"+this.url+"')";}};YAHOO.register("imageloader",YAHOO.util.ImageLoader,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/json/json-min.js b/Websites/bugs.webkit.org/js/yui/json/json-min.js
new file mode 100644
index 0000000..3130a24
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/json/json-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var l=YAHOO.lang,isFunction=l.isFunction,isObject=l.isObject,isArray=l.isArray,_toStr=Object.prototype.toString,Native=(YAHOO.env.ua.caja?window:this).JSON,_UNICODE_EXCEPTIONS=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,_ESCAPES=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,_VALUES=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,_BRACKETS=/(?:^|:|,)(?:\s*\[)+/g,_UNSAFE=/[^\],:{}\s]/,_SPECIAL_CHARS=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,_CHARS={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},UNDEFINED="undefined",OBJECT="object",NULL="null",STRING="string",NUMBER="number",BOOLEAN="boolean",DATE="date",_allowable={"undefined":UNDEFINED,"string":STRING,"[object String]":STRING,"number":NUMBER,"[object Number]":NUMBER,"boolean":BOOLEAN,"[object Boolean]":BOOLEAN,"[object Date]":DATE,"[object RegExp]":OBJECT},EMPTY="",OPEN_O="{",CLOSE_O="}",OPEN_A="[",CLOSE_A="]",COMMA=",",COMMA_CR=",\n",CR="\n",COLON=":",COLON_SP=": ",QUOTE='"';Native=_toStr.call(Native)==="[object JSON]"&&Native;function _char(c){if(!_CHARS[c]){_CHARS[c]="\\u"+("0000"+(+(c.charCodeAt(0))).toString(16)).slice(-4);}return _CHARS[c];}function _revive(data,reviver){var walk=function(o,key){var k,v,value=o[key];if(value&&typeof value==="object"){for(k in value){if(l.hasOwnProperty(value,k)){v=walk(value,k);if(v===undefined){delete value[k];}else{value[k]=v;}}}}return reviver.call(o,key,value);};return typeof reviver==="function"?walk({"":data},""):data;}function _prepare(s){return s.replace(_UNICODE_EXCEPTIONS,_char);}function _isSafe(str){return l.isString(str)&&!_UNSAFE.test(str.replace(_ESCAPES,"@").replace(_VALUES,"]").replace(_BRACKETS,""));}function _parse(s,reviver){s=_prepare(s);if(_isSafe(s)){return _revive(eval("("+s+")"),reviver);}throw new SyntaxError("JSON.parse");}function _type(o){var t=typeof o;return _allowable[t]||_allowable[_toStr.call(o)]||(t===OBJECT?(o?OBJECT:NULL):UNDEFINED);}function _string(s){return QUOTE+s.replace(_SPECIAL_CHARS,_char)+QUOTE;}function _indent(s,space){return s.replace(/^/gm,space);}function _stringify(o,w,space){if(o===undefined){return undefined;}var replacer=isFunction(w)?w:null,format=_toStr.call(space).match(/String|Number/)||[],_date=YAHOO.lang.JSON.dateToString,stack=[],tmp,i,len;if(replacer||!isArray(w)){w=undefined;}if(w){tmp={};for(i=0,len=w.length;i<len;++i){tmp[w[i]]=true;}w=tmp;}space=format[0]==="Number"?new Array(Math.min(Math.max(0,space),10)+1).join(" "):(space||EMPTY).slice(0,10);function _serialize(h,key){var value=h[key],t=_type(value),a=[],colon=space?COLON_SP:COLON,arr,i,keys,k,v;if(isObject(value)&&isFunction(value.toJSON)){value=value.toJSON(key);}else{if(t===DATE){value=_date(value);}}if(isFunction(replacer)){value=replacer.call(h,key,value);}if(value!==h[key]){t=_type(value);}switch(t){case DATE:case OBJECT:break;case STRING:return _string(value);case NUMBER:return isFinite(value)?value+EMPTY:NULL;case BOOLEAN:return value+EMPTY;case NULL:return NULL;default:return undefined;}for(i=stack.length-1;i>=0;--i){if(stack[i]===value){throw new Error("JSON.stringify. Cyclical reference");}}arr=isArray(value);stack.push(value);if(arr){for(i=value.length-1;i>=0;--i){a[i]=_serialize(value,i)||NULL;}}else{keys=w||value;i=0;for(k in keys){if(l.hasOwnProperty(keys,k)){v=_serialize(value,k);if(v){a[i++]=_string(k)+colon+v;}}}}stack.pop();if(space&&a.length){return arr?OPEN_A+CR+_indent(a.join(COMMA_CR),space)+CR+CLOSE_A:OPEN_O+CR+_indent(a.join(COMMA_CR),space)+CR+CLOSE_O;}else{return arr?OPEN_A+a.join(COMMA)+CLOSE_A:OPEN_O+a.join(COMMA)+CLOSE_O;}}return _serialize({"":o},"");}YAHOO.lang.JSON={useNativeParse:!!Native,useNativeStringify:!!Native,isSafe:function(s){return _isSafe(_prepare(s));},parse:function(s,reviver){if(typeof s!=="string"){s+="";}return Native&&YAHOO.lang.JSON.useNativeParse?Native.parse(s,reviver):_parse(s,reviver);},stringify:function(o,w,space){return Native&&YAHOO.lang.JSON.useNativeStringify?Native.stringify(o,w,space):_stringify(o,w,space);},dateToString:function(d){function _zeroPad(v){return v<10?"0"+v:v;}return d.getUTCFullYear()+"-"+_zeroPad(d.getUTCMonth()+1)+"-"+_zeroPad(d.getUTCDate())+"T"+_zeroPad(d.getUTCHours())+COLON+_zeroPad(d.getUTCMinutes())+COLON+_zeroPad(d.getUTCSeconds())+"Z";},stringToDate:function(str){var m=str.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{3}))?Z$/);if(m){var d=new Date();d.setUTCFullYear(m[1],m[2]-1,m[3]);d.setUTCHours(m[4],m[5],m[6],(m[7]||0));return d;}return str;}};YAHOO.lang.JSON.isValid=YAHOO.lang.JSON.isSafe;})();YAHOO.register("json",YAHOO.lang.JSON,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/layout/layout-min.js b/Websites/bugs.webkit.org/js/yui/layout/layout-min.js
new file mode 100644
index 0000000..8714716
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/layout/layout-min.js
@@ -0,0 +1,11 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var C=YAHOO.util.Dom,A=YAHOO.util.Event,D=YAHOO.lang;var B=function(F,E){if(D.isObject(F)&&!F.tagName){E=F;F=null;}if(D.isString(F)){if(C.get(F)){F=C.get(F);}}if(!F){F=document.body;}var G={element:F,attributes:E||{}};B.superclass.constructor.call(this,G.element,G.attributes);};B._instances={};B.getLayoutById=function(E){if(B._instances[E]){return B._instances[E];}return false;};YAHOO.extend(B,YAHOO.util.Element,{browser:function(){var E=YAHOO.env.ua;E.standardsMode=false;E.secure=false;return E;}(),_units:null,_rendered:null,_zIndex:null,_sizes:null,_setBodySize:function(G){var F=0,E=0;G=((G===false)?false:true);if(this._isBody){F=C.getClientHeight();E=C.getClientWidth();}else{F=parseInt(this.getStyle("height"),10);E=parseInt(this.getStyle("width"),10);if(isNaN(E)){E=this.get("element").clientWidth;}if(isNaN(F)){F=this.get("element").clientHeight;}}if(this.get("minWidth")){if(E<this.get("minWidth")){E=this.get("minWidth");}}if(this.get("minHeight")){if(F<this.get("minHeight")){F=this.get("minHeight");}}if(G){if(F<0){F=0;}if(E<0){E=0;}C.setStyle(this._doc,"height",F+"px");C.setStyle(this._doc,"width",E+"px");}this._sizes.doc={h:F,w:E};this._setSides(G);},_setSides:function(J){var H=((this._units.top)?this._units.top.get("height"):0),G=((this._units.bottom)?this._units.bottom.get("height"):0),I=this._sizes.doc.h,E=this._sizes.doc.w;J=((J===false)?false:true);this._sizes.top={h:H,w:((this._units.top)?E:0),t:0};this._sizes.bottom={h:G,w:((this._units.bottom)?E:0)};var F=(I-(H+G));this._sizes.left={h:F,w:((this._units.left)?this._units.left.get("width"):0)};this._sizes.right={h:F,w:((this._units.right)?this._units.right.get("width"):0),l:((this._units.right)?(E-this._units.right.get("width")):0),t:((this._units.top)?this._sizes.top.h:0)};if(this._units.right&&J){this._units.right.set("top",this._sizes.right.t);if(!this._units.right._collapsing){this._units.right.set("left",this._sizes.right.l);}this._units.right.set("height",this._sizes.right.h,true);}if(this._units.left){this._sizes.left.l=0;if(this._units.top){this._sizes.left.t=this._sizes.top.h;}else{this._sizes.left.t=0;}if(J){this._units.left.set("top",this._sizes.left.t);this._units.left.set("height",this._sizes.left.h,true);this._units.left.set("left",0);}}if(this._units.bottom){this._sizes.bottom.t=this._sizes.top.h+this._sizes.left.h;if(J){this._units.bottom.set("top",this._sizes.bottom.t);this._units.bottom.set("width",this._sizes.bottom.w,true);}}if(this._units.top){if(J){this._units.top.set("width",this._sizes.top.w,true);}}this._setCenter(J);},_setCenter:function(G){G=((G===false)?false:true);var F=this._sizes.left.h;var E=(this._sizes.doc.w-(this._sizes.left.w+this._sizes.right.w));if(G){this._units.center.set("height",F,true);this._units.center.set("width",E,true);this._units.center.set("top",this._sizes.top.h);this._units.center.set("left",this._sizes.left.w);}this._sizes.center={h:F,w:E,t:this._sizes.top.h,l:this._sizes.left.w};},getSizes:function(){return this._sizes;},getUnitById:function(E){return YAHOO.widget.LayoutUnit.getLayoutUnitById(E);},getUnitByPosition:function(E){if(E){E=E.toLowerCase();if(this._units[E]){return this._units[E];}return false;}return false;},removeUnit:function(E){delete this._units[E.get("position")];this.resize();},addUnit:function(G){if(!G.position){return false;}if(this._units[G.position]){return false;}var H=null,J=null;if(G.id){if(C.get(G.id)){H=C.get(G.id);delete G.id;}}if(G.element){H=G.element;}if(!J){J=document.createElement("div");var L=C.generateId();J.id=L;}if(!H){H=document.createElement("div");}C.addClass(H,"yui-layout-wrap");if(this.browser.ie&&!this.browser.standardsMode){J.style.zoom=1;H.style.zoom=1;}if(J.firstChild){J.insertBefore(H,J.firstChild);}else{J.appendChild(H);}this._doc.appendChild(J);var I=false,F=false;if(G.height){I=parseInt(G.height,10);}if(G.width){F=parseInt(G.width,10);}var E={};YAHOO.lang.augmentObject(E,G);E.parent=this;E.wrap=H;E.height=I;E.width=F;var K=new YAHOO.widget.LayoutUnit(J,E);K.on("heightChange",this.resize,{unit:K},this);K.on("widthChange",this.resize,{unit:K},this);K.on("gutterChange",this.resize,{unit:K},this);this._units[G.position]=K;if(this._rendered){this.resize();}return K;},_createUnits:function(){var E=this.get("units");for(var F in E){if(D.hasOwnProperty(E,F)){this.addUnit(E[F]);}}},resize:function(H,G){var E=H;if(E&&E.prevValue&&E.newValue){if(E.prevValue==E.newValue){if(G){if(G.unit){if(!G.unit.get("animate")){H=false;}}}}}H=((H===false)?false:true);if(H){var F=this.fireEvent("beforeResize");if(F===false){H=false;}if(this.browser.ie){if(this._isBody){C.removeClass(document.documentElement,"yui-layout");C.addClass(document.documentElement,"yui-layout");}else{this.removeClass("yui-layout");this.addClass("yui-layout");}}}this._setBodySize(H);if(H){this.fireEvent("resize",{target:this,sizes:this._sizes,event:E});}return this;},_setupBodyElements:function(){this._doc=C.get("layout-doc");if(!this._doc){this._doc=document.createElement("div");this._doc.id="layout-doc";if(document.body.firstChild){document.body.insertBefore(this._doc,document.body.firstChild);}else{document.body.appendChild(this._doc);}}this._createUnits();this._setBodySize();A.on(window,"resize",this.resize,this,true);C.addClass(this._doc,"yui-layout-doc");},_setupElements:function(){this._doc=this.getElementsByClassName("yui-layout-doc")[0];if(!this._doc){this._doc=document.createElement("div");this.get("element").appendChild(this._doc);}this._createUnits();this._setBodySize();C.addClass(this._doc,"yui-layout-doc");},_isBody:null,_doc:null,init:function(F,E){this._zIndex=0;B.superclass.init.call(this,F,E);if(this.get("parent")){this._zIndex=this.get("parent")._zIndex+10;}this._sizes={};this._units={};var G=F;if(!D.isString(G)){G=C.generateId(G);}B._instances[G]=this;},render:function(){this._stamp();var E=this.get("element");if(E&&E.tagName&&(E.tagName.toLowerCase()=="body")){this._isBody=true;C.addClass(document.body,"yui-layout");if(C.hasClass(document.body,"yui-skin-sam")){C.addClass(document.documentElement,"yui-skin-sam");
+C.removeClass(document.body,"yui-skin-sam");}this._setupBodyElements();}else{this._isBody=false;this.addClass("yui-layout");this._setupElements();}this.resize();this._rendered=true;this.fireEvent("render");return this;},_stamp:function(){if(document.compatMode=="CSS1Compat"){this.browser.standardsMode=true;}if(window.location.href.toLowerCase().indexOf("https")===0){C.addClass(document.documentElement,"secure");this.browser.secure=true;}},initAttributes:function(E){B.superclass.initAttributes.call(this,E);this.setAttributeConfig("units",{writeOnce:true,validator:YAHOO.lang.isArray,value:E.units||[]});this.setAttributeConfig("minHeight",{value:E.minHeight||false,validator:YAHOO.lang.isNumber});this.setAttributeConfig("minWidth",{value:E.minWidth||false,validator:YAHOO.lang.isNumber});this.setAttributeConfig("height",{value:E.height||false,validator:YAHOO.lang.isNumber,method:function(F){if(F<0){F=0;}this.setStyle("height",F+"px");}});this.setAttributeConfig("width",{value:E.width||false,validator:YAHOO.lang.isNumber,method:function(F){if(F<0){F=0;}this.setStyle("width",F+"px");}});this.setAttributeConfig("parent",{writeOnce:true,value:E.parent||false,method:function(F){if(F){F.on("resize",this.resize,this,true);}}});},destroy:function(){var G=this.get("parent");if(G){G.removeListener("resize",this.resize,this,true);}A.removeListener(window,"resize",this.resize,this,true);this.unsubscribeAll();for(var E in this._units){if(D.hasOwnProperty(this._units,E)){if(this._units[E]){this._units[E].destroy(true);}}}A.purgeElement(this.get("element"),true);this.get("parentNode").removeChild(this.get("element"));delete YAHOO.widget.Layout._instances[this.get("id")];for(var F in this){if(D.hasOwnProperty(this,F)){this[F]=null;delete this[F];}}if(G){G.resize();}},toString:function(){if(this.get){return"Layout #"+this.get("id");}return"Layout";}});YAHOO.widget.Layout=B;})();(function(){var D=YAHOO.util.Dom,C=YAHOO.util.Selector,A=YAHOO.util.Event,E=YAHOO.lang;var B=function(G,F){var H={element:G,attributes:F||{}};B.superclass.constructor.call(this,H.element,H.attributes);};B._instances={};B.getLayoutUnitById=function(F){if(B._instances[F]){return B._instances[F];}return false;};YAHOO.extend(B,YAHOO.util.Element,{STR_CLOSE:"Click to close this pane.",STR_COLLAPSE:"Click to collapse this pane.",STR_EXPAND:"Click to expand this pane.",LOADING_CLASSNAME:"loading",browser:null,_sizes:null,_anim:null,_resize:null,_clip:null,_gutter:null,header:null,body:null,footer:null,_collapsed:null,_collapsing:null,_lastWidth:null,_lastHeight:null,_lastTop:null,_lastLeft:null,_lastScroll:null,_lastCenterScroll:null,_lastScrollTop:null,resize:function(F){var G=this.fireEvent("beforeResize");if(G===false){return this;}if(!this._collapsing||(F===true)){var N=this.get("scroll");this.set("scroll",false);var K=this._getBoxSize(this.header),J=this._getBoxSize(this.footer),L=[this.get("height"),this.get("width")];var H=(L[0]-K[0]-J[0])-(this._gutter.top+this._gutter.bottom),M=L[1]-(this._gutter.left+this._gutter.right);var O=(H+(K[0]+J[0])),I=M;if(this._collapsed&&!this._collapsing){this._setHeight(this._clip,O);this._setWidth(this._clip,I);D.setStyle(this._clip,"top",this.get("top")+this._gutter.top+"px");D.setStyle(this._clip,"left",this.get("left")+this._gutter.left+"px");}else{if(!this._collapsed||(this._collapsed&&this._collapsing)){O=this._setHeight(this.get("wrap"),O);I=this._setWidth(this.get("wrap"),I);this._sizes.wrap.h=O;this._sizes.wrap.w=I;D.setStyle(this.get("wrap"),"top",this._gutter.top+"px");D.setStyle(this.get("wrap"),"left",this._gutter.left+"px");this._sizes.header.w=this._setWidth(this.header,I);this._sizes.header.h=K[0];this._sizes.footer.w=this._setWidth(this.footer,I);this._sizes.footer.h=J[0];D.setStyle(this.footer,"bottom","0px");this._sizes.body.h=this._setHeight(this.body,(O-(K[0]+J[0])));this._sizes.body.w=this._setWidth(this.body,I);D.setStyle(this.body,"top",K[0]+"px");this.set("scroll",N);this.fireEvent("resize");}}}return this;},_setWidth:function(H,G){if(H){var F=this._getBorderSizes(H);G=(G-(F[1]+F[3]));G=this._fixQuirks(H,G,"w");if(G<0){G=0;}D.setStyle(H,"width",G+"px");}return G;},_setHeight:function(H,G){if(H){var F=this._getBorderSizes(H);G=(G-(F[0]+F[2]));G=this._fixQuirks(H,G,"h");if(G<0){G=0;}D.setStyle(H,"height",G+"px");}return G;},_fixQuirks:function(I,L,G){var K=0,H=2;if(G=="w"){K=1;H=3;}if((this.browser.ie<8)&&!this.browser.standardsMode){var F=this._getBorderSizes(I),J=this._getBorderSizes(I.parentNode);if((F[K]===0)&&(F[H]===0)){if((J[K]!==0)&&(J[H]!==0)){L=(L-(J[K]+J[H]));}}else{if((J[K]===0)&&(J[H]===0)){L=(L+(F[K]+F[H]));}}}return L;},_getBoxSize:function(H){var G=[0,0];if(H){if(this.browser.ie&&!this.browser.standardsMode){H.style.zoom=1;}var F=this._getBorderSizes(H);G[0]=H.clientHeight+(F[0]+F[2]);G[1]=H.clientWidth+(F[1]+F[3]);}return G;},_getBorderSizes:function(H){var G=[];H=H||this.get("element");if(this.browser.ie&&!this.browser.standardsMode){H.style.zoom=1;}G[0]=parseInt(D.getStyle(H,"borderTopWidth"),10);G[1]=parseInt(D.getStyle(H,"borderRightWidth"),10);G[2]=parseInt(D.getStyle(H,"borderBottomWidth"),10);G[3]=parseInt(D.getStyle(H,"borderLeftWidth"),10);for(var F=0;F<G.length;F++){if(isNaN(G[F])){G[F]=0;}}return G;},_createClip:function(){if(!this._clip){this._clip=document.createElement("div");this._clip.className="yui-layout-clip yui-layout-clip-"+this.get("position");this._clip.innerHTML='<div class="collapse"></div>';var F=this._clip.firstChild;F.title=this.STR_EXPAND;A.on(F,"click",this.expand,this,true);this.get("element").parentNode.appendChild(this._clip);}},_toggleClip:function(){if(!this._collapsed){var J=this._getBoxSize(this.header),K=this._getBoxSize(this.footer),I=[this.get("height"),this.get("width")];var H=(I[0]-J[0]-K[0])-(this._gutter.top+this._gutter.bottom),F=I[1]-(this._gutter.left+this._gutter.right),G=(H+(J[0]+K[0]));switch(this.get("position")){case"top":case"bottom":this._setWidth(this._clip,F);this._setHeight(this._clip,this.get("collapseSize"));
+D.setStyle(this._clip,"left",(this._lastLeft+this._gutter.left)+"px");if(this.get("position")=="bottom"){D.setStyle(this._clip,"top",((this._lastTop+this._lastHeight)-(this.get("collapseSize")-this._gutter.top))+"px");}else{D.setStyle(this._clip,"top",this.get("top")+this._gutter.top+"px");}break;case"left":case"right":this._setWidth(this._clip,this.get("collapseSize"));this._setHeight(this._clip,G);D.setStyle(this._clip,"top",(this.get("top")+this._gutter.top)+"px");if(this.get("position")=="right"){D.setStyle(this._clip,"left",(((this._lastLeft+this._lastWidth)-this.get("collapseSize"))-this._gutter.left)+"px");}else{D.setStyle(this._clip,"left",(this.get("left")+this._gutter.left)+"px");}break;}D.setStyle(this._clip,"display","block");this.setStyle("display","none");}else{D.setStyle(this._clip,"display","none");}},getSizes:function(){return this._sizes;},toggle:function(){if(this._collapsed){this.expand();}else{this.collapse();}return this;},expand:function(){if(!this._collapsed){return this;}var L=this.fireEvent("beforeExpand");if(L===false){return this;}this._collapsing=true;this.setStyle("zIndex",this._zIndex);if(this._anim){this.setStyle("display","none");var F={},H;switch(this.get("position")){case"left":case"right":this.set("width",this._lastWidth,true);this.setStyle("width",this._lastWidth+"px");this.get("parent").resize(false);H=this.get("parent").getSizes()[this.get("position")];this.set("height",H.h,true);var K=H.l;F={left:{to:K}};if(this.get("position")=="left"){F.left.from=(K-H.w);this.setStyle("left",(K-H.w)+"px");}break;case"top":case"bottom":this.set("height",this._lastHeight,true);this.setStyle("height",this._lastHeight+"px");this.get("parent").resize(false);H=this.get("parent").getSizes()[this.get("position")];this.set("width",H.w,true);var J=H.t;F={top:{to:J}};if(this.get("position")=="top"){this.setStyle("top",(J-H.h)+"px");F.top.from=(J-H.h);}break;}this._anim.attributes=F;var I=function(){this.setStyle("display","block");this.resize(true);this._anim.onStart.unsubscribe(I,this,true);};var G=function(){this._collapsing=false;this.setStyle("zIndex",this._zIndex);this.set("width",this._lastWidth);this.set("height",this._lastHeight);this._collapsed=false;this.resize();this.set("scroll",this._lastScroll);if(this._lastScrollTop>0){this.body.scrollTop=this._lastScrollTop;}this._anim.onComplete.unsubscribe(G,this,true);this.fireEvent("expand");};this._anim.onStart.subscribe(I,this,true);this._anim.onComplete.subscribe(G,this,true);this._anim.animate();this._toggleClip();}else{this._collapsing=false;this._toggleClip();this._collapsed=false;this._zIndex=this.getStyle("zIndex");this.setStyle("zIndex",this.get("parent")._zIndex);this.setStyle("display","block");this.set("width",this._lastWidth);this.set("height",this._lastHeight);this.resize();this.set("scroll",this._lastScroll);if(this._lastScrollTop>0){this.body.scrollTop=this._lastScrollTop;}this.fireEvent("expand");}return this;},collapse:function(){if(this._collapsed){return this;}var J=this.fireEvent("beforeCollapse");if(J===false){return this;}if(!this._clip){this._createClip();}this._collapsing=true;var G=this.get("width"),H=this.get("height"),F={};this._lastWidth=G;this._lastHeight=H;this._lastScroll=this.get("scroll");this._lastScrollTop=this.body.scrollTop;this.set("scroll",false,true);this._lastLeft=parseInt(this.get("element").style.left,10);this._lastTop=parseInt(this.get("element").style.top,10);if(isNaN(this._lastTop)){this._lastTop=0;this.set("top",0);}if(isNaN(this._lastLeft)){this._lastLeft=0;this.set("left",0);}this._zIndex=this.getStyle("zIndex");this.setStyle("zIndex",this.get("parent")._zIndex+1);var K=this.get("position");switch(K){case"top":case"bottom":this.set("height",(this.get("collapseSize")+(this._gutter.top+this._gutter.bottom)));F={top:{to:(this.get("top")-H)}};if(K=="bottom"){F.top.to=(this.get("top")+H);}break;case"left":case"right":this.set("width",(this.get("collapseSize")+(this._gutter.left+this._gutter.right)));F={left:{to:-(this._lastWidth)}};if(K=="right"){F.left={to:(this.get("left")+G)};}break;}if(this._anim){this._anim.attributes=F;var I=function(){this._collapsing=false;this._toggleClip();this.setStyle("zIndex",this.get("parent")._zIndex);this._collapsed=true;this.get("parent").resize();this._anim.onComplete.unsubscribe(I,this,true);this.fireEvent("collapse");};this._anim.onComplete.subscribe(I,this,true);this._anim.animate();}else{this._collapsing=false;this.setStyle("display","none");this._toggleClip();this.setStyle("zIndex",this.get("parent")._zIndex);this.get("parent").resize();this._collapsed=true;this.fireEvent("collapse");}return this;},close:function(){this.setStyle("display","none");this.get("parent").removeUnit(this);this.fireEvent("close");if(this._clip){this._clip.parentNode.removeChild(this._clip);this._clip=null;}return this.get("parent");},loadHandler:{success:function(F){this.body.innerHTML=F.responseText;this.resize(true);},failure:function(F){}},dataConnection:null,_loading:false,loadContent:function(){if(YAHOO.util.Connect&&this.get("dataSrc")&&!this._loading&&!this.get("dataLoaded")){this._loading=true;D.addClass(this.body,this.LOADING_CLASSNAME);this.dataConnection=YAHOO.util.Connect.asyncRequest(this.get("loadMethod"),this.get("dataSrc"),{success:function(F){this.loadHandler.success.call(this,F);this.set("dataLoaded",true);this.dataConnection=null;D.removeClass(this.body,this.LOADING_CLASSNAME);this._loading=false;this.fireEvent("load");},failure:function(F){this.loadHandler.failure.call(this,F);this.dataConnection=null;D.removeClass(this.body,this.LOADING_CLASSNAME);this._loading=false;this.fireEvent("loadError",{error:F});},scope:this,timeout:this.get("dataTimeout")});return this.dataConnection;}return false;},init:function(H,G){this._gutter={left:0,right:0,top:0,bottom:0};this._sizes={wrap:{h:0,w:0},header:{h:0,w:0},body:{h:0,w:0},footer:{h:0,w:0}};B.superclass.init.call(this,H,G);this.browser=this.get("parent").browser;var K=H;if(!E.isString(K)){K=D.generateId(K);
+}B._instances[K]=this;this.setStyle("position","absolute");this.addClass("yui-layout-unit");this.addClass("yui-layout-unit-"+this.get("position"));var J=this.getElementsByClassName("yui-layout-hd","div")[0];if(J){this.header=J;}var F=this.getElementsByClassName("yui-layout-bd","div")[0];if(F){this.body=F;}var I=this.getElementsByClassName("yui-layout-ft","div")[0];if(I){this.footer=I;}this.on("contentChange",this.resize,this,true);this._lastScrollTop=0;this.set("animate",this.get("animate"));},initAttributes:function(F){B.superclass.initAttributes.call(this,F);this.setAttributeConfig("wrap",{value:F.wrap||null,method:function(G){if(G){var H=D.generateId(G);B._instances[H]=this;}}});this.setAttributeConfig("grids",{value:F.grids||false});this.setAttributeConfig("top",{value:F.top||0,validator:E.isNumber,method:function(G){if(!this._collapsing){this.setStyle("top",G+"px");}}});this.setAttributeConfig("left",{value:F.left||0,validator:E.isNumber,method:function(G){if(!this._collapsing){this.setStyle("left",G+"px");}}});this.setAttributeConfig("minWidth",{value:F.minWidth||false,method:function(G){if(this._resize){this._resize.set("minWidth",G);}},validator:YAHOO.lang.isNumber});this.setAttributeConfig("maxWidth",{value:F.maxWidth||false,method:function(G){if(this._resize){this._resize.set("maxWidth",G);}},validator:YAHOO.lang.isNumber});this.setAttributeConfig("minHeight",{value:F.minHeight||false,method:function(G){if(this._resize){this._resize.set("minHeight",G);}},validator:YAHOO.lang.isNumber});this.setAttributeConfig("maxHeight",{value:F.maxHeight||false,method:function(G){if(this._resize){this._resize.set("maxHeight",G);}},validator:YAHOO.lang.isNumber});this.setAttributeConfig("height",{value:F.height,validator:E.isNumber,method:function(G){if(!this._collapsing){if(G<0){G=0;}this.setStyle("height",G+"px");}}});this.setAttributeConfig("width",{value:F.width,validator:E.isNumber,method:function(G){if(!this._collapsing){if(G<0){G=0;}this.setStyle("width",G+"px");}}});this.setAttributeConfig("zIndex",{value:F.zIndex||false,method:function(G){this.setStyle("zIndex",G);}});this.setAttributeConfig("position",{value:F.position});this.setAttributeConfig("gutter",{value:F.gutter||0,validator:YAHOO.lang.isString,method:function(H){var G=H.split(" ");if(G.length){this._gutter.top=parseInt(G[0],10);if(G[1]){this._gutter.right=parseInt(G[1],10);}else{this._gutter.right=this._gutter.top;}if(G[2]){this._gutter.bottom=parseInt(G[2],10);}else{this._gutter.bottom=this._gutter.top;}if(G[3]){this._gutter.left=parseInt(G[3],10);}else{if(G[1]){this._gutter.left=this._gutter.right;}else{this._gutter.left=this._gutter.top;}}}}});this.setAttributeConfig("parent",{writeOnce:true,value:F.parent||false,method:function(G){if(G){G.on("resize",this.resize,this,true);}}});this.setAttributeConfig("collapseSize",{value:F.collapseSize||25,validator:YAHOO.lang.isNumber});this.setAttributeConfig("duration",{value:F.duration||0.5});this.setAttributeConfig("easing",{value:F.easing||((YAHOO.util&&YAHOO.util.Easing)?YAHOO.util.Easing.BounceIn:"false")});this.setAttributeConfig("animate",{value:((F.animate===false)?false:true),validator:function(){var G=false;if(YAHOO.util.Anim){G=true;}return G;},method:function(G){if(G){this._anim=new YAHOO.util.Anim(this.get("element"),{},this.get("duration"),this.get("easing"));}else{this._anim=false;}}});this.setAttributeConfig("header",{value:F.header||false,method:function(G){if(G===false){if(this.header){D.addClass(this.body,"yui-layout-bd-nohd");this.header.parentNode.removeChild(this.header);this.header=null;}}else{if(!this.header){var I=this.getElementsByClassName("yui-layout-hd","div")[0];if(!I){I=this._createHeader();}this.header=I;}var H=this.header.getElementsByTagName("h2")[0];if(!H){H=document.createElement("h2");this.header.appendChild(H);}H.innerHTML=G;if(this.body){D.removeClass(this.body,"yui-layout-bd-nohd");}}this.fireEvent("contentChange",{target:"header"});}});this.setAttributeConfig("proxy",{writeOnce:true,value:((F.proxy===false)?false:true)});this.setAttributeConfig("body",{value:F.body||false,method:function(I){if(!this.body){var G=this.getElementsByClassName("yui-layout-bd","div")[0];if(G){this.body=G;}else{G=document.createElement("div");G.className="yui-layout-bd";this.body=G;this.get("wrap").appendChild(G);}}if(!this.header){D.addClass(this.body,"yui-layout-bd-nohd");}D.addClass(this.body,"yui-layout-bd-noft");var H=null;if(E.isString(I)){H=D.get(I);}else{if(I&&I.tagName){H=I;}}if(H){var J=D.generateId(H);B._instances[J]=this;this.body.appendChild(H);}else{this.body.innerHTML=I;}this._cleanGrids();this.fireEvent("contentChange",{target:"body"});}});this.setAttributeConfig("footer",{value:F.footer||false,method:function(H){if(H===false){if(this.footer){D.addClass(this.body,"yui-layout-bd-noft");this.footer.parentNode.removeChild(this.footer);this.footer=null;}}else{if(!this.footer){var I=this.getElementsByClassName("yui-layout-ft","div")[0];if(!I){I=document.createElement("div");I.className="yui-layout-ft";this.footer=I;this.get("wrap").appendChild(I);}else{this.footer=I;}}var G=null;if(E.isString(H)){G=D.get(H);}else{if(H&&H.tagName){G=H;}}if(G){this.footer.appendChild(G);}else{this.footer.innerHTML=H;}D.removeClass(this.body,"yui-layout-bd-noft");}this.fireEvent("contentChange",{target:"footer"});}});this.setAttributeConfig("close",{value:F.close||false,method:function(G){if(this.get("position")=="center"){return false;}if(!this.header&&G){this._createHeader();}if(!this.header){return;}var H=this.header?D.getElementsByClassName("close","div",this.header)[0]:null;if(G){if(!this.get("header")){this.set("header"," ");}if(!H){H=document.createElement("div");H.className="close";this.header.appendChild(H);A.on(H,"click",this.close,this,true);}H.title=this.STR_CLOSE;}else{if(H&&H.parentNode){A.purgeElement(H);H.parentNode.removeChild(H);}}this._configs.close.value=G;this.set("collapse",this.get("collapse"));}});this.setAttributeConfig("collapse",{value:F.collapse||false,method:function(G){if(this.get("position")=="center"){return false;
+}if(!this.header&&G){this._createHeader();}if(!this.header){return;}var H=this.header?D.getElementsByClassName("collapse","div",this.header)[0]:null;if(G){if(!this.get("header")){this.set("header"," ");}if(!H){H=document.createElement("div");this.header.appendChild(H);A.on(H,"click",this.collapse,this,true);}H.title=this.STR_COLLAPSE;H.className="collapse"+((this.get("close"))?" collapse-close":"");}else{if(H&&H.parentNode){A.purgeElement(H);H.parentNode.removeChild(H);}}}});this.setAttributeConfig("scroll",{value:(((F.scroll===true)||(F.scroll===false)||(F.scroll===null))?F.scroll:false),method:function(G){if((G===false)&&!this._collapsed){if(this.body){if(this.body.scrollTop>0){this._lastScrollTop=this.body.scrollTop;}}}if(G===true){this.addClass("yui-layout-scroll");this.removeClass("yui-layout-noscroll");if(this._lastScrollTop>0){if(this.body){this.body.scrollTop=this._lastScrollTop;}}}else{if(G===false){this.removeClass("yui-layout-scroll");this.addClass("yui-layout-noscroll");}else{if(G===null){this.removeClass("yui-layout-scroll");this.removeClass("yui-layout-noscroll");}}}}});this.setAttributeConfig("hover",{writeOnce:true,value:F.hover||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("useShim",{value:F.useShim||false,validator:YAHOO.lang.isBoolean,method:function(G){if(this._resize){this._resize.set("useShim",G);}}});this.setAttributeConfig("resize",{value:F.resize||false,validator:function(G){if(YAHOO.util&&YAHOO.util.Resize){return true;}return false;},method:function(G){if(G&&!this._resize){if(this.get("position")=="center"){return false;}var I=false;switch(this.get("position")){case"top":I="b";break;case"bottom":I="t";break;case"right":I="l";break;case"left":I="r";break;}this.setStyle("position","absolute");if(I){this._resize=new YAHOO.util.Resize(this.get("element"),{proxy:this.get("proxy"),hover:this.get("hover"),status:false,autoRatio:false,handles:[I],minWidth:this.get("minWidth"),maxWidth:this.get("maxWidth"),minHeight:this.get("minHeight"),maxHeight:this.get("maxHeight"),height:this.get("height"),width:this.get("width"),setSize:false,useShim:this.get("useShim"),wrap:false});this._resize._handles[I].innerHTML='<div class="yui-layout-resize-knob"></div>';if(this.get("proxy")){var H=this._resize.getProxyEl();H.innerHTML='<div class="yui-layout-handle-'+I+'"></div>';}this._resize.on("startResize",function(J){this._lastScroll=this.get("scroll");this.set("scroll",false);if(this.get("parent")){this.get("parent").fireEvent("startResize");var K=this.get("parent").getUnitByPosition("center");this._lastCenterScroll=K.get("scroll");K.addClass(this._resize.CSS_RESIZING);K.set("scroll",false);}this.fireEvent("startResize");},this,true);this._resize.on("resize",function(J){this.set("height",J.height);this.set("width",J.width);},this,true);this._resize.on("endResize",function(J){this.set("scroll",this._lastScroll);if(this.get("parent")){var K=this.get("parent").getUnitByPosition("center");K.set("scroll",this._lastCenterScroll);K.removeClass(this._resize.CSS_RESIZING);}this.resize();this.fireEvent("endResize");},this,true);}}else{if(this._resize){this._resize.destroy();}}}});this.setAttributeConfig("dataSrc",{value:F.dataSrc});this.setAttributeConfig("loadMethod",{value:F.loadMethod||"GET",validator:YAHOO.lang.isString});this.setAttributeConfig("dataLoaded",{value:false,validator:YAHOO.lang.isBoolean,writeOnce:true});this.setAttributeConfig("dataTimeout",{value:F.dataTimeout||null,validator:YAHOO.lang.isNumber});},_cleanGrids:function(){if(this.get("grids")){var F=C.query("div.yui-b",this.body,true);if(F){D.removeClass(F,"yui-b");}A.onAvailable("yui-main",function(){D.setStyle(C.query("#yui-main"),"margin-left","0");D.setStyle(C.query("#yui-main"),"margin-right","0");});}},_createHeader:function(){var F=document.createElement("div");F.className="yui-layout-hd";if(this.get("firstChild")){this.get("wrap").insertBefore(F,this.get("wrap").firstChild);}else{this.get("wrap").appendChild(F);}this.header=F;return F;},destroy:function(H){if(this._resize){this._resize.destroy();}var G=this.get("parent");this.setStyle("display","none");if(this._clip){this._clip.parentNode.removeChild(this._clip);this._clip=null;}if(!H){G.removeUnit(this);}if(G){G.removeListener("resize",this.resize,this,true);}this.unsubscribeAll();A.purgeElement(this.get("element"),true);this.get("parentNode").removeChild(this.get("element"));delete YAHOO.widget.LayoutUnit._instances[this.get("id")];for(var F in this){if(E.hasOwnProperty(this,F)){this[F]=null;delete this[F];}}return G;},toString:function(){if(this.get){return"LayoutUnit #"+this.get("id")+" ("+this.get("position")+")";}return"LayoutUnit";}});YAHOO.widget.LayoutUnit=B;})();YAHOO.register("layout",YAHOO.widget.Layout,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/logger/logger-min.js b/Websites/bugs.webkit.org/js/yui/logger/logger-min.js
new file mode 100644
index 0000000..8d43c3c
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/logger/logger-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.widget.LogMsg=function(a){this.msg=this.time=this.category=this.source=this.sourceDetail=null;if(a&&(a.constructor==Object)){for(var b in a){if(a.hasOwnProperty(b)){this[b]=a[b];}}}};YAHOO.widget.LogWriter=function(a){if(!a){YAHOO.log("Could not instantiate LogWriter due to invalid source.","error","LogWriter");return;}this._source=a;};YAHOO.widget.LogWriter.prototype.toString=function(){return"LogWriter "+this._sSource;};YAHOO.widget.LogWriter.prototype.log=function(a,b){YAHOO.widget.Logger.log(a,b,this._source);};YAHOO.widget.LogWriter.prototype.getSource=function(){return this._source;};YAHOO.widget.LogWriter.prototype.setSource=function(a){if(!a){YAHOO.log("Could not set source due to invalid source.","error",this.toString());return;}else{this._source=a;}};YAHOO.widget.LogWriter.prototype._source=null;if(!YAHOO.widget.Logger){YAHOO.widget.Logger={loggerEnabled:true,_browserConsoleEnabled:false,categories:["info","warn","error","time","window"],sources:["global"],_stack:[],maxStackEntries:2500,_startTime:new Date().getTime(),_lastTime:null,_windowErrorsHandled:false,_origOnWindowError:null};YAHOO.widget.Logger.log=function(b,f,g){if(this.loggerEnabled){if(!f){f="info";}else{f=f.toLocaleLowerCase();if(this._isNewCategory(f)){this._createNewCategory(f);}}var c="global";var a=null;if(g){var d=g.indexOf(" ");if(d>0){c=g.substring(0,d);a=g.substring(d,g.length);}else{c=g;}if(this._isNewSource(c)){this._createNewSource(c);}}var h=new Date();var j=new YAHOO.widget.LogMsg({msg:b,time:h,category:f,source:c,sourceDetail:a});var i=this._stack;var e=this.maxStackEntries;if(e&&!isNaN(e)&&(i.length>=e)){i.shift();}i.push(j);this.newLogEvent.fire(j);if(this._browserConsoleEnabled){this._printToBrowserConsole(j);}return true;}else{return false;}};YAHOO.widget.Logger.reset=function(){this._stack=[];this._startTime=new Date().getTime();this.loggerEnabled=true;this.log("Logger reset");this.logResetEvent.fire();};YAHOO.widget.Logger.getStack=function(){return this._stack;};YAHOO.widget.Logger.getStartTime=function(){return this._startTime;};YAHOO.widget.Logger.disableBrowserConsole=function(){YAHOO.log("Logger output to the function console.log() has been disabled.");this._browserConsoleEnabled=false;};YAHOO.widget.Logger.enableBrowserConsole=function(){this._browserConsoleEnabled=true;YAHOO.log("Logger output to the function console.log() has been enabled.");};YAHOO.widget.Logger.handleWindowErrors=function(){if(!YAHOO.widget.Logger._windowErrorsHandled){if(window.error){YAHOO.widget.Logger._origOnWindowError=window.onerror;}window.onerror=YAHOO.widget.Logger._onWindowError;YAHOO.widget.Logger._windowErrorsHandled=true;YAHOO.log("Logger handling of window.onerror has been enabled.");}else{YAHOO.log("Logger handling of window.onerror had already been enabled.");}};YAHOO.widget.Logger.unhandleWindowErrors=function(){if(YAHOO.widget.Logger._windowErrorsHandled){if(YAHOO.widget.Logger._origOnWindowError){window.onerror=YAHOO.widget.Logger._origOnWindowError;YAHOO.widget.Logger._origOnWindowError=null;}else{window.onerror=null;}YAHOO.widget.Logger._windowErrorsHandled=false;YAHOO.log("Logger handling of window.onerror has been disabled.");}else{YAHOO.log("Logger handling of window.onerror had already been disabled.");}};YAHOO.widget.Logger.categoryCreateEvent=new YAHOO.util.CustomEvent("categoryCreate",this,true);YAHOO.widget.Logger.sourceCreateEvent=new YAHOO.util.CustomEvent("sourceCreate",this,true);YAHOO.widget.Logger.newLogEvent=new YAHOO.util.CustomEvent("newLog",this,true);YAHOO.widget.Logger.logResetEvent=new YAHOO.util.CustomEvent("logReset",this,true);YAHOO.widget.Logger._createNewCategory=function(a){this.categories.push(a);this.categoryCreateEvent.fire(a);};YAHOO.widget.Logger._isNewCategory=function(b){for(var a=0;a<this.categories.length;a++){if(b==this.categories[a]){return false;}}return true;};YAHOO.widget.Logger._createNewSource=function(a){this.sources.push(a);this.sourceCreateEvent.fire(a);};YAHOO.widget.Logger._isNewSource=function(a){if(a){for(var b=0;b<this.sources.length;b++){if(a==this.sources[b]){return false;}}return true;}};YAHOO.widget.Logger._printToBrowserConsole=function(c){if((window.console&&console.log)||(window.opera&&opera.postError)){var e=c.category;var d=c.category.substring(0,4).toUpperCase();var g=c.time;var f;if(g.toLocaleTimeString){f=g.toLocaleTimeString();}else{f=g.toString();}var h=g.getTime();var b=(YAHOO.widget.Logger._lastTime)?(h-YAHOO.widget.Logger._lastTime):0;YAHOO.widget.Logger._lastTime=h;var a=f+" ("+b+"ms): "+c.source+": ";if(window.console){console.log(a,c.msg);}else{opera.postError(a+c.msg);}}};YAHOO.widget.Logger._onWindowError=function(a,c,b){try{YAHOO.widget.Logger.log(a+" ("+c+", line "+b+")","window");if(YAHOO.widget.Logger._origOnWindowError){YAHOO.widget.Logger._origOnWindowError();}}catch(d){return false;}};YAHOO.widget.Logger.log("Logger initialized");}(function(){var c=YAHOO.widget.Logger,e=YAHOO.util,f=e.Dom,a=e.Event,h=document;function b(i,d){i=h.createElement(i);if(d){for(var j in d){if(d.hasOwnProperty(j)){i[j]=d[j];}}}return i;}function g(i,d){this._sName=g._index;g._index++;this._init.apply(this,arguments);if(this.autoRender!==false){this.render();}}YAHOO.lang.augmentObject(g,{_index:0,ENTRY_TEMPLATE:(function(){return b("pre",{className:"yui-log-entry"});})(),VERBOSE_TEMPLATE:"<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}:</p><p>{sourceAndDetail}</p><p>{message}</p>",BASIC_TEMPLATE:"<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}</p>"});g.prototype={logReaderEnabled:true,width:null,height:null,top:null,left:null,right:null,bottom:null,fontSize:null,footerEnabled:true,verboseOutput:true,entryFormat:null,newestOnTop:true,outputBuffer:100,thresholdMax:500,thresholdMin:100,isCollapsed:false,isPaused:false,draggable:true,toString:function(){return"LogReader instance"+this._sName;},pause:function(){this.isPaused=true;this._timeout=null;
+this.logReaderEnabled=false;if(this._btnPause){this._btnPause.value="Resume";}},resume:function(){this.isPaused=false;this.logReaderEnabled=true;this._printBuffer();if(this._btnPause){this._btnPause.value="Pause";}},render:function(){if(this.rendered){return;}this._initContainerEl();this._initHeaderEl();this._initConsoleEl();this._initFooterEl();this._initCategories();this._initSources();this._initDragDrop();c.newLogEvent.subscribe(this._onNewLog,this);c.logResetEvent.subscribe(this._onReset,this);c.categoryCreateEvent.subscribe(this._onCategoryCreate,this);c.sourceCreateEvent.subscribe(this._onSourceCreate,this);this.rendered=true;this._filterLogs();},destroy:function(){a.purgeElement(this._elContainer,true);this._elContainer.innerHTML="";this._elContainer.parentNode.removeChild(this._elContainer);this.rendered=false;},hide:function(){this._elContainer.style.display="none";},show:function(){this._elContainer.style.display="block";},collapse:function(){this._elConsole.style.display="none";if(this._elFt){this._elFt.style.display="none";}this._btnCollapse.value="Expand";this.isCollapsed=true;},expand:function(){this._elConsole.style.display="block";if(this._elFt){this._elFt.style.display="block";}this._btnCollapse.value="Collapse";this.isCollapsed=false;},getCheckbox:function(d){return this._filterCheckboxes[d];},getCategories:function(){return this._categoryFilters;},showCategory:function(j){var l=this._categoryFilters;if(l.indexOf){if(l.indexOf(j)>-1){return;}}else{for(var d=0;d<l.length;d++){if(l[d]===j){return;}}}this._categoryFilters.push(j);this._filterLogs();var k=this.getCheckbox(j);if(k){k.checked=true;}},hideCategory:function(j){var l=this._categoryFilters;for(var d=0;d<l.length;d++){if(j==l[d]){l.splice(d,1);break;}}this._filterLogs();var k=this.getCheckbox(j);if(k){k.checked=false;}},getSources:function(){return this._sourceFilters;},showSource:function(d){var l=this._sourceFilters;if(l.indexOf){if(l.indexOf(d)>-1){return;}}else{for(var j=0;j<l.length;j++){if(d==l[j]){return;}}}l.push(d);this._filterLogs();var k=this.getCheckbox(d);if(k){k.checked=true;}},hideSource:function(d){var l=this._sourceFilters;for(var j=0;j<l.length;j++){if(d==l[j]){l.splice(j,1);break;}}this._filterLogs();var k=this.getCheckbox(d);if(k){k.checked=false;}},clearConsole:function(){this._timeout=null;this._buffer=[];this._consoleMsgCount=0;var d=this._elConsole;d.innerHTML="";},setTitle:function(d){this._title.innerHTML=this.html2Text(d);},getLastTime:function(){return this._lastTime;},formatMsg:function(i){var d=this.entryFormat||(this.verboseOutput?g.VERBOSE_TEMPLATE:g.BASIC_TEMPLATE),j={category:i.category,label:i.category.substring(0,4).toUpperCase(),sourceAndDetail:i.sourceDetail?i.source+" "+i.sourceDetail:i.source,message:this.html2Text(i.msg||i.message||"")};if(i.time&&i.time.getTime){j.localTime=i.time.toLocaleTimeString?i.time.toLocaleTimeString():i.time.toString();j.elapsedTime=i.time.getTime()-this.getLastTime();j.totalTime=i.time.getTime()-c.getStartTime();}var k=g.ENTRY_TEMPLATE.cloneNode(true);if(this.verboseOutput){k.className+=" yui-log-verbose";}k.innerHTML=d.replace(/\{(\w+)\}/g,function(l,m){return(m in j)?j[m]:"";});return k;},html2Text:function(d){if(d){d+="";return d.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");}return"";},_sName:null,_buffer:null,_consoleMsgCount:0,_lastTime:null,_timeout:null,_filterCheckboxes:null,_categoryFilters:null,_sourceFilters:null,_elContainer:null,_elHd:null,_elCollapse:null,_btnCollapse:null,_title:null,_elConsole:null,_elFt:null,_elBtns:null,_elCategoryFilters:null,_elSourceFilters:null,_btnPause:null,_btnClear:null,_init:function(d,i){this._buffer=[];this._filterCheckboxes={};this._lastTime=c.getStartTime();if(i&&(i.constructor==Object)){for(var j in i){if(i.hasOwnProperty(j)){this[j]=i[j];}}}this._elContainer=f.get(d);YAHOO.log("LogReader initialized",null,this.toString());},_initContainerEl:function(){if(!this._elContainer||!/div$/i.test(this._elContainer.tagName)){this._elContainer=h.body.insertBefore(b("div"),h.body.firstChild);f.addClass(this._elContainer,"yui-log-container");}f.addClass(this._elContainer,"yui-log");var k=this._elContainer.style,d=["width","right","top","fontSize"],l,j;for(j=d.length-1;j>=0;--j){l=d[j];if(this[l]){k[l]=this[l];}}if(this.left){k.left=this.left;k.right="auto";}if(this.bottom){k.bottom=this.bottom;k.top="auto";}if(YAHOO.env.ua.opera){h.body.style+="";}},_initHeaderEl:function(){if(this._elHd){a.purgeElement(this._elHd,true);this._elHd.innerHTML="";}this._elHd=b("div",{className:"yui-log-hd"});f.generateId(this._elHd,"yui-log-hd"+this._sName);this._elCollapse=b("div",{className:"yui-log-btns"});this._btnCollapse=b("input",{type:"button",className:"yui-log-button",value:"Collapse"});a.on(this._btnCollapse,"click",this._onClickCollapseBtn,this);this._title=b("h4",{innerHTML:"Logger Console"});this._elCollapse.appendChild(this._btnCollapse);this._elHd.appendChild(this._elCollapse);this._elHd.appendChild(this._title);this._elContainer.appendChild(this._elHd);},_initConsoleEl:function(){if(this._elConsole){a.purgeElement(this._elConsole,true);this._elConsole.innerHTML="";}this._elConsole=b("div",{className:"yui-log-bd"});if(this.height){this._elConsole.style.height=this.height;}this._elContainer.appendChild(this._elConsole);},_initFooterEl:function(){if(this.footerEnabled){if(this._elFt){a.purgeElement(this._elFt,true);this._elFt.innerHTML="";}this._elFt=b("div",{className:"yui-log-ft"});this._elBtns=b("div",{className:"yui-log-btns"});this._btnPause=b("input",{type:"button",className:"yui-log-button",value:"Pause"});a.on(this._btnPause,"click",this._onClickPauseBtn,this);this._btnClear=b("input",{type:"button",className:"yui-log-button",value:"Clear"});a.on(this._btnClear,"click",this._onClickClearBtn,this);this._elCategoryFilters=b("div",{className:"yui-log-categoryfilters"});this._elSourceFilters=b("div",{className:"yui-log-sourcefilters"});this._elBtns.appendChild(this._btnPause);this._elBtns.appendChild(this._btnClear);
+this._elFt.appendChild(this._elBtns);this._elFt.appendChild(this._elCategoryFilters);this._elFt.appendChild(this._elSourceFilters);this._elContainer.appendChild(this._elFt);}},_initDragDrop:function(){if(e.DD&&this.draggable&&this._elHd){var d=new e.DD(this._elContainer);d.setHandleElId(this._elHd.id);this._elHd.style.cursor="move";}},_initCategories:function(){this._categoryFilters=[];var k=c.categories;for(var d=0;d<k.length;d++){var i=k[d];this._categoryFilters.push(i);if(this._elCategoryFilters){this._createCategoryCheckbox(i);}}},_initSources:function(){this._sourceFilters=[];var k=c.sources;for(var i=0;i<k.length;i++){var d=k[i];this._sourceFilters.push(d);if(this._elSourceFilters){this._createSourceCheckbox(d);}}},_createCategoryCheckbox:function(l){if(this._elFt){var k=b("span",{className:"yui-log-filtergrp"}),j=f.generateId(null,"yui-log-filter-"+l+this._sName),d=b("input",{id:j,className:"yui-log-filter-"+l,type:"checkbox",category:l}),i=b("label",{htmlFor:j,className:l,innerHTML:l});a.on(d,"click",this._onCheckCategory,this);this._filterCheckboxes[l]=d;k.appendChild(d);k.appendChild(i);this._elCategoryFilters.appendChild(k);d.checked=true;}},_createSourceCheckbox:function(d){if(this._elFt){var l=b("span",{className:"yui-log-filtergrp"}),k=f.generateId(null,"yui-log-filter-"+d+this._sName),i=b("input",{id:k,className:"yui-log-filter-"+d,type:"checkbox",source:d}),j=b("label",{htmlFor:k,className:d,innerHTML:d});a.on(i,"click",this._onCheckSource,this);this._filterCheckboxes[d]=i;l.appendChild(i);l.appendChild(j);this._elSourceFilters.appendChild(l);i.checked=true;}},_filterLogs:function(){if(this._elConsole!==null){this.clearConsole();this._printToConsole(c.getStack());}},_printBuffer:function(){this._timeout=null;if(this._elConsole!==null){var j=this.thresholdMax;j=(j&&!isNaN(j))?j:500;if(this._consoleMsgCount<j){var d=[];for(var k=0;k<this._buffer.length;k++){d[k]=this._buffer[k];}this._buffer=[];this._printToConsole(d);}else{this._filterLogs();}if(!this.newestOnTop){this._elConsole.scrollTop=this._elConsole.scrollHeight;}}},_printToConsole:function(r){var k=r.length,v=h.createDocumentFragment(),y=[],z=this.thresholdMin,l=this._sourceFilters.length,w=this._categoryFilters.length,t,q,p,o,u;if(isNaN(z)||(z>this.thresholdMax)){z=0;}t=(k>z)?(k-z):0;for(q=t;q<k;q++){var n=false,s=false,x=r[q],d=x.source,m=x.category;for(p=0;p<l;p++){if(d==this._sourceFilters[p]){s=true;break;}}if(s){for(p=0;p<w;p++){if(m==this._categoryFilters[p]){n=true;break;}}}if(n){if(this._consoleMsgCount===0){this._lastTime=x.time.getTime();}o=this.formatMsg(x);if(typeof o==="string"){y[y.length]=o;}else{v.insertBefore(o,this.newestOnTop?v.firstChild||null:null);}this._consoleMsgCount++;this._lastTime=x.time.getTime();}}if(y.length){y.splice(0,0,this._elConsole.innerHTML);this._elConsole.innerHTML=this.newestOnTop?y.reverse().join(""):y.join("");}else{if(v.firstChild){this._elConsole.insertBefore(v,this.newestOnTop?this._elConsole.firstChild||null:null);}}},_onCategoryCreate:function(k,j,d){var i=j[0];d._categoryFilters.push(i);if(d._elFt){d._createCategoryCheckbox(i);}},_onSourceCreate:function(k,j,d){var i=j[0];d._sourceFilters.push(i);if(d._elFt){d._createSourceCheckbox(i);}},_onCheckCategory:function(d,i){var j=this.category;if(!this.checked){i.hideCategory(j);}else{i.showCategory(j);}},_onCheckSource:function(d,i){var j=this.source;if(!this.checked){i.hideSource(j);}else{i.showSource(j);}},_onClickCollapseBtn:function(d,i){if(!i.isCollapsed){i.collapse();}else{i.expand();}},_onClickPauseBtn:function(d,i){if(!i.isPaused){i.pause();}else{i.resume();}},_onClickClearBtn:function(d,i){i.clearConsole();},_onNewLog:function(k,j,d){var i=j[0];d._buffer.push(i);if(d.logReaderEnabled===true&&d._timeout===null){d._timeout=setTimeout(function(){d._printBuffer();},d.outputBuffer);}},_onReset:function(j,i,d){d._filterLogs();}};YAHOO.widget.LogReader=g;})();YAHOO.register("logger",YAHOO.widget.Logger,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/menu/menu-min.js b/Websites/bugs.webkit.org/js/yui/menu/menu-min.js
new file mode 100644
index 0000000..6122d38
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/menu/menu-min.js
@@ -0,0 +1,16 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var K=YAHOO.env.ua,C=YAHOO.util.Dom,Z=YAHOO.util.Event,H=YAHOO.lang,T="DIV",P="hd",M="bd",O="ft",X="LI",A="disabled",D="mouseover",F="mouseout",U="mousedown",G="mouseup",V="click",B="keydown",N="keyup",I="keypress",L="clicktohide",S="position",Q="dynamic",Y="showdelay",J="selected",E="visible",W="UL",R="MenuManager";YAHOO.widget.MenuManager=function(){var l=false,d={},o={},h={},c={"click":"clickEvent","mousedown":"mouseDownEvent","mouseup":"mouseUpEvent","mouseover":"mouseOverEvent","mouseout":"mouseOutEvent","keydown":"keyDownEvent","keyup":"keyUpEvent","keypress":"keyPressEvent","focus":"focusEvent","focusin":"focusEvent","blur":"blurEvent","focusout":"blurEvent"},i=null;function b(r){var p,q;if(r&&r.tagName){switch(r.tagName.toUpperCase()){case T:p=r.parentNode;if((C.hasClass(r,P)||C.hasClass(r,M)||C.hasClass(r,O))&&p&&p.tagName&&p.tagName.toUpperCase()==T){q=p;}else{q=r;}break;case X:q=r;break;default:p=r.parentNode;if(p){q=b(p);}break;}}return q;}function e(t){var p=Z.getTarget(t),q=b(p),u=true,w=t.type,x,r,s,z,y;if(q){r=q.tagName.toUpperCase();if(r==X){s=q.id;if(s&&h[s]){z=h[s];y=z.parent;}}else{if(r==T){if(q.id){y=d[q.id];}}}}if(y){x=c[w];if(w=="click"&&(K.gecko&&y.platform!="mac")&&t.button>0){u=false;}if(u&&z&&!z.cfg.getProperty(A)){z[x].fire(t);}if(u){y[x].fire(t,z);}}else{if(w==U){for(var v in o){if(H.hasOwnProperty(o,v)){y=o[v];if(y.cfg.getProperty(L)&&!(y instanceof YAHOO.widget.MenuBar)&&y.cfg.getProperty(S)==Q){y.hide();if(K.ie&&p.focus&&(K.ie<9)){p.setActive();}}else{if(y.cfg.getProperty(Y)>0){y._cancelShowDelay();}if(y.activeItem){y.activeItem.blur();y.activeItem.cfg.setProperty(J,false);y.activeItem=null;}}}}}}}function n(q,p,r){if(d[r.id]){this.removeMenu(r);}}function k(q,p){var r=p[1];if(r){i=r;}}function f(q,p){i=null;}function a(r,q){var p=q[0],s=this.id;if(p){o[s]=this;}else{if(o[s]){delete o[s];}}}function j(q,p){m(this);}function m(q){var p=q.id;if(p&&h[p]){if(i==q){i=null;}delete h[p];q.destroyEvent.unsubscribe(j);}}function g(q,p){var s=p[0],r;if(s instanceof YAHOO.widget.MenuItem){r=s.id;if(!h[r]){h[r]=s;s.destroyEvent.subscribe(j);}}}return{addMenu:function(q){var p;if(q instanceof YAHOO.widget.Menu&&q.id&&!d[q.id]){d[q.id]=q;if(!l){p=document;Z.on(p,D,e,this,true);Z.on(p,F,e,this,true);Z.on(p,U,e,this,true);Z.on(p,G,e,this,true);Z.on(p,V,e,this,true);Z.on(p,B,e,this,true);Z.on(p,N,e,this,true);Z.on(p,I,e,this,true);Z.onFocus(p,e,this,true);Z.onBlur(p,e,this,true);l=true;}q.cfg.subscribeToConfigEvent(E,a);q.destroyEvent.subscribe(n,q,this);q.itemAddedEvent.subscribe(g);q.focusEvent.subscribe(k);q.blurEvent.subscribe(f);}},removeMenu:function(s){var q,p,r;if(s){q=s.id;if((q in d)&&(d[q]==s)){p=s.getItems();if(p&&p.length>0){r=p.length-1;do{m(p[r]);}while(r--);}delete d[q];if((q in o)&&(o[q]==s)){delete o[q];}if(s.cfg){s.cfg.unsubscribeFromConfigEvent(E,a);}s.destroyEvent.unsubscribe(n,s);s.itemAddedEvent.unsubscribe(g);s.focusEvent.unsubscribe(k);s.blurEvent.unsubscribe(f);}}},hideVisible:function(){var p;for(var q in o){if(H.hasOwnProperty(o,q)){p=o[q];if(!(p instanceof YAHOO.widget.MenuBar)&&p.cfg.getProperty(S)==Q){p.hide();}}}},getVisible:function(){return o;},getMenus:function(){return d;},getMenu:function(q){var p;if(q in d){p=d[q];}return p;},getMenuItem:function(q){var p;if(q in h){p=h[q];}return p;},getMenuItemGroup:function(t){var q=C.get(t),p,v,u,r,s;if(q&&q.tagName&&q.tagName.toUpperCase()==W){v=q.firstChild;if(v){p=[];do{r=v.id;if(r){u=this.getMenuItem(r);if(u){p[p.length]=u;}}}while((v=v.nextSibling));if(p.length>0){s=p;}}}return s;},getFocusedMenuItem:function(){return i;},getFocusedMenu:function(){var p;if(i){p=i.parent.getRoot();}return p;},toString:function(){return R;}};}();})();(function(){var AM=YAHOO.lang,Aq="Menu",G="DIV",K="div",Am="id",AH="SELECT",e="xy",R="y",Ax="UL",L="ul",AJ="first-of-type",k="LI",h="OPTGROUP",Az="OPTION",Ah="disabled",AY="none",y="selected",At="groupindex",i="index",O="submenu",Au="visible",AX="hidedelay",Ac="position",AD="dynamic",C="static",An=AD+","+C,Q="url",M="#",V="target",AU="maxheight",T="topscrollbar",x="bottomscrollbar",d="_",P=T+d+Ah,E=x+d+Ah,b="mousemove",Av="showdelay",c="submenuhidedelay",AF="iframe",w="constraintoviewport",A4="preventcontextoverlap",AO="submenualignment",Z="autosubmenudisplay",AC="clicktohide",g="container",j="scrollincrement",Aj="minscrollheight",A2="classname",Ag="shadow",Ar="keepopen",A0="hd",D="hastitle",p="context",u="",Ak="mousedown",Ae="keydown",Ao="height",U="width",AQ="px",Ay="effect",AE="monitorresize",AW="display",AV="block",J="visibility",z="absolute",AS="zindex",l="yui-menu-body-scrolled",AK=" ",A1=" ",Ai="mouseover",H="mouseout",AR="itemAdded",n="itemRemoved",AL="hidden",s="yui-menu-shadow",AG=s+"-visible",m=s+A1+AG;YAHOO.widget.Menu=function(A6,A5){if(A5){this.parent=A5.parent;this.lazyLoad=A5.lazyLoad||A5.lazyload;this.itemData=A5.itemData||A5.itemdata;}YAHOO.widget.Menu.superclass.constructor.call(this,A6,A5);};function B(A6){var A5=false;if(AM.isString(A6)){A5=(An.indexOf((A6.toLowerCase()))!=-1);}return A5;}var f=YAHOO.util.Dom,AA=YAHOO.util.Event,Aw=YAHOO.widget.Module,AB=YAHOO.widget.Overlay,r=YAHOO.widget.Menu,A3=YAHOO.widget.MenuManager,F=YAHOO.util.CustomEvent,As=YAHOO.env.ua,Ap,AT=false,Ad,Ab=[["mouseOverEvent",Ai],["mouseOutEvent",H],["mouseDownEvent",Ak],["mouseUpEvent","mouseup"],["clickEvent","click"],["keyPressEvent","keypress"],["keyDownEvent",Ae],["keyUpEvent","keyup"],["focusEvent","focus"],["blurEvent","blur"],["itemAddedEvent",AR],["itemRemovedEvent",n]],AZ={key:Au,value:false,validator:AM.isBoolean},AP={key:w,value:true,validator:AM.isBoolean,supercedes:[AF,"x",R,e]},AI={key:A4,value:true,validator:AM.isBoolean,supercedes:[w]},S={key:Ac,value:AD,validator:B,supercedes:[Au,AF]},A={key:AO,value:["tl","tr"]},t={key:Z,value:true,validator:AM.isBoolean,suppressEvent:true},Y={key:Av,value:250,validator:AM.isNumber,suppressEvent:true},q={key:AX,value:0,validator:AM.isNumber,suppressEvent:true},v={key:c,value:250,validator:AM.isNumber,suppressEvent:true},o={key:AC,value:true,validator:AM.isBoolean,suppressEvent:true},AN={key:g,suppressEvent:true},Af={key:j,value:1,validator:AM.isNumber,supercedes:[AU],suppressEvent:true},N={key:Aj,value:90,validator:AM.isNumber,supercedes:[AU],suppressEvent:true},X={key:AU,value:0,validator:AM.isNumber,supercedes:[AF],suppressEvent:true},W={key:A2,value:null,validator:AM.isString,suppressEvent:true},a={key:Ah,value:false,validator:AM.isBoolean,suppressEvent:true},I={key:Ag,value:true,validator:AM.isBoolean,suppressEvent:true,supercedes:[Au]},Al={key:Ar,value:false,validator:AM.isBoolean};
+function Aa(A5){Ad=AA.getTarget(A5);}YAHOO.lang.extend(r,AB,{CSS_CLASS_NAME:"yuimenu",ITEM_TYPE:null,GROUP_TITLE_TAG_NAME:"h6",OFF_SCREEN_POSITION:"-999em",_useHideDelay:false,_bHandledMouseOverEvent:false,_bHandledMouseOutEvent:false,_aGroupTitleElements:null,_aItemGroups:null,_aListElements:null,_nCurrentMouseX:0,_bStopMouseEventHandlers:false,_sClassName:null,lazyLoad:false,itemData:null,activeItem:null,parent:null,srcElement:null,init:function(A7,A6){this._aItemGroups=[];this._aListElements=[];this._aGroupTitleElements=[];if(!this.ITEM_TYPE){this.ITEM_TYPE=YAHOO.widget.MenuItem;}var A5;if(AM.isString(A7)){A5=f.get(A7);}else{if(A7.tagName){A5=A7;}}if(A5&&A5.tagName){switch(A5.tagName.toUpperCase()){case G:this.srcElement=A5;if(!A5.id){A5.setAttribute(Am,f.generateId());}r.superclass.init.call(this,A5);this.beforeInitEvent.fire(r);break;case AH:this.srcElement=A5;r.superclass.init.call(this,f.generateId());this.beforeInitEvent.fire(r);break;}}else{r.superclass.init.call(this,A7);this.beforeInitEvent.fire(r);}if(this.element){f.addClass(this.element,this.CSS_CLASS_NAME);this.initEvent.subscribe(this._onInit);this.beforeRenderEvent.subscribe(this._onBeforeRender);this.renderEvent.subscribe(this._onRender);this.beforeShowEvent.subscribe(this._onBeforeShow);this.hideEvent.subscribe(this._onHide);this.showEvent.subscribe(this._onShow);this.beforeHideEvent.subscribe(this._onBeforeHide);this.mouseOverEvent.subscribe(this._onMouseOver);this.mouseOutEvent.subscribe(this._onMouseOut);this.clickEvent.subscribe(this._onClick);this.keyDownEvent.subscribe(this._onKeyDown);this.keyPressEvent.subscribe(this._onKeyPress);this.blurEvent.subscribe(this._onBlur);if(!AT){AA.onFocus(document,Aa);AT=true;}if((As.gecko&&As.gecko<1.9)||(As.webkit&&As.webkit<523)){this.cfg.subscribeToConfigEvent(R,this._onYChange);}if(A6){this.cfg.applyConfig(A6,true);}A3.addMenu(this);this.initEvent.fire(r);}},_initSubTree:function(){var A6=this.srcElement,A5,A8,BB,BC,BA,A9,A7;if(A6){A5=(A6.tagName&&A6.tagName.toUpperCase());if(A5==G){BC=this.body.firstChild;if(BC){A8=0;BB=this.GROUP_TITLE_TAG_NAME.toUpperCase();do{if(BC&&BC.tagName){switch(BC.tagName.toUpperCase()){case BB:this._aGroupTitleElements[A8]=BC;break;case Ax:this._aListElements[A8]=BC;this._aItemGroups[A8]=[];A8++;break;}}}while((BC=BC.nextSibling));if(this._aListElements[0]){f.addClass(this._aListElements[0],AJ);}}}BC=null;if(A5){switch(A5){case G:BA=this._aListElements;A9=BA.length;if(A9>0){A7=A9-1;do{BC=BA[A7].firstChild;if(BC){do{if(BC&&BC.tagName&&BC.tagName.toUpperCase()==k){this.addItem(new this.ITEM_TYPE(BC,{parent:this}),A7);}}while((BC=BC.nextSibling));}}while(A7--);}break;case AH:BC=A6.firstChild;do{if(BC&&BC.tagName){switch(BC.tagName.toUpperCase()){case h:case Az:this.addItem(new this.ITEM_TYPE(BC,{parent:this}));break;}}}while((BC=BC.nextSibling));break;}}}},_getFirstEnabledItem:function(){var A5=this.getItems(),A9=A5.length,A8,A7;for(var A6=0;A6<A9;A6++){A8=A5[A6];if(A8&&!A8.cfg.getProperty(Ah)&&A8.element.style.display!=AY){A7=A8;break;}}return A7;},_addItemToGroup:function(BA,BB,BF){var BD,BG,A8,BE,A9,A6,A7,BC;function A5(BH,BI){return(BH[BI]||A5(BH,(BI+1)));}if(BB instanceof this.ITEM_TYPE){BD=BB;BD.parent=this;}else{if(AM.isString(BB)){BD=new this.ITEM_TYPE(BB,{parent:this});}else{if(AM.isObject(BB)){BB.parent=this;BD=new this.ITEM_TYPE(BB.text,BB);}}}if(BD){if(BD.cfg.getProperty(y)){this.activeItem=BD;}BG=AM.isNumber(BA)?BA:0;A8=this._getItemGroup(BG);if(!A8){A8=this._createItemGroup(BG);}if(AM.isNumber(BF)){A9=(BF>=A8.length);if(A8[BF]){A8.splice(BF,0,BD);}else{A8[BF]=BD;}BE=A8[BF];if(BE){if(A9&&(!BE.element.parentNode||BE.element.parentNode.nodeType==11)){this._aListElements[BG].appendChild(BE.element);}else{A6=A5(A8,(BF+1));if(A6&&(!BE.element.parentNode||BE.element.parentNode.nodeType==11)){this._aListElements[BG].insertBefore(BE.element,A6.element);}}BE.parent=this;this._subscribeToItemEvents(BE);this._configureSubmenu(BE);this._updateItemProperties(BG);this.itemAddedEvent.fire(BE);this.changeContentEvent.fire();BC=BE;}}else{A7=A8.length;A8[A7]=BD;BE=A8[A7];if(BE){if(!f.isAncestor(this._aListElements[BG],BE.element)){this._aListElements[BG].appendChild(BE.element);}BE.element.setAttribute(At,BG);BE.element.setAttribute(i,A7);BE.parent=this;BE.index=A7;BE.groupIndex=BG;this._subscribeToItemEvents(BE);this._configureSubmenu(BE);if(A7===0){f.addClass(BE.element,AJ);}this.itemAddedEvent.fire(BE);this.changeContentEvent.fire();BC=BE;}}}return BC;},_removeItemFromGroupByIndex:function(A8,A6){var A7=AM.isNumber(A8)?A8:0,A9=this._getItemGroup(A7),BB,BA,A5;if(A9){BB=A9.splice(A6,1);BA=BB[0];if(BA){this._updateItemProperties(A7);if(A9.length===0){A5=this._aListElements[A7];if(A5&&A5.parentNode){A5.parentNode.removeChild(A5);}this._aItemGroups.splice(A7,1);this._aListElements.splice(A7,1);A5=this._aListElements[0];if(A5){f.addClass(A5,AJ);}}this.itemRemovedEvent.fire(BA);this.changeContentEvent.fire();}}return BA;},_removeItemFromGroupByValue:function(A8,A5){var BA=this._getItemGroup(A8),BB,A9,A7,A6;if(BA){BB=BA.length;A9=-1;if(BB>0){A6=BB-1;do{if(BA[A6]==A5){A9=A6;break;}}while(A6--);if(A9>-1){A7=this._removeItemFromGroupByIndex(A8,A9);}}}return A7;},_updateItemProperties:function(A6){var A7=this._getItemGroup(A6),BA=A7.length,A9,A8,A5;if(BA>0){A5=BA-1;do{A9=A7[A5];if(A9){A8=A9.element;A9.index=A5;A9.groupIndex=A6;A8.setAttribute(At,A6);A8.setAttribute(i,A5);f.removeClass(A8,AJ);}}while(A5--);if(A8){f.addClass(A8,AJ);}}},_createItemGroup:function(A7){var A5,A6;if(!this._aItemGroups[A7]){this._aItemGroups[A7]=[];A5=document.createElement(L);this._aListElements[A7]=A5;A6=this._aItemGroups[A7];}return A6;},_getItemGroup:function(A7){var A5=AM.isNumber(A7)?A7:0,A8=this._aItemGroups,A6;if(A5 in A8){A6=A8[A5];}return A6;},_configureSubmenu:function(A5){var A6=A5.cfg.getProperty(O);if(A6){this.cfg.configChangedEvent.subscribe(this._onParentMenuConfigChange,A6,true);this.renderEvent.subscribe(this._onParentMenuRender,A6,true);}},_subscribeToItemEvents:function(A5){A5.destroyEvent.subscribe(this._onMenuItemDestroy,A5,this);
+A5.cfg.configChangedEvent.subscribe(this._onMenuItemConfigChange,A5,this);},_onVisibleChange:function(A7,A6){var A5=A6[0];if(A5){f.addClass(this.element,Au);}else{f.removeClass(this.element,Au);}},_cancelHideDelay:function(){var A5=this.getRoot()._hideDelayTimer;if(A5){A5.cancel();}},_execHideDelay:function(){this._cancelHideDelay();var A5=this.getRoot();A5._hideDelayTimer=AM.later(A5.cfg.getProperty(AX),this,function(){if(A5.activeItem){if(A5.hasFocus()){A5.activeItem.focus();}A5.clearActiveItem();}if(A5==this&&!(this instanceof YAHOO.widget.MenuBar)&&this.cfg.getProperty(Ac)==AD){this.hide();}});},_cancelShowDelay:function(){var A5=this.getRoot()._showDelayTimer;if(A5){A5.cancel();}},_execSubmenuHideDelay:function(A7,A6,A5){A7._submenuHideDelayTimer=AM.later(50,this,function(){if(this._nCurrentMouseX>(A6+10)){A7._submenuHideDelayTimer=AM.later(A5,A7,function(){this.hide();});}else{A7.hide();}});},_disableScrollHeader:function(){if(!this._bHeaderDisabled){f.addClass(this.header,P);this._bHeaderDisabled=true;}},_disableScrollFooter:function(){if(!this._bFooterDisabled){f.addClass(this.footer,E);this._bFooterDisabled=true;}},_enableScrollHeader:function(){if(this._bHeaderDisabled){f.removeClass(this.header,P);this._bHeaderDisabled=false;}},_enableScrollFooter:function(){if(this._bFooterDisabled){f.removeClass(this.footer,E);this._bFooterDisabled=false;}},_onMouseOver:function(BH,BA){var BI=BA[0],BE=BA[1],A5=AA.getTarget(BI),A9=this.getRoot(),BG=this._submenuHideDelayTimer,A6,A8,BD,A7,BC,BB;var BF=function(){if(this.parent.cfg.getProperty(y)){this.show();}};if(!this._bStopMouseEventHandlers){if(!this._bHandledMouseOverEvent&&(A5==this.element||f.isAncestor(this.element,A5))){if(this._useHideDelay){this._cancelHideDelay();}this._nCurrentMouseX=0;AA.on(this.element,b,this._onMouseMove,this,true);if(!(BE&&f.isAncestor(BE.element,AA.getRelatedTarget(BI)))){this.clearActiveItem();}if(this.parent&&BG){BG.cancel();this.parent.cfg.setProperty(y,true);A6=this.parent.parent;A6._bHandledMouseOutEvent=true;A6._bHandledMouseOverEvent=false;}this._bHandledMouseOverEvent=true;this._bHandledMouseOutEvent=false;}if(BE&&!BE.handledMouseOverEvent&&!BE.cfg.getProperty(Ah)&&(A5==BE.element||f.isAncestor(BE.element,A5))){A8=this.cfg.getProperty(Av);BD=(A8>0);if(BD){this._cancelShowDelay();}A7=this.activeItem;if(A7){A7.cfg.setProperty(y,false);}BC=BE.cfg;BC.setProperty(y,true);if(this.hasFocus()||A9._hasFocus){BE.focus();A9._hasFocus=false;}if(this.cfg.getProperty(Z)){BB=BC.getProperty(O);if(BB){if(BD){A9._showDelayTimer=AM.later(A9.cfg.getProperty(Av),BB,BF);}else{BB.show();}}}BE.handledMouseOverEvent=true;BE.handledMouseOutEvent=false;}}},_onMouseOut:function(BD,A7){var BE=A7[0],BB=A7[1],A8=AA.getRelatedTarget(BE),BC=false,BA,A9,A5,A6;if(!this._bStopMouseEventHandlers){if(BB&&!BB.cfg.getProperty(Ah)){BA=BB.cfg;A9=BA.getProperty(O);if(A9&&(A8==A9.element||f.isAncestor(A9.element,A8))){BC=true;}if(!BB.handledMouseOutEvent&&((A8!=BB.element&&!f.isAncestor(BB.element,A8))||BC)){if(!BC){BB.cfg.setProperty(y,false);if(A9){A5=this.cfg.getProperty(c);A6=this.cfg.getProperty(Av);if(!(this instanceof YAHOO.widget.MenuBar)&&A5>0&&A5>=A6){this._execSubmenuHideDelay(A9,AA.getPageX(BE),A5);}else{A9.hide();}}}BB.handledMouseOutEvent=true;BB.handledMouseOverEvent=false;}}if(!this._bHandledMouseOutEvent){if(this._didMouseLeave(A8)||BC){if(this._useHideDelay){this._execHideDelay();}AA.removeListener(this.element,b,this._onMouseMove);this._nCurrentMouseX=AA.getPageX(BE);this._bHandledMouseOutEvent=true;this._bHandledMouseOverEvent=false;}}}},_didMouseLeave:function(A5){return(A5===this._shadow||(A5!=this.element&&!f.isAncestor(this.element,A5)));},_onMouseMove:function(A6,A5){if(!this._bStopMouseEventHandlers){this._nCurrentMouseX=AA.getPageX(A6);}},_onClick:function(BG,A7){var BH=A7[0],BB=A7[1],BD=false,A9,BE,A6,A5,BA,BC,BF;var A8=function(){A6=this.getRoot();if(A6 instanceof YAHOO.widget.MenuBar||A6.cfg.getProperty(Ac)==C){A6.clearActiveItem();}else{A6.hide();}};if(BB){if(BB.cfg.getProperty(Ah)){AA.preventDefault(BH);A8.call(this);}else{A9=BB.cfg.getProperty(O);BA=BB.cfg.getProperty(Q);if(BA){BC=BA.indexOf(M);BF=BA.length;if(BC!=-1){BA=BA.substr(BC,BF);BF=BA.length;if(BF>1){A5=BA.substr(1,BF);BE=YAHOO.widget.MenuManager.getMenu(A5);if(BE){BD=(this.getRoot()===BE.getRoot());}}else{if(BF===1){BD=true;}}}}if(BD&&!BB.cfg.getProperty(V)){AA.preventDefault(BH);if(As.webkit){BB.focus();}else{BB.focusEvent.fire();}}if(!A9&&!this.cfg.getProperty(Ar)){A8.call(this);}}}},_stopMouseEventHandlers:function(){this._bStopMouseEventHandlers=true;AM.later(10,this,function(){this._bStopMouseEventHandlers=false;});},_onKeyDown:function(BJ,BD){var BG=BD[0],BF=BD[1],BC,BH,A6,A9,BK,A5,BN,A8,BI,A7,BE,BM,BA,BB;if(this._useHideDelay){this._cancelHideDelay();}if(BF&&!BF.cfg.getProperty(Ah)){BH=BF.cfg;A6=this.parent;switch(BG.keyCode){case 38:case 40:BK=(BG.keyCode==38)?BF.getPreviousEnabledSibling():BF.getNextEnabledSibling();if(BK){this.clearActiveItem();BK.cfg.setProperty(y,true);BK.focus();if(this.cfg.getProperty(AU)>0||f.hasClass(this.body,l)){A5=this.body;BN=A5.scrollTop;A8=A5.offsetHeight;BI=this.getItems();A7=BI.length-1;BE=BK.element.offsetTop;if(BG.keyCode==40){if(BE>=(A8+BN)){A5.scrollTop=BE-A8;}else{if(BE<=BN){A5.scrollTop=0;}}if(BK==BI[A7]){A5.scrollTop=BK.element.offsetTop;}}else{if(BE<=BN){A5.scrollTop=BE-BK.element.offsetHeight;}else{if(BE>=(BN+A8)){A5.scrollTop=BE;}}if(BK==BI[0]){A5.scrollTop=0;}}BN=A5.scrollTop;BM=A5.scrollHeight-A5.offsetHeight;if(BN===0){this._disableScrollHeader();this._enableScrollFooter();}else{if(BN==BM){this._enableScrollHeader();this._disableScrollFooter();}else{this._enableScrollHeader();this._enableScrollFooter();}}}}AA.preventDefault(BG);this._stopMouseEventHandlers();break;case 39:BC=BH.getProperty(O);if(BC){if(!BH.getProperty(y)){BH.setProperty(y,true);}BC.show();BC.setInitialFocus();BC.setInitialSelection();}else{A9=this.getRoot();if(A9 instanceof YAHOO.widget.MenuBar){BK=A9.activeItem.getNextEnabledSibling();
+if(BK){A9.clearActiveItem();BK.cfg.setProperty(y,true);BC=BK.cfg.getProperty(O);if(BC){BC.show();BC.setInitialFocus();}else{BK.focus();}}}}AA.preventDefault(BG);this._stopMouseEventHandlers();break;case 37:if(A6){BA=A6.parent;if(BA instanceof YAHOO.widget.MenuBar){BK=BA.activeItem.getPreviousEnabledSibling();if(BK){BA.clearActiveItem();BK.cfg.setProperty(y,true);BC=BK.cfg.getProperty(O);if(BC){BC.show();BC.setInitialFocus();}else{BK.focus();}}}else{this.hide();A6.focus();}}AA.preventDefault(BG);this._stopMouseEventHandlers();break;}}if(BG.keyCode==27){if(this.cfg.getProperty(Ac)==AD){this.hide();if(this.parent){this.parent.focus();}else{BB=this._focusedElement;if(BB&&BB.focus){try{BB.focus();}catch(BL){}}}}else{if(this.activeItem){BC=this.activeItem.cfg.getProperty(O);if(BC&&BC.cfg.getProperty(Au)){BC.hide();this.activeItem.focus();}else{this.activeItem.blur();this.activeItem.cfg.setProperty(y,false);}}}AA.preventDefault(BG);}},_onKeyPress:function(A7,A6){var A5=A6[0];if(A5.keyCode==40||A5.keyCode==38){AA.preventDefault(A5);}},_onBlur:function(A6,A5){if(this._hasFocus){this._hasFocus=false;}},_onYChange:function(A6,A5){var A8=this.parent,BA,A7,A9;if(A8){BA=A8.parent.body.scrollTop;if(BA>0){A9=(this.cfg.getProperty(R)-BA);f.setY(this.element,A9);A7=this.iframe;if(A7){f.setY(A7,A9);}this.cfg.setProperty(R,A9,true);}}},_onScrollTargetMouseOver:function(BB,BE){var BD=this._bodyScrollTimer;if(BD){BD.cancel();}this._cancelHideDelay();var A7=AA.getTarget(BB),A9=this.body,A8=this.cfg.getProperty(j),A5,A6;function BC(){var BF=A9.scrollTop;if(BF<A5){A9.scrollTop=(BF+A8);this._enableScrollHeader();}else{A9.scrollTop=A5;this._bodyScrollTimer.cancel();this._disableScrollFooter();}}function BA(){var BF=A9.scrollTop;if(BF>0){A9.scrollTop=(BF-A8);this._enableScrollFooter();}else{A9.scrollTop=0;this._bodyScrollTimer.cancel();this._disableScrollHeader();}}if(f.hasClass(A7,A0)){A6=BA;}else{A5=A9.scrollHeight-A9.offsetHeight;A6=BC;}this._bodyScrollTimer=AM.later(10,this,A6,null,true);},_onScrollTargetMouseOut:function(A7,A5){var A6=this._bodyScrollTimer;if(A6){A6.cancel();}this._cancelHideDelay();},_onInit:function(A6,A5){this.cfg.subscribeToConfigEvent(Au,this._onVisibleChange);var A7=!this.parent,A8=this.lazyLoad;if(((A7&&!A8)||(A7&&(this.cfg.getProperty(Au)||this.cfg.getProperty(Ac)==C))||(!A7&&!A8))&&this.getItemGroups().length===0){if(this.srcElement){this._initSubTree();}if(this.itemData){this.addItems(this.itemData);}}else{if(A8){this.cfg.fireQueue();}}},_onBeforeRender:function(A8,A7){var A9=this.element,BC=this._aListElements.length,A6=true,BB=0,A5,BA;if(BC>0){do{A5=this._aListElements[BB];if(A5){if(A6){f.addClass(A5,AJ);A6=false;}if(!f.isAncestor(A9,A5)){this.appendToBody(A5);}BA=this._aGroupTitleElements[BB];if(BA){if(!f.isAncestor(A9,BA)){A5.parentNode.insertBefore(BA,A5);}f.addClass(A5,D);}}BB++;}while(BB<BC);}},_onRender:function(A6,A5){if(this.cfg.getProperty(Ac)==AD){if(!this.cfg.getProperty(Au)){this.positionOffScreen();}}},_onBeforeShow:function(A7,A6){var A9,BC,A8,BA=this.cfg.getProperty(g);if(this.lazyLoad&&this.getItemGroups().length===0){if(this.srcElement){this._initSubTree();}if(this.itemData){if(this.parent&&this.parent.parent&&this.parent.parent.srcElement&&this.parent.parent.srcElement.tagName.toUpperCase()==AH){A9=this.itemData.length;for(BC=0;BC<A9;BC++){if(this.itemData[BC].tagName){this.addItem((new this.ITEM_TYPE(this.itemData[BC])));}}}else{this.addItems(this.itemData);}}A8=this.srcElement;if(A8){if(A8.tagName.toUpperCase()==AH){if(f.inDocument(A8)){this.render(A8.parentNode);}else{this.render(BA);}}else{this.render();}}else{if(this.parent){this.render(this.parent.element);}else{this.render(BA);}}}var BB=this.parent,A5;if(!BB&&this.cfg.getProperty(Ac)==AD){this.cfg.refireEvent(e);}if(BB){A5=BB.parent.cfg.getProperty(AO);this.cfg.setProperty(p,[BB.element,A5[0],A5[1]]);this.align();}},getConstrainedY:function(BH){var BS=this,BO=BS.cfg.getProperty(p),BV=BS.cfg.getProperty(AU),BR,BG={"trbr":true,"tlbl":true,"bltl":true,"brtr":true},BA=(BO&&BG[BO[1]+BO[2]]),BC=BS.element,BW=BC.offsetHeight,BQ=AB.VIEWPORT_OFFSET,BL=f.getViewportHeight(),BP=f.getDocumentScrollTop(),BM=(BS.cfg.getProperty(Aj)+BQ<BL),BU,BD,BJ,BK,BF=false,BE,A7,BI=BP+BQ,A9=BP+BL-BW-BQ,A5=BH;var BB=function(){var BX;if((BS.cfg.getProperty(R)-BP)>BJ){BX=(BJ-BW);}else{BX=(BJ+BK);}BS.cfg.setProperty(R,(BX+BP),true);return BX;};var A8=function(){if((BS.cfg.getProperty(R)-BP)>BJ){return(A7-BQ);}else{return(BE-BQ);}};var BN=function(){var BX;if((BS.cfg.getProperty(R)-BP)>BJ){BX=(BJ+BK);}else{BX=(BJ-BC.offsetHeight);}BS.cfg.setProperty(R,(BX+BP),true);};var A6=function(){BS._setScrollHeight(this.cfg.getProperty(AU));BS.hideEvent.unsubscribe(A6);};var BT=function(){var Ba=A8(),BX=(BS.getItems().length>0),BZ,BY;if(BW>Ba){BZ=BX?BS.cfg.getProperty(Aj):BW;if((Ba>BZ)&&BX){BR=Ba;}else{BR=BV;}BS._setScrollHeight(BR);BS.hideEvent.subscribe(A6);BN();if(Ba<BZ){if(BF){BB();}else{BB();BF=true;BY=BT();}}}else{if(BR&&(BR!==BV)){BS._setScrollHeight(BV);BS.hideEvent.subscribe(A6);BN();}}return BY;};if(BH<BI||BH>A9){if(BM){if(BS.cfg.getProperty(A4)&&BA){BD=BO[0];BK=BD.offsetHeight;BJ=(f.getY(BD)-BP);BE=BJ;A7=(BL-(BJ+BK));BT();A5=BS.cfg.getProperty(R);}else{if(!(BS instanceof YAHOO.widget.MenuBar)&&BW>=BL){BU=(BL-(BQ*2));if(BU>BS.cfg.getProperty(Aj)){BS._setScrollHeight(BU);BS.hideEvent.subscribe(A6);BN();A5=BS.cfg.getProperty(R);}}else{if(BH<BI){A5=BI;}else{if(BH>A9){A5=A9;}}}}}else{A5=BQ+BP;}}return A5;},_onHide:function(A6,A5){if(this.cfg.getProperty(Ac)===AD){this.positionOffScreen();}},_onShow:function(BD,BB){var A5=this.parent,A7,A8,BA,A6;function A9(BF){var BE;if(BF.type==Ak||(BF.type==Ae&&BF.keyCode==27)){BE=AA.getTarget(BF);if(BE!=A7.element||!f.isAncestor(A7.element,BE)){A7.cfg.setProperty(Z,false);AA.removeListener(document,Ak,A9);AA.removeListener(document,Ae,A9);}}}function BC(BF,BE,BG){this.cfg.setProperty(U,u);this.hideEvent.unsubscribe(BC,BG);}if(A5){A7=A5.parent;if(!A7.cfg.getProperty(Z)&&(A7 instanceof YAHOO.widget.MenuBar||A7.cfg.getProperty(Ac)==C)){A7.cfg.setProperty(Z,true);
+AA.on(document,Ak,A9);AA.on(document,Ae,A9);}if((this.cfg.getProperty("x")<A7.cfg.getProperty("x"))&&(As.gecko&&As.gecko<1.9)&&!this.cfg.getProperty(U)){A8=this.element;BA=A8.offsetWidth;A8.style.width=BA+AQ;A6=(BA-(A8.offsetWidth-BA))+AQ;this.cfg.setProperty(U,A6);this.hideEvent.subscribe(BC,A6);}}if(this===this.getRoot()&&this.cfg.getProperty(Ac)===AD){this._focusedElement=Ad;this.focus();}},_onBeforeHide:function(A7,A6){var A5=this.activeItem,A9=this.getRoot(),BA,A8;if(A5){BA=A5.cfg;BA.setProperty(y,false);A8=BA.getProperty(O);if(A8){A8.hide();}}if(As.ie&&this.cfg.getProperty(Ac)===AD&&this.parent){A9._hasFocus=this.hasFocus();}if(A9==this){A9.blur();}},_onParentMenuConfigChange:function(A6,A5,A9){var A7=A5[0][0],A8=A5[0][1];switch(A7){case AF:case w:case AX:case Av:case c:case AC:case Ay:case A2:case j:case AU:case Aj:case AE:case Ag:case A4:case Ar:A9.cfg.setProperty(A7,A8);break;case AO:if(!(this.parent.parent instanceof YAHOO.widget.MenuBar)){A9.cfg.setProperty(A7,A8);}break;}},_onParentMenuRender:function(A6,A5,BB){var A8=BB.parent.parent,A7=A8.cfg,A9={constraintoviewport:A7.getProperty(w),xy:[0,0],clicktohide:A7.getProperty(AC),effect:A7.getProperty(Ay),showdelay:A7.getProperty(Av),hidedelay:A7.getProperty(AX),submenuhidedelay:A7.getProperty(c),classname:A7.getProperty(A2),scrollincrement:A7.getProperty(j),maxheight:A7.getProperty(AU),minscrollheight:A7.getProperty(Aj),iframe:A7.getProperty(AF),shadow:A7.getProperty(Ag),preventcontextoverlap:A7.getProperty(A4),monitorresize:A7.getProperty(AE),keepopen:A7.getProperty(Ar)},BA;if(!(A8 instanceof YAHOO.widget.MenuBar)){A9[AO]=A7.getProperty(AO);}BB.cfg.applyConfig(A9);if(!this.lazyLoad){BA=this.parent.element;if(this.element.parentNode==BA){this.render();}else{this.render(BA);}}},_onMenuItemDestroy:function(A7,A6,A5){this._removeItemFromGroupByValue(A5.groupIndex,A5);},_onMenuItemConfigChange:function(A7,A6,A5){var A9=A6[0][0],BA=A6[0][1],A8;switch(A9){case y:if(BA===true){this.activeItem=A5;}break;case O:A8=A6[0][1];if(A8){this._configureSubmenu(A5);}break;}},configVisible:function(A7,A6,A8){var A5,A9;if(this.cfg.getProperty(Ac)==AD){r.superclass.configVisible.call(this,A7,A6,A8);}else{A5=A6[0];A9=f.getStyle(this.element,AW);f.setStyle(this.element,J,Au);if(A5){if(A9!=AV){this.beforeShowEvent.fire();f.setStyle(this.element,AW,AV);this.showEvent.fire();}}else{if(A9==AV){this.beforeHideEvent.fire();f.setStyle(this.element,AW,AY);this.hideEvent.fire();}}}},configPosition:function(A7,A6,BA){var A9=this.element,A8=A6[0]==C?C:z,BB=this.cfg,A5;f.setStyle(A9,Ac,A8);if(A8==C){f.setStyle(A9,AW,AV);BB.setProperty(Au,true);}else{f.setStyle(A9,J,AL);}if(A8==z){A5=BB.getProperty(AS);if(!A5||A5===0){BB.setProperty(AS,1);}}},configIframe:function(A6,A5,A7){if(this.cfg.getProperty(Ac)==AD){r.superclass.configIframe.call(this,A6,A5,A7);}},configHideDelay:function(A6,A5,A7){var A8=A5[0];this._useHideDelay=(A8>0);},configContainer:function(A6,A5,A8){var A7=A5[0];if(AM.isString(A7)){this.cfg.setProperty(g,f.get(A7),true);}},_clearSetWidthFlag:function(){this._widthSetForScroll=false;this.cfg.unsubscribeFromConfigEvent(U,this._clearSetWidthFlag);},_subscribeScrollHandlers:function(A6,A5){var A8=this._onScrollTargetMouseOver;var A7=this._onScrollTargetMouseOut;AA.on(A6,Ai,A8,this,true);AA.on(A6,H,A7,this,true);AA.on(A5,Ai,A8,this,true);AA.on(A5,H,A7,this,true);},_unsubscribeScrollHandlers:function(A6,A5){var A8=this._onScrollTargetMouseOver;var A7=this._onScrollTargetMouseOut;AA.removeListener(A6,Ai,A8);AA.removeListener(A6,H,A7);AA.removeListener(A5,Ai,A8);AA.removeListener(A5,H,A7);},_setScrollHeight:function(BF){var BC=BF,BB=false,BG=false,A8,A9,BE,A6,A5,BD,BA,A7;if(this.getItems().length>0){A8=this.element;A9=this.body;BE=this.header;A6=this.footer;A5=this.cfg.getProperty(Aj);if(BC>0&&BC<A5){BC=A5;}f.setStyle(A9,Ao,u);f.removeClass(A9,l);A9.scrollTop=0;BG=((As.gecko&&As.gecko<1.9)||As.ie);if(BC>0&&BG&&!this.cfg.getProperty(U)){BA=A8.offsetWidth;A8.style.width=BA+AQ;A7=(BA-(A8.offsetWidth-BA))+AQ;this.cfg.unsubscribeFromConfigEvent(U,this._clearSetWidthFlag);this.cfg.setProperty(U,A7);this._widthSetForScroll=true;this.cfg.subscribeToConfigEvent(U,this._clearSetWidthFlag);}if(BC>0&&(!BE&&!A6)){this.setHeader(AK);this.setFooter(AK);BE=this.header;A6=this.footer;f.addClass(BE,T);f.addClass(A6,x);A8.insertBefore(BE,A9);A8.appendChild(A6);}BD=BC;if(BE&&A6){BD=(BD-(BE.offsetHeight+A6.offsetHeight));}if((BD>0)&&(A9.offsetHeight>BC)){f.addClass(A9,l);f.setStyle(A9,Ao,(BD+AQ));if(!this._hasScrollEventHandlers){this._subscribeScrollHandlers(BE,A6);this._hasScrollEventHandlers=true;}this._disableScrollHeader();this._enableScrollFooter();BB=true;}else{if(BE&&A6){if(this._widthSetForScroll){this._widthSetForScroll=false;this.cfg.unsubscribeFromConfigEvent(U,this._clearSetWidthFlag);this.cfg.setProperty(U,u);}this._enableScrollHeader();this._enableScrollFooter();if(this._hasScrollEventHandlers){this._unsubscribeScrollHandlers(BE,A6);this._hasScrollEventHandlers=false;}A8.removeChild(BE);A8.removeChild(A6);this.header=null;this.footer=null;BB=true;}}if(BB){this.cfg.refireEvent(AF);this.cfg.refireEvent(Ag);}}},_setMaxHeight:function(A6,A5,A7){this._setScrollHeight(A7);this.renderEvent.unsubscribe(this._setMaxHeight);},configMaxHeight:function(A6,A5,A7){var A8=A5[0];if(this.lazyLoad&&!this.body&&A8>0){this.renderEvent.subscribe(this._setMaxHeight,A8,this);}else{this._setScrollHeight(A8);}},configClassName:function(A7,A6,A8){var A5=A6[0];if(this._sClassName){f.removeClass(this.element,this._sClassName);}f.addClass(this.element,A5);this._sClassName=A5;},_onItemAdded:function(A6,A5){var A7=A5[0];if(A7){A7.cfg.setProperty(Ah,true);}},configDisabled:function(A7,A6,BA){var A9=A6[0],A5=this.getItems(),BB,A8;if(AM.isArray(A5)){BB=A5.length;if(BB>0){A8=BB-1;do{A5[A8].cfg.setProperty(Ah,A9);}while(A8--);}if(A9){this.clearActiveItem(true);f.addClass(this.element,Ah);this.itemAddedEvent.subscribe(this._onItemAdded);}else{f.removeClass(this.element,Ah);this.itemAddedEvent.unsubscribe(this._onItemAdded);
+}}},_sizeShadow:function(){var A6=this.element,A5=this._shadow;if(A5&&A6){if(A5.style.width&&A5.style.height){A5.style.width=u;A5.style.height=u;}A5.style.width=(A6.offsetWidth+6)+AQ;A5.style.height=(A6.offsetHeight+1)+AQ;}},_replaceShadow:function(){this.element.appendChild(this._shadow);},_addShadowVisibleClass:function(){f.addClass(this._shadow,AG);},_removeShadowVisibleClass:function(){f.removeClass(this._shadow,AG);},_removeShadow:function(){var A5=(this._shadow&&this._shadow.parentNode);if(A5){A5.removeChild(this._shadow);}this.beforeShowEvent.unsubscribe(this._addShadowVisibleClass);this.beforeHideEvent.unsubscribe(this._removeShadowVisibleClass);this.cfg.unsubscribeFromConfigEvent(U,this._sizeShadow);this.cfg.unsubscribeFromConfigEvent(Ao,this._sizeShadow);this.cfg.unsubscribeFromConfigEvent(AU,this._sizeShadow);this.cfg.unsubscribeFromConfigEvent(AU,this._replaceShadow);this.changeContentEvent.unsubscribe(this._sizeShadow);Aw.textResizeEvent.unsubscribe(this._sizeShadow);},_createShadow:function(){var A6=this._shadow,A5;if(!A6){A5=this.element;if(!Ap){Ap=document.createElement(K);Ap.className=m;}A6=Ap.cloneNode(false);A5.appendChild(A6);this._shadow=A6;this.beforeShowEvent.subscribe(this._addShadowVisibleClass);this.beforeHideEvent.subscribe(this._removeShadowVisibleClass);if(As.ie){AM.later(0,this,function(){this._sizeShadow();this.syncIframe();});this.cfg.subscribeToConfigEvent(U,this._sizeShadow);this.cfg.subscribeToConfigEvent(Ao,this._sizeShadow);this.cfg.subscribeToConfigEvent(AU,this._sizeShadow);this.changeContentEvent.subscribe(this._sizeShadow);Aw.textResizeEvent.subscribe(this._sizeShadow,this,true);this.destroyEvent.subscribe(function(){Aw.textResizeEvent.unsubscribe(this._sizeShadow,this);});}this.cfg.subscribeToConfigEvent(AU,this._replaceShadow);}},_shadowBeforeShow:function(){if(this._shadow){this._replaceShadow();if(As.ie){this._sizeShadow();}}else{this._createShadow();}this.beforeShowEvent.unsubscribe(this._shadowBeforeShow);},configShadow:function(A6,A5,A7){var A8=A5[0];if(A8&&this.cfg.getProperty(Ac)==AD){if(this.cfg.getProperty(Au)){if(this._shadow){this._replaceShadow();if(As.ie){this._sizeShadow();}}else{this._createShadow();}}else{this.beforeShowEvent.subscribe(this._shadowBeforeShow);}}else{if(!A8){this.beforeShowEvent.unsubscribe(this._shadowBeforeShow);this._removeShadow();}}},initEvents:function(){r.superclass.initEvents.call(this);var A6=Ab.length-1,A7,A5;do{A7=Ab[A6];A5=this.createEvent(A7[1]);A5.signature=F.LIST;this[A7[0]]=A5;}while(A6--);},positionOffScreen:function(){var A6=this.iframe,A7=this.element,A5=this.OFF_SCREEN_POSITION;A7.style.top=u;A7.style.left=u;if(A6){A6.style.top=A5;A6.style.left=A5;}},getRoot:function(){var A7=this.parent,A6,A5;if(A7){A6=A7.parent;A5=A6?A6.getRoot():this;}else{A5=this;}return A5;},toString:function(){var A6=Aq,A5=this.id;if(A5){A6+=(A1+A5);}return A6;},setItemGroupTitle:function(BA,A9){var A8,A7,A6,A5;if(AM.isString(BA)&&BA.length>0){A8=AM.isNumber(A9)?A9:0;A7=this._aGroupTitleElements[A8];if(A7){A7.innerHTML=BA;}else{A7=document.createElement(this.GROUP_TITLE_TAG_NAME);A7.innerHTML=BA;this._aGroupTitleElements[A8]=A7;}A6=this._aGroupTitleElements.length-1;do{if(this._aGroupTitleElements[A6]){f.removeClass(this._aGroupTitleElements[A6],AJ);A5=A6;}}while(A6--);if(A5!==null){f.addClass(this._aGroupTitleElements[A5],AJ);}this.changeContentEvent.fire();}},addItem:function(A5,A6){return this._addItemToGroup(A6,A5);},addItems:function(A9,A8){var BB,A5,BA,A6,A7;if(AM.isArray(A9)){BB=A9.length;A5=[];for(A6=0;A6<BB;A6++){BA=A9[A6];if(BA){if(AM.isArray(BA)){A5[A5.length]=this.addItems(BA,A6);}else{A5[A5.length]=this._addItemToGroup(A8,BA);}}}if(A5.length){A7=A5;}}return A7;},insertItem:function(A5,A6,A7){return this._addItemToGroup(A7,A5,A6);},removeItem:function(A5,A7){var A8,A6;if(!AM.isUndefined(A5)){if(A5 instanceof YAHOO.widget.MenuItem){A8=this._removeItemFromGroupByValue(A7,A5);}else{if(AM.isNumber(A5)){A8=this._removeItemFromGroupByIndex(A7,A5);}}if(A8){A8.destroy();A6=A8;}}return A6;},getItems:function(){var A8=this._aItemGroups,A6,A7,A5=[];if(AM.isArray(A8)){A6=A8.length;A7=((A6==1)?A8[0]:(Array.prototype.concat.apply(A5,A8)));}return A7;},getItemGroups:function(){return this._aItemGroups;},getItem:function(A6,A7){var A8,A5;if(AM.isNumber(A6)){A8=this._getItemGroup(A7);if(A8){A5=A8[A6];}}return A5;},getSubmenus:function(){var A6=this.getItems(),BA=A6.length,A5,A7,A9,A8;if(BA>0){A5=[];for(A8=0;A8<BA;A8++){A9=A6[A8];if(A9){A7=A9.cfg.getProperty(O);if(A7){A5[A5.length]=A7;}}}}return A5;},clearContent:function(){var A9=this.getItems(),A6=A9.length,A7=this.element,A8=this.body,BD=this.header,A5=this.footer,BC,BB,BA;if(A6>0){BA=A6-1;do{BC=A9[BA];if(BC){BB=BC.cfg.getProperty(O);if(BB){this.cfg.configChangedEvent.unsubscribe(this._onParentMenuConfigChange,BB);this.renderEvent.unsubscribe(this._onParentMenuRender,BB);}this.removeItem(BC,BC.groupIndex);}}while(BA--);}if(BD){AA.purgeElement(BD);A7.removeChild(BD);}if(A5){AA.purgeElement(A5);A7.removeChild(A5);}if(A8){AA.purgeElement(A8);A8.innerHTML=u;}this.activeItem=null;this._aItemGroups=[];this._aListElements=[];this._aGroupTitleElements=[];this.cfg.setProperty(U,null);},destroy:function(A5){this.clearContent();this._aItemGroups=null;this._aListElements=null;this._aGroupTitleElements=null;r.superclass.destroy.call(this,A5);},setInitialFocus:function(){var A5=this._getFirstEnabledItem();if(A5){A5.focus();}},setInitialSelection:function(){var A5=this._getFirstEnabledItem();if(A5){A5.cfg.setProperty(y,true);}},clearActiveItem:function(A7){if(this.cfg.getProperty(Av)>0){this._cancelShowDelay();}var A5=this.activeItem,A8,A6;if(A5){A8=A5.cfg;if(A7){A5.blur();this.getRoot()._hasFocus=true;}A8.setProperty(y,false);A6=A8.getProperty(O);if(A6){A6.hide();}this.activeItem=null;}},focus:function(){if(!this.hasFocus()){this.setInitialFocus();}},blur:function(){var A5;if(this.hasFocus()){A5=A3.getFocusedMenuItem();if(A5){A5.blur();}}},hasFocus:function(){return(A3.getFocusedMenu()==this.getRoot());
+},_doItemSubmenuSubscribe:function(A6,A5,A8){var A9=A5[0],A7=A9.cfg.getProperty(O);if(A7){A7.subscribe.apply(A7,A8);}},_doSubmenuSubscribe:function(A6,A5,A8){var A7=this.cfg.getProperty(O);if(A7){A7.subscribe.apply(A7,A8);}},subscribe:function(){r.superclass.subscribe.apply(this,arguments);r.superclass.subscribe.call(this,AR,this._doItemSubmenuSubscribe,arguments);var A5=this.getItems(),A9,A8,A6,A7;if(A5){A9=A5.length;if(A9>0){A7=A9-1;do{A8=A5[A7];A6=A8.cfg.getProperty(O);if(A6){A6.subscribe.apply(A6,arguments);}else{A8.cfg.subscribeToConfigEvent(O,this._doSubmenuSubscribe,arguments);}}while(A7--);}}},unsubscribe:function(){r.superclass.unsubscribe.apply(this,arguments);r.superclass.unsubscribe.call(this,AR,this._doItemSubmenuSubscribe,arguments);var A5=this.getItems(),A9,A8,A6,A7;if(A5){A9=A5.length;if(A9>0){A7=A9-1;do{A8=A5[A7];A6=A8.cfg.getProperty(O);if(A6){A6.unsubscribe.apply(A6,arguments);}else{A8.cfg.unsubscribeFromConfigEvent(O,this._doSubmenuSubscribe,arguments);}}while(A7--);}}},initDefaultConfig:function(){r.superclass.initDefaultConfig.call(this);var A5=this.cfg;A5.addProperty(AZ.key,{handler:this.configVisible,value:AZ.value,validator:AZ.validator});A5.addProperty(AP.key,{handler:this.configConstrainToViewport,value:AP.value,validator:AP.validator,supercedes:AP.supercedes});A5.addProperty(AI.key,{value:AI.value,validator:AI.validator,supercedes:AI.supercedes});A5.addProperty(S.key,{handler:this.configPosition,value:S.value,validator:S.validator,supercedes:S.supercedes});A5.addProperty(A.key,{value:A.value,suppressEvent:A.suppressEvent});A5.addProperty(t.key,{value:t.value,validator:t.validator,suppressEvent:t.suppressEvent});A5.addProperty(Y.key,{value:Y.value,validator:Y.validator,suppressEvent:Y.suppressEvent});A5.addProperty(q.key,{handler:this.configHideDelay,value:q.value,validator:q.validator,suppressEvent:q.suppressEvent});A5.addProperty(v.key,{value:v.value,validator:v.validator,suppressEvent:v.suppressEvent});A5.addProperty(o.key,{value:o.value,validator:o.validator,suppressEvent:o.suppressEvent});A5.addProperty(AN.key,{handler:this.configContainer,value:document.body,suppressEvent:AN.suppressEvent});A5.addProperty(Af.key,{value:Af.value,validator:Af.validator,supercedes:Af.supercedes,suppressEvent:Af.suppressEvent});A5.addProperty(N.key,{value:N.value,validator:N.validator,supercedes:N.supercedes,suppressEvent:N.suppressEvent});A5.addProperty(X.key,{handler:this.configMaxHeight,value:X.value,validator:X.validator,suppressEvent:X.suppressEvent,supercedes:X.supercedes});A5.addProperty(W.key,{handler:this.configClassName,value:W.value,validator:W.validator,supercedes:W.supercedes});A5.addProperty(a.key,{handler:this.configDisabled,value:a.value,validator:a.validator,suppressEvent:a.suppressEvent});A5.addProperty(I.key,{handler:this.configShadow,value:I.value,validator:I.validator});A5.addProperty(Al.key,{value:Al.value,validator:Al.validator});}});})();(function(){YAHOO.widget.MenuItem=function(AS,AR){if(AS){if(AR){this.parent=AR.parent;this.value=AR.value;this.id=AR.id;}this.init(AS,AR);}};var x=YAHOO.util.Dom,j=YAHOO.widget.Module,AB=YAHOO.widget.Menu,c=YAHOO.widget.MenuItem,AK=YAHOO.util.CustomEvent,k=YAHOO.env.ua,AQ=YAHOO.lang,AL="text",O="#",Q="-",L="helptext",n="url",AH="target",A="emphasis",N="strongemphasis",b="checked",w="submenu",H="disabled",B="selected",P="hassubmenu",U="checked-disabled",AI="hassubmenu-disabled",AD="hassubmenu-selected",T="checked-selected",q="onclick",J="classname",AJ="",i="OPTION",v="OPTGROUP",K="LI",AE="href",r="SELECT",X="DIV",AN='<em class="helptext">',a="<em>",I="</em>",W="<strong>",y="</strong>",Y="preventcontextoverlap",h="obj",AG="scope",t="none",V="visible",E=" ",m="MenuItem",AA="click",D="show",M="hide",S="li",AF='<a href="#"></a>',p=[["mouseOverEvent","mouseover"],["mouseOutEvent","mouseout"],["mouseDownEvent","mousedown"],["mouseUpEvent","mouseup"],["clickEvent",AA],["keyPressEvent","keypress"],["keyDownEvent","keydown"],["keyUpEvent","keyup"],["focusEvent","focus"],["blurEvent","blur"],["destroyEvent","destroy"]],o={key:AL,value:AJ,validator:AQ.isString,suppressEvent:true},s={key:L,supercedes:[AL],suppressEvent:true},G={key:n,value:O,suppressEvent:true},AO={key:AH,suppressEvent:true},AP={key:A,value:false,validator:AQ.isBoolean,suppressEvent:true,supercedes:[AL]},d={key:N,value:false,validator:AQ.isBoolean,suppressEvent:true,supercedes:[AL]},l={key:b,value:false,validator:AQ.isBoolean,suppressEvent:true,supercedes:[H,B]},F={key:w,suppressEvent:true,supercedes:[H,B]},AM={key:H,value:false,validator:AQ.isBoolean,suppressEvent:true,supercedes:[AL,B]},f={key:B,value:false,validator:AQ.isBoolean,suppressEvent:true},u={key:q,suppressEvent:true},AC={key:J,value:null,validator:AQ.isString,suppressEvent:true},z={key:"keylistener",value:null,suppressEvent:true},C=null,e={};var Z=function(AU,AT){var AR=e[AU];if(!AR){e[AU]={};AR=e[AU];}var AS=AR[AT];if(!AS){AS=AU+Q+AT;AR[AT]=AS;}return AS;};var g=function(AR){x.addClass(this.element,Z(this.CSS_CLASS_NAME,AR));x.addClass(this._oAnchor,Z(this.CSS_LABEL_CLASS_NAME,AR));};var R=function(AR){x.removeClass(this.element,Z(this.CSS_CLASS_NAME,AR));x.removeClass(this._oAnchor,Z(this.CSS_LABEL_CLASS_NAME,AR));};c.prototype={CSS_CLASS_NAME:"yuimenuitem",CSS_LABEL_CLASS_NAME:"yuimenuitemlabel",SUBMENU_TYPE:null,_oAnchor:null,_oHelpTextEM:null,_oSubmenu:null,_oOnclickAttributeValue:null,_sClassName:null,constructor:c,index:null,groupIndex:null,parent:null,element:null,srcElement:null,value:null,browser:j.prototype.browser,id:null,init:function(AR,Ab){if(!this.SUBMENU_TYPE){this.SUBMENU_TYPE=AB;}this.cfg=new YAHOO.util.Config(this);this.initDefaultConfig();var AX=this.cfg,AY=O,AT,Aa,AZ,AS,AV,AU,AW;if(AQ.isString(AR)){this._createRootNodeStructure();AX.queueProperty(AL,AR);}else{if(AR&&AR.tagName){switch(AR.tagName.toUpperCase()){case i:this._createRootNodeStructure();AX.queueProperty(AL,AR.text);AX.queueProperty(H,AR.disabled);this.value=AR.value;this.srcElement=AR;break;case v:this._createRootNodeStructure();
+AX.queueProperty(AL,AR.label);AX.queueProperty(H,AR.disabled);this.srcElement=AR;this._initSubTree();break;case K:AZ=x.getFirstChild(AR);if(AZ){AY=AZ.getAttribute(AE,2);AS=AZ.getAttribute(AH);AV=AZ.innerHTML;}this.srcElement=AR;this.element=AR;this._oAnchor=AZ;AX.setProperty(AL,AV,true);AX.setProperty(n,AY,true);AX.setProperty(AH,AS,true);this._initSubTree();break;}}}if(this.element){AU=(this.srcElement||this.element).id;if(!AU){AU=this.id||x.generateId();this.element.id=AU;}this.id=AU;x.addClass(this.element,this.CSS_CLASS_NAME);x.addClass(this._oAnchor,this.CSS_LABEL_CLASS_NAME);AW=p.length-1;do{Aa=p[AW];AT=this.createEvent(Aa[1]);AT.signature=AK.LIST;this[Aa[0]]=AT;}while(AW--);if(Ab){AX.applyConfig(Ab);}AX.fireQueue();}},_createRootNodeStructure:function(){var AR,AS;if(!C){C=document.createElement(S);C.innerHTML=AF;}AR=C.cloneNode(true);AR.className=this.CSS_CLASS_NAME;AS=AR.firstChild;AS.className=this.CSS_LABEL_CLASS_NAME;this.element=AR;this._oAnchor=AS;},_initSubTree:function(){var AX=this.srcElement,AT=this.cfg,AV,AU,AS,AR,AW;if(AX.childNodes.length>0){if(this.parent.lazyLoad&&this.parent.srcElement&&this.parent.srcElement.tagName.toUpperCase()==r){AT.setProperty(w,{id:x.generateId(),itemdata:AX.childNodes});}else{AV=AX.firstChild;AU=[];do{if(AV&&AV.tagName){switch(AV.tagName.toUpperCase()){case X:AT.setProperty(w,AV);break;case i:AU[AU.length]=AV;break;}}}while((AV=AV.nextSibling));AS=AU.length;if(AS>0){AR=new this.SUBMENU_TYPE(x.generateId());AT.setProperty(w,AR);for(AW=0;AW<AS;AW++){AR.addItem((new AR.ITEM_TYPE(AU[AW])));}}}}},configText:function(Aa,AT,AV){var AS=AT[0],AU=this.cfg,AY=this._oAnchor,AR=AU.getProperty(L),AZ=AJ,AW=AJ,AX=AJ;if(AS){if(AR){AZ=AN+AR+I;}if(AU.getProperty(A)){AW=a;AX=I;}if(AU.getProperty(N)){AW=W;AX=y;}AY.innerHTML=(AW+AS+AX+AZ);}},configHelpText:function(AT,AS,AR){this.cfg.refireEvent(AL);},configURL:function(AT,AS,AR){var AV=AS[0];if(!AV){AV=O;}var AU=this._oAnchor;if(k.opera){AU.removeAttribute(AE);}AU.setAttribute(AE,AV);},configTarget:function(AU,AT,AS){var AR=AT[0],AV=this._oAnchor;if(AR&&AR.length>0){AV.setAttribute(AH,AR);}else{AV.removeAttribute(AH);}},configEmphasis:function(AT,AS,AR){var AV=AS[0],AU=this.cfg;if(AV&&AU.getProperty(N)){AU.setProperty(N,false);}AU.refireEvent(AL);},configStrongEmphasis:function(AU,AT,AS){var AR=AT[0],AV=this.cfg;if(AR&&AV.getProperty(A)){AV.setProperty(A,false);}AV.refireEvent(AL);},configChecked:function(AT,AS,AR){var AV=AS[0],AU=this.cfg;if(AV){g.call(this,b);}else{R.call(this,b);}AU.refireEvent(AL);if(AU.getProperty(H)){AU.refireEvent(H);}if(AU.getProperty(B)){AU.refireEvent(B);}},configDisabled:function(AT,AS,AR){var AV=AS[0],AW=this.cfg,AU=AW.getProperty(w),AX=AW.getProperty(b);if(AV){if(AW.getProperty(B)){AW.setProperty(B,false);}g.call(this,H);if(AU){g.call(this,AI);}if(AX){g.call(this,U);}}else{R.call(this,H);if(AU){R.call(this,AI);}if(AX){R.call(this,U);}}},configSelected:function(AT,AS,AR){var AX=this.cfg,AW=this._oAnchor,AV=AS[0],AY=AX.getProperty(b),AU=AX.getProperty(w);if(k.opera){AW.blur();}if(AV&&!AX.getProperty(H)){g.call(this,B);if(AU){g.call(this,AD);}if(AY){g.call(this,T);}}else{R.call(this,B);if(AU){R.call(this,AD);}if(AY){R.call(this,T);}}if(this.hasFocus()&&k.opera){AW.focus();}},_onSubmenuBeforeHide:function(AU,AT){var AV=this.parent,AR;function AS(){AV._oAnchor.blur();AR.beforeHideEvent.unsubscribe(AS);}if(AV.hasFocus()){AR=AV.parent;AR.beforeHideEvent.subscribe(AS);}},configSubmenu:function(AY,AT,AW){var AV=AT[0],AU=this.cfg,AS=this.parent&&this.parent.lazyLoad,AX,AZ,AR;if(AV){if(AV instanceof AB){AX=AV;AX.parent=this;AX.lazyLoad=AS;}else{if(AQ.isObject(AV)&&AV.id&&!AV.nodeType){AZ=AV.id;AR=AV;AR.lazyload=AS;AR.parent=this;AX=new this.SUBMENU_TYPE(AZ,AR);AU.setProperty(w,AX,true);}else{AX=new this.SUBMENU_TYPE(AV,{lazyload:AS,parent:this});AU.setProperty(w,AX,true);}}if(AX){AX.cfg.setProperty(Y,true);g.call(this,P);if(AU.getProperty(n)===O){AU.setProperty(n,(O+AX.id));}this._oSubmenu=AX;if(k.opera){AX.beforeHideEvent.subscribe(this._onSubmenuBeforeHide);}}}else{R.call(this,P);if(this._oSubmenu){this._oSubmenu.destroy();}}if(AU.getProperty(H)){AU.refireEvent(H);}if(AU.getProperty(B)){AU.refireEvent(B);}},configOnClick:function(AT,AS,AR){var AU=AS[0];if(this._oOnclickAttributeValue&&(this._oOnclickAttributeValue!=AU)){this.clickEvent.unsubscribe(this._oOnclickAttributeValue.fn,this._oOnclickAttributeValue.obj);this._oOnclickAttributeValue=null;}if(!this._oOnclickAttributeValue&&AQ.isObject(AU)&&AQ.isFunction(AU.fn)){this.clickEvent.subscribe(AU.fn,((h in AU)?AU.obj:this),((AG in AU)?AU.scope:null));this._oOnclickAttributeValue=AU;}},configClassName:function(AU,AT,AS){var AR=AT[0];if(this._sClassName){x.removeClass(this.element,this._sClassName);}x.addClass(this.element,AR);this._sClassName=AR;},_dispatchClickEvent:function(){var AS=this,AR;if(!AS.cfg.getProperty(H)){AR=x.getFirstChild(AS.element);this._dispatchDOMClick(AR);}},_dispatchDOMClick:function(AS){var AR;if(k.ie&&k.ie<9){AS.fireEvent(q);}else{if((k.gecko&&k.gecko>=1.9)||k.opera||k.webkit){AR=document.createEvent("HTMLEvents");AR.initEvent(AA,true,true);}else{AR=document.createEvent("MouseEvents");AR.initMouseEvent(AA,true,true,window,0,0,0,0,0,false,false,false,false,0,null);}AS.dispatchEvent(AR);}},_createKeyListener:function(AU,AT,AW){var AV=this,AS=AV.parent;var AR=new YAHOO.util.KeyListener(AS.element.ownerDocument,AW,{fn:AV._dispatchClickEvent,scope:AV,correctScope:true});if(AS.cfg.getProperty(V)){AR.enable();}AS.subscribe(D,AR.enable,null,AR);AS.subscribe(M,AR.disable,null,AR);AV._keyListener=AR;AS.unsubscribe(D,AV._createKeyListener,AW);},configKeyListener:function(AT,AS){var AV=AS[0],AU=this,AR=AU.parent;if(AU._keyData){AR.unsubscribe(D,AU._createKeyListener,AU._keyData);AU._keyData=null;}if(AU._keyListener){AR.unsubscribe(D,AU._keyListener.enable);AR.unsubscribe(M,AU._keyListener.disable);AU._keyListener.disable();AU._keyListener=null;}if(AV){AU._keyData=AV;AR.subscribe(D,AU._createKeyListener,AV,AU);}},initDefaultConfig:function(){var AR=this.cfg;
+AR.addProperty(o.key,{handler:this.configText,value:o.value,validator:o.validator,suppressEvent:o.suppressEvent});AR.addProperty(s.key,{handler:this.configHelpText,supercedes:s.supercedes,suppressEvent:s.suppressEvent});AR.addProperty(G.key,{handler:this.configURL,value:G.value,suppressEvent:G.suppressEvent});AR.addProperty(AO.key,{handler:this.configTarget,suppressEvent:AO.suppressEvent});AR.addProperty(AP.key,{handler:this.configEmphasis,value:AP.value,validator:AP.validator,suppressEvent:AP.suppressEvent,supercedes:AP.supercedes});AR.addProperty(d.key,{handler:this.configStrongEmphasis,value:d.value,validator:d.validator,suppressEvent:d.suppressEvent,supercedes:d.supercedes});AR.addProperty(l.key,{handler:this.configChecked,value:l.value,validator:l.validator,suppressEvent:l.suppressEvent,supercedes:l.supercedes});AR.addProperty(AM.key,{handler:this.configDisabled,value:AM.value,validator:AM.validator,suppressEvent:AM.suppressEvent});AR.addProperty(f.key,{handler:this.configSelected,value:f.value,validator:f.validator,suppressEvent:f.suppressEvent});AR.addProperty(F.key,{handler:this.configSubmenu,supercedes:F.supercedes,suppressEvent:F.suppressEvent});AR.addProperty(u.key,{handler:this.configOnClick,suppressEvent:u.suppressEvent});AR.addProperty(AC.key,{handler:this.configClassName,value:AC.value,validator:AC.validator,suppressEvent:AC.suppressEvent});AR.addProperty(z.key,{handler:this.configKeyListener,value:z.value,suppressEvent:z.suppressEvent});},getNextSibling:function(){var AR=function(AX){return(AX.nodeName.toLowerCase()==="ul");},AV=this.element,AU=x.getNextSibling(AV),AT,AS,AW;if(!AU){AT=AV.parentNode;AS=x.getNextSiblingBy(AT,AR);if(AS){AW=AS;}else{AW=x.getFirstChildBy(AT.parentNode,AR);}AU=x.getFirstChild(AW);}return YAHOO.widget.MenuManager.getMenuItem(AU.id);},getNextEnabledSibling:function(){var AR=this.getNextSibling();return(AR.cfg.getProperty(H)||AR.element.style.display==t)?AR.getNextEnabledSibling():AR;},getPreviousSibling:function(){var AR=function(AX){return(AX.nodeName.toLowerCase()==="ul");},AV=this.element,AU=x.getPreviousSibling(AV),AT,AS,AW;if(!AU){AT=AV.parentNode;AS=x.getPreviousSiblingBy(AT,AR);if(AS){AW=AS;}else{AW=x.getLastChildBy(AT.parentNode,AR);}AU=x.getLastChild(AW);}return YAHOO.widget.MenuManager.getMenuItem(AU.id);},getPreviousEnabledSibling:function(){var AR=this.getPreviousSibling();return(AR.cfg.getProperty(H)||AR.element.style.display==t)?AR.getPreviousEnabledSibling():AR;},focus:function(){var AU=this.parent,AT=this._oAnchor,AR=AU.activeItem;function AS(){try{if(!(k.ie&&!document.hasFocus())){if(AR){AR.blurEvent.fire();}AT.focus();this.focusEvent.fire();}}catch(AV){}}if(!this.cfg.getProperty(H)&&AU&&AU.cfg.getProperty(V)&&this.element.style.display!=t){AQ.later(0,this,AS);}},blur:function(){var AR=this.parent;if(!this.cfg.getProperty(H)&&AR&&AR.cfg.getProperty(V)){AQ.later(0,this,function(){try{this._oAnchor.blur();this.blurEvent.fire();}catch(AS){}},0);}},hasFocus:function(){return(YAHOO.widget.MenuManager.getFocusedMenuItem()==this);},destroy:function(){var AT=this.element,AS,AR,AV,AU;if(AT){AS=this.cfg.getProperty(w);if(AS){AS.destroy();}AR=AT.parentNode;if(AR){AR.removeChild(AT);this.destroyEvent.fire();}AU=p.length-1;do{AV=p[AU];this[AV[0]].unsubscribeAll();}while(AU--);this.cfg.configChangedEvent.unsubscribeAll();}},toString:function(){var AS=m,AR=this.id;if(AR){AS+=(E+AR);}return AS;}};AQ.augmentProto(c,YAHOO.util.EventProvider);})();(function(){var B="xy",C="mousedown",F="ContextMenu",J=" ";YAHOO.widget.ContextMenu=function(L,K){YAHOO.widget.ContextMenu.superclass.constructor.call(this,L,K);};var I=YAHOO.util.Event,E=YAHOO.env.ua,G=YAHOO.widget.ContextMenu,A={"TRIGGER_CONTEXT_MENU":"triggerContextMenu","CONTEXT_MENU":(E.opera?C:"contextmenu"),"CLICK":"click"},H={key:"trigger",suppressEvent:true};function D(L,K,M){this.cfg.setProperty(B,M);this.beforeShowEvent.unsubscribe(D,M);}YAHOO.lang.extend(G,YAHOO.widget.Menu,{_oTrigger:null,_bCancelled:false,contextEventTarget:null,triggerContextMenuEvent:null,init:function(L,K){G.superclass.init.call(this,L);this.beforeInitEvent.fire(G);if(K){this.cfg.applyConfig(K,true);}this.initEvent.fire(G);},initEvents:function(){G.superclass.initEvents.call(this);this.triggerContextMenuEvent=this.createEvent(A.TRIGGER_CONTEXT_MENU);this.triggerContextMenuEvent.signature=YAHOO.util.CustomEvent.LIST;},cancel:function(){this._bCancelled=true;},_removeEventHandlers:function(){var K=this._oTrigger;if(K){I.removeListener(K,A.CONTEXT_MENU,this._onTriggerContextMenu);if(E.opera){I.removeListener(K,A.CLICK,this._onTriggerClick);}}},_onTriggerClick:function(L,K){if(L.ctrlKey){I.stopEvent(L);}},_onTriggerContextMenu:function(M,K){var L;if(!(M.type==C&&!M.ctrlKey)){this.contextEventTarget=I.getTarget(M);this.triggerContextMenuEvent.fire(M);if(!this._bCancelled){I.stopEvent(M);YAHOO.widget.MenuManager.hideVisible();L=I.getXY(M);if(!YAHOO.util.Dom.inDocument(this.element)){this.beforeShowEvent.subscribe(D,L);}else{this.cfg.setProperty(B,L);}this.show();}this._bCancelled=false;}},toString:function(){var L=F,K=this.id;if(K){L+=(J+K);}return L;},initDefaultConfig:function(){G.superclass.initDefaultConfig.call(this);this.cfg.addProperty(H.key,{handler:this.configTrigger,suppressEvent:H.suppressEvent});},destroy:function(K){this._removeEventHandlers();G.superclass.destroy.call(this,K);},configTrigger:function(L,K,N){var M=K[0];if(M){if(this._oTrigger){this._removeEventHandlers();}this._oTrigger=M;I.on(M,A.CONTEXT_MENU,this._onTriggerContextMenu,this,true);if(E.opera){I.on(M,A.CLICK,this._onTriggerClick,this,true);}}else{this._removeEventHandlers();}}});}());YAHOO.widget.ContextMenuItem=YAHOO.widget.MenuItem;(function(){var D=YAHOO.lang,N="static",M="dynamic,"+N,A="disabled",F="selected",B="autosubmenudisplay",G="submenu",C="visible",Q=" ",H="submenutoggleregion",P="MenuBar";YAHOO.widget.MenuBar=function(T,S){YAHOO.widget.MenuBar.superclass.constructor.call(this,T,S);};function O(T){var S=false;if(D.isString(T)){S=(M.indexOf((T.toLowerCase()))!=-1);
+}return S;}var R=YAHOO.util.Event,L=YAHOO.widget.MenuBar,K={key:"position",value:N,validator:O,supercedes:[C]},E={key:"submenualignment",value:["tl","bl"]},J={key:B,value:false,validator:D.isBoolean,suppressEvent:true},I={key:H,value:false,validator:D.isBoolean};D.extend(L,YAHOO.widget.Menu,{init:function(T,S){if(!this.ITEM_TYPE){this.ITEM_TYPE=YAHOO.widget.MenuBarItem;}L.superclass.init.call(this,T);this.beforeInitEvent.fire(L);if(S){this.cfg.applyConfig(S,true);}this.initEvent.fire(L);},CSS_CLASS_NAME:"yuimenubar",SUBMENU_TOGGLE_REGION_WIDTH:20,_onKeyDown:function(U,T,Y){var S=T[0],Z=T[1],W,X,V;if(Z&&!Z.cfg.getProperty(A)){X=Z.cfg;switch(S.keyCode){case 37:case 39:if(Z==this.activeItem&&!X.getProperty(F)){X.setProperty(F,true);}else{V=(S.keyCode==37)?Z.getPreviousEnabledSibling():Z.getNextEnabledSibling();if(V){this.clearActiveItem();V.cfg.setProperty(F,true);W=V.cfg.getProperty(G);if(W){W.show();W.setInitialFocus();}else{V.focus();}}}R.preventDefault(S);break;case 40:if(this.activeItem!=Z){this.clearActiveItem();X.setProperty(F,true);Z.focus();}W=X.getProperty(G);if(W){if(W.cfg.getProperty(C)){W.setInitialSelection();W.setInitialFocus();}else{W.show();W.setInitialFocus();}}R.preventDefault(S);break;}}if(S.keyCode==27&&this.activeItem){W=this.activeItem.cfg.getProperty(G);if(W&&W.cfg.getProperty(C)){W.hide();this.activeItem.focus();}else{this.activeItem.cfg.setProperty(F,false);this.activeItem.blur();}R.preventDefault(S);}},_onClick:function(e,Y,b){L.superclass._onClick.call(this,e,Y,b);var d=Y[1],T=true,S,f,U,W,Z,a,c,V;var X=function(){if(a.cfg.getProperty(C)){a.hide();}else{a.show();}};if(d&&!d.cfg.getProperty(A)){f=Y[0];U=R.getTarget(f);W=this.activeItem;Z=this.cfg;if(W&&W!=d){this.clearActiveItem();}d.cfg.setProperty(F,true);a=d.cfg.getProperty(G);if(a){S=d.element;c=YAHOO.util.Dom.getX(S);V=c+(S.offsetWidth-this.SUBMENU_TOGGLE_REGION_WIDTH);if(Z.getProperty(H)){if(R.getPageX(f)>V){X();R.preventDefault(f);T=false;}}else{X();}}}return T;},configSubmenuToggle:function(U,T){var S=T[0];if(S){this.cfg.setProperty(B,false);}},toString:function(){var T=P,S=this.id;if(S){T+=(Q+S);}return T;},initDefaultConfig:function(){L.superclass.initDefaultConfig.call(this);var S=this.cfg;S.addProperty(K.key,{handler:this.configPosition,value:K.value,validator:K.validator,supercedes:K.supercedes});S.addProperty(E.key,{value:E.value,suppressEvent:E.suppressEvent});S.addProperty(J.key,{value:J.value,validator:J.validator,suppressEvent:J.suppressEvent});S.addProperty(I.key,{value:I.value,validator:I.validator,handler:this.configSubmenuToggle});}});}());YAHOO.widget.MenuBarItem=function(B,A){YAHOO.widget.MenuBarItem.superclass.constructor.call(this,B,A);};YAHOO.lang.extend(YAHOO.widget.MenuBarItem,YAHOO.widget.MenuItem,{init:function(B,A){if(!this.SUBMENU_TYPE){this.SUBMENU_TYPE=YAHOO.widget.Menu;}YAHOO.widget.MenuBarItem.superclass.init.call(this,B);var C=this.cfg;if(A){C.applyConfig(A,true);}C.fireQueue();},CSS_CLASS_NAME:"yuimenubaritem",CSS_LABEL_CLASS_NAME:"yuimenubaritemlabel",toString:function(){var A="MenuBarItem";if(this.cfg&&this.cfg.getProperty("text")){A+=(": "+this.cfg.getProperty("text"));}return A;}});YAHOO.register("menu",YAHOO.widget.Menu,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/paginator/paginator-min.js b/Websites/bugs.webkit.org/js/yui/paginator/paginator-min.js
new file mode 100644
index 0000000..fdf70c6
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/paginator/paginator-min.js
@@ -0,0 +1,11 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var d=YAHOO.util.Dom,f=YAHOO.lang,b=f.isObject,e=f.isFunction,c=f.isArray,a=f.isString;function g(k){var n=g.VALUE_UNLIMITED,l,h,i,j,m;k=b(k)?k:{};this.initConfig();this.initEvents();this.set("rowsPerPage",k.rowsPerPage,true);if(g.isNumeric(k.totalRecords)){this.set("totalRecords",k.totalRecords,true);}this.initUIComponents();for(l in k){if(k.hasOwnProperty(l)){this.set(l,k[l],true);}}h=this.get("initialPage");i=this.get("totalRecords");j=this.get("rowsPerPage");if(h>1&&j!==n){m=(h-1)*j;if(i===n||m<i){this.set("recordOffset",m,true);}}}f.augmentObject(g,{id:0,ID_BASE:"yui-pg",VALUE_UNLIMITED:-1,TEMPLATE_DEFAULT:"{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink}",TEMPLATE_ROWS_PER_PAGE:"{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}",ui:{},isNumeric:function(h){return isFinite(+h);},toNumber:function(h){return isFinite(+h)?+h:null;}},true);g.prototype={_containers:[],_batch:false,_pageChanged:false,_state:null,initConfig:function(){var h=g.VALUE_UNLIMITED;this.setAttributeConfig("rowsPerPage",{value:0,validator:g.isNumeric,setter:g.toNumber});this.setAttributeConfig("containers",{value:null,validator:function(l){if(!c(l)){l=[l];}for(var k=0,j=l.length;k<j;++k){if(a(l[k])||(b(l[k])&&l[k].nodeType===1)){continue;}return false;}return true;},method:function(i){i=d.get(i);if(!c(i)){i=[i];}this._containers=i;}});this.setAttributeConfig("totalRecords",{value:0,validator:g.isNumeric,setter:g.toNumber});this.setAttributeConfig("recordOffset",{value:0,validator:function(j){var i=this.get("totalRecords");if(g.isNumeric(j)){j=+j;return i===h||i>j||(i===0&&j===0);}return false;},setter:g.toNumber});this.setAttributeConfig("initialPage",{value:1,validator:g.isNumeric,setter:g.toNumber});this.setAttributeConfig("template",{value:g.TEMPLATE_DEFAULT,validator:a});this.setAttributeConfig("containerClass",{value:"yui-pg-container",validator:a});this.setAttributeConfig("alwaysVisible",{value:true,validator:f.isBoolean});this.setAttributeConfig("updateOnChange",{value:false,validator:f.isBoolean});this.setAttributeConfig("id",{value:g.id++,readOnly:true});this.setAttributeConfig("rendered",{value:false,readOnly:true});},initUIComponents:function(){var j=g.ui,i,h;for(i in j){if(j.hasOwnProperty(i)){h=j[i];if(b(h)&&e(h.init)){h.init(this);}}}},initEvents:function(){this.createEvent("render");this.createEvent("rendered");this.createEvent("changeRequest");this.createEvent("pageChange");this.createEvent("beforeDestroy");this.createEvent("destroy");this._selfSubscribe();},_selfSubscribe:function(){this.subscribe("totalRecordsChange",this.updateVisibility,this,true);this.subscribe("alwaysVisibleChange",this.updateVisibility,this,true);this.subscribe("totalRecordsChange",this._handleStateChange,this,true);this.subscribe("recordOffsetChange",this._handleStateChange,this,true);this.subscribe("rowsPerPageChange",this._handleStateChange,this,true);this.subscribe("totalRecordsChange",this._syncRecordOffset,this,true);},_syncRecordOffset:function(k){var h=k.newValue,j,i;if(k.prevValue!==h){if(h!==g.VALUE_UNLIMITED){j=this.get("rowsPerPage");if(j&&this.get("recordOffset")>=h){i=this.getState({totalRecords:k.prevValue,recordOffset:this.get("recordOffset")});this.set("recordOffset",i.before.recordOffset);this._firePageChange(i);}}}},_handleStateChange:function(i){if(i.prevValue!==i.newValue){var j=this._state||{},h;j[i.type.replace(/Change$/,"")]=i.prevValue;h=this.getState(j);if(h.page!==h.before.page){if(this._batch){this._pageChanged=true;}else{this._firePageChange(h);}}}},_firePageChange:function(h){if(b(h)){var i=h.before;delete h.before;this.fireEvent("pageChange",{type:"pageChange",prevValue:h.page,newValue:i.page,prevState:h,newState:i});}},render:function(){if(this.get("rendered")){return this;}var l=this.get("template"),m=this.getState(),k=g.ID_BASE+this.get("id")+"-",j,h;for(j=0,h=this._containers.length;j<h;++j){this._renderTemplate(this._containers[j],l,k+j,true);}this.updateVisibility();if(this._containers.length){this.setAttributeConfig("rendered",{value:true});this.fireEvent("render",m);this.fireEvent("rendered",m);}return this;},_renderTemplate:function(j,n,m,l){var p=this.get("containerClass"),o,k,h;if(!j){return;}d.setStyle(j,"display","none");d.addClass(j,p);j.innerHTML=n.replace(/\{([a-z0-9_ \-]+)\}/gi,'<span class="yui-pg-ui yui-pg-ui-$1"></span>');o=d.getElementsByClassName("yui-pg-ui","span",j);for(k=0,h=o.length;k<h;++k){this.renderUIComponent(o[k],m);}if(!l){d.setStyle(j,"display","");}},renderUIComponent:function(h,m){var l=h.parentNode,k=/yui-pg-ui-(\w+)/.exec(h.className),j=k&&g.ui[k[1]],i;if(e(j)){i=new j(this);if(e(i.render)){l.replaceChild(i.render(m),h);}}return this;},destroy:function(){this.fireEvent("beforeDestroy");this.fireEvent("destroy");this.setAttributeConfig("rendered",{value:false});this.unsubscribeAll();},updateVisibility:function(m){var p=this.get("alwaysVisible"),n,j,q,o,k,l,h;if(!m||m.type==="alwaysVisibleChange"||!p){n=this.get("totalRecords");j=true;q=this.get("rowsPerPage");o=this.get("rowsPerPageOptions");if(c(o)){for(k=0,l=o.length;k<l;++k){h=o[k];if(f.isNumber(h||h.value)){q=Math.min(q,(h.value||h));}}}if(n!==g.VALUE_UNLIMITED&&n<=q){j=false;}j=j||p;for(k=0,l=this._containers.length;k<l;++k){d.setStyle(this._containers[k],"display",j?"":"none");}}},getContainerNodes:function(){return this._containers;},getTotalPages:function(){var h=this.get("totalRecords"),i=this.get("rowsPerPage");if(!i){return null;}if(h===g.VALUE_UNLIMITED){return g.VALUE_UNLIMITED;}return Math.ceil(h/i);},hasPage:function(i){if(!f.isNumber(i)||i<1){return false;}var h=this.getTotalPages();return(h===g.VALUE_UNLIMITED||h>=i);},getCurrentPage:function(){var h=this.get("rowsPerPage");if(!h||!this.get("totalRecords")){return 0;}return Math.floor(this.get("recordOffset")/h)+1;},hasNextPage:function(){var h=this.getCurrentPage(),i=this.getTotalPages();return h&&(i===g.VALUE_UNLIMITED||h<i);},getNextPage:function(){return this.hasNextPage()?this.getCurrentPage()+1:null;
+},hasPreviousPage:function(){return(this.getCurrentPage()>1);},getPreviousPage:function(){return(this.hasPreviousPage()?this.getCurrentPage()-1:1);},getPageRecords:function(k){if(!f.isNumber(k)){k=this.getCurrentPage();}var j=this.get("rowsPerPage"),i=this.get("totalRecords"),l,h;if(!k||!j){return null;}l=(k-1)*j;if(i!==g.VALUE_UNLIMITED){if(l>=i){return null;}h=Math.min(l+j,i)-1;}else{h=l+j-1;}return[l,h];},setPage:function(i,h){if(this.hasPage(i)&&i!==this.getCurrentPage()){if(this.get("updateOnChange")||h){this.set("recordOffset",(i-1)*this.get("rowsPerPage"));}else{this.fireEvent("changeRequest",this.getState({"page":i}));}}},getRowsPerPage:function(){return this.get("rowsPerPage");},setRowsPerPage:function(i,h){if(g.isNumeric(i)&&+i>0&&+i!==this.get("rowsPerPage")){if(this.get("updateOnChange")||h){this.set("rowsPerPage",i);}else{this.fireEvent("changeRequest",this.getState({"rowsPerPage":+i}));}}},getTotalRecords:function(){return this.get("totalRecords");},setTotalRecords:function(i,h){if(g.isNumeric(i)&&+i>=0&&+i!==this.get("totalRecords")){if(this.get("updateOnChange")||h){this.set("totalRecords",i);}else{this.fireEvent("changeRequest",this.getState({"totalRecords":+i}));}}},getStartIndex:function(){return this.get("recordOffset");},setStartIndex:function(i,h){if(g.isNumeric(i)&&+i>=0&&+i!==this.get("recordOffset")){if(this.get("updateOnChange")||h){this.set("recordOffset",i);}else{this.fireEvent("changeRequest",this.getState({"recordOffset":+i}));}}},getState:function(n){var p=g.VALUE_UNLIMITED,l=Math,m=l.max,o=l.ceil,j,h,k;function i(s,q,r){if(s<=0||q===0){return 0;}if(q===p||q>s){return s-(s%r);}return q-(q%r||r);}j={paginator:this,totalRecords:this.get("totalRecords"),rowsPerPage:this.get("rowsPerPage"),records:this.getPageRecords()};j.recordOffset=i(this.get("recordOffset"),j.totalRecords,j.rowsPerPage);j.page=o(j.recordOffset/j.rowsPerPage)+1;if(!n){return j;}h={paginator:this,before:j,rowsPerPage:n.rowsPerPage||j.rowsPerPage,totalRecords:(g.isNumeric(n.totalRecords)?m(n.totalRecords,p):+j.totalRecords)};if(h.totalRecords===0){h.recordOffset=h.page=0;}else{k=g.isNumeric(n.page)?(n.page-1)*h.rowsPerPage:g.isNumeric(n.recordOffset)?+n.recordOffset:j.recordOffset;h.recordOffset=i(k,h.totalRecords,h.rowsPerPage);h.page=o(h.recordOffset/h.rowsPerPage)+1;}h.records=[h.recordOffset,h.recordOffset+h.rowsPerPage-1];if(h.totalRecords!==p&&h.recordOffset<h.totalRecords&&h.records&&h.records[1]>h.totalRecords-1){h.records[1]=h.totalRecords-1;}return h;},setState:function(i){if(b(i)){this._state=this.getState({});i={page:i.page,rowsPerPage:i.rowsPerPage,totalRecords:i.totalRecords,recordOffset:i.recordOffset};if(i.page&&i.recordOffset===undefined){i.recordOffset=(i.page-1)*(i.rowsPerPage||this.get("rowsPerPage"));}this._batch=true;this._pageChanged=false;for(var h in i){if(i.hasOwnProperty(h)&&this._configs.hasOwnProperty(h)){this.set(h,i[h]);}}this._batch=false;if(this._pageChanged){this._pageChanged=false;this._firePageChange(this.getState(this._state));}}}};f.augmentProto(g,YAHOO.util.AttributeProvider);YAHOO.widget.Paginator=g;})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.CurrentPageReport=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("pageReportTemplateChange",this.update,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("pageReportClassChange",this.update,this,true);};c.ui.CurrentPageReport.init=function(d){d.setAttributeConfig("pageReportClass",{value:"yui-pg-current",validator:b.isString});d.setAttributeConfig("pageReportTemplate",{value:"({currentPage} of {totalPages})",validator:b.isString});d.setAttributeConfig("pageReportValueGenerator",{value:function(g){var f=g.getCurrentPage(),e=g.getPageRecords();return{"currentPage":e?f:0,"totalPages":g.getTotalPages(),"startIndex":e?e[0]:0,"endIndex":e?e[1]:0,"startRecord":e?e[0]+1:0,"endRecord":e?e[1]+1:0,"totalRecords":g.get("totalRecords")};},validator:b.isFunction});};c.ui.CurrentPageReport.sprintf=function(e,d){return e.replace(/\{([\w\s\-]+)\}/g,function(f,g){return(g in d)?d[g]:"";});};c.ui.CurrentPageReport.prototype={span:null,render:function(d){this.span=document.createElement("span");this.span.className=this.paginator.get("pageReportClass");a(this.span,d+"-page-report");this.update();return this.span;},update:function(d){if(d&&d.prevValue===d.newValue){return;}this.span.innerHTML=c.ui.CurrentPageReport.sprintf(this.paginator.get("pageReportTemplate"),this.paginator.get("pageReportValueGenerator")(this.paginator));},destroy:function(){this.span.parentNode.removeChild(this.span);this.span=null;}};})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.PageLinks=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("pageLinksChange",this.rebuild,this,true);d.subscribe("pageLinkClassChange",this.rebuild,this,true);d.subscribe("currentPageClassChange",this.rebuild,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("pageLinksContainerClassChange",this.rebuild,this,true);};c.ui.PageLinks.init=function(d){d.setAttributeConfig("pageLinkClass",{value:"yui-pg-page",validator:b.isString});d.setAttributeConfig("currentPageClass",{value:"yui-pg-current-page",validator:b.isString});d.setAttributeConfig("pageLinksContainerClass",{value:"yui-pg-pages",validator:b.isString});d.setAttributeConfig("pageLinks",{value:10,validator:c.isNumeric});d.setAttributeConfig("pageLabelBuilder",{value:function(e,f){return e;},validator:b.isFunction});d.setAttributeConfig("pageTitleBuilder",{value:function(e,f){return"Page "+e;},validator:b.isFunction});};c.ui.PageLinks.calculateRange=function(f,g,e){var j=c.VALUE_UNLIMITED,i,d,h;if(!f||e===0||g===0||(g===j&&e===j)){return[0,-1];
+}if(g!==j){e=e===j?g:Math.min(e,g);}i=Math.max(1,Math.ceil(f-(e/2)));if(g===j){d=i+e-1;}else{d=Math.min(g,i+e-1);}h=e-(d-i+1);i=Math.max(1,i-h);return[i,d];};c.ui.PageLinks.prototype={current:0,container:null,render:function(d){var e=this.paginator;this.container=document.createElement("span");a(this.container,d+"-pages");this.container.className=e.get("pageLinksContainerClass");YAHOO.util.Event.on(this.container,"click",this.onClick,this,true);this.update({newValue:null,rebuild:true});return this.container;},update:function(q){if(q&&q.prevValue===q.newValue){return;}var g=this.paginator,m=g.getCurrentPage();if(this.current!==m||!m||q.rebuild){var r=g.get("pageLabelBuilder"),l=g.get("pageTitleBuilder"),k=c.ui.PageLinks.calculateRange(m,g.getTotalPages(),g.get("pageLinks")),f=k[0],h=k[1],o="",d,j,n;d='<a href="#" class="{class}" page="{page}" title="{title}">{label}</a>';n='<span class="{class}">{label}</span>';for(j=f;j<=h;++j){if(j===m){o+=b.substitute(n,{"class":g.get("currentPageClass")+" "+g.get("pageLinkClass"),"label":r(j,g)});}else{o+=b.substitute(d,{"class":g.get("pageLinkClass"),"page":j,"label":r(j,g),"title":l(j,g)});}}this.container.innerHTML=o;}},rebuild:function(d){d.rebuild=true;this.update(d);},destroy:function(){YAHOO.util.Event.purgeElement(this.container,true);this.container.parentNode.removeChild(this.container);this.container=null;},onClick:function(f){var d=YAHOO.util.Event.getTarget(f);if(d&&YAHOO.util.Dom.hasClass(d,this.paginator.get("pageLinkClass"))){YAHOO.util.Event.stopEvent(f);this.paginator.setPage(parseInt(d.getAttribute("page"),10));}}};})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.FirstPageLink=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("firstPageLinkLabelChange",this.update,this,true);d.subscribe("firstPageLinkClassChange",this.update,this,true);};c.ui.FirstPageLink.init=function(d){d.setAttributeConfig("firstPageLinkLabel",{value:"<< first",validator:b.isString});d.setAttributeConfig("firstPageLinkClass",{value:"yui-pg-first",validator:b.isString});d.setAttributeConfig("firstPageLinkTitle",{value:"First Page",validator:b.isString});};c.ui.FirstPageLink.prototype={current:null,link:null,span:null,render:function(e){var f=this.paginator,h=f.get("firstPageLinkClass"),d=f.get("firstPageLinkLabel"),g=f.get("firstPageLinkTitle");this.link=document.createElement("a");this.span=document.createElement("span");a(this.link,e+"-first-link");this.link.href="#";this.link.className=h;this.link.innerHTML=d;this.link.title=g;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);a(this.span,e+"-first-span");this.span.className=h;this.span.innerHTML=d;this.current=f.getCurrentPage()>1?this.link:this.span;return this.current;},update:function(f){if(f&&f.prevValue===f.newValue){return;}var d=this.current?this.current.parentNode:null;if(this.paginator.getCurrentPage()>1){if(d&&this.current===this.span){d.replaceChild(this.link,this.current);this.current=this.link;}}else{if(d&&this.current===this.link){d.replaceChild(this.span,this.current);this.current=this.span;}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null;},onClick:function(d){YAHOO.util.Event.stopEvent(d);this.paginator.setPage(1);}};})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.LastPageLink=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("lastPageLinkLabelChange",this.update,this,true);d.subscribe("lastPageLinkClassChange",this.update,this,true);};c.ui.LastPageLink.init=function(d){d.setAttributeConfig("lastPageLinkLabel",{value:"last >>",validator:b.isString});d.setAttributeConfig("lastPageLinkClass",{value:"yui-pg-last",validator:b.isString});d.setAttributeConfig("lastPageLinkTitle",{value:"Last Page",validator:b.isString});};c.ui.LastPageLink.prototype={current:null,link:null,span:null,na:null,render:function(e){var g=this.paginator,i=g.get("lastPageLinkClass"),d=g.get("lastPageLinkLabel"),f=g.getTotalPages(),h=g.get("lastPageLinkTitle");this.link=document.createElement("a");this.span=document.createElement("span");this.na=this.span.cloneNode(false);a(this.link,e+"-last-link");this.link.href="#";this.link.className=i;this.link.innerHTML=d;this.link.title=h;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);a(this.span,e+"-last-span");this.span.className=i;this.span.innerHTML=d;a(this.na,e+"-last-na");switch(f){case c.VALUE_UNLIMITED:this.current=this.na;break;case g.getCurrentPage():this.current=this.span;break;default:this.current=this.link;}return this.current;},update:function(f){if(f&&f.prevValue===f.newValue){return;}var d=this.current?this.current.parentNode:null,g=this.link;if(d){switch(this.paginator.getTotalPages()){case c.VALUE_UNLIMITED:g=this.na;break;case this.paginator.getCurrentPage():g=this.span;break;}if(this.current!==g){d.replaceChild(g,this.current);this.current=g;}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null;},onClick:function(d){YAHOO.util.Event.stopEvent(d);this.paginator.setPage(this.paginator.getTotalPages());}};})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.NextPageLink=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("nextPageLinkLabelChange",this.update,this,true);
+d.subscribe("nextPageLinkClassChange",this.update,this,true);};c.ui.NextPageLink.init=function(d){d.setAttributeConfig("nextPageLinkLabel",{value:"next >",validator:b.isString});d.setAttributeConfig("nextPageLinkClass",{value:"yui-pg-next",validator:b.isString});d.setAttributeConfig("nextPageLinkTitle",{value:"Next Page",validator:b.isString});};c.ui.NextPageLink.prototype={current:null,link:null,span:null,render:function(e){var g=this.paginator,i=g.get("nextPageLinkClass"),d=g.get("nextPageLinkLabel"),f=g.getTotalPages(),h=g.get("nextPageLinkTitle");this.link=document.createElement("a");this.span=document.createElement("span");a(this.link,e+"-next-link");this.link.href="#";this.link.className=i;this.link.innerHTML=d;this.link.title=h;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);a(this.span,e+"-next-span");this.span.className=i;this.span.innerHTML=d;this.current=g.getCurrentPage()===f?this.span:this.link;return this.current;},update:function(g){if(g&&g.prevValue===g.newValue){return;}var f=this.paginator.getTotalPages(),d=this.current?this.current.parentNode:null;if(this.paginator.getCurrentPage()!==f){if(d&&this.current===this.span){d.replaceChild(this.link,this.current);this.current=this.link;}}else{if(this.current===this.link){if(d){d.replaceChild(this.span,this.current);this.current=this.span;}}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null;},onClick:function(d){YAHOO.util.Event.stopEvent(d);this.paginator.setPage(this.paginator.getNextPage());}};})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.PreviousPageLink=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("previousPageLinkLabelChange",this.update,this,true);d.subscribe("previousPageLinkClassChange",this.update,this,true);};c.ui.PreviousPageLink.init=function(d){d.setAttributeConfig("previousPageLinkLabel",{value:"< prev",validator:b.isString});d.setAttributeConfig("previousPageLinkClass",{value:"yui-pg-previous",validator:b.isString});d.setAttributeConfig("previousPageLinkTitle",{value:"Previous Page",validator:b.isString});};c.ui.PreviousPageLink.prototype={current:null,link:null,span:null,render:function(e){var f=this.paginator,h=f.get("previousPageLinkClass"),d=f.get("previousPageLinkLabel"),g=f.get("previousPageLinkTitle");this.link=document.createElement("a");this.span=document.createElement("span");a(this.link,e+"-prev-link");this.link.href="#";this.link.className=h;this.link.innerHTML=d;this.link.title=g;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);a(this.span,e+"-prev-span");this.span.className=h;this.span.innerHTML=d;this.current=f.getCurrentPage()>1?this.link:this.span;return this.current;},update:function(f){if(f&&f.prevValue===f.newValue){return;}var d=this.current?this.current.parentNode:null;if(this.paginator.getCurrentPage()>1){if(d&&this.current===this.span){d.replaceChild(this.link,this.current);this.current=this.link;}}else{if(d&&this.current===this.link){d.replaceChild(this.span,this.current);this.current=this.span;}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null;},onClick:function(d){YAHOO.util.Event.stopEvent(d);this.paginator.setPage(this.paginator.getPreviousPage());}};})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.RowsPerPageDropdown=function(d){this.paginator=d;d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("rowsPerPageOptionsChange",this.rebuild,this,true);d.subscribe("totalRecordsChange",this._handleTotalRecordsChange,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("rowsPerPageDropdownClassChange",this.rebuild,this,true);};c.ui.RowsPerPageDropdown.init=function(d){d.setAttributeConfig("rowsPerPageOptions",{value:[],validator:b.isArray});d.setAttributeConfig("rowsPerPageDropdownClass",{value:"yui-pg-rpp-options",validator:b.isString});};c.ui.RowsPerPageDropdown.prototype={select:null,all:null,render:function(d){this.select=document.createElement("select");a(this.select,d+"-rpp");this.select.className=this.paginator.get("rowsPerPageDropdownClass");this.select.title="Rows per page";YAHOO.util.Event.on(this.select,"change",this.onChange,this,true);this.rebuild();return this.select;},rebuild:function(m){var d=this.paginator,g=this.select,n=d.get("rowsPerPageOptions"),f,l,h,j,k;this.all=null;for(j=0,k=n.length;j<k;++j){l=n[j];f=g.options[j]||g.appendChild(document.createElement("option"));h=b.isValue(l.value)?l.value:l;f.text=b.isValue(l.text)?l.text:l;if(b.isString(h)&&h.toLowerCase()==="all"){this.all=f;f.value=d.get("totalRecords");}else{f.value=h;}}while(g.options.length>n.length){g.removeChild(g.firstChild);}this.update();},update:function(j){if(j&&j.prevValue===j.newValue){return;}var h=this.paginator.get("rowsPerPage")+"",f=this.select.options,g,d;for(g=0,d=f.length;g<d;++g){if(f[g].value===h){f[g].selected=true;break;}}},onChange:function(d){this.paginator.setRowsPerPage(parseInt(this.select.options[this.select.selectedIndex].value,10));},_handleTotalRecordsChange:function(d){if(!this.all||(d&&d.prevValue===d.newValue)){return;}this.all.value=d.newValue;if(this.all.selected){this.paginator.set("rowsPerPage",d.newValue);}},destroy:function(){YAHOO.util.Event.purgeElement(this.select);this.select.parentNode.removeChild(this.select);this.select=null;}};})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.JumpToPageDropdown=function(d){this.paginator=d;d.subscribe("rowsPerPageChange",this.rebuild,this,true);d.subscribe("rowsPerPageOptionsChange",this.rebuild,this,true);d.subscribe("pageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.rebuild,this,true);
+d.subscribe("destroy",this.destroy,this,true);};c.ui.JumpToPageDropdown.init=function(d){d.setAttributeConfig("jumpToPageDropdownClass",{value:"yui-pg-jtp-options",validator:b.isString});};c.ui.JumpToPageDropdown.prototype={select:null,render:function(d){this.select=document.createElement("select");a(this.select,d+"-jtp");this.select.className=this.paginator.get("jumpToPageDropdownClass");this.select.title="Jump to page";YAHOO.util.Event.on(this.select,"change",this.onChange,this,true);this.rebuild();return this.select;},rebuild:function(l){var k=this.paginator,j=this.select,f=k.getTotalPages(),h,g,d;this.all=null;for(g=0,d=f;g<d;++g){h=j.options[g]||j.appendChild(document.createElement("option"));h.innerHTML=g+1;h.value=g+1;}for(g=f,d=j.options.length;g<d;g++){j.removeChild(j.lastChild);}this.update();},update:function(j){if(j&&j.prevValue===j.newValue){return;}var h=this.paginator.getCurrentPage()+"",f=this.select.options,g,d;for(g=0,d=f.length;g<d;++g){if(f[g].value===h){f[g].selected=true;break;}}},onChange:function(d){this.paginator.setPage(parseInt(this.select.options[this.select.selectedIndex].value,false));},destroy:function(){YAHOO.util.Event.purgeElement(this.select);this.select.parentNode.removeChild(this.select);this.select=null;}};})();YAHOO.register("paginator",YAHOO.widget.Paginator,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/profiler/profiler-min.js b/Websites/bugs.webkit.org/js/yui/profiler/profiler-min.js
new file mode 100644
index 0000000..fdb58cc
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/profiler/profiler-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.namespace("tool");YAHOO.tool.Profiler=function(){var container={},report={},stopwatches={},WATCH_STARTED=0,WATCH_STOPPED=1,WATCH_PAUSED=2,lang=YAHOO.lang;function createReport(name){report[name]={calls:0,max:0,min:0,avg:0,points:[]};}function saveDataPoint(name,duration){var functionData=report[name];if(!functionData){functionData=createReport(name);}functionData.calls++;functionData.points.push(duration);if(functionData.calls>1){functionData.avg=((functionData.avg*(functionData.calls-1))+duration)/functionData.calls;functionData.min=Math.min(functionData.min,duration);functionData.max=Math.max(functionData.max,duration);}else{functionData.avg=duration;functionData.min=duration;functionData.max=duration;}}return{clear:function(name){if(lang.isString(name)){delete report[name];delete stopwatches[name];}else{report={};stopwatches={};}},getOriginal:function(name){return container[name];},instrument:function(name,method){var newMethod=function(){var start=new Date(),retval=method.apply(this,arguments),stop=new Date();saveDataPoint(name,stop-start);return retval;};lang.augmentObject(newMethod,method);newMethod.__yuiProfiled=true;newMethod.prototype=method.prototype;container[name]=method;container[name].__yuiFuncName=name;createReport(name);return newMethod;},pause:function(name){var now=new Date(),stopwatch=stopwatches[name];if(stopwatch&&stopwatch.state==WATCH_STARTED){stopwatch.total+=(now-stopwatch.start);stopwatch.start=0;stopwatch.state=WATCH_PAUSED;}},start:function(name){if(container[name]){throw new Error("Cannot use '"+name+"' for profiling through start(), name is already in use.");}else{if(!report[name]){createReport(name);}if(!stopwatches[name]){stopwatches[name]={state:WATCH_STOPPED,start:0,total:0};}if(stopwatches[name].state==WATCH_STOPPED){stopwatches[name].state=WATCH_STARTED;stopwatches[name].start=new Date();}}},stop:function(name){var now=new Date(),stopwatch=stopwatches[name];if(stopwatch){if(stopwatch.state==WATCH_STARTED){saveDataPoint(name,stopwatch.total+(now-stopwatch.start));}else{if(stopwatch.state==WATCH_PAUSED){saveDataPoint(name,stopwatch.total);}}stopwatch.start=0;stopwatch.total=0;stopwatch.state=WATCH_STOPPED;}},getAverage:function(name){return report[name].avg;},getCallCount:function(name){return report[name].calls;},getMax:function(name){return report[name].max;},getMin:function(name){return report[name].min;},getFunctionReport:function(name){return report[name];},getReport:function(name){return report[name];},getFullReport:function(filter){filter=filter||function(){return true;};if(lang.isFunction(filter)){var fullReport={};for(var name in report){if(filter(report[name])){fullReport[name]=report[name];}}return fullReport;}},registerConstructor:function(name,owner){this.registerFunction(name,owner,true);},registerFunction:function(name,owner,registerPrototype){var funcName=(name.indexOf(".")>-1?name.substring(name.lastIndexOf(".")+1):name),method,prototype;if(!lang.isObject(owner)){owner=eval(name.substring(0,name.lastIndexOf(".")));}method=owner[funcName];prototype=method.prototype;if(lang.isFunction(method)&&!method.__yuiProfiled){owner[funcName]=this.instrument(name,method);container[name].__yuiOwner=owner;container[name].__yuiFuncName=funcName;if(registerPrototype){this.registerObject(name+".prototype",prototype);}}},registerObject:function(name,object,recurse){object=(lang.isObject(object)?object:eval(name));container[name]=object;for(var prop in object){if(typeof object[prop]=="function"){if(prop!="constructor"&&prop!="superclass"){this.registerFunction(name+"."+prop,object);}}else{if(typeof object[prop]=="object"&&recurse){this.registerObject(name+"."+prop,object[prop],recurse);}}}},unregisterConstructor:function(name){if(lang.isFunction(container[name])){this.unregisterFunction(name,true);}},unregisterFunction:function(name,unregisterPrototype){if(lang.isFunction(container[name])){if(unregisterPrototype){this.unregisterObject(name+".prototype",container[name].prototype);}var owner=container[name].__yuiOwner,funcName=container[name].__yuiFuncName;delete container[name].__yuiOwner;delete container[name].__yuiFuncName;owner[funcName]=container[name];delete container[name];}},unregisterObject:function(name,recurse){if(lang.isObject(container[name])){var object=container[name];for(var prop in object){if(typeof object[prop]=="function"){this.unregisterFunction(name+"."+prop);}else{if(typeof object[prop]=="object"&&recurse){this.unregisterObject(name+"."+prop,recurse);}}}delete container[name];}}};}();YAHOO.register("profiler",YAHOO.tool.Profiler,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/profilerviewer/profilerviewer-min.js b/Websites/bugs.webkit.org/js/yui/profilerviewer/profilerviewer-min.js
new file mode 100644
index 0000000..5fa0fcb
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/profilerviewer/profilerviewer-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){YAHOO.widget.ProfilerViewer=function(H,G){G=G||{};if(arguments.length==1&&!YAHOO.lang.isString(H)&&!H.nodeName){G=H;H=G.element||null;}if(!H&&!G.element){H=this._createProfilerViewerElement();}YAHOO.widget.ProfilerViewer.superclass.constructor.call(this,H,G);this._init();};YAHOO.extend(YAHOO.widget.ProfilerViewer,YAHOO.util.Element);YAHOO.lang.augmentObject(YAHOO.widget.ProfilerViewer,{CLASS:"yui-pv",CLASS_DASHBOARD:"yui-pv-dashboard",CLASS_REFRESH:"yui-pv-refresh",CLASS_BUSY:"yui-pv-busy",CLASS_CHART_CONTAINER:"yui-pv-chartcontainer",CLASS_CHART:"yui-pv-chart",CLASS_CHART_LEGEND:"yui-pv-chartlegend",CLASS_TABLE:"yui-pv-table",STRINGS:{title:"YUI ProfilerViewer",buttons:{viewprofiler:"View Profiler Data",hideprofiler:"Hide Profiler Report",showchart:"Show Chart",hidechart:"Hide Chart",refreshdata:"Refresh Data"},colHeads:{fn:["Function/Method",null],calls:["Calls",40],avg:["Average",80],min:["Shortest",70],max:["Longest",70],total:["Total Time",70],pct:["Percent",70]},millisecondsAbbrev:"ms",initMessage:"initialiazing chart...",installFlashMessage:"Unable to load Flash content. The YUI Charts Control requires Flash Player 9.0.45 or higher. You can download the latest version of Flash Player from the <a href='http://www.adobe.com/go/getflashplayer'>Adobe Flash Player Download Center</a>."},timeAxisLabelFunction:function(H){var G=(H===Math.floor(H))?H:(Math.round(H*1000))/1000;return(G+" "+YAHOO.widget.ProfilerViewer.STRINGS.millisecondsAbbrev);},percentAxisLabelFunction:function(H){var G=(H===Math.floor(H))?H:(Math.round(H*100))/100;return(G+"%");}},true);var C=YAHOO.util.Dom;var A=YAHOO.util.Event;var B=YAHOO.tool.Profiler;var E=YAHOO.widget.ProfilerViewer;var D=E.prototype;D.refreshData=function(){this.fireEvent("dataRefreshEvent");};D.getHeadEl=function(){return(this._headEl)?C.get(this._headEl):false;};D.getBodyEl=function(){return(this._bodyEl)?C.get(this._bodyEl):false;};D.getChartEl=function(){return(this._chartEl)?C.get(this._chartEl):false;};D.getTableEl=function(){return(this._tableEl)?C.get(this._tableEl):false;};D.getDataTable=function(){return this._dataTable;};D.getChart=function(){return this._chart;};D._rendered=false;D._headEl=null;D._bodyEl=null;D._toggleVisibleEl=null;D._busyEl=null;D._busy=false;D._tableEl=null;D._dataTable=null;D._chartEl=null;D._chartLegendEl=null;D._chartElHeight=250;D._chart=null;D._chartInitialized=false;D._init=function(){this.createEvent("dataRefreshEvent");this.createEvent("renderEvent");this.on("dataRefreshEvent",this._refreshDataTable,this,true);this._initLauncherDOM();if(this.get("showChart")){this.on("sortedByChange",this._refreshChart);}};D._createProfilerViewerElement=function(){var G=document.createElement("div");document.body.insertBefore(G,document.body.firstChild);C.addClass(G,this.SKIN_CLASS);C.addClass(G,E.CLASS);return G;};D.toString=function(){return"ProfilerViewer "+(this.get("id")||this.get("tagName"));};D._toggleVisible=function(){var G=(this.get("visible"))?false:true;this.set("visible",G);};D._show=function(){if(!this._busy){this._setBusyState(true);if(!this._rendered){var G=new YAHOO.util.YUILoader();if(this.get("base")){G.base=this.get("base");}var H=["datatable"];if(this.get("showChart")){H.push("charts");}G.insert({require:H,onSuccess:function(){this._render();},scope:this});}else{var I=this.get("element");C.removeClass(I,"yui-pv-minimized");this._toggleVisibleEl.innerHTML=E.STRINGS.buttons.hideprofiler;C.addClass(I,"yui-pv-null");C.removeClass(I,"yui-pv-null");this.refreshData();}}};D._hide=function(){this._toggleVisibleEl.innerHTML=E.STRINGS.buttons.viewprofiler;C.addClass(this.get("element"),"yui-pv-minimized");};D._render=function(){C.removeClass(this.get("element"),"yui-pv-minimized");this._initViewerDOM();this._initDataTable();if(this.get("showChart")){this._initChartDOM();this._initChart();}this._rendered=true;this._toggleVisibleEl.innerHTML=E.STRINGS.buttons.hideprofiler;this.fireEvent("renderEvent");};D._initLauncherDOM=function(){var I=this.get("element");C.addClass(I,E.CLASS);C.addClass(I,"yui-pv-minimized");this._headEl=document.createElement("div");C.addClass(this._headEl,"hd");var H=E.STRINGS.buttons;var G=(this.get("visible"))?H.hideprofiler:H.viewprofiler;this._toggleVisibleEl=this._createButton(G,this._headEl);this._refreshEl=this._createButton(H.refreshdata,this._headEl);C.addClass(this._refreshEl,E.CLASS_REFRESH);this._busyEl=document.createElement("span");this._headEl.appendChild(this._busyEl);var J=document.createElement("h4");J.innerHTML=E.STRINGS.title;this._headEl.appendChild(J);I.appendChild(this._headEl);A.on(this._toggleVisibleEl,"click",this._toggleVisible,this,true);A.on(this._refreshEl,"click",function(){if(!this._busy){this._setBusyState(true);this.fireEvent("dataRefreshEvent");}},this,true);};D._initViewerDOM=function(){var G=this.get("element");this._bodyEl=document.createElement("div");C.addClass(this._bodyEl,"bd");this._tableEl=document.createElement("div");C.addClass(this._tableEl,E.CLASS_TABLE);this._bodyEl.appendChild(this._tableEl);G.appendChild(this._bodyEl);};D._initChartDOM=function(){this._chartContainer=document.createElement("div");C.addClass(this._chartContainer,E.CLASS_CHART_CONTAINER);var H=document.createElement("div");C.addClass(H,E.CLASS_CHART_LEGEND);var G=document.createElement("div");this._chartLegendEl=document.createElement("dl");this._chartLegendEl.innerHTML="<dd>"+E.STRINGS.initMessage+"</dd>";this._chartEl=document.createElement("div");C.addClass(this._chartEl,E.CLASS_CHART);var I=document.createElement("p");I.innerHTML=E.STRINGS.installFlashMessage;this._chartEl.appendChild(I);this._chartContainer.appendChild(H);H.appendChild(G);G.appendChild(this._chartLegendEl);this._chartContainer.appendChild(this._chartEl);this._bodyEl.insertBefore(this._chartContainer,this._tableEl);};D._createButton=function(I,J,H){var G=document.createElement("a");G.innerHTML=G.title=I;if(J){if(!H){J.appendChild(G);}else{J.insertBefore(G,J.firstChild);}}return G;};D._setBusyState=function(G){if(G){C.addClass(this._busyEl,E.CLASS_BUSY);
+this._busy=true;}else{C.removeClass(this._busyEl,E.CLASS_BUSY);this._busy=false;}};D._genSortFunction=function(H,G){var J=H;var I=G;return function(L,K){if(I==YAHOO.widget.DataTable.CLASS_ASC){return L[J]-K[J];}else{return((L[J]-K[J])*-1);}};};var F=function(G){var I=0;for(var H=0;H<G.length;I+=G[H++]){}return I;};D._getProfilerData=function(){var L=B.getFullReport();var N=[];var H=0;for(name in L){if(YAHOO.lang.hasOwnProperty(L,name)){var G=L[name];var I={};I.fn=name;I.points=G.points.slice();I.calls=G.calls;I.min=G.min;I.max=G.max;I.avg=G.avg;I.total=F(I.points);I.points=G.points;var P=this.get("filter");if((!P)||(P(I))){N.push(I);H+=I.total;}}}for(var M=0,K=N.length;M<K;M++){N[M].pct=(H)?(N[M].total*100)/H:0;}var O=this.get("sortedBy");var Q=O.key;var J=O.dir;N.sort(this._genSortFunction(Q,J));return N;};D._initDataTable=function(){var P=this;this._dataSource=new YAHOO.util.DataSource(function(){return P._getProfilerData.call(P);},{responseType:YAHOO.util.DataSource.TYPE_JSARRAY,maxCacheEntries:0});var H=this._dataSource;H.responseSchema={fields:["fn","avg","calls","max","min","total","pct","points"]};var O=function(S,R,T,U){var Q=(U===Math.floor(U))?U:(Math.round(U*1000))/1000;S.innerHTML=Q+" "+E.STRINGS.millisecondsAbbrev;};var N=function(S,R,T,U){var Q=(U===Math.floor(U))?U:(Math.round(U*100))/100;S.innerHTML=Q+"%";};var M=YAHOO.widget.DataTable.CLASS_ASC;var J=YAHOO.widget.DataTable.CLASS_DESC;var K=E.STRINGS.colHeads;var I=O;var L=[{key:"fn",sortable:true,label:K.fn[0],sortOptions:{defaultDir:M},resizeable:(YAHOO.util.DragDrop)?true:false,minWidth:K.fn[1]},{key:"calls",sortable:true,label:K.calls[0],sortOptions:{defaultDir:J},width:K.calls[1]},{key:"avg",sortable:true,label:K.avg[0],sortOptions:{defaultDir:J},formatter:I,width:K.avg[1]},{key:"min",sortable:true,label:K.min[0],sortOptions:{defaultDir:M},formatter:I,width:K.min[1]},{key:"max",sortable:true,label:K.max[0],sortOptions:{defaultDir:J},formatter:I,width:K.max[1]},{key:"total",sortable:true,label:K.total[0],sortOptions:{defaultDir:J},formatter:I,width:K.total[1]},{key:"pct",sortable:true,label:K.pct[0],sortOptions:{defaultDir:J},formatter:N,width:K.pct[1]}];this._dataTable=new YAHOO.widget.DataTable(this._tableEl,L,H,{scrollable:true,height:this.get("tableHeight"),initialRequest:null,sortedBy:{key:"total",dir:YAHOO.widget.DataTable.CLASS_DESC}});var G=this._dataTable;G.subscribe("sortedByChange",this._sortedByChange,this,true);G.subscribe("renderEvent",this._dataTableRenderHandler,this,true);G.subscribe("initEvent",this._dataTableRenderHandler,this,true);A.on(this._tableEl.getElementsByTagName("th"),"click",this._thClickHandler,this,true);};D._sortedByChange=function(G){if(G.newValue&&G.newValue.key){this.set("sortedBy",{key:G.newValue.key,dir:G.newValue.dir});}};D._dataTableRenderHandler=function(G){this._setBusyState(false);};D._thClickHandler=function(G){this._setBusyState(true);};D._refreshDataTable=function(G){var H=this._dataTable;H.getDataSource().sendRequest("",H.onDataReturnInitializeTable,H);};D._refreshChart=function(){switch(this.get("sortedBy").key){case"fn":this._chart.set("dataSource",this._chart.get("dataSource"));return;case"calls":this._chart.set("xAxis",this._chartAxisDefinitionPlain);break;case"pct":this._chart.set("xAxis",this._chartAxisDefinitionPercent);break;default:this._chart.set("xAxis",this._chartAxisDefinitionTime);break;}this._drawChartLegend();this._chart.set("series",this._getSeriesDef(this.get("sortedBy").key));};D._getChartData=function(){var H=this._dataTable.getRecordSet().getRecords(0,this.get("maxChartFunctions"));var G=[];for(var J=0,I=H.length;J<I;J++){G.push(H[J].getData());}return G;};D._getSeriesDef=function(K){var J=this.get("chartSeriesDefinitions")[K];var G=[];for(var I=0,H=J.group.length;I<H;I++){var L=this.get("chartSeriesDefinitions")[J.group[I]];G.push({displayName:L.displayName,xField:L.xField,style:{color:L.style.color,size:L.style.size}});}return G;};D._initChart=function(){this._sizeChartCanvas();YAHOO.widget.Chart.SWFURL=this.get("swfUrl");var G=this;var H=new YAHOO.util.DataSource(function(){return G._getChartData.call(G);},{responseType:YAHOO.util.DataSource.TYPE_JSARRAY,maxCacheEntries:0});H.responseSchema={fields:["fn","avg","calls","max","min","total","pct"]};H.subscribe("responseEvent",this._sizeChartCanvas,this,true);this._chartAxisDefinitionTime=new YAHOO.widget.NumericAxis();this._chartAxisDefinitionTime.labelFunction="YAHOO.widget.ProfilerViewer.timeAxisLabelFunction";this._chartAxisDefinitionPercent=new YAHOO.widget.NumericAxis();this._chartAxisDefinitionPercent.labelFunction="YAHOO.widget.ProfilerViewer.percentAxisLabelFunction";this._chartAxisDefinitionPlain=new YAHOO.widget.NumericAxis();this._chart=new YAHOO.widget.BarChart(this._chartEl,H,{yField:"fn",series:this._getSeriesDef(this.get("sortedBy").key),style:this.get("chartStyle"),xAxis:this._chartAxisDefinitionTime});this._drawChartLegend();this._chartInitialized=true;this._dataTable.unsubscribe("initEvent",this._initChart,this);this._dataTable.subscribe("initEvent",this._refreshChart,this,true);};D._drawChartLegend=function(){var M=this.get("chartSeriesDefinitions");var I=M[this.get("sortedBy").key];var H=this._chartLegendEl;H.innerHTML="";for(var K=0,J=I.group.length;K<J;K++){var N=M[I.group[K]];var L=document.createElement("dt");C.setStyle(L,"backgroundColor","#"+N.style.color);var G=document.createElement("dd");G.innerHTML=N.displayName;H.appendChild(L);H.appendChild(G);}};D._sizeChartCanvas=function(I){var G=(I)?I.response.length:this.get("maxChartFunctions");var H=(G*36)+34;if(H!=parseInt(this._chartElHeight,10)){this._chartElHeight=H;C.setStyle(this._chartEl,"height",H+"px");}};D.initAttributes=function(G){YAHOO.widget.ProfilerViewer.superclass.initAttributes.call(this,G);this.setAttributeConfig("base",{value:G.base});this.setAttributeConfig("tableHeight",{value:G.tableHeight||"15em",method:function(H){if(this._dataTable){this._dataTable.set("height",H);}}});this.setAttributeConfig("sortedBy",{value:G.sortedBy||{key:"total",dir:"yui-dt-desc"}});
+this.setAttributeConfig("filter",{value:G.filter||null,validator:YAHOO.lang.isFunction});this.setAttributeConfig("swfUrl",{value:G.swfUrl||"http://yui.yahooapis.com/2.5.0/build/charts/assets/charts.swf"});this.setAttributeConfig("maxChartFunctions",{value:G.maxChartFunctions||6,method:function(H){if(this._rendered){this._sizeChartCanvas();}},validator:YAHOO.lang.isNumber});this.setAttributeConfig("chartStyle",{value:G.chartStyle||{font:{name:"Arial",color:15658588,size:12},background:{color:"6e6e63"}},method:function(){if(this._rendered&&this.get("showChart")){this._refreshChart();}}});this.setAttributeConfig("chartSeriesDefinitions",{value:G.chartSeriesDefinitions||{total:{displayName:E.STRINGS.colHeads.total[0],xField:"total",style:{color:"4d95dd",size:20},group:["total"]},calls:{displayName:E.STRINGS.colHeads.calls[0],xField:"calls",style:{color:"edff9f",size:20},group:["calls"]},avg:{displayName:E.STRINGS.colHeads.avg[0],xField:"avg",style:{color:"209daf",size:9},group:["avg","min","max"]},min:{displayName:E.STRINGS.colHeads.min[0],xField:"min",style:{color:"b6ecf4",size:9},group:["avg","min","max"]},max:{displayName:E.STRINGS.colHeads.max[0],xField:"max",style:{color:"29c7de",size:9},group:["avg","min","max"]},pct:{displayName:E.STRINGS.colHeads.pct[0],xField:"pct",style:{color:"C96EDB",size:20},group:["pct"]}},method:function(){if(this._rendered&&this.get("showChart")){this._refreshChart();}}});this.setAttributeConfig("visible",{value:G.visible||false,validator:YAHOO.lang.isBoolean,method:function(H){if(H){this._show();}else{if(this._rendered){this._hide();}}}});this.setAttributeConfig("showChart",{value:G.showChart||true,validator:YAHOO.lang.isBoolean,writeOnce:true});YAHOO.widget.ProfilerViewer.superclass.initAttributes.call(this,G);};})();YAHOO.register("profilerviewer",YAHOO.widget.ProfilerViewer,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/progressbar/progressbar-min.js b/Websites/bugs.webkit.org/js/yui/progressbar/progressbar-min.js
new file mode 100644
index 0000000..e3e8c62
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/progressbar/progressbar-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var c=YAHOO.util.Dom,i=YAHOO.lang,B="yui-pb",D=B+"-mask",A=B+"-bar",z=B+"-anim",q=B+"-tl",l=B+"-tr",k=B+"-bl",g=B+"-br",h="width",w="height",m="minValue",y="maxValue",j="value",a="anim",x="direction",e="ltr",t="rtl",G="ttb",s="btt",f="barEl",d="maskEl",v="ariaTextTemplate",n="animAcceleration",p="background-position",o="px",C="start",F="progress",u="complete";var r=function(b){r.superclass.constructor.call(this,document.createElement("div"),b);this._init(b);};YAHOO.widget.ProgressBar=r;r.MARKUP=['<div class="',A,'"></div><div class="',D,'"><div class="',q,'"></div><div class="',l,'"></div><div class="',k,'"></div><div class="',g,'"></div></div>'].join("");i.extend(r,YAHOO.util.Element,{_init:function(b){},initAttributes:function(I){r.superclass.initAttributes.call(this,I);this.set("innerHTML",r.MARKUP);this.addClass(B);var H,b=["id",h,w,"class","style"];while((H=b.pop())){if(H in I){this.set(H,I[H]);}}this.setAttributeConfig(f,{readOnly:true,value:this.getElementsByClassName(A)[0]});this.setAttributeConfig(d,{readOnly:true,value:this.getElementsByClassName(D)[0]});this.setAttributeConfig(x,{value:e,validator:function(J){if(this._rendered){return false;}switch(J){case e:case t:case G:case s:return true;default:return false;}}});this.setAttributeConfig(y,{value:100,validator:i.isNumber,method:function(J){this.get("element").setAttribute("aria-valuemax",J);if(this.get(j)>J){this.set(j,J);}}});this.setAttributeConfig(m,{value:0,validator:i.isNumber,method:function(J){this.get("element").setAttribute("aria-valuemin",J);if(this.get(j)<J){this.set(j,J);}}});this.setAttributeConfig(h,{getter:function(){return this.getStyle(h);},method:this._widthChange});this.setAttributeConfig(w,{getter:function(){return this.getStyle(w);},method:this._heightChange});this.setAttributeConfig(v,{value:"{value}"});this.setAttributeConfig(j,{value:0,validator:function(J){return i.isNumber(J)&&J>=this.get(m)&&J<=this.get(y);},method:this._valueChange});this.setAttributeConfig(a,{validator:function(J){return !!YAHOO.util.Anim;},setter:this._animSetter});this.setAttributeConfig(n,{value:null,validator:function(J){return i.isNumber(J)||i.isNull(J);},method:function(J){this._fixAnim(this.get(a),J);}});},render:function(H,I){if(this._rendered){return;}this._rendered=true;var J=this.get(x);this.addClass(B);this.addClass(B+"-"+J);var b=this.get("element");b.tabIndex=0;b.setAttribute("role","progressbar");b.setAttribute("aria-valuemin",this.get(m));b.setAttribute("aria-valuemax",this.get(y));this.appendTo(H,I);this.redraw(false);this._previousValue=this.get(j);this._fixEdges();this.on("minValueChange",this.redraw);this.on("maxValueChange",this.redraw);return this;},redraw:function(b){this._recalculateConstants();this._valueChange(this.get(j),b);},destroy:function(){this.set(a,false);this.unsubscribeAll();var b=this.get("element");if(b.parentNode){b.parentNode.removeChild(b);}},_previousValue:0,_barSpace:100,_barFactor:1,_rendered:false,_heightChange:function(b){if(i.isNumber(b)){b+=o;}this.setStyle(w,b);this._fixEdges();this.redraw(false);},_widthChange:function(b){if(i.isNumber(b)){b+=o;}this.setStyle(h,b);this._fixEdges();this.redraw(false);},_fixEdges:function(){if(!this._rendered||YAHOO.env.ua.ie||YAHOO.env.ua.gecko){return;}var J=this.get(d),L=c.getElementsByClassName(q,undefined,J)[0],I=c.getElementsByClassName(l,undefined,J)[0],K=c.getElementsByClassName(k,undefined,J)[0],H=c.getElementsByClassName(g,undefined,J)[0],b=(parseInt(c.getStyle(J,w),10)-parseInt(c.getStyle(L,w),10))+o;c.setStyle(K,w,b);c.setStyle(H,w,b);b=(parseInt(c.getStyle(J,h),10)-parseInt(c.getStyle(L,h),10))+o;c.setStyle(I,h,b);c.setStyle(H,h,b);},_recalculateConstants:function(){var b=this.get(f);switch(this.get(x)){case e:case t:this._barSpace=parseInt(this.get(h),10)-(parseInt(c.getStyle(b,"marginLeft"),10)||0)-(parseInt(c.getStyle(b,"marginRight"),10)||0);break;case G:case s:this._barSpace=parseInt(this.get(w),10)-(parseInt(c.getStyle(b,"marginTop"),10)||0)-(parseInt(c.getStyle(b,"marginBottom"),10)||0);break;}this._barFactor=this._barSpace/(this.get(y)-(this.get(m)||0))||1;},_animSetter:function(I){var H,b=this.get(f);if(I){if(I instanceof YAHOO.util.Anim){H=I;}else{H=new YAHOO.util.Anim(b);}H.onTween.subscribe(this._animOnTween,this,true);H.onComplete.subscribe(this._animComplete,this,true);this._fixAnim(H,this.get(n));}else{H=this.get(a);if(H){H.onTween.unsubscribeAll();H.onComplete.unsubscribeAll();}H=null;}return H;},_fixAnim:function(I,H){if(I){if(!this._oldSetAttribute){this._oldSetAttribute=I.setAttribute;}var b=this;switch(this.get(x)){case e:I.setAttribute=function(J,L,K){L=Math.round(L);b._oldSetAttribute.call(this,J,L,K);if(J==h){b._oldSetAttribute.call(this,p,-L*H,o);}};break;case t:I.setAttribute=function(J,M,K){M=Math.round(M);b._oldSetAttribute.call(this,J,M,K);if(J==h){var L=b._barSpace-M;b._oldSetAttribute.call(this,"left",L,o);b._oldSetAttribute.call(this,p,-L+M*H,o);}};break;case G:I.setAttribute=function(J,L,K){L=Math.round(L);b._oldSetAttribute.call(this,J,L,K);if(J==w){b._oldSetAttribute.call(this,p,"center "+(-L*H),o);}};break;case s:I.setAttribute=function(J,M,K){M=Math.round(M);b._oldSetAttribute.call(this,J,M,K);if(J==w){var L=b._barSpace-M;b._oldSetAttribute.call(this,"top",L,o);b._oldSetAttribute.call(this,p,"center "+(M*H-L),o);}};break;}}},_animComplete:function(){var b=this.get(j);this._previousValue=b;this.fireEvent(F,b);this.fireEvent(u,b);c.removeClass(this.get(f),z);},_animOnTween:function(b,H){var I=Math.floor(this._tweenFactor*H[0].currentFrame+this._previousValue);this.fireEvent(F,I);},_valueChange:function(J,H){var I=this.get(a),b=Math.floor((J-this.get(m))*this._barFactor);this._setAriaText(J);if(this._rendered){if(I){I.stop();if(I.isAnimated()){I._onComplete.fire();}}this.fireEvent(C,this._previousValue);r._barSizeFunctions[((H!==false)&&I)?1:0][this.get(x)].call(this,J,b,this.get(f),I);}},_setAriaText:function(H){var b=this.get("element"),I=i.substitute(this.get(v),{value:H,minValue:this.get(m),maxValue:this.get(y)});
+b.setAttribute("aria-valuenow",H);b.setAttribute("aria-valuetext",I);}});var E=[{},{}];r._barSizeFunctions=E;E[0][e]=function(J,b,H,I){c.setStyle(H,h,b+o);this.fireEvent(F,J);this.fireEvent(u,J);};E[0][t]=function(J,b,H,I){c.setStyle(H,h,b+o);c.setStyle(H,"left",(this._barSpace-b)+o);this.fireEvent(F,J);this.fireEvent(u,J);};E[0][G]=function(J,b,H,I){c.setStyle(H,w,b+o);this.fireEvent(F,J);this.fireEvent(u,J);};E[0][s]=function(J,b,H,I){c.setStyle(H,w,b+o);c.setStyle(H,"top",(this._barSpace-b)+o);this.fireEvent(F,J);this.fireEvent(u,J);};E[1][e]=function(J,b,H,I){c.addClass(H,z);this._tweenFactor=(J-this._previousValue)/I.totalFrames/I.duration;I.attributes={width:{to:b}};I.animate();};E[1][t]=E[1][e];E[1][G]=function(J,b,H,I){c.addClass(H,z);this._tweenFactor=(J-this._previousValue)/I.totalFrames/I.duration;I.attributes={height:{to:b}};I.animate();};E[1][s]=E[1][G];})();YAHOO.register("progressbar",YAHOO.widget.ProgressBar,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/reset-fonts-grids/reset-fonts-grids.css b/Websites/bugs.webkit.org/js/yui/reset-fonts-grids/reset-fonts-grids.css
new file mode 100644
index 0000000..7c73713
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/reset-fonts-grids/reset-fonts-grids.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,select,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,button,caption,cite,code,dfn,em,input,optgroup,option,select,strong,textarea,th,var{font:inherit}del,ins{text-decoration:none}li{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:baseline}sub{vertical-align:baseline}legend{color:#000}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small}select,input,textarea,button{font:99% arial,helvetica,clean,sans-serif}table{font-size:inherit;font:100%}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%}body{text-align:center}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em}#doc2{width:73.076em;*width:71.25em}#doc3{margin:auto 10px;width:auto}#doc4{width:74.923em;*width:73.05em}.yui-b{position:relative}.yui-b{_position:static}#yui-main .yui-b{position:static}#yui-main,.yui-g .yui-u .yui-g{width:100%}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0}#yui-main .yui-b{float:none;width:auto}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%}.yui-gc div.first,.yui-gd .yui-u{width:66%}.yui-gd div.first{width:32%}.yui-ge div.first,.yui-gf .yui-u{width:74.2%}.yui-ge .yui-u,.yui-gf div.first{width:24%}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0}.yui-g .yui-g .yui-u{width:48.1%}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%}.yui-g .yui-gb .yui-u{_margin-left:1.0%}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0}.yui-gb .yui-gb .yui-u{_margin-left:.7%}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0}.yui-gb .yui-gd div.first{width:32%}.yui-g .yui-gd div.first{_width:29.9%}.yui-ge .yui-g{width:24%}.yui-gf .yui-g{width:74.2%}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%}.yui-ge div.first .yui-gd .yui-u{width:65%}.yui-ge div.first .yui-gd div.first{width:32%}#hd:after,#bd:after,#ft:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:"";display:block;clear:both}#hd,#bd,#ft,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1}
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/reset-fonts/reset-fonts.css b/Websites/bugs.webkit.org/js/yui/reset-fonts/reset-fonts.css
new file mode 100644
index 0000000..c4df2bc
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/reset-fonts/reset-fonts.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,select,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,button,caption,cite,code,dfn,em,input,optgroup,option,select,strong,textarea,th,var{font:inherit}del,ins{text-decoration:none}li{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:baseline}sub{vertical-align:baseline}legend{color:#000}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small}select,input,textarea,button{font:99% arial,helvetica,clean,sans-serif}table{font-size:inherit;font:100%}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%}
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/reset/reset-min.css b/Websites/bugs.webkit.org/js/yui/reset/reset-min.css
new file mode 100644
index 0000000..dab807f
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/reset/reset-min.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,select,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,button,caption,cite,code,dfn,em,input,optgroup,option,select,strong,textarea,th,var{font:inherit}del,ins{text-decoration:none}li{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:baseline}sub{vertical-align:baseline}legend{color:#000}
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/reset/reset.css b/Websites/bugs.webkit.org/js/yui/reset/reset.css
new file mode 100644
index 0000000..9bca239
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/reset/reset.css
@@ -0,0 +1,123 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+/**
+ * YUI Reset
+ * @module reset
+ * @namespace
+ * @requires
+ */
+html {
+ color: #000;
+ background: #FFF;
+}
+
+body,
+div,
+dl,
+dt,
+dd,
+ul,
+ol,
+li,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+pre,
+code,
+form,
+fieldset,
+legend,
+input,
+button,
+textarea,
+select,
+p,
+blockquote,
+th,
+td {
+ margin: 0;
+ padding: 0;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+fieldset,
+img {
+ border: 0;
+}
+
+address,
+button,
+caption,
+cite,
+code,
+dfn,
+em,
+input,
+optgroup,
+option,
+select,
+strong,
+textarea,
+th,
+var {
+ font:inherit;
+}
+
+del,
+ins {
+ text-decoration: none;
+}
+
+li {
+ list-style: none;
+}
+
+caption,
+th {
+ text-align: left;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: 100%;
+ font-weight: normal;
+}
+
+q:before,
+q:after {
+ content: '';
+}
+
+abbr,
+acronym {
+ border: 0;
+ font-variant: normal;
+}
+
+sup {
+ vertical-align: baseline;
+}
+
+sub {
+ vertical-align: baseline;
+}
+
+/*because legend doesn't inherit in IE */
+legend {
+ color: #000;
+}
diff --git a/Websites/bugs.webkit.org/js/yui/resize/resize-min.js b/Websites/bugs.webkit.org/js/yui/resize/resize-min.js
new file mode 100644
index 0000000..58c3895
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/resize/resize-min.js
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var E=YAHOO.util.Dom,A=YAHOO.util.Event,C=YAHOO.lang;var B=function(F,D){var G={element:F,attributes:D||{}};B.superclass.constructor.call(this,G.element,G.attributes);};B._instances={};B.getResizeById=function(D){if(B._instances[D]){return B._instances[D];}return false;};YAHOO.extend(B,YAHOO.util.Element,{CSS_RESIZE:"yui-resize",CSS_DRAG:"yui-draggable",CSS_HOVER:"yui-resize-hover",CSS_PROXY:"yui-resize-proxy",CSS_WRAP:"yui-resize-wrap",CSS_KNOB:"yui-resize-knob",CSS_HIDDEN:"yui-resize-hidden",CSS_HANDLE:"yui-resize-handle",CSS_STATUS:"yui-resize-status",CSS_GHOST:"yui-resize-ghost",CSS_RESIZING:"yui-resize-resizing",_resizeEvent:null,dd:null,browser:YAHOO.env.ua,_locked:null,_positioned:null,_dds:null,_wrap:null,_proxy:null,_handles:null,_currentHandle:null,_currentDD:null,_cache:null,_active:null,_createProxy:function(){if(this.get("proxy")){this._proxy=document.createElement("div");this._proxy.className=this.CSS_PROXY;this._proxy.style.height=this.get("element").clientHeight+"px";this._proxy.style.width=this.get("element").clientWidth+"px";this._wrap.parentNode.appendChild(this._proxy);}else{this.set("animate",false);}},_createWrap:function(){this._positioned=false;if(this.get("wrap")===false){switch(this.get("element").tagName.toLowerCase()){case"img":case"textarea":case"input":case"iframe":case"select":this.set("wrap",true);break;}}if(this.get("wrap")===true){this._wrap=document.createElement("div");this._wrap.id=this.get("element").id+"_wrap";this._wrap.className=this.CSS_WRAP;if(this.get("element").tagName.toLowerCase()=="textarea"){E.addClass(this._wrap,"yui-resize-textarea");}E.setStyle(this._wrap,"width",this.get("width")+"px");E.setStyle(this._wrap,"height",this.get("height")+"px");E.setStyle(this._wrap,"z-index",this.getStyle("z-index"));this.setStyle("z-index",0);var F=E.getStyle(this.get("element"),"position");E.setStyle(this._wrap,"position",((F=="static")?"relative":F));E.setStyle(this._wrap,"top",E.getStyle(this.get("element"),"top"));E.setStyle(this._wrap,"left",E.getStyle(this.get("element"),"left"));if(E.getStyle(this.get("element"),"position")=="absolute"){this._positioned=true;E.setStyle(this.get("element"),"position","relative");E.setStyle(this.get("element"),"top","0");E.setStyle(this.get("element"),"left","0");}var D=this.get("element").parentNode;D.replaceChild(this._wrap,this.get("element"));this._wrap.appendChild(this.get("element"));}else{this._wrap=this.get("element");if(E.getStyle(this._wrap,"position")=="absolute"){this._positioned=true;}}if(this.get("draggable")){this._setupDragDrop();}if(this.get("hover")){E.addClass(this._wrap,this.CSS_HOVER);}if(this.get("knobHandles")){E.addClass(this._wrap,this.CSS_KNOB);}if(this.get("hiddenHandles")){E.addClass(this._wrap,this.CSS_HIDDEN);}E.addClass(this._wrap,this.CSS_RESIZE);},_setupDragDrop:function(){E.addClass(this._wrap,this.CSS_DRAG);this.dd=new YAHOO.util.DD(this._wrap,this.get("id")+"-resize",{dragOnly:true,useShim:this.get("useShim")});this.dd.on("dragEvent",function(){this.fireEvent("dragEvent",arguments);},this,true);},_createHandles:function(){this._handles={};this._dds={};var G=this.get("handles");for(var F=0;F<G.length;F++){this._handles[G[F]]=document.createElement("div");this._handles[G[F]].id=E.generateId(this._handles[G[F]]);this._handles[G[F]].className=this.CSS_HANDLE+" "+this.CSS_HANDLE+"-"+G[F];var D=document.createElement("div");D.className=this.CSS_HANDLE+"-inner-"+G[F];this._handles[G[F]].appendChild(D);this._wrap.appendChild(this._handles[G[F]]);A.on(this._handles[G[F]],"mouseover",this._handleMouseOver,this,true);A.on(this._handles[G[F]],"mouseout",this._handleMouseOut,this,true);this._dds[G[F]]=new YAHOO.util.DragDrop(this._handles[G[F]],this.get("id")+"-handle-"+G,{useShim:this.get("useShim")});this._dds[G[F]].setPadding(15,15,15,15);this._dds[G[F]].on("startDragEvent",this._handleStartDrag,this._dds[G[F]],this);this._dds[G[F]].on("mouseDownEvent",this._handleMouseDown,this._dds[G[F]],this);}this._status=document.createElement("span");this._status.className=this.CSS_STATUS;document.body.insertBefore(this._status,document.body.firstChild);},_ieSelectFix:function(){return false;},_ieSelectBack:null,_setAutoRatio:function(D){if(this.get("autoRatio")){if(D&&D.shiftKey){this.set("ratio",true);}else{this.set("ratio",this._configs.ratio._initialConfig.value);}}},_handleMouseDown:function(D){if(this._locked){return false;}if(E.getStyle(this._wrap,"position")=="absolute"){this._positioned=true;}if(D){this._setAutoRatio(D);}if(this.browser.ie){this._ieSelectBack=document.body.onselectstart;document.body.onselectstart=this._ieSelectFix;}},_handleMouseOver:function(G){if(this._locked){return false;}E.removeClass(this._wrap,this.CSS_RESIZE);if(this.get("hover")){E.removeClass(this._wrap,this.CSS_HOVER);}var D=A.getTarget(G);if(!E.hasClass(D,this.CSS_HANDLE)){D=D.parentNode;}if(E.hasClass(D,this.CSS_HANDLE)&&!this._active){E.addClass(D,this.CSS_HANDLE+"-active");for(var F in this._handles){if(C.hasOwnProperty(this._handles,F)){if(this._handles[F]==D){E.addClass(D,this.CSS_HANDLE+"-"+F+"-active");break;}}}}E.addClass(this._wrap,this.CSS_RESIZE);},_handleMouseOut:function(G){E.removeClass(this._wrap,this.CSS_RESIZE);if(this.get("hover")&&!this._active){E.addClass(this._wrap,this.CSS_HOVER);}var D=A.getTarget(G);if(!E.hasClass(D,this.CSS_HANDLE)){D=D.parentNode;}if(E.hasClass(D,this.CSS_HANDLE)&&!this._active){E.removeClass(D,this.CSS_HANDLE+"-active");for(var F in this._handles){if(C.hasOwnProperty(this._handles,F)){if(this._handles[F]==D){E.removeClass(D,this.CSS_HANDLE+"-"+F+"-active");break;}}}}E.addClass(this._wrap,this.CSS_RESIZE);},_handleStartDrag:function(G,F){var D=F.getDragEl();if(E.hasClass(D,this.CSS_HANDLE)){if(E.getStyle(this._wrap,"position")=="absolute"){this._positioned=true;}this._active=true;this._currentDD=F;if(this._proxy){this._proxy.style.visibility="visible";this._proxy.style.zIndex="1000";this._proxy.style.height=this.get("element").clientHeight+"px";this._proxy.style.width=this.get("element").clientWidth+"px";
+}for(var H in this._handles){if(C.hasOwnProperty(this._handles,H)){if(this._handles[H]==D){this._currentHandle=H;var I="_handle_for_"+H;E.addClass(D,this.CSS_HANDLE+"-"+H+"-active");F.on("dragEvent",this[I],this,true);F.on("mouseUpEvent",this._handleMouseUp,this,true);break;}}}E.addClass(D,this.CSS_HANDLE+"-active");if(this.get("proxy")){var J=E.getXY(this.get("element"));E.setXY(this._proxy,J);if(this.get("ghost")){this.addClass(this.CSS_GHOST);}}E.addClass(this._wrap,this.CSS_RESIZING);this._setCache();this._updateStatus(this._cache.height,this._cache.width,this._cache.top,this._cache.left);this.fireEvent("startResize",{type:"startresize",target:this});}},_setCache:function(){this._cache.xy=E.getXY(this._wrap);E.setXY(this._wrap,this._cache.xy);this._cache.height=this.get("clientHeight");this._cache.width=this.get("clientWidth");this._cache.start.height=this._cache.height;this._cache.start.width=this._cache.width;this._cache.start.top=this._cache.xy[1];this._cache.start.left=this._cache.xy[0];this._cache.top=this._cache.xy[1];this._cache.left=this._cache.xy[0];this.set("height",this._cache.height,true);this.set("width",this._cache.width,true);},_handleMouseUp:function(F){this._active=false;var G="_handle_for_"+this._currentHandle;this._currentDD.unsubscribe("dragEvent",this[G],this,true);this._currentDD.unsubscribe("mouseUpEvent",this._handleMouseUp,this,true);if(this._proxy){this._proxy.style.visibility="hidden";this._proxy.style.zIndex="-1";if(this.get("setSize")){this.resize(F,this._cache.height,this._cache.width,this._cache.top,this._cache.left,true);}else{this.fireEvent("resize",{ev:"resize",target:this,height:this._cache.height,width:this._cache.width,top:this._cache.top,left:this._cache.left});}if(this.get("ghost")){this.removeClass(this.CSS_GHOST);}}if(this.get("hover")){E.addClass(this._wrap,this.CSS_HOVER);}if(this._status){E.setStyle(this._status,"display","none");}if(this.browser.ie){document.body.onselectstart=this._ieSelectBack;}if(this.browser.ie){E.removeClass(this._wrap,this.CSS_RESIZE);}for(var D in this._handles){if(C.hasOwnProperty(this._handles,D)){E.removeClass(this._handles[D],this.CSS_HANDLE+"-active");}}if(this.get("hover")&&!this._active){E.addClass(this._wrap,this.CSS_HOVER);}E.removeClass(this._wrap,this.CSS_RESIZING);E.removeClass(this._handles[this._currentHandle],this.CSS_HANDLE+"-"+this._currentHandle+"-active");E.removeClass(this._handles[this._currentHandle],this.CSS_HANDLE+"-active");if(this.browser.ie){E.addClass(this._wrap,this.CSS_RESIZE);}this._resizeEvent=null;this._currentHandle=null;if(!this.get("animate")){this.set("height",this._cache.height,true);this.set("width",this._cache.width,true);}this.fireEvent("endResize",{ev:"endResize",target:this,height:this._cache.height,width:this._cache.width,top:this._cache.top,left:this._cache.left});},_setRatio:function(K,N,Q,I){var O=K,G=N;if(this.get("ratio")){var P=this._cache.height,H=this._cache.width,F=parseInt(this.get("height"),10),L=parseInt(this.get("width"),10),M=this.get("maxHeight"),R=this.get("minHeight"),D=this.get("maxWidth"),J=this.get("minWidth");switch(this._currentHandle){case"l":K=F*(N/L);K=Math.min(Math.max(R,K),M);N=L*(K/F);Q=(this._cache.start.top-(-((F-K)/2)));I=(this._cache.start.left-(-((L-N))));break;case"r":K=F*(N/L);K=Math.min(Math.max(R,K),M);N=L*(K/F);Q=(this._cache.start.top-(-((F-K)/2)));break;case"t":N=L*(K/F);K=F*(N/L);I=(this._cache.start.left-(-((L-N)/2)));Q=(this._cache.start.top-(-((F-K))));break;case"b":N=L*(K/F);K=F*(N/L);I=(this._cache.start.left-(-((L-N)/2)));break;case"bl":K=F*(N/L);N=L*(K/F);I=(this._cache.start.left-(-((L-N))));break;case"br":K=F*(N/L);N=L*(K/F);break;case"tl":K=F*(N/L);N=L*(K/F);I=(this._cache.start.left-(-((L-N))));Q=(this._cache.start.top-(-((F-K))));break;case"tr":K=F*(N/L);N=L*(K/F);I=(this._cache.start.left);Q=(this._cache.start.top-(-((F-K))));break;}O=this._checkHeight(K);G=this._checkWidth(N);if((O!=K)||(G!=N)){Q=0;I=0;if(O!=K){G=this._cache.width;}if(G!=N){O=this._cache.height;}}}return[O,G,Q,I];},_updateStatus:function(K,G,J,F){if(this._resizeEvent&&(!C.isString(this._resizeEvent))){K=((K===0)?this._cache.start.height:K);G=((G===0)?this._cache.start.width:G);var I=parseInt(this.get("height"),10),D=parseInt(this.get("width"),10);if(isNaN(I)){I=parseInt(K,10);}if(isNaN(D)){D=parseInt(G,10);}var L=(parseInt(K,10)-I);var H=(parseInt(G,10)-D);this._cache.offsetHeight=L;this._cache.offsetWidth=H;if(this.get("status")){E.setStyle(this._status,"display","inline");this._status.innerHTML="<strong>"+parseInt(K,10)+" x "+parseInt(G,10)+"</strong><em>"+((L>0)?"+":"")+L+" x "+((H>0)?"+":"")+H+"</em>";E.setXY(this._status,[A.getPageX(this._resizeEvent)+12,A.getPageY(this._resizeEvent)+12]);}}},lock:function(D){this._locked=true;if(D&&this.dd){E.removeClass(this._wrap,"yui-draggable");this.dd.lock();}return this;},unlock:function(D){this._locked=false;if(D&&this.dd){E.addClass(this._wrap,"yui-draggable");this.dd.unlock();}return this;},isLocked:function(){return this._locked;},reset:function(){this.resize(null,this._cache.start.height,this._cache.start.width,this._cache.start.top,this._cache.start.left,true);return this;},resize:function(M,J,P,Q,H,F,K){if(this._locked){return false;}this._resizeEvent=M;var G=this._wrap,I=this.get("animate"),O=true;if(this._proxy&&!F){G=this._proxy;I=false;}this._setAutoRatio(M);if(this._positioned){if(this._proxy){Q=this._cache.top-Q;H=this._cache.left-H;}}var L=this._setRatio(J,P,Q,H);J=parseInt(L[0],10);P=parseInt(L[1],10);Q=parseInt(L[2],10);H=parseInt(L[3],10);if(Q==0){Q=E.getY(G);}if(H==0){H=E.getX(G);}if(this._positioned){if(this._proxy&&F){if(!I){G.style.top=this._proxy.style.top;G.style.left=this._proxy.style.left;}else{Q=this._proxy.style.top;H=this._proxy.style.left;}}else{if(!this.get("ratio")&&!this._proxy){Q=this._cache.top+-(Q);H=this._cache.left+-(H);}if(Q){if(this.get("minY")){if(Q<this.get("minY")){Q=this.get("minY");}}if(this.get("maxY")){if(Q>this.get("maxY")){Q=this.get("maxY");}}}if(H){if(this.get("minX")){if(H<this.get("minX")){H=this.get("minX");
+}}if(this.get("maxX")){if((H+P)>this.get("maxX")){H=(this.get("maxX")-P);}}}}}if(!K){var N=this.fireEvent("beforeResize",{ev:"beforeResize",target:this,height:J,width:P,top:Q,left:H});if(N===false){return false;}}this._updateStatus(J,P,Q,H);if(this._positioned){if(this._proxy&&F){}else{if(Q){E.setY(G,Q);this._cache.top=Q;}if(H){E.setX(G,H);this._cache.left=H;}}}if(J){if(!I){O=true;if(this._proxy&&F){if(!this.get("setSize")){O=false;}}if(O){G.style.height=J+"px";}if((this._proxy&&F)||!this._proxy){if(this._wrap!=this.get("element")){this.get("element").style.height=J+"px";}}}this._cache.height=J;}if(P){this._cache.width=P;if(!I){O=true;if(this._proxy&&F){if(!this.get("setSize")){O=false;}}if(O){G.style.width=P+"px";}if((this._proxy&&F)||!this._proxy){if(this._wrap!=this.get("element")){this.get("element").style.width=P+"px";}}}}if(I){if(YAHOO.util.Anim){var D=new YAHOO.util.Anim(G,{height:{to:this._cache.height},width:{to:this._cache.width}},this.get("animateDuration"),this.get("animateEasing"));if(this._positioned){if(Q){D.attributes.top={to:parseInt(Q,10)};}if(H){D.attributes.left={to:parseInt(H,10)};}}if(this._wrap!=this.get("element")){D.onTween.subscribe(function(){this.get("element").style.height=G.style.height;this.get("element").style.width=G.style.width;},this,true);}D.onComplete.subscribe(function(){this.set("height",J);this.set("width",P);this.fireEvent("resize",{ev:"resize",target:this,height:J,width:P,top:Q,left:H});},this,true);D.animate();}}else{if(this._proxy&&!F){this.fireEvent("proxyResize",{ev:"proxyresize",target:this,height:J,width:P,top:Q,left:H});}else{this.fireEvent("resize",{ev:"resize",target:this,height:J,width:P,top:Q,left:H});}}return this;},_handle_for_br:function(F){var G=this._setWidth(F.e);var D=this._setHeight(F.e);this.resize(F.e,D,G,0,0);},_handle_for_bl:function(G){var H=this._setWidth(G.e,true);var F=this._setHeight(G.e);var D=(H-this._cache.width);this.resize(G.e,F,H,0,D);},_handle_for_tl:function(G){var I=this._setWidth(G.e,true);var F=this._setHeight(G.e,true);var H=(F-this._cache.height);var D=(I-this._cache.width);this.resize(G.e,F,I,H,D);},_handle_for_tr:function(F){var H=this._setWidth(F.e);var D=this._setHeight(F.e,true);var G=(D-this._cache.height);this.resize(F.e,D,H,G,0);},_handle_for_r:function(D){this._dds.r.setYConstraint(0,0);var F=this._setWidth(D.e);this.resize(D.e,0,F,0,0);},_handle_for_l:function(F){this._dds.l.setYConstraint(0,0);var G=this._setWidth(F.e,true);var D=(G-this._cache.width);this.resize(F.e,0,G,0,D);},_handle_for_b:function(F){this._dds.b.setXConstraint(0,0);var D=this._setHeight(F.e);this.resize(F.e,D,0,0,0);},_handle_for_t:function(F){this._dds.t.setXConstraint(0,0);var D=this._setHeight(F.e,true);var G=(D-this._cache.height);this.resize(F.e,D,0,G,0);},_setWidth:function(H,J){var I=this._cache.xy[0],G=this._cache.width,D=A.getPageX(H),F=(D-I);if(J){F=(I-D)+parseInt(this.get("width"),10);}F=this._snapTick(F,this.get("xTicks"));F=this._checkWidth(F);return F;},_checkWidth:function(D){if(this.get("minWidth")){if(D<=this.get("minWidth")){D=this.get("minWidth");}}if(this.get("maxWidth")){if(D>=this.get("maxWidth")){D=this.get("maxWidth");}}return D;},_checkHeight:function(D){if(this.get("minHeight")){if(D<=this.get("minHeight")){D=this.get("minHeight");}}if(this.get("maxHeight")){if(D>=this.get("maxHeight")){D=this.get("maxHeight");}}return D;},_setHeight:function(G,I){var H=this._cache.xy[1],F=this._cache.height,J=A.getPageY(G),D=(J-H);if(I){D=(H-J)+parseInt(this.get("height"),10);}D=this._snapTick(D,this.get("yTicks"));D=this._checkHeight(D);return D;},_snapTick:function(G,F){if(!G||!F){return G;}var H=G;var D=G%F;if(D>0){if(D>(F/2)){H=G+(F-D);}else{H=G-D;}}return H;},init:function(H,F){this._locked=false;this._cache={xy:[],height:0,width:0,top:0,left:0,offsetHeight:0,offsetWidth:0,start:{height:0,width:0,top:0,left:0}};B.superclass.init.call(this,H,F);this.set("setSize",this.get("setSize"));if(F.height){this.set("height",parseInt(F.height,10));}else{var G=this.getStyle("height");if(G=="auto"){this.set("height",parseInt(this.get("element").offsetHeight,10));}}if(F.width){this.set("width",parseInt(F.width,10));}else{var D=this.getStyle("width");if(D=="auto"){this.set("width",parseInt(this.get("element").offsetWidth,10));}}var I=H;if(!C.isString(I)){I=E.generateId(I);}B._instances[I]=this;this._active=false;this._createWrap();this._createProxy();this._createHandles();},getProxyEl:function(){return this._proxy;},getWrapEl:function(){return this._wrap;},getStatusEl:function(){return this._status;},getActiveHandleEl:function(){return this._handles[this._currentHandle];},isActive:function(){return((this._active)?true:false);},initAttributes:function(D){B.superclass.initAttributes.call(this,D);this.setAttributeConfig("useShim",{value:((D.useShim===true)?true:false),validator:YAHOO.lang.isBoolean,method:function(F){for(var G in this._dds){if(C.hasOwnProperty(this._dds,G)){this._dds[G].useShim=F;}}if(this.dd){this.dd.useShim=F;}}});this.setAttributeConfig("setSize",{value:((D.setSize===false)?false:true),validator:YAHOO.lang.isBoolean});this.setAttributeConfig("wrap",{writeOnce:true,validator:YAHOO.lang.isBoolean,value:D.wrap||false});this.setAttributeConfig("handles",{writeOnce:true,value:D.handles||["r","b","br"],validator:function(F){if(C.isString(F)&&F.toLowerCase()=="all"){F=["t","b","r","l","bl","br","tl","tr"];}if(!C.isArray(F)){F=F.replace(/, /g,",");F=F.split(",");}this._configs.handles.value=F;}});this.setAttributeConfig("width",{value:D.width||parseInt(this.getStyle("width"),10),validator:YAHOO.lang.isNumber,method:function(F){F=parseInt(F,10);if(F>0){if(this.get("setSize")){this.setStyle("width",F+"px");}this._cache.width=F;this._configs.width.value=F;}}});this.setAttributeConfig("height",{value:D.height||parseInt(this.getStyle("height"),10),validator:YAHOO.lang.isNumber,method:function(F){F=parseInt(F,10);if(F>0){if(this.get("setSize")){this.setStyle("height",F+"px");}this._cache.height=F;this._configs.height.value=F;
+}}});this.setAttributeConfig("minWidth",{value:D.minWidth||15,validator:YAHOO.lang.isNumber});this.setAttributeConfig("minHeight",{value:D.minHeight||15,validator:YAHOO.lang.isNumber});this.setAttributeConfig("maxWidth",{value:D.maxWidth||10000,validator:YAHOO.lang.isNumber});this.setAttributeConfig("maxHeight",{value:D.maxHeight||10000,validator:YAHOO.lang.isNumber});this.setAttributeConfig("minY",{value:D.minY||false});this.setAttributeConfig("minX",{value:D.minX||false});this.setAttributeConfig("maxY",{value:D.maxY||false});this.setAttributeConfig("maxX",{value:D.maxX||false});this.setAttributeConfig("animate",{value:D.animate||false,validator:function(G){var F=true;if(!YAHOO.util.Anim){F=false;}return F;}});this.setAttributeConfig("animateEasing",{value:D.animateEasing||function(){var F=false;if(YAHOO.util.Easing&&YAHOO.util.Easing.easeOut){F=YAHOO.util.Easing.easeOut;}return F;}()});this.setAttributeConfig("animateDuration",{value:D.animateDuration||0.5});this.setAttributeConfig("proxy",{value:D.proxy||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("ratio",{value:D.ratio||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("ghost",{value:D.ghost||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("draggable",{value:D.draggable||false,validator:YAHOO.lang.isBoolean,method:function(F){if(F&&this._wrap&&!this.dd){this._setupDragDrop();}else{if(this.dd){if(F){E.addClass(this._wrap,this.CSS_DRAG);this.dd.DDM.regDragDrop(this.dd,"default");}else{E.removeClass(this._wrap,this.CSS_DRAG);this.dd.unreg();}}}}});this.setAttributeConfig("hover",{value:D.hover||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("hiddenHandles",{value:D.hiddenHandles||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("knobHandles",{value:D.knobHandles||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("xTicks",{value:D.xTicks||false});this.setAttributeConfig("yTicks",{value:D.yTicks||false});this.setAttributeConfig("status",{value:D.status||false,validator:YAHOO.lang.isBoolean});this.setAttributeConfig("autoRatio",{value:D.autoRatio||false,validator:YAHOO.lang.isBoolean});},destroy:function(){for(var F in this._handles){if(C.hasOwnProperty(this._handles,F)){A.purgeElement(this._handles[F]);this._handles[F].parentNode.removeChild(this._handles[F]);}}if(this._proxy){this._proxy.parentNode.removeChild(this._proxy);}if(this._status){this._status.parentNode.removeChild(this._status);}if(this.dd){this.dd.unreg();E.removeClass(this._wrap,this.CSS_DRAG);}if(this._wrap!=this.get("element")){this.setStyle("position",(this._positioned?"absolute":"relative"));this.setStyle("top",E.getStyle(this._wrap,"top"));this.setStyle("left",E.getStyle(this._wrap,"left"));this._wrap.parentNode.replaceChild(this.get("element"),this._wrap);}this.removeClass(this.CSS_RESIZE);delete YAHOO.util.Resize._instances[this.get("id")];for(var D in this){if(C.hasOwnProperty(this,D)){this[D]=null;delete this[D];}}},toString:function(){if(this.get){return"Resize (#"+this.get("id")+")";}return"Resize Utility";}});YAHOO.util.Resize=B;})();YAHOO.register("resize",YAHOO.util.Resize,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/selector/selector-min.js b/Websites/bugs.webkit.org/js/yui/selector/selector-min.js
new file mode 100644
index 0000000..178c68a
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/selector/selector-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+var Y=YAHOO,Y_DOM=YAHOO.util.Dom,EMPTY_ARRAY=[],Y_UA=Y.env.ua,Y_Lang=Y.lang,Y_DOC=document,Y_DOCUMENT_ELEMENT=Y_DOC.documentElement,Y_DOM_inDoc=Y_DOM.inDocument,Y_mix=Y_Lang.augmentObject,Y_guid=Y_DOM.generateId,Y_getDoc=function(a){var b=Y_DOC;if(a){b=(a.nodeType===9)?a:a.ownerDocument||a.document||Y_DOC;}return b;},Y_Array=function(g,d){var c,b,h=d||0;try{return Array.prototype.slice.call(g,h);}catch(f){b=[];c=g.length;for(;h<c;h++){b.push(g[h]);}return b;}},Y_DOM_allById=function(f,a){a=a||Y_DOC;var b=[],c=[],d,e;if(a.querySelectorAll){c=a.querySelectorAll('[id="'+f+'"]');}else{if(a.all){b=a.all(f);if(b){if(b.nodeName){if(b.id===f){c.push(b);b=EMPTY_ARRAY;}else{b=[b];}}if(b.length){for(d=0;e=b[d++];){if(e.id===f||(e.attributes&&e.attributes.id&&e.attributes.id.value===f)){c.push(e);}}}}}else{c=[Y_getDoc(a).getElementById(f)];}}return c;};var COMPARE_DOCUMENT_POSITION="compareDocumentPosition",OWNER_DOCUMENT="ownerDocument",Selector={_foundCache:[],useNative:true,_compare:("sourceIndex" in Y_DOCUMENT_ELEMENT)?function(f,e){var d=f.sourceIndex,c=e.sourceIndex;if(d===c){return 0;}else{if(d>c){return 1;}}return -1;}:(Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION]?function(b,a){if(b[COMPARE_DOCUMENT_POSITION](a)&4){return -1;}else{return 1;}}:function(e,d){var c,a,b;if(e&&d){c=e[OWNER_DOCUMENT].createRange();c.setStart(e,0);a=d[OWNER_DOCUMENT].createRange();a.setStart(d,0);b=c.compareBoundaryPoints(1,a);}return b;}),_sort:function(a){if(a){a=Y_Array(a,0,true);if(a.sort){a.sort(Selector._compare);}}return a;},_deDupe:function(a){var b=[],c,d;for(c=0;(d=a[c++]);){if(!d._found){b[b.length]=d;d._found=true;}}for(c=0;(d=b[c++]);){d._found=null;d.removeAttribute("_found");}return b;},query:function(b,j,k,a){if(j&&typeof j=="string"){j=Y_DOM.get(j);if(!j){return(k)?null:[];}}else{j=j||Y_DOC;}var f=[],c=(Selector.useNative&&Y_DOC.querySelector&&!a),e=[[b,j]],g,l,d,h=(c)?Selector._nativeQuery:Selector._bruteQuery;if(b&&h){if(!a&&(!c||j.tagName)){e=Selector._splitQueries(b,j);}for(d=0;(g=e[d++]);){l=h(g[0],g[1],k);if(!k){l=Y_Array(l,0,true);}if(l){f=f.concat(l);}}if(e.length>1){f=Selector._sort(Selector._deDupe(f));}}return(k)?(f[0]||null):f;},_splitQueries:function(c,f){var b=c.split(","),d=[],g="",e,a;if(f){if(f.tagName){f.id=f.id||Y_guid();g='[id="'+f.id+'"] ';}for(e=0,a=b.length;e<a;++e){c=g+b[e];d.push([c,f]);}}return d;},_nativeQuery:function(a,b,c){if(Y_UA.webkit&&a.indexOf(":checked")>-1&&(Selector.pseudos&&Selector.pseudos.checked)){return Selector.query(a,b,c,true);}try{return b["querySelector"+(c?"":"All")](a);}catch(d){return Selector.query(a,b,c,true);}},filter:function(b,a){var c=[],d,e;if(b&&a){for(d=0;(e=b[d++]);){if(Selector.test(e,a)){c[c.length]=e;}}}else{}return c;},test:function(c,d,k){var g=false,b=d.split(","),a=false,l,o,h,n,f,e,m;if(c&&c.tagName){if(!k&&!Y_DOM_inDoc(c)){l=c.parentNode;if(l){k=l;}else{n=c[OWNER_DOCUMENT].createDocumentFragment();n.appendChild(c);k=n;a=true;}}k=k||c[OWNER_DOCUMENT];if(!c.id){c.id=Y_guid();}for(f=0;(m=b[f++]);){m+='[id="'+c.id+'"]';h=Selector.query(m,k);for(e=0;o=h[e++];){if(o===c){g=true;break;}}if(g){break;}}if(a){n.removeChild(c);}}return g;}};YAHOO.util.Selector=Selector;var PARENT_NODE="parentNode",TAG_NAME="tagName",ATTRIBUTES="attributes",COMBINATOR="combinator",PSEUDOS="pseudos",SelectorCSS2={_reRegExpTokens:/([\^\$\?\[\]\*\+\-\.\(\)\|\\])/,SORT_RESULTS:true,_children:function(e,a){var b=e.children,d,c=[],f,g;if(e.children&&a&&e.children.tags){c=e.children.tags(a);}else{if((!b&&e[TAG_NAME])||(b&&a)){f=b||e.childNodes;b=[];for(d=0;(g=f[d++]);){if(g.tagName){if(!a||a===g.tagName){b.push(g);}}}}}return b||[];},_re:{attr:/(\[[^\]]*\])/g,esc:/\\[:\[\]\(\)#\.\'\>+~"]/gi,pseudos:/(\([^\)]*\))/g},shorthand:{"\\#(-?[_a-z]+[-\\w\\uE000]*)":"[id=$1]","\\.(-?[_a-z]+[-\\w\\uE000]*)":"[className~=$1]"},operators:{"":function(b,a){return !!b.getAttribute(a);},"~=":"(?:^|\\s+){val}(?:\\s+|$)","|=":"^{val}(?:-|$)"},pseudos:{"first-child":function(a){return Selector._children(a[PARENT_NODE])[0]===a;}},_bruteQuery:function(f,j,l){var g=[],a=[],i=Selector._tokenize(f),e=i[i.length-1],k=Y_getDoc(j),c,b,h,d;if(e){b=e.id;h=e.className;d=e.tagName||"*";if(j.getElementsByTagName){if(b&&(j.all||(j.nodeType===9||Y_DOM_inDoc(j)))){a=Y_DOM_allById(b,j);}else{if(h){a=j.getElementsByClassName(h);}else{a=j.getElementsByTagName(d);}}}else{c=j.firstChild;while(c){if(c.tagName){a.push(c);}c=c.nextSilbing||c.firstChild;}}if(a.length){g=Selector._filterNodes(a,i,l);}}return g;},_filterNodes:function(l,f,h){var r=0,q,s=f.length,k=s-1,e=[],o=l[0],v=o,t=Selector.getters,d,p,c,g,a,m,b,u;for(r=0;(v=o=l[r++]);){k=s-1;g=null;testLoop:while(v&&v.tagName){c=f[k];b=c.tests;q=b.length;if(q&&!a){while((u=b[--q])){d=u[1];if(t[u[0]]){m=t[u[0]](v,u[0]);}else{m=v[u[0]];if(m===undefined&&v.getAttribute){m=v.getAttribute(u[0]);}}if((d==="="&&m!==u[2])||(typeof d!=="string"&&d.test&&!d.test(m))||(!d.test&&typeof d==="function"&&!d(v,u[0],u[2]))){if((v=v[g])){while(v&&(!v.tagName||(c.tagName&&c.tagName!==v.tagName))){v=v[g];}}continue testLoop;}}}k--;if(!a&&(p=c.combinator)){g=p.axis;v=v[g];while(v&&!v.tagName){v=v[g];}if(p.direct){g=null;}}else{e.push(o);if(h){return e;}break;}}}o=v=null;return e;},combinators:{" ":{axis:"parentNode"},">":{axis:"parentNode",direct:true},"+":{axis:"previousSibling",direct:true}},_parsers:[{name:ATTRIBUTES,re:/^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,fn:function(d,e){var c=d[2]||"",a=Selector.operators,b=(d[3])?d[3].replace(/\\/g,""):"",f;if((d[1]==="id"&&c==="=")||(d[1]==="className"&&Y_DOCUMENT_ELEMENT.getElementsByClassName&&(c==="~="||c==="="))){e.prefilter=d[1];d[3]=b;e[d[1]]=(d[1]==="id")?d[3]:b;}if(c in a){f=a[c];if(typeof f==="string"){d[3]=b.replace(Selector._reRegExpTokens,"\\$1");f=new RegExp(f.replace("{val}",d[3]));}d[2]=f;}if(!e.last||e.prefilter!==d[1]){return d.slice(1);}}},{name:TAG_NAME,re:/^((?:-?[_a-z]+[\w-]*)|\*)/i,fn:function(b,c){var a=b[1].toUpperCase();c.tagName=a;if(a!=="*"&&(!c.last||c.prefilter)){return[TAG_NAME,"=",a];
+}if(!c.prefilter){c.prefilter="tagName";}}},{name:COMBINATOR,re:/^\s*([>+~]|\s)\s*/,fn:function(a,b){}},{name:PSEUDOS,re:/^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,fn:function(a,b){var c=Selector[PSEUDOS][a[1]];if(c){if(a[2]){a[2]=a[2].replace(/\\/g,"");}return[a[2],c];}else{return false;}}}],_getToken:function(a){return{tagName:null,id:null,className:null,attributes:{},combinator:null,tests:[]};},_tokenize:function(c){c=c||"";c=Selector._replaceShorthand(Y_Lang.trim(c));var b=Selector._getToken(),h=c,g=[],j=false,e,f,d,a;outer:do{j=false;for(d=0;(a=Selector._parsers[d++]);){if((e=a.re.exec(c))){if(a.name!==COMBINATOR){b.selector=c;}c=c.replace(e[0],"");if(!c.length){b.last=true;}if(Selector._attrFilters[e[1]]){e[1]=Selector._attrFilters[e[1]];}f=a.fn(e,b);if(f===false){j=false;break outer;}else{if(f){b.tests.push(f);}}if(!c.length||a.name===COMBINATOR){g.push(b);b=Selector._getToken(b);if(a.name===COMBINATOR){b.combinator=Selector.combinators[e[1]];}}j=true;}}}while(j&&c.length);if(!j||c.length){g=[];}return g;},_replaceShorthand:function(b){var d=Selector.shorthand,c=b.match(Selector._re.esc),e,h,g,f,a;if(c){b=b.replace(Selector._re.esc,"\uE000");}e=b.match(Selector._re.attr);h=b.match(Selector._re.pseudos);if(e){b=b.replace(Selector._re.attr,"\uE001");}if(h){b=b.replace(Selector._re.pseudos,"\uE002");}for(g in d){if(d.hasOwnProperty(g)){b=b.replace(new RegExp(g,"gi"),d[g]);}}if(e){for(f=0,a=e.length;f<a;++f){b=b.replace(/\uE001/,e[f]);}}if(h){for(f=0,a=h.length;f<a;++f){b=b.replace(/\uE002/,h[f]);}}b=b.replace(/\[/g,"\uE003");b=b.replace(/\]/g,"\uE004");b=b.replace(/\(/g,"\uE005");b=b.replace(/\)/g,"\uE006");if(c){for(f=0,a=c.length;f<a;++f){b=b.replace("\uE000",c[f]);}}return b;},_attrFilters:{"class":"className","for":"htmlFor"},getters:{href:function(b,a){return Y_DOM.getAttribute(b,a);}}};Y_mix(Selector,SelectorCSS2,true);Selector.getters.src=Selector.getters.rel=Selector.getters.href;if(Selector.useNative&&Y_DOC.querySelector){Selector.shorthand["\\.([^\\s\\\\(\\[:]*)"]="[class~=$1]";}Selector._reNth=/^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;Selector._getNth=function(d,o,q,h){Selector._reNth.test(o);var m=parseInt(RegExp.$1,10),c=RegExp.$2,j=RegExp.$3,k=parseInt(RegExp.$4,10)||0,p=[],l=Selector._children(d.parentNode,q),f;if(j){m=2;f="+";c="n";k=(j==="odd")?1:0;}else{if(isNaN(m)){m=(c)?1:0;}}if(m===0){if(h){k=l.length-k+1;}if(l[k-1]===d){return true;}else{return false;}}else{if(m<0){h=!!h;m=Math.abs(m);}}if(!h){for(var e=k-1,g=l.length;e<g;e+=m){if(e>=0&&l[e]===d){return true;}}}else{for(var e=l.length-k,g=l.length;e>=0;e-=m){if(e<g&&l[e]===d){return true;}}}return false;};Y_mix(Selector.pseudos,{"root":function(a){return a===a.ownerDocument.documentElement;},"nth-child":function(a,b){return Selector._getNth(a,b);},"nth-last-child":function(a,b){return Selector._getNth(a,b,null,true);},"nth-of-type":function(a,b){return Selector._getNth(a,b,a.tagName);},"nth-last-of-type":function(a,b){return Selector._getNth(a,b,a.tagName,true);},"last-child":function(b){var a=Selector._children(b.parentNode);return a[a.length-1]===b;},"first-of-type":function(a){return Selector._children(a.parentNode,a.tagName)[0]===a;},"last-of-type":function(b){var a=Selector._children(b.parentNode,b.tagName);return a[a.length-1]===b;},"only-child":function(b){var a=Selector._children(b.parentNode);return a.length===1&&a[0]===b;},"only-of-type":function(b){var a=Selector._children(b.parentNode,b.tagName);return a.length===1&&a[0]===b;},"empty":function(a){return a.childNodes.length===0;},"not":function(a,b){return !Selector.test(a,b);},"contains":function(a,b){var c=a.innerText||a.textContent||"";return c.indexOf(b)>-1;},"checked":function(a){return(a.checked===true||a.selected===true);},enabled:function(a){return(a.disabled!==undefined&&!a.disabled);},disabled:function(a){return(a.disabled);}});Y_mix(Selector.operators,{"^=":"^{val}","!=":function(b,a,c){return b[a]!==c;},"$=":"{val}$","*=":"{val}"});Selector.combinators["~"]={axis:"previousSibling"};YAHOO.register("selector",YAHOO.util.Selector,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/slider/slider-min.js b/Websites/bugs.webkit.org/js/yui/slider/slider-min.js
new file mode 100644
index 0000000..a6e60b9
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/slider/slider-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var B=YAHOO.util.Dom.getXY,A=YAHOO.util.Event,D=Array.prototype.slice;function C(G,E,F,H){C.ANIM_AVAIL=(!YAHOO.lang.isUndefined(YAHOO.util.Anim));if(G){this.init(G,E,true);this.initSlider(H);this.initThumb(F);}}YAHOO.lang.augmentObject(C,{getHorizSlider:function(F,G,I,H,E){return new C(F,F,new YAHOO.widget.SliderThumb(G,F,I,H,0,0,E),"horiz");},getVertSlider:function(G,H,E,I,F){return new C(G,G,new YAHOO.widget.SliderThumb(H,G,0,0,E,I,F),"vert");},getSliderRegion:function(G,H,J,I,E,K,F){return new C(G,G,new YAHOO.widget.SliderThumb(H,G,J,I,E,K,F),"region");},SOURCE_UI_EVENT:1,SOURCE_SET_VALUE:2,SOURCE_KEY_EVENT:3,ANIM_AVAIL:false},true);YAHOO.extend(C,YAHOO.util.DragDrop,{_mouseDown:false,dragOnly:true,initSlider:function(E){this.type=E;this.createEvent("change",this);this.createEvent("slideStart",this);this.createEvent("slideEnd",this);this.isTarget=false;this.animate=C.ANIM_AVAIL;this.backgroundEnabled=true;this.tickPause=40;this.enableKeys=true;this.keyIncrement=20;this.moveComplete=true;this.animationDuration=0.2;this.SOURCE_UI_EVENT=1;this.SOURCE_SET_VALUE=2;this.valueChangeSource=0;this._silent=false;this.lastOffset=[0,0];},initThumb:function(F){var E=this;this.thumb=F;F.cacheBetweenDrags=true;if(F._isHoriz&&F.xTicks&&F.xTicks.length){this.tickPause=Math.round(360/F.xTicks.length);}else{if(F.yTicks&&F.yTicks.length){this.tickPause=Math.round(360/F.yTicks.length);}}F.onAvailable=function(){return E.setStartSliderState();};F.onMouseDown=function(){E._mouseDown=true;return E.focus();};F.startDrag=function(){E._slideStart();};F.onDrag=function(){E.fireEvents(true);};F.onMouseUp=function(){E.thumbMouseUp();};},onAvailable:function(){this._bindKeyEvents();},_bindKeyEvents:function(){A.on(this.id,"keydown",this.handleKeyDown,this,true);A.on(this.id,"keypress",this.handleKeyPress,this,true);},handleKeyPress:function(F){if(this.enableKeys){var E=A.getCharCode(F);switch(E){case 37:case 38:case 39:case 40:case 36:case 35:A.preventDefault(F);break;default:}}},handleKeyDown:function(J){if(this.enableKeys){var G=A.getCharCode(J),F=this.thumb,H=this.getXValue(),E=this.getYValue(),I=true;switch(G){case 37:H-=this.keyIncrement;break;case 38:E-=this.keyIncrement;break;case 39:H+=this.keyIncrement;break;case 40:E+=this.keyIncrement;break;case 36:H=F.leftConstraint;E=F.topConstraint;break;case 35:H=F.rightConstraint;E=F.bottomConstraint;break;default:I=false;}if(I){if(F._isRegion){this._setRegionValue(C.SOURCE_KEY_EVENT,H,E,true);}else{this._setValue(C.SOURCE_KEY_EVENT,(F._isHoriz?H:E),true);}A.stopEvent(J);}}},setStartSliderState:function(){this.setThumbCenterPoint();this.baselinePos=B(this.getEl());this.thumb.startOffset=this.thumb.getOffsetFromParent(this.baselinePos);if(this.thumb._isRegion){if(this.deferredSetRegionValue){this._setRegionValue.apply(this,this.deferredSetRegionValue);this.deferredSetRegionValue=null;}else{this.setRegionValue(0,0,true,true,true);}}else{if(this.deferredSetValue){this._setValue.apply(this,this.deferredSetValue);this.deferredSetValue=null;}else{this.setValue(0,true,true,true);}}},setThumbCenterPoint:function(){var E=this.thumb.getEl();if(E){this.thumbCenterPoint={x:parseInt(E.offsetWidth/2,10),y:parseInt(E.offsetHeight/2,10)};}},lock:function(){this.thumb.lock();this.locked=true;},unlock:function(){this.thumb.unlock();this.locked=false;},thumbMouseUp:function(){this._mouseDown=false;if(!this.isLocked()){this.endMove();}},onMouseUp:function(){this._mouseDown=false;if(this.backgroundEnabled&&!this.isLocked()){this.endMove();}},getThumb:function(){return this.thumb;},focus:function(){this.valueChangeSource=C.SOURCE_UI_EVENT;var E=this.getEl();if(E.focus){try{E.focus();}catch(F){}}this.verifyOffset();return !this.isLocked();},onChange:function(E,F){},onSlideStart:function(){},onSlideEnd:function(){},getValue:function(){return this.thumb.getValue();},getXValue:function(){return this.thumb.getXValue();},getYValue:function(){return this.thumb.getYValue();},setValue:function(){var E=D.call(arguments);E.unshift(C.SOURCE_SET_VALUE);return this._setValue.apply(this,E);},_setValue:function(I,L,G,H,E){var F=this.thumb,K,J;if(!F.available){this.deferredSetValue=arguments;return false;}if(this.isLocked()&&!H){return false;}if(isNaN(L)){return false;}if(F._isRegion){return false;}this._silent=E;this.valueChangeSource=I||C.SOURCE_SET_VALUE;F.lastOffset=[L,L];this.verifyOffset();this._slideStart();if(F._isHoriz){K=F.initPageX+L+this.thumbCenterPoint.x;this.moveThumb(K,F.initPageY,G);}else{J=F.initPageY+L+this.thumbCenterPoint.y;this.moveThumb(F.initPageX,J,G);}return true;},setRegionValue:function(){var E=D.call(arguments);E.unshift(C.SOURCE_SET_VALUE);return this._setRegionValue.apply(this,E);},_setRegionValue:function(F,J,H,I,G,K){var L=this.thumb,E,M;if(!L.available){this.deferredSetRegionValue=arguments;return false;}if(this.isLocked()&&!G){return false;}if(isNaN(J)){return false;}if(!L._isRegion){return false;}this._silent=K;this.valueChangeSource=F||C.SOURCE_SET_VALUE;L.lastOffset=[J,H];this.verifyOffset();this._slideStart();E=L.initPageX+J+this.thumbCenterPoint.x;M=L.initPageY+H+this.thumbCenterPoint.y;this.moveThumb(E,M,I);return true;},verifyOffset:function(){var F=B(this.getEl()),E=this.thumb;if(!this.thumbCenterPoint||!this.thumbCenterPoint.x){this.setThumbCenterPoint();}if(F){if(F[0]!=this.baselinePos[0]||F[1]!=this.baselinePos[1]){this.setInitPosition();this.baselinePos=F;E.initPageX=this.initPageX+E.startOffset[0];E.initPageY=this.initPageY+E.startOffset[1];E.deltaSetXY=null;this.resetThumbConstraints();return false;}}return true;},moveThumb:function(K,J,I,G){var L=this.thumb,M=this,F,E,H;if(!L.available){return;}L.setDelta(this.thumbCenterPoint.x,this.thumbCenterPoint.y);E=L.getTargetCoord(K,J);F=[Math.round(E.x),Math.round(E.y)];if(this.animate&&L._graduated&&!I){this.lock();this.curCoord=B(this.thumb.getEl());this.curCoord=[Math.round(this.curCoord[0]),Math.round(this.curCoord[1])];setTimeout(function(){M.moveOneTick(F);},this.tickPause);}else{if(this.animate&&C.ANIM_AVAIL&&!I){this.lock();
+H=new YAHOO.util.Motion(L.id,{points:{to:F}},this.animationDuration,YAHOO.util.Easing.easeOut);H.onComplete.subscribe(function(){M.unlock();if(!M._mouseDown){M.endMove();}});H.animate();}else{L.setDragElPos(K,J);if(!G&&!this._mouseDown){this.endMove();}}}},_slideStart:function(){if(!this._sliding){if(!this._silent){this.onSlideStart();this.fireEvent("slideStart");}this._sliding=true;this.moveComplete=false;}},_slideEnd:function(){if(this._sliding){var E=this._silent;this._sliding=false;this.moveComplete=true;this._silent=false;if(!E){this.onSlideEnd();this.fireEvent("slideEnd");}}},moveOneTick:function(F){var H=this.thumb,G=this,I=null,E,J;if(H._isRegion){I=this._getNextX(this.curCoord,F);E=(I!==null)?I[0]:this.curCoord[0];I=this._getNextY(this.curCoord,F);J=(I!==null)?I[1]:this.curCoord[1];I=E!==this.curCoord[0]||J!==this.curCoord[1]?[E,J]:null;}else{if(H._isHoriz){I=this._getNextX(this.curCoord,F);}else{I=this._getNextY(this.curCoord,F);}}if(I){this.curCoord=I;this.thumb.alignElWithMouse(H.getEl(),I[0]+this.thumbCenterPoint.x,I[1]+this.thumbCenterPoint.y);if(!(I[0]==F[0]&&I[1]==F[1])){setTimeout(function(){G.moveOneTick(F);},this.tickPause);}else{this.unlock();if(!this._mouseDown){this.endMove();}}}else{this.unlock();if(!this._mouseDown){this.endMove();}}},_getNextX:function(E,F){var H=this.thumb,J,G=[],I=null;if(E[0]>F[0]){J=H.tickSize-this.thumbCenterPoint.x;G=H.getTargetCoord(E[0]-J,E[1]);I=[G.x,G.y];}else{if(E[0]<F[0]){J=H.tickSize+this.thumbCenterPoint.x;G=H.getTargetCoord(E[0]+J,E[1]);I=[G.x,G.y];}else{}}return I;},_getNextY:function(E,F){var H=this.thumb,J,G=[],I=null;if(E[1]>F[1]){J=H.tickSize-this.thumbCenterPoint.y;G=H.getTargetCoord(E[0],E[1]-J);I=[G.x,G.y];}else{if(E[1]<F[1]){J=H.tickSize+this.thumbCenterPoint.y;G=H.getTargetCoord(E[0],E[1]+J);I=[G.x,G.y];}else{}}return I;},b4MouseDown:function(E){if(!this.backgroundEnabled){return false;}this.thumb.autoOffset();this.baselinePos=[];},onMouseDown:function(F){if(!this.backgroundEnabled||this.isLocked()){return false;}this._mouseDown=true;var E=A.getPageX(F),G=A.getPageY(F);this.focus();this._slideStart();this.moveThumb(E,G);},onDrag:function(F){if(this.backgroundEnabled&&!this.isLocked()){var E=A.getPageX(F),G=A.getPageY(F);this.moveThumb(E,G,true,true);this.fireEvents();}},endMove:function(){this.unlock();this.fireEvents();this._slideEnd();},resetThumbConstraints:function(){var E=this.thumb;E.setXConstraint(E.leftConstraint,E.rightConstraint,E.xTickSize);E.setYConstraint(E.topConstraint,E.bottomConstraint,E.xTickSize);},fireEvents:function(G){var F=this.thumb,I,H,E;if(!G){F.cachePosition();}if(!this.isLocked()){if(F._isRegion){I=F.getXValue();H=F.getYValue();if(I!=this.previousX||H!=this.previousY){if(!this._silent){this.onChange(I,H);this.fireEvent("change",{x:I,y:H});}}this.previousX=I;this.previousY=H;}else{E=F.getValue();if(E!=this.previousVal){if(!this._silent){this.onChange(E);this.fireEvent("change",E);}}this.previousVal=E;}}},toString:function(){return("Slider ("+this.type+") "+this.id);}});YAHOO.lang.augmentProto(C,YAHOO.util.EventProvider);YAHOO.widget.Slider=C;})();YAHOO.widget.SliderThumb=function(G,B,E,D,A,F,C){if(G){YAHOO.widget.SliderThumb.superclass.constructor.call(this,G,B);this.parentElId=B;}this.isTarget=false;this.tickSize=C;this.maintainOffset=true;this.initSlider(E,D,A,F,C);this.scroll=false;};YAHOO.extend(YAHOO.widget.SliderThumb,YAHOO.util.DD,{startOffset:null,dragOnly:true,_isHoriz:false,_prevVal:0,_graduated:false,getOffsetFromParent0:function(C){var A=YAHOO.util.Dom.getXY(this.getEl()),B=C||YAHOO.util.Dom.getXY(this.parentElId);return[(A[0]-B[0]),(A[1]-B[1])];},getOffsetFromParent:function(H){var A=this.getEl(),E,I,F,B,K,D,C,J,G;if(!this.deltaOffset){I=YAHOO.util.Dom.getXY(A);F=H||YAHOO.util.Dom.getXY(this.parentElId);E=[(I[0]-F[0]),(I[1]-F[1])];B=parseInt(YAHOO.util.Dom.getStyle(A,"left"),10);K=parseInt(YAHOO.util.Dom.getStyle(A,"top"),10);D=B-E[0];C=K-E[1];if(isNaN(D)||isNaN(C)){}else{this.deltaOffset=[D,C];}}else{J=parseInt(YAHOO.util.Dom.getStyle(A,"left"),10);G=parseInt(YAHOO.util.Dom.getStyle(A,"top"),10);E=[J+this.deltaOffset[0],G+this.deltaOffset[1]];}return E;},initSlider:function(D,C,A,E,B){this.initLeft=D;this.initRight=C;this.initUp=A;this.initDown=E;this.setXConstraint(D,C,B);this.setYConstraint(A,E,B);if(B&&B>1){this._graduated=true;}this._isHoriz=(D||C);this._isVert=(A||E);this._isRegion=(this._isHoriz&&this._isVert);},clearTicks:function(){YAHOO.widget.SliderThumb.superclass.clearTicks.call(this);this.tickSize=0;this._graduated=false;},getValue:function(){return(this._isHoriz)?this.getXValue():this.getYValue();},getXValue:function(){if(!this.available){return 0;}var A=this.getOffsetFromParent();if(YAHOO.lang.isNumber(A[0])){this.lastOffset=A;return(A[0]-this.startOffset[0]);}else{return(this.lastOffset[0]-this.startOffset[0]);}},getYValue:function(){if(!this.available){return 0;}var A=this.getOffsetFromParent();if(YAHOO.lang.isNumber(A[1])){this.lastOffset=A;return(A[1]-this.startOffset[1]);}else{return(this.lastOffset[1]-this.startOffset[1]);}},toString:function(){return"SliderThumb "+this.id;},onChange:function(A,B){}});(function(){var A=YAHOO.util.Event,B=YAHOO.widget;function C(I,F,H,D){var G=this,J={min:false,max:false},E,K;this.minSlider=I;this.maxSlider=F;this.activeSlider=I;this.isHoriz=I.thumb._isHoriz;E=this.minSlider.thumb.onMouseDown;K=this.maxSlider.thumb.onMouseDown;this.minSlider.thumb.onMouseDown=function(){G.activeSlider=G.minSlider;E.apply(this,arguments);};this.maxSlider.thumb.onMouseDown=function(){G.activeSlider=G.maxSlider;K.apply(this,arguments);};this.minSlider.thumb.onAvailable=function(){I.setStartSliderState();J.min=true;if(J.max){G.fireEvent("ready",G);}};this.maxSlider.thumb.onAvailable=function(){F.setStartSliderState();J.max=true;if(J.min){G.fireEvent("ready",G);}};I.onMouseDown=F.onMouseDown=function(L){return this.backgroundEnabled&&G._handleMouseDown(L);};I.onDrag=F.onDrag=function(L){G._handleDrag(L);};I.onMouseUp=F.onMouseUp=function(L){G._handleMouseUp(L);
+};I._bindKeyEvents=function(){G._bindKeyEvents(this);};F._bindKeyEvents=function(){};I.subscribe("change",this._handleMinChange,I,this);I.subscribe("slideStart",this._handleSlideStart,I,this);I.subscribe("slideEnd",this._handleSlideEnd,I,this);F.subscribe("change",this._handleMaxChange,F,this);F.subscribe("slideStart",this._handleSlideStart,F,this);F.subscribe("slideEnd",this._handleSlideEnd,F,this);this.createEvent("ready",this);this.createEvent("change",this);this.createEvent("slideStart",this);this.createEvent("slideEnd",this);D=YAHOO.lang.isArray(D)?D:[0,H];D[0]=Math.min(Math.max(parseInt(D[0],10)|0,0),H);D[1]=Math.max(Math.min(parseInt(D[1],10)|0,H),0);if(D[0]>D[1]){D.splice(0,2,D[1],D[0]);}this.minVal=D[0];this.maxVal=D[1];this.minSlider.setValue(this.minVal,true,true,true);this.maxSlider.setValue(this.maxVal,true,true,true);}C.prototype={minVal:-1,maxVal:-1,minRange:0,_handleSlideStart:function(E,D){this.fireEvent("slideStart",D);},_handleSlideEnd:function(E,D){this.fireEvent("slideEnd",D);},_handleDrag:function(D){B.Slider.prototype.onDrag.call(this.activeSlider,D);},_handleMinChange:function(){this.activeSlider=this.minSlider;this.updateValue();},_handleMaxChange:function(){this.activeSlider=this.maxSlider;this.updateValue();},_bindKeyEvents:function(D){A.on(D.id,"keydown",this._handleKeyDown,this,true);A.on(D.id,"keypress",this._handleKeyPress,this,true);},_handleKeyDown:function(D){this.activeSlider.handleKeyDown.apply(this.activeSlider,arguments);},_handleKeyPress:function(D){this.activeSlider.handleKeyPress.apply(this.activeSlider,arguments);},setValues:function(H,K,I,E,J){var F=this.minSlider,M=this.maxSlider,D=F.thumb,L=M.thumb,N=this,G={min:false,max:false};if(D._isHoriz){D.setXConstraint(D.leftConstraint,L.rightConstraint,D.tickSize);L.setXConstraint(D.leftConstraint,L.rightConstraint,L.tickSize);}else{D.setYConstraint(D.topConstraint,L.bottomConstraint,D.tickSize);L.setYConstraint(D.topConstraint,L.bottomConstraint,L.tickSize);}this._oneTimeCallback(F,"slideEnd",function(){G.min=true;if(G.max){N.updateValue(J);setTimeout(function(){N._cleanEvent(F,"slideEnd");N._cleanEvent(M,"slideEnd");},0);}});this._oneTimeCallback(M,"slideEnd",function(){G.max=true;if(G.min){N.updateValue(J);setTimeout(function(){N._cleanEvent(F,"slideEnd");N._cleanEvent(M,"slideEnd");},0);}});F.setValue(H,I,E,false);M.setValue(K,I,E,false);},setMinValue:function(F,H,I,E){var G=this.minSlider,D=this;this.activeSlider=G;D=this;this._oneTimeCallback(G,"slideEnd",function(){D.updateValue(E);setTimeout(function(){D._cleanEvent(G,"slideEnd");},0);});G.setValue(F,H,I);},setMaxValue:function(D,H,I,F){var G=this.maxSlider,E=this;this.activeSlider=G;this._oneTimeCallback(G,"slideEnd",function(){E.updateValue(F);setTimeout(function(){E._cleanEvent(G,"slideEnd");},0);});G.setValue(D,H,I);},updateValue:function(J){var E=this.minSlider.getValue(),K=this.maxSlider.getValue(),F=false,D,M,H,I,L,G;if(E!=this.minVal||K!=this.maxVal){F=true;D=this.minSlider.thumb;M=this.maxSlider.thumb;H=this.isHoriz?"x":"y";G=this.minSlider.thumbCenterPoint[H]+this.maxSlider.thumbCenterPoint[H];I=Math.max(K-G-this.minRange,0);L=Math.min(-E-G-this.minRange,0);if(this.isHoriz){I=Math.min(I,M.rightConstraint);D.setXConstraint(D.leftConstraint,I,D.tickSize);M.setXConstraint(L,M.rightConstraint,M.tickSize);}else{I=Math.min(I,M.bottomConstraint);D.setYConstraint(D.leftConstraint,I,D.tickSize);M.setYConstraint(L,M.bottomConstraint,M.tickSize);}}this.minVal=E;this.maxVal=K;if(F&&!J){this.fireEvent("change",this);}},selectActiveSlider:function(H){var E=this.minSlider,D=this.maxSlider,J=E.isLocked()||!E.backgroundEnabled,G=D.isLocked()||!E.backgroundEnabled,F=YAHOO.util.Event,I;if(J||G){this.activeSlider=J?D:E;}else{if(this.isHoriz){I=F.getPageX(H)-E.thumb.initPageX-E.thumbCenterPoint.x;}else{I=F.getPageY(H)-E.thumb.initPageY-E.thumbCenterPoint.y;}this.activeSlider=I*2>D.getValue()+E.getValue()?D:E;}},_handleMouseDown:function(D){if(!D._handled&&!this.minSlider._sliding&&!this.maxSlider._sliding){D._handled=true;this.selectActiveSlider(D);return B.Slider.prototype.onMouseDown.call(this.activeSlider,D);}else{return false;}},_handleMouseUp:function(D){B.Slider.prototype.onMouseUp.apply(this.activeSlider,arguments);},_oneTimeCallback:function(G,D,F){var E=function(){G.unsubscribe(D,E);F.apply({},arguments);};G.subscribe(D,E);},_cleanEvent:function(K,E){var J,I,D,G,H,F;if(K.__yui_events&&K.events[E]){for(I=K.__yui_events.length;I>=0;--I){if(K.__yui_events[I].type===E){J=K.__yui_events[I];break;}}if(J){H=J.subscribers;F=[];G=0;for(I=0,D=H.length;I<D;++I){if(H[I]){F[G++]=H[I];}}J.subscribers=F;}}}};YAHOO.lang.augmentProto(C,YAHOO.util.EventProvider);B.Slider.getHorizDualSlider=function(H,J,K,G,F,D){var I=new B.SliderThumb(J,H,0,G,0,0,F),E=new B.SliderThumb(K,H,0,G,0,0,F);return new C(new B.Slider(H,H,I,"horiz"),new B.Slider(H,H,E,"horiz"),G,D);};B.Slider.getVertDualSlider=function(H,J,K,G,F,D){var I=new B.SliderThumb(J,H,0,0,0,G,F),E=new B.SliderThumb(K,H,0,0,0,G,F);return new B.DualSlider(new B.Slider(H,H,I,"vert"),new B.Slider(H,H,E,"vert"),G,D);};YAHOO.widget.DualSlider=C;})();YAHOO.register("slider",YAHOO.widget.Slider,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/storage/storage-min.js b/Websites/bugs.webkit.org/js/yui/storage/storage-min.js
new file mode 100644
index 0000000..8cf0932
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/storage/storage-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var g=YAHOO,f=g.util,e=g.lang,c,d,b=/^type=(\w+)/i,a=/&value=(.*)/i;if(!f.Storage){c=function(h){g.log("Exception in YAHOO.util.Storage.?? - must be extended by a storage engine".replace("??",h).replace("??",this.getName?this.getName():"Unknown"),"error");};d=function(h,k,j){var i=this;g.env._id_counter+=1;i._cfg=e.isObject(j)?j:{};i._location=h;i._name=k;i.isReady=false;i.createEvent(d.CE_READY,{scope:i,fireOnce:true});i.createEvent(d.CE_CHANGE,{scope:i});i.subscribe(d.CE_READY,function(){i.isReady=true;});};d.CE_READY="YUIStorageReady";d.CE_CHANGE="YUIStorageChange";d.prototype={CE_READY:d.CE_READY,CE_CHANGE:d.CE_CHANGE,_cfg:"",_name:"",_location:"",length:0,isReady:false,clear:function(){this._clear();this.length=0;},getItem:function(h){g.log("Fetching item at "+h);var i=this._getItem(h);return e.isValue(i)?this._getValue(i):null;},getName:function(){return this._name;},hasKey:function(h){return e.isString(h)&&this._hasKey(h);},key:function(h){g.log("Fetching key at "+h);if(e.isNumber(h)&&-1<h&&this.length>h){var i=this._key(h);if(i){return i;}}throw ("INDEX_SIZE_ERR - Storage.setItem - The provided index ("+h+") is not available");},removeItem:function(j){g.log("removing "+j);var i=this,h;if(i.hasKey(j)){h=i._getItem(j);if(!h){h=null;}i._removeItem(j);i.fireEvent(d.CE_CHANGE,new f.StorageEvent(i,j,h,null,f.StorageEvent.TYPE_REMOVE_ITEM));}else{}},setItem:function(j,k){g.log("SETTING "+k+" to "+j);if(e.isString(j)){var i=this,h=i._getItem(j);if(!h){h=null;}if(i._setItem(j,i._createValue(k))){i.fireEvent(d.CE_CHANGE,new f.StorageEvent(i,j,h,k,i.hasKey(j)?f.StorageEvent.TYPE_UPDATE_ITEM:f.StorageEvent.TYPE_ADD_ITEM));}else{throw ("QUOTA_EXCEEDED_ERROR - Storage.setItem - The choosen storage method ("+i.getName()+") has exceeded capacity");}}else{}},_clear:function(){c("_clear");return"";},_createValue:function(h){var i=(e.isNull(h)||e.isUndefined(h))?(""+h):typeof h;return"type="+i+"&value="+encodeURIComponent(""+h);},_getItem:function(h){c("_getItem");return"";},_getValue:function(h){var j=h.match(b)[1],i=h.match(a)[1];switch(j){case"boolean":return"true"==i;case"number":return parseFloat(i);case"null":return null;default:return decodeURIComponent(i);}},_key:function(h){c("_key");return"";},_hasKey:function(h){return null!==this._getItem(h);},_removeItem:function(h){c("_removeItem");return"";},_setItem:function(h,i){c("_setItem");return"";}};e.augmentProto(d,f.EventProvider);f.Storage=d;}}());(function(){var h=YAHOO.util,e=YAHOO.lang,d={},g=[],f={},b=function(i){return(i&&i.isAvailable())?i:null;},a=function(i,l,k){var j=d[i+l.ENGINE_NAME];if(!j){j=new l(i,k);d[i+l.ENGINE_NAME]=j;}return j;},c=function(i){switch(i){case h.StorageManager.LOCATION_LOCAL:case h.StorageManager.LOCATION_SESSION:return i;default:return h.StorageManager.LOCATION_SESSION;}};h.StorageManager={LOCATION_SESSION:"sessionStorage",LOCATION_LOCAL:"localStorage",get:function(q,k,p){var n=e.isObject(p)?p:{},o=b(f[q]),m,l;if(!o&&!n.force){if(n.order){l=n.order.length;for(m=0;m<l&&!o;m+=1){o=b(n.order[m]);}}if(!o){l=g.length;for(m=0;m<l&&!o;m+=1){o=b(g[m]);}}}if(o){return a(c(k),o,n.engine);}throw ("YAHOO.util.StorageManager.get - No engine available, please include an engine before calling this function.");},getByteSize:function(i){return encodeURIComponent(""+i).length;},register:function(i){if(e.isFunction(i)&&e.isFunction(i.isAvailable)&&e.isString(i.ENGINE_NAME)){f[i.ENGINE_NAME]=i;g.push(i);return true;}return false;}};YAHOO.register("StorageManager",h.SWFStore,{version:"2.9.0",build:"2800"});}());(function(){function a(e,d,c,b,f){this.key=d;this.oldValue=c;this.newValue=b;this.url=window.location.href;this.window=window;this.storageArea=e;this.type=f;}YAHOO.lang.augmentObject(a,{TYPE_ADD_ITEM:"addItem",TYPE_REMOVE_ITEM:"removeItem",TYPE_UPDATE_ITEM:"updateItem"});a.prototype={key:null,newValue:null,oldValue:null,source:null,storageArea:null,type:null,url:null};YAHOO.util.StorageEvent=a;}());(function(){var a=YAHOO.util;a.StorageEngineKeyed=function(){a.StorageEngineKeyed.superclass.constructor.apply(this,arguments);this._keys=[];this._keyMap={};};YAHOO.lang.extend(a.StorageEngineKeyed,a.Storage,{_keys:null,_keyMap:null,_addKey:function(b){if(!this._keyMap.hasOwnProperty(b)){this._keys.push(b);this._keyMap[b]=this.length;this.length=this._keys.length;}},_clear:function(){this._keys=[];this.length=0;},_indexOfKey:function(c){var b=this._keyMap[c];return undefined===b?-1:b;},_key:function(b){return this._keys[b];},_removeItem:function(f){var e=this,c=e._indexOfKey(f),d=e._keys.slice(c+1),b;delete e._keyMap[f];for(b in e._keyMap){if(c<e._keyMap[b]){e._keyMap[b]-=1;}}e._keys.length=c;e._keys=e._keys.concat(d);e.length=e._keys.length;}});}());(function(){var e=YAHOO.util,c=YAHOO.lang,a=function(f){f.begin();},b=function(f){f.commit();},d=function(f,h){var g=this,i=window[f];d.superclass.constructor.call(g,f,d.ENGINE_NAME,h);if(!i.begin){a=function(){};}if(!i.commit){b=function(){};}g.length=i.length;g._driver=i;g.fireEvent(e.Storage.CE_READY);};c.extend(d,e.Storage,{_driver:null,_clear:function(){var g=this,f,h;if(g._driver.clear){g._driver.clear();}else{for(f=g.length;0<=f;f-=1){h=g._key(f);g._removeItem(h);}}},_getItem:function(f){var g=this._driver.getItem(f);return c.isObject(g)?g.value:g;},_key:function(f){return this._driver.key(f);},_removeItem:function(f){var g=this._driver;a(g);g.removeItem(f);b(g);this.length=g.length;},_setItem:function(g,f){var i=this._driver;try{a(i);i.setItem(g,f);b(i);this.length=i.length;return true;}catch(h){return false;}}},true);d.ENGINE_NAME="html5";d.isAvailable=function(){try{return("localStorage" in window)&&window["localStorage"]!==null&&("sessionStorage" in window)&&window["sessionStorage"]!==null;}catch(f){return false;}};e.StorageManager.register(d);e.StorageEngineHTML5=d;}());(function(){var h=YAHOO.util,d=YAHOO.lang,e=9948,g="YUIStorageEngine",b=null,f=encodeURIComponent,a=decodeURIComponent,c=function(l,o){var n=this,p={},k,i,j;c.superclass.constructor.call(n,l,c.ENGINE_NAME,o);
+if(!b){b=google.gears.factory.create(c.GEARS);b.open(window.location.host.replace(/[\/\:\*\?"\<\>\|;,]/g,"")+"-"+c.DATABASE);b.execute("CREATE TABLE IF NOT EXISTS "+g+" (key TEXT, location TEXT, value TEXT)");}k=h.StorageManager.LOCATION_SESSION===n._location;i=h.Cookie.get("sessionKey"+c.ENGINE_NAME);if(!i){b.execute("BEGIN");b.execute("DELETE FROM "+g+' WHERE location="'+f(h.StorageManager.LOCATION_SESSION)+'"');b.execute("COMMIT");}j=b.execute("SELECT key FROM "+g+' WHERE location="'+f(n._location)+'"');p={};try{while(j.isValidRow()){var m=a(j.field(0));if(!p[m]){p[m]=true;n._addKey(m);}j.next();}}finally{j.close();}if(k){h.Cookie.set("sessionKey"+c.ENGINE_NAME,true);}n.fireEvent(h.Storage.CE_READY);};d.extend(c,h.StorageEngineKeyed,{_clear:function(){c.superclass._clear.call(this);b.execute("BEGIN");b.execute("DELETE FROM "+g+' WHERE location="'+f(this._location)+'"');b.execute("COMMIT");},_getItem:function(k){var i=b.execute("SELECT value FROM "+g+' WHERE key="'+f(k)+'" AND location="'+f(this._location)+'"'),j="";try{while(i.isValidRow()){j+=i.field(0);i.next();}}finally{i.close();}return j?a(j):null;},_removeItem:function(i){c.superclass._removeItem.call(this,i);b.execute("BEGIN");b.execute("DELETE FROM "+g+' WHERE key="'+f(i)+'" AND location="'+f(this._location)+'"');b.execute("COMMIT");},_setItem:function(r,k){this._addKey(r);var l=f(r),m=f(this._location),p=f(k),q=[],s=e-(l+m).length,o=0,n;if(s<p.length){for(n=p.length;o<n;o+=s){q.push(p.substr(o,s));}}else{q.push(p);}b.execute("BEGIN");b.execute("DELETE FROM "+g+' WHERE key="'+l+'" AND location="'+m+'"');for(o=0,n=q.length;o<n;o+=1){b.execute("INSERT INTO "+g+' VALUES ("'+l+'", "'+m+'", "'+q[o]+'")');}b.execute("COMMIT");return true;}});h.Event.on("unload",function(){if(b){b.close();}});c.ENGINE_NAME="gears";c.GEARS="beta.database";c.DATABASE="yui.database";c.isAvailable=function(){if(("google" in window)&&("gears" in window.google)){try{google.gears.factory.create(c.GEARS);return true;}catch(i){}}return false;};h.StorageManager.register(c);h.StorageEngineGears=c;}());(function(){var b=YAHOO,i=b.util,g=b.lang,f=i.Dom,j=i.StorageManager,c=215,h=138,d=new RegExp("^("+j.LOCATION_SESSION+"|"+j.LOCATION_LOCAL+")"),e=null,k=function(m,n){return m._location+n;},a=function(n){if(!e){if(!g.isString(n.swfURL)){n.swfURL=l.SWFURL;}if(!n.containerID){var o=document.getElementsByTagName("body")[0],m=o.appendChild(document.createElement("div"));n.containerID=f.generateId(m);}if(!n.attributes){n.attributes={};}if(!n.attributes.flashVars){n.attributes.flashVars={};}n.attributes.flashVars.allowedDomain=document.location.hostname;n.attributes.flashVars.useCompression="true";n.attributes.version=9.115;e=new b.widget.SWF(n.containerID,n.swfURL,n.attributes);e.subscribe("save",function(p){b.log(p.message,"info");});e.subscribe("quotaExceededError",function(p){b.log(p.message,"error");});e.subscribe("inadequateDimensions",function(p){b.log(p.message,"error");});e.subscribe("error",function(p){b.log(p.message,"error");});e.subscribe("securityError",function(p){b.log(p.message,"error");});}},l=function(m,p){var o=this;l.superclass.constructor.call(o,m,l.ENGINE_NAME,p);a(o._cfg);var n=function(){o._swf=e._swf;e.initialized=true;var s=j.LOCATION_SESSION===o._location,r=i.Cookie.get("sessionKey"+l.ENGINE_NAME),u,t,q;for(u=e.callSWF("getLength",[])-1;0<=u;u-=1){t=e.callSWF("getNameAt",[u]);q=s&&(-1<t.indexOf(j.LOCATION_SESSION));if(q&&!r){e.callSWF("removeItem",[t]);}else{if(s===q){o._addKey(t);}}}if(s){i.Cookie.set("sessionKey"+l.ENGINE_NAME,true);}o.fireEvent(i.Storage.CE_READY);};if(e.initialized){n();}else{e.addListener("contentReady",n);}};g.extend(l,i.StorageEngineKeyed,{_swf:null,_clear:function(){for(var m=this._keys.length-1,n;0<=m;m-=1){n=this._keys[m];e.callSWF("removeItem",[n]);}l.superclass._clear.call(this);},_getItem:function(m){var n=k(this,m);return e.callSWF("getValueOf",[n]);},_key:function(m){return l.superclass._key.call(this,m).replace(d,"");},_removeItem:function(m){b.log("removing SWF key: "+m);var n=k(this,m);l.superclass._removeItem.call(this,n);e.callSWF("removeItem",[n]);},_setItem:function(m,p){var n=k(this,m),o;if(e.callSWF("setItem",[n,p])){this._addKey(n);return true;}else{o=f.get(e._id);if(c>f.getStyle(o,"width").replace(/\D+/g,"")){f.setStyle(o,"width",c+"px");}if(h>f.getStyle(o,"height").replace(/\D+/g,"")){f.setStyle(o,"height",h+"px");}b.log("attempting to show settings. are dimensions adequate? "+e.callSWF("hasAdequateDimensions"));return e.callSWF("displaySettings",[]);}}});l.SWFURL="swfstore.swf";l.ENGINE_NAME="swf";l.isAvailable=function(){return(6<=b.env.ua.flash&&b.widget.SWF);};j.register(l);i.StorageEngineSWF=l;}());YAHOO.register("storage",YAHOO.util.Storage,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/stylesheet/stylesheet-min.js b/Websites/bugs.webkit.org/js/yui/stylesheet/stylesheet-min.js
new file mode 100644
index 0000000..fbb918d
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/stylesheet/stylesheet-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var j=document,b=j.createElement("p"),e=b.style,c=YAHOO.lang,m={},i={},f=0,k=("cssFloat" in e)?"cssFloat":"styleFloat",g,a,l;a=("opacity" in e)?function(d){d.opacity="";}:function(d){d.filter="";};e.border="1px solid red";e.border="";l=e.borderLeft?function(d,o){var n;if(o!==k&&o.toLowerCase().indexOf("float")!=-1){o=k;}if(typeof d[o]==="string"){switch(o){case"opacity":case"filter":a(d);break;case"font":d.font=d.fontStyle=d.fontVariant=d.fontWeight=d.fontSize=d.lineHeight=d.fontFamily="";break;default:for(n in d){if(n.indexOf(o)===0){d[n]="";}}}}}:function(d,n){if(n!==k&&n.toLowerCase().indexOf("float")!=-1){n=k;}if(c.isString(d[n])){if(n==="opacity"){a(d);}else{d[n]="";}}};function h(u,o){var x,s,w,v={},n,y,q,t,d,p;if(!(this instanceof h)){return new h(u,o);}s=u&&(u.nodeName?u:j.getElementById(u));if(u&&i[u]){return i[u];}else{if(s&&s.yuiSSID&&i[s.yuiSSID]){return i[s.yuiSSID];}}if(!s||!/^(?:style|link)$/i.test(s.nodeName)){s=j.createElement("style");s.type="text/css";}if(c.isString(u)){if(u.indexOf("{")!=-1){if(s.styleSheet){s.styleSheet.cssText=u;}else{s.appendChild(j.createTextNode(u));}}else{if(!o){o=u;}}}if(!s.parentNode||s.parentNode.nodeName.toLowerCase()!=="head"){x=(s.ownerDocument||j).getElementsByTagName("head")[0];x.appendChild(s);}w=s.sheet||s.styleSheet;n=w&&("cssRules" in w)?"cssRules":"rules";q=("deleteRule" in w)?function(r){w.deleteRule(r);}:function(r){w.removeRule(r);};y=("insertRule" in w)?function(A,z,r){w.insertRule(A+" {"+z+"}",r);}:function(A,z,r){w.addRule(A,z,r);};for(t=w[n].length-1;t>=0;--t){d=w[n][t];p=d.selectorText;if(v[p]){v[p].style.cssText+=";"+d.style.cssText;q(t);}else{v[p]=d;}}s.yuiSSID="yui-stylesheet-"+(f++);h.register(s.yuiSSID,this);if(o){h.register(o,this);}c.augmentObject(this,{getId:function(){return s.yuiSSID;},node:s,enable:function(){w.disabled=false;return this;},disable:function(){w.disabled=true;return this;},isEnabled:function(){return !w.disabled;},set:function(B,A){var D=v[B],C=B.split(/\s*,\s*/),z,r;if(C.length>1){for(z=C.length-1;z>=0;--z){this.set(C[z],A);}return this;}if(!h.isValidSelector(B)){return this;}if(D){D.style.cssText=h.toCssText(A,D.style.cssText);}else{r=w[n].length;A=h.toCssText(A);if(A){y(B,A,r);v[B]=w[n][r];}}return this;},unset:function(B,A){var D=v[B],C=B.split(/\s*,\s*/),r=!A,E,z;if(C.length>1){for(z=C.length-1;z>=0;--z){this.unset(C[z],A);}return this;}if(D){if(!r){if(!c.isArray(A)){A=[A];}e.cssText=D.style.cssText;for(z=A.length-1;z>=0;--z){l(e,A[z]);}if(e.cssText){D.style.cssText=e.cssText;}else{r=true;}}if(r){E=w[n];for(z=E.length-1;z>=0;--z){if(E[z]===D){delete v[B];q(z);break;}}}}return this;},getCssText:function(A){var B,z,r;if(c.isString(A)){B=v[A.split(/\s*,\s*/)[0]];return B?B.style.cssText:null;}else{z=[];for(r in v){if(v.hasOwnProperty(r)){B=v[r];z.push(B.selectorText+" {"+B.style.cssText+"}");}}return z.join("\n");}}},true);}g=function(n,p){var o=n.styleFloat||n.cssFloat||n["float"],r;try{e.cssText=p||"";}catch(d){b=j.createElement("p");e=b.style;e.cssText=p||"";}if(c.isString(n)){e.cssText+=";"+n;}else{if(o&&!n[k]){n=c.merge(n);delete n.styleFloat;delete n.cssFloat;delete n["float"];n[k]=o;}for(r in n){if(n.hasOwnProperty(r)){try{e[r]=c.trim(n[r]);}catch(q){}}}}return e.cssText;};c.augmentObject(h,{toCssText:(("opacity" in e)?g:function(d,n){if(c.isObject(d)&&"opacity" in d){d=c.merge(d,{filter:"alpha(opacity="+(d.opacity*100)+")"});delete d.opacity;}return g(d,n);}),register:function(d,n){return !!(d&&n instanceof h&&!i[d]&&(i[d]=n));},isValidSelector:function(n){var d=false;if(n&&c.isString(n)){if(!m.hasOwnProperty(n)){m[n]=!/\S/.test(n.replace(/\s+|\s*[+~>]\s*/g," ").replace(/([^ ])\[.*?\]/g,"$1").replace(/([^ ])::?[a-z][a-z\-]+[a-z](?:\(.*?\))?/ig,"$1").replace(/(?:^| )[a-z0-6]+/ig," ").replace(/\\./g,"").replace(/[.#]\w[\w\-]*/g,""));}d=m[n];}return d;}},true);YAHOO.util.StyleSheet=h;})();YAHOO.register("stylesheet",YAHOO.util.StyleSheet,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/swf/swf-min.js b/Websites/bugs.webkit.org/js/yui/swf/swf-min.js
new file mode 100644
index 0000000..f91288f
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/swf/swf-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.namespace("widget");(function(){var f=0;var m=YAHOO.env.ua;var p="ShockwaveFlash";var o,q;if(m.gecko||m.webkit||m.opera){if((o=navigator.mimeTypes["application/x-shockwave-flash"])){if((q=o.enabledPlugin)){var j=[];j=q.description.replace(/\s[rd]/g,".").replace(/[A-Za-z\s]+/g,"").split(".");f=j[0]+".";switch((j[2].toString()).length){case 1:f+="00";break;case 2:f+="0";break;}f+=j[2];f=parseFloat(f);}}}else{if(m.ie){try{var i=new ActiveXObject(p+"."+p+".6");i.AllowScriptAccess="always";}catch(r){if(i!=null){f=6;}}if(f==0){try{var l=new ActiveXObject(p+"."+p);var j=[];j=l.GetVariable("$version").replace(/[A-Za-z\s]+/g,"").split(",");f=j[0]+".";switch((j[2].toString()).length){case 1:f+="00";break;case 2:f+="0";break;}f+=j[2];f=parseFloat(f);}catch(r){}}}}m.flash=f;YAHOO.util.SWFDetect={getFlashVersion:function(){return f;},isFlashVersionAtLeast:function(e){return f>=e;},parseFlashVersion:function(e){var u=e;if(YAHOO.lang.isString(e)){var v=e.split(".");if(v.length>2){u=parseInt(v[0]);u+=parseInt(v[2])*0.001;}else{u=parseFloat(e);}}return YAHOO.lang.isNumber(u)?u:null;}};var b=YAHOO.util.Dom,t=YAHOO.util.Event,g=YAHOO.util.SWFDetect,h=YAHOO.lang,a="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000",n="application/x-shockwave-flash",s="10.22",d="http://fpdownload.macromedia.com/pub/flashplayer/update/current/swf/autoUpdater.swf?"+Math.random(),c="YAHOO.widget.SWF.eventHandler",k={align:"",allowfullscreen:"",allownetworking:"",allowscriptaccess:"",base:"",bgcolor:"",devicefont:"",loop:"",menu:"",name:"",play:"",quality:"",salign:"",seamlesstabbing:"",scale:"",swliveconnect:"",tabindex:"",wmode:""};YAHOO.widget.SWF=function(e,K,F){this._queue=this._queue||[];this._events=this._events||{};this._configs=this._configs||{};this._id=b.generateId(null,"yuiswf");if(F.host){this._host=F.host;}var H=this._id;var x=b.get(e);var u=g.parseFlashVersion((F["version"])||s);var E=g.isFlashVersionAtLeast(u);var D=(m.flash>=8);var y=D&&!E&&F["useExpressInstall"];var C=(y)?d:K;var B="<object ";var I,A;var J="YUISwfId="+H+"&YUIBridgeCallback="+c;YAHOO.widget.SWF._instances[H]=this;if(x&&(E||y)&&C){B+='id="'+H+'" ';if(m.ie){B+='classid="'+a+'" ';}else{B+='type="'+n+'" data="'+YAHOO.lang.escapeHTML(C)+'" ';}I="100%";A="100%";B+='width="'+I+'" height="'+A+'">';if(m.ie){B+='<param name="movie" value="'+YAHOO.lang.escapeHTML(C)+'"/>';}for(var v in F.fixedAttributes){if(k.hasOwnProperty(v.toLowerCase())){B+='<param name="'+YAHOO.lang.escapeHTML(v.toLowerCase())+'" value="'+YAHOO.lang.escapeHTML(F.fixedAttributes[v])+'"/>';}}for(var G in F.flashVars){var z=F.flashVars[G];if(h.isString(z)){J+="&"+YAHOO.lang.escapeHTML(G)+"="+YAHOO.lang.escapeHTML(encodeURIComponent(z));}}if(J){B+='<param name="flashVars" value="'+J+'"/>';}B+="</object>";x.innerHTML=B;YAHOO.widget.SWF.superclass.constructor.call(this,b.get(H));this._swf=b.get(H);}};YAHOO.widget.SWF._instances=YAHOO.widget.SWF._instances||{};YAHOO.widget.SWF.eventHandler=function(e,u){YAHOO.widget.SWF._instances[e]._eventHandler(u);};YAHOO.extend(YAHOO.widget.SWF,YAHOO.util.Element,{_eventHandler:function(e){if(e.type=="swfReady"){this.createEvent("swfReady",{fireOnce:true});this.fireEvent("swfReady",e);}else{if(e.type=="log"){}else{if(this._host&&this._host.fireEvent){this._host.fireEvent(e.type,e);}else{this.fireEvent(e.type,e);}}}},callSWF:function(u,e){if(!e){e=[];}if(this._swf[u]){return(this._swf[u].apply(this._swf,e));}else{return null;}},toString:function(){return"SWF "+this._id;}});})();YAHOO.register("swf",YAHOO.widget.SWF,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/swfdetect/swfdetect-min.js b/Websites/bugs.webkit.org/js/yui/swfdetect/swfdetect-min.js
new file mode 100644
index 0000000..02da324
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/swfdetect/swfdetect-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.namespace("util");(function(){var h=0;var f=YAHOO.env.ua;var i="ShockwaveFlash";var b,d;if(f.gecko||f.webkit||f.opera){if((b=navigator.mimeTypes["application/x-shockwave-flash"])){if((d=b.enabledPlugin)){var c=[];c=d.description.replace(/\s[rd]/g,".").replace(/[A-Za-z\s]+/g,"").split(".");h=c[0]+".";switch((c[2].toString()).length){case 1:h+="00";break;case 2:h+="0";break;}h+=c[2];h=parseFloat(h);}}}else{if(f.ie){try{var j=new ActiveXObject(i+"."+i+".6");j.AllowScriptAccess="always";}catch(g){if(j!=null){h=6;}}if(h==0){try{var a=new ActiveXObject(i+"."+i);var c=[];c=a.GetVariable("$version").replace(/[A-Za-z\s]+/g,"").split(",");h=c[0]+".";switch((c[2].toString()).length){case 1:h+="00";break;case 2:h+="0";break;}h+=c[2];h=parseFloat(h);}catch(g){}}}}f.flash=h;YAHOO.util.SWFDetect={getFlashVersion:function(){return h;},isFlashVersionAtLeast:function(e){return h>=e;},parseFlashVersion:function(e){var k=e;if(YAHOO.lang.isString(e)){var l=e.split(".");if(l.length>2){k=parseInt(l[0]);k+=parseInt(l[2])*0.001;}else{k=parseFloat(e);}}return YAHOO.lang.isNumber(k)?k:null;}};})();YAHOO.register("swfdetect",YAHOO.util.SWFDetect,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/swfstore/swfstore-min.js b/Websites/bugs.webkit.org/js/yui/swfstore/swfstore-min.js
new file mode 100644
index 0000000..1efbba6
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/swfstore/swfstore-min.js
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.util.SWFStore=function(a,c,d){var b;var e;c=c.toString();d=d.toString();if(YAHOO.env.ua.ie){b="ie";}else{if(YAHOO.env.ua.gecko){b="gecko";}else{if(YAHOO.env.ua.webkit){b="webkit";}else{if(YAHOO.env.ua.caja){b="caja";}else{if(YAHOO.env.ua.opera){b="opera";}else{b="other";}}}}}if(YAHOO.util.Cookie.get("swfstore")==null||YAHOO.util.Cookie.get("swfstore")=="null"||YAHOO.util.Cookie.get("swfstore")==""){e=Math.round(Math.random()*Math.PI*100000);YAHOO.util.Cookie.set("swfstore",e);}else{e=YAHOO.util.Cookie.get("swfstore");}var f={version:9.115,useExpressInstall:false,fixedAttributes:{allowScriptAccess:"always",allowNetworking:"all",scale:"noScale"},flashVars:{allowedDomain:document.location.hostname,shareData:c,browser:e,useCompression:d}};this.embeddedSWF=new YAHOO.widget.SWF(a,YAHOO.util.SWFStore.SWFURL,f);this.createEvent("error");this.createEvent("quotaExceededError");this.createEvent("securityError");this.createEvent("save");this.createEvent("clear");this.createEvent("pending");this.createEvent("openingDialog");this.createEvent("inadequateDimensions");};YAHOO.extend(YAHOO.util.SWFStore,YAHOO.util.AttributeProvider,{on:function(a,b){this.embeddedSWF.addListener(a,b);},addListener:function(a,b){this.embeddedSWF.addListener(a,b);},toString:function(){return"SWFStore "+this._id;},getShareData:function(){return this.embeddedSWF.callSWF("getShareData");},setShareData:function(a){this.embeddedSWF.callSWF("setShareData",[a]);},hasAdequateDimensions:function(){return this.embeddedSWF.callSWF("hasAdequateDimensions");},getUseCompression:function(){return this.embeddedSWF.callSWF("getUseCompression");},setUseCompression:function(a){this.embeddedSWF.callSWF("setUseCompression",[a]);},setItem:function(a,b){if(typeof b=="string"){b=b.replace(/\\/g,"\\\\");}return this.embeddedSWF.callSWF("setItem",[a,b]);},getValueAt:function(a){return this.embeddedSWF.callSWF("getValueAt",[a]);},getNameAt:function(a){return this.embeddedSWF.callSWF("getNameAt",[a]);},getValueOf:function(a){return this.embeddedSWF.callSWF("getValueOf",[a]);},getTypeOf:function(a){return this.embeddedSWF.callSWF("getTypeOf",[a]);},getTypeAt:function(a){return this.embeddedSWF.callSWF("getTypeAt",[a]);},getItems:function(){return this.embeddedSWF.callSWF("getItems",[]);},removeItem:function(a){return this.embeddedSWF.callSWF("removeItem",[a]);},removeItemAt:function(a){return this.embeddedSWF.callSWF("removeItemAt",[a]);},getLength:function(){return this.embeddedSWF.callSWF("getLength",[]);},clear:function(){return this.embeddedSWF.callSWF("clear",[]);},calculateCurrentSize:function(){return this.embeddedSWF.callSWF("calculateCurrentSize",[]);},getModificationDate:function(){return this.embeddedSWF.callSWF("getModificationDate",[]);},setSize:function(b){var a=this.embeddedSWF.callSWF("setSize",[b]);return a;},displaySettings:function(){this.embeddedSWF.callSWF("displaySettings",[]);}});YAHOO.util.SWFStore.SWFURL="swfstore.swf";YAHOO.register("swfstore",YAHOO.util.SWFStore,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/swfstore/swfstore.swf b/Websites/bugs.webkit.org/js/yui/swfstore/swfstore.swf
new file mode 100644
index 0000000..b2f5cd0
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/swfstore/swfstore.swf
Binary files differ
diff --git a/Websites/bugs.webkit.org/js/yui/tabview/tabview-min.js b/Websites/bugs.webkit.org/js/yui/tabview/tabview-min.js
new file mode 100644
index 0000000..9df66f2
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/tabview/tabview-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var b=YAHOO.util,c=b.Dom,i=b.Event,g=window.document,k="active",d="activeIndex",f="activeTab",e="disabled",a="contentEl",h="element",j=function(m,l){l=l||{};if(arguments.length==1&&!YAHOO.lang.isString(m)&&!m.nodeName){l=m;m=l.element||null;}if(!m&&!l.element){m=this._createTabViewElement(l);}j.superclass.constructor.call(this,m,l);};YAHOO.extend(j,b.Element,{CLASSNAME:"yui-navset",TAB_PARENT_CLASSNAME:"yui-nav",CONTENT_PARENT_CLASSNAME:"yui-content",_tabParent:null,_contentParent:null,addTab:function(n,o){var p=this.get("tabs"),s=this._tabParent,q=this._contentParent,l=n.get(h),m=n.get(a),t=this.get(d),r;if(!p){this._queue[this._queue.length]=["addTab",arguments];return false;}r=this.getTab(o);o=(o===undefined)?p.length:o;p.splice(o,0,n);if(r){s.insertBefore(l,r.get(h));if(m){q.appendChild(m);}}else{s.appendChild(l);if(m){q.appendChild(m);}}if(!n.get(k)){n.set("contentVisible",false,true);if(o<=t){this.set(d,t+1,true);}}else{this.set(f,n,true);this.set("activeIndex",o,true);}this._initTabEvents(n);},_initTabEvents:function(l){l.addListener(l.get("activationEvent"),l._onActivate,this,l);l.addListener("activationEventChange",l._onActivationEventChange,this,l);},_removeTabEvents:function(l){l.removeListener(l.get("activationEvent"),l._onActivate,this,l);l.removeListener("activationEventChange",l._onActivationEventChange,this,l);},DOMEventHandler:function(q){var r=i.getTarget(q),t=this._tabParent,s=this.get("tabs"),n,m,l;if(c.isAncestor(t,r)){for(var o=0,p=s.length;o<p;o++){m=s[o].get(h);l=s[o].get(a);if(r==m||c.isAncestor(m,r)){n=s[o];break;}}if(n){n.fireEvent(q.type,q);}}},getTab:function(l){return this.get("tabs")[l];},getTabIndex:function(p){var m=null,o=this.get("tabs");for(var n=0,l=o.length;n<l;++n){if(p==o[n]){m=n;break;}}return m;},removeTab:function(o){var n=this.get("tabs").length,l=this.get(d),m=this.getTabIndex(o);if(o===this.get(f)){if(n>1){if(m+1===n){this.set(d,m-1);}else{this.set(d,m+1);}}else{this.set(f,null);}}else{if(m<l){this.set(d,l-1,true);}}this._removeTabEvents(o);this._tabParent.removeChild(o.get(h));this._contentParent.removeChild(o.get(a));this._configs.tabs.value.splice(m,1);o.fireEvent("remove",{type:"remove",tabview:this});},toString:function(){var l=this.get("id")||this.get("tagName");return"TabView "+l;},contentTransition:function(m,l){if(m){m.set("contentVisible",true);}if(l){l.set("contentVisible",false);}},initAttributes:function(l){j.superclass.initAttributes.call(this,l);if(!l.orientation){l.orientation="top";}var n=this.get(h);if(!this.hasClass(this.CLASSNAME)){this.addClass(this.CLASSNAME);}this.setAttributeConfig("tabs",{value:[],readOnly:true});this._tabParent=this.getElementsByClassName(this.TAB_PARENT_CLASSNAME,"ul")[0]||this._createTabParent();this._contentParent=this.getElementsByClassName(this.CONTENT_PARENT_CLASSNAME,"div")[0]||this._createContentParent();this.setAttributeConfig("orientation",{value:l.orientation,method:function(o){var p=this.get("orientation");this.addClass("yui-navset-"+o);if(p!=o){this.removeClass("yui-navset-"+p);}if(o==="bottom"){this.appendChild(this._tabParent);}}});this.setAttributeConfig(d,{value:l.activeIndex,validator:function(q){var o=true,p;if(q){p=this.getTab(q);if(p&&p.get(e)){o=false;}}return o;}});this.setAttributeConfig(f,{value:l[f],method:function(p){var o=this.get(f);if(p){p.set(k,true);}if(o&&o!==p){o.set(k,false);}if(o&&p!==o){this.contentTransition(p,o);}else{if(p){p.set("contentVisible",true);}}},validator:function(p){var o=true;if(p&&p.get(e)){o=false;}return o;}});this.on("activeTabChange",this._onActiveTabChange);this.on("activeIndexChange",this._onActiveIndexChange);if(this._tabParent){this._initTabs();}this.DOM_EVENTS.submit=false;this.DOM_EVENTS.focus=false;this.DOM_EVENTS.blur=false;this.DOM_EVENTS.change=false;for(var m in this.DOM_EVENTS){if(YAHOO.lang.hasOwnProperty(this.DOM_EVENTS,m)){this.addListener.call(this,m,this.DOMEventHandler);}}},deselectTab:function(l){if(this.getTab(l)===this.get(f)){this.set(f,null);}},selectTab:function(l){this.set(f,this.getTab(l));},_onActiveTabChange:function(n){var l=this.get(d),m=this.getTabIndex(n.newValue);if(l!==m){if(!(this.set(d,m))){this.set(f,n.prevValue);}}},_onActiveIndexChange:function(l){if(l.newValue!==this.getTabIndex(this.get(f))){if(!(this.set(f,this.getTab(l.newValue)))){this.set(d,l.prevValue);}}},_initTabs:function(){var q=c.getChildren(this._tabParent),o=c.getChildren(this._contentParent),n=this.get(d),r,m,s;for(var p=0,l=q.length;p<l;++p){m={};if(o[p]){m.contentEl=o[p];}r=new YAHOO.widget.Tab(q[p],m);this.addTab(r);if(r.hasClass(r.ACTIVE_CLASSNAME)){s=r;}}if(n!=undefined){this.set(f,this.getTab(n));}else{this._configs[f].value=s;this._configs[d].value=this.getTabIndex(s);}},_createTabViewElement:function(l){var m=g.createElement("div");if(this.CLASSNAME){m.className=this.CLASSNAME;}return m;},_createTabParent:function(l){var m=g.createElement("ul");if(this.TAB_PARENT_CLASSNAME){m.className=this.TAB_PARENT_CLASSNAME;}this.get(h).appendChild(m);return m;},_createContentParent:function(l){var m=g.createElement("div");if(this.CONTENT_PARENT_CLASSNAME){m.className=this.CONTENT_PARENT_CLASSNAME;}this.get(h).appendChild(m);return m;}});YAHOO.widget.TabView=j;})();(function(){var d=YAHOO.util,i=d.Dom,l=YAHOO.lang,m="activeTab",j="label",g="labelEl",q="content",c="contentEl",o="element",p="cacheData",b="dataSrc",h="dataLoaded",a="dataTimeout",n="loadMethod",f="postData",k="disabled",e=function(s,r){r=r||{};if(arguments.length==1&&!l.isString(s)&&!s.nodeName){r=s;s=r.element;}if(!s&&!r.element){s=this._createTabElement(r);}this.loadHandler={success:function(t){this.set(q,t.responseText);},failure:function(t){}};e.superclass.constructor.call(this,s,r);this.DOM_EVENTS={};};YAHOO.extend(e,YAHOO.util.Element,{LABEL_TAGNAME:"em",ACTIVE_CLASSNAME:"selected",HIDDEN_CLASSNAME:"yui-hidden",ACTIVE_TITLE:"active",DISABLED_CLASSNAME:k,LOADING_CLASSNAME:"loading",dataConnection:null,loadHandler:null,_loading:false,toString:function(){var r=this.get(o),s=r.id||r.tagName;
+return"Tab "+s;},initAttributes:function(r){r=r||{};e.superclass.initAttributes.call(this,r);this.setAttributeConfig("activationEvent",{value:r.activationEvent||"click"});this.setAttributeConfig(g,{value:r[g]||this._getLabelEl(),method:function(s){s=i.get(s);var t=this.get(g);if(t){if(t==s){return false;}t.parentNode.replaceChild(s,t);this.set(j,s.innerHTML);}}});this.setAttributeConfig(j,{value:r.label||this._getLabel(),method:function(t){var s=this.get(g);if(!s){this.set(g,this._createLabelEl());}s.innerHTML=t;}});this.setAttributeConfig(c,{value:r[c]||document.createElement("div"),method:function(s){s=i.get(s);var t=this.get(c);if(t){if(t===s){return false;}if(!this.get("selected")){i.addClass(s,this.HIDDEN_CLASSNAME);}t.parentNode.replaceChild(s,t);this.set(q,s.innerHTML);}}});this.setAttributeConfig(q,{value:r[q]||this.get(c).innerHTML,method:function(s){this.get(c).innerHTML=s;}});this.setAttributeConfig(b,{value:r.dataSrc});this.setAttributeConfig(p,{value:r.cacheData||false,validator:l.isBoolean});this.setAttributeConfig(n,{value:r.loadMethod||"GET",validator:l.isString});this.setAttributeConfig(h,{value:false,validator:l.isBoolean,writeOnce:true});this.setAttributeConfig(a,{value:r.dataTimeout||null,validator:l.isNumber});this.setAttributeConfig(f,{value:r.postData||null});this.setAttributeConfig("active",{value:r.active||this.hasClass(this.ACTIVE_CLASSNAME),method:function(s){if(s===true){this.addClass(this.ACTIVE_CLASSNAME);this.set("title",this.ACTIVE_TITLE);}else{this.removeClass(this.ACTIVE_CLASSNAME);this.set("title","");}},validator:function(s){return l.isBoolean(s)&&!this.get(k);}});this.setAttributeConfig(k,{value:r.disabled||this.hasClass(this.DISABLED_CLASSNAME),method:function(s){if(s===true){this.addClass(this.DISABLED_CLASSNAME);}else{this.removeClass(this.DISABLED_CLASSNAME);}},validator:l.isBoolean});this.setAttributeConfig("href",{value:r.href||this.getElementsByTagName("a")[0].getAttribute("href",2)||"#",method:function(s){this.getElementsByTagName("a")[0].href=s;},validator:l.isString});this.setAttributeConfig("contentVisible",{value:r.contentVisible,method:function(s){if(s){i.removeClass(this.get(c),this.HIDDEN_CLASSNAME);if(this.get(b)){if(!this._loading&&!(this.get(h)&&this.get(p))){this._dataConnect();}}}else{i.addClass(this.get(c),this.HIDDEN_CLASSNAME);}},validator:l.isBoolean});},_dataConnect:function(){if(!d.Connect){return false;}i.addClass(this.get(c).parentNode,this.LOADING_CLASSNAME);this._loading=true;this.dataConnection=d.Connect.asyncRequest(this.get(n),this.get(b),{success:function(r){this.loadHandler.success.call(this,r);this.set(h,true);this.dataConnection=null;i.removeClass(this.get(c).parentNode,this.LOADING_CLASSNAME);this._loading=false;},failure:function(r){this.loadHandler.failure.call(this,r);this.dataConnection=null;i.removeClass(this.get(c).parentNode,this.LOADING_CLASSNAME);this._loading=false;},scope:this,timeout:this.get(a)},this.get(f));},_createTabElement:function(r){var v=document.createElement("li"),s=document.createElement("a"),u=r.label||null,t=r.labelEl||null;s.href=r.href||"#";v.appendChild(s);if(t){if(!u){u=this._getLabel();}}else{t=this._createLabelEl();}s.appendChild(t);return v;},_getLabelEl:function(){return this.getElementsByTagName(this.LABEL_TAGNAME)[0];},_createLabelEl:function(){var r=document.createElement(this.LABEL_TAGNAME);return r;},_getLabel:function(){var r=this.get(g);if(!r){return undefined;}return r.innerHTML;},_onActivate:function(u,t){var s=this,r=false;d.Event.preventDefault(u);if(s===t.get(m)){r=true;}t.set(m,s,r);},_onActivationEventChange:function(s){var r=this;if(s.prevValue!=s.newValue){r.removeListener(s.prevValue,r._onActivate);r.addListener(s.newValue,r._onActivate,this,r);}}});YAHOO.widget.Tab=e;})();YAHOO.register("tabview",YAHOO.widget.TabView,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/treeview/treeview-min.js b/Websites/bugs.webkit.org/js/yui/treeview/treeview-min.js
new file mode 100644
index 0000000..f8cb593
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/treeview/treeview-min.js
@@ -0,0 +1,12 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+(function(){var d=YAHOO.util.Dom,b=YAHOO.util.Event,f=YAHOO.lang,e=YAHOO.widget;YAHOO.widget.TreeView=function(h,g){if(h){this.init(h);}if(g){this.buildTreeFromObject(g);}else{if(f.trim(this._el.innerHTML)){this.buildTreeFromMarkup(h);}}};var c=e.TreeView;c.prototype={id:null,_el:null,_nodes:null,locked:false,_expandAnim:null,_collapseAnim:null,_animCount:0,maxAnim:2,_hasDblClickSubscriber:false,_dblClickTimer:null,currentFocus:null,singleNodeHighlight:false,_currentlyHighlighted:null,setExpandAnim:function(g){this._expandAnim=(e.TVAnim.isValid(g))?g:null;},setCollapseAnim:function(g){this._collapseAnim=(e.TVAnim.isValid(g))?g:null;},animateExpand:function(i,j){if(this._expandAnim&&this._animCount<this.maxAnim){var g=this;var h=e.TVAnim.getAnim(this._expandAnim,i,function(){g.expandComplete(j);});if(h){++this._animCount;this.fireEvent("animStart",{"node":j,"type":"expand"});h.animate();}return true;}return false;},animateCollapse:function(i,j){if(this._collapseAnim&&this._animCount<this.maxAnim){var g=this;var h=e.TVAnim.getAnim(this._collapseAnim,i,function(){g.collapseComplete(j);});if(h){++this._animCount;this.fireEvent("animStart",{"node":j,"type":"collapse"});h.animate();}return true;}return false;},expandComplete:function(g){--this._animCount;this.fireEvent("animComplete",{"node":g,"type":"expand"});},collapseComplete:function(g){--this._animCount;this.fireEvent("animComplete",{"node":g,"type":"collapse"});},init:function(i){this._el=d.get(i);this.id=d.generateId(this._el,"yui-tv-auto-id-");this.createEvent("animStart",this);this.createEvent("animComplete",this);this.createEvent("collapse",this);this.createEvent("collapseComplete",this);this.createEvent("expand",this);this.createEvent("expandComplete",this);this.createEvent("enterKeyPressed",this);this.createEvent("clickEvent",this);this.createEvent("focusChanged",this);var g=this;this.createEvent("dblClickEvent",{scope:this,onSubscribeCallback:function(){g._hasDblClickSubscriber=true;}});this.createEvent("labelClick",this);this.createEvent("highlightEvent",this);this._nodes=[];c.trees[this.id]=this;this.root=new e.RootNode(this);var h=e.LogWriter;if(this._initEditor){this._initEditor();}},buildTreeFromObject:function(g){var h=function(q,n){var m,r,l,k,p,j,o;for(m=0;m<n.length;m++){r=n[m];if(f.isString(r)){l=new e.TextNode(r,q);}else{if(f.isObject(r)){k=r.children;delete r.children;p=r.type||"text";delete r.type;switch(f.isString(p)&&p.toLowerCase()){case"text":l=new e.TextNode(r,q);break;case"menu":l=new e.MenuNode(r,q);break;case"html":l=new e.HTMLNode(r,q);break;default:if(f.isString(p)){j=e[p];}else{j=p;}if(f.isObject(j)){for(o=j;o&&o!==e.Node;o=o.superclass.constructor){}if(o){l=new j(r,q);}else{}}else{}}if(k){h(l,k);}}else{}}}};if(!f.isArray(g)){g=[g];}h(this.root,g);},buildTreeFromMarkup:function(i){var h=function(j){var n,q,m=[],l={},k,o;for(n=d.getFirstChild(j);n;n=d.getNextSibling(n)){switch(n.tagName.toUpperCase()){case"LI":k="";l={expanded:d.hasClass(n,"expanded"),title:n.title||n.alt||null,className:f.trim(n.className.replace(/\bexpanded\b/,""))||null};q=n.firstChild;if(q.nodeType==3){k=f.trim(q.nodeValue.replace(/[\n\t\r]*/g,""));if(k){l.type="text";l.label=k;}else{q=d.getNextSibling(q);}}if(!k){if(q.tagName.toUpperCase()=="A"){l.type="text";l.label=q.innerHTML;l.href=q.href;l.target=q.target;l.title=q.title||q.alt||l.title;}else{l.type="html";var p=document.createElement("div");p.appendChild(q.cloneNode(true));l.html=p.innerHTML;l.hasIcon=true;}}q=d.getNextSibling(q);switch(q&&q.tagName.toUpperCase()){case"UL":case"OL":l.children=h(q);break;}if(YAHOO.lang.JSON){o=n.getAttribute("yuiConfig");if(o){o=YAHOO.lang.JSON.parse(o);l=YAHOO.lang.merge(l,o);}}m.push(l);break;case"UL":case"OL":l={type:"text",label:"",children:h(q)};m.push(l);break;}}return m;};var g=d.getChildrenBy(d.get(i),function(k){var j=k.tagName.toUpperCase();return j=="UL"||j=="OL";});if(g.length){this.buildTreeFromObject(h(g[0]));}else{}},_getEventTargetTdEl:function(h){var i=b.getTarget(h);while(i&&!(i.tagName.toUpperCase()=="TD"&&d.hasClass(i.parentNode,"ygtvrow"))){i=d.getAncestorByTagName(i,"td");}if(f.isNull(i)){return null;}if(/\bygtv(blank)?depthcell/.test(i.className)){return null;}if(i.id){var g=i.id.match(/\bygtv([^\d]*)(.*)/);if(g&&g[2]&&this._nodes[g[2]]){return i;}}return null;},_onClickEvent:function(j){var h=this,l=this._getEventTargetTdEl(j),i,k,g=function(m){i.focus();if(m||!i.href){i.toggle();try{b.preventDefault(j);}catch(n){}}};if(!l){return;}i=this.getNodeByElement(l);if(!i){return;}k=b.getTarget(j);if(d.hasClass(k,i.labelStyle)||d.getAncestorByClassName(k,i.labelStyle)){this.fireEvent("labelClick",i);}if(this._closeEditor){this._closeEditor(false);}if(/\bygtv[tl][mp]h?h?/.test(l.className)){g(true);}else{if(this._dblClickTimer){window.clearTimeout(this._dblClickTimer);this._dblClickTimer=null;}else{if(this._hasDblClickSubscriber){this._dblClickTimer=window.setTimeout(function(){h._dblClickTimer=null;if(h.fireEvent("clickEvent",{event:j,node:i})!==false){g();}},200);}else{if(h.fireEvent("clickEvent",{event:j,node:i})!==false){g();}}}}},_onDblClickEvent:function(g){if(!this._hasDblClickSubscriber){return;}var h=this._getEventTargetTdEl(g);if(!h){return;}if(!(/\bygtv[tl][mp]h?h?/.test(h.className))){this.fireEvent("dblClickEvent",{event:g,node:this.getNodeByElement(h)});if(this._dblClickTimer){window.clearTimeout(this._dblClickTimer);this._dblClickTimer=null;}}},_onMouseOverEvent:function(g){var h;if((h=this._getEventTargetTdEl(g))&&(h=this.getNodeByElement(h))&&(h=h.getToggleEl())){h.className=h.className.replace(/\bygtv([lt])([mp])\b/gi,"ygtv$1$2h");}},_onMouseOutEvent:function(g){var h;if((h=this._getEventTargetTdEl(g))&&(h=this.getNodeByElement(h))&&(h=h.getToggleEl())){h.className=h.className.replace(/\bygtv([lt])([mp])h\b/gi,"ygtv$1$2");}},_onKeyDownEvent:function(l){var n=b.getTarget(l),k=this.getNodeByElement(n),j=k,g=YAHOO.util.KeyListener.KEY;switch(l.keyCode){case g.UP:do{if(j.previousSibling){j=j.previousSibling;}else{j=j.parent;
+}}while(j&&!j._canHaveFocus());if(j){j.focus();}b.preventDefault(l);break;case g.DOWN:do{if(j.nextSibling){j=j.nextSibling;}else{j.expand();j=(j.children.length||null)&&j.children[0];}}while(j&&!j._canHaveFocus);if(j){j.focus();}b.preventDefault(l);break;case g.LEFT:do{if(j.parent){j=j.parent;}else{j=j.previousSibling;}}while(j&&!j._canHaveFocus());if(j){j.focus();}b.preventDefault(l);break;case g.RIGHT:var i=this,m,h=function(o){i.unsubscribe("expandComplete",h);m(o);};m=function(o){do{if(o.isDynamic()&&!o.childrenRendered){i.subscribe("expandComplete",h);o.expand();o=null;break;}else{o.expand();if(o.children.length){o=o.children[0];}else{o=o.nextSibling;}}}while(o&&!o._canHaveFocus());if(o){o.focus();}};m(j);b.preventDefault(l);break;case g.ENTER:if(k.href){if(k.target){window.open(k.href,k.target);}else{window.location(k.href);}}else{k.toggle();}this.fireEvent("enterKeyPressed",k);b.preventDefault(l);break;case g.HOME:j=this.getRoot();if(j.children.length){j=j.children[0];}if(j._canHaveFocus()){j.focus();}b.preventDefault(l);break;case g.END:j=j.parent.children;j=j[j.length-1];if(j._canHaveFocus()){j.focus();}b.preventDefault(l);break;case 107:case 187:if(l.shiftKey){k.parent.expandAll();}else{k.expand();}break;case 109:case 189:if(l.shiftKey){k.parent.collapseAll();}else{k.collapse();}break;default:break;}},render:function(){var g=this.root.getHtml(),h=this.getEl();h.innerHTML=g;if(!this._hasEvents){b.on(h,"click",this._onClickEvent,this,true);b.on(h,"dblclick",this._onDblClickEvent,this,true);b.on(h,"mouseover",this._onMouseOverEvent,this,true);b.on(h,"mouseout",this._onMouseOutEvent,this,true);b.on(h,"keydown",this._onKeyDownEvent,this,true);}this._hasEvents=true;},getEl:function(){if(!this._el){this._el=d.get(this.id);}return this._el;},regNode:function(g){this._nodes[g.index]=g;},getRoot:function(){return this.root;},setDynamicLoad:function(g,h){this.root.setDynamicLoad(g,h);},expandAll:function(){if(!this.locked){this.root.expandAll();}},collapseAll:function(){if(!this.locked){this.root.collapseAll();}},getNodeByIndex:function(h){var g=this._nodes[h];return(g)?g:null;},getNodeByProperty:function(j,h){for(var g in this._nodes){if(this._nodes.hasOwnProperty(g)){var k=this._nodes[g];if((j in k&&k[j]==h)||(k.data&&h==k.data[j])){return k;}}}return null;},getNodesByProperty:function(k,j){var g=[];for(var h in this._nodes){if(this._nodes.hasOwnProperty(h)){var l=this._nodes[h];if((k in l&&l[k]==j)||(l.data&&j==l.data[k])){g.push(l);}}}return(g.length)?g:null;},getNodesBy:function(j){var g=[];for(var h in this._nodes){if(this._nodes.hasOwnProperty(h)){var k=this._nodes[h];if(j(k)){g.push(k);}}}return(g.length)?g:null;},getNodeByElement:function(i){var j=i,g,h=/ygtv([^\d]*)(.*)/;do{if(j&&j.id){g=j.id.match(h);if(g&&g[2]){return this.getNodeByIndex(g[2]);}}j=j.parentNode;if(!j||!j.tagName){break;}}while(j.id!==this.id&&j.tagName.toLowerCase()!=="body");return null;},getHighlightedNode:function(){return this._currentlyHighlighted;},removeNode:function(h,g){if(h.isRoot()){return false;}var i=h.parent;if(i.parent){i=i.parent;}this._deleteNode(h);if(g&&i&&i.childrenRendered){i.refresh();}return true;},_removeChildren_animComplete:function(g){this.unsubscribe(this._removeChildren_animComplete);this.removeChildren(g.node);},removeChildren:function(g){if(g.expanded){if(this._collapseAnim){this.subscribe("animComplete",this._removeChildren_animComplete,this,true);e.Node.prototype.collapse.call(g);return;}g.collapse();}while(g.children.length){this._deleteNode(g.children[0]);}if(g.isRoot()){e.Node.prototype.expand.call(g);}g.childrenRendered=false;g.dynamicLoadComplete=false;g.updateIcon();},_deleteNode:function(g){this.removeChildren(g);this.popNode(g);},popNode:function(k){var l=k.parent;var h=[];for(var j=0,g=l.children.length;j<g;++j){if(l.children[j]!=k){h[h.length]=l.children[j];}}l.children=h;l.childrenRendered=false;if(k.previousSibling){k.previousSibling.nextSibling=k.nextSibling;}if(k.nextSibling){k.nextSibling.previousSibling=k.previousSibling;}if(this.currentFocus==k){this.currentFocus=null;}if(this._currentlyHighlighted==k){this._currentlyHighlighted=null;}k.parent=null;k.previousSibling=null;k.nextSibling=null;k.tree=null;delete this._nodes[k.index];},destroy:function(){if(this._destroyEditor){this._destroyEditor();}var h=this.getEl();b.removeListener(h,"click");b.removeListener(h,"dblclick");b.removeListener(h,"mouseover");b.removeListener(h,"mouseout");b.removeListener(h,"keydown");for(var g=0;g<this._nodes.length;g++){var j=this._nodes[g];if(j&&j.destroy){j.destroy();}}h.innerHTML="";this._hasEvents=false;},toString:function(){return"TreeView "+this.id;},getNodeCount:function(){return this.getRoot().getNodeCount();},getTreeDefinition:function(){return this.getRoot().getNodeDefinition();},onExpand:function(g){},onCollapse:function(g){},setNodesProperty:function(g,i,h){this.root.setNodesProperty(g,i);if(h){this.root.refresh();}},onEventToggleHighlight:function(h){var g;if("node" in h&&h.node instanceof e.Node){g=h.node;}else{if(h instanceof e.Node){g=h;}else{return false;}}g.toggleHighlight();return false;}};var a=c.prototype;a.draw=a.render;YAHOO.augment(c,YAHOO.util.EventProvider);c.nodeCount=0;c.trees=[];c.getTree=function(h){var g=c.trees[h];return(g)?g:null;};c.getNode=function(h,i){var g=c.getTree(h);return(g)?g.getNodeByIndex(i):null;};c.FOCUS_CLASS_NAME="ygtvfocus";})();(function(){var b=YAHOO.util.Dom,c=YAHOO.lang,a=YAHOO.util.Event;YAHOO.widget.Node=function(f,e,d){if(f){this.init(f,e,d);}};YAHOO.widget.Node.prototype={index:0,children:null,tree:null,data:null,parent:null,depth:-1,expanded:false,multiExpand:true,renderHidden:false,childrenRendered:false,dynamicLoadComplete:false,previousSibling:null,nextSibling:null,_dynLoad:false,dataLoader:null,isLoading:false,hasIcon:true,iconMode:0,nowrap:false,isLeaf:false,contentStyle:"",contentElId:null,enableHighlight:true,highlightState:0,propagateHighlightUp:false,propagateHighlightDown:false,className:null,_type:"Node",init:function(g,f,d){this.data={};
+this.children=[];this.index=YAHOO.widget.TreeView.nodeCount;++YAHOO.widget.TreeView.nodeCount;this.contentElId="ygtvcontentel"+this.index;if(c.isObject(g)){for(var e in g){if(g.hasOwnProperty(e)){if(e.charAt(0)!="_"&&!c.isUndefined(this[e])&&!c.isFunction(this[e])){this[e]=g[e];}else{this.data[e]=g[e];}}}}if(!c.isUndefined(d)){this.expanded=d;}this.createEvent("parentChange",this);if(f){f.appendChild(this);}},applyParent:function(e){if(!e){return false;}this.tree=e.tree;this.parent=e;this.depth=e.depth+1;this.tree.regNode(this);e.childrenRendered=false;for(var f=0,d=this.children.length;f<d;++f){this.children[f].applyParent(this);}this.fireEvent("parentChange");return true;},appendChild:function(e){if(this.hasChildren()){var d=this.children[this.children.length-1];d.nextSibling=e;e.previousSibling=d;}this.children[this.children.length]=e;e.applyParent(this);if(this.childrenRendered&&this.expanded){this.getChildrenEl().style.display="";}return e;},appendTo:function(d){return d.appendChild(this);},insertBefore:function(d){var f=d.parent;if(f){if(this.tree){this.tree.popNode(this);}var e=d.isChildOf(f);f.children.splice(e,0,this);if(d.previousSibling){d.previousSibling.nextSibling=this;}this.previousSibling=d.previousSibling;this.nextSibling=d;d.previousSibling=this;this.applyParent(f);}return this;},insertAfter:function(d){var f=d.parent;if(f){if(this.tree){this.tree.popNode(this);}var e=d.isChildOf(f);if(!d.nextSibling){this.nextSibling=null;return this.appendTo(f);}f.children.splice(e+1,0,this);d.nextSibling.previousSibling=this;this.previousSibling=d;this.nextSibling=d.nextSibling;d.nextSibling=this;this.applyParent(f);}return this;},isChildOf:function(e){if(e&&e.children){for(var f=0,d=e.children.length;f<d;++f){if(e.children[f]===this){return f;}}}return -1;},getSiblings:function(){var d=this.parent.children.slice(0);for(var e=0;e<d.length&&d[e]!=this;e++){}d.splice(e,1);if(d.length){return d;}return null;},showChildren:function(){if(!this.tree.animateExpand(this.getChildrenEl(),this)){if(this.hasChildren()){this.getChildrenEl().style.display="";}}},hideChildren:function(){if(!this.tree.animateCollapse(this.getChildrenEl(),this)){this.getChildrenEl().style.display="none";}},getElId:function(){return"ygtv"+this.index;},getChildrenElId:function(){return"ygtvc"+this.index;},getToggleElId:function(){return"ygtvt"+this.index;},getEl:function(){return b.get(this.getElId());},getChildrenEl:function(){return b.get(this.getChildrenElId());},getToggleEl:function(){return b.get(this.getToggleElId());},getContentEl:function(){return b.get(this.contentElId);},collapse:function(){if(!this.expanded){return;}var d=this.tree.onCollapse(this);if(false===d){return;}d=this.tree.fireEvent("collapse",this);if(false===d){return;}if(!this.getEl()){this.expanded=false;}else{this.hideChildren();this.expanded=false;this.updateIcon();}d=this.tree.fireEvent("collapseComplete",this);},expand:function(f){if(this.isLoading||(this.expanded&&!f)){return;}var d=true;if(!f){d=this.tree.onExpand(this);if(false===d){return;}d=this.tree.fireEvent("expand",this);}if(false===d){return;}if(!this.getEl()){this.expanded=true;return;}if(!this.childrenRendered){this.getChildrenEl().innerHTML=this.renderChildren();}else{}this.expanded=true;this.updateIcon();if(this.isLoading){this.expanded=false;return;}if(!this.multiExpand){var g=this.getSiblings();for(var e=0;g&&e<g.length;++e){if(g[e]!=this&&g[e].expanded){g[e].collapse();}}}this.showChildren();d=this.tree.fireEvent("expandComplete",this);},updateIcon:function(){if(this.hasIcon){var d=this.getToggleEl();if(d){d.className=d.className.replace(/\bygtv(([tl][pmn]h?)|(loading))\b/gi,this.getStyle());}}d=b.get("ygtvtableel"+this.index);if(d){if(this.expanded){b.replaceClass(d,"ygtv-collapsed","ygtv-expanded");}else{b.replaceClass(d,"ygtv-expanded","ygtv-collapsed");}}},getStyle:function(){if(this.isLoading){return"ygtvloading";}else{var e=(this.nextSibling)?"t":"l";var d="n";if(this.hasChildren(true)||(this.isDynamic()&&!this.getIconMode())){d=(this.expanded)?"m":"p";}return"ygtv"+e+d;}},getHoverStyle:function(){var d=this.getStyle();if(this.hasChildren(true)&&!this.isLoading){d+="h";}return d;},expandAll:function(){var d=this.children.length;for(var e=0;e<d;++e){var f=this.children[e];if(f.isDynamic()){break;}else{if(!f.multiExpand){break;}else{f.expand();f.expandAll();}}}},collapseAll:function(){for(var d=0;d<this.children.length;++d){this.children[d].collapse();this.children[d].collapseAll();}},setDynamicLoad:function(d,e){if(d){this.dataLoader=d;this._dynLoad=true;}else{this.dataLoader=null;this._dynLoad=false;}if(e){this.iconMode=e;}},isRoot:function(){return(this==this.tree.root);},isDynamic:function(){if(this.isLeaf){return false;}else{return(!this.isRoot()&&(this._dynLoad||this.tree.root._dynLoad));}},getIconMode:function(){return(this.iconMode||this.tree.root.iconMode);},hasChildren:function(d){if(this.isLeaf){return false;}else{return(this.children.length>0||(d&&this.isDynamic()&&!this.dynamicLoadComplete));}},toggle:function(){if(!this.tree.locked&&(this.hasChildren(true)||this.isDynamic())){if(this.expanded){this.collapse();}else{this.expand();}}},getHtml:function(){this.childrenRendered=false;return['<div class="ygtvitem" id="',this.getElId(),'">',this.getNodeHtml(),this.getChildrenHtml(),"</div>"].join("");},getChildrenHtml:function(){var d=[];d[d.length]='<div class="ygtvchildren" id="'+this.getChildrenElId()+'"';if(!this.expanded||!this.hasChildren()){d[d.length]=' style="display:none;"';}d[d.length]=">";if((this.hasChildren(true)&&this.expanded)||(this.renderHidden&&!this.isDynamic())){d[d.length]=this.renderChildren();}d[d.length]="</div>";return d.join("");},renderChildren:function(){var d=this;if(this.isDynamic()&&!this.dynamicLoadComplete){this.isLoading=true;this.tree.locked=true;if(this.dataLoader){setTimeout(function(){d.dataLoader(d,function(){d.loadComplete();});},10);}else{if(this.tree.root.dataLoader){setTimeout(function(){d.tree.root.dataLoader(d,function(){d.loadComplete();
+});},10);}else{return"Error: data loader not found or not specified.";}}return"";}else{return this.completeRender();}},completeRender:function(){var e=[];for(var d=0;d<this.children.length;++d){e[e.length]=this.children[d].getHtml();}this.childrenRendered=true;return e.join("");},loadComplete:function(){this.getChildrenEl().innerHTML=this.completeRender();if(this.propagateHighlightDown){if(this.highlightState===1&&!this.tree.singleNodeHighlight){for(var d=0;d<this.children.length;d++){this.children[d].highlight(true);}}else{if(this.highlightState===0||this.tree.singleNodeHighlight){for(d=0;d<this.children.length;d++){this.children[d].unhighlight(true);}}}}this.dynamicLoadComplete=true;this.isLoading=false;this.expand(true);this.tree.locked=false;},getAncestor:function(e){if(e>=this.depth||e<0){return null;}var d=this.parent;while(d.depth>e){d=d.parent;}return d;},getDepthStyle:function(d){return(this.getAncestor(d).nextSibling)?"ygtvdepthcell":"ygtvblankdepthcell";},getNodeHtml:function(){var e=[];e[e.length]='<table id="ygtvtableel'+this.index+'" border="0" cellpadding="0" cellspacing="0" class="ygtvtable ygtvdepth'+this.depth;e[e.length]=" ygtv-"+(this.expanded?"expanded":"collapsed");if(this.enableHighlight){e[e.length]=" ygtv-highlight"+this.highlightState;}if(this.className){e[e.length]=" "+this.className;}e[e.length]='"><tr class="ygtvrow">';for(var d=0;d<this.depth;++d){e[e.length]='<td class="ygtvcell '+this.getDepthStyle(d)+'"><div class="ygtvspacer"></div></td>';}if(this.hasIcon){e[e.length]='<td id="'+this.getToggleElId();e[e.length]='" class="ygtvcell ';e[e.length]=this.getStyle();e[e.length]='"><a href="#" class="ygtvspacer"> </a></td>';}e[e.length]='<td id="'+this.contentElId;e[e.length]='" class="ygtvcell ';e[e.length]=this.contentStyle+' ygtvcontent" ';e[e.length]=(this.nowrap)?' nowrap="nowrap" ':"";e[e.length]=" >";e[e.length]=this.getContentHtml();e[e.length]="</td></tr></table>";return e.join("");},getContentHtml:function(){return"";},refresh:function(){this.getChildrenEl().innerHTML=this.completeRender();if(this.hasIcon){var d=this.getToggleEl();if(d){d.className=d.className.replace(/\bygtv[lt][nmp]h*\b/gi,this.getStyle());}}},toString:function(){return this._type+" ("+this.index+")";},_focusHighlightedItems:[],_focusedItem:null,_canHaveFocus:function(){return this.getEl().getElementsByTagName("a").length>0;},_removeFocus:function(){if(this._focusedItem){a.removeListener(this._focusedItem,"blur");this._focusedItem=null;}var d;while((d=this._focusHighlightedItems.shift())){b.removeClass(d,YAHOO.widget.TreeView.FOCUS_CLASS_NAME);}},focus:function(){var f=false,d=this;if(this.tree.currentFocus){this.tree.currentFocus._removeFocus();}var e=function(g){if(g.parent){e(g.parent);g.parent.expand();}};e(this);b.getElementsBy(function(g){return(/ygtv(([tl][pmn]h?)|(content))/).test(g.className);},"td",d.getEl().firstChild,function(h){b.addClass(h,YAHOO.widget.TreeView.FOCUS_CLASS_NAME);if(!f){var g=h.getElementsByTagName("a");if(g.length){g=g[0];g.focus();d._focusedItem=g;a.on(g,"blur",function(){d.tree.fireEvent("focusChanged",{oldNode:d.tree.currentFocus,newNode:null});d.tree.currentFocus=null;d._removeFocus();});f=true;}}d._focusHighlightedItems.push(h);});if(f){this.tree.fireEvent("focusChanged",{oldNode:this.tree.currentFocus,newNode:this});this.tree.currentFocus=this;}else{this.tree.fireEvent("focusChanged",{oldNode:d.tree.currentFocus,newNode:null});this.tree.currentFocus=null;this._removeFocus();}return f;},getNodeCount:function(){for(var d=0,e=0;d<this.children.length;d++){e+=this.children[d].getNodeCount();}return e+1;},getNodeDefinition:function(){if(this.isDynamic()){return false;}var g,d=c.merge(this.data),f=[];if(this.expanded){d.expanded=this.expanded;}if(!this.multiExpand){d.multiExpand=this.multiExpand;}if(this.renderHidden){d.renderHidden=this.renderHidden;}if(!this.hasIcon){d.hasIcon=this.hasIcon;}if(this.nowrap){d.nowrap=this.nowrap;}if(this.className){d.className=this.className;}if(this.editable){d.editable=this.editable;}if(!this.enableHighlight){d.enableHighlight=this.enableHighlight;}if(this.highlightState){d.highlightState=this.highlightState;}if(this.propagateHighlightUp){d.propagateHighlightUp=this.propagateHighlightUp;}if(this.propagateHighlightDown){d.propagateHighlightDown=this.propagateHighlightDown;}d.type=this._type;for(var e=0;e<this.children.length;e++){g=this.children[e].getNodeDefinition();if(g===false){return false;}f.push(g);}if(f.length){d.children=f;}return d;},getToggleLink:function(){return"return false;";},setNodesProperty:function(d,g,f){if(d.charAt(0)!="_"&&!c.isUndefined(this[d])&&!c.isFunction(this[d])){this[d]=g;}else{this.data[d]=g;}for(var e=0;e<this.children.length;e++){this.children[e].setNodesProperty(d,g);}if(f){this.refresh();}},toggleHighlight:function(){if(this.enableHighlight){if(this.highlightState==1){this.unhighlight();}else{this.highlight();}}},highlight:function(e){if(this.enableHighlight){if(this.tree.singleNodeHighlight){if(this.tree._currentlyHighlighted){this.tree._currentlyHighlighted.unhighlight(e);}this.tree._currentlyHighlighted=this;}this.highlightState=1;this._setHighlightClassName();if(!this.tree.singleNodeHighlight){if(this.propagateHighlightDown){for(var d=0;d<this.children.length;d++){this.children[d].highlight(true);}}if(this.propagateHighlightUp){if(this.parent){this.parent._childrenHighlighted();}}}if(!e){this.tree.fireEvent("highlightEvent",this);}}},unhighlight:function(e){if(this.enableHighlight){this.tree._currentlyHighlighted=null;this.highlightState=0;this._setHighlightClassName();if(!this.tree.singleNodeHighlight){if(this.propagateHighlightDown){for(var d=0;d<this.children.length;d++){this.children[d].unhighlight(true);}}if(this.propagateHighlightUp){if(this.parent){this.parent._childrenHighlighted();}}}if(!e){this.tree.fireEvent("highlightEvent",this);}}},_childrenHighlighted:function(){var f=false,e=false;if(this.enableHighlight){for(var d=0;d<this.children.length;d++){switch(this.children[d].highlightState){case 0:e=true;
+break;case 1:f=true;break;case 2:f=e=true;break;}}if(f&&e){this.highlightState=2;}else{if(f){this.highlightState=1;}else{this.highlightState=0;}}this._setHighlightClassName();if(this.propagateHighlightUp){if(this.parent){this.parent._childrenHighlighted();}}}},_setHighlightClassName:function(){var d=b.get("ygtvtableel"+this.index);if(d){d.className=d.className.replace(/\bygtv-highlight\d\b/gi,"ygtv-highlight"+this.highlightState);}}};YAHOO.augment(YAHOO.widget.Node,YAHOO.util.EventProvider);})();YAHOO.widget.RootNode=function(a){this.init(null,null,true);this.tree=a;};YAHOO.extend(YAHOO.widget.RootNode,YAHOO.widget.Node,{_type:"RootNode",getNodeHtml:function(){return"";},toString:function(){return this._type;},loadComplete:function(){this.tree.draw();},getNodeCount:function(){for(var a=0,b=0;a<this.children.length;a++){b+=this.children[a].getNodeCount();}return b;},getNodeDefinition:function(){for(var c,a=[],b=0;b<this.children.length;b++){c=this.children[b].getNodeDefinition();if(c===false){return false;}a.push(c);}return a;},collapse:function(){},expand:function(){},getSiblings:function(){return null;},focus:function(){}});(function(){var b=YAHOO.util.Dom,c=YAHOO.lang,a=YAHOO.util.Event;YAHOO.widget.TextNode=function(f,e,d){if(f){if(c.isString(f)){f={label:f};}this.init(f,e,d);this.setUpLabel(f);}};YAHOO.extend(YAHOO.widget.TextNode,YAHOO.widget.Node,{labelStyle:"ygtvlabel",labelElId:null,label:null,title:null,href:null,target:"_self",_type:"TextNode",setUpLabel:function(d){if(c.isString(d)){d={label:d};}else{if(d.style){this.labelStyle=d.style;}}this.label=d.label;this.labelElId="ygtvlabelel"+this.index;},getLabelEl:function(){return b.get(this.labelElId);},getContentHtml:function(){var d=[];d[d.length]=this.href?"<a":"<span";d[d.length]=' id="'+c.escapeHTML(this.labelElId)+'"';d[d.length]=' class="'+c.escapeHTML(this.labelStyle)+'"';if(this.href){d[d.length]=' href="'+c.escapeHTML(this.href)+'"';d[d.length]=' target="'+c.escapeHTML(this.target)+'"';}if(this.title){d[d.length]=' title="'+c.escapeHTML(this.title)+'"';}d[d.length]=" >";d[d.length]=c.escapeHTML(this.label);d[d.length]=this.href?"</a>":"</span>";return d.join("");},getNodeDefinition:function(){var d=YAHOO.widget.TextNode.superclass.getNodeDefinition.call(this);if(d===false){return false;}d.label=this.label;if(this.labelStyle!="ygtvlabel"){d.style=this.labelStyle;}if(this.title){d.title=this.title;}if(this.href){d.href=this.href;}if(this.target!="_self"){d.target=this.target;}return d;},toString:function(){return YAHOO.widget.TextNode.superclass.toString.call(this)+": "+this.label;},onLabelClick:function(){return false;},refresh:function(){YAHOO.widget.TextNode.superclass.refresh.call(this);var d=this.getLabelEl();d.innerHTML=this.label;if(d.tagName.toUpperCase()=="A"){d.href=this.href;d.target=this.target;}}});})();YAHOO.widget.MenuNode=function(c,b,a){YAHOO.widget.MenuNode.superclass.constructor.call(this,c,b,a);this.multiExpand=false;};YAHOO.extend(YAHOO.widget.MenuNode,YAHOO.widget.TextNode,{_type:"MenuNode"});(function(){var b=YAHOO.util.Dom,c=YAHOO.lang,a=YAHOO.util.Event;var d=function(h,g,f,e){if(h){this.init(h,g,f);this.initContent(h,e);}};YAHOO.widget.HTMLNode=d;YAHOO.extend(d,YAHOO.widget.Node,{contentStyle:"ygtvhtml",html:null,_type:"HTMLNode",initContent:function(f,e){this.setHtml(f);this.contentElId="ygtvcontentel"+this.index;if(!c.isUndefined(e)){this.hasIcon=e;}},setHtml:function(f){this.html=(c.isObject(f)&&"html" in f)?f.html:f;var e=this.getContentEl();if(e){if(f.nodeType&&f.nodeType==1&&f.tagName){e.innerHTML="";}else{e.innerHTML=this.html;}}},getContentHtml:function(){if(typeof this.html==="string"){return this.html;}else{d._deferredNodes.push(this);if(!d._timer){d._timer=window.setTimeout(function(){var e;while((e=d._deferredNodes.pop())){e.getContentEl().appendChild(e.html);}d._timer=null;},0);}return"";}},getNodeDefinition:function(){var e=d.superclass.getNodeDefinition.call(this);if(e===false){return false;}e.html=this.html;return e;}});d._deferredNodes=[];d._timer=null;})();(function(){var b=YAHOO.util.Dom,c=YAHOO.lang,a=YAHOO.util.Event,d=YAHOO.widget.Calendar;YAHOO.widget.DateNode=function(g,f,e){YAHOO.widget.DateNode.superclass.constructor.call(this,g,f,e);};YAHOO.extend(YAHOO.widget.DateNode,YAHOO.widget.TextNode,{_type:"DateNode",calendarConfig:null,fillEditorContainer:function(g){var h,f=g.inputContainer;if(c.isUndefined(d)){b.replaceClass(g.editorPanel,"ygtv-edit-DateNode","ygtv-edit-TextNode");YAHOO.widget.DateNode.superclass.fillEditorContainer.call(this,g);return;}if(g.nodeType!=this._type){g.nodeType=this._type;g.saveOnEnter=false;g.node.destroyEditorContents(g);g.inputObject=h=new d(f.appendChild(document.createElement("div")));if(this.calendarConfig){h.cfg.applyConfig(this.calendarConfig,true);h.cfg.fireQueue();}h.selectEvent.subscribe(function(){this.tree._closeEditor(true);},this,true);}else{h=g.inputObject;}g.oldValue=this.label;h.cfg.setProperty("selected",this.label,false);var i=h.cfg.getProperty("DATE_FIELD_DELIMITER");var e=this.label.split(i);h.cfg.setProperty("pagedate",e[h.cfg.getProperty("MDY_MONTH_POSITION")-1]+i+e[h.cfg.getProperty("MDY_YEAR_POSITION")-1]);h.cfg.fireQueue();h.render();h.oDomContainer.focus();},getEditorValue:function(f){if(c.isUndefined(d)){return f.inputElement.value;}else{var h=f.inputObject,g=h.getSelectedDates()[0],e=[];e[h.cfg.getProperty("MDY_DAY_POSITION")-1]=g.getDate();e[h.cfg.getProperty("MDY_MONTH_POSITION")-1]=g.getMonth()+1;e[h.cfg.getProperty("MDY_YEAR_POSITION")-1]=g.getFullYear();return e.join(h.cfg.getProperty("DATE_FIELD_DELIMITER"));}},displayEditedValue:function(g,e){var f=e.node;f.label=g;f.getLabelEl().innerHTML=g;},getNodeDefinition:function(){var e=YAHOO.widget.DateNode.superclass.getNodeDefinition.call(this);if(e===false){return false;}if(this.calendarConfig){e.calendarConfig=this.calendarConfig;}return e;}});})();(function(){var e=YAHOO.util.Dom,f=YAHOO.lang,b=YAHOO.util.Event,d=YAHOO.widget.TreeView,c=d.prototype;d.editorData={active:false,whoHasIt:null,nodeType:null,editorPanel:null,inputContainer:null,buttonsContainer:null,node:null,saveOnEnter:true,oldValue:undefined};
+c.validator=null;c._initEditor=function(){this.createEvent("editorSaveEvent",this);this.createEvent("editorCancelEvent",this);};c._nodeEditing=function(m){if(m.fillEditorContainer&&m.editable){var i,k,l,j,h=d.editorData;h.active=true;h.whoHasIt=this;if(!h.nodeType){h.editorPanel=i=this.getEl().appendChild(document.createElement("div"));e.addClass(i,"ygtv-label-editor");i.tabIndex=0;l=h.buttonsContainer=i.appendChild(document.createElement("div"));e.addClass(l,"ygtv-button-container");j=l.appendChild(document.createElement("button"));e.addClass(j,"ygtvok");j.innerHTML=" ";j=l.appendChild(document.createElement("button"));e.addClass(j,"ygtvcancel");j.innerHTML=" ";b.on(l,"click",function(q){var r=b.getTarget(q),o=d.editorData,p=o.node,n=o.whoHasIt;if(e.hasClass(r,"ygtvok")){b.stopEvent(q);n._closeEditor(true);}if(e.hasClass(r,"ygtvcancel")){b.stopEvent(q);n._closeEditor(false);}});h.inputContainer=i.appendChild(document.createElement("div"));e.addClass(h.inputContainer,"ygtv-input");b.on(i,"keydown",function(q){var p=d.editorData,n=YAHOO.util.KeyListener.KEY,o=p.whoHasIt;switch(q.keyCode){case n.ENTER:b.stopEvent(q);if(p.saveOnEnter){o._closeEditor(true);}break;case n.ESCAPE:b.stopEvent(q);o._closeEditor(false);break;}});}else{i=h.editorPanel;}h.node=m;if(h.nodeType){e.removeClass(i,"ygtv-edit-"+h.nodeType);}e.addClass(i," ygtv-edit-"+m._type);e.setStyle(i,"display","block");e.setXY(i,e.getXY(m.getContentEl()));i.focus();m.fillEditorContainer(h);return true;}};c.onEventEditNode=function(h){if(h instanceof YAHOO.widget.Node){h.editNode();}else{if(h.node instanceof YAHOO.widget.Node){h.node.editNode();}}return false;};c._closeEditor=function(j){var h=d.editorData,i=h.node,k=true;if(!i||!h.active){return;}if(j){k=h.node.saveEditorValue(h)!==false;}else{this.fireEvent("editorCancelEvent",i);}if(k){e.setStyle(h.editorPanel,"display","none");h.active=false;i.focus();}};c._destroyEditor=function(){var h=d.editorData;if(h&&h.nodeType&&(!h.active||h.whoHasIt===this)){b.removeListener(h.editorPanel,"keydown");b.removeListener(h.buttonContainer,"click");h.node.destroyEditorContents(h);document.body.removeChild(h.editorPanel);h.nodeType=h.editorPanel=h.inputContainer=h.buttonsContainer=h.whoHasIt=h.node=null;h.active=false;}};var g=YAHOO.widget.Node.prototype;g.editable=false;g.editNode=function(){this.tree._nodeEditing(this);};g.fillEditorContainer=null;g.destroyEditorContents=function(h){b.purgeElement(h.inputContainer,true);h.inputContainer.innerHTML="";};g.saveEditorValue=function(h){var j=h.node,k,i=j.tree.validator;k=this.getEditorValue(h);if(f.isFunction(i)){k=i(k,h.oldValue,j);if(f.isUndefined(k)){return false;}}if(this.tree.fireEvent("editorSaveEvent",{newValue:k,oldValue:h.oldValue,node:j})!==false){this.displayEditedValue(k,h);}};g.getEditorValue=function(h){};g.displayEditedValue=function(i,h){};var a=YAHOO.widget.TextNode.prototype;a.fillEditorContainer=function(i){var h;if(i.nodeType!=this._type){i.nodeType=this._type;i.saveOnEnter=true;i.node.destroyEditorContents(i);i.inputElement=h=i.inputContainer.appendChild(document.createElement("input"));}else{h=i.inputElement;}i.oldValue=this.label;h.value=this.label;h.focus();h.select();};a.getEditorValue=function(h){return h.inputElement.value;};a.displayEditedValue=function(j,h){var i=h.node;i.label=j;i.getLabelEl().innerHTML=j;};a.destroyEditorContents=function(h){h.inputContainer.innerHTML="";};})();YAHOO.widget.TVAnim=function(){return{FADE_IN:"TVFadeIn",FADE_OUT:"TVFadeOut",getAnim:function(b,a,c){if(YAHOO.widget[b]){return new YAHOO.widget[b](a,c);}else{return null;}},isValid:function(a){return(YAHOO.widget[a]);}};}();YAHOO.widget.TVFadeIn=function(a,b){this.el=a;this.callback=b;};YAHOO.widget.TVFadeIn.prototype={animate:function(){var e=this;var d=this.el.style;d.opacity=0.1;d.filter="alpha(opacity=10)";d.display="";var c=0.4;var b=new YAHOO.util.Anim(this.el,{opacity:{from:0.1,to:1,unit:""}},c);b.onComplete.subscribe(function(){e.onComplete();});b.animate();},onComplete:function(){this.callback();},toString:function(){return"TVFadeIn";}};YAHOO.widget.TVFadeOut=function(a,b){this.el=a;this.callback=b;};YAHOO.widget.TVFadeOut.prototype={animate:function(){var d=this;var c=0.4;var b=new YAHOO.util.Anim(this.el,{opacity:{from:1,to:0.1,unit:""}},c);b.onComplete.subscribe(function(){d.onComplete();});b.animate();},onComplete:function(){var a=this.el.style;a.display="none";a.opacity=1;a.filter="alpha(opacity=100)";this.callback();},toString:function(){return"TVFadeOut";}};YAHOO.register("treeview",YAHOO.widget.TreeView,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/uploader/uploader-min.js b/Websites/bugs.webkit.org/js/yui/uploader/uploader-min.js
new file mode 100644
index 0000000..d1b28fc
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/uploader/uploader-min.js
@@ -0,0 +1,15 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+/*!
+ * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
+ *
+ * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
+ * http://www.opensource.org/licenses/mit-license.php
+ * @namespace YAHOO
+ */
+YAHOO.namespace("deconcept");YAHOO.deconcept=YAHOO.deconcept||{};if(typeof YAHOO.deconcept.util=="undefined"||!YAHOO.deconcept.util){YAHOO.deconcept.util={};}if(typeof YAHOO.deconcept.SWFObjectUtil=="undefined"||!YAHOO.deconcept.SWFObjectUtil){YAHOO.deconcept.SWFObjectUtil={};}YAHOO.deconcept.SWFObject=function(f,d,m,g,j,l,n,i,a,e){if(!document.getElementById){return;}this.DETECT_KEY=e?e:"detectflash";this.skipDetect=YAHOO.deconcept.util.getRequestParameter(this.DETECT_KEY);this.params={};this.variables={};this.attributes=[];if(f){this.setAttribute("swf",f);}if(d){this.setAttribute("id",d);}if(m){this.setAttribute("width",m);}if(g){this.setAttribute("height",g);}if(j){this.setAttribute("version",new YAHOO.deconcept.PlayerVersion(j.toString().split(".")));}this.installedVer=YAHOO.deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){YAHOO.deconcept.SWFObject.doPrepUnload=true;}if(l){this.addParam("bgcolor",l);}var b=n?n:"high";this.addParam("quality",b);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var k=(i)?i:window.location;this.setAttribute("xiRedirectUrl",k);this.setAttribute("redirectUrl","");if(a){this.setAttribute("redirectUrl",a);}};YAHOO.deconcept.SWFObject.prototype={useExpressInstall:function(a){this.xiSWFPath=!a?"expressinstall.swf":a;this.setAttribute("useExpressInstall",true);},setAttribute:function(a,b){this.attributes[a]=b;},getAttribute:function(a){return this.attributes[a];},addParam:function(a,b){this.params[a]=b;},getParams:function(){return this.params;},addVariable:function(a,b){this.variables[a]=b;},getVariable:function(a){return this.variables[a];},getVariables:function(){return this.variables;},getVariablePairs:function(){var a=[];var b;var c=this.getVariables();for(b in c){if(c.hasOwnProperty(b)){a[a.length]=YAHOO.lang.escapeHTML(b||"")+"="+YAHOO.lang.escapeHTML(encodeURIComponent(c[b]||""));}}return a;},getSWFHTML:function(){var d="";var c={};var a="";var b="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}d='<embed type="application/x-shockwave-flash" src="'+YAHOO.lang.escapeHTML(this.getAttribute("swf")||"")+'" width="'+YAHOO.lang.escapeHTML(this.getAttribute("width")||"")+'" height="'+YAHOO.lang.escapeHTML(this.getAttribute("height")||"")+'" style="'+YAHOO.lang.escapeHTML(this.getAttribute("style")||"")+'"';d+=' id="'+YAHOO.lang.escapeHTML(this.getAttribute("id")||"")+'" name="'+YAHOO.lang.escapeHTML(this.getAttribute("id")||"")+'" ';c=this.getParams();for(a in c){if(c.hasOwnProperty(a)){d+=YAHOO.lang.escapeHTML(a||"")+'="'+YAHOO.lang.escapeHTML(c[a]||"")+'" ';}}b=this.getVariablePairs().join("&");if(b.length>0){d+='flashvars="'+b+'"';}d+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}d='<object id="'+YAHOO.lang.escapeHTML(this.getAttribute("id")||"")+'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+YAHOO.lang.escapeHTML(this.getAttribute("width")||"")+'" height="'+YAHOO.lang.escapeHTML(this.getAttribute("height")||"")+'" style="'+YAHOO.lang.escapeHTML(this.getAttribute("style")||"")+'">';d+='<param name="movie" value="'+YAHOO.lang.escapeHTML(this.getAttribute("swf")||"")+'" />';c=this.getParams();for(a in c){if(c.hasOwnProperty(a)){d+='<param name="'+YAHOO.lang.escapeHTML(a||"")+'" value="'+YAHOO.lang.escapeHTML(c[a]||"")+'" />';}}b=this.getVariablePairs().join("&");if(b.length>0){d+='<param name="flashvars" value="'+b+'" />';}d+="</object>";}return d;},write:function(a){if(this.getAttribute("useExpressInstall")){var b=new YAHOO.deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(b)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var c=(typeof a=="string")?document.getElementById(a):a;c.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!==""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};YAHOO.deconcept.SWFObjectUtil.getPlayerVersion=function(){var d=null;var c=new YAHOO.deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var a=navigator.plugins["Shockwave Flash"];if(a&&a.description){c=new YAHOO.deconcept.PlayerVersion(a.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var b=3;while(d){try{b++;d=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+b);c=new YAHOO.deconcept.PlayerVersion([b,0,0]);}catch(f){d=null;}}}else{try{d=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(f){try{d=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");c=new YAHOO.deconcept.PlayerVersion([6,0,21]);d.AllowScriptAccess="always";}catch(f){if(c.major==6){return c;}}try{d=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(f){}}if(d!==null){c=new YAHOO.deconcept.PlayerVersion(d.GetVariable("$version").split(" ")[1].split(","));}}}return c;};YAHOO.deconcept.PlayerVersion=function(a){this.major=a[0]!==null?parseInt(a[0],0):0;this.minor=a[1]!==null?parseInt(a[1],0):0;this.rev=a[2]!==null?parseInt(a[2],0):0;};YAHOO.deconcept.PlayerVersion.prototype.versionIsValid=function(a){if(this.major<a.major){return false;
+}if(this.major>a.major){return true;}if(this.minor<a.minor){return false;}if(this.minor>a.minor){return true;}if(this.rev<a.rev){return false;}return true;};YAHOO.deconcept.util={getRequestParameter:function(d){var c=document.location.search||document.location.hash;if(d===null){return c;}if(c){var b=c.substring(1).split("&");for(var a=0;a<b.length;a++){if(b[a].substring(0,b[a].indexOf("="))==d){return b[a].substring((b[a].indexOf("=")+1));}}}return"";}};YAHOO.deconcept.SWFObjectUtil.cleanupSWFs=function(){var c=document.getElementsByTagName("OBJECT");for(var b=c.length-1;b>=0;b--){c[b].style.display="none";for(var a in c[b]){if(typeof c[b][a]=="function"){c[b][a]=function(){};}}}};if(YAHOO.deconcept.SWFObject.doPrepUnload){if(!YAHOO.deconcept.unloadSet){YAHOO.deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",YAHOO.deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",YAHOO.deconcept.SWFObjectUtil.prepUnload);YAHOO.deconcept.unloadSet=true;}}if(!document.getElementById&&document.all){document.getElementById=function(a){return document.all[a];};}YAHOO.widget.FlashAdapter=function(f,a,b,c){this._queue=this._queue||[];this._events=this._events||{};this._configs=this._configs||{};b=b||{};this._id=b.id=b.id||YAHOO.util.Dom.generateId(null,"yuigen");b.version=b.version||"9.0.45";b.backgroundColor=b.backgroundColor||"#ffffff";this._attributes=b;this._swfURL=f;this._containerID=a;this._embedSWF(this._swfURL,this._containerID,b.id,b.version,b.backgroundColor,b.expressInstall,b.wmode,c);try{this.createEvent("contentReady");}catch(d){}};YAHOO.widget.FlashAdapter.owners=YAHOO.widget.FlashAdapter.owners||{};YAHOO.extend(YAHOO.widget.FlashAdapter,YAHOO.util.AttributeProvider,{_swfURL:null,_containerID:null,_swf:null,_id:null,_initialized:false,_attributes:null,toString:function(){return"FlashAdapter "+this._id;},destroy:function(){if(this._swf){var b=YAHOO.util.Dom.get(this._containerID);b.removeChild(this._swf);}var a=this._id;for(var c in this){if(YAHOO.lang.hasOwnProperty(this,c)){this[c]=null;}}},_embedSWF:function(j,i,e,c,f,g,b,h){var d=new YAHOO.deconcept.SWFObject(j,e,"100%","100%",c,f);if(g){d.useExpressInstall(g);}d.addParam("allowScriptAccess","always");if(b){d.addParam("wmode",b);}d.addParam("menu","false");d.addVariable("allowedDomain",document.location.hostname);d.addVariable("YUISwfId",e);d.addVariable("YUIBridgeCallback","YAHOO.widget.FlashAdapter.eventHandler");if(h){d.addVariable("buttonSkin",h);}var a=YAHOO.util.Dom.get(i);var k=d.write(a);if(k){this._swf=YAHOO.util.Dom.get(e);YAHOO.widget.FlashAdapter.owners[e]=this;}else{}},_eventHandler:function(b){var a=b.type;switch(a){case"swfReady":this._loadHandler();return;case"log":return;}this.fireEvent(a,b);},_loadHandler:function(){this._initialized=false;this._initAttributes(this._attributes);this.setAttributes(this._attributes,true);this._initialized=true;this.fireEvent("contentReady");},set:function(a,b){this._attributes[a]=b;YAHOO.widget.FlashAdapter.superclass.set.call(this,a,b);},_initAttributes:function(a){this.getAttributeConfig("altText",{method:this._getAltText});this.setAttributeConfig("altText",{method:this._setAltText});this.getAttributeConfig("swfURL",{method:this._getSWFURL});},_getSWFURL:function(){return this._swfURL;},_getAltText:function(){return this._swf.getAltText();},_setAltText:function(a){return this._swf.setAltText(a);}});YAHOO.widget.FlashAdapter.eventHandler=function(a,b){if(!YAHOO.widget.FlashAdapter.owners[a]){setTimeout(function(){YAHOO.widget.FlashAdapter.eventHandler(a,b);},0);}else{YAHOO.widget.FlashAdapter.owners[a]._eventHandler(b);}};YAHOO.widget.FlashAdapter.proxyFunctionCount=0;YAHOO.widget.FlashAdapter.createProxyFunction=function(b){var a=YAHOO.widget.FlashAdapter.proxyFunctionCount;YAHOO.widget.FlashAdapter["proxyFunction"+a]=function(){return b.apply(null,arguments);};YAHOO.widget.FlashAdapter.proxyFunctionCount++;return"YAHOO.widget.FlashAdapter.proxyFunction"+a.toString();};YAHOO.widget.FlashAdapter.removeProxyFunction=function(a){if(!a||a.indexOf("YAHOO.widget.FlashAdapter.proxyFunction")<0){return;}a=a.substr(26);YAHOO.widget.FlashAdapter[a]=null;};YAHOO.widget.Uploader=function(a,b,d){var c="window";if(!(b)||(b&&d)){c="transparent";}YAHOO.widget.Uploader.superclass.constructor.call(this,YAHOO.widget.Uploader.SWFURL,a,{wmode:c},b);this.createEvent("mouseDown");this.createEvent("mouseUp");this.createEvent("rollOver");this.createEvent("rollOut");this.createEvent("click");this.createEvent("fileSelect");this.createEvent("uploadStart");this.createEvent("uploadProgress");this.createEvent("uploadCancel");this.createEvent("uploadComplete");this.createEvent("uploadCompleteData");this.createEvent("uploadError");};YAHOO.widget.Uploader.SWFURL="assets/uploader.swf";YAHOO.extend(YAHOO.widget.Uploader,YAHOO.widget.FlashAdapter,{upload:function(a,b,e,c,d){this._swf.upload(a,b,e,c,d);},uploadThese:function(b,a,e,c,d){this._swf.uploadThese(b,a,e,c,d);},uploadAll:function(a,d,b,c){this._swf.uploadAll(a,d,b,c);},cancel:function(a){this._swf.cancel(a);},clearFileList:function(){this._swf.clearFileList();},removeFile:function(a){this._swf.removeFile(a);},setAllowLogging:function(a){this._swf.setAllowLogging(a);},setSimUploadLimit:function(a){this._swf.setSimUploadLimit(a);},setAllowMultipleFiles:function(a){this._swf.setAllowMultipleFiles(a);},setFileFilters:function(a){this._swf.setFileFilters(a);},enable:function(){this._swf.enable();},disable:function(){this._swf.disable();}});YAHOO.register("uploader",YAHOO.widget.Uploader,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/yahoo-dom-event.js b/Websites/bugs.webkit.org/js/yui/yahoo-dom-event.js
deleted file mode 100644
index d089967..0000000
--- a/Websites/bugs.webkit.org/js/yui/yahoo-dom-event.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
-Copyright (c) 2007, Yahoo! Inc. All rights reserved.
-Code licensed under the BSD License:
-http://developer.yahoo.net/yui/license.txt
-version: 2.3.1
-*/
-if(typeof YAHOO=="undefined"){var YAHOO={};}YAHOO.namespace=function(){var A=arguments,E=null,C,B,D;for(C=0;C<A.length;C=C+1){D=A[C].split(".");E=YAHOO;for(B=(D[0]=="YAHOO")?1:0;B<D.length;B=B+1){E[D[B]]=E[D[B]]||{};E=E[D[B]];}}return E;};YAHOO.log=function(D,A,C){var B=YAHOO.widget.Logger;if(B&&B.log){return B.log(D,A,C);}else{return false;}};YAHOO.register=function(A,E,D){var I=YAHOO.env.modules;if(!I[A]){I[A]={versions:[],builds:[]};}var B=I[A],H=D.version,G=D.build,F=YAHOO.env.listeners;B.name=A;B.version=H;B.build=G;B.versions.push(H);B.builds.push(G);B.mainClass=E;for(var C=0;C<F.length;C=C+1){F[C](B);}if(E){E.VERSION=H;E.BUILD=G;}else{YAHOO.log("mainClass is undefined for module "+A,"warn");}};YAHOO.env=YAHOO.env||{modules:[],listeners:[]};YAHOO.env.getVersion=function(A){return YAHOO.env.modules[A]||null;};YAHOO.env.ua=function(){var C={ie:0,opera:0,gecko:0,webkit:0};var B=navigator.userAgent,A;if((/KHTML/).test(B)){C.webkit=1;}A=B.match(/AppleWebKit\/([^\s]*)/);if(A&&A[1]){C.webkit=parseFloat(A[1]);}if(!C.webkit){A=B.match(/Opera[\s\/]([^\s]*)/);if(A&&A[1]){C.opera=parseFloat(A[1]);}else{A=B.match(/MSIE\s([^;]*)/);if(A&&A[1]){C.ie=parseFloat(A[1]);}else{A=B.match(/Gecko\/([^\s]*)/);if(A){C.gecko=1;A=B.match(/rv:([^\s\)]*)/);if(A&&A[1]){C.gecko=parseFloat(A[1]);}}}}}return C;}();(function(){YAHOO.namespace("util","widget","example");if("undefined"!==typeof YAHOO_config){var B=YAHOO_config.listener,A=YAHOO.env.listeners,D=true,C;if(B){for(C=0;C<A.length;C=C+1){if(A[C]==B){D=false;break;}}if(D){A.push(B);}}}})();YAHOO.lang={isArray:function(B){if(B){var A=YAHOO.lang;return A.isNumber(B.length)&&A.isFunction(B.splice)&&!A.hasOwnProperty(B.length);}return false;},isBoolean:function(A){return typeof A==="boolean";},isFunction:function(A){return typeof A==="function";},isNull:function(A){return A===null;},isNumber:function(A){return typeof A==="number"&&isFinite(A);},isObject:function(A){return(A&&(typeof A==="object"||YAHOO.lang.isFunction(A)))||false;},isString:function(A){return typeof A==="string";},isUndefined:function(A){return typeof A==="undefined";},hasOwnProperty:function(A,B){if(Object.prototype.hasOwnProperty){return A.hasOwnProperty(B);}return !YAHOO.lang.isUndefined(A[B])&&A.constructor.prototype[B]!==A[B];},_IEEnumFix:function(C,B){if(YAHOO.env.ua.ie){var E=["toString","valueOf"],A;for(A=0;A<E.length;A=A+1){var F=E[A],D=B[F];if(YAHOO.lang.isFunction(D)&&D!=Object.prototype[F]){C[F]=D;}}}},extend:function(D,E,C){if(!E||!D){throw new Error("YAHOO.lang.extend failed, please check that all dependencies are included.");}var B=function(){};B.prototype=E.prototype;D.prototype=new B();D.prototype.constructor=D;D.superclass=E.prototype;if(E.prototype.constructor==Object.prototype.constructor){E.prototype.constructor=E;}if(C){for(var A in C){D.prototype[A]=C[A];}YAHOO.lang._IEEnumFix(D.prototype,C);}},augmentObject:function(E,D){if(!D||!E){throw new Error("Absorb failed, verify dependencies.");}var A=arguments,C,F,B=A[2];if(B&&B!==true){for(C=2;C<A.length;C=C+1){E[A[C]]=D[A[C]];}}else{for(F in D){if(B||!E[F]){E[F]=D[F];}}YAHOO.lang._IEEnumFix(E,D);}},augmentProto:function(D,C){if(!C||!D){throw new Error("Augment failed, verify dependencies.");}var A=[D.prototype,C.prototype];for(var B=2;B<arguments.length;B=B+1){A.push(arguments[B]);}YAHOO.lang.augmentObject.apply(this,A);},dump:function(A,G){var C=YAHOO.lang,D,F,I=[],J="{...}",B="f(){...}",H=", ",E=" => ";if(!C.isObject(A)){return A+"";}else{if(A instanceof Date||("nodeType" in A&&"tagName" in A)){return A;}else{if(C.isFunction(A)){return B;}}}G=(C.isNumber(G))?G:3;if(C.isArray(A)){I.push("[");for(D=0,F=A.length;D<F;D=D+1){if(C.isObject(A[D])){I.push((G>0)?C.dump(A[D],G-1):J);}else{I.push(A[D]);}I.push(H);}if(I.length>1){I.pop();}I.push("]");}else{I.push("{");for(D in A){if(C.hasOwnProperty(A,D)){I.push(D+E);if(C.isObject(A[D])){I.push((G>0)?C.dump(A[D],G-1):J);}else{I.push(A[D]);}I.push(H);}}if(I.length>1){I.pop();}I.push("}");}return I.join("");},substitute:function(Q,B,J){var G,F,E,M,N,P,D=YAHOO.lang,L=[],C,H="dump",K=" ",A="{",O="}";for(;;){G=Q.lastIndexOf(A);if(G<0){break;}F=Q.indexOf(O,G);if(G+1>=F){break;}C=Q.substring(G+1,F);M=C;P=null;E=M.indexOf(K);if(E>-1){P=M.substring(E+1);M=M.substring(0,E);}N=B[M];if(J){N=J(M,N,P);}if(D.isObject(N)){if(D.isArray(N)){N=D.dump(N,parseInt(P,10));}else{P=P||"";var I=P.indexOf(H);if(I>-1){P=P.substring(4);}if(N.toString===Object.prototype.toString||I>-1){N=D.dump(N,parseInt(P,10));}else{N=N.toString();}}}else{if(!D.isString(N)&&!D.isNumber(N)){N="~-"+L.length+"-~";L[L.length]=C;}}Q=Q.substring(0,G)+N+Q.substring(F+1);}for(G=L.length-1;G>=0;G=G-1){Q=Q.replace(new RegExp("~-"+G+"-~"),"{"+L[G]+"}","g");}return Q;},trim:function(A){try{return A.replace(/^\s+|\s+$/g,"");}catch(B){return A;}},merge:function(){var C={},A=arguments,B;for(B=0;B<A.length;B=B+1){YAHOO.lang.augmentObject(C,A[B],true);}return C;},isValue:function(B){var A=YAHOO.lang;return(A.isObject(B)||A.isString(B)||A.isNumber(B)||A.isBoolean(B));}};YAHOO.util.Lang=YAHOO.lang;YAHOO.lang.augment=YAHOO.lang.augmentProto;YAHOO.augment=YAHOO.lang.augmentProto;YAHOO.extend=YAHOO.lang.extend;YAHOO.register("yahoo",YAHOO,{version:"2.3.1",build:"541"});(function(){var B=YAHOO.util,K,I,H=0,J={},F={};var C=YAHOO.env.ua.opera,L=YAHOO.env.ua.webkit,A=YAHOO.env.ua.gecko,G=YAHOO.env.ua.ie;var E={HYPHEN:/(-[a-z])/i,ROOT_TAG:/^body|html$/i};var M=function(O){if(!E.HYPHEN.test(O)){return O;}if(J[O]){return J[O];}var P=O;while(E.HYPHEN.exec(P)){P=P.replace(RegExp.$1,RegExp.$1.substr(1).toUpperCase());}J[O]=P;return P;};var N=function(P){var O=F[P];if(!O){O=new RegExp("(?:^|\\s+)"+P+"(?:\\s+|$)");F[P]=O;}return O;};if(document.defaultView&&document.defaultView.getComputedStyle){K=function(O,R){var Q=null;if(R=="float"){R="cssFloat";}var P=document.defaultView.getComputedStyle(O,"");if(P){Q=P[M(R)];}return O.style[R]||Q;};}else{if(document.documentElement.currentStyle&&G){K=function(O,Q){switch(M(Q)){case"opacity":var S=100;try{S=O.filters["DXImageTransform.Microsoft.Alpha"].opacity;}catch(R){try{S=O.filters("alpha").opacity;}catch(R){}}return S/100;case"float":Q="styleFloat";default:var P=O.currentStyle?O.currentStyle[Q]:null;return(O.style[Q]||P);}};}else{K=function(O,P){return O.style[P];};}}if(G){I=function(O,P,Q){switch(P){case"opacity":if(YAHOO.lang.isString(O.style.filter)){O.style.filter="alpha(opacity="+Q*100+")";if(!O.currentStyle||!O.currentStyle.hasLayout){O.style.zoom=1;}}break;case"float":P="styleFloat";default:O.style[P]=Q;}};}else{I=function(O,P,Q){if(P=="float"){P="cssFloat";}O.style[P]=Q;};}var D=function(O,P){return O&&O.nodeType==1&&(!P||P(O));};YAHOO.util.Dom={get:function(Q){if(Q&&(Q.tagName||Q.item)){return Q;}if(YAHOO.lang.isString(Q)||!Q){return document.getElementById(Q);}if(Q.length!==undefined){var R=[];for(var P=0,O=Q.length;P<O;++P){R[R.length]=B.Dom.get(Q[P]);}return R;}return Q;},getStyle:function(O,Q){Q=M(Q);var P=function(R){return K(R,Q);};return B.Dom.batch(O,P,B.Dom,true);},setStyle:function(O,Q,R){Q=M(Q);var P=function(S){I(S,Q,R);};B.Dom.batch(O,P,B.Dom,true);},getXY:function(O){var P=function(R){if((R.parentNode===null||R.offsetParent===null||this.getStyle(R,"display")=="none")&&R!=document.body){return false;}var Q=null;var V=[];var S;var T=R.ownerDocument;if(R.getBoundingClientRect){S=R.getBoundingClientRect();return[S.left+B.Dom.getDocumentScrollLeft(R.ownerDocument),S.top+B.Dom.getDocumentScrollTop(R.ownerDocument)];}else{V=[R.offsetLeft,R.offsetTop];Q=R.offsetParent;var U=this.getStyle(R,"position")=="absolute";if(Q!=R){while(Q){V[0]+=Q.offsetLeft;V[1]+=Q.offsetTop;if(L&&!U&&this.getStyle(Q,"position")=="absolute"){U=true;}Q=Q.offsetParent;}}if(L&&U){V[0]-=R.ownerDocument.body.offsetLeft;V[1]-=R.ownerDocument.body.offsetTop;}}Q=R.parentNode;while(Q.tagName&&!E.ROOT_TAG.test(Q.tagName)){if(B.Dom.getStyle(Q,"display").search(/^inline|table-row.*$/i)){V[0]-=Q.scrollLeft;V[1]-=Q.scrollTop;}Q=Q.parentNode;}return V;};return B.Dom.batch(O,P,B.Dom,true);},getX:function(O){var P=function(Q){return B.Dom.getXY(Q)[0];};return B.Dom.batch(O,P,B.Dom,true);},getY:function(O){var P=function(Q){return B.Dom.getXY(Q)[1];};return B.Dom.batch(O,P,B.Dom,true);},setXY:function(O,R,Q){var P=function(U){var T=this.getStyle(U,"position");if(T=="static"){this.setStyle(U,"position","relative");T="relative";}var W=this.getXY(U);if(W===false){return false;}var V=[parseInt(this.getStyle(U,"left"),10),parseInt(this.getStyle(U,"top"),10)];if(isNaN(V[0])){V[0]=(T=="relative")?0:U.offsetLeft;}if(isNaN(V[1])){V[1]=(T=="relative")?0:U.offsetTop;}if(R[0]!==null){U.style.left=R[0]-W[0]+V[0]+"px";}if(R[1]!==null){U.style.top=R[1]-W[1]+V[1]+"px";}if(!Q){var S=this.getXY(U);if((R[0]!==null&&S[0]!=R[0])||(R[1]!==null&&S[1]!=R[1])){this.setXY(U,R,true);}}};B.Dom.batch(O,P,B.Dom,true);},setX:function(P,O){B.Dom.setXY(P,[O,null]);},setY:function(O,P){B.Dom.setXY(O,[null,P]);},getRegion:function(O){var P=function(Q){if((Q.parentNode===null||Q.offsetParent===null||this.getStyle(Q,"display")=="none")&&Q!=document.body){return false;}var R=B.Region.getRegion(Q);return R;};return B.Dom.batch(O,P,B.Dom,true);},getClientWidth:function(){return B.Dom.getViewportWidth();},getClientHeight:function(){return B.Dom.getViewportHeight();},getElementsByClassName:function(S,W,T,U){W=W||"*";T=(T)?B.Dom.get(T):null||document;if(!T){return[];}var P=[],O=T.getElementsByTagName(W),V=N(S);for(var Q=0,R=O.length;Q<R;++Q){if(V.test(O[Q].className)){P[P.length]=O[Q];if(U){U.call(O[Q],O[Q]);}}}return P;},hasClass:function(Q,P){var O=N(P);var R=function(S){return O.test(S.className);};return B.Dom.batch(Q,R,B.Dom,true);},addClass:function(P,O){var Q=function(R){if(this.hasClass(R,O)){return false;}R.className=YAHOO.lang.trim([R.className,O].join(" "));return true;};return B.Dom.batch(P,Q,B.Dom,true);},removeClass:function(Q,P){var O=N(P);var R=function(S){if(!this.hasClass(S,P)){return false;}var T=S.className;S.className=T.replace(O," ");if(this.hasClass(S,P)){this.removeClass(S,P);}S.className=YAHOO.lang.trim(S.className);return true;};return B.Dom.batch(Q,R,B.Dom,true);},replaceClass:function(R,P,O){if(!O||P===O){return false;}var Q=N(P);var S=function(T){if(!this.hasClass(T,P)){this.addClass(T,O);return true;}T.className=T.className.replace(Q," "+O+" ");if(this.hasClass(T,P)){this.replaceClass(T,P,O);}T.className=YAHOO.lang.trim(T.className);return true;};return B.Dom.batch(R,S,B.Dom,true);},generateId:function(O,Q){Q=Q||"yui-gen";var P=function(R){if(R&&R.id){return R.id;}var S=Q+H++;if(R){R.id=S;}return S;};return B.Dom.batch(O,P,B.Dom,true)||P.apply(B.Dom,arguments);},isAncestor:function(P,Q){P=B.Dom.get(P);if(!P||!Q){return false;}var O=function(R){if(P.contains&&R.nodeType&&!L){return P.contains(R);}else{if(P.compareDocumentPosition&&R.nodeType){return !!(P.compareDocumentPosition(R)&16);}else{if(R.nodeType){return !!this.getAncestorBy(R,function(S){return S==P;});}}}return false;};return B.Dom.batch(Q,O,B.Dom,true);},inDocument:function(O){var P=function(Q){if(L){while(Q=Q.parentNode){if(Q==document.documentElement){return true;}}return false;}return this.isAncestor(document.documentElement,Q);};return B.Dom.batch(O,P,B.Dom,true);},getElementsBy:function(V,P,Q,S){P=P||"*";
-Q=(Q)?B.Dom.get(Q):null||document;if(!Q){return[];}var R=[],U=Q.getElementsByTagName(P);for(var T=0,O=U.length;T<O;++T){if(V(U[T])){R[R.length]=U[T];if(S){S(U[T]);}}}return R;},batch:function(S,V,U,Q){S=(S&&(S.tagName||S.item))?S:B.Dom.get(S);if(!S||!V){return false;}var R=(Q)?U:window;if(S.tagName||S.length===undefined){return V.call(R,S,U);}var T=[];for(var P=0,O=S.length;P<O;++P){T[T.length]=V.call(R,S[P],U);}return T;},getDocumentHeight:function(){var P=(document.compatMode!="CSS1Compat")?document.body.scrollHeight:document.documentElement.scrollHeight;var O=Math.max(P,B.Dom.getViewportHeight());return O;},getDocumentWidth:function(){var P=(document.compatMode!="CSS1Compat")?document.body.scrollWidth:document.documentElement.scrollWidth;var O=Math.max(P,B.Dom.getViewportWidth());return O;},getViewportHeight:function(){var O=self.innerHeight;var P=document.compatMode;if((P||G)&&!C){O=(P=="CSS1Compat")?document.documentElement.clientHeight:document.body.clientHeight;}return O;},getViewportWidth:function(){var O=self.innerWidth;var P=document.compatMode;if(P||G){O=(P=="CSS1Compat")?document.documentElement.clientWidth:document.body.clientWidth;}return O;},getAncestorBy:function(O,P){while(O=O.parentNode){if(D(O,P)){return O;}}return null;},getAncestorByClassName:function(P,O){P=B.Dom.get(P);if(!P){return null;}var Q=function(R){return B.Dom.hasClass(R,O);};return B.Dom.getAncestorBy(P,Q);},getAncestorByTagName:function(P,O){P=B.Dom.get(P);if(!P){return null;}var Q=function(R){return R.tagName&&R.tagName.toUpperCase()==O.toUpperCase();};return B.Dom.getAncestorBy(P,Q);},getPreviousSiblingBy:function(O,P){while(O){O=O.previousSibling;if(D(O,P)){return O;}}return null;},getPreviousSibling:function(O){O=B.Dom.get(O);if(!O){return null;}return B.Dom.getPreviousSiblingBy(O);},getNextSiblingBy:function(O,P){while(O){O=O.nextSibling;if(D(O,P)){return O;}}return null;},getNextSibling:function(O){O=B.Dom.get(O);if(!O){return null;}return B.Dom.getNextSiblingBy(O);},getFirstChildBy:function(O,Q){var P=(D(O.firstChild,Q))?O.firstChild:null;return P||B.Dom.getNextSiblingBy(O.firstChild,Q);},getFirstChild:function(O,P){O=B.Dom.get(O);if(!O){return null;}return B.Dom.getFirstChildBy(O);},getLastChildBy:function(O,Q){if(!O){return null;}var P=(D(O.lastChild,Q))?O.lastChild:null;return P||B.Dom.getPreviousSiblingBy(O.lastChild,Q);},getLastChild:function(O){O=B.Dom.get(O);return B.Dom.getLastChildBy(O);},getChildrenBy:function(P,R){var Q=B.Dom.getFirstChildBy(P,R);var O=Q?[Q]:[];B.Dom.getNextSiblingBy(Q,function(S){if(!R||R(S)){O[O.length]=S;}return false;});return O;},getChildren:function(O){O=B.Dom.get(O);if(!O){}return B.Dom.getChildrenBy(O);},getDocumentScrollLeft:function(O){O=O||document;return Math.max(O.documentElement.scrollLeft,O.body.scrollLeft);},getDocumentScrollTop:function(O){O=O||document;return Math.max(O.documentElement.scrollTop,O.body.scrollTop);},insertBefore:function(P,O){P=B.Dom.get(P);O=B.Dom.get(O);if(!P||!O||!O.parentNode){return null;}return O.parentNode.insertBefore(P,O);},insertAfter:function(P,O){P=B.Dom.get(P);O=B.Dom.get(O);if(!P||!O||!O.parentNode){return null;}if(O.nextSibling){return O.parentNode.insertBefore(P,O.nextSibling);}else{return O.parentNode.appendChild(P);}}};})();YAHOO.util.Region=function(C,D,A,B){this.top=C;this[1]=C;this.right=D;this.bottom=A;this.left=B;this[0]=B;};YAHOO.util.Region.prototype.contains=function(A){return(A.left>=this.left&&A.right<=this.right&&A.top>=this.top&&A.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(E){var C=Math.max(this.top,E.top);var D=Math.min(this.right,E.right);var A=Math.min(this.bottom,E.bottom);var B=Math.max(this.left,E.left);if(A>=C&&D>=B){return new YAHOO.util.Region(C,D,A,B);}else{return null;}};YAHOO.util.Region.prototype.union=function(E){var C=Math.min(this.top,E.top);var D=Math.max(this.right,E.right);var A=Math.max(this.bottom,E.bottom);var B=Math.min(this.left,E.left);return new YAHOO.util.Region(C,D,A,B);};YAHOO.util.Region.prototype.toString=function(){return("Region {top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+"}");};YAHOO.util.Region.getRegion=function(D){var F=YAHOO.util.Dom.getXY(D);var C=F[1];var E=F[0]+D.offsetWidth;var A=F[1]+D.offsetHeight;var B=F[0];return new YAHOO.util.Region(C,E,A,B);};YAHOO.util.Point=function(A,B){if(YAHOO.lang.isArray(A)){B=A[1];A=A[0];}this.x=this.right=this.left=this[0]=A;this.y=this.top=this.bottom=this[1]=B;};YAHOO.util.Point.prototype=new YAHOO.util.Region();YAHOO.register("dom",YAHOO.util.Dom,{version:"2.3.1",build:"541"});YAHOO.util.CustomEvent=function(D,B,C,A){this.type=D;this.scope=B||window;this.silent=C;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var E="_YUICEOnSubscribe";if(D!==E){this.subscribeEvent=new YAHOO.util.CustomEvent(E,this,true);}this.lastError=null;};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(B,C,A){if(!B){throw new Error("Invalid callback for subscriber to '"+this.type+"'");}if(this.subscribeEvent){this.subscribeEvent.fire(B,C,A);}this.subscribers.push(new YAHOO.util.Subscriber(B,C,A));},unsubscribe:function(D,F){if(!D){return this.unsubscribeAll();}var E=false;for(var B=0,A=this.subscribers.length;B<A;++B){var C=this.subscribers[B];if(C&&C.contains(D,F)){this._delete(B);E=true;}}return E;},fire:function(){var E=this.subscribers.length;if(!E&&this.silent){return true;}var H=[],G=true,D,I=false;for(D=0;D<arguments.length;++D){H.push(arguments[D]);}var A=H.length;if(!this.silent){}for(D=0;D<E;++D){var L=this.subscribers[D];if(!L){I=true;}else{if(!this.silent){}var K=L.getScope(this.scope);if(this.signature==YAHOO.util.CustomEvent.FLAT){var B=null;if(H.length>0){B=H[0];}try{G=L.fn.call(K,B,L.obj);}catch(F){this.lastError=F;}}else{try{G=L.fn.call(K,this.type,H,L.obj);}catch(F){this.lastError=F;}}if(false===G){if(!this.silent){}return false;}}}if(I){var J=[],C=this.subscribers;for(D=0,E=C.length;D<E;D=D+1){J.push(C[D]);}this.subscribers=J;}return true;},unsubscribeAll:function(){for(var B=0,A=this.subscribers.length;B<A;++B){this._delete(A-1-B);}this.subscribers=[];return B;},_delete:function(A){var B=this.subscribers[A];if(B){delete B.fn;delete B.obj;}this.subscribers[A]=null;},toString:function(){return"CustomEvent: '"+this.type+"', scope: "+this.scope;}};YAHOO.util.Subscriber=function(B,C,A){this.fn=B;this.obj=YAHOO.lang.isUndefined(C)?null:C;this.override=A;};YAHOO.util.Subscriber.prototype.getScope=function(A){if(this.override){if(this.override===true){return this.obj;}else{return this.override;}}return A;};YAHOO.util.Subscriber.prototype.contains=function(A,B){if(B){return(this.fn==A&&this.obj==B);}else{return(this.fn==A);}};YAHOO.util.Subscriber.prototype.toString=function(){return"Subscriber { obj: "+this.obj+", override: "+(this.override||"no")+" }";};if(!YAHOO.util.Event){YAHOO.util.Event=function(){var H=false;var J=false;var I=[];var K=[];var G=[];var E=[];var C=0;var F=[];var B=[];var A=0;var D={63232:38,63233:40,63234:37,63235:39};return{POLL_RETRYS:4000,POLL_INTERVAL:10,EL:0,TYPE:1,FN:2,WFN:3,UNLOAD_OBJ:3,ADJ_SCOPE:4,OBJ:5,OVERRIDE:6,lastError:null,isSafari:YAHOO.env.ua.webkit,webkit:YAHOO.env.ua.webkit,isIE:YAHOO.env.ua.ie,_interval:null,startInterval:function(){if(!this._interval){var L=this;var M=function(){L._tryPreloadAttach();};this._interval=setInterval(M,this.POLL_INTERVAL);}},onAvailable:function(N,L,O,M){F.push({id:N,fn:L,obj:O,override:M,checkReady:false});C=this.POLL_RETRYS;this.startInterval();},onDOMReady:function(L,N,M){if(J){setTimeout(function(){var O=window;if(M){if(M===true){O=N;}else{O=M;}}L.call(O,"DOMReady",[],N);},0);}else{this.DOMReadyEvent.subscribe(L,N,M);}},onContentReady:function(N,L,O,M){F.push({id:N,fn:L,obj:O,override:M,checkReady:true});C=this.POLL_RETRYS;this.startInterval();},addListener:function(N,L,W,R,M){if(!W||!W.call){return false;}if(this._isValidCollection(N)){var X=true;for(var S=0,U=N.length;S<U;++S){X=this.on(N[S],L,W,R,M)&&X;}return X;}else{if(YAHOO.lang.isString(N)){var Q=this.getEl(N);if(Q){N=Q;}else{this.onAvailable(N,function(){YAHOO.util.Event.on(N,L,W,R,M);});return true;}}}if(!N){return false;}if("unload"==L&&R!==this){K[K.length]=[N,L,W,R,M];return true;}var Z=N;if(M){if(M===true){Z=R;}else{Z=M;}}var O=function(a){return W.call(Z,YAHOO.util.Event.getEvent(a,N),R);};var Y=[N,L,W,O,Z,R,M];var T=I.length;I[T]=Y;if(this.useLegacyEvent(N,L)){var P=this.getLegacyIndex(N,L);if(P==-1||N!=G[P][0]){P=G.length;B[N.id+L]=P;G[P]=[N,L,N["on"+L]];E[P]=[];N["on"+L]=function(a){YAHOO.util.Event.fireLegacyEvent(YAHOO.util.Event.getEvent(a),P);};}E[P].push(Y);}else{try{this._simpleAdd(N,L,O,false);}catch(V){this.lastError=V;this.removeListener(N,L,W);return false;}}return true;},fireLegacyEvent:function(P,N){var R=true,L,T,S,U,Q;T=E[N];for(var M=0,O=T.length;M<O;++M){S=T[M];if(S&&S[this.WFN]){U=S[this.ADJ_SCOPE];Q=S[this.WFN].call(U,P);R=(R&&Q);}}L=G[N];if(L&&L[2]){L[2](P);}return R;},getLegacyIndex:function(M,N){var L=this.generateId(M)+N;if(typeof B[L]=="undefined"){return -1;}else{return B[L];}},useLegacyEvent:function(M,N){if(this.webkit&&("click"==N||"dblclick"==N)){var L=parseInt(this.webkit,10);if(!isNaN(L)&&L<418){return true;}}return false;},removeListener:function(M,L,U){var P,S,W;if(typeof M=="string"){M=this.getEl(M);}else{if(this._isValidCollection(M)){var V=true;for(P=0,S=M.length;P<S;++P){V=(this.removeListener(M[P],L,U)&&V);}return V;}}if(!U||!U.call){return this.purgeElement(M,false,L);}if("unload"==L){for(P=0,S=K.length;P<S;P++){W=K[P];if(W&&W[0]==M&&W[1]==L&&W[2]==U){K[P]=null;return true;}}return false;}var Q=null;var R=arguments[3];if("undefined"===typeof R){R=this._getCacheIndex(M,L,U);}if(R>=0){Q=I[R];}if(!M||!Q){return false;}if(this.useLegacyEvent(M,L)){var O=this.getLegacyIndex(M,L);var N=E[O];if(N){for(P=0,S=N.length;P<S;++P){W=N[P];if(W&&W[this.EL]==M&&W[this.TYPE]==L&&W[this.FN]==U){N[P]=null;break;}}}}else{try{this._simpleRemove(M,L,Q[this.WFN],false);}catch(T){this.lastError=T;return false;}}delete I[R][this.WFN];delete I[R][this.FN];I[R]=null;return true;},getTarget:function(N,M){var L=N.target||N.srcElement;return this.resolveTextNode(L);},resolveTextNode:function(L){if(L&&3==L.nodeType){return L.parentNode;}else{return L;}},getPageX:function(M){var L=M.pageX;if(!L&&0!==L){L=M.clientX||0;if(this.isIE){L+=this._getScrollLeft();}}return L;},getPageY:function(L){var M=L.pageY;if(!M&&0!==M){M=L.clientY||0;if(this.isIE){M+=this._getScrollTop();}}return M;},getXY:function(L){return[this.getPageX(L),this.getPageY(L)];
-},getRelatedTarget:function(M){var L=M.relatedTarget;if(!L){if(M.type=="mouseout"){L=M.toElement;}else{if(M.type=="mouseover"){L=M.fromElement;}}}return this.resolveTextNode(L);},getTime:function(N){if(!N.time){var M=new Date().getTime();try{N.time=M;}catch(L){this.lastError=L;return M;}}return N.time;},stopEvent:function(L){this.stopPropagation(L);this.preventDefault(L);},stopPropagation:function(L){if(L.stopPropagation){L.stopPropagation();}else{L.cancelBubble=true;}},preventDefault:function(L){if(L.preventDefault){L.preventDefault();}else{L.returnValue=false;}},getEvent:function(Q,O){var P=Q||window.event;if(!P){var R=this.getEvent.caller;while(R){P=R.arguments[0];if(P&&Event==P.constructor){break;}R=R.caller;}}if(P&&this.isIE){try{var N=P.srcElement;if(N){var M=N.type;}}catch(L){P.target=O;}}return P;},getCharCode:function(M){var L=M.keyCode||M.charCode||0;if(YAHOO.env.ua.webkit&&(L in D)){L=D[L];}return L;},_getCacheIndex:function(P,Q,O){for(var N=0,M=I.length;N<M;++N){var L=I[N];if(L&&L[this.FN]==O&&L[this.EL]==P&&L[this.TYPE]==Q){return N;}}return -1;},generateId:function(L){var M=L.id;if(!M){M="yuievtautoid-"+A;++A;L.id=M;}return M;},_isValidCollection:function(M){try{return(typeof M!=="string"&&M.length&&!M.tagName&&!M.alert&&typeof M[0]!=="undefined");}catch(L){return false;}},elCache:{},getEl:function(L){return(typeof L==="string")?document.getElementById(L):L;},clearCache:function(){},DOMReadyEvent:new YAHOO.util.CustomEvent("DOMReady",this),_load:function(M){if(!H){H=true;var L=YAHOO.util.Event;L._ready();L._tryPreloadAttach();}},_ready:function(M){if(!J){J=true;var L=YAHOO.util.Event;L.DOMReadyEvent.fire();L._simpleRemove(document,"DOMContentLoaded",L._ready);}},_tryPreloadAttach:function(){if(this.locked){return false;}if(this.isIE){if(!J){this.startInterval();return false;}}this.locked=true;var Q=!H;if(!Q){Q=(C>0);}var P=[];var R=function(T,U){var S=T;if(U.override){if(U.override===true){S=U.obj;}else{S=U.override;}}U.fn.call(S,U.obj);};var M,L,O,N;for(M=0,L=F.length;M<L;++M){O=F[M];if(O&&!O.checkReady){N=this.getEl(O.id);if(N){R(N,O);F[M]=null;}else{P.push(O);}}}for(M=0,L=F.length;M<L;++M){O=F[M];if(O&&O.checkReady){N=this.getEl(O.id);if(N){if(H||N.nextSibling){R(N,O);F[M]=null;}}else{P.push(O);}}}C=(P.length===0)?0:C-1;if(Q){this.startInterval();}else{clearInterval(this._interval);this._interval=null;}this.locked=false;return true;},purgeElement:function(O,P,R){var Q=this.getListeners(O,R),N,L;if(Q){for(N=0,L=Q.length;N<L;++N){var M=Q[N];this.removeListener(O,M.type,M.fn,M.index);}}if(P&&O&&O.childNodes){for(N=0,L=O.childNodes.length;N<L;++N){this.purgeElement(O.childNodes[N],P,R);}}},getListeners:function(N,L){var Q=[],M;if(!L){M=[I,K];}else{if(L=="unload"){M=[K];}else{M=[I];}}for(var P=0;P<M.length;P=P+1){var T=M[P];if(T&&T.length>0){for(var R=0,S=T.length;R<S;++R){var O=T[R];if(O&&O[this.EL]===N&&(!L||L===O[this.TYPE])){Q.push({type:O[this.TYPE],fn:O[this.FN],obj:O[this.OBJ],adjust:O[this.OVERRIDE],scope:O[this.ADJ_SCOPE],index:R});}}}}return(Q.length)?Q:null;},_unload:function(S){var R=YAHOO.util.Event,P,O,M,L,N;for(P=0,L=K.length;P<L;++P){M=K[P];if(M){var Q=window;if(M[R.ADJ_SCOPE]){if(M[R.ADJ_SCOPE]===true){Q=M[R.UNLOAD_OBJ];}else{Q=M[R.ADJ_SCOPE];}}M[R.FN].call(Q,R.getEvent(S,M[R.EL]),M[R.UNLOAD_OBJ]);K[P]=null;M=null;Q=null;}}K=null;if(I&&I.length>0){O=I.length;while(O){N=O-1;M=I[N];if(M){R.removeListener(M[R.EL],M[R.TYPE],M[R.FN],N);}O=O-1;}M=null;R.clearCache();}for(P=0,L=G.length;P<L;++P){G[P][0]=null;G[P]=null;}G=null;R._simpleRemove(window,"unload",R._unload);},_getScrollLeft:function(){return this._getScroll()[1];},_getScrollTop:function(){return this._getScroll()[0];},_getScroll:function(){var L=document.documentElement,M=document.body;if(L&&(L.scrollTop||L.scrollLeft)){return[L.scrollTop,L.scrollLeft];}else{if(M){return[M.scrollTop,M.scrollLeft];}else{return[0,0];}}},regCE:function(){},_simpleAdd:function(){if(window.addEventListener){return function(N,O,M,L){N.addEventListener(O,M,(L));};}else{if(window.attachEvent){return function(N,O,M,L){N.attachEvent("on"+O,M);};}else{return function(){};}}}(),_simpleRemove:function(){if(window.removeEventListener){return function(N,O,M,L){N.removeEventListener(O,M,(L));};}else{if(window.detachEvent){return function(M,N,L){M.detachEvent("on"+N,L);};}else{return function(){};}}}()};}();(function(){var D=YAHOO.util.Event;D.on=D.addListener;if(D.isIE){YAHOO.util.Event.onDOMReady(YAHOO.util.Event._tryPreloadAttach,YAHOO.util.Event,true);var B,E=document,A=E.body;if(("undefined"!==typeof YAHOO_config)&&YAHOO_config.injecting){B=document.createElement("script");var C=E.getElementsByTagName("head")[0]||A;C.insertBefore(B,C.firstChild);}else{E.write("<script id=\"_yui_eu_dr\" defer=\"true\" src=\"//:\"></script>");B=document.getElementById("_yui_eu_dr");}if(B){B.onreadystatechange=function(){if("complete"===this.readyState){this.parentNode.removeChild(this);YAHOO.util.Event._ready();}};}else{}B=null;}else{if(D.webkit){D._drwatch=setInterval(function(){var F=document.readyState;if("loaded"==F||"complete"==F){clearInterval(D._drwatch);D._drwatch=null;D._ready();}},D.POLL_INTERVAL);}else{D._simpleAdd(document,"DOMContentLoaded",D._ready);}}D._simpleAdd(window,"load",D._load);D._simpleAdd(window,"unload",D._unload);D._tryPreloadAttach();})();}YAHOO.util.EventProvider=function(){};YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(A,C,F,E){this.__yui_events=this.__yui_events||{};var D=this.__yui_events[A];if(D){D.subscribe(C,F,E);}else{this.__yui_subscribers=this.__yui_subscribers||{};var B=this.__yui_subscribers;if(!B[A]){B[A]=[];}B[A].push({fn:C,obj:F,override:E});}},unsubscribe:function(C,E,G){this.__yui_events=this.__yui_events||{};var A=this.__yui_events;if(C){var F=A[C];if(F){return F.unsubscribe(E,G);}}else{var B=true;for(var D in A){if(YAHOO.lang.hasOwnProperty(A,D)){B=B&&A[D].unsubscribe(E,G);}}return B;}return false;},unsubscribeAll:function(A){return this.unsubscribe(A);},createEvent:function(G,D){this.__yui_events=this.__yui_events||{};
-var A=D||{};var I=this.__yui_events;if(I[G]){}else{var H=A.scope||this;var E=(A.silent);var B=new YAHOO.util.CustomEvent(G,H,E,YAHOO.util.CustomEvent.FLAT);I[G]=B;if(A.onSubscribeCallback){B.subscribeEvent.subscribe(A.onSubscribeCallback);}this.__yui_subscribers=this.__yui_subscribers||{};var F=this.__yui_subscribers[G];if(F){for(var C=0;C<F.length;++C){B.subscribe(F[C].fn,F[C].obj,F[C].override);}}}return I[G];},fireEvent:function(E,D,A,C){this.__yui_events=this.__yui_events||{};var G=this.__yui_events[E];if(!G){return null;}var B=[];for(var F=1;F<arguments.length;++F){B.push(arguments[F]);}return G.fire.apply(G,B);},hasEvent:function(A){if(this.__yui_events){if(this.__yui_events[A]){return true;}}return false;}};YAHOO.util.KeyListener=function(A,F,B,C){if(!A){}else{if(!F){}else{if(!B){}}}if(!C){C=YAHOO.util.KeyListener.KEYDOWN;}var D=new YAHOO.util.CustomEvent("keyPressed");this.enabledEvent=new YAHOO.util.CustomEvent("enabled");this.disabledEvent=new YAHOO.util.CustomEvent("disabled");if(typeof A=="string"){A=document.getElementById(A);}if(typeof B=="function"){D.subscribe(B);}else{D.subscribe(B.fn,B.scope,B.correctScope);}function E(K,J){if(!F.shift){F.shift=false;}if(!F.alt){F.alt=false;}if(!F.ctrl){F.ctrl=false;}if(K.shiftKey==F.shift&&K.altKey==F.alt&&K.ctrlKey==F.ctrl){var H;var G;if(F.keys instanceof Array){for(var I=0;I<F.keys.length;I++){H=F.keys[I];if(H==K.charCode){D.fire(K.charCode,K);break;}else{if(H==K.keyCode){D.fire(K.keyCode,K);break;}}}}else{H=F.keys;if(H==K.charCode){D.fire(K.charCode,K);}else{if(H==K.keyCode){D.fire(K.keyCode,K);}}}}}this.enable=function(){if(!this.enabled){YAHOO.util.Event.addListener(A,C,E);this.enabledEvent.fire(F);}this.enabled=true;};this.disable=function(){if(this.enabled){YAHOO.util.Event.removeListener(A,C,E);this.disabledEvent.fire(F);}this.enabled=false;};this.toString=function(){return"KeyListener ["+F.keys+"] "+A.tagName+(A.id?"["+A.id+"]":"");};};YAHOO.util.KeyListener.KEYDOWN="keydown";YAHOO.util.KeyListener.KEYUP="keyup";YAHOO.register("event",YAHOO.util.Event,{version:"2.3.1",build:"541"});YAHOO.register("yahoo-dom-event", YAHOO, {version: "2.3.1", build: "541"});
diff --git a/Websites/bugs.webkit.org/js/yui/yahoo-dom-event/yahoo-dom-event.js b/Websites/bugs.webkit.org/js/yui/yahoo-dom-event/yahoo-dom-event.js
new file mode 100644
index 0000000..46c58bf
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/yahoo-dom-event/yahoo-dom-event.js
@@ -0,0 +1,14 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+if(typeof YAHOO=="undefined"||!YAHOO){var YAHOO={};}YAHOO.namespace=function(){var b=arguments,g=null,e,c,f;for(e=0;e<b.length;e=e+1){f=(""+b[e]).split(".");g=YAHOO;for(c=(f[0]=="YAHOO")?1:0;c<f.length;c=c+1){g[f[c]]=g[f[c]]||{};g=g[f[c]];}}return g;};YAHOO.log=function(d,a,c){var b=YAHOO.widget.Logger;if(b&&b.log){return b.log(d,a,c);}else{return false;}};YAHOO.register=function(a,f,e){var k=YAHOO.env.modules,c,j,h,g,d;if(!k[a]){k[a]={versions:[],builds:[]};}c=k[a];j=e.version;h=e.build;g=YAHOO.env.listeners;c.name=a;c.version=j;c.build=h;c.versions.push(j);c.builds.push(h);c.mainClass=f;for(d=0;d<g.length;d=d+1){g[d](c);}if(f){f.VERSION=j;f.BUILD=h;}else{YAHOO.log("mainClass is undefined for module "+a,"warn");}};YAHOO.env=YAHOO.env||{modules:[],listeners:[]};YAHOO.env.getVersion=function(a){return YAHOO.env.modules[a]||null;};YAHOO.env.parseUA=function(d){var e=function(i){var j=0;return parseFloat(i.replace(/\./g,function(){return(j++==1)?"":".";}));},h=navigator,g={ie:0,opera:0,gecko:0,webkit:0,chrome:0,mobile:null,air:0,ipad:0,iphone:0,ipod:0,ios:null,android:0,webos:0,caja:h&&h.cajaVersion,secure:false,os:null},c=d||(navigator&&navigator.userAgent),f=window&&window.location,b=f&&f.href,a;g.secure=b&&(b.toLowerCase().indexOf("https")===0);if(c){if((/windows|win32/i).test(c)){g.os="windows";}else{if((/macintosh/i).test(c)){g.os="macintosh";}else{if((/rhino/i).test(c)){g.os="rhino";}}}if((/KHTML/).test(c)){g.webkit=1;}a=c.match(/AppleWebKit\/([^\s]*)/);if(a&&a[1]){g.webkit=e(a[1]);if(/ Mobile\//.test(c)){g.mobile="Apple";a=c.match(/OS ([^\s]*)/);if(a&&a[1]){a=e(a[1].replace("_","."));}g.ios=a;g.ipad=g.ipod=g.iphone=0;a=c.match(/iPad|iPod|iPhone/);if(a&&a[0]){g[a[0].toLowerCase()]=g.ios;}}else{a=c.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/);if(a){g.mobile=a[0];}if(/webOS/.test(c)){g.mobile="WebOS";a=c.match(/webOS\/([^\s]*);/);if(a&&a[1]){g.webos=e(a[1]);}}if(/ Android/.test(c)){g.mobile="Android";a=c.match(/Android ([^\s]*);/);if(a&&a[1]){g.android=e(a[1]);}}}a=c.match(/Chrome\/([^\s]*)/);if(a&&a[1]){g.chrome=e(a[1]);}else{a=c.match(/AdobeAIR\/([^\s]*)/);if(a){g.air=a[0];}}}if(!g.webkit){a=c.match(/Opera[\s\/]([^\s]*)/);if(a&&a[1]){g.opera=e(a[1]);a=c.match(/Version\/([^\s]*)/);if(a&&a[1]){g.opera=e(a[1]);}a=c.match(/Opera Mini[^;]*/);if(a){g.mobile=a[0];}}else{a=c.match(/MSIE\s([^;]*)/);if(a&&a[1]){g.ie=e(a[1]);}else{a=c.match(/Gecko\/([^\s]*)/);if(a){g.gecko=1;a=c.match(/rv:([^\s\)]*)/);if(a&&a[1]){g.gecko=e(a[1]);}}}}}}return g;};YAHOO.env.ua=YAHOO.env.parseUA();(function(){YAHOO.namespace("util","widget","example");if("undefined"!==typeof YAHOO_config){var b=YAHOO_config.listener,a=YAHOO.env.listeners,d=true,c;if(b){for(c=0;c<a.length;c++){if(a[c]==b){d=false;break;}}if(d){a.push(b);}}}})();YAHOO.lang=YAHOO.lang||{};(function(){var f=YAHOO.lang,a=Object.prototype,c="[object Array]",h="[object Function]",i="[object Object]",b=[],g={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`"},d=["toString","valueOf"],e={isArray:function(j){return a.toString.apply(j)===c;},isBoolean:function(j){return typeof j==="boolean";},isFunction:function(j){return(typeof j==="function")||a.toString.apply(j)===h;},isNull:function(j){return j===null;},isNumber:function(j){return typeof j==="number"&&isFinite(j);},isObject:function(j){return(j&&(typeof j==="object"||f.isFunction(j)))||false;},isString:function(j){return typeof j==="string";},isUndefined:function(j){return typeof j==="undefined";},_IEEnumFix:(YAHOO.env.ua.ie)?function(l,k){var j,n,m;for(j=0;j<d.length;j=j+1){n=d[j];m=k[n];if(f.isFunction(m)&&m!=a[n]){l[n]=m;}}}:function(){},escapeHTML:function(j){return j.replace(/[&<>"'\/`]/g,function(k){return g[k];});},extend:function(m,n,l){if(!n||!m){throw new Error("extend failed, please check that "+"all dependencies are included.");}var k=function(){},j;k.prototype=n.prototype;m.prototype=new k();m.prototype.constructor=m;m.superclass=n.prototype;if(n.prototype.constructor==a.constructor){n.prototype.constructor=n;}if(l){for(j in l){if(f.hasOwnProperty(l,j)){m.prototype[j]=l[j];}}f._IEEnumFix(m.prototype,l);}},augmentObject:function(n,m){if(!m||!n){throw new Error("Absorb failed, verify dependencies.");}var j=arguments,l,o,k=j[2];if(k&&k!==true){for(l=2;l<j.length;l=l+1){n[j[l]]=m[j[l]];}}else{for(o in m){if(k||!(o in n)){n[o]=m[o];}}f._IEEnumFix(n,m);}return n;},augmentProto:function(m,l){if(!l||!m){throw new Error("Augment failed, verify dependencies.");}var j=[m.prototype,l.prototype],k;for(k=2;k<arguments.length;k=k+1){j.push(arguments[k]);}f.augmentObject.apply(this,j);return m;},dump:function(j,p){var l,n,r=[],t="{...}",k="f(){...}",q=", ",m=" => ";if(!f.isObject(j)){return j+"";}else{if(j instanceof Date||("nodeType" in j&&"tagName" in j)){return j;}else{if(f.isFunction(j)){return k;}}}p=(f.isNumber(p))?p:3;if(f.isArray(j)){r.push("[");for(l=0,n=j.length;l<n;l=l+1){if(f.isObject(j[l])){r.push((p>0)?f.dump(j[l],p-1):t);}else{r.push(j[l]);}r.push(q);}if(r.length>1){r.pop();}r.push("]");}else{r.push("{");for(l in j){if(f.hasOwnProperty(j,l)){r.push(l+m);if(f.isObject(j[l])){r.push((p>0)?f.dump(j[l],p-1):t);}else{r.push(j[l]);}r.push(q);}}if(r.length>1){r.pop();}r.push("}");}return r.join("");},substitute:function(x,y,E,l){var D,C,B,G,t,u,F=[],p,z=x.length,A="dump",r=" ",q="{",m="}",n,w;for(;;){D=x.lastIndexOf(q,z);if(D<0){break;}C=x.indexOf(m,D);if(D+1>C){break;}p=x.substring(D+1,C);G=p;u=null;B=G.indexOf(r);if(B>-1){u=G.substring(B+1);G=G.substring(0,B);}t=y[G];if(E){t=E(G,t,u);}if(f.isObject(t)){if(f.isArray(t)){t=f.dump(t,parseInt(u,10));}else{u=u||"";n=u.indexOf(A);if(n>-1){u=u.substring(4);}w=t.toString();if(w===i||n>-1){t=f.dump(t,parseInt(u,10));}else{t=w;}}}else{if(!f.isString(t)&&!f.isNumber(t)){t="~-"+F.length+"-~";F[F.length]=p;}}x=x.substring(0,D)+t+x.substring(C+1);if(l===false){z=D-1;}}for(D=F.length-1;D>=0;D=D-1){x=x.replace(new RegExp("~-"+D+"-~"),"{"+F[D]+"}","g");}return x;},trim:function(j){try{return j.replace(/^\s+|\s+$/g,"");}catch(k){return j;
+}},merge:function(){var n={},k=arguments,j=k.length,m;for(m=0;m<j;m=m+1){f.augmentObject(n,k[m],true);}return n;},later:function(t,k,u,n,p){t=t||0;k=k||{};var l=u,s=n,q,j;if(f.isString(u)){l=k[u];}if(!l){throw new TypeError("method undefined");}if(!f.isUndefined(n)&&!f.isArray(s)){s=[n];}q=function(){l.apply(k,s||b);};j=(p)?setInterval(q,t):setTimeout(q,t);return{interval:p,cancel:function(){if(this.interval){clearInterval(j);}else{clearTimeout(j);}}};},isValue:function(j){return(f.isObject(j)||f.isString(j)||f.isNumber(j)||f.isBoolean(j));}};f.hasOwnProperty=(a.hasOwnProperty)?function(j,k){return j&&j.hasOwnProperty&&j.hasOwnProperty(k);}:function(j,k){return !f.isUndefined(j[k])&&j.constructor.prototype[k]!==j[k];};e.augmentObject(f,e,true);YAHOO.util.Lang=f;f.augment=f.augmentProto;YAHOO.augment=f.augmentProto;YAHOO.extend=f.extend;})();YAHOO.register("yahoo",YAHOO,{version:"2.9.0",build:"2800"});(function(){YAHOO.env._id_counter=YAHOO.env._id_counter||0;var e=YAHOO.util,k=YAHOO.lang,L=YAHOO.env.ua,a=YAHOO.lang.trim,B={},F={},m=/^t(?:able|d|h)$/i,w=/color$/i,j=window.document,v=j.documentElement,C="ownerDocument",M="defaultView",U="documentElement",S="compatMode",z="offsetLeft",o="offsetTop",T="offsetParent",x="parentNode",K="nodeType",c="tagName",n="scrollLeft",H="scrollTop",p="getBoundingClientRect",V="getComputedStyle",y="currentStyle",l="CSS1Compat",A="BackCompat",E="class",f="className",i="",b=" ",R="(?:^|\\s)",J="(?= |$)",t="g",O="position",D="fixed",u="relative",I="left",N="top",Q="medium",P="borderLeftWidth",q="borderTopWidth",d=L.opera,h=L.webkit,g=L.gecko,s=L.ie;e.Dom={CUSTOM_ATTRIBUTES:(!v.hasAttribute)?{"for":"htmlFor","class":f}:{"htmlFor":"for","className":E},DOT_ATTRIBUTES:{checked:true},get:function(aa){var ac,X,ab,Z,W,G,Y=null;if(aa){if(typeof aa=="string"||typeof aa=="number"){ac=aa+"";aa=j.getElementById(aa);G=(aa)?aa.attributes:null;if(aa&&G&&G.id&&G.id.value===ac){return aa;}else{if(aa&&j.all){aa=null;X=j.all[ac];if(X&&X.length){for(Z=0,W=X.length;Z<W;++Z){if(X[Z].id===ac){return X[Z];}}}}}}else{if(e.Element&&aa instanceof e.Element){aa=aa.get("element");}else{if(!aa.nodeType&&"length" in aa){ab=[];for(Z=0,W=aa.length;Z<W;++Z){ab[ab.length]=e.Dom.get(aa[Z]);}aa=ab;}}}Y=aa;}return Y;},getComputedStyle:function(G,W){if(window[V]){return G[C][M][V](G,null)[W];}else{if(G[y]){return e.Dom.IE_ComputedStyle.get(G,W);}}},getStyle:function(G,W){return e.Dom.batch(G,e.Dom._getStyle,W);},_getStyle:function(){if(window[V]){return function(G,Y){Y=(Y==="float")?Y="cssFloat":e.Dom._toCamel(Y);var X=G.style[Y],W;if(!X){W=G[C][M][V](G,null);if(W){X=W[Y];}}return X;};}else{if(v[y]){return function(G,Y){var X;switch(Y){case"opacity":X=100;try{X=G.filters["DXImageTransform.Microsoft.Alpha"].opacity;}catch(Z){try{X=G.filters("alpha").opacity;}catch(W){}}return X/100;case"float":Y="styleFloat";default:Y=e.Dom._toCamel(Y);X=G[y]?G[y][Y]:null;return(G.style[Y]||X);}};}}}(),setStyle:function(G,W,X){e.Dom.batch(G,e.Dom._setStyle,{prop:W,val:X});},_setStyle:function(){if(!window.getComputedStyle&&j.documentElement.currentStyle){return function(W,G){var X=e.Dom._toCamel(G.prop),Y=G.val;if(W){switch(X){case"opacity":if(Y===""||Y===null||Y===1){W.style.removeAttribute("filter");}else{if(k.isString(W.style.filter)){W.style.filter="alpha(opacity="+Y*100+")";if(!W[y]||!W[y].hasLayout){W.style.zoom=1;}}}break;case"float":X="styleFloat";default:W.style[X]=Y;}}else{}};}else{return function(W,G){var X=e.Dom._toCamel(G.prop),Y=G.val;if(W){if(X=="float"){X="cssFloat";}W.style[X]=Y;}else{}};}}(),getXY:function(G){return e.Dom.batch(G,e.Dom._getXY);},_canPosition:function(G){return(e.Dom._getStyle(G,"display")!=="none"&&e.Dom._inDoc(G));},_getXY:function(W){var X,G,Z,ab,Y,aa,ac=Math.round,ad=false;if(e.Dom._canPosition(W)){Z=W[p]();ab=W[C];X=e.Dom.getDocumentScrollLeft(ab);G=e.Dom.getDocumentScrollTop(ab);ad=[Z[I],Z[N]];if(Y||aa){ad[0]-=aa;ad[1]-=Y;}if((G||X)){ad[0]+=X;ad[1]+=G;}ad[0]=ac(ad[0]);ad[1]=ac(ad[1]);}else{}return ad;},getX:function(G){var W=function(X){return e.Dom.getXY(X)[0];};return e.Dom.batch(G,W,e.Dom,true);},getY:function(G){var W=function(X){return e.Dom.getXY(X)[1];};return e.Dom.batch(G,W,e.Dom,true);},setXY:function(G,X,W){e.Dom.batch(G,e.Dom._setXY,{pos:X,noRetry:W});},_setXY:function(G,Z){var aa=e.Dom._getStyle(G,O),Y=e.Dom.setStyle,ad=Z.pos,W=Z.noRetry,ab=[parseInt(e.Dom.getComputedStyle(G,I),10),parseInt(e.Dom.getComputedStyle(G,N),10)],ac,X;ac=e.Dom._getXY(G);if(!ad||ac===false){return false;}if(aa=="static"){aa=u;Y(G,O,aa);}if(isNaN(ab[0])){ab[0]=(aa==u)?0:G[z];}if(isNaN(ab[1])){ab[1]=(aa==u)?0:G[o];}if(ad[0]!==null){Y(G,I,ad[0]-ac[0]+ab[0]+"px");}if(ad[1]!==null){Y(G,N,ad[1]-ac[1]+ab[1]+"px");}if(!W){X=e.Dom._getXY(G);if((ad[0]!==null&&X[0]!=ad[0])||(ad[1]!==null&&X[1]!=ad[1])){e.Dom._setXY(G,{pos:ad,noRetry:true});}}},setX:function(W,G){e.Dom.setXY(W,[G,null]);},setY:function(G,W){e.Dom.setXY(G,[null,W]);},getRegion:function(G){var W=function(X){var Y=false;if(e.Dom._canPosition(X)){Y=e.Region.getRegion(X);}else{}return Y;};return e.Dom.batch(G,W,e.Dom,true);},getClientWidth:function(){return e.Dom.getViewportWidth();},getClientHeight:function(){return e.Dom.getViewportHeight();},getElementsByClassName:function(ab,af,ac,ae,X,ad){af=af||"*";ac=(ac)?e.Dom.get(ac):null||j;if(!ac){return[];}var W=[],G=ac.getElementsByTagName(af),Z=e.Dom.hasClass;for(var Y=0,aa=G.length;Y<aa;++Y){if(Z(G[Y],ab)){W[W.length]=G[Y];}}if(ae){e.Dom.batch(W,ae,X,ad);}return W;},hasClass:function(W,G){return e.Dom.batch(W,e.Dom._hasClass,G);},_hasClass:function(X,W){var G=false,Y;if(X&&W){Y=e.Dom._getAttribute(X,f)||i;if(Y){Y=Y.replace(/\s+/g,b);}if(W.exec){G=W.test(Y);}else{G=W&&(b+Y+b).indexOf(b+W+b)>-1;}}else{}return G;},addClass:function(W,G){return e.Dom.batch(W,e.Dom._addClass,G);},_addClass:function(X,W){var G=false,Y;if(X&&W){Y=e.Dom._getAttribute(X,f)||i;if(!e.Dom._hasClass(X,W)){e.Dom.setAttribute(X,f,a(Y+b+W));G=true;}}else{}return G;},removeClass:function(W,G){return e.Dom.batch(W,e.Dom._removeClass,G);},_removeClass:function(Y,X){var W=false,aa,Z,G;if(Y&&X){aa=e.Dom._getAttribute(Y,f)||i;e.Dom.setAttribute(Y,f,aa.replace(e.Dom._getClassRegex(X),i));Z=e.Dom._getAttribute(Y,f);if(aa!==Z){e.Dom.setAttribute(Y,f,a(Z));W=true;if(e.Dom._getAttribute(Y,f)===""){G=(Y.hasAttribute&&Y.hasAttribute(E))?E:f;Y.removeAttribute(G);}}}else{}return W;},replaceClass:function(X,W,G){return e.Dom.batch(X,e.Dom._replaceClass,{from:W,to:G});},_replaceClass:function(Y,X){var W,ab,aa,G=false,Z;if(Y&&X){ab=X.from;aa=X.to;if(!aa){G=false;}else{if(!ab){G=e.Dom._addClass(Y,X.to);}else{if(ab!==aa){Z=e.Dom._getAttribute(Y,f)||i;W=(b+Z.replace(e.Dom._getClassRegex(ab),b+aa).replace(/\s+/g,b)).split(e.Dom._getClassRegex(aa));W.splice(1,0,b+aa);e.Dom.setAttribute(Y,f,a(W.join(i)));G=true;}}}}else{}return G;},generateId:function(G,X){X=X||"yui-gen";var W=function(Y){if(Y&&Y.id){return Y.id;}var Z=X+YAHOO.env._id_counter++;
+if(Y){if(Y[C]&&Y[C].getElementById(Z)){return e.Dom.generateId(Y,Z+X);}Y.id=Z;}return Z;};return e.Dom.batch(G,W,e.Dom,true)||W.apply(e.Dom,arguments);},isAncestor:function(W,X){W=e.Dom.get(W);X=e.Dom.get(X);var G=false;if((W&&X)&&(W[K]&&X[K])){if(W.contains&&W!==X){G=W.contains(X);}else{if(W.compareDocumentPosition){G=!!(W.compareDocumentPosition(X)&16);}}}else{}return G;},inDocument:function(G,W){return e.Dom._inDoc(e.Dom.get(G),W);},_inDoc:function(W,X){var G=false;if(W&&W[c]){X=X||W[C];G=e.Dom.isAncestor(X[U],W);}else{}return G;},getElementsBy:function(W,af,ab,ad,X,ac,ae){af=af||"*";ab=(ab)?e.Dom.get(ab):null||j;var aa=(ae)?null:[],G;if(ab){G=ab.getElementsByTagName(af);for(var Y=0,Z=G.length;Y<Z;++Y){if(W(G[Y])){if(ae){aa=G[Y];break;}else{aa[aa.length]=G[Y];}}}if(ad){e.Dom.batch(aa,ad,X,ac);}}return aa;},getElementBy:function(X,G,W){return e.Dom.getElementsBy(X,G,W,null,null,null,true);},batch:function(X,ab,aa,Z){var Y=[],W=(Z)?aa:null;X=(X&&(X[c]||X.item))?X:e.Dom.get(X);if(X&&ab){if(X[c]||X.length===undefined){return ab.call(W,X,aa);}for(var G=0;G<X.length;++G){Y[Y.length]=ab.call(W||X[G],X[G],aa);}}else{return false;}return Y;},getDocumentHeight:function(){var W=(j[S]!=l||h)?j.body.scrollHeight:v.scrollHeight,G=Math.max(W,e.Dom.getViewportHeight());return G;},getDocumentWidth:function(){var W=(j[S]!=l||h)?j.body.scrollWidth:v.scrollWidth,G=Math.max(W,e.Dom.getViewportWidth());return G;},getViewportHeight:function(){var G=self.innerHeight,W=j[S];if((W||s)&&!d){G=(W==l)?v.clientHeight:j.body.clientHeight;}return G;},getViewportWidth:function(){var G=self.innerWidth,W=j[S];if(W||s){G=(W==l)?v.clientWidth:j.body.clientWidth;}return G;},getAncestorBy:function(G,W){while((G=G[x])){if(e.Dom._testElement(G,W)){return G;}}return null;},getAncestorByClassName:function(W,G){W=e.Dom.get(W);if(!W){return null;}var X=function(Y){return e.Dom.hasClass(Y,G);};return e.Dom.getAncestorBy(W,X);},getAncestorByTagName:function(W,G){W=e.Dom.get(W);if(!W){return null;}var X=function(Y){return Y[c]&&Y[c].toUpperCase()==G.toUpperCase();};return e.Dom.getAncestorBy(W,X);},getPreviousSiblingBy:function(G,W){while(G){G=G.previousSibling;if(e.Dom._testElement(G,W)){return G;}}return null;},getPreviousSibling:function(G){G=e.Dom.get(G);if(!G){return null;}return e.Dom.getPreviousSiblingBy(G);},getNextSiblingBy:function(G,W){while(G){G=G.nextSibling;if(e.Dom._testElement(G,W)){return G;}}return null;},getNextSibling:function(G){G=e.Dom.get(G);if(!G){return null;}return e.Dom.getNextSiblingBy(G);},getFirstChildBy:function(G,X){var W=(e.Dom._testElement(G.firstChild,X))?G.firstChild:null;return W||e.Dom.getNextSiblingBy(G.firstChild,X);},getFirstChild:function(G,W){G=e.Dom.get(G);if(!G){return null;}return e.Dom.getFirstChildBy(G);},getLastChildBy:function(G,X){if(!G){return null;}var W=(e.Dom._testElement(G.lastChild,X))?G.lastChild:null;return W||e.Dom.getPreviousSiblingBy(G.lastChild,X);},getLastChild:function(G){G=e.Dom.get(G);return e.Dom.getLastChildBy(G);},getChildrenBy:function(W,Y){var X=e.Dom.getFirstChildBy(W,Y),G=X?[X]:[];e.Dom.getNextSiblingBy(X,function(Z){if(!Y||Y(Z)){G[G.length]=Z;}return false;});return G;},getChildren:function(G){G=e.Dom.get(G);if(!G){}return e.Dom.getChildrenBy(G);},getDocumentScrollLeft:function(G){G=G||j;return Math.max(G[U].scrollLeft,G.body.scrollLeft);},getDocumentScrollTop:function(G){G=G||j;return Math.max(G[U].scrollTop,G.body.scrollTop);},insertBefore:function(W,G){W=e.Dom.get(W);G=e.Dom.get(G);if(!W||!G||!G[x]){return null;}return G[x].insertBefore(W,G);},insertAfter:function(W,G){W=e.Dom.get(W);G=e.Dom.get(G);if(!W||!G||!G[x]){return null;}if(G.nextSibling){return G[x].insertBefore(W,G.nextSibling);}else{return G[x].appendChild(W);}},getClientRegion:function(){var X=e.Dom.getDocumentScrollTop(),W=e.Dom.getDocumentScrollLeft(),Y=e.Dom.getViewportWidth()+W,G=e.Dom.getViewportHeight()+X;return new e.Region(X,Y,G,W);},setAttribute:function(W,G,X){e.Dom.batch(W,e.Dom._setAttribute,{attr:G,val:X});},_setAttribute:function(X,W){var G=e.Dom._toCamel(W.attr),Y=W.val;if(X&&X.setAttribute){if(e.Dom.DOT_ATTRIBUTES[G]&&X.tagName&&X.tagName!="BUTTON"){X[G]=Y;}else{G=e.Dom.CUSTOM_ATTRIBUTES[G]||G;X.setAttribute(G,Y);}}else{}},getAttribute:function(W,G){return e.Dom.batch(W,e.Dom._getAttribute,G);},_getAttribute:function(W,G){var X;G=e.Dom.CUSTOM_ATTRIBUTES[G]||G;if(e.Dom.DOT_ATTRIBUTES[G]){X=W[G];}else{if(W&&"getAttribute" in W){if(/^(?:href|src)$/.test(G)){X=W.getAttribute(G,2);}else{X=W.getAttribute(G);}}else{}}return X;},_toCamel:function(W){var X=B;function G(Y,Z){return Z.toUpperCase();}return X[W]||(X[W]=W.indexOf("-")===-1?W:W.replace(/-([a-z])/gi,G));},_getClassRegex:function(W){var G;if(W!==undefined){if(W.exec){G=W;}else{G=F[W];if(!G){W=W.replace(e.Dom._patterns.CLASS_RE_TOKENS,"\\$1");W=W.replace(/\s+/g,b);G=F[W]=new RegExp(R+W+J,t);}}}return G;},_patterns:{ROOT_TAG:/^body|html$/i,CLASS_RE_TOKENS:/([\.\(\)\^\$\*\+\?\|\[\]\{\}\\])/g},_testElement:function(G,W){return G&&G[K]==1&&(!W||W(G));},_calcBorders:function(X,Y){var W=parseInt(e.Dom[V](X,q),10)||0,G=parseInt(e.Dom[V](X,P),10)||0;if(g){if(m.test(X[c])){W=0;G=0;}}Y[0]+=G;Y[1]+=W;return Y;}};var r=e.Dom[V];if(L.opera){e.Dom[V]=function(W,G){var X=r(W,G);if(w.test(G)){X=e.Dom.Color.toRGB(X);}return X;};}if(L.webkit){e.Dom[V]=function(W,G){var X=r(W,G);if(X==="rgba(0, 0, 0, 0)"){X="transparent";}return X;};}if(L.ie&&L.ie>=8){e.Dom.DOT_ATTRIBUTES.type=true;}})();YAHOO.util.Region=function(d,e,a,c){this.top=d;this.y=d;this[1]=d;this.right=e;this.bottom=a;this.left=c;this.x=c;this[0]=c;this.width=this.right-this.left;this.height=this.bottom-this.top;};YAHOO.util.Region.prototype.contains=function(a){return(a.left>=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(f){var d=Math.max(this.top,f.top),e=Math.min(this.right,f.right),a=Math.min(this.bottom,f.bottom),c=Math.max(this.left,f.left);
+if(a>=d&&e>=c){return new YAHOO.util.Region(d,e,a,c);}else{return null;}};YAHOO.util.Region.prototype.union=function(f){var d=Math.min(this.top,f.top),e=Math.max(this.right,f.right),a=Math.max(this.bottom,f.bottom),c=Math.min(this.left,f.left);return new YAHOO.util.Region(d,e,a,c);};YAHOO.util.Region.prototype.toString=function(){return("Region {"+"top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+", height: "+this.height+", width: "+this.width+"}");};YAHOO.util.Region.getRegion=function(e){var g=YAHOO.util.Dom.getXY(e),d=g[1],f=g[0]+e.offsetWidth,a=g[1]+e.offsetHeight,c=g[0];return new YAHOO.util.Region(d,f,a,c);};YAHOO.util.Point=function(a,b){if(YAHOO.lang.isArray(a)){b=a[1];a=a[0];}YAHOO.util.Point.superclass.constructor.call(this,b,a,b,a);};YAHOO.extend(YAHOO.util.Point,YAHOO.util.Region);(function(){var b=YAHOO.util,a="clientTop",f="clientLeft",j="parentNode",k="right",w="hasLayout",i="px",u="opacity",l="auto",d="borderLeftWidth",g="borderTopWidth",p="borderRightWidth",v="borderBottomWidth",s="visible",q="transparent",n="height",e="width",h="style",t="currentStyle",r=/^width|height$/,o=/^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i,m={get:function(x,z){var y="",A=x[t][z];if(z===u){y=b.Dom.getStyle(x,u);}else{if(!A||(A.indexOf&&A.indexOf(i)>-1)){y=A;}else{if(b.Dom.IE_COMPUTED[z]){y=b.Dom.IE_COMPUTED[z](x,z);}else{if(o.test(A)){y=b.Dom.IE.ComputedStyle.getPixel(x,z);}else{y=A;}}}}return y;},getOffset:function(z,E){var B=z[t][E],x=E.charAt(0).toUpperCase()+E.substr(1),C="offset"+x,y="pixel"+x,A="",D;if(B==l){D=z[C];if(D===undefined){A=0;}A=D;if(r.test(E)){z[h][E]=D;if(z[C]>D){A=D-(z[C]-D);}z[h][E]=l;}}else{if(!z[h][y]&&!z[h][E]){z[h][E]=B;}A=z[h][y];}return A+i;},getBorderWidth:function(x,z){var y=null;if(!x[t][w]){x[h].zoom=1;}switch(z){case g:y=x[a];break;case v:y=x.offsetHeight-x.clientHeight-x[a];break;case d:y=x[f];break;case p:y=x.offsetWidth-x.clientWidth-x[f];break;}return y+i;},getPixel:function(y,x){var A=null,B=y[t][k],z=y[t][x];y[h][k]=z;A=y[h].pixelRight;y[h][k]=B;return A+i;},getMargin:function(y,x){var z;if(y[t][x]==l){z=0+i;}else{z=b.Dom.IE.ComputedStyle.getPixel(y,x);}return z;},getVisibility:function(y,x){var z;while((z=y[t])&&z[x]=="inherit"){y=y[j];}return(z)?z[x]:s;},getColor:function(y,x){return b.Dom.Color.toRGB(y[t][x])||q;},getBorderColor:function(y,x){var z=y[t],A=z[x]||z.color;return b.Dom.Color.toRGB(b.Dom.Color.toHex(A));}},c={};c.top=c.right=c.bottom=c.left=c[e]=c[n]=m.getOffset;c.color=m.getColor;c[g]=c[p]=c[v]=c[d]=m.getBorderWidth;c.marginTop=c.marginRight=c.marginBottom=c.marginLeft=m.getMargin;c.visibility=m.getVisibility;c.borderColor=c.borderTopColor=c.borderRightColor=c.borderBottomColor=c.borderLeftColor=m.getBorderColor;b.Dom.IE_COMPUTED=c;b.Dom.IE_ComputedStyle=m;})();(function(){var c="toString",a=parseInt,b=RegExp,d=YAHOO.util;d.Dom.Color={KEYWORDS:{black:"000",silver:"c0c0c0",gray:"808080",white:"fff",maroon:"800000",red:"f00",purple:"800080",fuchsia:"f0f",green:"008000",lime:"0f0",olive:"808000",yellow:"ff0",navy:"000080",blue:"00f",teal:"008080",aqua:"0ff"},re_RGB:/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,re_hex:/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,re_hex3:/([0-9A-F])/gi,toRGB:function(e){if(!d.Dom.Color.re_RGB.test(e)){e=d.Dom.Color.toHex(e);}if(d.Dom.Color.re_hex.exec(e)){e="rgb("+[a(b.$1,16),a(b.$2,16),a(b.$3,16)].join(", ")+")";}return e;},toHex:function(f){f=d.Dom.Color.KEYWORDS[f]||f;if(d.Dom.Color.re_RGB.exec(f)){f=[Number(b.$1).toString(16),Number(b.$2).toString(16),Number(b.$3).toString(16)];for(var e=0;e<f.length;e++){if(f[e].length<2){f[e]="0"+f[e];}}f=f.join("");}if(f.length<6){f=f.replace(d.Dom.Color.re_hex3,"$1$1");}if(f!=="transparent"&&f.indexOf("#")<0){f="#"+f;}return f.toUpperCase();}};}());YAHOO.register("dom",YAHOO.util.Dom,{version:"2.9.0",build:"2800"});YAHOO.util.CustomEvent=function(d,c,b,a,e){this.type=d;this.scope=c||window;this.silent=b;this.fireOnce=e;this.fired=false;this.firedWith=null;this.signature=a||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var f="_YUICEOnSubscribe";if(d!==f){this.subscribeEvent=new YAHOO.util.CustomEvent(f,this,true);}this.lastError=null;};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(b,c,d){if(!b){throw new Error("Invalid callback for subscriber to '"+this.type+"'");}if(this.subscribeEvent){this.subscribeEvent.fire(b,c,d);}var a=new YAHOO.util.Subscriber(b,c,d);if(this.fireOnce&&this.fired){this.notify(a,this.firedWith);}else{this.subscribers.push(a);}},unsubscribe:function(d,f){if(!d){return this.unsubscribeAll();}var e=false;for(var b=0,a=this.subscribers.length;b<a;++b){var c=this.subscribers[b];if(c&&c.contains(d,f)){this._delete(b);e=true;}}return e;},fire:function(){this.lastError=null;var h=[],a=this.subscribers.length;var d=[].slice.call(arguments,0),c=true,f,b=false;if(this.fireOnce){if(this.fired){return true;}else{this.firedWith=d;}}this.fired=true;if(!a&&this.silent){return true;}if(!this.silent){}var e=this.subscribers.slice();for(f=0;f<a;++f){var g=e[f];if(!g||!g.fn){b=true;}else{c=this.notify(g,d);if(false===c){if(!this.silent){}break;}}}return(c!==false);},notify:function(g,c){var b,i=null,f=g.getScope(this.scope),a=YAHOO.util.Event.throwErrors;if(!this.silent){}if(this.signature==YAHOO.util.CustomEvent.FLAT){if(c.length>0){i=c[0];}try{b=g.fn.call(f,i,g.obj);}catch(h){this.lastError=h;if(a){throw h;}}}else{try{b=g.fn.call(f,this.type,c,g.obj);}catch(d){this.lastError=d;if(a){throw d;}}}return b;},unsubscribeAll:function(){var a=this.subscribers.length,b;for(b=a-1;b>-1;b--){this._delete(b);}this.subscribers=[];return a;},_delete:function(a){var b=this.subscribers[a];if(b){delete b.fn;delete b.obj;}this.subscribers.splice(a,1);},toString:function(){return"CustomEvent: "+"'"+this.type+"', "+"context: "+this.scope;}};YAHOO.util.Subscriber=function(a,b,c){this.fn=a;this.obj=YAHOO.lang.isUndefined(b)?null:b;this.overrideContext=c;};YAHOO.util.Subscriber.prototype.getScope=function(a){if(this.overrideContext){if(this.overrideContext===true){return this.obj;}else{return this.overrideContext;}}return a;};YAHOO.util.Subscriber.prototype.contains=function(a,b){if(b){return(this.fn==a&&this.obj==b);}else{return(this.fn==a);}};YAHOO.util.Subscriber.prototype.toString=function(){return"Subscriber { obj: "+this.obj+", overrideContext: "+(this.overrideContext||"no")+" }";};if(!YAHOO.util.Event){YAHOO.util.Event=function(){var g=false,h=[],j=[],a=0,e=[],b=0,c={63232:38,63233:40,63234:37,63235:39,63276:33,63277:34,25:9},d=YAHOO.env.ua.ie,f="focusin",i="focusout";return{POLL_RETRYS:500,POLL_INTERVAL:40,EL:0,TYPE:1,FN:2,WFN:3,UNLOAD_OBJ:3,ADJ_SCOPE:4,OBJ:5,OVERRIDE:6,CAPTURE:7,lastError:null,isSafari:YAHOO.env.ua.webkit,webkit:YAHOO.env.ua.webkit,isIE:d,_interval:null,_dri:null,_specialTypes:{focusin:(d?"focusin":"focus"),focusout:(d?"focusout":"blur")},DOMReady:false,throwErrors:false,startInterval:function(){if(!this._interval){this._interval=YAHOO.lang.later(this.POLL_INTERVAL,this,this._tryPreloadAttach,null,true);}},onAvailable:function(q,m,o,p,n){var k=(YAHOO.lang.isString(q))?[q]:q;for(var l=0;l<k.length;l=l+1){e.push({id:k[l],fn:m,obj:o,overrideContext:p,checkReady:n});}a=this.POLL_RETRYS;this.startInterval();},onContentReady:function(n,k,l,m){this.onAvailable(n,k,l,m,true);},onDOMReady:function(){this.DOMReadyEvent.subscribe.apply(this.DOMReadyEvent,arguments);},_addListener:function(m,k,v,p,t,y){if(!v||!v.call){return false;}if(this._isValidCollection(m)){var w=true;for(var q=0,s=m.length;q<s;++q){w=this.on(m[q],k,v,p,t)&&w;}return w;}else{if(YAHOO.lang.isString(m)){var o=this.getEl(m);if(o){m=o;}else{this.onAvailable(m,function(){YAHOO.util.Event._addListener(m,k,v,p,t,y);});return true;}}}if(!m){return false;}if("unload"==k&&p!==this){j[j.length]=[m,k,v,p,t];return true;}var l=m;if(t){if(t===true){l=p;}else{l=t;}}var n=function(z){return v.call(l,YAHOO.util.Event.getEvent(z,m),p);};var x=[m,k,v,n,l,p,t,y];var r=h.length;h[r]=x;try{this._simpleAdd(m,k,n,y);}catch(u){this.lastError=u;this.removeListener(m,k,v);return false;}return true;},_getType:function(k){return this._specialTypes[k]||k;},addListener:function(m,p,l,n,o){var k=((p==f||p==i)&&!YAHOO.env.ua.ie)?true:false;return this._addListener(m,this._getType(p),l,n,o,k);},addFocusListener:function(l,k,m,n){return this.on(l,f,k,m,n);},removeFocusListener:function(l,k){return this.removeListener(l,f,k);},addBlurListener:function(l,k,m,n){return this.on(l,i,k,m,n);},removeBlurListener:function(l,k){return this.removeListener(l,i,k);},removeListener:function(l,k,r){var m,p,u;k=this._getType(k);if(typeof l=="string"){l=this.getEl(l);}else{if(this._isValidCollection(l)){var s=true;for(m=l.length-1;m>-1;m--){s=(this.removeListener(l[m],k,r)&&s);}return s;}}if(!r||!r.call){return this.purgeElement(l,false,k);}if("unload"==k){for(m=j.length-1;m>-1;m--){u=j[m];if(u&&u[0]==l&&u[1]==k&&u[2]==r){j.splice(m,1);return true;}}return false;}var n=null;var o=arguments[3];if("undefined"===typeof o){o=this._getCacheIndex(h,l,k,r);}if(o>=0){n=h[o];}if(!l||!n){return false;}var t=n[this.CAPTURE]===true?true:false;try{this._simpleRemove(l,k,n[this.WFN],t);}catch(q){this.lastError=q;return false;}delete h[o][this.WFN];delete h[o][this.FN];h.splice(o,1);return true;},getTarget:function(m,l){var k=m.target||m.srcElement;return this.resolveTextNode(k);},resolveTextNode:function(l){try{if(l&&3==l.nodeType){return l.parentNode;}}catch(k){return null;}return l;},getPageX:function(l){var k=l.pageX;if(!k&&0!==k){k=l.clientX||0;if(this.isIE){k+=this._getScrollLeft();}}return k;},getPageY:function(k){var l=k.pageY;if(!l&&0!==l){l=k.clientY||0;if(this.isIE){l+=this._getScrollTop();}}return l;},getXY:function(k){return[this.getPageX(k),this.getPageY(k)];},getRelatedTarget:function(l){var k=l.relatedTarget;
+if(!k){if(l.type=="mouseout"){k=l.toElement;}else{if(l.type=="mouseover"){k=l.fromElement;}}}return this.resolveTextNode(k);},getTime:function(m){if(!m.time){var l=new Date().getTime();try{m.time=l;}catch(k){this.lastError=k;return l;}}return m.time;},stopEvent:function(k){this.stopPropagation(k);this.preventDefault(k);},stopPropagation:function(k){if(k.stopPropagation){k.stopPropagation();}else{k.cancelBubble=true;}},preventDefault:function(k){if(k.preventDefault){k.preventDefault();}else{k.returnValue=false;}},getEvent:function(m,k){var l=m||window.event;if(!l){var n=this.getEvent.caller;while(n){l=n.arguments[0];if(l&&Event==l.constructor){break;}n=n.caller;}}return l;},getCharCode:function(l){var k=l.keyCode||l.charCode||0;if(YAHOO.env.ua.webkit&&(k in c)){k=c[k];}return k;},_getCacheIndex:function(n,q,r,p){for(var o=0,m=n.length;o<m;o=o+1){var k=n[o];if(k&&k[this.FN]==p&&k[this.EL]==q&&k[this.TYPE]==r){return o;}}return -1;},generateId:function(k){var l=k.id;if(!l){l="yuievtautoid-"+b;++b;k.id=l;}return l;},_isValidCollection:function(l){try{return(l&&typeof l!=="string"&&l.length&&!l.tagName&&!l.alert&&typeof l[0]!=="undefined");}catch(k){return false;}},elCache:{},getEl:function(k){return(typeof k==="string")?document.getElementById(k):k;},clearCache:function(){},DOMReadyEvent:new YAHOO.util.CustomEvent("DOMReady",YAHOO,0,0,1),_load:function(l){if(!g){g=true;var k=YAHOO.util.Event;k._ready();k._tryPreloadAttach();}},_ready:function(l){var k=YAHOO.util.Event;if(!k.DOMReady){k.DOMReady=true;k.DOMReadyEvent.fire();k._simpleRemove(document,"DOMContentLoaded",k._ready);}},_tryPreloadAttach:function(){if(e.length===0){a=0;if(this._interval){this._interval.cancel();this._interval=null;}return;}if(this.locked){return;}if(this.isIE){if(!this.DOMReady){this.startInterval();return;}}this.locked=true;var q=!g;if(!q){q=(a>0&&e.length>0);}var p=[];var r=function(t,u){var s=t;if(u.overrideContext){if(u.overrideContext===true){s=u.obj;}else{s=u.overrideContext;}}u.fn.call(s,u.obj);};var l,k,o,n,m=[];for(l=0,k=e.length;l<k;l=l+1){o=e[l];if(o){n=this.getEl(o.id);if(n){if(o.checkReady){if(g||n.nextSibling||!q){m.push(o);e[l]=null;}}else{r(n,o);e[l]=null;}}else{p.push(o);}}}for(l=0,k=m.length;l<k;l=l+1){o=m[l];r(this.getEl(o.id),o);}a--;if(q){for(l=e.length-1;l>-1;l--){o=e[l];if(!o||!o.id){e.splice(l,1);}}this.startInterval();}else{if(this._interval){this._interval.cancel();this._interval=null;}}this.locked=false;},purgeElement:function(p,q,s){var n=(YAHOO.lang.isString(p))?this.getEl(p):p;var r=this.getListeners(n,s),o,k;if(r){for(o=r.length-1;o>-1;o--){var m=r[o];this.removeListener(n,m.type,m.fn);}}if(q&&n&&n.childNodes){for(o=0,k=n.childNodes.length;o<k;++o){this.purgeElement(n.childNodes[o],q,s);}}},getListeners:function(n,k){var q=[],m;if(!k){m=[h,j];}else{if(k==="unload"){m=[j];}else{k=this._getType(k);m=[h];}}var s=(YAHOO.lang.isString(n))?this.getEl(n):n;for(var p=0;p<m.length;p=p+1){var u=m[p];if(u){for(var r=0,t=u.length;r<t;++r){var o=u[r];if(o&&o[this.EL]===s&&(!k||k===o[this.TYPE])){q.push({type:o[this.TYPE],fn:o[this.FN],obj:o[this.OBJ],adjust:o[this.OVERRIDE],scope:o[this.ADJ_SCOPE],index:r});}}}}return(q.length)?q:null;},_unload:function(s){var m=YAHOO.util.Event,p,o,n,r,q,t=j.slice(),k;for(p=0,r=j.length;p<r;++p){n=t[p];if(n){try{k=window;if(n[m.ADJ_SCOPE]){if(n[m.ADJ_SCOPE]===true){k=n[m.UNLOAD_OBJ];}else{k=n[m.ADJ_SCOPE];}}n[m.FN].call(k,m.getEvent(s,n[m.EL]),n[m.UNLOAD_OBJ]);}catch(w){}t[p]=null;}}n=null;k=null;j=null;if(h){for(o=h.length-1;o>-1;o--){n=h[o];if(n){try{m.removeListener(n[m.EL],n[m.TYPE],n[m.FN],o);}catch(v){}}}n=null;}try{m._simpleRemove(window,"unload",m._unload);m._simpleRemove(window,"load",m._load);}catch(u){}},_getScrollLeft:function(){return this._getScroll()[1];},_getScrollTop:function(){return this._getScroll()[0];},_getScroll:function(){var k=document.documentElement,l=document.body;if(k&&(k.scrollTop||k.scrollLeft)){return[k.scrollTop,k.scrollLeft];}else{if(l){return[l.scrollTop,l.scrollLeft];}else{return[0,0];}}},regCE:function(){},_simpleAdd:function(){if(window.addEventListener){return function(m,n,l,k){m.addEventListener(n,l,(k));};}else{if(window.attachEvent){return function(m,n,l,k){m.attachEvent("on"+n,l);};}else{return function(){};}}}(),_simpleRemove:function(){if(window.removeEventListener){return function(m,n,l,k){m.removeEventListener(n,l,(k));};}else{if(window.detachEvent){return function(l,m,k){l.detachEvent("on"+m,k);};}else{return function(){};}}}()};}();(function(){var a=YAHOO.util.Event;a.on=a.addListener;a.onFocus=a.addFocusListener;a.onBlur=a.addBlurListener;
+/*! DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller/Diego Perini */
+if(a.isIE){if(self!==self.top){document.onreadystatechange=function(){if(document.readyState=="complete"){document.onreadystatechange=null;a._ready();}};}else{YAHOO.util.Event.onDOMReady(YAHOO.util.Event._tryPreloadAttach,YAHOO.util.Event,true);var b=document.createElement("p");a._dri=setInterval(function(){try{b.doScroll("left");clearInterval(a._dri);a._dri=null;a._ready();b=null;}catch(c){}},a.POLL_INTERVAL);}}else{if(a.webkit&&a.webkit<525){a._dri=setInterval(function(){var c=document.readyState;if("loaded"==c||"complete"==c){clearInterval(a._dri);a._dri=null;a._ready();}},a.POLL_INTERVAL);}else{a._simpleAdd(document,"DOMContentLoaded",a._ready);}}a._simpleAdd(window,"load",a._load);a._simpleAdd(window,"unload",a._unload);a._tryPreloadAttach();})();}YAHOO.util.EventProvider=function(){};YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(a,c,f,e){this.__yui_events=this.__yui_events||{};var d=this.__yui_events[a];if(d){d.subscribe(c,f,e);}else{this.__yui_subscribers=this.__yui_subscribers||{};var b=this.__yui_subscribers;if(!b[a]){b[a]=[];}b[a].push({fn:c,obj:f,overrideContext:e});}},unsubscribe:function(c,e,g){this.__yui_events=this.__yui_events||{};var a=this.__yui_events;if(c){var f=a[c];if(f){return f.unsubscribe(e,g);}}else{var b=true;for(var d in a){if(YAHOO.lang.hasOwnProperty(a,d)){b=b&&a[d].unsubscribe(e,g);
+}}return b;}return false;},unsubscribeAll:function(a){return this.unsubscribe(a);},createEvent:function(b,g){this.__yui_events=this.__yui_events||{};var e=g||{},d=this.__yui_events,f;if(d[b]){}else{f=new YAHOO.util.CustomEvent(b,e.scope||this,e.silent,YAHOO.util.CustomEvent.FLAT,e.fireOnce);d[b]=f;if(e.onSubscribeCallback){f.subscribeEvent.subscribe(e.onSubscribeCallback);}this.__yui_subscribers=this.__yui_subscribers||{};var a=this.__yui_subscribers[b];if(a){for(var c=0;c<a.length;++c){f.subscribe(a[c].fn,a[c].obj,a[c].overrideContext);}}}return d[b];},fireEvent:function(b){this.__yui_events=this.__yui_events||{};var d=this.__yui_events[b];if(!d){return null;}var a=[];for(var c=1;c<arguments.length;++c){a.push(arguments[c]);}return d.fire.apply(d,a);},hasEvent:function(a){if(this.__yui_events){if(this.__yui_events[a]){return true;}}return false;}};(function(){var a=YAHOO.util.Event,c=YAHOO.lang;YAHOO.util.KeyListener=function(d,i,e,f){if(!d){}else{if(!i){}else{if(!e){}}}if(!f){f=YAHOO.util.KeyListener.KEYDOWN;}var g=new YAHOO.util.CustomEvent("keyPressed");this.enabledEvent=new YAHOO.util.CustomEvent("enabled");this.disabledEvent=new YAHOO.util.CustomEvent("disabled");if(c.isString(d)){d=document.getElementById(d);}if(c.isFunction(e)){g.subscribe(e);}else{g.subscribe(e.fn,e.scope,e.correctScope);}function h(o,n){if(!i.shift){i.shift=false;}if(!i.alt){i.alt=false;}if(!i.ctrl){i.ctrl=false;}if(o.shiftKey==i.shift&&o.altKey==i.alt&&o.ctrlKey==i.ctrl){var j,m=i.keys,l;if(YAHOO.lang.isArray(m)){for(var k=0;k<m.length;k++){j=m[k];l=a.getCharCode(o);if(j==l){g.fire(l,o);break;}}}else{l=a.getCharCode(o);if(m==l){g.fire(l,o);}}}}this.enable=function(){if(!this.enabled){a.on(d,f,h);this.enabledEvent.fire(i);}this.enabled=true;};this.disable=function(){if(this.enabled){a.removeListener(d,f,h);this.disabledEvent.fire(i);}this.enabled=false;};this.toString=function(){return"KeyListener ["+i.keys+"] "+d.tagName+(d.id?"["+d.id+"]":"");};};var b=YAHOO.util.KeyListener;b.KEYDOWN="keydown";b.KEYUP="keyup";b.KEY={ALT:18,BACK_SPACE:8,CAPS_LOCK:20,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,META:224,NUM_LOCK:144,PAGE_DOWN:34,PAGE_UP:33,PAUSE:19,PRINTSCREEN:44,RIGHT:39,SCROLL_LOCK:145,SHIFT:16,SPACE:32,TAB:9,UP:38};})();YAHOO.register("event",YAHOO.util.Event,{version:"2.9.0",build:"2800"});YAHOO.register("yahoo-dom-event", YAHOO, {version: "2.9.0", build: "2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/yahoo/yahoo-min.js b/Websites/bugs.webkit.org/js/yui/yahoo/yahoo-min.js
new file mode 100644
index 0000000..4f18140
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/yahoo/yahoo-min.js
@@ -0,0 +1,8 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+if(typeof YAHOO=="undefined"||!YAHOO){var YAHOO={};}YAHOO.namespace=function(){var b=arguments,g=null,e,c,f;for(e=0;e<b.length;e=e+1){f=(""+b[e]).split(".");g=YAHOO;for(c=(f[0]=="YAHOO")?1:0;c<f.length;c=c+1){g[f[c]]=g[f[c]]||{};g=g[f[c]];}}return g;};YAHOO.log=function(d,a,c){var b=YAHOO.widget.Logger;if(b&&b.log){return b.log(d,a,c);}else{return false;}};YAHOO.register=function(a,f,e){var k=YAHOO.env.modules,c,j,h,g,d;if(!k[a]){k[a]={versions:[],builds:[]};}c=k[a];j=e.version;h=e.build;g=YAHOO.env.listeners;c.name=a;c.version=j;c.build=h;c.versions.push(j);c.builds.push(h);c.mainClass=f;for(d=0;d<g.length;d=d+1){g[d](c);}if(f){f.VERSION=j;f.BUILD=h;}else{YAHOO.log("mainClass is undefined for module "+a,"warn");}};YAHOO.env=YAHOO.env||{modules:[],listeners:[]};YAHOO.env.getVersion=function(a){return YAHOO.env.modules[a]||null;};YAHOO.env.parseUA=function(d){var e=function(i){var j=0;return parseFloat(i.replace(/\./g,function(){return(j++==1)?"":".";}));},h=navigator,g={ie:0,opera:0,gecko:0,webkit:0,chrome:0,mobile:null,air:0,ipad:0,iphone:0,ipod:0,ios:null,android:0,webos:0,caja:h&&h.cajaVersion,secure:false,os:null},c=d||(navigator&&navigator.userAgent),f=window&&window.location,b=f&&f.href,a;g.secure=b&&(b.toLowerCase().indexOf("https")===0);if(c){if((/windows|win32/i).test(c)){g.os="windows";}else{if((/macintosh/i).test(c)){g.os="macintosh";}else{if((/rhino/i).test(c)){g.os="rhino";}}}if((/KHTML/).test(c)){g.webkit=1;}a=c.match(/AppleWebKit\/([^\s]*)/);if(a&&a[1]){g.webkit=e(a[1]);if(/ Mobile\//.test(c)){g.mobile="Apple";a=c.match(/OS ([^\s]*)/);if(a&&a[1]){a=e(a[1].replace("_","."));}g.ios=a;g.ipad=g.ipod=g.iphone=0;a=c.match(/iPad|iPod|iPhone/);if(a&&a[0]){g[a[0].toLowerCase()]=g.ios;}}else{a=c.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/);if(a){g.mobile=a[0];}if(/webOS/.test(c)){g.mobile="WebOS";a=c.match(/webOS\/([^\s]*);/);if(a&&a[1]){g.webos=e(a[1]);}}if(/ Android/.test(c)){g.mobile="Android";a=c.match(/Android ([^\s]*);/);if(a&&a[1]){g.android=e(a[1]);}}}a=c.match(/Chrome\/([^\s]*)/);if(a&&a[1]){g.chrome=e(a[1]);}else{a=c.match(/AdobeAIR\/([^\s]*)/);if(a){g.air=a[0];}}}if(!g.webkit){a=c.match(/Opera[\s\/]([^\s]*)/);if(a&&a[1]){g.opera=e(a[1]);a=c.match(/Version\/([^\s]*)/);if(a&&a[1]){g.opera=e(a[1]);}a=c.match(/Opera Mini[^;]*/);if(a){g.mobile=a[0];}}else{a=c.match(/MSIE\s([^;]*)/);if(a&&a[1]){g.ie=e(a[1]);}else{a=c.match(/Gecko\/([^\s]*)/);if(a){g.gecko=1;a=c.match(/rv:([^\s\)]*)/);if(a&&a[1]){g.gecko=e(a[1]);}}}}}}return g;};YAHOO.env.ua=YAHOO.env.parseUA();(function(){YAHOO.namespace("util","widget","example");if("undefined"!==typeof YAHOO_config){var b=YAHOO_config.listener,a=YAHOO.env.listeners,d=true,c;if(b){for(c=0;c<a.length;c++){if(a[c]==b){d=false;break;}}if(d){a.push(b);}}}})();YAHOO.lang=YAHOO.lang||{};(function(){var f=YAHOO.lang,a=Object.prototype,c="[object Array]",h="[object Function]",i="[object Object]",b=[],g={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`"},d=["toString","valueOf"],e={isArray:function(j){return a.toString.apply(j)===c;},isBoolean:function(j){return typeof j==="boolean";},isFunction:function(j){return(typeof j==="function")||a.toString.apply(j)===h;},isNull:function(j){return j===null;},isNumber:function(j){return typeof j==="number"&&isFinite(j);},isObject:function(j){return(j&&(typeof j==="object"||f.isFunction(j)))||false;},isString:function(j){return typeof j==="string";},isUndefined:function(j){return typeof j==="undefined";},_IEEnumFix:(YAHOO.env.ua.ie)?function(l,k){var j,n,m;for(j=0;j<d.length;j=j+1){n=d[j];m=k[n];if(f.isFunction(m)&&m!=a[n]){l[n]=m;}}}:function(){},escapeHTML:function(j){return j.replace(/[&<>"'\/`]/g,function(k){return g[k];});},extend:function(m,n,l){if(!n||!m){throw new Error("extend failed, please check that "+"all dependencies are included.");}var k=function(){},j;k.prototype=n.prototype;m.prototype=new k();m.prototype.constructor=m;m.superclass=n.prototype;if(n.prototype.constructor==a.constructor){n.prototype.constructor=n;}if(l){for(j in l){if(f.hasOwnProperty(l,j)){m.prototype[j]=l[j];}}f._IEEnumFix(m.prototype,l);}},augmentObject:function(n,m){if(!m||!n){throw new Error("Absorb failed, verify dependencies.");}var j=arguments,l,o,k=j[2];if(k&&k!==true){for(l=2;l<j.length;l=l+1){n[j[l]]=m[j[l]];}}else{for(o in m){if(k||!(o in n)){n[o]=m[o];}}f._IEEnumFix(n,m);}return n;},augmentProto:function(m,l){if(!l||!m){throw new Error("Augment failed, verify dependencies.");}var j=[m.prototype,l.prototype],k;for(k=2;k<arguments.length;k=k+1){j.push(arguments[k]);}f.augmentObject.apply(this,j);return m;},dump:function(j,p){var l,n,r=[],t="{...}",k="f(){...}",q=", ",m=" => ";if(!f.isObject(j)){return j+"";}else{if(j instanceof Date||("nodeType" in j&&"tagName" in j)){return j;}else{if(f.isFunction(j)){return k;}}}p=(f.isNumber(p))?p:3;if(f.isArray(j)){r.push("[");for(l=0,n=j.length;l<n;l=l+1){if(f.isObject(j[l])){r.push((p>0)?f.dump(j[l],p-1):t);}else{r.push(j[l]);}r.push(q);}if(r.length>1){r.pop();}r.push("]");}else{r.push("{");for(l in j){if(f.hasOwnProperty(j,l)){r.push(l+m);if(f.isObject(j[l])){r.push((p>0)?f.dump(j[l],p-1):t);}else{r.push(j[l]);}r.push(q);}}if(r.length>1){r.pop();}r.push("}");}return r.join("");},substitute:function(x,y,E,l){var D,C,B,G,t,u,F=[],p,z=x.length,A="dump",r=" ",q="{",m="}",n,w;for(;;){D=x.lastIndexOf(q,z);if(D<0){break;}C=x.indexOf(m,D);if(D+1>C){break;}p=x.substring(D+1,C);G=p;u=null;B=G.indexOf(r);if(B>-1){u=G.substring(B+1);G=G.substring(0,B);}t=y[G];if(E){t=E(G,t,u);}if(f.isObject(t)){if(f.isArray(t)){t=f.dump(t,parseInt(u,10));}else{u=u||"";n=u.indexOf(A);if(n>-1){u=u.substring(4);}w=t.toString();if(w===i||n>-1){t=f.dump(t,parseInt(u,10));}else{t=w;}}}else{if(!f.isString(t)&&!f.isNumber(t)){t="~-"+F.length+"-~";F[F.length]=p;}}x=x.substring(0,D)+t+x.substring(C+1);if(l===false){z=D-1;}}for(D=F.length-1;D>=0;D=D-1){x=x.replace(new RegExp("~-"+D+"-~"),"{"+F[D]+"}","g");}return x;},trim:function(j){try{return j.replace(/^\s+|\s+$/g,"");}catch(k){return j;
+}},merge:function(){var n={},k=arguments,j=k.length,m;for(m=0;m<j;m=m+1){f.augmentObject(n,k[m],true);}return n;},later:function(t,k,u,n,p){t=t||0;k=k||{};var l=u,s=n,q,j;if(f.isString(u)){l=k[u];}if(!l){throw new TypeError("method undefined");}if(!f.isUndefined(n)&&!f.isArray(s)){s=[n];}q=function(){l.apply(k,s||b);};j=(p)?setInterval(q,t):setTimeout(q,t);return{interval:p,cancel:function(){if(this.interval){clearInterval(j);}else{clearTimeout(j);}}};},isValue:function(j){return(f.isObject(j)||f.isString(j)||f.isNumber(j)||f.isBoolean(j));}};f.hasOwnProperty=(a.hasOwnProperty)?function(j,k){return j&&j.hasOwnProperty&&j.hasOwnProperty(k);}:function(j,k){return !f.isUndefined(j[k])&&j.constructor.prototype[k]!==j[k];};e.augmentObject(f,e,true);YAHOO.util.Lang=f;f.augment=f.augmentProto;YAHOO.augment=f.augmentProto;YAHOO.extend=f.extend;})();YAHOO.register("yahoo",YAHOO,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/yuiloader/yuiloader-min.js b/Websites/bugs.webkit.org/js/yui/yuiloader/yuiloader-min.js
new file mode 100644
index 0000000..ef12e38
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/yuiloader/yuiloader-min.js
@@ -0,0 +1,11 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+if(typeof YAHOO=="undefined"||!YAHOO){var YAHOO={};}YAHOO.namespace=function(){var b=arguments,g=null,e,c,f;for(e=0;e<b.length;e=e+1){f=(""+b[e]).split(".");g=YAHOO;for(c=(f[0]=="YAHOO")?1:0;c<f.length;c=c+1){g[f[c]]=g[f[c]]||{};g=g[f[c]];}}return g;};YAHOO.log=function(d,a,c){var b=YAHOO.widget.Logger;if(b&&b.log){return b.log(d,a,c);}else{return false;}};YAHOO.register=function(a,f,e){var k=YAHOO.env.modules,c,j,h,g,d;if(!k[a]){k[a]={versions:[],builds:[]};}c=k[a];j=e.version;h=e.build;g=YAHOO.env.listeners;c.name=a;c.version=j;c.build=h;c.versions.push(j);c.builds.push(h);c.mainClass=f;for(d=0;d<g.length;d=d+1){g[d](c);}if(f){f.VERSION=j;f.BUILD=h;}else{YAHOO.log("mainClass is undefined for module "+a,"warn");}};YAHOO.env=YAHOO.env||{modules:[],listeners:[]};YAHOO.env.getVersion=function(a){return YAHOO.env.modules[a]||null;};YAHOO.env.parseUA=function(d){var e=function(i){var j=0;return parseFloat(i.replace(/\./g,function(){return(j++==1)?"":".";}));},h=navigator,g={ie:0,opera:0,gecko:0,webkit:0,chrome:0,mobile:null,air:0,ipad:0,iphone:0,ipod:0,ios:null,android:0,webos:0,caja:h&&h.cajaVersion,secure:false,os:null},c=d||(navigator&&navigator.userAgent),f=window&&window.location,b=f&&f.href,a;g.secure=b&&(b.toLowerCase().indexOf("https")===0);if(c){if((/windows|win32/i).test(c)){g.os="windows";}else{if((/macintosh/i).test(c)){g.os="macintosh";}else{if((/rhino/i).test(c)){g.os="rhino";}}}if((/KHTML/).test(c)){g.webkit=1;}a=c.match(/AppleWebKit\/([^\s]*)/);if(a&&a[1]){g.webkit=e(a[1]);if(/ Mobile\//.test(c)){g.mobile="Apple";a=c.match(/OS ([^\s]*)/);if(a&&a[1]){a=e(a[1].replace("_","."));}g.ios=a;g.ipad=g.ipod=g.iphone=0;a=c.match(/iPad|iPod|iPhone/);if(a&&a[0]){g[a[0].toLowerCase()]=g.ios;}}else{a=c.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/);if(a){g.mobile=a[0];}if(/webOS/.test(c)){g.mobile="WebOS";a=c.match(/webOS\/([^\s]*);/);if(a&&a[1]){g.webos=e(a[1]);}}if(/ Android/.test(c)){g.mobile="Android";a=c.match(/Android ([^\s]*);/);if(a&&a[1]){g.android=e(a[1]);}}}a=c.match(/Chrome\/([^\s]*)/);if(a&&a[1]){g.chrome=e(a[1]);}else{a=c.match(/AdobeAIR\/([^\s]*)/);if(a){g.air=a[0];}}}if(!g.webkit){a=c.match(/Opera[\s\/]([^\s]*)/);if(a&&a[1]){g.opera=e(a[1]);a=c.match(/Version\/([^\s]*)/);if(a&&a[1]){g.opera=e(a[1]);}a=c.match(/Opera Mini[^;]*/);if(a){g.mobile=a[0];}}else{a=c.match(/MSIE\s([^;]*)/);if(a&&a[1]){g.ie=e(a[1]);}else{a=c.match(/Gecko\/([^\s]*)/);if(a){g.gecko=1;a=c.match(/rv:([^\s\)]*)/);if(a&&a[1]){g.gecko=e(a[1]);}}}}}}return g;};YAHOO.env.ua=YAHOO.env.parseUA();(function(){YAHOO.namespace("util","widget","example");if("undefined"!==typeof YAHOO_config){var b=YAHOO_config.listener,a=YAHOO.env.listeners,d=true,c;if(b){for(c=0;c<a.length;c++){if(a[c]==b){d=false;break;}}if(d){a.push(b);}}}})();YAHOO.lang=YAHOO.lang||{};(function(){var f=YAHOO.lang,a=Object.prototype,c="[object Array]",h="[object Function]",i="[object Object]",b=[],g={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`"},d=["toString","valueOf"],e={isArray:function(j){return a.toString.apply(j)===c;},isBoolean:function(j){return typeof j==="boolean";},isFunction:function(j){return(typeof j==="function")||a.toString.apply(j)===h;},isNull:function(j){return j===null;},isNumber:function(j){return typeof j==="number"&&isFinite(j);},isObject:function(j){return(j&&(typeof j==="object"||f.isFunction(j)))||false;},isString:function(j){return typeof j==="string";},isUndefined:function(j){return typeof j==="undefined";},_IEEnumFix:(YAHOO.env.ua.ie)?function(l,k){var j,n,m;for(j=0;j<d.length;j=j+1){n=d[j];m=k[n];if(f.isFunction(m)&&m!=a[n]){l[n]=m;}}}:function(){},escapeHTML:function(j){return j.replace(/[&<>"'\/`]/g,function(k){return g[k];});},extend:function(m,n,l){if(!n||!m){throw new Error("extend failed, please check that "+"all dependencies are included.");}var k=function(){},j;k.prototype=n.prototype;m.prototype=new k();m.prototype.constructor=m;m.superclass=n.prototype;if(n.prototype.constructor==a.constructor){n.prototype.constructor=n;}if(l){for(j in l){if(f.hasOwnProperty(l,j)){m.prototype[j]=l[j];}}f._IEEnumFix(m.prototype,l);}},augmentObject:function(n,m){if(!m||!n){throw new Error("Absorb failed, verify dependencies.");}var j=arguments,l,o,k=j[2];if(k&&k!==true){for(l=2;l<j.length;l=l+1){n[j[l]]=m[j[l]];}}else{for(o in m){if(k||!(o in n)){n[o]=m[o];}}f._IEEnumFix(n,m);}return n;},augmentProto:function(m,l){if(!l||!m){throw new Error("Augment failed, verify dependencies.");}var j=[m.prototype,l.prototype],k;for(k=2;k<arguments.length;k=k+1){j.push(arguments[k]);}f.augmentObject.apply(this,j);return m;},dump:function(j,p){var l,n,r=[],t="{...}",k="f(){...}",q=", ",m=" => ";if(!f.isObject(j)){return j+"";}else{if(j instanceof Date||("nodeType" in j&&"tagName" in j)){return j;}else{if(f.isFunction(j)){return k;}}}p=(f.isNumber(p))?p:3;if(f.isArray(j)){r.push("[");for(l=0,n=j.length;l<n;l=l+1){if(f.isObject(j[l])){r.push((p>0)?f.dump(j[l],p-1):t);}else{r.push(j[l]);}r.push(q);}if(r.length>1){r.pop();}r.push("]");}else{r.push("{");for(l in j){if(f.hasOwnProperty(j,l)){r.push(l+m);if(f.isObject(j[l])){r.push((p>0)?f.dump(j[l],p-1):t);}else{r.push(j[l]);}r.push(q);}}if(r.length>1){r.pop();}r.push("}");}return r.join("");},substitute:function(x,y,E,l){var D,C,B,G,t,u,F=[],p,z=x.length,A="dump",r=" ",q="{",m="}",n,w;for(;;){D=x.lastIndexOf(q,z);if(D<0){break;}C=x.indexOf(m,D);if(D+1>C){break;}p=x.substring(D+1,C);G=p;u=null;B=G.indexOf(r);if(B>-1){u=G.substring(B+1);G=G.substring(0,B);}t=y[G];if(E){t=E(G,t,u);}if(f.isObject(t)){if(f.isArray(t)){t=f.dump(t,parseInt(u,10));}else{u=u||"";n=u.indexOf(A);if(n>-1){u=u.substring(4);}w=t.toString();if(w===i||n>-1){t=f.dump(t,parseInt(u,10));}else{t=w;}}}else{if(!f.isString(t)&&!f.isNumber(t)){t="~-"+F.length+"-~";F[F.length]=p;}}x=x.substring(0,D)+t+x.substring(C+1);if(l===false){z=D-1;}}for(D=F.length-1;D>=0;D=D-1){x=x.replace(new RegExp("~-"+D+"-~"),"{"+F[D]+"}","g");}return x;},trim:function(j){try{return j.replace(/^\s+|\s+$/g,"");}catch(k){return j;
+}},merge:function(){var n={},k=arguments,j=k.length,m;for(m=0;m<j;m=m+1){f.augmentObject(n,k[m],true);}return n;},later:function(t,k,u,n,p){t=t||0;k=k||{};var l=u,s=n,q,j;if(f.isString(u)){l=k[u];}if(!l){throw new TypeError("method undefined");}if(!f.isUndefined(n)&&!f.isArray(s)){s=[n];}q=function(){l.apply(k,s||b);};j=(p)?setInterval(q,t):setTimeout(q,t);return{interval:p,cancel:function(){if(this.interval){clearInterval(j);}else{clearTimeout(j);}}};},isValue:function(j){return(f.isObject(j)||f.isString(j)||f.isNumber(j)||f.isBoolean(j));}};f.hasOwnProperty=(a.hasOwnProperty)?function(j,k){return j&&j.hasOwnProperty&&j.hasOwnProperty(k);}:function(j,k){return !f.isUndefined(j[k])&&j.constructor.prototype[k]!==j[k];};e.augmentObject(f,e,true);YAHOO.util.Lang=f;f.augment=f.augmentProto;YAHOO.augment=f.augmentProto;YAHOO.extend=f.extend;})();YAHOO.register("yahoo",YAHOO,{version:"2.9.0",build:"2800"});YAHOO.util.Get=function(){var m={},k=0,r=0,l=false,n=YAHOO.env.ua,s=YAHOO.lang,q,d,e,i=function(x,t,y){var u=y||window,z=u.document,A=z.createElement(x),v;for(v in t){if(t.hasOwnProperty(v)){A.setAttribute(v,t[v]);}}return A;},h=function(u,v,t){var w={id:"yui__dyn_"+(r++),type:"text/css",rel:"stylesheet",href:u};if(t){s.augmentObject(w,t);}return i("link",w,v);},p=function(u,v,t){var w={id:"yui__dyn_"+(r++),type:"text/javascript",src:u};if(t){s.augmentObject(w,t);}return i("script",w,v);},a=function(t,u){return{tId:t.tId,win:t.win,data:t.data,nodes:t.nodes,msg:u,purge:function(){d(this.tId);}};},b=function(t,w){var u=m[w],v=(s.isString(t))?u.win.document.getElementById(t):t;if(!v){q(w,"target node not found: "+t);}return v;},c=function(w){YAHOO.log("Finishing transaction "+w);var u=m[w],v,t;u.finished=true;if(u.aborted){v="transaction "+w+" was aborted";q(w,v);return;}if(u.onSuccess){t=u.scope||u.win;u.onSuccess.call(t,a(u));}},o=function(v){YAHOO.log("Timeout "+v,"info","get");var u=m[v],t;if(u.onTimeout){t=u.scope||u;u.onTimeout.call(t,a(u));}},f=function(v,A){YAHOO.log("_next: "+v+", loaded: "+A,"info","Get");var u=m[v],D=u.win,C=D.document,B=C.getElementsByTagName("head")[0],x,y,t,E,z;if(u.timer){u.timer.cancel();}if(u.aborted){y="transaction "+v+" was aborted";q(v,y);return;}if(A){u.url.shift();if(u.varName){u.varName.shift();}}else{u.url=(s.isString(u.url))?[u.url]:u.url;if(u.varName){u.varName=(s.isString(u.varName))?[u.varName]:u.varName;}}if(u.url.length===0){if(u.type==="script"&&n.webkit&&n.webkit<420&&!u.finalpass&&!u.varName){z=p(null,u.win,u.attributes);z.innerHTML='YAHOO.util.Get._finalize("'+v+'");';u.nodes.push(z);B.appendChild(z);}else{c(v);}return;}t=u.url[0];if(!t){u.url.shift();YAHOO.log("skipping empty url");return f(v);}YAHOO.log("attempting to load "+t,"info","Get");if(u.timeout){u.timer=s.later(u.timeout,u,o,v);}if(u.type==="script"){x=p(t,D,u.attributes);}else{x=h(t,D,u.attributes);}e(u.type,x,v,t,D,u.url.length);u.nodes.push(x);if(u.insertBefore){E=b(u.insertBefore,v);if(E){E.parentNode.insertBefore(x,E);}}else{B.appendChild(x);}YAHOO.log("Appending node: "+t,"info","Get");if((n.webkit||n.gecko)&&u.type==="css"){f(v,t);}},j=function(){if(l){return;}l=true;var t,u;for(t in m){if(m.hasOwnProperty(t)){u=m[t];if(u.autopurge&&u.finished){d(u.tId);delete m[t];}}}l=false;},g=function(u,t,v){var x="q"+(k++),w;v=v||{};if(k%YAHOO.util.Get.PURGE_THRESH===0){j();}m[x]=s.merge(v,{tId:x,type:u,url:t,finished:false,aborted:false,nodes:[]});w=m[x];w.win=w.win||window;w.scope=w.scope||w.win;w.autopurge=("autopurge" in w)?w.autopurge:(u==="script")?true:false;w.attributes=w.attributes||{};w.attributes.charset=v.charset||w.attributes.charset||"utf-8";s.later(0,w,f,x);return{tId:x};};e=function(H,z,x,u,D,E,G){var F=G||f,B,t,I,v,J,A,C,y;if(n.ie){z.onreadystatechange=function(){B=this.readyState;if("loaded"===B||"complete"===B){YAHOO.log(x+" onload "+u,"info","Get");z.onreadystatechange=null;F(x,u);}};}else{if(n.webkit){if(H==="script"){if(n.webkit>=420){z.addEventListener("load",function(){YAHOO.log(x+" DOM2 onload "+u,"info","Get");F(x,u);});}else{t=m[x];if(t.varName){v=YAHOO.util.Get.POLL_FREQ;YAHOO.log("Polling for "+t.varName[0]);t.maxattempts=YAHOO.util.Get.TIMEOUT/v;t.attempts=0;t._cache=t.varName[0].split(".");t.timer=s.later(v,t,function(w){I=this._cache;A=I.length;J=this.win;for(C=0;C<A;C=C+1){J=J[I[C]];if(!J){this.attempts++;if(this.attempts++>this.maxattempts){y="Over retry limit, giving up";t.timer.cancel();q(x,y);}else{YAHOO.log(I[C]+" failed, retrying");}return;}}YAHOO.log("Safari poll complete");t.timer.cancel();F(x,u);},null,true);}else{s.later(YAHOO.util.Get.POLL_FREQ,null,F,[x,u]);}}}}else{z.onload=function(){YAHOO.log(x+" onload "+u,"info","Get");F(x,u);};}}};q=function(w,v){YAHOO.log("get failure: "+v,"warn","Get");var u=m[w],t;if(u.onFailure){t=u.scope||u.win;u.onFailure.call(t,a(u,v));}};d=function(z){if(m[z]){var t=m[z],u=t.nodes,x=u.length,C=t.win.document,A=C.getElementsByTagName("head")[0],v,y,w,B;if(t.insertBefore){v=b(t.insertBefore,z);if(v){A=v.parentNode;}}for(y=0;y<x;y=y+1){w=u[y];if(w.clearAttributes){w.clearAttributes();}else{for(B in w){if(w.hasOwnProperty(B)){delete w[B];}}}A.removeChild(w);}t.nodes=[];}};return{POLL_FREQ:10,PURGE_THRESH:20,TIMEOUT:2000,_finalize:function(t){YAHOO.log(t+" finalized ","info","Get");s.later(0,null,c,t);},abort:function(u){var v=(s.isString(u))?u:u.tId,t=m[v];if(t){YAHOO.log("Aborting "+v,"info","Get");t.aborted=true;}},script:function(t,u){return g("script",t,u);},css:function(t,u){return g("css",t,u);}};}();YAHOO.register("get",YAHOO.util.Get,{version:"2.9.0",build:"2800"});(function(){var Y=YAHOO,util=Y.util,lang=Y.lang,env=Y.env,PROV="_provides",SUPER="_supersedes",REQ="expanded",AFTER="_after",VERSION="2.9.0";var YUI={dupsAllowed:{"yahoo":true,"get":true},info:{"root":VERSION+"/build/","base":"http://yui.yahooapis.com/"+VERSION+"/build/","comboBase":"http://yui.yahooapis.com/combo?","skin":{"defaultSkin":"sam","base":"assets/skins/","path":"skin.css","after":["reset","fonts","grids","base"],"rollup":3},dupsAllowed:["yahoo","get"],"moduleInfo":{"animation":{"type":"js","path":"animation/animation-min.js","requires":["dom","event"]},"autocomplete":{"type":"js","path":"autocomplete/autocomplete-min.js","requires":["dom","event","datasource"],"optional":["connection","animation"],"skinnable":true},"base":{"type":"css","path":"base/base-min.css","after":["reset","fonts","grids"]},"button":{"type":"js","path":"button/button-min.js","requires":["element"],"optional":["menu"],"skinnable":true},"calendar":{"type":"js","path":"calendar/calendar-min.js","requires":["event","dom"],supersedes:["datemath"],"skinnable":true},"carousel":{"type":"js","path":"carousel/carousel-min.js","requires":["element"],"optional":["animation"],"skinnable":true},"charts":{"type":"js","path":"charts/charts-min.js","requires":["element","json","datasource","swf"]},"colorpicker":{"type":"js","path":"colorpicker/colorpicker-min.js","requires":["slider","element"],"optional":["animation"],"skinnable":true},"connection":{"type":"js","path":"connection/connection-min.js","requires":["event"],"supersedes":["connectioncore"]},"connectioncore":{"type":"js","path":"connection/connection_core-min.js","requires":["event"],"pkg":"connection"},"container":{"type":"js","path":"container/container-min.js","requires":["dom","event"],"optional":["dragdrop","animation","connection"],"supersedes":["containercore"],"skinnable":true},"containercore":{"type":"js","path":"container/container_core-min.js","requires":["dom","event"],"pkg":"container"},"cookie":{"type":"js","path":"cookie/cookie-min.js","requires":["yahoo"]},"datasource":{"type":"js","path":"datasource/datasource-min.js","requires":["event"],"optional":["connection"]},"datatable":{"type":"js","path":"datatable/datatable-min.js","requires":["element","datasource"],"optional":["calendar","dragdrop","paginator"],"skinnable":true},datemath:{"type":"js","path":"datemath/datemath-min.js","requires":["yahoo"]},"dom":{"type":"js","path":"dom/dom-min.js","requires":["yahoo"]},"dragdrop":{"type":"js","path":"dragdrop/dragdrop-min.js","requires":["dom","event"]},"editor":{"type":"js","path":"editor/editor-min.js","requires":["menu","element","button"],"optional":["animation","dragdrop"],"supersedes":["simpleeditor"],"skinnable":true},"element":{"type":"js","path":"element/element-min.js","requires":["dom","event"],"optional":["event-mouseenter","event-delegate"]},"element-delegate":{"type":"js","path":"element-delegate/element-delegate-min.js","requires":["element"]},"event":{"type":"js","path":"event/event-min.js","requires":["yahoo"]},"event-simulate":{"type":"js","path":"event-simulate/event-simulate-min.js","requires":["event"]},"event-delegate":{"type":"js","path":"event-delegate/event-delegate-min.js","requires":["event"],"optional":["selector"]},"event-mouseenter":{"type":"js","path":"event-mouseenter/event-mouseenter-min.js","requires":["dom","event"]},"fonts":{"type":"css","path":"fonts/fonts-min.css"},"get":{"type":"js","path":"get/get-min.js","requires":["yahoo"]},"grids":{"type":"css","path":"grids/grids-min.css","requires":["fonts"],"optional":["reset"]},"history":{"type":"js","path":"history/history-min.js","requires":["event"]},"imagecropper":{"type":"js","path":"imagecropper/imagecropper-min.js","requires":["dragdrop","element","resize"],"skinnable":true},"imageloader":{"type":"js","path":"imageloader/imageloader-min.js","requires":["event","dom"]},"json":{"type":"js","path":"json/json-min.js","requires":["yahoo"]},"layout":{"type":"js","path":"layout/layout-min.js","requires":["element"],"optional":["animation","dragdrop","resize","selector"],"skinnable":true},"logger":{"type":"js","path":"logger/logger-min.js","requires":["event","dom"],"optional":["dragdrop"],"skinnable":true},"menu":{"type":"js","path":"menu/menu-min.js","requires":["containercore"],"skinnable":true},"paginator":{"type":"js","path":"paginator/paginator-min.js","requires":["element"],"skinnable":true},"profiler":{"type":"js","path":"profiler/profiler-min.js","requires":["yahoo"]},"profilerviewer":{"type":"js","path":"profilerviewer/profilerviewer-min.js","requires":["profiler","yuiloader","element"],"skinnable":true},"progressbar":{"type":"js","path":"progressbar/progressbar-min.js","requires":["element"],"optional":["animation"],"skinnable":true},"reset":{"type":"css","path":"reset/reset-min.css"},"reset-fonts-grids":{"type":"css","path":"reset-fonts-grids/reset-fonts-grids.css","supersedes":["reset","fonts","grids","reset-fonts"],"rollup":4},"reset-fonts":{"type":"css","path":"reset-fonts/reset-fonts.css","supersedes":["reset","fonts"],"rollup":2},"resize":{"type":"js","path":"resize/resize-min.js","requires":["dragdrop","element"],"optional":["animation"],"skinnable":true},"selector":{"type":"js","path":"selector/selector-min.js","requires":["yahoo","dom"]},"simpleeditor":{"type":"js","path":"editor/simpleeditor-min.js","requires":["element"],"optional":["containercore","menu","button","animation","dragdrop"],"skinnable":true,"pkg":"editor"},"slider":{"type":"js","path":"slider/slider-min.js","requires":["dragdrop"],"optional":["animation"],"skinnable":true},"storage":{"type":"js","path":"storage/storage-min.js","requires":["yahoo","event","cookie"],"optional":["swfstore"]},"stylesheet":{"type":"js","path":"stylesheet/stylesheet-min.js","requires":["yahoo"]},"swf":{"type":"js","path":"swf/swf-min.js","requires":["element"],"supersedes":["swfdetect"]},"swfdetect":{"type":"js","path":"swfdetect/swfdetect-min.js","requires":["yahoo"]},"swfstore":{"type":"js","path":"swfstore/swfstore-min.js","requires":["element","cookie","swf"]},"tabview":{"type":"js","path":"tabview/tabview-min.js","requires":["element"],"optional":["connection"],"skinnable":true},"treeview":{"type":"js","path":"treeview/treeview-min.js","requires":["event","dom"],"optional":["json","animation","calendar"],"skinnable":true},"uploader":{"type":"js","path":"uploader/uploader-min.js","requires":["element"]},"utilities":{"type":"js","path":"utilities/utilities.js","supersedes":["yahoo","event","dragdrop","animation","dom","connection","element","yahoo-dom-event","get","yuiloader","yuiloader-dom-event"],"rollup":8},"yahoo":{"type":"js","path":"yahoo/yahoo-min.js"},"yahoo-dom-event":{"type":"js","path":"yahoo-dom-event/yahoo-dom-event.js","supersedes":["yahoo","event","dom"],"rollup":3},"yuiloader":{"type":"js","path":"yuiloader/yuiloader-min.js","supersedes":["yahoo","get"]},"yuiloader-dom-event":{"type":"js","path":"yuiloader-dom-event/yuiloader-dom-event.js","supersedes":["yahoo","dom","event","get","yuiloader","yahoo-dom-event"],"rollup":5},"yuitest":{"type":"js","path":"yuitest/yuitest-min.js","requires":["logger"],"optional":["event-simulate"],"skinnable":true}}},ObjectUtil:{appendArray:function(o,a){if(a){for(var i=0;
+i<a.length;i=i+1){o[a[i]]=true;}}},keys:function(o,ordered){var a=[],i;for(i in o){if(lang.hasOwnProperty(o,i)){a.push(i);}}return a;}},ArrayUtil:{appendArray:function(a1,a2){Array.prototype.push.apply(a1,a2);},indexOf:function(a,val){for(var i=0;i<a.length;i=i+1){if(a[i]===val){return i;}}return -1;},toObject:function(a){var o={};for(var i=0;i<a.length;i=i+1){o[a[i]]=true;}return o;},uniq:function(a){return YUI.ObjectUtil.keys(YUI.ArrayUtil.toObject(a));}}};YAHOO.util.YUILoader=function(o){this._internalCallback=null;this._useYahooListener=false;this.onSuccess=null;this.onFailure=Y.log;this.onProgress=null;this.onTimeout=null;this.scope=this;this.data=null;this.insertBefore=null;this.charset=null;this.varName=null;this.base=YUI.info.base;this.comboBase=YUI.info.comboBase;this.combine=false;this.root=YUI.info.root;this.timeout=0;this.ignore=null;this.force=null;this.allowRollup=true;this.filter=null;this.required={};this.moduleInfo=lang.merge(YUI.info.moduleInfo);this.rollups=null;this.loadOptional=false;this.sorted=[];this.loaded={};this.dirty=true;this.inserted={};var self=this;env.listeners.push(function(m){if(self._useYahooListener){self.loadNext(m.name);}});this.skin=lang.merge(YUI.info.skin);this._config(o);};Y.util.YUILoader.prototype={FILTERS:{RAW:{"searchExp":"-min\\.js","replaceStr":".js"},DEBUG:{"searchExp":"-min\\.js","replaceStr":"-debug.js"}},SKIN_PREFIX:"skin-",_config:function(o){if(o){for(var i in o){if(lang.hasOwnProperty(o,i)){if(i=="require"){this.require(o[i]);}else{this[i]=o[i];}}}}var f=this.filter;if(lang.isString(f)){f=f.toUpperCase();if(f==="DEBUG"){this.require("logger");}if(!Y.widget.LogWriter){Y.widget.LogWriter=function(){return Y;};}this.filter=this.FILTERS[f];}},addModule:function(o){if(!o||!o.name||!o.type||(!o.path&&!o.fullpath)){return false;}o.ext=("ext" in o)?o.ext:true;o.requires=o.requires||[];this.moduleInfo[o.name]=o;this.dirty=true;return true;},require:function(what){var a=(typeof what==="string")?arguments:what;this.dirty=true;YUI.ObjectUtil.appendArray(this.required,a);},_addSkin:function(skin,mod){var name=this.formatSkin(skin),info=this.moduleInfo,sinf=this.skin,ext=info[mod]&&info[mod].ext;if(!info[name]){this.addModule({"name":name,"type":"css","path":sinf.base+skin+"/"+sinf.path,"after":sinf.after,"rollup":sinf.rollup,"ext":ext});}if(mod){name=this.formatSkin(skin,mod);if(!info[name]){var mdef=info[mod],pkg=mdef.pkg||mod;this.addModule({"name":name,"type":"css","after":sinf.after,"path":pkg+"/"+sinf.base+skin+"/"+mod+".css","ext":ext});}}return name;},getRequires:function(mod){if(!mod){return[];}if(!this.dirty&&mod.expanded){return mod.expanded;}mod.requires=mod.requires||[];var i,d=[],r=mod.requires,o=mod.optional,info=this.moduleInfo,m;for(i=0;i<r.length;i=i+1){d.push(r[i]);m=info[r[i]];YUI.ArrayUtil.appendArray(d,this.getRequires(m));}if(o&&this.loadOptional){for(i=0;i<o.length;i=i+1){d.push(o[i]);YUI.ArrayUtil.appendArray(d,this.getRequires(info[o[i]]));}}mod.expanded=YUI.ArrayUtil.uniq(d);return mod.expanded;},getProvides:function(name,notMe){var addMe=!(notMe),ckey=(addMe)?PROV:SUPER,m=this.moduleInfo[name],o={};if(!m){return o;}if(m[ckey]){return m[ckey];}var s=m.supersedes,done={},me=this;var add=function(mm){if(!done[mm]){done[mm]=true;lang.augmentObject(o,me.getProvides(mm));}};if(s){for(var i=0;i<s.length;i=i+1){add(s[i]);}}m[SUPER]=o;m[PROV]=lang.merge(o);m[PROV][name]=true;return m[ckey];},calculate:function(o){if(o||this.dirty){this._config(o);this._setup();this._explode();if(this.allowRollup){this._rollup();}this._reduce();this._sort();this.dirty=false;}},_setup:function(){var info=this.moduleInfo,name,i,j;for(name in info){if(lang.hasOwnProperty(info,name)){var m=info[name];if(m&&m.skinnable){var o=this.skin.overrides,smod;if(o&&o[name]){for(i=0;i<o[name].length;i=i+1){smod=this._addSkin(o[name][i],name);}}else{smod=this._addSkin(this.skin.defaultSkin,name);}if(YUI.ArrayUtil.indexOf(m.requires,smod)==-1){m.requires.push(smod);}}}}var l=lang.merge(this.inserted);if(!this._sandbox){l=lang.merge(l,env.modules);}if(this.ignore){YUI.ObjectUtil.appendArray(l,this.ignore);}if(this.force){for(i=0;i<this.force.length;i=i+1){if(this.force[i] in l){delete l[this.force[i]];}}}for(j in l){if(lang.hasOwnProperty(l,j)){lang.augmentObject(l,this.getProvides(j));}}this.loaded=l;},_explode:function(){var r=this.required,i,mod;for(i in r){if(lang.hasOwnProperty(r,i)){mod=this.moduleInfo[i];if(mod){var req=this.getRequires(mod);if(req){YUI.ObjectUtil.appendArray(r,req);}}}}},_skin:function(){},formatSkin:function(skin,mod){var s=this.SKIN_PREFIX+skin;if(mod){s=s+"-"+mod;}return s;},parseSkin:function(mod){if(mod.indexOf(this.SKIN_PREFIX)===0){var a=mod.split("-");return{skin:a[1],module:a[2]};}return null;},_rollup:function(){var i,j,m,s,rollups={},r=this.required,roll,info=this.moduleInfo;if(this.dirty||!this.rollups){for(i in info){if(lang.hasOwnProperty(info,i)){m=info[i];if(m&&m.rollup){rollups[i]=m;}}}this.rollups=rollups;}for(;;){var rolled=false;for(i in rollups){if(!r[i]&&!this.loaded[i]){m=info[i];s=m.supersedes;roll=false;if(!m.rollup){continue;}var skin=(m.ext)?false:this.parseSkin(i),c=0;if(skin){for(j in r){if(lang.hasOwnProperty(r,j)){if(i!==j&&this.parseSkin(j)){c++;roll=(c>=m.rollup);if(roll){break;}}}}}else{for(j=0;j<s.length;j=j+1){if(this.loaded[s[j]]&&(!YUI.dupsAllowed[s[j]])){roll=false;break;}else{if(r[s[j]]){c++;roll=(c>=m.rollup);if(roll){break;}}}}}if(roll){r[i]=true;rolled=true;this.getRequires(m);}}}if(!rolled){break;}}},_reduce:function(){var i,j,s,m,r=this.required;for(i in r){if(i in this.loaded){delete r[i];}else{var skinDef=this.parseSkin(i);if(skinDef){if(!skinDef.module){var skin_pre=this.SKIN_PREFIX+skinDef.skin;for(j in r){if(lang.hasOwnProperty(r,j)){m=this.moduleInfo[j];var ext=m&&m.ext;if(!ext&&j!==i&&j.indexOf(skin_pre)>-1){delete r[j];}}}}}else{m=this.moduleInfo[i];s=m&&m.supersedes;if(s){for(j=0;j<s.length;j=j+1){if(s[j] in r){delete r[s[j]];}}}}}}},_onFailure:function(msg){YAHOO.log("Failure","info","loader");
+var f=this.onFailure;if(f){f.call(this.scope,{msg:"failure: "+msg,data:this.data,success:false});}},_onTimeout:function(){YAHOO.log("Timeout","info","loader");var f=this.onTimeout;if(f){f.call(this.scope,{msg:"timeout",data:this.data,success:false});}},_sort:function(){var s=[],info=this.moduleInfo,loaded=this.loaded,checkOptional=!this.loadOptional,me=this;var requires=function(aa,bb){var mm=info[aa];if(loaded[bb]||!mm){return false;}var ii,rr=mm.expanded,after=mm.after,other=info[bb],optional=mm.optional;if(rr&&YUI.ArrayUtil.indexOf(rr,bb)>-1){return true;}if(after&&YUI.ArrayUtil.indexOf(after,bb)>-1){return true;}if(checkOptional&&optional&&YUI.ArrayUtil.indexOf(optional,bb)>-1){return true;}var ss=info[bb]&&info[bb].supersedes;if(ss){for(ii=0;ii<ss.length;ii=ii+1){if(requires(aa,ss[ii])){return true;}}}if(mm.ext&&mm.type=="css"&&!other.ext&&other.type=="css"){return true;}return false;};for(var i in this.required){if(lang.hasOwnProperty(this.required,i)){s.push(i);}}var p=0;for(;;){var l=s.length,a,b,j,k,moved=false;for(j=p;j<l;j=j+1){a=s[j];for(k=j+1;k<l;k=k+1){if(requires(a,s[k])){b=s.splice(k,1);s.splice(j,0,b[0]);moved=true;break;}}if(moved){break;}else{p=p+1;}}if(!moved){break;}}this.sorted=s;},toString:function(){var o={type:"YUILoader",base:this.base,filter:this.filter,required:this.required,loaded:this.loaded,inserted:this.inserted};lang.dump(o,1);},_combine:function(){this._combining=[];var self=this,s=this.sorted,len=s.length,js=this.comboBase,css=this.comboBase,target,startLen=js.length,i,m,type=this.loadType;YAHOO.log("type "+type);for(i=0;i<len;i=i+1){m=this.moduleInfo[s[i]];if(m&&!m.ext&&(!type||type===m.type)){target=this.root+m.path;target+="&";if(m.type=="js"){js+=target;}else{css+=target;}this._combining.push(s[i]);}}if(this._combining.length){YAHOO.log("Attempting to combine: "+this._combining,"info","loader");var callback=function(o){var c=this._combining,len=c.length,i,m;for(i=0;i<len;i=i+1){this.inserted[c[i]]=true;}this.loadNext(o.data);},loadScript=function(){if(js.length>startLen){YAHOO.util.Get.script(self._filter(js),{data:self._loading,onSuccess:callback,onFailure:self._onFailure,onTimeout:self._onTimeout,insertBefore:self.insertBefore,charset:self.charset,timeout:self.timeout,scope:self});}else{this.loadNext();}};if(css.length>startLen){YAHOO.util.Get.css(this._filter(css),{data:this._loading,onSuccess:loadScript,onFailure:this._onFailure,onTimeout:this._onTimeout,insertBefore:this.insertBefore,charset:this.charset,timeout:this.timeout,scope:self});}else{loadScript();}return;}else{this.loadNext(this._loading);}},insert:function(o,type){this.calculate(o);this._loading=true;this.loadType=type;if(this.combine){return this._combine();}if(!type){var self=this;this._internalCallback=function(){self._internalCallback=null;self.insert(null,"js");};this.insert(null,"css");return;}this.loadNext();},sandbox:function(o,type){var self=this,success=function(o){var idx=o.argument[0],name=o.argument[2];self._scriptText[idx]=o.responseText;if(self.onProgress){self.onProgress.call(self.scope,{name:name,scriptText:o.responseText,xhrResponse:o,data:self.data});}self._loadCount++;if(self._loadCount>=self._stopCount){var v=self.varName||"YAHOO";var t="(function() {\n";var b="\nreturn "+v+";\n})();";var ref=eval(t+self._scriptText.join("\n")+b);self._pushEvents(ref);if(ref){self.onSuccess.call(self.scope,{reference:ref,data:self.data});}else{self._onFailure.call(self.varName+" reference failure");}}},failure=function(o){self.onFailure.call(self.scope,{msg:"XHR failure",xhrResponse:o,data:self.data});};self._config(o);if(!self.onSuccess){throw new Error("You must supply an onSuccess handler for your sandbox");}self._sandbox=true;if(!type||type!=="js"){self._internalCallback=function(){self._internalCallback=null;self.sandbox(null,"js");};self.insert(null,"css");return;}if(!util.Connect){var ld=new YAHOO.util.YUILoader();ld.insert({base:self.base,filter:self.filter,require:"connection",insertBefore:self.insertBefore,charset:self.charset,onSuccess:function(){self.sandbox(null,"js");},scope:self},"js");return;}self._scriptText=[];self._loadCount=0;self._stopCount=self.sorted.length;self._xhr=[];self.calculate();var s=self.sorted,l=s.length,i,m,url;for(i=0;i<l;i=i+1){m=self.moduleInfo[s[i]];if(!m){self._onFailure("undefined module "+m);for(var j=0;j<self._xhr.length;j=j+1){self._xhr[j].abort();}return;}if(m.type!=="js"){self._loadCount++;continue;}url=m.fullpath;url=(url)?self._filter(url):self._url(m.path);var xhrData={success:success,failure:failure,scope:self,argument:[i,url,s[i]]};self._xhr.push(util.Connect.asyncRequest("GET",url,xhrData));}},loadNext:function(mname){if(!this._loading){return;}var self=this,donext=function(o){self.loadNext(o.data);},successfn,s=this.sorted,len=s.length,i,fn,m,url;if(mname){if(mname!==this._loading){return;}this.inserted[mname]=true;if(this.onProgress){this.onProgress.call(this.scope,{name:mname,data:this.data});}}for(i=0;i<len;i=i+1){if(s[i] in this.inserted){continue;}if(s[i]===this._loading){return;}m=this.moduleInfo[s[i]];if(!m){this.onFailure.call(this.scope,{msg:"undefined module "+m,data:this.data});return;}if(!this.loadType||this.loadType===m.type){successfn=donext;this._loading=s[i];fn=(m.type==="css")?util.Get.css:util.Get.script;url=m.fullpath;url=(url)?this._filter(url):this._url(m.path);if(env.ua.webkit&&env.ua.webkit<420&&m.type==="js"&&!m.varName){successfn=null;this._useYahooListener=true;}fn(url,{data:s[i],onSuccess:successfn,onFailure:this._onFailure,onTimeout:this._onTimeout,insertBefore:this.insertBefore,charset:this.charset,timeout:this.timeout,varName:m.varName,scope:self});return;}}this._loading=null;if(this._internalCallback){var f=this._internalCallback;this._internalCallback=null;f.call(this);}else{if(this.onSuccess){this._pushEvents();this.onSuccess.call(this.scope,{data:this.data});}}},_pushEvents:function(ref){var r=ref||YAHOO;if(r.util&&r.util.Event){r.util.Event._load();}},_filter:function(str){var f=this.filter;return(f)?str.replace(new RegExp(f.searchExp,"g"),f.replaceStr):str;
+},_url:function(path){return this._filter((this.base||"")+path);}};})();YAHOO.register("yuiloader",YAHOO.util.YUILoader,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/yuitest/yuitest-min.js b/Websites/bugs.webkit.org/js/yui/yuitest/yuitest-min.js
new file mode 100644
index 0000000..10398b2
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/yuitest/yuitest-min.js
@@ -0,0 +1,11 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.namespace("tool");(function(){var a=0;YAHOO.tool.TestCase=function(b){this._should={};for(var c in b){this[c]=b[c];}if(!YAHOO.lang.isString(this.name)){this.name="testCase"+(a++);}};YAHOO.tool.TestCase.prototype={resume:function(b){YAHOO.tool.TestRunner.resume(b);},wait:function(d,c){var b=arguments;if(YAHOO.lang.isFunction(b[0])){throw new YAHOO.tool.TestCase.Wait(b[0],b[1]);}else{throw new YAHOO.tool.TestCase.Wait(function(){YAHOO.util.Assert.fail("Timeout: wait() called but resume() never called.");},(YAHOO.lang.isNumber(b[0])?b[0]:10000));}},setUp:function(){},tearDown:function(){}};YAHOO.tool.TestCase.Wait=function(c,b){this.segment=(YAHOO.lang.isFunction(c)?c:null);this.delay=(YAHOO.lang.isNumber(b)?b:0);};})();YAHOO.namespace("tool");YAHOO.tool.TestSuite=function(a){this.name="";this.items=[];if(YAHOO.lang.isString(a)){this.name=a;}else{if(YAHOO.lang.isObject(a)){YAHOO.lang.augmentObject(this,a,true);}}if(this.name===""){this.name=YAHOO.util.Dom.generateId(null,"testSuite");}};YAHOO.tool.TestSuite.prototype={add:function(a){if(a instanceof YAHOO.tool.TestSuite||a instanceof YAHOO.tool.TestCase){this.items.push(a);}},setUp:function(){},tearDown:function(){}};YAHOO.namespace("tool");YAHOO.tool.TestRunner=(function(){function b(c){this.testObject=c;this.firstChild=null;this.lastChild=null;this.parent=null;this.next=null;this.results={passed:0,failed:0,total:0,ignored:0,duration:0};if(c instanceof YAHOO.tool.TestSuite){this.results.type="testsuite";this.results.name=c.name;}else{if(c instanceof YAHOO.tool.TestCase){this.results.type="testcase";this.results.name=c.name;}}}b.prototype={appendChild:function(c){var d=new b(c);if(this.firstChild===null){this.firstChild=this.lastChild=d;}else{this.lastChild.next=d;this.lastChild=d;}d.parent=this;return d;}};function a(){a.superclass.constructor.apply(this,arguments);this.masterSuite=new YAHOO.tool.TestSuite("yuitests"+(new Date()).getTime());this._cur=null;this._root=null;this._running=false;this._lastResults=null;var d=[this.TEST_CASE_BEGIN_EVENT,this.TEST_CASE_COMPLETE_EVENT,this.TEST_SUITE_BEGIN_EVENT,this.TEST_SUITE_COMPLETE_EVENT,this.TEST_PASS_EVENT,this.TEST_FAIL_EVENT,this.TEST_IGNORE_EVENT,this.COMPLETE_EVENT,this.BEGIN_EVENT];for(var c=0;c<d.length;c++){this.createEvent(d[c],{scope:this});}}YAHOO.lang.extend(a,YAHOO.util.EventProvider,{TEST_CASE_BEGIN_EVENT:"testcasebegin",TEST_CASE_COMPLETE_EVENT:"testcasecomplete",TEST_SUITE_BEGIN_EVENT:"testsuitebegin",TEST_SUITE_COMPLETE_EVENT:"testsuitecomplete",TEST_PASS_EVENT:"pass",TEST_FAIL_EVENT:"fail",TEST_IGNORE_EVENT:"ignore",COMPLETE_EVENT:"complete",BEGIN_EVENT:"begin",getName:function(){return this.masterSuite.name;},setName:function(c){this.masterSuite.name=c;},isRunning:function(){return this._running;},getResults:function(c){if(!this._running&&this._lastResults){if(YAHOO.lang.isFunction(c)){return c(this._lastResults);}else{return this._lastResults;}}else{return null;}},getCoverage:function(c){if(!this._running&&typeof _yuitest_coverage=="object"){if(YAHOO.lang.isFunction(c)){return c(_yuitest_coverage);}else{return _yuitest_coverage;}}else{return null;}},getName:function(){return this.masterSuite.name;},setName:function(c){this.masterSuite.name=c;},_addTestCaseToTestTree:function(c,d){var e=c.appendChild(d);for(var f in d){if(f.indexOf("test")===0&&YAHOO.lang.isFunction(d[f])){e.appendChild(f);}}},_addTestSuiteToTestTree:function(c,f){var e=c.appendChild(f);for(var d=0;d<f.items.length;d++){if(f.items[d] instanceof YAHOO.tool.TestSuite){this._addTestSuiteToTestTree(e,f.items[d]);}else{if(f.items[d] instanceof YAHOO.tool.TestCase){this._addTestCaseToTestTree(e,f.items[d]);}}}},_buildTestTree:function(){this._root=new b(this.masterSuite);for(var c=0;c<this.masterSuite.items.length;c++){if(this.masterSuite.items[c] instanceof YAHOO.tool.TestSuite){this._addTestSuiteToTestTree(this._root,this.masterSuite.items[c]);}else{if(this.masterSuite.items[c] instanceof YAHOO.tool.TestCase){this._addTestCaseToTestTree(this._root,this.masterSuite.items[c]);}}}},_handleTestObjectComplete:function(c){if(YAHOO.lang.isObject(c.testObject)){c.parent.results.passed+=c.results.passed;c.parent.results.failed+=c.results.failed;c.parent.results.total+=c.results.total;c.parent.results.ignored+=c.results.ignored;c.parent.results[c.testObject.name]=c.results;if(c.testObject instanceof YAHOO.tool.TestSuite){c.testObject.tearDown();c.results.duration=(new Date())-c._start;this.fireEvent(this.TEST_SUITE_COMPLETE_EVENT,{testSuite:c.testObject,results:c.results});}else{if(c.testObject instanceof YAHOO.tool.TestCase){c.results.duration=(new Date())-c._start;this.fireEvent(this.TEST_CASE_COMPLETE_EVENT,{testCase:c.testObject,results:c.results});}}}},_next:function(){if(this._cur===null){this._cur=this._root;}else{if(this._cur.firstChild){this._cur=this._cur.firstChild;}else{if(this._cur.next){this._cur=this._cur.next;}else{while(this._cur&&!this._cur.next&&this._cur!==this._root){this._handleTestObjectComplete(this._cur);this._cur=this._cur.parent;}if(this._cur==this._root){this._cur.results.type="report";this._cur.results.timestamp=(new Date()).toLocaleString();this._cur.results.duration=(new Date())-this._cur._start;this._lastResults=this._cur.results;this._running=false;this.fireEvent(this.COMPLETE_EVENT,{results:this._lastResults});this._cur=null;}else{this._handleTestObjectComplete(this._cur);this._cur=this._cur.next;}}}}return this._cur;},_run:function(){var e=false;var d=this._next();if(d!==null){this._running=true;this._lastResult=null;var c=d.testObject;if(YAHOO.lang.isObject(c)){if(c instanceof YAHOO.tool.TestSuite){this.fireEvent(this.TEST_SUITE_BEGIN_EVENT,{testSuite:c});d._start=new Date();c.setUp();}else{if(c instanceof YAHOO.tool.TestCase){this.fireEvent(this.TEST_CASE_BEGIN_EVENT,{testCase:c});d._start=new Date();}}if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}}else{this._runTest(d);}}},_resumeTest:function(h){var c=this._cur;var i=c.testObject;
+var f=c.parent.testObject;if(f.__yui_wait){clearTimeout(f.__yui_wait);delete f.__yui_wait;}var l=(f._should.fail||{})[i];var d=(f._should.error||{})[i];var g=false;var j=null;try{h.apply(f);if(l){j=new YAHOO.util.ShouldFail();g=true;}else{if(d){j=new YAHOO.util.ShouldError();g=true;}}}catch(k){if(k instanceof YAHOO.util.AssertionError){if(!l){j=k;g=true;}}else{if(k instanceof YAHOO.tool.TestCase.Wait){if(YAHOO.lang.isFunction(k.segment)){if(YAHOO.lang.isNumber(k.delay)){if(typeof setTimeout!="undefined"){f.__yui_wait=setTimeout(function(){YAHOO.tool.TestRunner._resumeTest(k.segment);},k.delay);}else{throw new Error("Asynchronous tests not supported in this environment.");}}}return;}else{if(!d){j=new YAHOO.util.UnexpectedError(k);g=true;}else{if(YAHOO.lang.isString(d)){if(k.message!=d){j=new YAHOO.util.UnexpectedError(k);g=true;}}else{if(YAHOO.lang.isFunction(d)){if(!(k instanceof d)){j=new YAHOO.util.UnexpectedError(k);g=true;}}else{if(YAHOO.lang.isObject(d)){if(!(k instanceof d.constructor)||k.message!=d.message){j=new YAHOO.util.UnexpectedError(k);g=true;}}}}}}}}if(g){this.fireEvent(this.TEST_FAIL_EVENT,{testCase:f,testName:i,error:j});}else{this.fireEvent(this.TEST_PASS_EVENT,{testCase:f,testName:i});}f.tearDown();var e=(new Date())-c._start;c.parent.results[i]={result:g?"fail":"pass",message:j?j.getMessage():"Test passed",type:"test",name:i,duration:e};if(g){c.parent.results.failed++;}else{c.parent.results.passed++;}c.parent.results.total++;if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}},_runTest:function(f){var c=f.testObject;var d=f.parent.testObject;var g=d[c];var e=(d._should.ignore||{})[c];if(e){f.parent.results[c]={result:"ignore",message:"Test ignored",type:"test",name:c};f.parent.results.ignored++;f.parent.results.total++;this.fireEvent(this.TEST_IGNORE_EVENT,{testCase:d,testName:c});if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}}else{f._start=new Date();d.setUp();this._resumeTest(g);}},fireEvent:function(c,d){d=d||{};d.type=c;a.superclass.fireEvent.call(this,c,d);},add:function(c){this.masterSuite.add(c);},clear:function(){this.masterSuite=new YAHOO.tool.TestSuite("yuitests"+(new Date()).getTime());},resume:function(c){this._resumeTest(c||function(){});},run:function(c){var d=YAHOO.tool.TestRunner;if(!c&&this.masterSuite.items.length==1&&this.masterSuite.items[0] instanceof YAHOO.tool.TestSuite){this.masterSuite=this.masterSuite.items[0];}d._buildTestTree();d._root._start=new Date();d.fireEvent(d.BEGIN_EVENT);d._run();}});return new a();})();YAHOO.namespace("util");YAHOO.util.Assert={_formatMessage:function(b,a){var c=b;if(YAHOO.lang.isString(b)&&b.length>0){return YAHOO.lang.substitute(b,{message:a});}else{return a;}},fail:function(a){throw new YAHOO.util.AssertionError(this._formatMessage(a,"Test force-failed."));},areEqual:function(b,c,a){if(b!=c){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Values should be equal."),b,c);}},areNotEqual:function(a,c,b){if(a==c){throw new YAHOO.util.UnexpectedValue(this._formatMessage(b,"Values should not be equal."),a);}},areNotSame:function(a,c,b){if(a===c){throw new YAHOO.util.UnexpectedValue(this._formatMessage(b,"Values should not be the same."),a);}},areSame:function(b,c,a){if(b!==c){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Values should be the same."),b,c);}},isFalse:function(b,a){if(false!==b){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be false."),false,b);}},isTrue:function(b,a){if(true!==b){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be true."),true,b);}},isNaN:function(b,a){if(!isNaN(b)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be NaN."),NaN,b);}},isNotNaN:function(b,a){if(isNaN(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Values should not be NaN."),NaN);}},isNotNull:function(b,a){if(YAHOO.lang.isNull(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Values should not be null."),null);}},isNotUndefined:function(b,a){if(YAHOO.lang.isUndefined(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should not be undefined."),undefined);}},isNull:function(b,a){if(!YAHOO.lang.isNull(b)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be null."),null,b);}},isUndefined:function(b,a){if(!YAHOO.lang.isUndefined(b)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be undefined."),undefined,b);}},isArray:function(b,a){if(!YAHOO.lang.isArray(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be an array."),b);}},isBoolean:function(b,a){if(!YAHOO.lang.isBoolean(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be a Boolean."),b);}},isFunction:function(b,a){if(!YAHOO.lang.isFunction(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be a function."),b);}},isInstanceOf:function(b,c,a){if(!(c instanceof b)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value isn't an instance of expected type."),b,c);}},isNumber:function(b,a){if(!YAHOO.lang.isNumber(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be a number."),b);}},isObject:function(b,a){if(!YAHOO.lang.isObject(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be an object."),b);}},isString:function(b,a){if(!YAHOO.lang.isString(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be a string."),b);}},isTypeOf:function(b,c,a){if(typeof c!=b){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be of type "+b+"."),b,typeof c);}}};YAHOO.util.AssertionError=function(a){this.message=a;this.name="AssertionError";};YAHOO.lang.extend(YAHOO.util.AssertionError,Object,{getMessage:function(){return this.message;},toString:function(){return this.name+": "+this.getMessage();
+}});YAHOO.util.ComparisonFailure=function(b,a,c){YAHOO.util.AssertionError.call(this,b);this.expected=a;this.actual=c;this.name="ComparisonFailure";};YAHOO.lang.extend(YAHOO.util.ComparisonFailure,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nExpected: "+this.expected+" ("+(typeof this.expected)+")"+"\nActual:"+this.actual+" ("+(typeof this.actual)+")";}});YAHOO.util.UnexpectedValue=function(b,a){YAHOO.util.AssertionError.call(this,b);this.unexpected=a;this.name="UnexpectedValue";};YAHOO.lang.extend(YAHOO.util.UnexpectedValue,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nUnexpected: "+this.unexpected+" ("+(typeof this.unexpected)+") ";}});YAHOO.util.ShouldFail=function(a){YAHOO.util.AssertionError.call(this,a||"This test should fail but didn't.");this.name="ShouldFail";};YAHOO.lang.extend(YAHOO.util.ShouldFail,YAHOO.util.AssertionError);YAHOO.util.ShouldError=function(a){YAHOO.util.AssertionError.call(this,a||"This test should have thrown an error but didn't.");this.name="ShouldError";};YAHOO.lang.extend(YAHOO.util.ShouldError,YAHOO.util.AssertionError);YAHOO.util.UnexpectedError=function(a){YAHOO.util.AssertionError.call(this,"Unexpected error: "+a.message);this.cause=a;this.name="UnexpectedError";this.stack=a.stack;};YAHOO.lang.extend(YAHOO.util.UnexpectedError,YAHOO.util.AssertionError);YAHOO.util.ArrayAssert={contains:function(e,d,b){var c=false;var f=YAHOO.util.Assert;for(var a=0;a<d.length&&!c;a++){if(d[a]===e){c=true;}}if(!c){f.fail(f._formatMessage(b,"Value "+e+" ("+(typeof e)+") not found in array ["+d+"]."));}},containsItems:function(c,d,b){for(var a=0;a<c.length;a++){this.contains(c[a],d,b);}},containsMatch:function(e,d,b){if(typeof e!="function"){throw new TypeError("ArrayAssert.containsMatch(): First argument must be a function.");}var c=false;var f=YAHOO.util.Assert;for(var a=0;a<d.length&&!c;a++){if(e(d[a])){c=true;}}if(!c){f.fail(f._formatMessage(b,"No match found in array ["+d+"]."));}},doesNotContain:function(e,d,b){var c=false;var f=YAHOO.util.Assert;for(var a=0;a<d.length&&!c;a++){if(d[a]===e){c=true;}}if(c){f.fail(f._formatMessage(b,"Value found in array ["+d+"]."));}},doesNotContainItems:function(c,d,b){for(var a=0;a<c.length;a++){this.doesNotContain(c[a],d,b);}},doesNotContainMatch:function(e,d,b){if(typeof e!="function"){throw new TypeError("ArrayAssert.doesNotContainMatch(): First argument must be a function.");}var c=false;var f=YAHOO.util.Assert;for(var a=0;a<d.length&&!c;a++){if(e(d[a])){c=true;}}if(c){f.fail(f._formatMessage(b,"Value found in array ["+d+"]."));}},indexOf:function(e,d,a,c){for(var b=0;b<d.length;b++){if(d[b]===e){YAHOO.util.Assert.areEqual(a,b,c||"Value exists at index "+b+" but should be at index "+a+".");return;}}var f=YAHOO.util.Assert;f.fail(f._formatMessage(c,"Value doesn't exist in array ["+d+"]."));},itemsAreEqual:function(d,f,c){var a=Math.max(d.length,f.length||0);var e=YAHOO.util.Assert;for(var b=0;b<a;b++){e.areEqual(d[b],f[b],e._formatMessage(c,"Values in position "+b+" are not equal."));}},itemsAreEquivalent:function(e,f,b,d){if(typeof b!="function"){throw new TypeError("ArrayAssert.itemsAreEquivalent(): Third argument must be a function.");}var a=Math.max(e.length,f.length||0);for(var c=0;c<a;c++){if(!b(e[c],f[c])){throw new YAHOO.util.ComparisonFailure(YAHOO.util.Assert._formatMessage(d,"Values in position "+c+" are not equivalent."),e[c],f[c]);}}},isEmpty:function(c,a){if(c.length>0){var b=YAHOO.util.Assert;b.fail(b._formatMessage(a,"Array should be empty."));}},isNotEmpty:function(c,a){if(c.length===0){var b=YAHOO.util.Assert;b.fail(b._formatMessage(a,"Array should not be empty."));}},itemsAreSame:function(d,f,c){var a=Math.max(d.length,f.length||0);var e=YAHOO.util.Assert;for(var b=0;b<a;b++){e.areSame(d[b],f[b],e._formatMessage(c,"Values in position "+b+" are not the same."));}},lastIndexOf:function(e,d,a,c){var f=YAHOO.util.Assert;for(var b=d.length;b>=0;b--){if(d[b]===e){f.areEqual(a,b,f._formatMessage(c,"Value exists at index "+b+" but should be at index "+a+"."));return;}}f.fail(f._formatMessage(c,"Value doesn't exist in array."));}};YAHOO.namespace("util");YAHOO.util.ObjectAssert={propertiesAreEqual:function(d,g,c){var f=YAHOO.util.Assert;var b=[];for(var e in d){b.push(e);}for(var a=0;a<b.length;a++){f.isNotUndefined(g[b[a]],f._formatMessage(c,"Property '"+b[a]+"' expected."));}},hasProperty:function(a,b,c){if(!(a in b)){var d=YAHOO.util.Assert;d.fail(d._formatMessage(c,"Property '"+a+"' not found on object."));}},hasOwnProperty:function(a,b,c){if(!YAHOO.lang.hasOwnProperty(b,a)){var d=YAHOO.util.Assert;d.fail(d._formatMessage(c,"Property '"+a+"' not found on object instance."));}}};YAHOO.util.DateAssert={datesAreEqual:function(b,d,a){if(b instanceof Date&&d instanceof Date){var c=YAHOO.util.Assert;c.areEqual(b.getFullYear(),d.getFullYear(),c._formatMessage(a,"Years should be equal."));c.areEqual(b.getMonth(),d.getMonth(),c._formatMessage(a,"Months should be equal."));c.areEqual(b.getDate(),d.getDate(),c._formatMessage(a,"Day of month should be equal."));}else{throw new TypeError("DateAssert.datesAreEqual(): Expected and actual values must be Date objects.");}},timesAreEqual:function(b,d,a){if(b instanceof Date&&d instanceof Date){var c=YAHOO.util.Assert;c.areEqual(b.getHours(),d.getHours(),c._formatMessage(a,"Hours should be equal."));c.areEqual(b.getMinutes(),d.getMinutes(),c._formatMessage(a,"Minutes should be equal."));c.areEqual(b.getSeconds(),d.getSeconds(),c._formatMessage(a,"Seconds should be equal."));}else{throw new TypeError("DateAssert.timesAreEqual(): Expected and actual values must be Date objects.");}}};YAHOO.namespace("tool");YAHOO.tool.TestManager={TEST_PAGE_BEGIN_EVENT:"testpagebegin",TEST_PAGE_COMPLETE_EVENT:"testpagecomplete",TEST_MANAGER_BEGIN_EVENT:"testmanagerbegin",TEST_MANAGER_COMPLETE_EVENT:"testmanagercomplete",_curPage:null,_frame:null,_logger:null,_timeoutId:0,_pages:[],_results:null,_handleTestRunnerComplete:function(a){this.fireEvent(this.TEST_PAGE_COMPLETE_EVENT,{page:this._curPage,results:a.results});
+this._processResults(this._curPage,a.results);this._logger.clearTestRunner();if(this._pages.length){this._timeoutId=setTimeout(function(){YAHOO.tool.TestManager._run();},1000);}else{this.fireEvent(this.TEST_MANAGER_COMPLETE_EVENT,this._results);}},_processResults:function(c,a){var b=this._results;b.passed+=a.passed;b.failed+=a.failed;b.ignored+=a.ignored;b.total+=a.total;b.duration+=a.duration;if(a.failed){b.failedPages.push(c);}else{b.passedPages.push(c);}a.name=c;a.type="page";b[c]=a;},_run:function(){this._curPage=this._pages.shift();this.fireEvent(this.TEST_PAGE_BEGIN_EVENT,this._curPage);this._frame.location.replace(this._curPage);},load:function(){if(parent.YAHOO.tool.TestManager!==this){parent.YAHOO.tool.TestManager.load();}else{if(this._frame){var a=this._frame.YAHOO.tool.TestRunner;this._logger.setTestRunner(a);a.subscribe(a.COMPLETE_EVENT,this._handleTestRunnerComplete,this,true);a.run();}}},setPages:function(a){this._pages=a;},start:function(){if(!this._initialized){this.createEvent(this.TEST_PAGE_BEGIN_EVENT);this.createEvent(this.TEST_PAGE_COMPLETE_EVENT);this.createEvent(this.TEST_MANAGER_BEGIN_EVENT);this.createEvent(this.TEST_MANAGER_COMPLETE_EVENT);if(!this._frame){var a=document.createElement("iframe");a.style.visibility="hidden";a.style.position="absolute";document.body.appendChild(a);this._frame=a.contentWindow||a.contentDocument.parentWindow;}if(!this._logger){this._logger=new YAHOO.tool.TestLogger();}this._initialized=true;}this._results={passed:0,failed:0,ignored:0,total:0,type:"report",name:"YUI Test Results",duration:0,failedPages:[],passedPages:[]};this.fireEvent(this.TEST_MANAGER_BEGIN_EVENT,null);this._run();},stop:function(){clearTimeout(this._timeoutId);}};YAHOO.lang.augmentObject(YAHOO.tool.TestManager,YAHOO.util.EventProvider.prototype);YAHOO.namespace("tool");YAHOO.tool.TestLogger=function(b,a){YAHOO.tool.TestLogger.superclass.constructor.call(this,b,a);this.init();};YAHOO.lang.extend(YAHOO.tool.TestLogger,YAHOO.widget.LogReader,{footerEnabled:true,newestOnTop:false,formatMsg:function(b){var a=b.category;var c=this.html2Text(b.msg);return'<pre><p><span class="'+a+'">'+a.toUpperCase()+"</span> "+c+"</p></pre>";},init:function(){if(YAHOO.tool.TestRunner){this.setTestRunner(YAHOO.tool.TestRunner);}this.hideSource("global");this.hideSource("LogReader");this.hideCategory("warn");this.hideCategory("window");this.hideCategory("time");this.clearConsole();},clearTestRunner:function(){if(this._runner){this._runner.unsubscribeAll();this._runner=null;}},setTestRunner:function(a){if(this._runner){this.clearTestRunner();}this._runner=a;a.subscribe(a.TEST_PASS_EVENT,this._handleTestRunnerEvent,this,true);a.subscribe(a.TEST_FAIL_EVENT,this._handleTestRunnerEvent,this,true);a.subscribe(a.TEST_IGNORE_EVENT,this._handleTestRunnerEvent,this,true);a.subscribe(a.BEGIN_EVENT,this._handleTestRunnerEvent,this,true);a.subscribe(a.COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);a.subscribe(a.TEST_SUITE_BEGIN_EVENT,this._handleTestRunnerEvent,this,true);a.subscribe(a.TEST_SUITE_COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);a.subscribe(a.TEST_CASE_BEGIN_EVENT,this._handleTestRunnerEvent,this,true);a.subscribe(a.TEST_CASE_COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);},_handleTestRunnerEvent:function(d){var a=YAHOO.tool.TestRunner;var c="";var b="";switch(d.type){case a.BEGIN_EVENT:c="Testing began at "+(new Date()).toString()+".";b="info";break;case a.COMPLETE_EVENT:c="Testing completed at "+(new Date()).toString()+".\nPassed:"+d.results.passed+" Failed:"+d.results.failed+" Total:"+d.results.total;b="info";break;case a.TEST_FAIL_EVENT:c=d.testName+": "+d.error.getMessage();b="fail";break;case a.TEST_IGNORE_EVENT:c=d.testName+": ignored.";b="ignore";break;case a.TEST_PASS_EVENT:c=d.testName+": passed.";b="pass";break;case a.TEST_SUITE_BEGIN_EVENT:c='Test suite "'+d.testSuite.name+'" started.';b="info";break;case a.TEST_SUITE_COMPLETE_EVENT:c='Test suite "'+d.testSuite.name+'" completed.\nPassed:'+d.results.passed+" Failed:"+d.results.failed+" Total:"+d.results.total;b="info";break;case a.TEST_CASE_BEGIN_EVENT:c='Test case "'+d.testCase.name+'" started.';b="info";break;case a.TEST_CASE_COMPLETE_EVENT:c='Test case "'+d.testCase.name+'" completed.\nPassed:'+d.results.passed+" Failed:"+d.results.failed+" Total:"+d.results.total;b="info";break;default:c="Unexpected event "+d.type;c="info";}YAHOO.log(c,b,"TestRunner");}});YAHOO.namespace("tool.TestFormat");(function(){YAHOO.tool.TestFormat.JSON=function(b){return YAHOO.lang.JSON.stringify(b);};function a(b){return b.replace(/["'<>&]/g,function(d){switch(d){case"<":return"<";case">":return">";case'"':return""";case"'":return"'";case"&":return"&";}});}YAHOO.tool.TestFormat.XML=function(c){function b(f){var d=YAHOO.lang,e="<"+f.type+' name="'+a(f.name)+'"';if(d.isNumber(f.duration)){e+=' duration="'+f.duration+'"';}if(f.type=="test"){e+=' result="'+f.result+'" message="'+a(f.message)+'">';}else{e+=' passed="'+f.passed+'" failed="'+f.failed+'" ignored="'+f.ignored+'" total="'+f.total+'">';for(var g in f){if(d.hasOwnProperty(f,g)&&d.isObject(f[g])&&!d.isArray(f[g])){e+=b(f[g]);}}}e+="</"+f.type+">";return e;}return'<?xml version="1.0" encoding="UTF-8"?>'+b(c);};YAHOO.tool.TestFormat.JUnitXML=function(b){function c(f){var d=YAHOO.lang,e="",g;switch(f.type){case"test":if(f.result!="ignore"){e='<testcase name="'+a(f.name)+'">';if(f.result=="fail"){e+='<failure message="'+a(f.message)+'"><![CDATA['+f.message+"]]></failure>";}e+="</testcase>";}break;case"testcase":e='<testsuite name="'+a(f.name)+'" tests="'+f.total+'" failures="'+f.failed+'">';for(g in f){if(d.hasOwnProperty(f,g)&&d.isObject(f[g])&&!d.isArray(f[g])){e+=c(f[g]);}}e+="</testsuite>";break;case"testsuite":for(g in f){if(d.hasOwnProperty(f,g)&&d.isObject(f[g])&&!d.isArray(f[g])){e+=c(f[g]);}}break;case"report":e="<testsuites>";for(g in f){if(d.hasOwnProperty(f,g)&&d.isObject(f[g])&&!d.isArray(f[g])){e+=c(f[g]);}}e+="</testsuites>";}return e;}return'<?xml version="1.0" encoding="UTF-8"?>'+c(b);
+};YAHOO.tool.TestFormat.TAP=function(c){var d=1;function b(f){var e=YAHOO.lang,g="";switch(f.type){case"test":if(f.result!="ignore"){g="ok "+(d++)+" - "+f.name;if(f.result=="fail"){g="not "+g+" - "+f.message;}g+="\n";}else{g="#Ignored test "+f.name+"\n";}break;case"testcase":g="#Begin testcase "+f.name+"("+f.failed+" failed of "+f.total+")\n";for(prop in f){if(e.hasOwnProperty(f,prop)&&e.isObject(f[prop])&&!e.isArray(f[prop])){g+=b(f[prop]);}}g+="#End testcase "+f.name+"\n";break;case"testsuite":g="#Begin testsuite "+f.name+"("+f.failed+" failed of "+f.total+")\n";for(prop in f){if(e.hasOwnProperty(f,prop)&&e.isObject(f[prop])&&!e.isArray(f[prop])){g+=b(f[prop]);}}g+="#End testsuite "+f.name+"\n";break;case"report":for(prop in f){if(e.hasOwnProperty(f,prop)&&e.isObject(f[prop])&&!e.isArray(f[prop])){g+=b(f[prop]);}}}return g;}return"1.."+c.total+"\n"+b(c);};})();YAHOO.namespace("tool.CoverageFormat");YAHOO.tool.CoverageFormat.JSON=function(a){return YAHOO.lang.JSON.stringify(a);};YAHOO.tool.CoverageFormat.XdebugJSON=function(b){var a={},c;for(c in b){if(b.hasOwnProperty(c)){a[c]=b[c].lines;}}return YAHOO.lang.JSON.stringify(a);};YAHOO.namespace("tool");YAHOO.tool.TestReporter=function(a,b){this.url=a;this.format=b||YAHOO.tool.TestFormat.XML;this._fields=new Object();this._form=null;this._iframe=null;};YAHOO.tool.TestReporter.prototype={constructor:YAHOO.tool.TestReporter,_convertToISOString:function(a){function b(c){return c<10?"0"+c:c;}return a.getUTCFullYear()+"-"+b(a.getUTCMonth()+1)+"-"+b(a.getUTCDate())+"T"+b(a.getUTCHours())+":"+b(a.getUTCMinutes())+":"+b(a.getUTCSeconds())+"Z";},addField:function(a,b){this._fields[a]=b;},clearFields:function(){this._fields=new Object();},destroy:function(){if(this._form){this._form.parentNode.removeChild(this._form);this._form=null;}if(this._iframe){this._iframe.parentNode.removeChild(this._iframe);this._iframe=null;}this._fields=null;},report:function(a){if(!this._form){this._form=document.createElement("form");this._form.method="post";this._form.style.visibility="hidden";this._form.style.position="absolute";this._form.style.top=0;document.body.appendChild(this._form);if(YAHOO.env.ua.ie){this._iframe=document.createElement('<iframe name="yuiTestTarget" />');}else{this._iframe=document.createElement("iframe");this._iframe.name="yuiTestTarget";}this._iframe.src="javascript:false";this._iframe.style.visibility="hidden";this._iframe.style.position="absolute";this._iframe.style.top=0;document.body.appendChild(this._iframe);this._form.target="yuiTestTarget";}this._form.action=this.url;while(this._form.hasChildNodes()){this._form.removeChild(this._form.lastChild);}this._fields.results=this.format(a);this._fields.useragent=navigator.userAgent;this._fields.timestamp=this._convertToISOString(new Date());for(var b in this._fields){if(YAHOO.lang.hasOwnProperty(this._fields,b)&&typeof this._fields[b]!="function"){if(YAHOO.env.ua.ie){input=document.createElement('<input name="'+b+'" >');}else{input=document.createElement("input");input.name=b;}input.type="hidden";input.value=this._fields[b];this._form.appendChild(input);}}delete this._fields.results;delete this._fields.useragent;delete this._fields.timestamp;if(arguments[1]!==false){this._form.submit();}}};YUITest={TestRunner:YAHOO.tool.TestRunner,ResultsFormat:YAHOO.tool.TestFormat,CoverageFormat:YAHOO.tool.CoverageFormat};YAHOO.register("yuitest",YAHOO.tool.TestRunner,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/js/yui/yuitest/yuitest_core-min.js b/Websites/bugs.webkit.org/js/yui/yuitest/yuitest_core-min.js
new file mode 100644
index 0000000..3ddd3ce
--- /dev/null
+++ b/Websites/bugs.webkit.org/js/yui/yuitest/yuitest_core-min.js
@@ -0,0 +1,9 @@
+/*
+Copyright (c) 2011, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 2.9.0
+*/
+YAHOO.namespace("tool");(function(){var a=0;YAHOO.tool.TestCase=function(b){this._should={};for(var c in b){this[c]=b[c];}if(!YAHOO.lang.isString(this.name)){this.name="testCase"+(a++);}};YAHOO.tool.TestCase.prototype={resume:function(b){YAHOO.tool.TestRunner.resume(b);},wait:function(d,c){var b=arguments;if(YAHOO.lang.isFunction(b[0])){throw new YAHOO.tool.TestCase.Wait(b[0],b[1]);}else{throw new YAHOO.tool.TestCase.Wait(function(){YAHOO.util.Assert.fail("Timeout: wait() called but resume() never called.");},(YAHOO.lang.isNumber(b[0])?b[0]:10000));}},setUp:function(){},tearDown:function(){}};YAHOO.tool.TestCase.Wait=function(c,b){this.segment=(YAHOO.lang.isFunction(c)?c:null);this.delay=(YAHOO.lang.isNumber(b)?b:0);};})();YAHOO.namespace("tool");YAHOO.tool.TestSuite=function(a){this.name="";this.items=[];if(YAHOO.lang.isString(a)){this.name=a;}else{if(YAHOO.lang.isObject(a)){YAHOO.lang.augmentObject(this,a,true);}}if(this.name===""){this.name=YAHOO.util.Dom.generateId(null,"testSuite");}};YAHOO.tool.TestSuite.prototype={add:function(a){if(a instanceof YAHOO.tool.TestSuite||a instanceof YAHOO.tool.TestCase){this.items.push(a);}},setUp:function(){},tearDown:function(){}};YAHOO.namespace("tool");YAHOO.tool.TestRunner=(function(){function b(c){this.testObject=c;this.firstChild=null;this.lastChild=null;this.parent=null;this.next=null;this.results={passed:0,failed:0,total:0,ignored:0,duration:0};if(c instanceof YAHOO.tool.TestSuite){this.results.type="testsuite";this.results.name=c.name;}else{if(c instanceof YAHOO.tool.TestCase){this.results.type="testcase";this.results.name=c.name;}}}b.prototype={appendChild:function(c){var d=new b(c);if(this.firstChild===null){this.firstChild=this.lastChild=d;}else{this.lastChild.next=d;this.lastChild=d;}d.parent=this;return d;}};function a(){a.superclass.constructor.apply(this,arguments);this.masterSuite=new YAHOO.tool.TestSuite("yuitests"+(new Date()).getTime());this._cur=null;this._root=null;this._running=false;this._lastResults=null;var d=[this.TEST_CASE_BEGIN_EVENT,this.TEST_CASE_COMPLETE_EVENT,this.TEST_SUITE_BEGIN_EVENT,this.TEST_SUITE_COMPLETE_EVENT,this.TEST_PASS_EVENT,this.TEST_FAIL_EVENT,this.TEST_IGNORE_EVENT,this.COMPLETE_EVENT,this.BEGIN_EVENT];for(var c=0;c<d.length;c++){this.createEvent(d[c],{scope:this});}}YAHOO.lang.extend(a,YAHOO.util.EventProvider,{TEST_CASE_BEGIN_EVENT:"testcasebegin",TEST_CASE_COMPLETE_EVENT:"testcasecomplete",TEST_SUITE_BEGIN_EVENT:"testsuitebegin",TEST_SUITE_COMPLETE_EVENT:"testsuitecomplete",TEST_PASS_EVENT:"pass",TEST_FAIL_EVENT:"fail",TEST_IGNORE_EVENT:"ignore",COMPLETE_EVENT:"complete",BEGIN_EVENT:"begin",getName:function(){return this.masterSuite.name;},setName:function(c){this.masterSuite.name=c;},isRunning:function(){return this._running;},getResults:function(c){if(!this._running&&this._lastResults){if(YAHOO.lang.isFunction(c)){return c(this._lastResults);}else{return this._lastResults;}}else{return null;}},getCoverage:function(c){if(!this._running&&typeof _yuitest_coverage=="object"){if(YAHOO.lang.isFunction(c)){return c(_yuitest_coverage);}else{return _yuitest_coverage;}}else{return null;}},getName:function(){return this.masterSuite.name;},setName:function(c){this.masterSuite.name=c;},_addTestCaseToTestTree:function(c,d){var e=c.appendChild(d);for(var f in d){if(f.indexOf("test")===0&&YAHOO.lang.isFunction(d[f])){e.appendChild(f);}}},_addTestSuiteToTestTree:function(c,f){var e=c.appendChild(f);for(var d=0;d<f.items.length;d++){if(f.items[d] instanceof YAHOO.tool.TestSuite){this._addTestSuiteToTestTree(e,f.items[d]);}else{if(f.items[d] instanceof YAHOO.tool.TestCase){this._addTestCaseToTestTree(e,f.items[d]);}}}},_buildTestTree:function(){this._root=new b(this.masterSuite);for(var c=0;c<this.masterSuite.items.length;c++){if(this.masterSuite.items[c] instanceof YAHOO.tool.TestSuite){this._addTestSuiteToTestTree(this._root,this.masterSuite.items[c]);}else{if(this.masterSuite.items[c] instanceof YAHOO.tool.TestCase){this._addTestCaseToTestTree(this._root,this.masterSuite.items[c]);}}}},_handleTestObjectComplete:function(c){if(YAHOO.lang.isObject(c.testObject)){c.parent.results.passed+=c.results.passed;c.parent.results.failed+=c.results.failed;c.parent.results.total+=c.results.total;c.parent.results.ignored+=c.results.ignored;c.parent.results[c.testObject.name]=c.results;if(c.testObject instanceof YAHOO.tool.TestSuite){c.testObject.tearDown();c.results.duration=(new Date())-c._start;this.fireEvent(this.TEST_SUITE_COMPLETE_EVENT,{testSuite:c.testObject,results:c.results});}else{if(c.testObject instanceof YAHOO.tool.TestCase){c.results.duration=(new Date())-c._start;this.fireEvent(this.TEST_CASE_COMPLETE_EVENT,{testCase:c.testObject,results:c.results});}}}},_next:function(){if(this._cur===null){this._cur=this._root;}else{if(this._cur.firstChild){this._cur=this._cur.firstChild;}else{if(this._cur.next){this._cur=this._cur.next;}else{while(this._cur&&!this._cur.next&&this._cur!==this._root){this._handleTestObjectComplete(this._cur);this._cur=this._cur.parent;}if(this._cur==this._root){this._cur.results.type="report";this._cur.results.timestamp=(new Date()).toLocaleString();this._cur.results.duration=(new Date())-this._cur._start;this._lastResults=this._cur.results;this._running=false;this.fireEvent(this.COMPLETE_EVENT,{results:this._lastResults});this._cur=null;}else{this._handleTestObjectComplete(this._cur);this._cur=this._cur.next;}}}}return this._cur;},_run:function(){var e=false;var d=this._next();if(d!==null){this._running=true;this._lastResult=null;var c=d.testObject;if(YAHOO.lang.isObject(c)){if(c instanceof YAHOO.tool.TestSuite){this.fireEvent(this.TEST_SUITE_BEGIN_EVENT,{testSuite:c});d._start=new Date();c.setUp();}else{if(c instanceof YAHOO.tool.TestCase){this.fireEvent(this.TEST_CASE_BEGIN_EVENT,{testCase:c});d._start=new Date();}}if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}}else{this._runTest(d);}}},_resumeTest:function(h){var c=this._cur;var i=c.testObject;
+var f=c.parent.testObject;if(f.__yui_wait){clearTimeout(f.__yui_wait);delete f.__yui_wait;}var l=(f._should.fail||{})[i];var d=(f._should.error||{})[i];var g=false;var j=null;try{h.apply(f);if(l){j=new YAHOO.util.ShouldFail();g=true;}else{if(d){j=new YAHOO.util.ShouldError();g=true;}}}catch(k){if(k instanceof YAHOO.util.AssertionError){if(!l){j=k;g=true;}}else{if(k instanceof YAHOO.tool.TestCase.Wait){if(YAHOO.lang.isFunction(k.segment)){if(YAHOO.lang.isNumber(k.delay)){if(typeof setTimeout!="undefined"){f.__yui_wait=setTimeout(function(){YAHOO.tool.TestRunner._resumeTest(k.segment);},k.delay);}else{throw new Error("Asynchronous tests not supported in this environment.");}}}return;}else{if(!d){j=new YAHOO.util.UnexpectedError(k);g=true;}else{if(YAHOO.lang.isString(d)){if(k.message!=d){j=new YAHOO.util.UnexpectedError(k);g=true;}}else{if(YAHOO.lang.isFunction(d)){if(!(k instanceof d)){j=new YAHOO.util.UnexpectedError(k);g=true;}}else{if(YAHOO.lang.isObject(d)){if(!(k instanceof d.constructor)||k.message!=d.message){j=new YAHOO.util.UnexpectedError(k);g=true;}}}}}}}}if(g){this.fireEvent(this.TEST_FAIL_EVENT,{testCase:f,testName:i,error:j});}else{this.fireEvent(this.TEST_PASS_EVENT,{testCase:f,testName:i});}f.tearDown();var e=(new Date())-c._start;c.parent.results[i]={result:g?"fail":"pass",message:j?j.getMessage():"Test passed",type:"test",name:i,duration:e};if(g){c.parent.results.failed++;}else{c.parent.results.passed++;}c.parent.results.total++;if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}},_runTest:function(f){var c=f.testObject;var d=f.parent.testObject;var g=d[c];var e=(d._should.ignore||{})[c];if(e){f.parent.results[c]={result:"ignore",message:"Test ignored",type:"test",name:c};f.parent.results.ignored++;f.parent.results.total++;this.fireEvent(this.TEST_IGNORE_EVENT,{testCase:d,testName:c});if(typeof setTimeout!="undefined"){setTimeout(function(){YAHOO.tool.TestRunner._run();},0);}else{this._run();}}else{f._start=new Date();d.setUp();this._resumeTest(g);}},fireEvent:function(c,d){d=d||{};d.type=c;a.superclass.fireEvent.call(this,c,d);},add:function(c){this.masterSuite.add(c);},clear:function(){this.masterSuite=new YAHOO.tool.TestSuite("yuitests"+(new Date()).getTime());},resume:function(c){this._resumeTest(c||function(){});},run:function(c){var d=YAHOO.tool.TestRunner;if(!c&&this.masterSuite.items.length==1&&this.masterSuite.items[0] instanceof YAHOO.tool.TestSuite){this.masterSuite=this.masterSuite.items[0];}d._buildTestTree();d._root._start=new Date();d.fireEvent(d.BEGIN_EVENT);d._run();}});return new a();})();YAHOO.namespace("util");YAHOO.util.Assert={_formatMessage:function(b,a){var c=b;if(YAHOO.lang.isString(b)&&b.length>0){return YAHOO.lang.substitute(b,{message:a});}else{return a;}},fail:function(a){throw new YAHOO.util.AssertionError(this._formatMessage(a,"Test force-failed."));},areEqual:function(b,c,a){if(b!=c){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Values should be equal."),b,c);}},areNotEqual:function(a,c,b){if(a==c){throw new YAHOO.util.UnexpectedValue(this._formatMessage(b,"Values should not be equal."),a);}},areNotSame:function(a,c,b){if(a===c){throw new YAHOO.util.UnexpectedValue(this._formatMessage(b,"Values should not be the same."),a);}},areSame:function(b,c,a){if(b!==c){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Values should be the same."),b,c);}},isFalse:function(b,a){if(false!==b){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be false."),false,b);}},isTrue:function(b,a){if(true!==b){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be true."),true,b);}},isNaN:function(b,a){if(!isNaN(b)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be NaN."),NaN,b);}},isNotNaN:function(b,a){if(isNaN(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Values should not be NaN."),NaN);}},isNotNull:function(b,a){if(YAHOO.lang.isNull(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Values should not be null."),null);}},isNotUndefined:function(b,a){if(YAHOO.lang.isUndefined(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should not be undefined."),undefined);}},isNull:function(b,a){if(!YAHOO.lang.isNull(b)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be null."),null,b);}},isUndefined:function(b,a){if(!YAHOO.lang.isUndefined(b)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be undefined."),undefined,b);}},isArray:function(b,a){if(!YAHOO.lang.isArray(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be an array."),b);}},isBoolean:function(b,a){if(!YAHOO.lang.isBoolean(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be a Boolean."),b);}},isFunction:function(b,a){if(!YAHOO.lang.isFunction(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be a function."),b);}},isInstanceOf:function(b,c,a){if(!(c instanceof b)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value isn't an instance of expected type."),b,c);}},isNumber:function(b,a){if(!YAHOO.lang.isNumber(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be a number."),b);}},isObject:function(b,a){if(!YAHOO.lang.isObject(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be an object."),b);}},isString:function(b,a){if(!YAHOO.lang.isString(b)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(a,"Value should be a string."),b);}},isTypeOf:function(b,c,a){if(typeof c!=b){throw new YAHOO.util.ComparisonFailure(this._formatMessage(a,"Value should be of type "+b+"."),b,typeof c);}}};YAHOO.util.AssertionError=function(a){this.message=a;this.name="AssertionError";};YAHOO.lang.extend(YAHOO.util.AssertionError,Object,{getMessage:function(){return this.message;},toString:function(){return this.name+": "+this.getMessage();
+}});YAHOO.util.ComparisonFailure=function(b,a,c){YAHOO.util.AssertionError.call(this,b);this.expected=a;this.actual=c;this.name="ComparisonFailure";};YAHOO.lang.extend(YAHOO.util.ComparisonFailure,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nExpected: "+this.expected+" ("+(typeof this.expected)+")"+"\nActual:"+this.actual+" ("+(typeof this.actual)+")";}});YAHOO.util.UnexpectedValue=function(b,a){YAHOO.util.AssertionError.call(this,b);this.unexpected=a;this.name="UnexpectedValue";};YAHOO.lang.extend(YAHOO.util.UnexpectedValue,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nUnexpected: "+this.unexpected+" ("+(typeof this.unexpected)+") ";}});YAHOO.util.ShouldFail=function(a){YAHOO.util.AssertionError.call(this,a||"This test should fail but didn't.");this.name="ShouldFail";};YAHOO.lang.extend(YAHOO.util.ShouldFail,YAHOO.util.AssertionError);YAHOO.util.ShouldError=function(a){YAHOO.util.AssertionError.call(this,a||"This test should have thrown an error but didn't.");this.name="ShouldError";};YAHOO.lang.extend(YAHOO.util.ShouldError,YAHOO.util.AssertionError);YAHOO.util.UnexpectedError=function(a){YAHOO.util.AssertionError.call(this,"Unexpected error: "+a.message);this.cause=a;this.name="UnexpectedError";this.stack=a.stack;};YAHOO.lang.extend(YAHOO.util.UnexpectedError,YAHOO.util.AssertionError);YAHOO.util.ArrayAssert={contains:function(e,d,b){var c=false;var f=YAHOO.util.Assert;for(var a=0;a<d.length&&!c;a++){if(d[a]===e){c=true;}}if(!c){f.fail(f._formatMessage(b,"Value "+e+" ("+(typeof e)+") not found in array ["+d+"]."));}},containsItems:function(c,d,b){for(var a=0;a<c.length;a++){this.contains(c[a],d,b);}},containsMatch:function(e,d,b){if(typeof e!="function"){throw new TypeError("ArrayAssert.containsMatch(): First argument must be a function.");}var c=false;var f=YAHOO.util.Assert;for(var a=0;a<d.length&&!c;a++){if(e(d[a])){c=true;}}if(!c){f.fail(f._formatMessage(b,"No match found in array ["+d+"]."));}},doesNotContain:function(e,d,b){var c=false;var f=YAHOO.util.Assert;for(var a=0;a<d.length&&!c;a++){if(d[a]===e){c=true;}}if(c){f.fail(f._formatMessage(b,"Value found in array ["+d+"]."));}},doesNotContainItems:function(c,d,b){for(var a=0;a<c.length;a++){this.doesNotContain(c[a],d,b);}},doesNotContainMatch:function(e,d,b){if(typeof e!="function"){throw new TypeError("ArrayAssert.doesNotContainMatch(): First argument must be a function.");}var c=false;var f=YAHOO.util.Assert;for(var a=0;a<d.length&&!c;a++){if(e(d[a])){c=true;}}if(c){f.fail(f._formatMessage(b,"Value found in array ["+d+"]."));}},indexOf:function(e,d,a,c){for(var b=0;b<d.length;b++){if(d[b]===e){YAHOO.util.Assert.areEqual(a,b,c||"Value exists at index "+b+" but should be at index "+a+".");return;}}var f=YAHOO.util.Assert;f.fail(f._formatMessage(c,"Value doesn't exist in array ["+d+"]."));},itemsAreEqual:function(d,f,c){var a=Math.max(d.length,f.length||0);var e=YAHOO.util.Assert;for(var b=0;b<a;b++){e.areEqual(d[b],f[b],e._formatMessage(c,"Values in position "+b+" are not equal."));}},itemsAreEquivalent:function(e,f,b,d){if(typeof b!="function"){throw new TypeError("ArrayAssert.itemsAreEquivalent(): Third argument must be a function.");}var a=Math.max(e.length,f.length||0);for(var c=0;c<a;c++){if(!b(e[c],f[c])){throw new YAHOO.util.ComparisonFailure(YAHOO.util.Assert._formatMessage(d,"Values in position "+c+" are not equivalent."),e[c],f[c]);}}},isEmpty:function(c,a){if(c.length>0){var b=YAHOO.util.Assert;b.fail(b._formatMessage(a,"Array should be empty."));}},isNotEmpty:function(c,a){if(c.length===0){var b=YAHOO.util.Assert;b.fail(b._formatMessage(a,"Array should not be empty."));}},itemsAreSame:function(d,f,c){var a=Math.max(d.length,f.length||0);var e=YAHOO.util.Assert;for(var b=0;b<a;b++){e.areSame(d[b],f[b],e._formatMessage(c,"Values in position "+b+" are not the same."));}},lastIndexOf:function(e,d,a,c){var f=YAHOO.util.Assert;for(var b=d.length;b>=0;b--){if(d[b]===e){f.areEqual(a,b,f._formatMessage(c,"Value exists at index "+b+" but should be at index "+a+"."));return;}}f.fail(f._formatMessage(c,"Value doesn't exist in array."));}};YAHOO.namespace("util");YAHOO.util.ObjectAssert={propertiesAreEqual:function(d,g,c){var f=YAHOO.util.Assert;var b=[];for(var e in d){b.push(e);}for(var a=0;a<b.length;a++){f.isNotUndefined(g[b[a]],f._formatMessage(c,"Property '"+b[a]+"' expected."));}},hasProperty:function(a,b,c){if(!(a in b)){var d=YAHOO.util.Assert;d.fail(d._formatMessage(c,"Property '"+a+"' not found on object."));}},hasOwnProperty:function(a,b,c){if(!YAHOO.lang.hasOwnProperty(b,a)){var d=YAHOO.util.Assert;d.fail(d._formatMessage(c,"Property '"+a+"' not found on object instance."));}}};YAHOO.util.DateAssert={datesAreEqual:function(b,d,a){if(b instanceof Date&&d instanceof Date){var c=YAHOO.util.Assert;c.areEqual(b.getFullYear(),d.getFullYear(),c._formatMessage(a,"Years should be equal."));c.areEqual(b.getMonth(),d.getMonth(),c._formatMessage(a,"Months should be equal."));c.areEqual(b.getDate(),d.getDate(),c._formatMessage(a,"Day of month should be equal."));}else{throw new TypeError("DateAssert.datesAreEqual(): Expected and actual values must be Date objects.");}},timesAreEqual:function(b,d,a){if(b instanceof Date&&d instanceof Date){var c=YAHOO.util.Assert;c.areEqual(b.getHours(),d.getHours(),c._formatMessage(a,"Hours should be equal."));c.areEqual(b.getMinutes(),d.getMinutes(),c._formatMessage(a,"Minutes should be equal."));c.areEqual(b.getSeconds(),d.getSeconds(),c._formatMessage(a,"Seconds should be equal."));}else{throw new TypeError("DateAssert.timesAreEqual(): Expected and actual values must be Date objects.");}}};YAHOO.register("yuitest_core",YAHOO.tool.TestRunner,{version:"2.9.0",build:"2800"});
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/jsonrpc.cgi b/Websites/bugs.webkit.org/jsonrpc.cgi
new file mode 100755
index 0000000..b64b98d
--- /dev/null
+++ b/Websites/bugs.webkit.org/jsonrpc.cgi
@@ -0,0 +1,41 @@
+#!/usr/bin/env perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla JSON Webservices Interface.
+#
+# The Initial Developer of the Original Code is the San Jose State
+# University Foundation. Portions created by the Initial Developer
+# are Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::WebService::Constants;
+BEGIN {
+ if (!Bugzilla->feature('jsonrpc')) {
+ ThrowCodeError('feature_disabled', { feature => 'jsonrpc' });
+ }
+}
+use Bugzilla::WebService::Server::JSONRPC;
+
+Bugzilla->usage_mode(USAGE_MODE_JSON);
+
+local @INC = (bz_locations()->{extensionsdir}, @INC);
+my $server = new Bugzilla::WebService::Server::JSONRPC;
+$server->dispatch(WS_DISPATCH)->handle();
diff --git a/Websites/bugs.webkit.org/long_list.cgi b/Websites/bugs.webkit.org/long_list.cgi
deleted file mode 100755
index ae77cce..0000000
--- a/Websites/bugs.webkit.org/long_list.cgi
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env perl -wT
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Bug Tracking System.
-#
-# The Initial Developer of the Original Code is Netscape Communications
-# Corporation. Portions created by Netscape are
-# Copyright (C) 1998 Netscape Communications Corporation. All
-# Rights Reserved.
-#
-# Contributor(s): Terry Weissman <terry@mozilla.org>
-# Gervase Markham <gerv@gerv.net>
-
-use strict;
-use lib qw(. lib);
-use Bugzilla;
-
-my $cgi = Bugzilla->cgi;
-
-# Convert comma/space separated elements into separate params
-my $buglist = $cgi->param('buglist') || $cgi->param('bug_id') || $cgi->param('id');
-my @ids = split (/[\s,]+/, $buglist);
-
-my $ids = join('', map { $_ = "&id=" . $_ } @ids);
-
-print $cgi->redirect("show_bug.cgi?format=multiple$ids");
diff --git a/Websites/bugs.webkit.org/migrate.pl b/Websites/bugs.webkit.org/migrate.pl
new file mode 100755
index 0000000..f074541
--- /dev/null
+++ b/Websites/bugs.webkit.org/migrate.pl
@@ -0,0 +1,110 @@
+#!/usr/bin/env perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is The Bugzilla Migration Tool.
+#
+# The Initial Developer of the Original Code is Lambda Research
+# Corporation. Portions created by the Initial Developer are Copyright
+# (C) 2009 the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use File::Basename;
+BEGIN { chdir dirname($0); }
+use lib qw(. lib);
+use Bugzilla;
+use Bugzilla::Migrate;
+
+use Getopt::Long;
+use Pod::Usage;
+
+my %switch;
+GetOptions(\%switch, 'help|h|?', 'from=s', 'verbose|v+', 'dry-run');
+
+# Print the help message if that switch was selected or if --from
+# wasn't specified.
+if (!$switch{'from'} or $switch{'help'}) {
+ pod2usage({-exitval => 1});
+}
+
+my $migrator = Bugzilla::Migrate->load($switch{'from'});
+$migrator->verbose($switch{'verbose'});
+$migrator->dry_run($switch{'dry-run'});
+$migrator->check_requirements();
+$migrator->do_migration();
+
+# Even if there's an error, we want to be sure that the serial values
+# get reset properly.
+END {
+ if ($migrator and $migrator->dry_run) {
+ my $dbh = Bugzilla->dbh;
+ if ($dbh->bz_in_transaction) {
+ $dbh->bz_rollback_transaction();
+ }
+ $migrator->reset_serial_values();
+ }
+}
+
+__END__
+
+=head1 NAME
+
+migrate.pl - A script to migrate from other bug-trackers to Bugzilla.
+
+=head1 SYNOPSIS
+
+ ./migrate.pl --from=<tracker> [--verbose] [--dry-run]
+
+ Migrates from another bug-tracker to Bugzilla. If you want
+ to upgrade Bugzilla, use checksetup.pl instead.
+
+ Always test this on a backup copy of your database before
+ running it on your live Bugzilla.
+
+=head1 OPTIONS
+
+=over
+
+=item B<--from=tracker>
+
+Specifies what bug-tracker you're migrating from. To see what values
+are valid, see the contents of the F<Bugzilla/Migrate/> directory.
+
+=item B<--dry-run>
+
+Don't modify the Bugzilla database at all, just test the import.
+Note that this could cause significant slowdown and other strange effects
+on a live Bugzilla, so only use it on a test instance.
+
+=item B<--verbose>
+
+If specified, this script will output extra debugging information
+to STDERR. Specify multiple times (up to three) for more information.
+
+=back
+
+=head1 DESCRIPTION
+
+This script copies data from another bug-tracker into Bugzilla. It migrates
+users, products, and bugs from the other bug-tracker into this Bugzilla,
+without removing any of the data currently in this Bugzilla.
+
+Note that you will need enough space in your temporary directory to hold
+the size of all attachments in your current bug-tracker.
+
+You may also need to increase the number of file handles a process is allowed
+to hold open (as the migrator will create a file handle for each attachment
+in your database). On Linux and simliar systems, you can do this as root
+by typing C<ulimit -n 65535> before running your script.
diff --git a/Websites/bugs.webkit.org/mod_perl.pl b/Websites/bugs.webkit.org/mod_perl.pl
index 3faff85..e0ab9a4 100644
--- a/Websites/bugs.webkit.org/mod_perl.pl
+++ b/Websites/bugs.webkit.org/mod_perl.pl
@@ -15,44 +15,57 @@
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
-use lib qw(/var/www/html);
+use lib qw(/var/www/html); # WEBKIT_CHANGES
package Bugzilla::ModPerl;
-
use strict;
+use warnings;
+
+# This sets up our libpath without having to specify it in the mod_perl
+# configuration.
+use File::Basename;
+use lib dirname(__FILE__);
+use Bugzilla::Constants ();
+use lib Bugzilla::Constants::bz_locations()->{'ext_libpath'};
# If you have an Apache2::Status handler in your Apache configuration,
-# you need to load Apache2::Status *here*, so that Apache::DBI can
-# report information to Apache2::Status.
+# you need to load Apache2::Status *here*, so that any later-loaded modules
+# can report information to Apache2::Status.
#use Apache2::Status ();
# We don't want to import anything into the global scope during
# startup, so we always specify () after using any module in this
# file.
+use Apache2::Log ();
use Apache2::ServerUtil;
-use Apache2::SizeLimit;
use ModPerl::RegistryLoader ();
-use CGI ();
-CGI->compile(qw(:cgi -no_xhtml -oldstyle_urls :private_tempfiles
- :unique_headers SERVER_PUSH :push));
-use Template::Config ();
-Template::Config->preload();
+use File::Basename ();
+# This loads most of our modules.
use Bugzilla ();
-use Bugzilla::Constants ();
+# Loading Bugzilla.pm doesn't load this, though, and we want it preloaded.
+use Bugzilla::BugMail ();
use Bugzilla::CGI ();
-use Bugzilla::Mailer ();
-use Bugzilla::Template ();
+use Bugzilla::Extension ();
+use Bugzilla::Install::Requirements ();
use Bugzilla::Util ();
+use Bugzilla::RNG ();
-# For PerlChildInitHandler
-eval { require Math::Random::Secure };
+# Make warnings go to the virtual host's log and not the main
+# server log.
+BEGIN { *CORE::GLOBAL::warn = \&Apache2::ServerRec::warn; }
-#if WEBKIT_CHANGES
+# Pre-compile the CGI.pm methods that we're going to use.
+Bugzilla::CGI->compile(qw(:cgi :push));
+
+use Apache2::SizeLimit;
# This means that every httpd child will die after processing
-# a CGI if it is taking up more than 700MB of RAM all by itself.
-# our children are normally about 400MB
-$Apache2::SizeLimit::MAX_UNSHARED_SIZE = 700000;
+# a CGI if it is taking up more than 45MB of RAM all by itself,
+# not counting RAM it is sharing with the other httpd processes.
+#if WEBKIT_CHANGES
+# bugs.webkit.org children are normally about 400MB.
+#Apache2::SizeLimit->set_max_unshared_size(45_000);
+Apache2::SizeLimit->set_max_unshared_size(700_000);
#endif // WEBKIT_CHANGES
my $cgi_path = Bugzilla::Constants::bz_locations()->{'cgi_path'};
@@ -61,41 +74,51 @@
my $server = Apache2::ServerUtil->server;
my $conf = <<EOT;
# Make sure each httpd child receives a different random seed (bug 476622).
-# Math::Random::Secure has one srand that needs to be called for
+# Bugzilla::RNG has one srand that needs to be called for
# every process, and Perl has another. (Various Perl modules still use
-# the built-in rand(), even though we only use Math::Random::Secure in
-# Bugzilla itself, so we need to srand() both of them.) However,
-# Math::Random::Secure may not be installed, so we call its srand in an
-# eval.
-PerlChildInitHandler "sub { eval { Math::Random::Secure::srand() }; srand(); }"
+# the built-in rand(), even though we never use it in Bugzilla itself,
+# so we need to srand() both of them.)
+PerlChildInitHandler "sub { Bugzilla::RNG::srand(); srand(); }"
<Directory "$cgi_path">
AddHandler perl-script .cgi
# No need to PerlModule these because they're already defined in mod_perl.pl
PerlResponseHandler Bugzilla::ModPerl::ResponseHandler
- PerlCleanupHandler Bugzilla::ModPerl::CleanupHandler
- PerlCleanupHandler Apache2::SizeLimit
+ PerlCleanupHandler Apache2::SizeLimit Bugzilla::ModPerl::CleanupHandler
PerlOptions +ParseHeaders
Options +ExecCGI
- AllowOverride Limit
+ AllowOverride Limit FileInfo Indexes
DirectoryIndex index.cgi index.html
</Directory>
EOT
$server->add_config([split("\n", $conf)]);
+# Pre-load all extensions
+$Bugzilla::extension_packages = Bugzilla::Extension->load_all();
+
# Have ModPerl::RegistryLoader pre-compile all CGI scripts.
my $rl = new ModPerl::RegistryLoader();
# If we try to do this in "new" it fails because it looks for a
# Bugzilla/ModPerl/ResponseHandler.pm
$rl->{package} = 'Bugzilla::ModPerl::ResponseHandler';
-# Note that $cgi_path will be wrong if somebody puts the libraries
-# in a different place than the CGIs.
+my $feature_files = Bugzilla::Install::Requirements::map_files_to_features();
+
+# Prevent "use lib" from doing anything when the .cgi files are compiled.
+# This is important to prevent the current directory from getting into
+# @INC and messing things up. (See bug 630750.)
+no warnings 'redefine';
+local *lib::import = sub {};
+use warnings;
+
foreach my $file (glob "$cgi_path/*.cgi") {
+ my $base_filename = File::Basename::basename($file);
+ if (my $feature = $feature_files->{$base_filename}) {
+ next if !Bugzilla->feature($feature);
+ }
Bugzilla::Util::trick_taint($file);
$rl->handler($file, $file);
}
-
package Bugzilla::ModPerl::ResponseHandler;
use strict;
use base qw(ModPerl::Registry);
@@ -108,6 +131,14 @@
# here explicitly or init_page's shutdownhtml code won't work right.
$0 = $ENV{'SCRIPT_FILENAME'};
+ # Prevent "use lib" from modifying @INC in the case where a .cgi file
+ # is being automatically recompiled by mod_perl when Apache is
+ # running. (This happens if a file changes while Apache is already
+ # running.)
+ no warnings 'redefine';
+ local *lib::import = sub {};
+ use warnings;
+
Bugzilla::init_page();
return $class->SUPER::handler(@_);
}
diff --git a/Websites/bugs.webkit.org/page.cgi b/Websites/bugs.webkit.org/page.cgi
index c419a0c..27a645e 100755
--- a/Websites/bugs.webkit.org/page.cgi
+++ b/Websites/bugs.webkit.org/page.cgi
@@ -34,6 +34,30 @@
use Bugzilla;
use Bugzilla::Error;
+use Bugzilla::Hook;
+use Bugzilla::Search::Quicksearch;
+
+###############
+# Subroutines #
+###############
+
+# For quicksearch.html.
+sub quicksearch_field_names {
+ my $fields = Bugzilla::Search::Quicksearch->FIELD_MAP;
+ my %fields_reverse;
+ # Put longer names before shorter names.
+ my @nicknames = sort { length($b) <=> length($a) } (keys %$fields);
+ foreach my $nickname (@nicknames) {
+ my $db_field = $fields->{$nickname};
+ $fields_reverse{$db_field} ||= [];
+ push(@{ $fields_reverse{$db_field} }, $nickname);
+ }
+ return \%fields_reverse;
+}
+
+###############
+# Main Script #
+###############
Bugzilla->login();
@@ -42,21 +66,31 @@
my $id = $cgi->param('id');
if ($id) {
- # Remove all dodgy chars, and split into name and ctype.
- $id =~ s/[^\w\-\.]//g;
- $id =~ /(.*)\.(.*)/;
+ # Be careful not to allow directory traversal.
+ if ($id =~ /\.\./) {
+ # two dots in a row is bad
+ ThrowCodeError("bad_page_cgi_id", { "page_id" => $id });
+ }
+ # Split into name and ctype.
+ $id =~ /^([\w\-\/\.]+)\.(\w+)$/;
if (!$2) {
# if this regexp fails to match completely, something bad came in
ThrowCodeError("bad_page_cgi_id", { "page_id" => $id });
}
+ my %vars = (
+ quicksearch_field_names => \&quicksearch_field_names,
+ );
+ Bugzilla::Hook::process('page_before_template',
+ { page_id => $id, vars => \%vars });
+
my $format = $template->get_format("pages/$1", undef, $2);
$cgi->param('id', $id);
print $cgi->header($format->{'ctype'});
- $template->process("$format->{'template'}")
+ $template->process("$format->{'template'}", \%vars)
|| ThrowTemplateError($template->error());
}
else {
diff --git a/Websites/bugs.webkit.org/post_bug.cgi b/Websites/bugs.webkit.org/post_bug.cgi
index 2920df3..8df79d2 100755
--- a/Websites/bugs.webkit.org/post_bug.cgi
+++ b/Websites/bugs.webkit.org/post_bug.cgi
@@ -55,42 +55,20 @@
######################################################################
# redirect to enter_bug if no field is passed.
-print $cgi->redirect(correct_urlbase() . 'enter_bug.cgi') unless $cgi->param();
+unless ($cgi->param()) {
+ print $cgi->redirect(correct_urlbase() . 'enter_bug.cgi');
+ exit;
+}
# Detect if the user already used the same form to submit a bug
my $token = trim($cgi->param('token'));
-if ($token) {
- my ($creator_id, $date, $old_bug_id) = Bugzilla::Token::GetTokenData($token);
- unless ($creator_id
- && ($creator_id == $user->id)
- && ($old_bug_id =~ "^createbug:"))
- {
- # The token is invalid.
- ThrowUserError('token_does_not_exist');
- }
-
- $old_bug_id =~ s/^createbug://;
-
- if ($old_bug_id && (!$cgi->param('ignore_token')
- || ($cgi->param('ignore_token') != $old_bug_id)))
- {
- $vars->{'bugid'} = $old_bug_id;
- $vars->{'allow_override'} = defined $cgi->param('ignore_token') ? 0 : 1;
-
- print $cgi->header();
- $template->process("bug/create/confirm-create-dupe.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
-}
+check_token_data($token, 'create_bug', 'index.cgi');
# do a match on the fields if applicable
-
-&Bugzilla::User::match_field ($cgi, {
+Bugzilla::User::match_field ({
'cc' => { 'type' => 'multi' },
'assigned_to' => { 'type' => 'single' },
'qa_contact' => { 'type' => 'single' },
- '^requestee_type-(\d+)$' => { 'type' => 'multi' },
});
if (defined $cgi->param('maketemplate')) {
@@ -105,16 +83,6 @@
umask 0;
-# get current time
-my $timestamp = $dbh->selectrow_array(q{SELECT NOW()});
-
-# Group Validation
-my @selected_groups;
-foreach my $group (grep(/^bit-\d+$/, $cgi->param())) {
- $group =~ /^bit-(\d+)$/;
- push(@selected_groups, $1);
-}
-
# The format of the initial comment can be structured by adding fields to the
# enter_bug template and then referencing them in the comment template.
my $comment;
@@ -141,7 +109,7 @@
alias
blocked
- commentprivacy
+ comment_is_private
bug_file_loc
bug_severity
bug_status
@@ -162,22 +130,30 @@
foreach my $field (@bug_fields) {
$bug_params{$field} = $cgi->param($field);
}
-$bug_params{'creation_ts'} = $timestamp;
-$bug_params{'cc'} = [$cgi->param('cc')];
-$bug_params{'groups'} = \@selected_groups;
-$bug_params{'comment'} = $comment;
+foreach my $field (qw(cc groups)) {
+ next if !$cgi->should_set($field);
+ $bug_params{$field} = [$cgi->param($field)];
+}
+$bug_params{'comment'} = $comment;
my @multi_selects = grep {$_->type == FIELD_TYPE_MULTI_SELECT && $_->enter_bug}
Bugzilla->active_custom_fields;
foreach my $field (@multi_selects) {
+ next if !$cgi->should_set($field->name);
$bug_params{$field->name} = [$cgi->param($field->name)];
}
my $bug = Bugzilla::Bug->create(\%bug_params);
-# Get the bug ID back.
+# Get the bug ID back and delete the token used to create this bug.
my $id = $bug->bug_id;
+delete_token($token);
+
+# We do this directly from the DB because $bug->creation_ts has the seconds
+# formatted out of it (which should be fixed some day).
+my $timestamp = $dbh->selectrow_array(
+ 'SELECT creation_ts FROM bugs WHERE bug_id = ?', undef, $id);
# Set Version cookie, but only if the user actually selected
# a version on the page.
@@ -192,93 +168,78 @@
# after the bug is filed.
# Add an attachment if requested.
-if (defined($cgi->upload('data')) || $cgi->param('attachurl')) {
- $cgi->param('isprivate', $cgi->param('commentprivacy'));
- my $attachment = Bugzilla::Attachment->insert_attachment_for_bug(!THROW_ERROR,
- $bug, $user, $timestamp, $vars);
+if (defined($cgi->upload('data')) || $cgi->param('attach_text')) {
+ $cgi->param('isprivate', $cgi->param('comment_is_private'));
+
+ # Must be called before create() as it may alter $cgi->param('ispatch').
+ my $content_type = Bugzilla::Attachment::get_content_type();
+ my $attachment;
+
+ # If the attachment cannot be successfully added to the bug,
+ # we notify the user, but we don't interrupt the bug creation process.
+ my $error_mode_cache = Bugzilla->error_mode;
+ Bugzilla->error_mode(ERROR_MODE_DIE);
+ eval {
+ $attachment = Bugzilla::Attachment->create(
+ {bug => $bug,
+ creation_ts => $timestamp,
+ data => scalar $cgi->param('attach_text') || $cgi->upload('data'),
+ description => scalar $cgi->param('description'),
+ filename => $cgi->param('attach_text') ? "file_$id.txt" : scalar $cgi->upload('data'),
+ ispatch => scalar $cgi->param('ispatch'),
+ isprivate => scalar $cgi->param('isprivate'),
+ mimetype => $content_type,
+ });
+ };
+ Bugzilla->error_mode($error_mode_cache);
if ($attachment) {
- # Update the comment to include the new attachment ID.
- # This string is hardcoded here because Template::quoteUrls()
- # expects to find this exact string.
- my $new_comment = "Created an attachment (id=" . $attachment->id . ")\n" .
- $attachment->description . "\n";
- # We can use $bug->longdescs here because we are sure that the bug
- # description is of type CMT_NORMAL. No need to include it if it's
- # empty, though.
- if ($bug->longdescs->[0]->{'body'} !~ /^\s+$/) {
- $new_comment .= "\n" . $bug->longdescs->[0]->{'body'};
- }
- $bug->update_comment($bug->longdescs->[0]->{'id'}, $new_comment);
+ # Set attachment flags.
+ my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi(
+ $bug, $attachment, $vars, SKIP_REQUESTEE_ON_ERROR);
+ $attachment->set_flags($flags, $new_flags);
+ $attachment->update($timestamp);
+ my $comment = $bug->comments->[0];
+ $comment->set_all({ type => CMT_ATTACHMENT_CREATED,
+ extra_data => $attachment->id });
+ $comment->update();
}
else {
$vars->{'message'} = 'attachment_creation_failed';
}
-
- # Determine if Patch Viewer is installed, for Diff link
- eval {
- require PatchReader;
- $vars->{'patchviewerinstalled'} = 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.
-my $error_mode_cache = Bugzilla->error_mode;
-Bugzilla->error_mode(ERROR_MODE_DIE);
-eval {
- Bugzilla::Flag::validate($id, undef, SKIP_REQUESTEE_ON_ERROR);
- Bugzilla::Flag->process($bug, undef, $timestamp, $vars);
-};
-Bugzilla->error_mode($error_mode_cache);
-if ($@) {
- $vars->{'message'} = 'flag_creation_failed';
- $vars->{'flag_creation_error'} = $@;
-}
-
-# Email everyone the details of the new bug
-$vars->{'mailrecipients'} = {'changer' => $user->login};
+# Set bug flags.
+my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi($bug, undef, $vars,
+ SKIP_REQUESTEE_ON_ERROR);
+$bug->set_flags($flags, $new_flags);
+$bug->update($timestamp);
$vars->{'id'} = $id;
$vars->{'bug'} = $bug;
-Bugzilla::Hook::process("post_bug-after_creation", { vars => $vars });
+Bugzilla::Hook::process('post_bug_after_creation', { vars => $vars });
ThrowCodeError("bug_error", { bug => $bug }) if $bug->error;
-$vars->{'sentmail'} = [];
-
-push (@{$vars->{'sentmail'}}, { type => 'created',
- id => $id,
- });
-
-foreach my $i (@{$bug->dependson || []}, @{$bug->blocked || []}) {
- push (@{$vars->{'sentmail'}}, { type => 'dep', id => $i, });
+my $recipients = { changer => $user };
+my $bug_sent = Bugzilla::BugMail::Send($id, $recipients);
+$bug_sent->{type} = 'created';
+$bug_sent->{id} = $id;
+my @all_mail_results = ($bug_sent);
+foreach my $dep (@{$bug->dependson || []}, @{$bug->blocked || []}) {
+ my $dep_sent = Bugzilla::BugMail::Send($dep, $recipients);
+ $dep_sent->{type} = 'dep';
+ $dep_sent->{id} = $dep;
+ push(@all_mail_results, $dep_sent);
}
+$vars->{sentmail} = \@all_mail_results;
-my @bug_list;
-if ($cgi->cookie("BUGLIST")) {
- @bug_list = split(/:/, $cgi->cookie("BUGLIST"));
-}
-$vars->{'bug_list'} = \@bug_list;
-$vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
-
-if ($token) {
- trick_taint($token);
- $dbh->do('UPDATE tokens SET eventdata = ? WHERE token = ?', undef,
- ("createbug:$id", $token));
-}
-
-if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
- Bugzilla::BugMail::Send($id, $vars->{'mailrecipients'});
-}
-else {
- print $cgi->header();
- $template->process("bug/create/created.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
-}
+$format = $template->get_format("bug/create/created",
+ scalar($cgi->param('created-format')),
+ "html");
+print $cgi->header();
+$template->process($format->{'template'}, $vars)
+ || ThrowTemplateError($template->error());
1;
diff --git a/Websites/bugs.webkit.org/process_bug.cgi b/Websites/bugs.webkit.org/process_bug.cgi
index c62854a..fe3425e 100755
--- a/Websites/bugs.webkit.org/process_bug.cgi
+++ b/Websites/bugs.webkit.org/process_bug.cgi
@@ -48,8 +48,6 @@
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Bug;
-use Bugzilla::BugMail;
-use Bugzilla::Mailer;
use Bugzilla::User;
use Bugzilla::Util;
use Bugzilla::Error;
@@ -61,6 +59,7 @@
use Bugzilla::Status;
use Bugzilla::Token;
+use List::MoreUtils qw(firstidx);
use Storable qw(dclone);
my $user = Bugzilla->login(LOGIN_REQUIRED);
@@ -69,26 +68,11 @@
my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
my $vars = {};
-$vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
######################################################################
# Subroutines
######################################################################
-# Used to send email when an update is done.
-sub send_results {
- my ($bug_id, $vars) = @_;
- my $template = Bugzilla->template;
- if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
- Bugzilla::BugMail::Send($bug_id, $vars->{'mailrecipients'});
- }
- else {
- $template->process("bug/process/results.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- }
- $vars->{'header_done'} = 1;
-}
-
# Tells us whether or not a field should be changed by process_bug.
sub should_set {
# check_defined is used for fields where there's another field
@@ -112,23 +96,16 @@
# Create a list of objects for all bugs being modified in this request.
my @bug_objects;
if (defined $cgi->param('id')) {
- my $id = $cgi->param('id');
- ValidateBugID($id);
-
- # Store the validated, and detainted id back in the cgi data, as
- # lots of later code will need it, and will obtain it from there
- $cgi->param('id', $id);
- push(@bug_objects, new Bugzilla::Bug($id));
+ my $bug = Bugzilla::Bug->check(scalar $cgi->param('id'));
+ $cgi->param('id', $bug->id);
+ push(@bug_objects, $bug);
} else {
- my @ids;
foreach my $i ($cgi->param()) {
if ($i =~ /^id_([1-9][0-9]*)/) {
my $id = $1;
- ValidateBugID($id);
- push(@ids, $id);
+ push(@bug_objects, Bugzilla::Bug->check($id));
}
}
- @bug_objects = @{Bugzilla::Bug->new_from_list(\@ids)};
}
# Make sure there are bugs to process.
@@ -150,51 +127,44 @@
}
# do a match on the fields if applicable
-
-# 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.
-&Bugzilla::User::match_field($cgi, {
+Bugzilla::User::match_field({
'qa_contact' => { 'type' => 'single' },
'newcc' => { 'type' => 'multi' },
'masscc' => { 'type' => 'multi' },
'assigned_to' => { 'type' => 'single' },
- '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
});
-# Validate flags in all cases. validate() should not detect any
-# reference to flags if $cgi->param('id') is undefined.
-Bugzilla::Flag::validate($cgi->param('id'));
-
print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_EMAIL;
# Check for a mid-air collision. Currently this only works when updating
# an individual bug.
-if (defined $cgi->param('delta_ts')
- && $cgi->param('delta_ts') ne $first_bug->delta_ts)
+if (defined $cgi->param('delta_ts'))
{
- ($vars->{'operations'}) =
- Bugzilla::Bug::GetBugActivity($first_bug->id, undef,
- scalar $cgi->param('delta_ts'));
+ my $delta_ts_z = datetime_from($cgi->param('delta_ts'));
+ my $first_delta_tz_z = datetime_from($first_bug->delta_ts);
+ if ($first_delta_tz_z ne $delta_ts_z) {
+ ($vars->{'operations'}) =
+ Bugzilla::Bug::GetBugActivity($first_bug->id, undef,
+ scalar $cgi->param('delta_ts'));
- $vars->{'title_tag'} = "mid_air";
+ $vars->{'title_tag'} = "mid_air";
- ThrowCodeError('undefined_field', { field => 'longdesclength' })
- if !defined $cgi->param('longdesclength');
+ ThrowCodeError('undefined_field', { field => 'longdesclength' })
+ if !defined $cgi->param('longdesclength');
- $vars->{'start_at'} = $cgi->param('longdesclength');
- # Always sort midair collision comments oldest to newest,
- # regardless of the user's personal preference.
- $vars->{'comments'} = Bugzilla::Bug::GetComments($first_bug->id,
- "oldest_to_newest");
- $vars->{'bug'} = $first_bug;
- # The token contains the old delta_ts. We need a new one.
- $cgi->param('token', issue_hash_token([$first_bug->id, $first_bug->delta_ts]));
-
- # Warn the user about the mid-air collision and ask them what to do.
- $template->process("bug/process/midair.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ $vars->{'start_at'} = $cgi->param('longdesclength');
+ # Always sort midair collision comments oldest to newest,
+ # regardless of the user's personal preference.
+ $vars->{'comments'} = $first_bug->comments({ order => "oldest_to_newest" });
+ $vars->{'bug'} = $first_bug;
+
+ # The token contains the old delta_ts. We need a new one.
+ $cgi->param('token', issue_hash_token([$first_bug->id, $first_bug->delta_ts]));
+ # Warn the user about the mid-air collision and ask them what to do.
+ $template->process("bug/process/midair.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
}
# We couldn't do this check earlier as we first had to validate bug IDs
@@ -215,30 +185,27 @@
$vars->{'title_tag'} = "bug_processed";
-# Set up the vars for navigational <link> elements
-my @bug_list;
-if ($cgi->cookie("BUGLIST")) {
- @bug_list = split(/:/, $cgi->cookie("BUGLIST"));
- $vars->{'bug_list'} = \@bug_list;
-}
-
-my ($action, $next_bug);
+my $action;
if (defined $cgi->param('id')) {
- $action = Bugzilla->user->settings->{'post_bug_submit_action'}->{'value'};
+ $action = $user->setting('post_bug_submit_action');
if ($action eq 'next_bug') {
- my $cur = lsearch(\@bug_list, $cgi->param('id'));
+ my $bug_list_obj = $user->recent_search_for($first_bug);
+ my @bug_list = $bug_list_obj ? @{$bug_list_obj->bug_list} : ();
+ my $cur = firstidx { $_ eq $cgi->param('id') } @bug_list;
if ($cur >= 0 && $cur < $#bug_list) {
- $next_bug = $bug_list[$cur + 1];
- # No need to check whether the user can see the bug or not.
- # All we want is its ID. An error will be thrown later
- # if the user cannot see it.
- $vars->{'bug'} = {bug_id => $next_bug};
+ my $next_bug_id = $bug_list[$cur + 1];
+ detaint_natural($next_bug_id);
+ if ($next_bug_id and $user->can_see_bug($next_bug_id)) {
+ # We create an object here so that $bug->send_changes can use it
+ # when displaying the header.
+ $vars->{'bug'} = new Bugzilla::Bug($next_bug_id);
+ }
}
}
# Include both action = 'same_bug' and 'nothing'.
else {
- $vars->{'bug'} = {bug_id => $cgi->param('id')};
+ $vars->{'bug'} = $first_bug;
}
}
else {
@@ -255,418 +222,192 @@
}
}
-# For security purposes, and because lots of other checks depend on it,
-# we set the product first before anything else.
-my $product_change; # Used only for strict_isolation checks, right now.
-if (should_set('product')) {
- foreach my $b (@bug_objects) {
- my $changed = $b->set_product(scalar $cgi->param('product'),
- { component => scalar $cgi->param('component'),
- version => scalar $cgi->param('version'),
- target_milestone => scalar $cgi->param('target_milestone'),
- change_confirmed => scalar $cgi->param('confirm_product_change'),
- other_bugs => \@bug_objects,
- });
- $product_change ||= $changed;
- }
-}
-
-# strict_isolation checks mean that we should set the groups
-# immediately after changing the product.
-foreach my $b (@bug_objects) {
- foreach my $group (@{$b->product_obj->groups_valid}) {
- my $gid = $group->id;
- if (should_set("bit-$gid", 1)) {
- # Check ! first to avoid having to check defined below.
- if (!$cgi->param("bit-$gid")) {
- $b->remove_group($gid);
- }
- # "== 1" is important because mass-change uses -1 to mean
- # "don't change this restriction"
- elsif ($cgi->param("bit-$gid") == 1) {
- $b->add_group($gid);
- }
- }
- }
-}
-
-if ($cgi->param('id') && (defined $cgi->param('dependson')
- || defined $cgi->param('blocked')) )
-{
- $first_bug->set_dependencies(scalar $cgi->param('dependson'),
- scalar $cgi->param('blocked'));
-}
-# Right now, you can't modify dependencies on a mass change.
-else {
- $cgi->delete('dependson');
- $cgi->delete('blocked');
-}
-
-my $any_keyword_changes;
-if (defined $cgi->param('keywords')) {
- foreach my $b (@bug_objects) {
- my $return =
- $b->modify_keywords(scalar $cgi->param('keywords'),
- scalar $cgi->param('keywordaction'));
- $any_keyword_changes ||= $return;
- }
-}
-
# Component, target_milestone, and version are in here just in case
# the 'product' field wasn't defined in the CGI. It doesn't hurt to set
# them twice.
my @set_fields = qw(op_sys rep_platform priority bug_severity
component target_milestone version
bug_file_loc status_whiteboard short_desc
- deadline remaining_time estimated_time);
+ deadline remaining_time estimated_time
+ work_time set_default_assignee set_default_qa_contact
+ cclist_accessible reporter_accessible
+ product confirm_product_change
+ bug_status resolution dup_id);
push(@set_fields, 'assigned_to') if !$cgi->param('set_default_assignee');
push(@set_fields, 'qa_contact') if !$cgi->param('set_default_qa_contact');
-my @custom_fields = Bugzilla->active_custom_fields;
-
-my %methods = (
- bug_severity => 'set_severity',
- rep_platform => 'set_platform',
- short_desc => 'set_summary',
- bug_file_loc => 'set_url',
+my %field_translation = (
+ bug_severity => 'severity',
+ rep_platform => 'platform',
+ short_desc => 'summary',
+ bug_file_loc => 'url',
+ set_default_assignee => 'reset_assigned_to',
+ set_default_qa_contact => 'reset_qa_contact',
+ confirm_product_change => 'product_change_confirmed',
);
-foreach my $b (@bug_objects) {
- if (should_set('comment') || $cgi->param('work_time')) {
- # Add a comment as needed to each bug. This is done early because
- # there are lots of things that want to check if we added a comment.
- $b->add_comment(scalar($cgi->param('comment')),
- { isprivate => scalar $cgi->param('commentprivacy'),
- work_time => scalar $cgi->param('work_time') });
- }
- foreach my $field_name (@set_fields) {
- if (should_set($field_name)) {
- my $method = $methods{$field_name};
- $method ||= "set_" . $field_name;
- $b->$method($cgi->param($field_name));
- }
- }
- $b->reset_assigned_to if $cgi->param('set_default_assignee');
- $b->reset_qa_contact if $cgi->param('set_default_qa_contact');
- # And set custom fields.
- foreach my $field (@custom_fields) {
- my $fname = $field->name;
- if (should_set($fname, 1)) {
- $b->set_custom_field($field, [$cgi->param($fname)]);
- }
+my %set_all_fields = ( other_bugs => \@bug_objects );
+foreach my $field_name (@set_fields) {
+ if (should_set($field_name, 1)) {
+ my $param_name = $field_translation{$field_name} || $field_name;
+ $set_all_fields{$param_name} = $cgi->param($field_name);
}
}
-# Certain changes can only happen on individual bugs, never on mass-changes.
+if (should_set('keywords')) {
+ my $action = $cgi->param('keywordaction') || '';
+ # Backward-compatibility for Bugzilla 3.x and older.
+ $action = 'remove' if $action eq 'delete';
+ $action = 'set' if $action eq 'makeexact';
+ $set_all_fields{keywords}->{$action} = $cgi->param('keywords');
+}
+if (should_set('comment')) {
+ $set_all_fields{comment} = {
+ body => scalar $cgi->param('comment'),
+ is_private => scalar $cgi->param('comment_is_private'),
+ };
+}
+if (should_set('see_also')) {
+ $set_all_fields{'see_also'}->{add} =
+ [split(/[\s,]+/, $cgi->param('see_also'))];
+}
+if (should_set('remove_see_also')) {
+ $set_all_fields{'see_also'}->{remove} = [$cgi->param('remove_see_also')];
+}
+foreach my $dep_field (qw(dependson blocked)) {
+ if (should_set($dep_field)) {
+ if (my $dep_action = $cgi->param("${dep_field}_action")) {
+ $set_all_fields{$dep_field}->{$dep_action} =
+ [split(/\s,/, $cgi->param($dep_field))];
+ }
+ else {
+ $set_all_fields{$dep_field}->{set} = $cgi->param($dep_field);
+ }
+ }
+}
+# Formulate the CC data into two arrays of users involved in this CC change.
+if (defined $cgi->param('newcc')
+ or defined $cgi->param('addselfcc')
+ or defined $cgi->param('removecc')
+ or defined $cgi->param('masscc'))
+{
+ my (@cc_add, @cc_remove);
+ # If masscc is defined, then we came from buglist and need to either add or
+ # remove cc's... otherwise, we came from show_bug and may need to do both.
+ if (defined $cgi->param('masscc')) {
+ if ($cgi->param('ccaction') eq 'add') {
+ @cc_add = $cgi->param('masscc');
+ } elsif ($cgi->param('ccaction') eq 'remove') {
+ @cc_remove = $cgi->param('masscc');
+ }
+ } else {
+ @cc_add = $cgi->param('newcc');
+ push(@cc_add, Bugzilla->user) if $cgi->param('addselfcc');
+
+ # We came from show_bug which uses a select box to determine what cc's
+ # need to be removed...
+ if ($cgi->param('removecc') && $cgi->param('cc')) {
+ @cc_remove = $cgi->param('cc');
+ }
+ }
+
+ $set_all_fields{cc} = { add => \@cc_add, remove => \@cc_remove };
+}
+
+# Fields that can only be set on one bug at a time.
if (defined $cgi->param('id')) {
# Since aliases are unique (like bug numbers), they can only be changed
# for one bug at a time.
if (Bugzilla->params->{"usebugaliases"} && defined $cgi->param('alias')) {
- $first_bug->set_alias($cgi->param('alias'));
- }
-
- # reporter_accessible and cclist_accessible--these are only set if
- # the user can change them and they appear on the page.
- if (should_set('cclist_accessible', 1)) {
- $first_bug->set_cclist_accessible($cgi->param('cclist_accessible'))
- }
- if (should_set('reporter_accessible', 1)) {
- $first_bug->set_reporter_accessible($cgi->param('reporter_accessible'))
- }
-
- # You can only mark/unmark comments as private on single bugs. If
- # you're not in the insider group, this code won't do anything.
- foreach my $field (grep(/^defined_isprivate/, $cgi->param())) {
- $field =~ /(\d+)$/;
- my $comment_id = $1;
- $first_bug->set_comment_is_private($comment_id,
- $cgi->param("isprivate_$comment_id"));
+ $set_all_fields{alias} = $cgi->param('alias');
}
}
-# We need to check the addresses involved in a CC change before we touch
-# any bugs. What we'll do here is formulate the CC data into two arrays of
-# users involved in this CC change. Then those arrays can be used later
-# on for the actual change.
-my (@cc_add, @cc_remove);
-if (defined $cgi->param('newcc')
- || defined $cgi->param('addselfcc')
- || defined $cgi->param('removecc')
- || defined $cgi->param('masscc')) {
-
- # If masscc is defined, then we came from buglist and need to either add or
- # remove cc's... otherwise, we came from bugform and may need to do both.
- my ($cc_add, $cc_remove) = "";
- if (defined $cgi->param('masscc')) {
- if ($cgi->param('ccaction') eq 'add') {
- $cc_add = join(' ',$cgi->param('masscc'));
- } elsif ($cgi->param('ccaction') eq 'remove') {
- $cc_remove = join(' ',$cgi->param('masscc'));
- }
- } else {
- $cc_add = join(' ',$cgi->param('newcc'));
- # We came from bug_form which uses a select box to determine what cc's
- # need to be removed...
- if (defined $cgi->param('removecc') && $cgi->param('cc')) {
- $cc_remove = join (",", $cgi->param('cc'));
- }
+my %is_private;
+foreach my $field (grep(/^defined_isprivate/, $cgi->param())) {
+ $field =~ /(\d+)$/;
+ my $comment_id = $1;
+ $is_private{$comment_id} = $cgi->param("isprivate_$comment_id");
+}
+$set_all_fields{comment_is_private} = \%is_private;
+
+my @check_groups = $cgi->param('defined_groups');
+my @set_groups = $cgi->param('groups');
+my ($removed_groups) = diff_arrays(\@check_groups, \@set_groups);
+$set_all_fields{groups} = { add => \@set_groups, remove => $removed_groups };
+
+my @custom_fields = Bugzilla->active_custom_fields;
+foreach my $field (@custom_fields) {
+ my $fname = $field->name;
+ if (should_set($fname, 1)) {
+ $set_all_fields{$fname} = [$cgi->param($fname)];
}
-
- push(@cc_add, split(/[\s,]+/, $cc_add)) if $cc_add;
- push(@cc_add, Bugzilla->user) if $cgi->param('addselfcc');
-
- push(@cc_remove, split(/[\s,]+/, $cc_remove)) if $cc_remove;
}
+# We are going to alter the list of removed groups, so we keep a copy here.
+my @unchecked_groups = @$removed_groups;
foreach my $b (@bug_objects) {
- $b->remove_cc($_) foreach @cc_remove;
- $b->add_cc($_) foreach @cc_add;
- # Theoretically you could move a product without ever specifying
- # a new assignee or qa_contact, or adding/removing any CCs. So,
- # we have to check that the current assignee, qa, and CCs are still
- # valid if we've switched products, under strict_isolation. We can only
- # do that here. There ought to be some better way to do this,
- # architecturally, but I haven't come up with it.
- if ($product_change) {
- $b->_check_strict_isolation();
+ # Don't blindly ask to remove unchecked groups available in the UI.
+ # A group can be already unchecked, and the user didn't try to remove it.
+ # In this case, we don't want remove_group() to complain.
+ my @remove_groups;
+ foreach my $g (@{$b->groups_in}) {
+ push(@remove_groups, $g->name) if grep { $_ eq $g->name } @unchecked_groups;
}
+ local $set_all_fields{groups}->{remove} = \@remove_groups;
+ $b->set_all(\%set_all_fields);
}
-my $move_action = $cgi->param('action') || '';
-if ($move_action eq Bugzilla->params->{'move-button-text'}) {
- Bugzilla->params->{'move-enabled'} || ThrowUserError("move_bugs_disabled");
-
- $user->is_mover || ThrowUserError("auth_failure", {action => 'move',
- object => 'bugs'});
-
- $dbh->bz_start_transaction();
-
- # First update all moved bugs.
- foreach my $bug (@bug_objects) {
- $bug->add_comment('', { type => CMT_MOVED_TO, extra_data => $user->login });
- }
- # Don't export the new status and resolution. We want the current ones.
- local $Storable::forgive_me = 1;
- my $bugs = dclone(\@bug_objects);
-
- my $new_status = Bugzilla->params->{'duplicate_or_move_bug_status'};
- foreach my $bug (@bug_objects) {
- $bug->set_status($new_status, {resolution => 'MOVED', moving => 1});
- }
- $_->update() foreach @bug_objects;
- $dbh->bz_commit_transaction();
-
- # Now send emails.
- foreach my $bug (@bug_objects) {
- $vars->{'mailrecipients'} = { 'changer' => $user->login };
- $vars->{'id'} = $bug->id;
- $vars->{'type'} = "move";
- send_results($bug->id, $vars);
- }
- # Prepare and send all data about these bugs to the new database
- my $to = Bugzilla->params->{'move-to-address'};
- $to =~ s/@/\@/;
- my $from = Bugzilla->params->{'moved-from-address'};
- $from =~ s/@/\@/;
- my $msg = "To: $to\n";
- $msg .= "From: Bugzilla <" . $from . ">\n";
- $msg .= "Subject: Moving bug(s) " . join(', ', map($_->id, @bug_objects))
- . "\n\n";
-
- my @fieldlist = (Bugzilla::Bug->fields, 'group', 'long_desc',
- 'attachment', 'attachmentdata');
- my %displayfields;
- foreach (@fieldlist) {
- $displayfields{$_} = 1;
- }
-
- $template->process("bug/show.xml.tmpl", { bugs => $bugs,
- displayfields => \%displayfields,
- }, \$msg)
- || ThrowTemplateError($template->error());
-
- $msg .= "\n";
- MessageToMTA($msg);
-
- # End the response page.
- unless (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
- $template->process("bug/navigate.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- $template->process("global/footer.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- }
- exit;
-}
-
-
-# You cannot mark bugs as duplicates when changing several bugs at once
-# (because currently there is no way to check for duplicate loops in that
-# situation).
-if (!$cgi->param('id') && $cgi->param('dup_id')) {
- ThrowUserError('dupe_not_allowed');
-}
-
-# Set the status, resolution, and dupe_of (if needed). This has to be done
-# down here, because the validity of status changes depends on other fields,
-# such as Target Milestone.
-foreach my $b (@bug_objects) {
- if (should_set('bug_status')) {
- $b->set_status(
- scalar $cgi->param('bug_status'),
- {resolution => scalar $cgi->param('resolution'),
- dupe_of => scalar $cgi->param('dup_id')}
- );
- }
- elsif (should_set('resolution')) {
- $b->set_resolution(scalar $cgi->param('resolution'),
- {dupe_of => scalar $cgi->param('dup_id')});
- }
- elsif (should_set('dup_id')) {
- $b->set_dup_id(scalar $cgi->param('dup_id'));
- }
+if (defined $cgi->param('id')) {
+ # Flags should be set AFTER the bug has been moved into another
+ # product/component. The structure of flags code doesn't currently
+ # allow them to be set using set_all.
+ my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi(
+ $first_bug, undef, $vars);
+ $first_bug->set_flags($flags, $new_flags);
}
##############################
# Do Actual Database Updates #
##############################
foreach my $bug (@bug_objects) {
- $dbh->bz_start_transaction();
+ my $changes = $bug->update();
- my $timestamp = $dbh->selectrow_array(q{SELECT NOW()});
- my $changes = $bug->update($timestamp);
-
- my %notify_deps;
if ($changes->{'bug_status'}) {
- my ($old_status, $new_status) = @{ $changes->{'bug_status'} };
-
- # If this bug has changed from opened to closed or vice-versa,
- # then all of the bugs we block need to be notified.
- if (is_open_state($old_status) ne is_open_state($new_status)) {
- $notify_deps{$_} = 1 foreach (@{$bug->blocked});
- }
-
+ my $new_status = $changes->{'bug_status'}->[1];
# We may have zeroed the remaining time, if we moved into a closed
# status, so we should inform the user about that.
if (!is_open_state($new_status) && $changes->{'remaining_time'}) {
$vars->{'message'} = "remaining_time_zeroed"
- if Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'});
+ if Bugzilla->user->is_timetracker;
}
}
- # To get a list of all changed dependencies, convert the "changes" arrays
- # into a long string, then collapse that string into unique numbers in
- # a hash.
- my $all_changed_deps = join(', ', @{ $changes->{'dependson'} || [] });
- $all_changed_deps = join(', ', @{ $changes->{'blocked'} || [] },
- $all_changed_deps);
- my %changed_deps = map { $_ => 1 } split(', ', $all_changed_deps);
- # When clearning one field (say, blocks) and filling in the other
- # (say, dependson), an empty string can get into the hash and cause
- # an error later.
- delete $changed_deps{''};
-
- # $msgs will store emails which have to be sent to voters, if any.
- my $msgs;
- if ($changes->{'product'}) {
- # If some votes have been removed, RemoveVotes() returns
- # a list of messages to send to voters.
- # We delay the sending of these messages till tables are unlocked.
- $msgs = RemoveVotes($bug->id, 0, 'votes_bug_moved');
- CheckIfVotedConfirmed($bug->id, Bugzilla->user->id);
- }
-
- # Set and update flags.
- Bugzilla::Flag->process($bug, undef, $timestamp, $vars);
-
- $dbh->bz_commit_transaction();
-
- ###############
- # Send Emails #
- ###############
-
- # Now is a good time to send email to voters.
- foreach my $msg (@$msgs) {
- MessageToMTA($msg);
- }
-
- my $old_qa = $changes->{'qa_contact'} ? $changes->{'qa_contact'}->[0] : '';
- my $old_own = $changes->{'assigned_to'} ? $changes->{'assigned_to'}->[0] : '';
- my $old_cc = $changes->{cc} ? $changes->{cc}->[0] : '';
- $vars->{'mailrecipients'} = {
- cc => [split(/[\s,]+/, $old_cc)],
- owner => $old_own,
- qacontact => $old_qa,
- changer => Bugzilla->user->login };
-
- $vars->{'id'} = $bug->id;
- $vars->{'type'} = "bug";
-
- # Let the user know the bug was changed and who did and didn't
- # receive email about the change.
- send_results($bug->id, $vars);
-
- # If the bug was marked as a duplicate, we need to notify users on the
- # other bug of any changes to that bug.
- my $new_dup_id = $changes->{'dup_id'} ? $changes->{'dup_id'}->[1] : undef;
- if ($new_dup_id) {
- $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
-
- $vars->{'id'} = $new_dup_id;
- $vars->{'type'} = "dupe";
-
- # Let the user know a duplication notation was added to the
- # original bug.
- send_results($new_dup_id, $vars);
- }
-
- my %all_dep_changes = (%notify_deps, %changed_deps);
- foreach my $id (sort { $a <=> $b } (keys %all_dep_changes)) {
- $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
- $vars->{'id'} = $id;
- $vars->{'type'} = "dep";
-
- # Let the user (if he is able to see the bug) know we checked to
- # see if we should email notice of this change to users with a
- # relationship to the dependent bug and who did and didn't
- # receive email about it.
- send_results($id, $vars);
- }
+ $bug->send_changes($changes, $vars);
}
-# Determine if Patch Viewer is installed, for Diff link
-# (NB: Duplicate code with show_bug.cgi.)
-eval {
- require PatchReader;
- $vars->{'patchviewerinstalled'} = 1;
-};
+# Delete the session token used for the mass-change.
+delete_token($token) unless $cgi->param('id');
if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
# Do nothing.
}
-elsif ($action eq 'next_bug') {
- if ($next_bug) {
- if (detaint_natural($next_bug) && Bugzilla->user->can_see_bug($next_bug)) {
- my $bug = new Bugzilla::Bug($next_bug);
- ThrowCodeError("bug_error", { bug => $bug }) if $bug->error;
-
- $vars->{'bugs'} = [$bug];
- $vars->{'nextbug'} = $bug->bug_id;
-
- $template->process("bug/show.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
-
- exit;
+elsif ($action eq 'next_bug' or $action eq 'same_bug') {
+ my $bug = $vars->{'bug'};
+ if ($bug and $user->can_see_bug($bug)) {
+ if ($action eq 'same_bug') {
+ # $bug->update() does not update the internal structure of
+ # the bug sufficiently to display the bug with the new values.
+ # (That is, if we just passed in the old Bug object, we'd get
+ # a lot of old values displayed.)
+ $bug = new Bugzilla::Bug($bug->id);
+ $vars->{'bug'} = $bug;
}
- }
-} elsif ($action eq 'same_bug') {
- if (Bugzilla->user->can_see_bug($cgi->param('id'))) {
- my $bug = new Bugzilla::Bug($cgi->param('id'));
- ThrowCodeError("bug_error", { bug => $bug }) if $bug->error;
-
$vars->{'bugs'} = [$bug];
-
+ if ($action eq 'next_bug') {
+ $vars->{'nextbug'} = $bug->id;
+ }
$template->process("bug/show.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
-
exit;
}
} elsif ($action ne 'nothing') {
diff --git a/Websites/bugs.webkit.org/query.cgi b/Websites/bugs.webkit.org/query.cgi
index 6789405..1aebb8d 100755
--- a/Websites/bugs.webkit.org/query.cgi
+++ b/Websites/bugs.webkit.org/query.cgi
@@ -49,44 +49,6 @@
my $user = Bugzilla->login();
my $userid = $user->id;
-# Backwards compatibility hack -- if there are any of the old QUERY_*
-# cookies around, and we are logged in, then move them into the database
-# and nuke the cookie. This is required for Bugzilla 2.8 and earlier.
-if ($userid) {
- my @oldquerycookies;
- foreach my $i ($cgi->cookie()) {
- if ($i =~ /^QUERY_(.*)$/) {
- push(@oldquerycookies, [$1, $i, $cgi->cookie($i)]);
- }
- }
- if (defined $cgi->cookie('DEFAULTQUERY')) {
- push(@oldquerycookies, [DEFAULT_QUERY_NAME, 'DEFAULTQUERY',
- $cgi->cookie('DEFAULTQUERY')]);
- }
- if (@oldquerycookies) {
- foreach my $ref (@oldquerycookies) {
- my ($name, $cookiename, $value) = (@$ref);
- if ($value) {
- # If the query name contains invalid characters, don't import.
- $name =~ /[<>&]/ && next;
- trick_taint($name);
- $dbh->bz_start_transaction();
- my $query = $dbh->selectrow_array(
- "SELECT query FROM namedqueries " .
- "WHERE userid = ? AND name = ?",
- undef, ($userid, $name));
- if (!$query) {
- $dbh->do("INSERT INTO namedqueries " .
- "(userid, name, query) VALUES " .
- "(?, ?, ?)", undef, ($userid, $name, $value));
- }
- $dbh->bz_commit_transaction();
- }
- $cgi->remove_cookie($cookiename);
- }
- }
-}
-
if ($cgi->param('nukedefaultquery')) {
if ($userid) {
$dbh->do("DELETE FROM namedqueries" .
@@ -96,6 +58,9 @@
$buffer = "";
}
+# We are done with changes committed to the DB.
+$dbh = Bugzilla->switch_to_shadow_db;
+
my $userdefaultquery;
if ($userid) {
$userdefaultquery = $dbh->selectrow_array(
@@ -110,64 +75,46 @@
# Items which are single-valued, the template should only reference [0]
# and ignore any multiple values.
sub PrefillForm {
- my ($buf) = (@_);
+ my ($buf) = @_;
my $cgi = Bugzilla->cgi;
$buf = new Bugzilla::CGI($buf);
my $foundone = 0;
- # Nothing must be undef, otherwise the template complains.
- foreach my $name ("bug_status", "resolution", "assigned_to",
- "rep_platform", "priority", "bug_severity",
- "classification", "product", "reporter", "op_sys",
- "component", "version", "chfield", "chfieldfrom",
- "chfieldto", "chfieldvalue", "target_milestone",
- "email", "emailtype", "emailreporter",
- "emailassigned_to", "emailcc", "emailqa_contact",
- "emaillongdesc", "content",
- "changedin", "votes", "short_desc", "short_desc_type",
- "long_desc", "long_desc_type", "bug_file_loc",
- "bug_file_loc_type", "status_whiteboard",
- "status_whiteboard_type", "bug_id",
- "bugidtype", "keywords", "keywords_type",
- "deadlinefrom", "deadlineto",
- "x_axis_field", "y_axis_field", "z_axis_field",
- "chart_format", "cumulate", "x_labels_vertical",
- "category", "subcategory", "name", "newcategory",
- "newsubcategory", "public", "frequency")
- {
- $default{$name} = [];
+ # If there are old-style boolean charts in the URL (from an old saved
+ # search or from an old link on the web somewhere) then convert them
+ # to the new "custom search" format so that the form is populated
+ # properly.
+ my $any_boolean_charts = grep { /^field-?\d+/ } $buf->param();
+ if ($any_boolean_charts) {
+ my $search = new Bugzilla::Search(params => scalar $buf->Vars);
+ $search->boolean_charts_to_custom_search($buf);
}
-
- # we won't prefill the boolean chart data from this query if
- # there are any being submitted via params
- my $prefillcharts = (grep(/^field-/, $cgi->param)) ? 0 : 1;
-
+
+ # Query parameters that don't represent form fields on this page.
+ my @skip = qw(format query_format list_id columnlist);
+
# Iterate over the URL parameters
foreach my $name ($buf->param()) {
+ next if grep { $_ eq $name } @skip;
+ $foundone = 1;
my @values = $buf->param($name);
-
- # If the name begins with the string 'field', 'type', 'value', or
- # 'negate', then it is part of the boolean charts. Because
- # these are built different than the rest of the form, we need
- # to store these as parameters. We also need to indicate that
- # we found something so the default query isn't added in if
- # all we have are boolean chart items.
- if ($name =~ m/^(?:field|type|value|negate)/) {
- $cgi->param(-name => $name, -value => $values[0]) if ($prefillcharts);
- $foundone = 1;
+
+ # If the name is a single letter followed by numbers, it's part
+ # of Custom Search. We store these as an array of hashes.
+ if ($name =~ /^([[:lower:]])(\d+)$/) {
+ $default{'custom_search'}->[$2]->{$1} = $values[0];
}
# If the name ends in a number (which it does for the fields which
# are part of the email searching), we use the array
# positions to show the defaults for that number field.
- elsif ($name =~ m/^(.+)(\d)$/ && defined($default{$1})) {
- $foundone = 1;
+ elsif ($name =~ /^(\w+)(\d)$/) {
$default{$1}->[$2] = $values[0];
}
- elsif (exists $default{$name}) {
- $foundone = 1;
- push (@{$default{$name}}, @values);
+ else {
+ push (@{ $default{$name} }, @values);
}
}
+
return $foundone;
}
@@ -181,10 +128,6 @@
}
}
-if (!scalar(@{$default{'chfieldto'}}) || $default{'chfieldto'}->[0] eq "") {
- $default{'chfieldto'} = ["Now"];
-}
-
# if using groups for entry, then we don't want people to see products they
# don't have access to. Remove them from the list.
my @selectable_products = sort {lc($a->name) cmp lc($b->name)}
@@ -196,6 +139,9 @@
my %versions;
my %milestones;
+# Exclude products with no components.
+@selectable_products = grep { scalar @{$_->components} } @selectable_products;
+
foreach my $product (@selectable_products) {
$components{$_->name} = 1 foreach (@{$product->components});
$versions{$_->name} = 1 foreach (@{$product->versions});
@@ -222,13 +168,6 @@
$vars->{'target_milestone'} = \@milestones;
}
-$vars->{'have_keywords'} = Bugzilla::Keyword::keyword_count();
-
-my $legal_resolutions = get_legal_field_values('resolution');
-push(@$legal_resolutions, "---"); # Oy, what a hack.
-# Another hack - this array contains "" for some reason. See bug 106589.
-$vars->{'resolution'} = [grep ($_, @$legal_resolutions)];
-
my @chfields;
push @chfields, "[Bug creation]";
@@ -246,28 +185,28 @@
push @chfields, $val;
}
-if (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
+if (Bugzilla->user->is_timetracker) {
push @chfields, "work_time";
} else {
+ @chfields = grep($_ ne "deadline", @chfields);
@chfields = grep($_ ne "estimated_time", @chfields);
@chfields = grep($_ ne "remaining_time", @chfields);
}
@chfields = (sort(@chfields));
$vars->{'chfield'} = \@chfields;
-$vars->{'bug_status'} = get_legal_field_values('bug_status');
-$vars->{'rep_platform'} = get_legal_field_values('rep_platform');
-$vars->{'op_sys'} = get_legal_field_values('op_sys');
-$vars->{'priority'} = get_legal_field_values('priority');
-$vars->{'bug_severity'} = get_legal_field_values('bug_severity');
+$vars->{'bug_status'} = Bugzilla::Field->new({name => 'bug_status'})->legal_values;
+$vars->{'rep_platform'} = Bugzilla::Field->new({name => 'rep_platform'})->legal_values;
+$vars->{'op_sys'} = Bugzilla::Field->new({name => 'op_sys'})->legal_values;
+$vars->{'priority'} = Bugzilla::Field->new({name => 'priority'})->legal_values;
+$vars->{'bug_severity'} = Bugzilla::Field->new({name => 'bug_severity'})->legal_values;
+$vars->{'resolution'} = Bugzilla::Field->new({name => 'resolution'})->legal_values;
# Boolean charts
-my @fields = Bugzilla->get_fields({ obsolete => 0 });
+my @fields = @{ Bugzilla->fields({ obsolete => 0 }) };
# If we're not in the time-tracking group, exclude time-tracking fields.
-if (!Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
- foreach my $tt_field (qw(estimated_time remaining_time work_time
- percentage_complete deadline))
- {
+if (!Bugzilla->user->is_timetracker) {
+ foreach my $tt_field (TIMETRACKING_FIELDS) {
@fields = grep($_->name ne $tt_field, @fields);
}
}
@@ -276,43 +215,6 @@
unshift(@fields, { name => "noop", description => "---" });
$vars->{'fields'} = \@fields;
-# Creating new charts - if the cmd-add value is there, we define the field
-# value so the code sees it and creates the chart. It will attempt to select
-# "xyzzy" as the default, and fail. This is the correct behaviour.
-foreach my $cmd (grep(/^cmd-/, $cgi->param)) {
- if ($cmd =~ /^cmd-add(\d+)-(\d+)-(\d+)$/) {
- $cgi->param(-name => "field$1-$2-$3", -value => "xyzzy");
- }
-}
-
-if (!$cgi->param('field0-0-0')) {
- $cgi->param(-name => 'field0-0-0', -value => "xyzzy");
-}
-
-# Create data structure of boolean chart info. It's an array of arrays of
-# arrays - with the inner arrays having three members - field, type and
-# value.
-my @charts;
-for (my $chart = 0; $cgi->param("field$chart-0-0"); $chart++) {
- my @rows;
- for (my $row = 0; $cgi->param("field$chart-$row-0"); $row++) {
- my @cols;
- for (my $col = 0; $cgi->param("field$chart-$row-$col"); $col++) {
- my $value = $cgi->param("value$chart-$row-$col");
- if (!defined($value)) {
- $value = '';
- }
- push(@cols, { field => $cgi->param("field$chart-$row-$col"),
- type => $cgi->param("type$chart-$row-$col") || 'noop',
- value => $value });
- }
- push(@rows, \@cols);
- }
- push(@charts, {'rows' => \@rows, 'negate' => scalar($cgi->param("negate$chart")) });
-}
-
-$default{'charts'} = \@charts;
-
# Named queries
if ($userid) {
$vars->{'namedqueries'} = $dbh->selectcol_arrayref(
@@ -343,7 +245,15 @@
$vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
}
+if ($cgi->param('format') && $cgi->param('format') =~ /^report-(table|graph)$/) {
+ # Get legal custom fields for tabular and graphical reports.
+ my @custom_fields_for_reports =
+ grep { $_->type == FIELD_TYPE_SINGLE_SELECT } Bugzilla->active_custom_fields;
+ $vars->{'custom_fields'} = \@custom_fields_for_reports;
+}
+
$vars->{'known_name'} = $cgi->param('known_name');
+$vars->{'columnlist'} = $cgi->param('columnlist');
# Add in the defaults.
diff --git a/Websites/bugs.webkit.org/quips.cgi b/Websites/bugs.webkit.org/quips.cgi
index a423a75..92d0fbc 100755
--- a/Websites/bugs.webkit.org/quips.cgi
+++ b/Websites/bugs.webkit.org/quips.cgi
@@ -32,6 +32,7 @@
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::User;
+use Bugzilla::Token;
my $user = Bugzilla->login(LOGIN_REQUIRED);
@@ -41,6 +42,7 @@
my $vars = {};
my $action = $cgi->param('action') || "";
+my $token = $cgi->param('token');
if ($action eq "show") {
# Read in the entire quip list
@@ -74,9 +76,10 @@
(Bugzilla->params->{'quip_list_entry_control'} eq "closed") &&
ThrowUserError("no_new_quips");
+ check_hash_token($token, ['create-quips']);
# Add the quip
my $approved = (Bugzilla->params->{'quip_list_entry_control'} eq "open")
- || Bugzilla->user->in_group('admin') || 0;
+ || $user->in_group('bz_quip_moderators') || 0;
my $comment = $cgi->param("quip");
$comment || ThrowUserError("need_quip");
trick_taint($comment); # Used in a placeholder below
@@ -88,11 +91,12 @@
}
if ($action eq 'approve') {
- $user->in_group('admin')
- || ThrowUserError("auth_failure", {group => "admin",
+ $user->in_group('bz_quip_moderators')
+ || ThrowUserError("auth_failure", {group => "bz_quip_moderators",
action => "approve",
object => "quips"});
-
+
+ check_hash_token($token, ['approve-quips']);
# Read in the entire quip list
my $quipsref = $dbh->selectall_arrayref("SELECT quipid, approved FROM quips");
@@ -127,13 +131,14 @@
}
if ($action eq "delete") {
- Bugzilla->user->in_group("admin")
- || ThrowUserError("auth_failure", {group => "admin",
+ $user->in_group('bz_quip_moderators')
+ || ThrowUserError("auth_failure", {group => "bz_quip_moderators",
action => "delete",
object => "quips"});
my $quipid = $cgi->param("quipid");
ThrowCodeError("need_quipid") unless $quipid =~ /(\d+)/;
$quipid = $1;
+ check_hash_token($token, ['quips', $quipid]);
($vars->{'deleted_quip'}) = $dbh->selectrow_array(
"SELECT quip FROM quips WHERE quipid = ?",
diff --git a/Websites/bugs.webkit.org/relogin.cgi b/Websites/bugs.webkit.org/relogin.cgi
index 63b4444..04042c3 100755
--- a/Websites/bugs.webkit.org/relogin.cgi
+++ b/Websites/bugs.webkit.org/relogin.cgi
@@ -37,13 +37,18 @@
my $template = Bugzilla->template;
my $cgi = Bugzilla->cgi;
-my $action = $cgi->param('action') || 'logout';
+my $action = $cgi->param('action') || '';
my $vars = {};
my $target;
+if (!$action) {
+ # redirect to index.cgi if no action is defined.
+ print $cgi->redirect(correct_urlbase() . 'index.cgi');
+ exit;
+}
# prepare-sudo: Display the sudo information & login page
-if ($action eq 'prepare-sudo') {
+elsif ($action eq 'prepare-sudo') {
# We must have a logged-in user to do this
# That user must be in the 'bz_sudoers' group
my $user = Bugzilla->login(LOGIN_REQUIRED);
@@ -140,15 +145,16 @@
# If we have a reason passed in, keep it under 200 characters
my $reason = $cgi->param('reason') || '';
- $reason = substr($reason, $[, 200);
+ $reason = substr($reason, 0, 200);
# Calculate the session expiry time (T + 6 hours)
- my $time_string = time2str('%a, %d-%b-%Y %T %Z', time+(6*60*60), 'GMT');
+ my $time_string = time2str('%a, %d-%b-%Y %T %Z', time + MAX_SUDO_TOKEN_AGE, 'GMT');
# For future sessions, store the unique ID of the target user
+ my $token = Bugzilla::Token::_create_token($user->id, 'sudo', $target_user->id);
$cgi->send_cookie('-name' => 'sudo',
'-expires' => $time_string,
- '-value' => $target_user->id
+ '-value' => $token
);
# For the present, change the values of Bugzilla::user & Bugzilla::sudoer
@@ -158,9 +164,8 @@
# Go ahead and send out the message now
my $message;
- my $mail_template = Bugzilla->template_inner($target_user->settings->{'lang'}->{'value'});
+ my $mail_template = Bugzilla->template_inner($target_user->setting('lang'));
$mail_template->process('email/sudo.txt.tmpl', { reason => $reason }, \$message);
- Bugzilla->template_inner("");
MessageToMTA($message);
$vars->{'message'} = 'sudo_started';
@@ -170,6 +175,7 @@
# end-sudo: End the current sudo session (if one is in progress)
elsif ($action eq 'end-sudo') {
# Regardless of our state, delete the sudo cookie if it exists
+ my $token = $cgi->cookie('sudo');
$cgi->remove_cookie('sudo');
# Are we in an sudo session?
@@ -178,30 +184,18 @@
if (defined($sudoer)) {
Bugzilla->sudo_request($sudoer, undef);
}
+ # Now that the session is over, remove the token from the DB.
+ delete_token($token);
# NOTE: If you want to log the end of an sudo session, so it here.
$vars->{'message'} = 'sudo_ended';
$target = 'global/message.html.tmpl';
}
-# Log out the currently logged-in user (this used to be the only thing this did)
-elsif ($action eq 'logout') {
- # We don't want to remove a random logincookie from the db, so
- # call Bugzilla->login(). If we're logged in after this, then
- # the logincookie must be correct
- Bugzilla->login(LOGIN_OPTIONAL);
-
- $cgi->remove_cookie('sudo');
-
- Bugzilla->logout();
-
- $vars->{'message'} = "logged_out";
- $target = 'global/message.html.tmpl';
-}
# No valid action found
else {
Bugzilla->login(LOGIN_OPTIONAL);
- ThrowCodeError('unknown_action', {action => $action});
+ ThrowUserError('unknown_action', {action => $action});
}
# Display the template
diff --git a/Websites/bugs.webkit.org/report.cgi b/Websites/bugs.webkit.org/report.cgi
index f4360ef..5a4e3db 100755
--- a/Websites/bugs.webkit.org/report.cgi
+++ b/Websites/bugs.webkit.org/report.cgi
@@ -29,11 +29,13 @@
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Field;
+use Bugzilla::Search;
+
+use List::MoreUtils qw(uniq);
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
my $vars = {};
-my $buffer = $cgi->query_string();
# Go straight back to query.cgi if we are adding a boolean chart.
if (grep(/^cmd-/, $cgi->param())) {
@@ -45,12 +47,7 @@
exit;
}
-use Bugzilla::Search;
-
Bugzilla->login();
-
-my $dbh = Bugzilla->switch_to_shadow_db();
-
my $action = $cgi->param('action') || 'menu';
if ($action eq "menu") {
@@ -61,6 +58,9 @@
exit;
}
+# Sanitize the URL, to make URLs shorter.
+$cgi->clean_search_url;
+
my $col_field = $cgi->param('x_axis_field') || '';
my $row_field = $cgi->param('y_axis_field') || '';
my $tbl_field = $cgi->param('z_axis_field') || '';
@@ -95,6 +95,10 @@
}
}
else {
+ if (!Bugzilla->feature('graphical_reports')) {
+ ThrowCodeError('feature_disabled', { feature => 'graphical_reports' });
+ }
+
if ($row_field && !$col_field) {
# 1D *charts* should be displayed horizontally (with an col_field only)
$col_field = $row_field;
@@ -102,50 +106,35 @@
}
}
-my %columns;
-$columns{'bug_severity'} = "bugs.bug_severity";
-$columns{'priority'} = "bugs.priority";
-$columns{'rep_platform'} = "bugs.rep_platform";
-$columns{'assigned_to'} = "map_assigned_to.login_name";
-$columns{'reporter'} = "map_reporter.login_name";
-$columns{'qa_contact'} = "map_qa_contact.login_name";
-$columns{'bug_status'} = "bugs.bug_status";
-$columns{'resolution'} = "bugs.resolution";
-$columns{'component'} = "map_components.name";
-$columns{'product'} = "map_products.name";
-$columns{'classification'} = "map_classifications.name";
-$columns{'version'} = "bugs.version";
-$columns{'op_sys'} = "bugs.op_sys";
-$columns{'votes'} = "bugs.votes";
-$columns{'keywords'} = "bugs.keywords";
-$columns{'target_milestone'} = "bugs.target_milestone";
-# One which means "nothing". Any number would do, really. It just gets SELECTed
-# so that we always select 3 items in the query.
-$columns{''} = "42217354";
+# Valid bug fields that can be reported on.
+my $valid_columns = Bugzilla::Search::REPORT_COLUMNS;
# Validate the values in the axis fields or throw an error.
!$row_field
- || ($columns{$row_field} && trick_taint($row_field))
+ || ($valid_columns->{$row_field} && trick_taint($row_field))
|| ThrowCodeError("report_axis_invalid", {fld => "x", val => $row_field});
!$col_field
- || ($columns{$col_field} && trick_taint($col_field))
+ || ($valid_columns->{$col_field} && trick_taint($col_field))
|| ThrowCodeError("report_axis_invalid", {fld => "y", val => $col_field});
!$tbl_field
- || ($columns{$tbl_field} && trick_taint($tbl_field))
+ || ($valid_columns->{$tbl_field} && trick_taint($tbl_field))
|| ThrowCodeError("report_axis_invalid", {fld => "z", val => $tbl_field});
-my @axis_fields = ($row_field, $col_field, $tbl_field);
-my @selectnames = map($columns{$_}, @axis_fields);
+my @axis_fields = grep { $_ } ($row_field, $col_field, $tbl_field);
# Clone the params, so that Bugzilla::Search can modify them
my $params = new Bugzilla::CGI($cgi);
-my $search = new Bugzilla::Search('fields' => \@selectnames,
- 'params' => $params);
-my $query = $search->getSQL();
+my $search = new Bugzilla::Search(
+ fields => \@axis_fields,
+ params => scalar $params->Vars,
+ allow_unlimited => 1,
+);
+my $query = $search->sql;
$::SIG{TERM} = 'DEFAULT';
$::SIG{PIPE} = 'DEFAULT';
+my $dbh = Bugzilla->switch_to_shadow_db();
my $results = $dbh->selectall_arrayref($query);
# We have a hash of hashes for the data itself, and a hash to hold the
@@ -163,22 +152,10 @@
my $tbl_isnumeric = 1;
foreach my $result (@$results) {
- my ($row, $col, $tbl) = @$result;
-
# handle empty dimension member names
- $row = ' ' if ($row eq '');
- $col = ' ' if ($col eq '');
- $tbl = ' ' if ($tbl eq '');
-
- $row = "" if ($row eq $columns{''});
- $col = "" if ($col eq $columns{''});
- $tbl = "" if ($tbl eq $columns{''});
-
- # account for the fact that names may start with '_' or '.'. Change this
- # so the template doesn't hide hash elements with those keys
- $row =~ s/^([._])/ $1/;
- $col =~ s/^([._])/ $1/;
- $tbl =~ s/^([._])/ $1/;
+ my $row = check_value($row_field, $result);
+ my $col = check_value($col_field, $result);
+ my $tbl = check_value($tbl_field, $result);
$data{$tbl}{$col}{$row}++;
$names{"col"}{$col}++;
@@ -190,9 +167,9 @@
$tbl_isnumeric &&= ($tbl =~ /^-?\d+(\.\d+)?$/o);
}
-my @col_names = @{get_names($names{"col"}, $col_isnumeric, $col_field)};
-my @row_names = @{get_names($names{"row"}, $row_isnumeric, $row_field)};
-my @tbl_names = @{get_names($names{"tbl"}, $tbl_isnumeric, $tbl_field)};
+my @col_names = get_names($names{"col"}, $col_isnumeric, $col_field);
+my @row_names = get_names($names{"row"}, $row_isnumeric, $row_field);
+my @tbl_names = get_names($names{"tbl"}, $tbl_isnumeric, $tbl_field);
# The GD::Graph package requires a particular format of data, so once we've
# gathered everything into the hashes and made sure we know the size of the
@@ -226,7 +203,7 @@
$vars->{'col_field'} = $col_field;
$vars->{'row_field'} = $row_field;
$vars->{'tbl_field'} = $tbl_field;
-$vars->{'time'} = time();
+$vars->{'time'} = localtime(time());
$vars->{'col_names'} = \@col_names;
$vars->{'row_names'} = \@row_names;
@@ -272,10 +249,10 @@
# We need to keep track of the defined restrictions on each of the
# axes, because buglistbase, below, throws them away. Without this, we
# get buglistlinks wrong if there is a restriction on an axis field.
- $vars->{'col_vals'} = join("&", $buffer =~ /[&?]($col_field=[^&]+)/g);
- $vars->{'row_vals'} = join("&", $buffer =~ /[&?]($row_field=[^&]+)/g);
- $vars->{'tbl_vals'} = join("&", $buffer =~ /[&?]($tbl_field=[^&]+)/g);
-
+ $vars->{'col_vals'} = get_field_restrictions($col_field);
+ $vars->{'row_vals'} = get_field_restrictions($row_field);
+ $vars->{'tbl_vals'} = get_field_restrictions($tbl_field);
+
# We need a number of different variants of the base URL for different
# URLs in the HTML.
$vars->{'buglistbase'} = $cgi->canonicalise_query(
@@ -295,7 +272,7 @@
$vars->{'data'} = \@image_data;
}
else {
- ThrowCodeError("unknown_action", {action => $cgi->param('action')});
+ ThrowUserError('unknown_action', {action => $action});
}
my $format = $template->get_format("reports/report", $formatparam,
@@ -317,9 +294,9 @@
if ($cgi->param('debug')) {
require Data::Dumper;
print "<pre>data hash:\n";
- print Data::Dumper::Dumper(%data) . "\n\n";
+ print html_quote(Data::Dumper::Dumper(%data)) . "\n\n";
print "data array:\n";
- print Data::Dumper::Dumper(@image_data) . "\n\n</pre>";
+ print html_quote(Data::Dumper::Dumper(@image_data)) . "\n\n</pre>";
}
# All formats point to the same section of the documentation.
@@ -330,42 +307,55 @@
$template->process("$format->{'template'}", $vars)
|| ThrowTemplateError($template->error());
-exit;
-
sub get_names {
- my ($names, $isnumeric, $field) = @_;
-
- # These are all the fields we want to preserve the order of in reports.
- my %fields = ('priority' => get_legal_field_values('priority'),
- 'bug_severity' => get_legal_field_values('bug_severity'),
- 'rep_platform' => get_legal_field_values('rep_platform'),
- 'op_sys' => get_legal_field_values('op_sys'),
- 'bug_status' => get_legal_field_values('bug_status'),
- 'resolution' => [' ', @{get_legal_field_values('resolution')}]);
+ my ($names, $isnumeric, $field_name) = @_;
+ my ($field, @sorted);
+ # _realname fields aren't real Bugzilla::Field objects, but they are a
+ # valid axis, so we don't vailidate them as Bugzilla::Field objects.
+ $field = Bugzilla::Field->check($field_name)
+ if ($field_name && $field_name !~ /_realname$/);
- my $field_list = $fields{$field};
- my @sorted;
-
- if ($field_list) {
- my @unsorted = keys %{$names};
-
- # Extract the used fields from the field_list, in the order they
- # appear in the field_list. This lets us keep e.g. severities in
- # the normal order.
- #
- # This is O(n^2) but it shouldn't matter for short lists.
- @sorted = map {lsearch(\@unsorted, $_) == -1 ? () : $_} @{$field_list};
+ if ($field && $field->is_select) {
+ foreach my $value (@{$field->legal_values}) {
+ push(@sorted, $value->name) if $names->{$value->name};
+ }
+ unshift(@sorted, ' ') if $field_name eq 'resolution';
+ @sorted = uniq @sorted;
}
elsif ($isnumeric) {
# It's not a field we are preserving the order of, so sort it
# numerically...
- sub numerically { $a <=> $b }
- @sorted = sort numerically keys(%{$names});
- } else {
- # ...or alphabetically, as appropriate.
- @sorted = sort(keys(%{$names}));
+ @sorted = sort { $a <=> $b } keys %$names;
}
-
- return \@sorted;
+ else {
+ # ...or alphabetically, as appropriate.
+ @sorted = sort keys %$names;
+ }
+
+ return @sorted;
+}
+
+sub check_value {
+ my ($field, $result) = @_;
+
+ my $value;
+ if (!defined $field) {
+ $value = '';
+ }
+ elsif ($field eq '') {
+ $value = ' ';
+ }
+ else {
+ $value = shift @$result;
+ $value = ' ' if (!defined $value || $value eq '');
+ }
+ return $value;
+}
+
+sub get_field_restrictions {
+ my $field = shift;
+ my $cgi = Bugzilla->cgi;
+
+ return join('&', map {"$field=$_"} $cgi->param($field));
}
diff --git a/Websites/bugs.webkit.org/reports.cgi b/Websites/bugs.webkit.org/reports.cgi
index 5cd04bb..7f79738 100755
--- a/Websites/bugs.webkit.org/reports.cgi
+++ b/Websites/bugs.webkit.org/reports.cgi
@@ -18,22 +18,15 @@
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
-# Contributor(s): Harrison Page <harrison@netscape.com>,
-# Terry Weissman <terry@mozilla.org>,
-# Dawn Endico <endico@mozilla.org>
-# Bryce Nesbitt <bryce@nextbus.COM>,
-# Joe Robins <jmrobins@tgix.com>,
-# Gervase Markham <gerv@gerv.net> and Adam Spiers <adam@spiers.net>
-# Added ability to chart any combination of resolutions/statuses.
-# Derive the choice of resolutions/statuses from the -All- data file
-# Removed hardcoded order of resolutions/statuses when reading from
-# daily stats file, so now works independently of collectstats.pl
-# version
-# Added image caching by date and datasets
-# Myk Melez <myk@mozilla.org>:
-# Implemented form field validation and reorganized code.
-# Frédéric Buclin <LpSolit@gmail.com>:
-# Templatization.
+# Contributor(s): Harrison Page <harrison@netscape.com>
+# Terry Weissman <terry@mozilla.org>
+# Dawn Endico <endico@mozilla.org>
+# Bryce Nesbitt <bryce@nextbus.com>
+# Joe Robins <jmrobins@tgix.com>
+# Gervase Markham <gerv@gerv.net>
+# Adam Spiers <adam@spiers.net>
+# Myk Melez <myk@mozilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
use strict;
@@ -45,32 +38,28 @@
use Bugzilla::Error;
use Bugzilla::Status;
-eval "use GD";
-$@ && ThrowCodeError("gd_not_installed");
-eval "use Chart::Lines";
-$@ && ThrowCodeError("chart_lines_not_installed");
-
-my $dir = bz_locations()->{'datadir'} . "/mining";
-my $graph_url = 'graphs';
-my $graph_dir = bz_locations()->{'libpath'} . '/' .$graph_url;
+use File::Basename;
+use Digest::MD5 qw(md5_hex);
# If we're using bug groups for products, we should apply those restrictions
# to viewing reports, as well. Time to check the login in that case.
my $user = Bugzilla->login();
-
-Bugzilla->switch_to_shadow_db();
-
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
my $vars = {};
-# We only want those products that the user has permissions for.
-my @myproducts;
-push( @myproducts, "-All-");
-# Extract product names from objects and add them to the list.
-push( @myproducts, map { $_->name } @{$user->get_selectable_products} );
+if (!Bugzilla->feature('old_charts')) {
+ ThrowCodeError('feature_disabled', { feature => 'old_charts' });
+}
-if (! defined $cgi->param('product')) {
+my $dir = bz_locations()->{'datadir'} . "/mining";
+my $graph_dir = bz_locations()->{'graphsdir'};
+my $graph_url = basename($graph_dir);
+my $product_name = $cgi->param('product') || '';
+
+Bugzilla->switch_to_shadow_db();
+
+if (!$product_name) {
# Can we do bug charts?
(-d $dir && -d $graph_dir)
|| ThrowCodeError('chart_dir_nonexistent',
@@ -88,51 +77,61 @@
push(@datasets, $datasets);
}
+ # We only want those products that the user has permissions for.
+ my @myproducts = ('-All-');
+ # Extract product names from objects and add them to the list.
+ push( @myproducts, map { $_->name } @{$user->get_selectable_products} );
+
$vars->{'datasets'} = \@datasets;
$vars->{'products'} = \@myproducts;
print $cgi->header();
-
- $template->process('reports/old-charts.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
- exit;
}
else {
- my $product = $cgi->param('product');
-
# For security and correctness, validate the value of the "product" form variable.
# Valid values are those products for which the user has permissions which appear
# in the "product" drop-down menu on the report generation form.
- grep($_ eq $product, @myproducts)
- || ThrowUserError("invalid_product_name", {product => $product});
+ my ($product) = grep { $_->name eq $product_name } @{$user->get_selectable_products};
+ ($product || $product_name eq '-All-')
+ || ThrowUserError('invalid_product_name', {product => $product_name});
- # We've checked that the product exists, and that the user can see it
- # This means that is OK to detaint
- trick_taint($product);
+ # Product names can change over time. Their ID cannot; so use the ID
+ # to generate the filename.
+ my $prod_id = $product ? $product->id : 0;
- defined($cgi->param('datasets')) || ThrowUserError('missing_datasets');
+ # Make sure there is something to plot.
+ my @datasets = $cgi->param('datasets');
+ scalar(@datasets) || ThrowUserError('missing_datasets');
- my $datasets = join('', $cgi->param('datasets'));
-
- my $type = chart_image_type();
- my $data_file = daily_stats_filename($product);
- my $image_file = chart_image_name($data_file, $type, $datasets);
- my $url_image = correct_urlbase() . "$graph_url/$image_file";
-
- if (! -e "$graph_dir/$image_file") {
- generate_chart("$dir/$data_file", "$graph_dir/$image_file", $type,
- $product, $datasets);
+ if (grep { $_ !~ /^[A-Za-z0-9:_-]+$/ } @datasets) {
+ ThrowUserError('invalid_datasets', {'datasets' => \@datasets});
}
- $vars->{'url_image'} = $url_image;
+ # Filenames must not be guessable as they can point to products
+ # you are not allowed to see. Also, different projects can have
+ # the same product names.
+ my $key = Bugzilla->localconfig->{'site_wide_secret'};
+ my $project = bz_locations()->{'project'} || '';
+ my $image_file = join(':', ($key, $project, $prod_id, @datasets));
+ # Wide characters cause md5_hex() to die.
+ if (Bugzilla->params->{'utf8'}) {
+ utf8::encode($image_file) if utf8::is_utf8($image_file);
+ }
+ $image_file = md5_hex($image_file) . '.png';
+ trick_taint($image_file);
+
+ if (! -e "$graph_dir/$image_file") {
+ generate_chart($dir, "$graph_dir/$image_file", $product, \@datasets);
+ }
+
+ $vars->{'url_image'} = "$graph_url/$image_file";
print $cgi->header(-Content_Disposition=>'inline; filename=bugzilla_report.html');
-
- $template->process('reports/old-charts.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
- exit;
}
+$template->process('reports/old-charts.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+
#####################
# Subroutines #
#####################
@@ -141,9 +140,8 @@
my $dir = shift;
my @datasets;
- my $datafile = daily_stats_filename('-All-');
- open(DATA, '<', "$dir/$datafile")
- || ThrowCodeError('chart_file_open_fail', {filename => "$dir/$datafile"});
+ open(DATA, '<', "$dir/-All-")
+ || ThrowCodeError('chart_file_open_fail', {filename => "$dir/-All-"});
while (<DATA>) {
if (/^# fields?: (.+)\s*$/) {
@@ -155,47 +153,12 @@
return @datasets;
}
-sub daily_stats_filename {
- my ($prodname) = @_;
- $prodname =~ s/\//-/gs;
- return $prodname;
-}
-
-sub chart_image_type {
- # what chart type should we be generating?
- my $testimg = Chart::Lines->new(2,2);
- my $type = $testimg->can('gif') ? "gif" : "png";
-
- undef $testimg;
- return $type;
-}
-
-sub chart_image_name {
- my ($data_file, $type, $datasets) = @_;
-
- # This routine generates a filename from the requested fields. The problem
- # is that we have to check the safety of doing this. We can't just require
- # that the fields exist, because what stats were collected could change
- # over time (eg by changing the resolutions available)
- # Instead, just require that each field name consists only of letters,
- # numbers, underscores and hyphens.
-
- if ($datasets !~ m/^[A-Za-z0-9:_-]+$/) {
- ThrowUserError('invalid_datasets', {'datasets' => $datasets});
- }
-
- # Since we pass the tests, consider it OK
- trick_taint($datasets);
-
- # Cache charts by generating a unique filename based on what they
- # show. Charts should be deleted by collectstats.pl nightly.
- my $id = join ("_", split (":", $datasets));
-
- return "${data_file}_${id}.$type";
-}
-
sub generate_chart {
- my ($data_file, $image_file, $type, $product, $datasets) = @_;
+ my ($dir, $image_file, $product, $datasets) = @_;
+ $product = $product ? $product->name : '-All-';
+ my $data_file = $product;
+ $data_file =~ s/\//-/gs;
+ $data_file = $dir . '/' . $data_file;
if (! open FILE, $data_file) {
if ($product eq '-All-') {
@@ -206,7 +169,7 @@
my @fields;
my @labels = qw(DATE);
- my %datasets = map { $_ => 1 } split /:/, $datasets;
+ my %datasets = map { $_ => 1 } @$datasets;
my %data = ();
while (<FILE>) {
@@ -280,5 +243,5 @@
);
$img->set (%settings);
- $img->$type($image_file, [ @data{('DATE', @labels)} ]);
+ $img->png($image_file, [ @data{('DATE', @labels)} ]);
}
diff --git a/Websites/bugs.webkit.org/request.cgi b/Websites/bugs.webkit.org/request.cgi
index 9b08e59..1edf288 100755
--- a/Websites/bugs.webkit.org/request.cgi
+++ b/Websites/bugs.webkit.org/request.cgi
@@ -42,7 +42,8 @@
# Make sure the user is logged in.
my $user = Bugzilla->login();
my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
+# Force the script to run against the shadow DB. We already validated credentials.
+Bugzilla->switch_to_shadow_db;
my $template = Bugzilla->template;
my $action = $cgi->param('action') || '';
@@ -62,23 +63,21 @@
$fields->{'requestee'}->{'type'} = 'single';
}
-Bugzilla::User::match_field($cgi, $fields);
+Bugzilla::User::match_field($fields);
if ($action eq 'queue') {
queue();
}
else {
- my $flagtypes = $dbh->selectcol_arrayref('SELECT DISTINCT(name) FROM flagtypes
- ORDER BY name');
+ my $flagtypes = get_flag_types();
my @types = ('all', @$flagtypes);
my $vars = {};
- $vars->{'products'} = $user->get_selectable_products;
$vars->{'types'} = \@types;
$vars->{'requests'} = {};
my %components;
- foreach my $prod (@{$vars->{'products'}}) {
+ foreach my $prod (@{$user->get_selectable_products}) {
foreach my $comp (@{$prod->components}) {
$components{$comp->name} = 1;
}
@@ -96,7 +95,6 @@
sub queue {
my $cgi = Bugzilla->cgi;
- # There are some user privilege checks to do. We do them against the main DB.
my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
my $user = Bugzilla->user;
@@ -115,7 +113,7 @@
products.name, components.name,
flags.attach_id, attachments.description,
requesters.realname, requesters.login_name,
- requestees.realname, requestees.login_name,
+ requestees.realname, requestees.login_name, COUNT(privs.group_id),
" . $dbh->sql_date_format('flags.modification_date', '%Y.%m.%d %H:%i') .
# Use the flags and flagtypes tables for information about the flags,
# the bugs and attachments tables for target info, the profiles tables
@@ -142,7 +140,9 @@
LEFT JOIN bug_group_map AS bgmap
ON bgmap.bug_id = bugs.bug_id
AND bgmap.group_id NOT IN (" .
- join(', ', (-1, values(%{$user->groups}))) . ")
+ $user->groups_as_string . ")
+ LEFT JOIN bug_group_map AS privs
+ ON privs.bug_id = bugs.bug_id
LEFT JOIN cc AS ccmap
ON ccmap.who = $userid
AND ccmap.bug_id = bugs.bug_id
@@ -166,9 +166,7 @@
$query .= " AND flags.status = '?' " unless $status;
# The set of criteria by which we filter records to display in the queue.
- # We now move to the shadow DB to query the DB.
my @criteria = ();
- $dbh = Bugzilla->switch_to_shadow_db;
# A list of columns to exclude from the report because the report conditions
# limit the data being displayed to exact matches for those columns.
@@ -210,7 +208,7 @@
# Filter results by exact product or component.
if (defined $cgi->param('product') && $cgi->param('product') ne "") {
- my $product = Bugzilla::Product::check_product(scalar $cgi->param('product'));
+ my $product = Bugzilla::Product->check(scalar $cgi->param('product'));
push(@criteria, "bugs.product_id = " . $product->id);
push(@excluded_columns, 'product') unless $cgi->param('do_union');
if (defined $cgi->param('component') && $cgi->param('component') ne "") {
@@ -296,28 +294,24 @@
'attach_summary' => $data[8] ,
'requester' => ($data[9] ? "$data[9] <$data[10]>" : $data[10]) ,
'requestee' => ($data[11] ? "$data[11] <$data[12]>" : $data[12]) ,
- 'created' => $data[13]
+ 'restricted' => $data[13] ? 1 : 0,
+ 'created' => $data[14]
};
push(@requests, $request);
}
# Get a list of request type names to use in the filter form.
my @types = ("all");
- my $flagtypes = $dbh->selectcol_arrayref(
- "SELECT DISTINCT(name) FROM flagtypes ORDER BY name");
+ my $flagtypes = get_flag_types();
push(@types, @$flagtypes);
- # We move back to the main DB to get the list of products the user can see.
- $dbh = Bugzilla->switch_to_main_db;
-
- $vars->{'products'} = $user->get_selectable_products;
$vars->{'excluded_columns'} = \@excluded_columns;
$vars->{'group_field'} = $form_group;
$vars->{'requests'} = \@requests;
$vars->{'types'} = \@types;
my %components;
- foreach my $prod (@{$vars->{'products'}}) {
+ foreach my $prod (@{$user->get_selectable_products}) {
foreach my $comp (@{$prod->components}) {
$components{$comp->name} = 1;
}
@@ -338,8 +332,7 @@
return if !defined $status;
grep($status eq $_, qw(? +- + - all))
- || ThrowCodeError("flag_status_invalid",
- { status => $status });
+ || ThrowUserError("flag_status_invalid", { status => $status });
trick_taint($status);
return $status;
}
@@ -349,9 +342,19 @@
return if !defined $group;
grep($group eq $_, qw(requester requestee category type))
- || ThrowCodeError("request_queue_group_invalid",
- { group => $group });
+ || ThrowUserError("request_queue_group_invalid", { group => $group });
trick_taint($group);
return $group;
}
+# Returns all flag types which have at least one flag of this type.
+# If a flag type is inactive but still has flags, we want it.
+sub get_flag_types {
+ my $dbh = Bugzilla->dbh;
+ my $flag_types = $dbh->selectcol_arrayref('SELECT DISTINCT name
+ FROM flagtypes
+ WHERE flagtypes.id IN
+ (SELECT DISTINCT type_id FROM flags)
+ ORDER BY name');
+ return $flag_types;
+}
diff --git a/Websites/bugs.webkit.org/sanitycheck.cgi b/Websites/bugs.webkit.org/sanitycheck.cgi
index b89748a..231730c 100755
--- a/Websites/bugs.webkit.org/sanitycheck.cgi
+++ b/Websites/bugs.webkit.org/sanitycheck.cgi
@@ -31,9 +31,11 @@
use Bugzilla;
use Bugzilla::Bug;
use Bugzilla::Constants;
-use Bugzilla::Util;
use Bugzilla::Error;
+use Bugzilla::Hook;
+use Bugzilla::Util;
use Bugzilla::Status;
+use Bugzilla::Token;
###########################################################################
# General subs
@@ -74,10 +76,19 @@
# take the user prefs into account rather than querying the web browser.
my $template;
if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+ $template = Bugzilla->template_inner($user->setting('lang'));
}
else {
$template = Bugzilla->template;
+
+ # Only check the token if we are running this script from the
+ # web browser and a parameter is passed to the script.
+ # XXX - Maybe these two parameters should be deleted once logged in?
+ $cgi->delete('GoAheadAndLogIn', 'Bugzilla_restrictlogin');
+ if (scalar($cgi->param())) {
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['sanitycheck']);
+ }
}
my $vars = {};
@@ -87,7 +98,6 @@
# As this script can now alter the group_control_map table, we no longer
# let users with editbugs privs run it anymore.
$user->in_group("editcomponents")
- || ($user->in_group('editkeywords') && $cgi->param('rebuildkeywordcache'))
|| ThrowUserError("auth_failure", {group => "editcomponents",
action => "run",
object => "sanity_check"});
@@ -98,39 +108,6 @@
}
###########################################################################
-# Users with 'editkeywords' privs only can only check keywords.
-###########################################################################
-unless ($user->in_group('editcomponents')) {
- check_votes_or_keywords('keywords');
- Status('checks_completed');
-
- $template->process('global/footer.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
- exit;
-}
-
-###########################################################################
-# Fix vote cache
-###########################################################################
-
-if ($cgi->param('rebuildvotecache')) {
- Status('vote_cache_rebuild_start');
- $dbh->bz_start_transaction();
- $dbh->do(q{UPDATE bugs SET votes = 0});
- my $sth_update = $dbh->prepare(q{UPDATE bugs
- SET votes = ?
- WHERE bug_id = ?});
- my $sth = $dbh->prepare(q{SELECT bug_id, SUM(vote_count)
- FROM votes }. $dbh->sql_group_by('bug_id'));
- $sth->execute();
- while (my ($id, $v) = $sth->fetchrow_array) {
- $sth_update->execute($v, $id);
- }
- $dbh->bz_commit_transaction();
- Status('vote_cache_rebuild_end');
-}
-
-###########################################################################
# Create missing group_control_map entries
###########################################################################
@@ -140,14 +117,10 @@
my $na = CONTROLMAPNA;
my $shown = CONTROLMAPSHOWN;
my $insertsth = $dbh->prepare(
- qq{INSERT INTO group_control_map (
- group_id, product_id, entry,
- membercontrol, othercontrol, canedit
- )
- VALUES (
- ?, ?, 0,
- $shown, $na, 0
- )});
+ qq{INSERT INTO group_control_map
+ (group_id, product_id, membercontrol, othercontrol)
+ VALUES (?, ?, $shown, $na)});
+
my $updatesth = $dbh->prepare(qq{UPDATE group_control_map
SET membercontrol = $shown
WHERE group_id = ?
@@ -223,6 +196,22 @@
}
###########################################################################
+# Fix everconfirmed
+###########################################################################
+
+if ($cgi->param('repair_everconfirmed')) {
+ Status('everconfirmed_start');
+
+ my @confirmed_open_states = grep {$_ ne 'UNCONFIRMED'} BUG_STATE_OPEN;
+ my $confirmed_open_states = join(', ', map {$dbh->quote($_)} @confirmed_open_states);
+
+ $dbh->do("UPDATE bugs SET everconfirmed = 0 WHERE bug_status = 'UNCONFIRMED'");
+ $dbh->do("UPDATE bugs SET everconfirmed = 1 WHERE bug_status IN ($confirmed_open_states)");
+
+ Status('everconfirmed_end');
+}
+
+###########################################################################
# Fix entries in Bugs full_text
###########################################################################
@@ -250,14 +239,14 @@
require Bugzilla::BugMail;
Status('send_bugmail_start');
- my $time = $dbh->sql_interval(30, 'MINUTE');
+ my $time = $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE');
my $list = $dbh->selectcol_arrayref(qq{
SELECT bug_id
FROM bugs
WHERE (lastdiffed IS NULL
OR lastdiffed < delta_ts)
- AND delta_ts < now() - $time
+ AND delta_ts < $time
ORDER BY bug_id});
Status('send_bugmail_status', {bug_count => scalar(@$list)});
@@ -269,7 +258,7 @@
# and so choosing this user as being the last one having done a change
# for the bug may be problematic. So the best we can do at this point
# is to choose the currently logged in user for email notification.
- $vars->{'changer'} = Bugzilla->user->login;
+ $vars->{'changer'} = Bugzilla->user;
foreach my $bugid (@$list) {
Bugzilla::BugMail::Send($bugid, $vars);
@@ -293,17 +282,12 @@
$dbh->bz_start_transaction();
- # Include custom multi-select fields to the list.
- my @multi_selects = Bugzilla->get_fields({custom => 1, type => FIELD_TYPE_MULTI_SELECT});
- my @addl_fields = map { 'bug_' . $_->name . '/' } @multi_selects;
-
foreach my $pair ('attachments/', 'bug_group_map/', 'bugs_activity/',
'bugs_fulltext/', 'cc/',
'dependencies/blocked', 'dependencies/dependson',
'duplicates/dupe', 'duplicates/dupe_of',
- 'flags/', 'keywords/', 'longdescs/', 'votes/',
- @addl_fields)
- {
+ 'flags/', 'keywords/', 'longdescs/') {
+
my ($table, $field) = split('/', $pair);
$field ||= "bug_id";
@@ -376,6 +360,15 @@
Status('whines_obsolete_target_deletion_end');
}
+###########################################################################
+# Repair hook
+###########################################################################
+
+Bugzilla::Hook::process('sanitycheck_repair', { status => \&Status });
+
+###########################################################################
+# Checks
+###########################################################################
Status('checks_start');
###########################################################################
@@ -462,10 +455,6 @@
["flagexclusions", "type_id"],
["flaginclusions", "type_id"]);
-# Include custom multi-select fields to the list.
-my @multi_selects = Bugzilla->get_fields({custom => 1, type => FIELD_TYPE_MULTI_SELECT});
-my @addl_fields = map { ['bug_' . $_->name, 'bug_id'] } @multi_selects;
-
CrossCheck("bugs", "bug_id",
["bugs_activity", "bug_id"],
["bug_group_map", "bug_id"],
@@ -476,11 +465,9 @@
["dependencies", "blocked"],
["dependencies", "dependson"],
['flags', 'bug_id'],
- ["votes", "bug_id"],
["keywords", "bug_id"],
["duplicates", "dupe_of", "dupe"],
- ["duplicates", "dupe", "dupe_of"],
- @addl_fields);
+ ["duplicates", "dupe", "dupe_of"]);
CrossCheck("groups", "id",
["bug_group_map", "group_id"],
@@ -512,7 +499,6 @@
["bugs_activity", "who", "bug_id"],
["cc", "who", "bug_id"],
['quips', 'userid'],
- ["votes", "who", "bug_id"],
["longdescs", "who", "bug_id"],
["logincookies", "userid"],
["namedqueries", "userid"],
@@ -669,76 +655,15 @@
}
###########################################################################
-# Perform vote/keyword cache checks
+# Perform keyword checks
###########################################################################
-check_votes_or_keywords();
-
-sub check_votes_or_keywords {
- my $check = shift || 'all';
-
- my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare(q{SELECT bug_id, votes, keywords
- FROM bugs
- WHERE votes != 0 OR keywords != ''});
- $sth->execute;
-
- my %votes;
- my %keyword;
-
- while (my ($id, $v, $k) = $sth->fetchrow_array) {
- if ($v != 0) {
- $votes{$id} = $v;
- }
- if ($k) {
- $keyword{$id} = $k;
- }
- }
-
- # If we only want to check keywords, skip checks about votes.
- _check_votes(\%votes) unless ($check eq 'keywords');
- # If we only want to check votes, skip checks about keywords.
- _check_keywords(\%keyword) unless ($check eq 'votes');
-}
-
-sub _check_votes {
- my $votes = shift;
-
- Status('vote_count_start');
- my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare(q{SELECT bug_id, SUM(vote_count)
- FROM votes }.
- $dbh->sql_group_by('bug_id'));
- $sth->execute;
-
- my $offer_votecache_rebuild = 0;
-
- while (my ($id, $v) = $sth->fetchrow_array) {
- if ($v <= 0) {
- Status('vote_count_alert', {id => $id}, 'alert');
- } else {
- if (!defined $votes->{$id} || $votes->{$id} != $v) {
- Status('vote_cache_alert', {id => $id}, 'alert');
- $offer_votecache_rebuild = 1;
- }
- delete $votes->{$id};
- }
- }
- foreach my $id (keys %$votes) {
- Status('vote_cache_alert', {id => $id}, 'alert');
- $offer_votecache_rebuild = 1;
- }
-
- Status('vote_cache_rebuild_fix') if $offer_votecache_rebuild;
-}
-
-sub _check_keywords {
- my $keyword = shift;
-
- Status('keyword_check_start');
+sub check_keywords {
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
+ Status('keyword_check_start');
+
my %keywordids;
my $keywords = $dbh->selectall_arrayref(q{SELECT id, name
FROM keyworddefs});
@@ -770,79 +695,6 @@
$lastid = $id;
$lastk = $k;
}
-
- Status('keyword_cache_start');
-
- if ($cgi->param('rebuildkeywordcache')) {
- $dbh->bz_start_transaction();
- }
-
- my $query = q{SELECT keywords.bug_id, keyworddefs.name
- FROM keywords
- INNER JOIN keyworddefs
- ON keyworddefs.id = keywords.keywordid
- INNER JOIN bugs
- ON keywords.bug_id = bugs.bug_id
- ORDER BY keywords.bug_id, keyworddefs.name};
-
- $sth = $dbh->prepare($query);
- $sth->execute;
-
- my $lastb = 0;
- my @list;
- my %realk;
- while (1) {
- my ($b, $k) = $sth->fetchrow_array;
- if (!defined $b || $b != $lastb) {
- if (@list) {
- $realk{$lastb} = join(', ', @list);
- }
- last unless $b;
-
- $lastb = $b;
- @list = ();
- }
- push(@list, $k);
- }
-
- my @badbugs = ();
-
- foreach my $b (keys(%$keyword)) {
- if (!exists $realk{$b} || $realk{$b} ne $keyword->{$b}) {
- push(@badbugs, $b);
- }
- }
- foreach my $b (keys(%realk)) {
- if (!exists $keyword->{$b}) {
- push(@badbugs, $b);
- }
- }
- if (@badbugs) {
- @badbugs = sort {$a <=> $b} @badbugs;
-
- if ($cgi->param('rebuildkeywordcache')) {
- my $sth_update = $dbh->prepare(q{UPDATE bugs
- SET keywords = ?
- WHERE bug_id = ?});
-
- Status('keyword_cache_fixing');
- foreach my $b (@badbugs) {
- my $k = '';
- if (exists($realk{$b})) {
- $k = $realk{$b};
- }
- $sth_update->execute($k, $b);
- }
- Status('keyword_cache_fixed');
- } else {
- Status('keyword_cache_alert', {badbugs => \@badbugs}, 'alert');
- Status('keyword_cache_rebuild');
- }
- }
-
- if ($cgi->param('rebuildkeywordcache')) {
- $dbh->bz_commit_transaction();
- }
}
###########################################################################
@@ -953,19 +805,13 @@
Status('bug_check_status_everconfirmed');
BugCheck("bugs WHERE bug_status = 'UNCONFIRMED' AND everconfirmed = 1",
- 'bug_check_status_everconfirmed_error_text');
+ 'bug_check_status_everconfirmed_error_text', 'repair_everconfirmed');
my @confirmed_open_states = grep {$_ ne 'UNCONFIRMED'} BUG_STATE_OPEN;
my $confirmed_open_states = join(', ', map {$dbh->quote($_)} @confirmed_open_states);
BugCheck("bugs WHERE bug_status IN ($confirmed_open_states) AND everconfirmed = 0",
- 'bug_check_status_everconfirmed_error_text2');
-
-Status('bug_check_votes_everconfirmed');
-
-BugCheck("bugs INNER JOIN products ON bugs.product_id = products.id " .
- "WHERE everconfirmed = 0 AND votestoconfirm <= votes",
- 'bug_check_votes_everconfirmed_error_text');
+ 'bug_check_status_everconfirmed_error_text2', 'repair_everconfirmed');
###########################################################################
# Control Values
@@ -1021,12 +867,12 @@
Status('unsent_bugmail_check');
-my $time = $dbh->sql_interval(30, 'MINUTE');
+my $time = $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE');
my $badbugs = $dbh->selectcol_arrayref(qq{
SELECT bug_id
FROM bugs
WHERE (lastdiffed IS NULL OR lastdiffed < delta_ts)
- AND delta_ts < now() - $time
+ AND delta_ts < $time
ORDER BY bug_id});
@@ -1060,6 +906,12 @@
Status('whines_obsolete_target_fix') if $display_repair_whines_link;
###########################################################################
+# Check hook
+###########################################################################
+
+Bugzilla::Hook::process('sanitycheck_check', { status => \&Status });
+
+###########################################################################
# End
###########################################################################
diff --git a/Websites/bugs.webkit.org/sanitycheck.pl b/Websites/bugs.webkit.org/sanitycheck.pl
index 592069a..d9c5963 100755
--- a/Websites/bugs.webkit.org/sanitycheck.pl
+++ b/Websites/bugs.webkit.org/sanitycheck.pl
@@ -42,8 +42,6 @@
pod2usage({-verbose => 1, -exitval => 1}) if $help;
-Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
-
# Be sure a login name if given.
$login || ThrowUserError('invalid_username');
@@ -113,4 +111,4 @@
This script provides a way of running a 'Sanity Check' on the database
via either a CLI or cron. It is equivalent to calling sanitycheck.cgi
-via a web broswer.
+via a web browser.
diff --git a/Websites/bugs.webkit.org/search_plugin.cgi b/Websites/bugs.webkit.org/search_plugin.cgi
index 8b8b954..4d58f7b 100755
--- a/Websites/bugs.webkit.org/search_plugin.cgi
+++ b/Websites/bugs.webkit.org/search_plugin.cgi
@@ -20,14 +20,24 @@
use Bugzilla;
use Bugzilla::Error;
+use Bugzilla::Constants;
Bugzilla->login();
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
+my $vars = {};
# Return the appropriate HTTP response headers.
print $cgi->header('application/xml');
-$template->process("search/search-plugin.xml.tmpl")
+# Get the contents of favicon.ico
+my $filename = bz_locations()->{'libpath'} . "/images/favicon.ico";
+if (open(IN, $filename)) {
+ local $/;
+ binmode IN;
+ $vars->{'favicon'} = <IN>;
+ close IN;
+}
+$template->process("search/search-plugin.xml.tmpl", $vars)
|| ThrowTemplateError($template->error());
diff --git a/Websites/bugs.webkit.org/show_activity.cgi b/Websites/bugs.webkit.org/show_activity.cgi
index 74af09d..c11895c 100755
--- a/Websites/bugs.webkit.org/show_activity.cgi
+++ b/Websites/bugs.webkit.org/show_activity.cgi
@@ -43,17 +43,21 @@
# Make sure the bug ID is a positive integer representing an existing
# bug that the user is authorized to access.
-my $bug_id = $cgi->param('id');
-ValidateBugID($bug_id);
+my $id = $cgi->param('id');
+my $bug = Bugzilla::Bug->check($id);
###############################################################################
# End Data/Security Validation
###############################################################################
-($vars->{'operations'}, $vars->{'incomplete_data'}) =
- Bugzilla::Bug::GetBugActivity($bug_id);
+# Run queries against the shadow DB. In the worst case, new changes are not
+# visible immediately due to replication lag.
+Bugzilla->switch_to_shadow_db;
-$vars->{'bug'} = new Bugzilla::Bug($bug_id);
+($vars->{'operations'}, $vars->{'incomplete_data'}) =
+ Bugzilla::Bug::GetBugActivity($bug->id);
+
+$vars->{'bug'} = $bug;
print $cgi->header();
diff --git a/Websites/bugs.webkit.org/show_bug.cgi b/Websites/bugs.webkit.org/show_bug.cgi
index 716a11e..5f2dbc5 100755
--- a/Websites/bugs.webkit.org/show_bug.cgi
+++ b/Websites/bugs.webkit.org/show_bug.cgi
@@ -51,15 +51,20 @@
exit;
}
-my @bugs = ();
+my $format = $template->get_format("bug/show", scalar $cgi->param('format'),
+ scalar $cgi->param('ctype'));
+
+my @bugs;
my %marks;
+# If the user isn't logged in, we use data from the shadow DB. If he plans
+# to edit the bug(s), he will have to log in first, meaning that the data
+# will be reloaded anyway, from the main DB.
+Bugzilla->switch_to_shadow_db unless $user->id;
+
if ($single) {
my $id = $cgi->param('id');
- # Its a bit silly to do the validation twice - that functionality should
- # probably move into Bug.pm at some point
- ValidateBugID($id);
- push @bugs, new Bugzilla::Bug($id);
+ push @bugs, Bugzilla::Bug->check($id);
if (defined $cgi->param('mark')) {
foreach my $range (split ',', $cgi->param('mark')) {
if ($range =~ /^(\d+)-(\d+)$/) {
@@ -88,32 +93,19 @@
}
}
-# Determine if Patch Viewer is installed, for Diff link
-eval {
- require PatchReader;
- $vars->{'patchviewerinstalled'} = 1;
-};
+Bugzilla::Bug->preload(\@bugs);
$vars->{'bugs'} = \@bugs;
$vars->{'marks'} = \%marks;
-$vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
my @bugids = map {$_->bug_id} grep {!$_->error} @bugs;
$vars->{'bugids'} = join(", ", @bugids);
-# Next bug in list (if there is one)
-my @bug_list;
-if ($cgi->cookie("BUGLIST")) {
- @bug_list = split(/:/, $cgi->cookie("BUGLIST"));
-}
-
-$vars->{'bug_list'} = \@bug_list;
-
# Work out which fields we are displaying (currently XML only.)
# If no explicit list is defined, we show all fields. We then exclude any
# on the exclusion list. This is so you can say e.g. "Everything except
# attachments" without listing almost all the fields.
-my @fieldlist = (Bugzilla::Bug->fields, 'group', 'long_desc',
+my @fieldlist = (Bugzilla::Bug->fields, 'flag', 'group', 'long_desc',
'attachment', 'attachmentdata', 'token');
my %displayfields;
@@ -121,7 +113,7 @@
@fieldlist = $cgi->param("field");
}
-unless (Bugzilla->user->in_group(Bugzilla->params->{"timetrackinggroup"})) {
+unless (Bugzilla->user->is_timetracker) {
@fieldlist = grep($_ !~ /(^deadline|_time)$/, @fieldlist);
}
diff --git a/Websites/bugs.webkit.org/showattachment.cgi b/Websites/bugs.webkit.org/showattachment.cgi
deleted file mode 100755
index 6ae47d5..0000000
--- a/Websites/bugs.webkit.org/showattachment.cgi
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env perl -wT
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Bug Tracking System.
-#
-# The Initial Developer of the Original Code is Netscape Communications
-# Corporation. Portions created by Netscape are
-# Copyright (C) 1998 Netscape Communications Corporation. All
-# Rights Reserved.
-#
-# Contributor(s): Terry Weissman <terry@mozilla.org>
-# Jacob Steenhagen <jake@bugzilla.org>
-
-use strict;
-
-use lib qw(. lib);
-
-use Bugzilla;
-use Bugzilla::Util;
-
-my $cgi = Bugzilla->cgi;
-
-my $id = $cgi->param('attach_id');
-detaint_natural($id) if defined $id;
-$id ||= "";
-
-print $cgi->redirect(-location=>"attachment.cgi?id=$id",
- -status=>'301 Permanent Redirect');
-
-exit;
diff --git a/Websites/bugs.webkit.org/showdependencygraph.cgi b/Websites/bugs.webkit.org/showdependencygraph.cgi
index a26d543..a54785e 100755
--- a/Websites/bugs.webkit.org/showdependencygraph.cgi
+++ b/Websites/bugs.webkit.org/showdependencygraph.cgi
@@ -29,6 +29,7 @@
use Bugzilla;
use Bugzilla::Constants;
+use Bugzilla::Install::Filesystem;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Bug;
@@ -58,7 +59,7 @@
sub CreateImagemap {
my $mapfilename = shift;
my $map = "<map name=\"imagemap\">\n";
- my $default;
+ my $default = "";
open MAP, "<$mapfilename";
while(my $line = <MAP>) {
@@ -89,32 +90,36 @@
my $key = "$blocked,$dependson";
if (!exists $edgesdone{$key}) {
$edgesdone{$key} = 1;
- print $fh "$blocked -> $dependson\n";
+ print $fh "$dependson -> $blocked\n";
$seen{$blocked} = 1;
$seen{$dependson} = 1;
}
}
+ThrowCodeError("missing_bug_id") if !defined $cgi->param('id');
+
# The list of valid directions. Some are not proposed in the dropdrown
# menu despite the fact that they are valid.
my @valid_rankdirs = ('LR', 'RL', 'TB', 'BT');
my $rankdir = $cgi->param('rankdir') || 'TB';
# Make sure the submitted 'rankdir' value is valid.
-if (lsearch(\@valid_rankdirs, $rankdir) < 0) {
+if (!grep { $_ eq $rankdir } @valid_rankdirs) {
$rankdir = 'TB';
}
my $display = $cgi->param('display') || 'tree';
my $webdotdir = bz_locations()->{'webdotdir'};
-if (!defined $cgi->param('id') && $display ne 'doall') {
- ThrowCodeError("missing_bug_id");
-}
-
my ($fh, $filename) = File::Temp::tempfile("XXXXXXXXXX",
SUFFIX => '.dot',
- DIR => $webdotdir);
+ DIR => $webdotdir,
+ UNLINK => 1);
+
+chmod Bugzilla::Install::Filesystem::CGI_WRITE, $filename
+ or warn install_string('chmod_failed', { path => $filename,
+ error => $! });
+
my $urlbase = Bugzilla->params->{'urlbase'};
print $fh "digraph G {";
@@ -125,64 +130,54 @@
my %baselist;
-if ($display eq 'doall') {
- my $dependencies = $dbh->selectall_arrayref(
- "SELECT blocked, dependson FROM dependencies");
+foreach my $i (split('[\s,]+', $cgi->param('id'))) {
+ my $bug = Bugzilla::Bug->check($i);
+ $baselist{$bug->id} = 1;
+}
- foreach my $dependency (@$dependencies) {
- my ($blocked, $dependson) = @$dependency;
- AddLink($blocked, $dependson, $fh);
- }
-} else {
- foreach my $i (split('[\s,]+', $cgi->param('id'))) {
- ValidateBugID($i);
- $baselist{$i} = 1;
- }
+my @stack = keys(%baselist);
- my @stack = keys(%baselist);
+if ($display eq 'web') {
+ my $sth = $dbh->prepare(q{SELECT blocked, dependson
+ FROM dependencies
+ WHERE blocked = ? OR dependson = ?});
- if ($display eq 'web') {
- my $sth = $dbh->prepare(q{SELECT blocked, dependson
- FROM dependencies
- WHERE blocked = ? OR dependson = ?});
-
- foreach my $id (@stack) {
- my $dependencies = $dbh->selectall_arrayref($sth, undef, ($id, $id));
- foreach my $dependency (@$dependencies) {
- my ($blocked, $dependson) = @$dependency;
- if ($blocked != $id && !exists $seen{$blocked}) {
- push @stack, $blocked;
- }
- if ($dependson != $id && !exists $seen{$dependson}) {
- push @stack, $dependson;
- }
- AddLink($blocked, $dependson, $fh);
+ foreach my $id (@stack) {
+ my $dependencies = $dbh->selectall_arrayref($sth, undef, ($id, $id));
+ foreach my $dependency (@$dependencies) {
+ my ($blocked, $dependson) = @$dependency;
+ if ($blocked != $id && !exists $seen{$blocked}) {
+ push @stack, $blocked;
}
+ if ($dependson != $id && !exists $seen{$dependson}) {
+ push @stack, $dependson;
+ }
+ AddLink($blocked, $dependson, $fh);
}
}
- # This is the default: a tree instead of a spider web.
- else {
- my @blocker_stack = @stack;
- foreach my $id (@blocker_stack) {
- my $blocker_ids = Bugzilla::Bug::EmitDependList('blocked', 'dependson', $id);
- foreach my $blocker_id (@$blocker_ids) {
- push(@blocker_stack, $blocker_id) unless $seen{$blocker_id};
- AddLink($id, $blocker_id, $fh);
- }
- }
- my @dependent_stack = @stack;
- foreach my $id (@dependent_stack) {
- my $dep_bug_ids = Bugzilla::Bug::EmitDependList('dependson', 'blocked', $id);
- foreach my $dep_bug_id (@$dep_bug_ids) {
- push(@dependent_stack, $dep_bug_id) unless $seen{$dep_bug_id};
- AddLink($dep_bug_id, $id, $fh);
- }
+}
+# This is the default: a tree instead of a spider web.
+else {
+ my @blocker_stack = @stack;
+ foreach my $id (@blocker_stack) {
+ my $blocker_ids = Bugzilla::Bug::EmitDependList('blocked', 'dependson', $id);
+ foreach my $blocker_id (@$blocker_ids) {
+ push(@blocker_stack, $blocker_id) unless $seen{$blocker_id};
+ AddLink($id, $blocker_id, $fh);
}
}
+ my @dependent_stack = @stack;
+ foreach my $id (@dependent_stack) {
+ my $dep_bug_ids = Bugzilla::Bug::EmitDependList('dependson', 'blocked', $id);
+ foreach my $dep_bug_id (@$dep_bug_ids) {
+ push(@dependent_stack, $dep_bug_id) unless $seen{$dep_bug_id};
+ AddLink($dep_bug_id, $id, $fh);
+ }
+ }
+}
- foreach my $k (keys(%baselist)) {
- $seen{$k} = 1;
- }
+foreach my $k (keys(%baselist)) {
+ $seen{$k} = 1;
}
my $sth = $dbh->prepare(
@@ -192,9 +187,6 @@
foreach my $k (keys(%seen)) {
# Retrieve bug information from the database
my ($stat, $resolution, $summary) = $dbh->selectrow_array($sth, undef, $k);
- $stat ||= 'NEW';
- $resolution ||= '';
- $summary ||= '';
# Resolution and summary are shown only if user can see the bug
if (!Bugzilla->user->can_see_bug($k)) {
@@ -206,6 +198,10 @@
my @params;
if ($summary ne "" && $cgi->param('showsummary')) {
+ # Wide characters cause GraphViz to die.
+ if (Bugzilla->params->{'utf8'}) {
+ utf8::encode($summary) if utf8::is_utf8($summary);
+ }
$summary =~ s/([\\\"])/\\$1/g;
push(@params, qq{label="$k\\n$summary"});
}
@@ -240,8 +236,6 @@
print $fh "}\n";
close $fh;
-chmod 0777, $filename;
-
my $webdotbase = Bugzilla->params->{'webdotbase'};
if ($webdotbase =~ /^https?:/) {
@@ -259,15 +253,20 @@
my ($pngfh, $pngfilename) = File::Temp::tempfile("XXXXXXXXXX",
SUFFIX => '.png',
DIR => $webdotdir);
+
+ chmod Bugzilla::Install::Filesystem::WS_SERVE, $pngfilename
+ or warn install_string('chmod_failed', { path => $pngfilename,
+ error => $! });
+
binmode $pngfh;
open(DOT, "\"$webdotbase\" -Tpng $filename|");
binmode DOT;
print $pngfh $_ while <DOT>;
close DOT;
close $pngfh;
-
+
# On Windows $pngfilename will contain \ instead of /
- $pngfilename =~ s|\\|/|g if $^O eq 'MSWin32';
+ $pngfilename =~ s|\\|/|g if ON_WINDOWS;
# Under mod_perl, pngfilename will have an absolute path, and we
# need to make that into a relative path.
@@ -283,12 +282,18 @@
my ($mapfh, $mapfilename) = File::Temp::tempfile("XXXXXXXXXX",
SUFFIX => '.map',
DIR => $webdotdir);
+
+ chmod Bugzilla::Install::Filesystem::WS_SERVE, $mapfilename
+ or warn install_string('chmod_failed', { path => $mapfilename,
+ error => $! });
+
binmode $mapfh;
open(DOT, "\"$webdotbase\" -Tismap $filename|");
binmode DOT;
print $mapfh $_ while <DOT>;
close DOT;
close $mapfh;
+
$vars->{'image_map'} = CreateImagemap($mapfilename);
}
diff --git a/Websites/bugs.webkit.org/showdependencytree.cgi b/Websites/bugs.webkit.org/showdependencytree.cgi
index 8193d92..ec53600 100755
--- a/Websites/bugs.webkit.org/showdependencytree.cgi
+++ b/Websites/bugs.webkit.org/showdependencytree.cgi
@@ -49,9 +49,8 @@
# Make sure the bug ID is a positive integer representing an existing
# bug that the user is authorized to access.
-my $id = $cgi->param('id') || ThrowUserError('improper_bug_id_field_value');
-ValidateBugID($id);
-my $current_bug = new Bugzilla::Bug($id);
+my $bug = Bugzilla::Bug->check(scalar $cgi->param('id'));
+my $id = $bug->id;
local our $hide_resolved = $cgi->param('hide_resolved') ? 1 : 0;
@@ -67,7 +66,7 @@
# Generate the tree of bugs that this bug depends on and a list of IDs
# appearing in the tree.
-my $dependson_tree = { $id => $current_bug };
+my $dependson_tree = { $id => $bug };
my $dependson_ids = {};
GenerateTree($id, "dependson", 1, $dependson_tree, $dependson_ids);
$vars->{'dependson_tree'} = $dependson_tree;
@@ -75,7 +74,7 @@
# Generate the tree of bugs that this bug blocks and a list of IDs
# appearing in the tree.
-my $blocked_tree = { $id => $current_bug };
+my $blocked_tree = { $id => $bug };
my $blocked_ids = {};
GenerateTree($id, "blocked", 1, $blocked_tree, $blocked_ids);
$vars->{'blocked_tree'} = $blocked_tree;
diff --git a/Websites/bugs.webkit.org/sidebar.cgi b/Websites/bugs.webkit.org/sidebar.cgi
deleted file mode 100755
index 82e0ecf..0000000
--- a/Websites/bugs.webkit.org/sidebar.cgi
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env perl -wT
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Bug Tracking System.
-#
-# Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
-
-use strict;
-
-use lib qw(. lib);
-
-use Bugzilla;
-use Bugzilla::Error;
-
-Bugzilla->login();
-my $cgi = Bugzilla->cgi;
-my $template = Bugzilla->template;
-
-###############################################################################
-# Main Body Execution
-###############################################################################
-
-# This sidebar is currently for use with Mozilla based web browsers.
-# Internet Explorer 6 is supposed to have a similar feature, but it
-# most likely won't support XUL ;) When that does come out, this
-# can be expanded to output normal HTML for IE. Until then, I like
-# the way Scott's sidebar looks so I'm using that as the base for
-# this file.
-# http://bugzilla.mozilla.org/show_bug.cgi?id=37339
-
-my $useragent = $ENV{HTTP_USER_AGENT};
-if ($useragent =~ m:Mozilla/([1-9][0-9]*):i && $1 >= 5 && $useragent !~ m/compatible/i) {
- print $cgi->header("application/vnd.mozilla.xul+xml");
- # Generate and return the XUL from the appropriate template.
- $template->process("sidebar.xul.tmpl")
- || ThrowTemplateError($template->error());
-} else {
- ThrowUserError("sidebar_supports_mozilla_only");
-}
diff --git a/Websites/bugs.webkit.org/skins/.cvsignore b/Websites/bugs.webkit.org/skins/.cvsignore
deleted file mode 100644
index 0d82d79..0000000
--- a/Websites/bugs.webkit.org/skins/.cvsignore
+++ /dev/null
@@ -1 +0,0 @@
-custom
diff --git a/Websites/bugs.webkit.org/skins/README b/Websites/bugs.webkit.org/skins/README
new file mode 100644
index 0000000..111c00f
--- /dev/null
+++ b/Websites/bugs.webkit.org/skins/README
@@ -0,0 +1,20 @@
+There are three directories here, standard/, custom/, and contrib/.
+
+standard/ holds the standard stylesheets. These are used no matter
+what skin the user selects. If the user selects the "Classic" skin,
+then *only* the standard/ stylesheets are used.
+
+contrib/ holds "skins" that the user can select in their preferences.
+skins are in directories, and they contain files with the same names
+as the files in skins/standard/. Simply putting a new directory
+into the contrib/ directory adds a new skin as an option in users'
+preferences.
+
+custom/ allows you to locally override the standard/ and contrib/ CSS.
+If you put files into the custom/ directory with the same names as the CSS
+files in skins/standard/, you can override the standard/ and contrib/
+CSS. For example, if you want to override some CSS in
+skins/standard/global.css, then you should create a file called "global.css"
+in custom/ and put some CSS in it. The CSS you put into files in custom/ will
+be used *in addition* to the CSS in skins/standard/ or the CSS in
+skins/contrib/. It will apply to every skin.
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/.cvsignore b/Websites/bugs.webkit.org/skins/contrib/Dusk/.cvsignore
deleted file mode 100644
index 8e2561a..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/.cvsignore
+++ /dev/null
@@ -1,17 +0,0 @@
-IE-fixes.css
-admin.css
-attachment.css
-create_attachment.css
-dependency-tree.css
-duplicates.css
-editusers.css
-help.css
-index.css
-panel.css
-params.css
-release-notes.css
-show_bug.css
-show_multiple.css
-summarize-time.css
-voting.css
-yui
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/IE-fixes.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/IE-fixes.css
deleted file mode 100644
index e79eb0c..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/IE-fixes.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for IE-fixes.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/admin.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/admin.css
deleted file mode 100644
index 3d94a813..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/admin.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for admin.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/create_attachment.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/create_attachment.css
deleted file mode 100644
index b21f718e..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/create_attachment.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for create_attachment.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/dependency-tree.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/dependency-tree.css
deleted file mode 100644
index cdfea6d..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/dependency-tree.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for dependency-tree.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/duplicates.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/duplicates.css
deleted file mode 100644
index ffd50c6..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/duplicates.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for duplicates.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/editusers.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/editusers.css
deleted file mode 100644
index 44cc442..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/editusers.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for editusers.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/global.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/global.css
index d6e8159..3a18e40 100644
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/global.css
+++ b/Websites/bugs.webkit.org/skins/contrib/Dusk/global.css
@@ -34,16 +34,23 @@
-moz-border-radius-topright: 5px;
}
-#header .links {
+#header .links, #footer {
background-color: #929bb1;
- color: #f1dbc7;
+ color: #ddd;
+}
+
+#header {
-moz-border-radius-bottomleft: 5px;
-moz-border-radius-bottomright: 5px;
border: none;
}
-#header a {
+#header a, #footer a {
color: white;
+ text-decoration: none;
+}
+#header a:hover, #footer a:hover {
+ text-decoration: underline;
}
/* body */
@@ -51,31 +58,17 @@
#bugzilla-body {
background: #f0f0f0;
color: black;
- margin-top: 10px;
- margin-bottom: 10px;
border: 1px solid #747e93;
padding: 10px;
font-size: 10pt;
-moz-border-radius: 5px;
}
-a:link,
-a:link:hover {
- color: #6169c0;
+a {
+ color: #6070cf;
}
-
-a:visited {
- color: #3d4a68;
-}
-
-a:link,
-a:visited {
- text-decoration: none;
-}
-
-a:link:hover,
-a:visited:hover {
- text-decoration: underline;
+a:hover {
+ color: #8090ef;
}
hr {
@@ -123,7 +116,9 @@
border-top: 1px solid #c8c8ba;
}
-/* comments */
+/************/
+/* Comments */
+/************/
#comments th {
font-size: 9pt;
@@ -155,26 +150,21 @@
font-size: 9pt;
}
-.bz_first_comment {
-}
-
-.bz_comment_head,
-.bz_first_comment_head {
+.bz_comment_head, .bz_first_comment_head {
margin: 0; padding: 0;
background-color: transparent;
font-weight: bold;
}
+.bz_comment_user {
+ margin-left: 0;
+}
+
.bz_comment.bz_private {
background-color: #f0e8e8;
border-color: #f8c8ba;
}
-.bz_comment_head i,
-.bz_first_comment_head i {
- font-style: normal;
-}
-
.comment_rule {
display: none;
}
@@ -182,18 +172,11 @@
/* footer */
#footer {
- background: #929bb1;
- color: #f1dbc7;
border: 1px solid #747e93;
width: 100%;
- font-size: 9pt;
-moz-border-radius: 5px;
}
-#footer a {
- color: white;
-}
-
#footer #links-actions,
#footer #links-edit,
#footer #links-saved,
@@ -201,24 +184,15 @@
margin-top: 2ex;
}
-#footer .label {
- font-weight: bold;
- color: #dddddd;
-}
-
#footer .links {
border-spacing: 30px;
- padding-bottom: 2ex;
+ margin-bottom: 2ex;
}
.separator {
color: #cccccc;
}
-#footer li.form {
- background-color: transparent;
-}
-
/* tabs */
.tabbed .tabbody {
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/help.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/help.css
deleted file mode 100644
index 629f4ef..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/help.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for help.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/index.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/index.css
index 8226a7e..c9c2d17 100644
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/index.css
+++ b/Websites/bugs.webkit.org/skins/contrib/Dusk/index.css
@@ -2,3 +2,8 @@
* Custom rules for index.css.
* The rules you put here override rules in that stylesheet.
*/
+
+ div#page-index .outro
+ {
+ clear:both;
+ }
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/panel.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/panel.css
deleted file mode 100644
index 95c7075..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/panel.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for panel.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/params.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/params.css
deleted file mode 100644
index e12c282..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/params.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for params.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/release-notes.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/release-notes.css
deleted file mode 100644
index ed8f726..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/release-notes.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for release-notes.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/show_bug.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/show_bug.css
deleted file mode 100644
index 7e2e544..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/show_bug.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for show_bug.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/show_multiple.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/show_multiple.css
deleted file mode 100644
index c2f2e30..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/show_multiple.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for show_multiple.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/summarize-time.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/summarize-time.css
deleted file mode 100644
index c34a6a8..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/summarize-time.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for summarize-time.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/voting.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/voting.css
deleted file mode 100644
index 0bf24f5..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/voting.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for voting.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/contrib/Dusk/yui/calendar.css b/Websites/bugs.webkit.org/skins/contrib/Dusk/yui/calendar.css
deleted file mode 100644
index 6e02e8e..0000000
--- a/Websites/bugs.webkit.org/skins/contrib/Dusk/yui/calendar.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for yui/calendar.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/IE-fixes.css b/Websites/bugs.webkit.org/skins/custom/IE-fixes.css
deleted file mode 100644
index e79eb0c..0000000
--- a/Websites/bugs.webkit.org/skins/custom/IE-fixes.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for IE-fixes.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/admin.css b/Websites/bugs.webkit.org/skins/custom/admin.css
deleted file mode 100644
index 3d94a813..0000000
--- a/Websites/bugs.webkit.org/skins/custom/admin.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for admin.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/buglist.css b/Websites/bugs.webkit.org/skins/custom/buglist.css
deleted file mode 100644
index a36abbc..0000000
--- a/Websites/bugs.webkit.org/skins/custom/buglist.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for buglist.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/create_attachment.css b/Websites/bugs.webkit.org/skins/custom/create_attachment.css
deleted file mode 100644
index b21f718e..0000000
--- a/Websites/bugs.webkit.org/skins/custom/create_attachment.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for create_attachment.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/dependency-tree.css b/Websites/bugs.webkit.org/skins/custom/dependency-tree.css
deleted file mode 100644
index cdfea6d..0000000
--- a/Websites/bugs.webkit.org/skins/custom/dependency-tree.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for dependency-tree.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/duplicates.css b/Websites/bugs.webkit.org/skins/custom/duplicates.css
deleted file mode 100644
index ffd50c6..0000000
--- a/Websites/bugs.webkit.org/skins/custom/duplicates.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for duplicates.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/editusers.css b/Websites/bugs.webkit.org/skins/custom/editusers.css
deleted file mode 100644
index 44cc442..0000000
--- a/Websites/bugs.webkit.org/skins/custom/editusers.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for editusers.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/help.css b/Websites/bugs.webkit.org/skins/custom/help.css
deleted file mode 100644
index 629f4ef..0000000
--- a/Websites/bugs.webkit.org/skins/custom/help.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for help.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/opendarwin.gif b/Websites/bugs.webkit.org/skins/custom/opendarwin.gif
deleted file mode 100644
index e3dd251..0000000
--- a/Websites/bugs.webkit.org/skins/custom/opendarwin.gif
+++ /dev/null
Binary files differ
diff --git a/Websites/bugs.webkit.org/skins/custom/panel.css b/Websites/bugs.webkit.org/skins/custom/panel.css
deleted file mode 100644
index 95c7075..0000000
--- a/Websites/bugs.webkit.org/skins/custom/panel.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for panel.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/params.css b/Websites/bugs.webkit.org/skins/custom/params.css
deleted file mode 100644
index e12c282..0000000
--- a/Websites/bugs.webkit.org/skins/custom/params.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for params.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/release-notes.css b/Websites/bugs.webkit.org/skins/custom/release-notes.css
deleted file mode 100644
index ed8f726..0000000
--- a/Websites/bugs.webkit.org/skins/custom/release-notes.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for release-notes.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/show_bug.css b/Websites/bugs.webkit.org/skins/custom/show_bug.css
deleted file mode 100644
index 7e2e544..0000000
--- a/Websites/bugs.webkit.org/skins/custom/show_bug.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for show_bug.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/show_multiple.css b/Websites/bugs.webkit.org/skins/custom/show_multiple.css
deleted file mode 100644
index c2f2e30..0000000
--- a/Websites/bugs.webkit.org/skins/custom/show_multiple.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for show_multiple.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/summarize-time.css b/Websites/bugs.webkit.org/skins/custom/summarize-time.css
deleted file mode 100644
index c34a6a8..0000000
--- a/Websites/bugs.webkit.org/skins/custom/summarize-time.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for summarize-time.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/voting.css b/Websites/bugs.webkit.org/skins/custom/voting.css
deleted file mode 100644
index 0bf24f5..0000000
--- a/Websites/bugs.webkit.org/skins/custom/voting.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for voting.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/custom/yui/calendar.css b/Websites/bugs.webkit.org/skins/custom/yui/calendar.css
deleted file mode 100644
index 6e02e8e..0000000
--- a/Websites/bugs.webkit.org/skins/custom/yui/calendar.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * Custom rules for yui/calendar.css.
- * The rules you put here override rules in that stylesheet.
- */
diff --git a/Websites/bugs.webkit.org/skins/standard/IE-fixes.css b/Websites/bugs.webkit.org/skins/standard/IE-fixes.css
index bfd525b..7ef30e5 100644
--- a/Websites/bugs.webkit.org/skins/standard/IE-fixes.css
+++ b/Websites/bugs.webkit.org/skins/standard/IE-fixes.css
@@ -13,11 +13,19 @@
* Contributor(s): Marc Schumann <wurblzap@gmail.com>
*/
-.bz_comment_text, .uneditable_textarea {
+.bz_comment_text, .uneditable_textarea, tbody.file pre {
white-space: pre;
word-wrap: break-word;
}
+.component_table {
+ margin-top: .5em;
+}
+
+form#Create #comp_desc {
+ margin: .5em 1em;
+}
+
#footer #useful-links li {
padding-bottom: 0.8ex;
}
@@ -36,3 +44,20 @@
#footer .links {
display: inline;
}
+
+#bug_id_container, .search_field_grid,
+.search_email_fields, ul.bug_changes li {
+ zoom: 1;
+ display: inline;
+}
+
+#keyword_container .yui-ac-content {
+ _height: 30em; /* ie6 */
+}
+
+.bz_short_desc_column a, .bz_short_short_desc_column a {
+ /* color:inherit */
+ color: expression(this.parentNode.currentStyle['color']);
+}
+
+
diff --git a/Websites/bugs.webkit.org/skins/standard/admin.css b/Websites/bugs.webkit.org/skins/standard/admin.css
index 184c496..6b330ef 100644
--- a/Websites/bugs.webkit.org/skins/standard/admin.css
+++ b/Websites/bugs.webkit.org/skins/standard/admin.css
@@ -113,3 +113,15 @@
text-align: center;
vertical-align: middle;
}
+
+#edit_custom_field tr {
+ vertical-align: top;
+}
+
+#edit_custom_field th {
+ text-align: right;
+}
+
+#edit_custom_field th.narrow_label {
+ white-space: normal;
+}
diff --git a/Websites/bugs.webkit.org/skins/standard/attachment.css b/Websites/bugs.webkit.org/skins/standard/attachment.css
new file mode 100644
index 0000000..55e62f2
--- /dev/null
+++ b/Websites/bugs.webkit.org/skins/standard/attachment.css
@@ -0,0 +1,247 @@
+ /* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Myk Melez <myk@mozilla.org>
+ * Joel Peshkin <bugreport@peshkin.net>
+ * Erik Stambaugh <erik@dasbistro.com>
+ * Marc Schumann <wurblzap@gmail.com>
+ * Guy Pyrzak <guy.pyrzak@gmail.com>
+ */
+
+table.attachment_entry th {
+ text-align: right;
+ vertical-align: baseline;
+ white-space: nowrap;
+}
+
+table.attachment_entry td {
+ text-align: left;
+ vertical-align: baseline;
+ padding-bottom: 5px;
+}
+
+table#flags th,
+table#flags td {
+ text-align: left;
+ vertical-align: baseline;
+ font-size: small;
+}
+
+/* Rules used to view patches in diff mode. */
+
+.file_head {
+ font-weight: bold;
+ font-size: 1em;
+ background-color: #c3c3c3;
+ border: 1px solid black;
+}
+
+.file_head a {
+ text-decoration: none;
+ font-family: monospace;
+ font-size: 1.1em;
+}
+
+.file_collapse {
+ display: none;
+}
+
+.section_head {
+ background-color: #f0f0f0;
+ border: 1px solid black;
+ text-align: left;
+}
+
+table.file_table {
+ table-layout: fixed;
+ width: 100%;
+ empty-cells: show;
+ border-spacing: 0px;
+ border-collapse: collapse;
+ /* draw border below last open context section in listing */
+ border-bottom: 1px solid black;
+}
+
+tbody.file pre {
+ display: inline;
+ font-size: 0.9em;
+}
+
+tbody.file pre:empty {
+ display: block;
+}
+
+.changed {
+ background-color: lightblue;
+}
+
+.added {
+ background-color: lightgreen;
+}
+
+.removed {
+ background-color: #FFCC99;
+}
+
+.num {
+ background-color: #ffe9ae;
+ text-align:right;
+ padding: 0 0.3em;
+ width: 3em;
+}
+
+table.attachment_info th {
+ text-align: right;
+ vertical-align: top;
+}
+
+table.attachment_info td {
+ text-align: left;
+ vertical-align: top;
+}
+
+/* Text displayed when the attachment is not viewable by the web browser */
+#noview {
+ text-align: left;
+ vertical-align: middle;
+}
+
+#attachment_attributes div {
+ padding-bottom: 0.4em;
+}
+
+#attachment_attributes label,
+#attachment_attributes span.label,
+#attachment_actions span.label
+{
+ font-weight: bold;
+}
+
+#attachment_attributes .block {
+ display: block;
+}
+
+#smallCommentFrame, #attachment_flags {
+ float: left;
+}
+
+#smallCommentFrame {
+ margin-right: 1.5em;
+}
+
+#attachment_comments_and_flags, #attachment_actions {
+ clear: both;
+ margin-bottom: 1ex;
+}
+
+#attachment_information_read_only .title {
+ font-weight: bold;
+ font-size: 1.5em;
+ padding: 0;
+ margin: 0;
+}
+
+#attachment_information_read_only .title #bz_edit {
+ font-size: 0.7em;
+}
+
+#attachment_information_read_only .details {
+ font-size: 90%;
+}
+
+#attachment_info.read #attachment_information_edit {
+ display: none;
+}
+
+#attachment_info.edit #attachment_information_read_only {
+ display: none;
+}
+
+#attachment_info.edit #attachment_view_window {
+ float: left;
+ width: 80%;
+}
+
+#attachment_info.edit #attachment_information_edit {
+ width: 20%;
+}
+
+#attachment_info.edit #attachment_information_edit input.text,
+#attachment_info.edit #attachment_information_edit textarea {
+ width: 90%;
+}
+
+#attachment_isobsolete {
+ padding-right: 1em;
+}
+
+#attachment_information_edit {
+ float: left;
+}
+
+#smallCommentFrame textarea {
+ display: block;
+}
+
+textarea.bz_private {
+ border: 1px solid #F8C8BA;
+}
+
+#update {
+ clear: both;
+ display: block;
+}
+
+div#update_container {
+ clear: both;
+ padding: 1.5em 0;
+}
+
+#attachment_flags {
+ margin-bottom: 1em;
+}
+
+#attachment_flags p {
+ padding-bottom: 0;
+ margin-bottom: 0;
+}
+
+#editFrame, #viewDiffFrame, #viewFrame {
+ height: 400px;
+ width: 95%;
+ margin-left: 2%;
+}
+
+.viewall_frame {
+ width: 75%;
+ height: 350px;
+}
+
+.details span.bz_private{
+ border-left: 1px solid darkred;
+ padding-left: 0.5em;
+}
+
+.no_javascript .bz_hide, .no_javascript .bz_edit {
+ display: none;
+}
+
+#hidden_obsolete_message {
+ text-align: left;
+ width: 75%;
+ margin: 0 auto;
+ font-weight: bold
+}
+
+#description {
+ resize: vertical;
+}
diff --git a/Websites/bugs.webkit.org/skins/standard/buglist.css b/Websites/bugs.webkit.org/skins/standard/buglist.css
index 71206fc..ebebfb3 100644
--- a/Websites/bugs.webkit.org/skins/standard/buglist.css
+++ b/Websites/bugs.webkit.org/skins/standard/buglist.css
@@ -17,6 +17,42 @@
*
* Contributor(s): Myk Melez <myk@mozilla.org>
*/
+.bz_query_head {
+ text-align: center;
+}
+
+.bz_query_timestamp {
+ font-weight: bold;
+}
+
+.search_description {
+ margin: .5em 0;
+ padding: 0;
+}
+.search_description li {
+ list-style-type: none;
+ display: inline;
+ margin-right: 2em;
+}
+
+.zero_results, .zero_result_links {
+ font-size: 120%;
+ font-weight: bold;
+}
+
+.bz_buglist_header th {
+ text-align: left;
+}
+
+.bz_sort_order_primary,
+.bz_sort_order_secondary {
+ display: inline-block;
+ padding-left: .2em;
+ text-decoration: none;
+}
+.bz_sort_order_primary { color: black; }
+.bz_sort_order_secondary { color: #777; }
+
.bz_id_column {
}
@@ -44,14 +80,14 @@
/* we use a first-child class and not the pseudo-class because IE
* doesn't support it :-( */
-tr.bz_secure td.first-child {
+tr.bz_secure td.first-child, a.bz_secure {
background-image: url("../../images/padlock.png");
background-position: center left;
background-repeat: no-repeat;
background-color: inherit;
}
-th.first-child, td.first-child {
+th.first-child, td.first-child, a.bz_secure {
padding-left: 20px;
}
@@ -61,6 +97,33 @@
tr.bz_secure_mode_manual td.first-child {
}
+td.bz_estimated_time_column,
+td.bz_remaining_time_column,
+td.bz_actual_time_column,
+td.bz_percentage_complete_column {
+ text-align: right;
+}
+
+td.bz_total_label {
+ font-weight: bold;
+}
+
+td.bz_total {
+ border-top-style: solid;
+ border-top-color: #929bb1;
+ border-top-width: 3px;
+ text-align: right;
+}
+
#commit, #action {
margin-top: .25em;
}
+
+.bz_query_explain {
+ text-align: left;
+}
+
+.bz_short_desc_column a, .bz_short_short_desc_column a {
+ color: inherit;
+}
+
diff --git a/Websites/bugs.webkit.org/skins/standard/create_attachment.css b/Websites/bugs.webkit.org/skins/standard/create_attachment.css
deleted file mode 100644
index dcb1983..0000000
--- a/Websites/bugs.webkit.org/skins/standard/create_attachment.css
+++ /dev/null
@@ -1,36 +0,0 @@
- /* The contents of this file are subject to the Mozilla Public
- * License Version 1.1 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of
- * the License at http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS
- * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- * implied. See the License for the specific language governing
- * rights and limitations under the License.
- *
- * The Original Code is the Bugzilla Bug Tracking System.
- *
- * Contributor(s): Myk Melez <myk@mozilla.org>
- * Joel Peshkin <bugreport@peshkin.net>
- * Erik Stambaugh <erik@dasbistro.com>
- * Marc Schumann <wurblzap@gmail.com>
- */
-
-table.attachment_entry th {
- text-align: right;
- vertical-align: baseline;
- white-space: nowrap;
-}
-
-table.attachment_entry td {
- text-align: left;
- vertical-align: baseline;
- padding-bottom: 5px;
-}
-
-table#flags th,
-table#flags td {
- text-align: left;
- vertical-align: baseline;
- font-size: small;
-}
diff --git a/Websites/bugs.webkit.org/skins/standard/duplicates.css b/Websites/bugs.webkit.org/skins/standard/duplicates.css
index 9948b78..e89b72c 100644
--- a/Websites/bugs.webkit.org/skins/standard/duplicates.css
+++ b/Websites/bugs.webkit.org/skins/standard/duplicates.css
@@ -10,25 +10,40 @@
*
* The Original Code is the Bugzilla Bug Tracking System.
*
- * The Initial Developer of the Original Code is Netscape Communications
- * Corporation. Portions created by Netscape are
- * Copyright (C) 1998 Netscape Communications Corporation. All
- * Rights Reserved.
+ * The Initial Developer of the Original Code is Everything Solved, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
*
- * Contributor(s): Myk Melez <myk@mozilla.org>
+ * Contributor(s):
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
*/
-tree#results-tree {
- margin-right: 0px;
- border-right-width: 0px;
- margin-left: 0px;
- border-left-width: 0px;
+#duplicates_table {
+ border-collapse: collapse;
}
-treechildren:-moz-tree-cell-text(resolution-FIXED) {
- text-decoration: line-through;
+#duplicates_table .resolved {
+ background-color: #d9d9d9;
+ color: black;
}
-treecol#id_column { width: 6em; }
-treecol#duplicate_count_column { width: 5em; }
-treecol#duplicate_delta_column { width: 5em; }
+#duplicates_table thead tr {
+ background-color: #ccc;
+ color: black;
+}
+
+#duplicates_table thead tr th {
+ vertical-align: middle;
+}
+
+#duplicates_table td, #duplicates_table th {
+ border: 1px solid black;
+ padding: .1em .25em;
+}
+
+#duplicates_table tbody td {
+ text-align: center;
+}
+#duplicates_table tbody td.short_desc {
+ text-align: left;
+}
diff --git a/Websites/bugs.webkit.org/skins/standard/enter_bug.css b/Websites/bugs.webkit.org/skins/standard/enter_bug.css
new file mode 100644
index 0000000..88d9e9e
--- /dev/null
+++ b/Websites/bugs.webkit.org/skins/standard/enter_bug.css
@@ -0,0 +1,72 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Netscape Communications
+ * Corporation. Portions created by Netscape are Copyright (C) 1998
+ * Netscape Communications Corporation. All Rights Reserved.
+ *
+ * Contributor(s): Byron Jones <bugzilla@glob.com.au>
+ * Christian Reis <kiko@async.com.br>
+ * Vitaly Harisov <vitaly@rathedg.com>
+ * Svetlana Harisova <light@rathedg.com>
+ * Marc Schumann <wurblzap@gmail.com>
+ * Pascal Held <paheld@gmail.com>
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+/* These are specified using the class instead of the id so that they
+ don't override the YUI CSS. */
+.enter_bug_form table {
+ border-spacing: 0;
+ border-width: 0;
+}
+.enter_bug_form td, .enter_bug_form th { padding: .25em; }
+.enter_bug_form th { text-align: right; }
+
+/* This makes the "component" column as small as possible (since it
+ * contains only fixed-width content) and the Reporter column
+ * as large as possible, which makes the form not jump around
+ * when the Component Description changes size. This works
+ * pretty well on all browsers except IE 8.
+ */
+#Create #field_container_component { width: 1px; }
+#Create #field_container_reporter { width: 100%; }
+
+#Create .comment {
+ vertical-align: top;
+ overflow: auto;
+ color: green;
+}
+#Create #comp_desc_container td { padding: 0; }
+#Create #comp_desc { height: 11ex; }
+#Create #os_guess_note {
+ padding-top: 0;
+}
+#Create #os_guess_note div {
+ max-width: 35em;
+}
+
+/* Text inputs need to be a little shorter on enter_bug
+ * than the 100% that they are on show_bug.
+ */
+#Create .field_value .text_input { max-width: 50em; }
+
+/* The Possible Duplicates table on enter_bug. */
+#possible_duplicates th {
+ text-align: center;
+ background: none;
+ border-collapse: collapse;
+}
+/* Make the Add Me to CC button never wrap. */
+#possible_duplicates .yui-dt-col-update_token { white-space: nowrap; }
+
+form#Create #possible_duplicates td { vertical-align: middle; }
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/skins/standard/global.css b/Websites/bugs.webkit.org/skins/standard/global.css
index aca0ab5..0b28ff3 100644
--- a/Websites/bugs.webkit.org/skins/standard/global.css
+++ b/Websites/bugs.webkit.org/skins/standard/global.css
@@ -20,6 +20,7 @@
* Vitaly Harisov <vitaly@rathedg.com>
* Svetlana Harisova <light@rathedg.com>
* Marc Schumann <wurblzap@gmail.com>
+ * Pascal Held <paheld@gmail.com>
*/
/* global (begin) */
@@ -41,24 +42,36 @@
/* header (begin) */
#header {
margin-bottom: 1em;
- padding-bottom: 2px;
}
- #header form {
- font-size: 85%;
+ #header form, #header form input,
+ #footer form, #footer form input
+ {
+ font-size: 95%;
display: inline;
}
#header .links {
- font-size: 85%;
- border-left: 1px solid silver;
- border-right: 1px solid silver;
- border-bottom: 1px solid silver;
+ border-left: 1px solid #747E93;
+ border-right: 1px solid #747E93;
+ border-bottom: 1px solid #747E93;
-moz-border-radius-bottomleft: 5px;
-moz-border-radius-bottomright: 5px;
padding: 0.5em;
}
+ #lang_links_container {
+ float: right;
+ }
+ #lang_links_container .links {
+ border: none;
+ padding: .5em;
+ }
+
+ .lang_current {
+ font-weight: bold;
+ }
+
#message {
border: 1px solid red;
margin: 0.3em 0em;
@@ -66,6 +79,19 @@
color: green;
}
+ form.mini_login input.bz_login {
+ width: 10em;
+ }
+ form.mini_login input.bz_password {
+ width: 6em;
+ }
+ form.mini_login input.bz_remember {
+ margin: 0;
+ }
+ .bz_mini_login_help {
+ color: #777;
+ }
+
/* header (end) */
/* banner (begin) */
@@ -116,46 +142,40 @@
/* titles (end) */
-/* footer (begin) */
+/* footer (begin)
+ * See also the "header" section for styles that apply
+ * to both the header and footer.
+ */
#footer {
clear: both;
- margin-top: 5px;
+ margin-top: 1em;
width: 100%;
background: #edf2f2;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
- #footer form {
- display: inline;
- }
-
- #footer .btn,
- #footer .txt {
- font-size: 80%;
- }
-
#footer #useful-links {
- display: table;
padding-left: 1ex;
padding-right: 1ex;
}
- #footer #links-actions,
- #footer #links-saved,
- #footer #links-special {
- display: table-row;
+ #footer ul {
list-style-type: none;
}
+ #links-saved ul {
+ display: inline;
+ }
+ #links-saved th {
+ vertical-align: top;
+ }
#footer .label {
- display: table-cell;
white-space: nowrap;
vertical-align: top;
}
#footer .links {
- display: table-cell;
vertical-align: top;
}
/* footer (end) */
@@ -193,27 +213,36 @@
/* tabs (end) */
/* generic (begin) */
- :link {
+ a {
color: #039;
}
- :visited {
+ a:visited {
color: #636;
}
- :link:hover, :visited:hover {
+ a:hover {
color: #333;
}
- :link:active, :link:active {
+ a:active {
color: #000;
}
.clickable_area {
cursor: pointer;
}
+
+ textarea {
+ font-family: monospace;
+ }
/* generic (end) */
+/* Links that control whether or not something is visible. */
+a.controller {
+ font-size: 115%;
+}
+
div#docslinks {
float: right;
border: 1px solid black;
@@ -225,6 +254,18 @@
margin: 0;
}
+/**************************/
+/* Bug links and statuses */
+/**************************/
+
+.bz_bug_link {
+ /* Catch-all if you want common styles for all bug links */
+}
+
+.bz_bug_link .bz_status_UNCONFIRMED {
+ font-style: italic;
+}
+
.bz_obsolete {
text-decoration: line-through;
}
@@ -243,39 +284,82 @@
color: #a0a0a0;
}
+/************/
+/* Comments */
+/************/
+
+.bz_comment_table td {
+ vertical-align: top;
+}
+
.bz_comment {
margin-bottom: 2em;
}
-/* The rules for these classes make international text wrap correctly,
- even for languages like Japanese that have no spaces. */
-.bz_comment_text, .uneditable_textarea {
+/* tbody.file pre is for the Diff view of attachments. */
+.bz_comment_text, .uneditable_textarea, tbody.file pre {
font-family: monospace;
- /* Note that these must all be on separate lines or they stop
- working in Konqueror. */
- white-space: pre-wrap; /* CSS 3 & 2.1 */
- white-space: -moz-pre-wrap; /* Gecko */
- white-space: -pre-wrap; /* Opera 4-6 */
- white-space: -o-pre-wrap; /* Opera 7 */
+ white-space: pre-wrap;
}
.bz_comment_text {
width: 50em;
}
-.bz_first_comment {
+.bz_comment_user, .bz_comment_time, .bz_comment_number,
+.bz_private_checkbox, .bz_comment_actions
+{
+ margin: 0 .5em;
+}
+
+.bz_comment_actions, .bz_comment_number, .bz_private_checkbox {
+ float: right;
+}
+
+.bz_collapse_expand_comments {
+ padding: 0;
+ margin: 0 0 0 1em;
+ list-style-type: none;
+}
+.bz_collapse_expand_comments li {
+ margin-bottom: .5em;
+}
+.bz_collapse_comment {
+ text-decoration: none;
+}
+
+.bz_private_checkbox input {
+ margin: 0;
+ vertical-align: middle;
}
.bz_comment_head, .bz_first_comment_head {
+ padding-top: .1em;
+ padding-bottom: .1em;
+ padding-left: .5em;
background-color: #e0e0e0;
}
+
+.bz_comment_user_images img {
+ vertical-align: bottom;
+}
+
.bz_comment_hilite pre {
background-color: lightgreen;
margin: 0;
padding: 1em 0;
}
-span.quote {
+/** End Comments **/
+
+.bz_default_hidden, .bz_tui_hidden, .bz_hidden_field, .bz_hidden_option {
+ /* We have !important because we want elements with these classes to always
+ * be hidden, even if there is some CSS that overrides it (we use these
+ * classes inside JavaScript to hide things). */
+ display: none !important;
+}
+
+.bz_comment_text span.quote {
color: #65379c;
/* Make quoted text not wrap. */
white-space: pre;
@@ -291,10 +375,18 @@
min-width: 3em;
}
+input.requestee {
+ width: 15em;
+}
+
#error_msg {
font-size: x-large;
}
+.warning {
+ color: red;
+}
+
.throw_error {
background-color: #ff0000;
color: black;
@@ -304,7 +396,7 @@
}
dt {
- font-weight: bolder;
+ font-weight: bold;
}
body > dl > dt {
border-top: dotted gray thin;
@@ -317,10 +409,15 @@
white-space: normal !important;
}
+/* Arrow buttons are buttons with only ↑, ↓, ← or → on
+ * them. We want these to look a little less spidery. */
+.arrow_button {
+ font-size: 150%;
+}
+
/* Style of the attachment table and time tracking table */
#attachment_table {
border-collapse: collapse;
- width: 40em;
border: 1px solid #333333;
}
@@ -346,28 +443,6 @@
padding-left: 1em;
}
-table.attachment_info th {
- text-align: right;
- vertical-align: top;
-}
-
-table.attachment_info td {
- text-align: left;
- vertical-align: top;
-}
-
-/* Text displayed when the attachment is not viewable by the web browser */
-#noview {
- text-align: left;
- vertical-align: middle;
-}
-
-/* For bug fields */
-.uneditable_textarea {
- width: 30em;
- font-size: medium;
-}
-
div.user_match {
margin-bottom: 1em;
}
@@ -390,20 +465,51 @@
display: none;
}
+ div.bz_query_buttons {
+ display: none;
+ }
+
body {
background-image: none;
background-color: #fff;
}
}
+/**************/
+/* Bug Fields */
+/**************/
+
.field_label {
text-align: right;
vertical-align: top;
font-weight: bold;
}
+.field_help_link {
+ cursor: help;
+}
.field_value, form#Create th, form#Create td {
vertical-align: top;
}
+.field_value .text_input {
+ width: 100%;
+ min-width: 25em;
+}
+
+.uneditable_textarea {
+ width: 30em;
+ font-size: medium;
+}
+
+th.required:before {
+ content: "* ";
+}
+th.required:before, span.required_star {
+ color: red;
+}
+input.required, select.required, span.required_explanation {
+ background-color: #fff7cd;
+ color: #000;
+}
.calendar_button {
background: transparent url("global/calendar.png") no-repeat;
@@ -420,15 +526,43 @@
border: 1px solid #404D6C;
}
-form#Create th {
- text-align: right;
+.bug_urls {
+ margin: 0 0 1em 0;
+ padding: 0;
+ list-style-type: none;
}
-form#Create .comment {
- vertical-align: top;
+/* custom styles for inline instances of autocomplete input fields */
+.yui-skin-sam .yui-ac-input { position:static !important;
+ vertical-align:middle !important; }
+.yui-skin-sam .yui-ac-container { left:0px !important; }
+.yui-skin-sam .yui-ac { display: inline-block; }
+#bugzilla-body .yui-ac-content {
+ max-height: 19em;
overflow: auto;
- color: green;
- margin: 0 0.5em;
- padding: 0.3em;
- height: 8ex;
+ overflow-x: hidden;
+}
+
+#keyword_container {
+ padding-top: .2em;
+}
+
+
+#keyword_container .yui-ac-content {
+ margin-left: -1px;
+}
+
+/*******************/
+/* Form Validation */
+/*******************/
+
+.validation_error_text {
+ font-size: 120%;
+ color: #B70000;
+ font-weight: bold;
+}
+
+.validation_error_field, input.validation_error_field {
+ border: 2px solid #B70000;
+ background-color: #FFEBEB;
}
diff --git a/Websites/bugs.webkit.org/skins/standard/help.css b/Websites/bugs.webkit.org/skins/standard/help.css
deleted file mode 100644
index bc888ca..0000000
--- a/Websites/bugs.webkit.org/skins/standard/help.css
+++ /dev/null
@@ -1,41 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is the Bugzilla Bug Tracking System.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Gervase Markham <gerv@gerv.net>
- *
- * ***** END LICENSE BLOCK ***** */
-
-/* Help system */
-#helpDiv {
- border-style: solid;
- border-color: #F0A000;
- background-color: white;
- padding: 5px;
- position: absolute;
- z-index: 2;
-}
-
-#helpIframe {
- overflow: hidden;
- position: absolute;
- z-index: 1;
- display: none;
-}
diff --git a/Websites/bugs.webkit.org/skins/standard/index.css b/Websites/bugs.webkit.org/skins/standard/index.css
index 2b021f3..1e440cb 100644
--- a/Websites/bugs.webkit.org/skins/standard/index.css
+++ b/Websites/bugs.webkit.org/skins/standard/index.css
@@ -11,6 +11,7 @@
* The Original Code is the Bugzilla Bug Tracking System.
*
* Contributor(s): Vitaly Harisov <vitaly@rathedg.com>
+ * Guy Pyrzak <guy.pyrzak@gmail.com>
*/
/* index page (begin) */
@@ -18,51 +19,16 @@
#page-index
{
padding: 0.2em 0.2em 0.15em 0.2em;
+ max-width: 1000px;
}
- #page-index ul, #page-index li,
- #page-index p, #page-index form p
- {
- margin: 0;
- padding: 0;
- }
-
- #page-index ul
- {
- padding-bottom: 1em;
- }
-
- #page-index li
- {
- list-style: none;
- }
-
- #page-index p
- {
- padding-bottom: 0.5em;
+ /* By default these contain nothing, but these CSS rules make things
+ easier on customizers. */
+ .intro, .outro {
+ text-align: center;
}
/* Hide from NN4 */
- div#page-index .intro
- {
- width: 250px;
- height: 200px;
-
- margin-top: 2.3em;
- margin-right: 2.3em;
- float: right;
- background: transparent no-repeat url(index/front.png);
- }
-
- #page-index #report
- {
- padding-bottom: 1em;
- }
-
- #page-index #sidebar
- {
- padding-top: 1em;
- }
#new_release
{
@@ -82,4 +48,96 @@
{
font-weight: bold;
}
+
+ .bz_common_actions {
+ text-align: center;
+ }
+ .bz_common_actions ul {
+ list-style-type: none;
+ padding: 0;
+ }
+ .bz_common_actions ul li {
+ display: inline;
+ vertical-align: top;
+ }
+ .bz_common_actions ul li a {
+ display: inline-block;
+ height: 170px;
+ width: 145px;
+ margin: 0 2ex 2em 0;
+ }
+ .bz_common_actions ul li a span {
+ position: relative;
+ top: 90%;
+ font-weight: bold;
+ }
+ .bz_common_actions a,
+ .bz_common_actions a:visited,
+ .bz_common_actions a:hover {
+ text-decoration: none;
+ }
+ #enter_bug { background: url(index/file-a-bug.png) no-repeat; }
+ #query { background: url(index/search.png) no-repeat; }
+ #account {
+ background: url(index/new-account.png) no-repeat;
+ margin-right: 0;
+ }
+
+ #quicksearchForm
+ {
+ clear: both;
+ text-align: center;
+ margin-bottom: 2em;
+ }
+
+ #quicksearchForm #quicksearch_main
+ {
+ width: 27em;
+ }
+
+ #quicksearchForm
+ {
+ margin: 0;
+ padding: 0;
+ }
+
+ #page-index table{
+ border-collapse: collapse;
+ margin: auto;
+ }
+
+ #welcome
+ {
+ font-size: x-large;
+ font-weight: bold;
+ text-align: center;
+ margin: 0 0 0.8em 0;
+ padding: 0;
+ }
+
+ ul.additional_links
+ {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ }
+
+ ul#quicksearch_links{
+ margin-bottom: 1em;
+ }
+
+ ul.additional_links li
+ {
+ display: inline;
+ }
+
+ ul.additional_links li.bz_default_hidden
+ {
+ display: none;
+ }
+
+ input.quicksearch_help_text
+ {
+ color: #ccc;
+ }
/* index page (end) */
diff --git a/Websites/bugs.webkit.org/skins/standard/index/file-a-bug.png b/Websites/bugs.webkit.org/skins/standard/index/file-a-bug.png
new file mode 100644
index 0000000..cf4c941
--- /dev/null
+++ b/Websites/bugs.webkit.org/skins/standard/index/file-a-bug.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/skins/standard/index/front.png b/Websites/bugs.webkit.org/skins/standard/index/front.png
deleted file mode 100644
index a4ab61b..0000000
--- a/Websites/bugs.webkit.org/skins/standard/index/front.png
+++ /dev/null
Binary files differ
diff --git a/Websites/bugs.webkit.org/skins/standard/index/help.png b/Websites/bugs.webkit.org/skins/standard/index/help.png
new file mode 100644
index 0000000..6f9035b
--- /dev/null
+++ b/Websites/bugs.webkit.org/skins/standard/index/help.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/skins/standard/index/new-account.png b/Websites/bugs.webkit.org/skins/standard/index/new-account.png
new file mode 100644
index 0000000..4ad9ff2
--- /dev/null
+++ b/Websites/bugs.webkit.org/skins/standard/index/new-account.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/skins/standard/index/search.png b/Websites/bugs.webkit.org/skins/standard/index/search.png
new file mode 100644
index 0000000..8d33ebd
--- /dev/null
+++ b/Websites/bugs.webkit.org/skins/standard/index/search.png
Binary files differ
diff --git a/Websites/bugs.webkit.org/skins/standard/page.css b/Websites/bugs.webkit.org/skins/standard/page.css
new file mode 100644
index 0000000..da0c3be
--- /dev/null
+++ b/Websites/bugs.webkit.org/skins/standard/page.css
@@ -0,0 +1,103 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Initial Developer of the Original Code is Everything Solved.
+ * Portions created by Everything Solved are Copyright (C) 2006
+ * Everything Solved. All Rights Reserved.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+/* This CSS is used by various informational pages in the
+ template/en/default/pages/ directory. */
+
+#bugzilla-body {
+ padding: 0 1em;
+}
+
+#bugzilla-body > * {
+ /* People have an easier time reading narrower columns of text. */
+ max-width: 45em;
+}
+
+/*****************/
+/* Release Notes */
+/*****************/
+
+.req_new {
+ color: red;
+}
+
+.req_table {
+ border-collapse: collapse;
+}
+
+.req_table td, .req_table th {
+ border: 1px solid black;
+ padding: .25em;
+}
+
+/********************/
+/* QuickSearch Help */
+/********************/
+
+.qs_help li {
+ margin-top: 1ex;
+}
+
+.qs_fields th {
+ padding: 0 .25em;
+}
+.qs_fields th.field_nickname {
+ text-align: left;
+}
+.qs_fields td {
+ padding: .25em;
+ border-top: 1px solid gray;
+}
+.qs_fields .field_name {
+ width: 10em;
+}
+
+/***************/
+/* fields.html */
+/***************/
+
+table.field_value_explanation {
+ table-layout: fixed;
+ border-collapse: collapse;
+}
+
+.field_value_explanation thead h2 {
+ margin: 0;
+}
+
+.field_value_explanation .header_row td {
+ text-align: center;
+ font-size: 120%;
+ font-weight: bold;
+}
+
+.field_value_explanation tbody td {
+ border: 1px solid black;
+ padding: 1em;
+}
+
+.field_value_explanation dt,
+.field_descriptions dt
+{
+ margin-top: 1em;
+}
+
+.field_descriptions dt {
+ font-size: 120%;
+}
diff --git a/Websites/bugs.webkit.org/skins/standard/panel.css b/Websites/bugs.webkit.org/skins/standard/panel.css
deleted file mode 100644
index 23b5270..0000000
--- a/Websites/bugs.webkit.org/skins/standard/panel.css
+++ /dev/null
@@ -1,37 +0,0 @@
-body
- {
- font-family: sans-serif;
- font-size: 10pt;
- background-color: white;
- }
-
-ul
- {
- padding-left: 12px;
- }
-
-radio
- {
- -moz-user-select: ignore;
- }
-
-.text-link
- {
- margin-left: 3px;
- }
-
-.text-link:hover
- {
- text-decoration: underline;
- cursor: pointer;
- }
-
-.descriptive-content
- {
- color: #AAAAAA;
- }
-
-.descriptive-content[focused=true]
- {
- color: black;
- }
diff --git a/Websites/bugs.webkit.org/skins/standard/release-notes.css b/Websites/bugs.webkit.org/skins/standard/release-notes.css
deleted file mode 100644
index 51159ae..0000000
--- a/Websites/bugs.webkit.org/skins/standard/release-notes.css
+++ /dev/null
@@ -1,35 +0,0 @@
-/* The contents of this file are subject to the Mozilla Public
- * License Version 1.1 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of
- * the License at http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS
- * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- * implied. See the License for the specific language governing
- * rights and limitations under the License.
- *
- * The Initial Developer of the Original Code is Everything Solved.
- * Portions created by Everything Solved are Copyright (C) 2006
- * Everything Solved. All Rights Reserved.
- *
- * The Original Code is the Bugzilla Bug Tracking System.
- *
- * Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
- */
-
-#bugzilla-body {
- padding: 0 1em;
-}
-
-.req_new {
- color: red;
-}
-
-.req_table {
- border-collapse: collapse;
-}
-
-.req_table td, .req_table th {
- border: 1px solid black;
- padding: .25em;
-}
diff --git a/Websites/bugs.webkit.org/skins/standard/reports.css b/Websites/bugs.webkit.org/skins/standard/reports.css
new file mode 100644
index 0000000..00272fdb
--- /dev/null
+++ b/Websites/bugs.webkit.org/skins/standard/reports.css
@@ -0,0 +1,92 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Everything Solved,
+ * Inc. Portions created by the Initial Developer are Copyright (C)
+ * 2009 the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Max Kanat-Alexander <mkanat@bugzilla.org>
+ */
+
+/* describecomponents.cgi */
+
+#components_header_table {
+ margin-bottom: 1em;
+}
+
+.product_container {
+ width: 65%;
+}
+
+.product_name {
+ font-weight: bold;
+ font-size: 150%;
+ margin: 0;
+}
+
+.product_desc {
+ /* This is padding instead of margin because it looks better
+ * with the scrollbar. */
+ padding: 0 2em;
+ font-style: italic;
+ max-height: 5em;
+ overflow: auto;
+}
+
+.instructions {
+ font-weight: bold;
+ font-size: 105%;
+ padding-right: 1em;
+}
+
+.components_header {
+ margin: 0;
+ font-size: 140%;
+ font-weight: bold;
+}
+
+.component_table {
+ margin-top: -1em;
+ margin-left: 2em;
+}
+
+.component_table thead th {
+ padding-right: 1em;
+ vertical-align: bottom;
+ text-align: left;
+}
+
+.component_table td {
+ border-bottom: 1px dotted gray;
+}
+
+.component_table td.component_assignee,
+.component_table td.component_qa_contact
+{
+ border: none;
+ padding-top: .5em;
+}
+
+.component_name {
+ font-size: 115%;
+ font-weight: bold;
+ padding-right: 1em;
+ vertical-align: middle;
+ min-width: 8em;
+}
+
+.component_description {
+ padding-bottom: .5em;
+ color: #333;
+}
+
diff --git a/Websites/bugs.webkit.org/skins/standard/search_form.css b/Websites/bugs.webkit.org/skins/standard/search_form.css
new file mode 100644
index 0000000..509d87f
--- /dev/null
+++ b/Websites/bugs.webkit.org/skins/standard/search_form.css
@@ -0,0 +1,206 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Guy Pyrzak
+ * Portions created by the Initial Developer are Copyright (C) 2010 the
+ * Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Guy Pyrzak <guy.pyrzak@gmail.com>
+ */
+
+#summary_field {
+ padding: 1em;
+ margin: 1em;
+ border: 1px solid black;
+ background-color: #eee;
+ white-space: nowrap;
+}
+
+#bug_id_container {
+ display: inline-block;
+ vertical-align: middle;
+ padding-bottom: 1ex;
+}
+
+#bug_id_container input {
+ width: 9em;
+}
+
+.container_date_from,
+.container_date_to {
+ width: 14em;
+ padding-bottom: 1ex;
+}
+.container_date_from input,
+.container_date_to input {
+ width: 8em;
+}
+
+#bug_id_container input {
+ width: 9em;
+}
+
+#bug_id_type{
+ width: inherit;
+}
+
+.search_field_grid {
+ margin-top: 1em;
+ display: inline-block;
+}
+
+.search_field_grid .field_help_link,
+.history_query .field_help_link
+{
+ display: block;
+ text-align: left;
+}
+
+#chart .section_help {
+ font-size: 0.8em;
+ font-weight: normal
+}
+
+#bug_id_container .field_help {
+ font-size: 0.75em
+}
+
+.search_field_row {
+ white-space: nowrap;
+ margin-bottom: 0.5em;
+}
+
+.search_field_row .container_date_from, .search_field_row .container_date_to {
+ display: inline;
+}
+
+#summary_field.search_field_row {
+ display: block;
+}
+
+#summary_field.search_field_row input,
+#summary_field.search_field_row select
+{
+ display: inline;
+ padding-bottom: 0;
+ vertical-align: middle;
+}
+
+.search_field_row .field_label, #field_label_short_desc {
+ width: 14em;
+ display: inline-block;
+ line-height: 2em;
+ margin-right: 0.8em;
+}
+
+#field_label_short_desc {
+ text-align: right;
+}
+
+#summary_field.search_field_row {
+ width: inherit;
+}
+
+#keyword_container {
+ padding-bottom: 0;
+}
+
+.search_field_grid .field_label,
+.search_field_grid .field_label
+ {
+ display: block;
+ padding-bottom: 1ex;
+}
+
+.search_field_grid select {
+ width: 17em;
+ height: 15ex;
+}
+
+.search_field_grid, .search_field_row {
+ padding-left: 1.5em;
+}
+
+.search_email_fields {
+ display: inline-block;
+ width: 14.5em;
+ padding-left: 1.5em;
+}
+
+ul.bug_changes {
+ margin: 0;
+ padding: 0;
+}
+
+ul.bug_changes li {
+ display: inline-block;
+ width: 14.5em;
+ vertical-align: top;
+ padding-left: 1.5em;
+}
+
+ul.bug_changes select {
+ width: 15em;
+}
+
+ul.bug_changes li label {
+ display: block;
+}
+
+div.bz_section_title {
+ display: block;
+ margin-top: 2em;
+ font-size: 1.2em;
+}
+
+div.bz_section_title a {
+ font-weight: bold;
+}
+
+div.bz_section_title span {
+ font-size: 0.75em;
+ margin-left: 1em;
+}
+
+#summary_field label {
+ font-weight: bold;
+}
+
+#queryform, #reportform {
+ margin-bottom: 2em;
+}
+
+#knob {
+ margin-top: 2em;
+}
+
+.hide_people_filter #people_filter_section,
+.hide_history_filter #history_filter_section,
+.hide_detailed_information #detailed_information_section
+{
+ display: none;
+}
+
+.arrow {
+ display: inline;
+ width: 16px;
+ height: 16px;
+}
+
+.bz_search_section, ul.bz_search_section {
+ margin-top: 1em;
+}
+
+.bz_simple_search_form th {
+ text-align: right;
+}
diff --git a/Websites/bugs.webkit.org/skins/standard/show_bug.css b/Websites/bugs.webkit.org/skins/standard/show_bug.css
index 52d99a8..99c0b40 100644
--- a/Websites/bugs.webkit.org/skins/standard/show_bug.css
+++ b/Websites/bugs.webkit.org/skins/standard/show_bug.css
@@ -7,12 +7,26 @@
font-weight: bold;
}
-.bz_column_spacer {
- width: 2em;
+.bz_bug .edit_form {
+ width: 100%;
+}
+.bz_bug .edit_form table {
+ width: 100%;
+}
+.bz_bug #alias {
+ min-width: 0;
+ width: 10em;
}
-.bz_default_hidden {
- display: none;
+.flags_label {
+ text-align: left;
+}
+table#flags {
+ width: auto;
+}
+
+.bz_column_spacer {
+ width: 0.5em;
}
.related_actions {
@@ -36,9 +50,28 @@
height: 1em;
}
-#duplicate_settings, #votes_container {
+#duplicate_settings {
white-space: nowrap;
-
+}
+
+#bz_big_form_parts td {
+ vertical-align: top;
+}
+
+.bz_group_visibility_section {
+ margin-left: 1em;
+}
+
+.bz_group_visibility_section .instructions {
+ font-style: italic;
+}
+
+#bz_restrict_group_visibility_help .instructions {
+ margin-top: 0;
+}
+
+#bz_enable_role_visibility_help {
+ margin-top: 1em;
}
.bz_time_tracking_table {
@@ -61,6 +94,9 @@
.bz_time_tracking_table .bz_summarize_time {
text-align: right;
}
+.bz_time_tracking_table #deadline {
+ width: 7em;
+}
#summary tr td {
vertical-align:top;
@@ -70,6 +106,13 @@
margin-bottom: 3ex;
}
-#knob-buttons {
+.knob-buttons {
float: right;
}
+
+.text_input, .bz_userfield, #keyword_container {
+ width: 100%;
+}
+.bz_bug .bz_alias_short_desc_container {
+ width: inherit;
+}
diff --git a/Websites/bugs.webkit.org/skins/standard/yui/calendar.css b/Websites/bugs.webkit.org/skins/standard/yui/calendar.css
deleted file mode 100644
index 80886d5..0000000
--- a/Websites/bugs.webkit.org/skins/standard/yui/calendar.css
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
-Copyright (c) 2007, Yahoo! Inc. All rights reserved.
-Code licensed under the BSD License:
-http://developer.yahoo.net/yui/license.txt
-version: 2.3.1
-*/
-.yui-calcontainer{position:relative;float:left;_overflow:hidden;}.yui-calcontainer iframe{position:absolute;border:none;margin:0;padding:0;z-index:0;width:100%;height:100%;left:0px;top:0px;}.yui-calcontainer iframe.fixedsize{width:50em;height:50em;top:-1px;left:-1px;}.yui-calcontainer.multi .groupcal{z-index:1;float:left;position:relative;}.yui-calcontainer .title{position:relative;z-index:1;}.yui-calcontainer .close-icon{position:absolute;z-index:1;}.yui-calendar{position:relative;}.yui-calendar .calnavleft{position:absolute;z-index:1;}.yui-calendar .calnavright{position:absolute;z-index:1;}.yui-calendar .calheader{position:relative;width:100%;text-align:center;}.yui-calendar .calbody a:hover{background:inherit;}p#clear{clear:left;padding-top:10px;}.yui-skin-sam .yui-calcontainer{background-color:#f2f2f2;border:1px solid #808080;padding:10px;}.yui-skin-sam .yui-calcontainer.multi{padding:0 5px 0 5px;}.yui-skin-sam .yui-calcontainer.multi .groupcal{background-color:transparent;border:none;padding:10px 5px 10px 5px;margin:0;}.yui-skin-sam .yui-calcontainer .title{background:url(sprite.png) repeat-x 0 0;border-bottom:1px solid #cccccc;font:100% sans-serif;color:#000;font-weight:bold;height:auto;padding:.4em;margin:0 -10px 10px -10px;top:0;left:0;text-align:left;}.yui-skin-sam .yui-calcontainer.multi .title{margin:0 -5px 0 -5px;}.yui-skin-sam .yui-calcontainer.withtitle{padding-top:0;}.yui-skin-sam .yui-calcontainer .calclose{background:url(sprite.png) no-repeat 0 -300px;width:25px;height:15px;top:.4em;right:.4em;cursor:pointer;}.yui-skin-sam .yui-calendar{border-spacing:0;border-collapse:collapse;font:100% sans-serif;text-align:center;}.yui-skin-sam .yui-calendar .calhead{background:transparent;border:none;vertical-align:middle;}.yui-skin-sam .yui-calendar .calheader{background:transparent;font-weight:bold;padding:0 0 .6em 0;text-align:center;}.yui-skin-sam .yui-calendar .calheader img{border:none;}.yui-skin-sam .yui-calendar .calnavleft{background:url(sprite.png) no-repeat 0 -450px;width:25px;height:15px;top:0;bottom:0;left:-10px;margin-left:.4em;cursor:pointer;}.yui-skin-sam .yui-calendar .calnavright{background:url(sprite.png) no-repeat 0 -500px;width:25px;height:15px;top:0;bottom:0;right:-10px;margin-right:.4em;cursor:pointer;}.yui-skin-sam .yui-calendar .calweekdayrow{height:2em;}.yui-skin-sam .yui-calendar .calweekdaycell{color:#000;font-weight:bold;text-align:center;width:2em;}.yui-skin-sam .yui-calendar .calfoot{background-color:#f2f2f2;}.yui-skin-sam .yui-calendar .calrowhead,.yui-skin-sam .yui-calendar .calrowfoot{color:#a6a6a6;font-size:85%;font-style:normal;font-weight:normal;}.yui-skin-sam .yui-calendar .calrowhead{text-align:right;padding-right:2px;}.yui-skin-sam .yui-calendar .calrowfoot{text-align:left;padding-left:2px;}.yui-skin-sam .yui-calendar td.calcell{border:1px solid #cccccc;background:#fff;padding:1px;height:1.6em;line-height:1.6em;text-align:center;white-space:nowrap;}.yui-skin-sam .yui-calendar td.calcell a{color:#0066cc;display:block;height:100%;text-decoration:none;}.yui-skin-sam .yui-calendar td.calcell.today{background-color:#000;}.yui-skin-sam .yui-calendar td.calcell.today a{background-color:#fff;}.yui-skin-sam .yui-calendar td.calcell.oom{background-color:#cccccc;color:#a6a6a6;cursor:default;}.yui-skin-sam .yui-calendar td.calcell.selected{background-color:#fff;color:#000;}.yui-skin-sam .yui-calendar td.calcell.selected a{background-color:#b3d4ff;color:#000;}.yui-skin-sam .yui-calendar td.calcell.calcellhover{background-color:#426fd9;color:#fff;cursor:pointer;}.yui-skin-sam .yui-calendar td.calcell.calcellhover a{background-color:#426fd9;color:#fff;}.yui-skin-sam .yui-calendar td.calcell.previous{color:#e0e0e0;}.yui-skin-sam .yui-calendar td.calcell.restricted{text-decoration:line-through;}.yui-skin-sam .yui-calendar td.calcell.highlight1{background-color:#ccff99;}.yui-skin-sam .yui-calendar td.calcell.highlight2{background-color:#99ccff;}.yui-skin-sam .yui-calendar td.calcell.highlight3{background-color:#ffcccc;}.yui-skin-sam .yui-calendar td.calcell.highlight4{background-color:#ccff99;}
diff --git a/Websites/bugs.webkit.org/skins/standard/yui/sprite.png b/Websites/bugs.webkit.org/skins/standard/yui/sprite.png
deleted file mode 100644
index afd65e0..0000000
--- a/Websites/bugs.webkit.org/skins/standard/yui/sprite.png
+++ /dev/null
Binary files differ
diff --git a/Websites/bugs.webkit.org/summarize_time.cgi b/Websites/bugs.webkit.org/summarize_time.cgi
index 5ebb7e1..ca9b66b6 100755
--- a/Websites/bugs.webkit.org/summarize_time.cgi
+++ b/Websites/bugs.webkit.org/summarize_time.cgi
@@ -232,6 +232,20 @@
return $bugs;
}
+# Return 1st day of the month of the earliest activity date for a given list of bugs.
+sub get_earliest_activity_date {
+ my ($bugids) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my ($date) = $dbh->selectrow_array(
+ 'SELECT ' . $dbh->sql_date_format('MIN(bug_when)', '%Y-%m-01')
+ . ' FROM longdescs
+ WHERE ' . $dbh->sql_in('bug_id', $bugids)
+ . ' AND work_time > 0');
+
+ return $date;
+}
+
#
# Template code starts here
#
@@ -245,13 +259,13 @@
Bugzilla->switch_to_shadow_db();
-$user->in_group(Bugzilla->params->{"timetrackinggroup"})
+$user->is_timetracker
|| ThrowUserError("auth_failure", {group => "time-tracking",
action => "access",
object => "timetracking_summaries"});
-my @ids = split(",", $cgi->param('id'));
-map { ValidateBugID($_) } @ids;
+my @ids = split(",", $cgi->param('id') || '');
+@ids = map { Bugzilla::Bug->check($_)->id } @ids;
scalar(@ids) || ThrowUserError('no_bugs_chosen', {action => 'view'});
my $group_by = $cgi->param('group_by') || "number";
@@ -279,17 +293,17 @@
$start_date = trim $cgi->param('start_date');
$end_date = trim $cgi->param('end_date');
+ foreach my $date ($start_date, $end_date) {
+ next unless $date;
+ validate_date($date)
+ || ThrowUserError('illegal_date', {date => $date, format => 'YYYY-MM-DD'});
+ }
# Swap dates in case the user put an end_date before the start_date
if ($start_date && $end_date &&
str2time($start_date) > str2time($end_date)) {
$vars->{'warn_swap_dates'} = 1;
($start_date, $end_date) = ($end_date, $start_date);
}
- foreach my $date ($start_date, $end_date) {
- next unless $date;
- validate_date($date)
- || ThrowUserError('illegal_date', {date => $date, format => 'YYYY-MM-DD'});
- }
# Store dates in a session cookie so re-visiting the page
# for other bugs keeps them around.
@@ -301,16 +315,14 @@
# Break dates apart into months if necessary; if not, we use the
# same @parts list to allow us to use a common codepath.
if ($monthly) {
- # unfortunately it's not too easy to guess a start date, since
- # it depends on what bugs we're looking at. We risk bothering
- # the user here. XXX: perhaps run a query to see what the
- # earliest activity in longdescs for all bugs and use that as a
- # start date.
- $start_date || ThrowUserError("illegal_date", {'date' => $start_date});
- # we can, however, provide a default end date. Note that this
- # differs in semantics from the open-ended queries we use when
- # start/end_date aren't provided -- and clock skews will make
- # this evident!
+ # Calculate the earliest activity date if the user doesn't
+ # specify a start date.
+ if (!$start_date) {
+ $start_date = get_earliest_activity_date(\@bugs);
+ }
+ # Provide a default end date. Note that this differs in semantics
+ # from the open-ended queries we use when start/end_date aren't
+ # provided -- and clock skews will make this evident!
@parts = split_by_month($start_date,
$end_date || format_time(scalar localtime(time()), '%Y-%m-%d'));
} else {
diff --git a/Websites/bugs.webkit.org/t/001compile.t b/Websites/bugs.webkit.org/t/001compile.t
index 78fc6a6..97a339b 100644
--- a/Websites/bugs.webkit.org/t/001compile.t
+++ b/Websites/bugs.webkit.org/t/001compile.t
@@ -13,11 +13,11 @@
# The Original Code are the Bugzilla Tests.
#
# The Initial Developer of the Original Code is Zach Lipton
-# Portions created by Zach Lipton are
-# Copyright (C) 2001 Zach Lipton. All
-# Rights Reserved.
+# Portions created by Zach Lipton are Copyright (C) 2001 Zach Lipton.
+# All Rights Reserved.
#
# Contributor(s): Zach Lipton <zach@zachlipton.com>
+# Max Kanat-Alexander <mkanat@bugzilla.org>
#################
@@ -25,92 +25,82 @@
###Compilation###
use strict;
-
-use lib 't';
-
+use 5.008001;
+use lib qw(. lib t);
+use Config;
use Support::Files;
-
use Test::More tests => scalar(@Support::Files::testitems);
-# Need this to get the available driver information
-use DBI;
-my @DBI_drivers = DBI->available_drivers;
-
-# Bugzilla requires Perl 5.8.1 now. Checksetup will tell you this if you run it, but
-# it tests it in a polite/passive way that won't make it fail at compile time. We'll
-# slip in a compile-time failure if it's missing here so a tinderbox on < 5.8.1 won't
-# pass and mistakenly let people think Bugzilla works on any perl below 5.8.1.
-require 5.008001;
-
-# Capture the TESTOUT from Test::More or Test::Builder for printing errors.
-# This will handle verbosity for us automatically.
-my $fh;
-{
- local $^W = 0; # Don't complain about non-existent filehandles
- if (-e \*Test::More::TESTOUT) {
- $fh = \*Test::More::TESTOUT;
- } elsif (-e \*Test::Builder::TESTOUT) {
- $fh = \*Test::Builder::TESTOUT;
- } else {
- $fh = \*STDOUT;
- }
+BEGIN {
+ use_ok('Bugzilla::Constants');
+ use_ok('Bugzilla::Install::Requirements');
+ use_ok('Bugzilla');
}
-my @testitems = @Support::Files::testitems;
-my $perlapp = "\"$^X\"";
+sub compile_file {
+ my ($file) = @_;
-# Test the scripts by compiling them
+ # Don't allow CPAN.pm to modify the global @INC, which the version
+ # shipped with Perl 5.8.8 does. (It gets loaded by
+ # Bugzilla::Install::CPAN.)
+ local @INC = @INC;
-foreach my $file (@testitems) {
- $file =~ s/\s.*$//; # nuke everything after the first space (#comment)
- next if (!$file); # skip null entries
-
- # Skip mod_perl.pl in all cases. It doesn't compile correctly from the command line.
- if ($file eq 'mod_perl.pl') {
- ok(1, "Skipping mod_perl.pl");
- next;
+ if ($file =~ s/\.pm$//) {
+ $file =~ s{/}{::}g;
+ use_ok($file);
+ return;
}
- # Check that we have a DBI module to support the DB, if this is a database
- # module (but not Schema)
- if ($file =~ m#Bugzilla/DB/([^/]+)\.pm$# && $file ne "Bugzilla/DB/Schema.pm") {
- if (!grep(lc($_) =~ /$1/i, @DBI_drivers)) {
- ok(1,$file." - Skipping, as the DBD module not installed");
- next;
- }
- }
+ open(my $fh, $file);
+ my $bang = <$fh>;
+ close $fh;
- open (FILE,$file);
- my $bang = <FILE>;
- close (FILE);
my $T = "";
if ($bang =~ m/#!\S*perl\s+-.*T/) {
$T = "T";
}
- my $command = "$perlapp -c$T $file 2>&1";
- my $loginfo=`$command`;
- #print '@@'.$loginfo.'##';
- if ($loginfo =~ /syntax ok$/im) {
- # Special hack due to CPAN.pm on Windows with Cygwin installed throwing
- # strings of the form "Set up gcc environment - 3.4.4 (cygming special,
- # gdc 0.12, using dmd 0.125)". See bug 416047 for details.
- if ($^O =~ /MSWin32/i
- && grep($_ eq $file, 'install-module.pl', 'Bugzilla/Install/CPAN.pm'))
- {
- $loginfo =~ s/^Set up gcc environment.*?\n//;
- }
- if ($loginfo ne "$file syntax OK\n") {
- ok(0,$file." --WARNING");
- print $fh $loginfo;
- }
- else {
- ok(1,$file);
- }
+
+ my $libs = '';
+ if ($ENV{PERL5LIB}) {
+ $libs = join " ", map { "-I\"$_\"" } split /$Config{path_sep}/, $ENV{PERL5LIB};
}
- else {
- ok(0,$file." --ERROR");
- print $fh $loginfo;
+ my $perl = qq{"$^X"};
+ my $output = `$perl $libs -wc$T $file 2>&1`;
+ chomp($output);
+ my $return_val = $?;
+ $output =~ s/^\Q$file\E syntax OK$//ms;
+ diag($output) if $output;
+ ok(!$return_val, $file) or diag('--ERROR');
+}
+
+my @testitems = @Support::Files::testitems;
+my $file_features = map_files_to_features();
+
+# Test the scripts by compiling them
+foreach my $file (@testitems) {
+ # These were already compiled, above.
+ next if ($file eq 'Bugzilla.pm'
+ or $file eq 'Bugzilla/Constants.pm'
+ or $file eq 'Bugzilla/Install/Requirements.pm');
+ SKIP: {
+ if ($file eq 'mod_perl.pl') {
+ skip 'mod_perl.pl cannot be compiled from the command line', 1;
+ }
+ my $feature = $file_features->{$file};
+ if ($feature and !Bugzilla->feature($feature)) {
+ skip "$file: $feature not enabled", 1;
+ }
+
+ # Check that we have a DBI module to support the DB, if this
+ # is a database module (but not Schema)
+ if ($file =~ m{Bugzilla/DB/([^/]+)\.pm$}
+ and $file ne "Bugzilla/DB/Schema.pm")
+ {
+ my $module = lc($1);
+ my $dbd = DB_MODULE->{$module}->{dbd}->{module};
+ eval("use $dbd; 1") or skip "$file: $dbd not installed", 1;
+ }
+
+ compile_file($file);
}
}
-
-exit 0;
diff --git a/Websites/bugs.webkit.org/t/002goodperl.t b/Websites/bugs.webkit.org/t/002goodperl.t
index e9726cb..2fc0d4a 100644
--- a/Websites/bugs.webkit.org/t/002goodperl.t
+++ b/Websites/bugs.webkit.org/t/002goodperl.t
@@ -66,7 +66,7 @@
next;
}
- if ($file_line1 =~ m#^\#\!/usr/bin/perl\s#) {
+ if ($file_line1 =~ m#^\#\!/usr/bin/env perl\s#) {
if ($file_line1 =~ m#\s-$flags#) {
ok(1,"$file uses standard perl location and -$flags");
} else {
diff --git a/Websites/bugs.webkit.org/t/004template.t b/Websites/bugs.webkit.org/t/004template.t
index 31ce792..3b858c0 100644
--- a/Websites/bugs.webkit.org/t/004template.t
+++ b/Websites/bugs.webkit.org/t/004template.t
@@ -38,8 +38,7 @@
use File::Spec;
use Template;
-use Test::More tests => ( scalar(@referenced_files) * scalar(@languages)
- + $num_actual_files );
+use Test::More tests => ( scalar(@referenced_files) + $num_actual_files );
# Capture the TESTOUT from Test::More or Test::Builder for printing errors.
# This will handle verbosity for us automatically.
@@ -55,27 +54,17 @@
}
}
-# Checks whether one of the passed files exists
-sub existOnce {
- foreach my $file (@_) {
- return $file if -e $file;
- }
- return 0;
-}
+# Check to make sure all templates that are referenced in Bugzilla
+# exist in the proper place in the English template directory.
+# All other languages may or may not include any template as Bugzilla will
+# fall back to English if necessary.
-# Check to make sure all templates that are referenced in
-# Bugzilla exist in the proper place.
-
-foreach my $lang (@languages) {
- foreach my $file (@referenced_files) {
- my @path = map(File::Spec->catfile($_, $file),
- split(':', $include_path{$lang} . ":" . $include_path{"en"}));
- if (my $path = existOnce(@path)) {
- ok(1, "$path exists");
- } else {
- ok(0, "$file cannot be located --ERROR");
- print $fh "Looked in:\n " . join("\n ", @path) . "\n";
- }
+foreach my $file (@referenced_files) {
+ my $path = File::Spec->catfile($english_default_include_path, $file);
+ if (-e $path) {
+ ok(1, "$path exists");
+ } else {
+ ok(0, "$path cannot be located --ERROR");
}
}
@@ -117,19 +106,17 @@
foreach my $file (@{$actual_files{$include_path}}) {
my $path = File::Spec->catfile($include_path, $file);
- if (-e $path) {
- my ($data, $err) = $provider->fetch($file);
- if (!$err) {
- ok(1, "$file syntax ok");
- }
- else {
- ok(0, "$file has bad syntax --ERROR");
- print $fh $data . "\n";
- }
+ # These are actual files, so there's no need to check for existence.
+
+ my ($data, $err) = $provider->fetch($file);
+
+ if (!$err) {
+ ok(1, "$path syntax ok");
}
else {
- ok(1, "$path doesn't exist, skipping test");
+ ok(0, "$path has bad syntax --ERROR");
+ print $fh $data . "\n";
}
}
}
diff --git a/Websites/bugs.webkit.org/t/005no_tabs.t b/Websites/bugs.webkit.org/t/005whitespace.t
similarity index 66%
rename from Websites/bugs.webkit.org/t/005no_tabs.t
rename to Websites/bugs.webkit.org/t/005whitespace.t
index 75f5329..edba8b2 100644
--- a/Websites/bugs.webkit.org/t/005no_tabs.t
+++ b/Websites/bugs.webkit.org/t/005whitespace.t
@@ -19,6 +19,8 @@
#
# Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
# David D. Kilzer <ddkilzer@kilzer.net>
+# Colin Ogilvie <mozilla@colinogilvie.co.uk>
+# Marc Schumann <wurblzap@gmail.com>
#
#################
@@ -34,7 +36,7 @@
use File::Spec;
use Test::More tests => ( scalar(@Support::Files::testitems)
- + $Support::Templates::num_actual_files);
+ + $Support::Templates::num_actual_files) * 3;
my @testitems = @Support::Files::testitems;
for my $path (@Support::Templates::include_paths) {
@@ -42,9 +44,12 @@
Support::Templates::find_actual_files($path)));
}
+my %results;
+
foreach my $file (@testitems) {
open (FILE, "$file");
- if (grep /\t/, <FILE>) {
+ my @contents = <FILE>;
+ if (grep /\t/, @contents) {
ok(0, "$file contains tabs --WARNING");
} else {
ok(1, "$file has no tabs");
@@ -52,4 +57,26 @@
close (FILE);
}
+foreach my $file (@testitems) {
+ open (FILE, "$file");
+ my @contents = <FILE>;
+ if (grep /\r/, @contents) {
+ ok(0, "$file contains non-OS-conformant line endings --WARNING");
+ } else {
+ ok(1, "All line endings of $file are OS conformant");
+ }
+ close (FILE);
+}
+
+foreach my $file (@testitems) {
+ open (FILE, "$file");
+ my $first_line = <FILE>;
+ if ($first_line =~ /\xef\xbb\xbf/) {
+ ok(0, "$file contains Byte Order Mark --WARNING");
+ } else {
+ ok(1, "$file is free of a Byte Order Mark");
+ }
+ close (FILE);
+}
+
exit 0;
diff --git a/Websites/bugs.webkit.org/t/007util.t b/Websites/bugs.webkit.org/t/007util.t
index 18d58148..b32a1b9 100644
--- a/Websites/bugs.webkit.org/t/007util.t
+++ b/Websites/bugs.webkit.org/t/007util.t
@@ -13,12 +13,12 @@
# The Original Code are the Bugzilla Tests.
#
# The Initial Developer of the Original Code is Zach Lipton
-# Portions created by Zach Lipton are
-# Copyright (C) 2002 Zach Lipton. All
-# Rights Reserved.
+# Portions created by Zach Lipton are Copyright (C) 2002 Zach Lipton.
+# All Rights Reserved.
#
# Contributor(s): Zach Lipton <zach@zachlipton.com>
-
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+# Frédéric Buclin <LpSolit@gmail.com>
#################
#Bugzilla Test 7#
@@ -26,39 +26,62 @@
use lib 't';
use Support::Files;
+use Test::More tests => 15;
BEGIN {
- use Test::More tests => 12;
- use_ok(Bugzilla);
- use_ok(Bugzilla::Util);
+ use_ok(Bugzilla);
+ use_ok(Bugzilla::Util);
}
-# We need to override Bugzilla->params so we can get an expected value when
-# Bugzilla::Util::format_time() calls ask for the 'timezone' parameter.
-# This will also prevent the tests from failing on site that do not have a
-# data/params file containing 'timezone' yet.
-Bugzilla->params->{'timezone'} = "TEST";
+# We need to override user preferences so we can get an expected value when
+# Bugzilla::Util::format_time() calls ask for the 'timezone' user preference.
+Bugzilla->user->{'settings'}->{'timezone'}->{'value'} = "local";
+
+# We need to know the local timezone for the date chosen in our tests.
+# Below, tests are run against Nov. 24, 2002.
+my $tz = Bugzilla->local_timezone->short_name_for_datetime(DateTime->new(year => 2002, month => 11, day => 24));
# we don't test the taint functions since that's going to take some more work.
# XXX: test taint functions
#html_quote():
-is(html_quote("<lala&>"),"<lala&>",'html_quote');
+is(html_quote("<lala&@>"),"<lala&@>",'html_quote');
#url_quote():
is(url_quote("<lala&>gaa\"'[]{\\"),"%3Clala%26%3Egaa%22%27%5B%5D%7B%5C",'url_quote');
-#lsearch():
-my @list = ('apple','pear','plum','<"\\%');
-is(lsearch(\@list,'pear'),1,'lsearch 1');
-is(lsearch(\@list,'<"\\%'),3,'lsearch 2');
-is(lsearch(\@list,'kiwi'),-1,'lsearch 3 (missing item)');
-
#trim():
is(trim(" fg<*\$%>+=~~ "),'fg<*$%>+=~~','trim()');
#format_time();
-is(format_time("2002.11.24 00:05"),'2002-11-24 00:05 TEST','format_time("2002.11.24 00:05")');
-is(format_time("2002.11.24 00:05:56"),'2002-11-24 00:05:56 TEST','format_time("2002.11.24 00:05:56")');
+is(format_time("2002.11.24 00:05"), "2002-11-24 00:05 $tz",'format_time("2002.11.24 00:05") is ' . format_time("2002.11.24 00:05"));
+is(format_time("2002.11.24 00:05:56"), "2002-11-24 00:05:56 $tz",'format_time("2002.11.24 00:05:56")');
is(format_time("2002.11.24 00:05:56", "%Y-%m-%d %R"), '2002-11-24 00:05', 'format_time("2002.11.24 00:05:56", "%Y-%m-%d %R") (with no timezone)');
-is(format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z"), '2002-11-24 00:05 TEST', 'format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z") (with timezone)');
+is(format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z"), "2002-11-24 00:05 $tz", 'format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z") (with timezone)');
+
+# email_filter
+my %email_strings = (
+ 'somebody@somewhere.com' => 'somebody',
+ 'Somebody <somebody@somewhere.com>' => 'Somebody <somebody>',
+ 'One Person <one@person.com>, Two Person <two@person.com>'
+ => 'One Person <one>, Two Person <two>',
+ 'This string contains somebody@somewhere.com and also this@that.com'
+ => 'This string contains somebody and also this',
+);
+foreach my $input (keys %email_strings) {
+ is(Bugzilla::Util::email_filter($input), $email_strings{$input},
+ "email_filter('$input')");
+}
+
+# diff_arrays():
+my @old_array = qw(alpha beta alpha gamma gamma beta alpha delta epsilon gamma);
+my @new_array = qw(alpha alpha beta gamma epsilon delta beta delta);
+# The order is not relevant when comparing both arrays for matching items,
+# i.e. (foo bar) and (bar foo) are the same arrays (same items).
+# But when returning data, we try to respect the initial order.
+# We remove the leftmost items first, and return what's left. This means:
+# Removed (in this order): gamma alpha gamma.
+# Added (in this order): delta
+my ($removed, $added) = diff_arrays(\@old_array, \@new_array);
+is_deeply($removed, [qw(gamma alpha gamma)], 'diff_array(\@old, \@new) (check removal)');
+is_deeply($added, [qw(delta)], 'diff_array(\@old, \@new) (check addition)');
diff --git a/Websites/bugs.webkit.org/t/008filter.t b/Websites/bugs.webkit.org/t/008filter.t
index 9a53ced..e73d238 100644
--- a/Websites/bugs.webkit.org/t/008filter.t
+++ b/Websites/bugs.webkit.org/t/008filter.t
@@ -30,10 +30,11 @@
# Sample exploit code: '>"><script>alert('Oh dear...')</script>
use strict;
-use lib 't';
+use lib qw(. lib t);
use vars qw(%safe);
+use Bugzilla::Constants;
use Support::Templates;
use File::Spec;
use Test::More tests => $Support::Templates::num_actual_files;
@@ -45,7 +46,7 @@
$/ = undef;
foreach my $path (@Support::Templates::include_paths) {
- $path =~ s|\\|/|g if $^O eq 'MSWin32'; # convert \ to / in path if on windows
+ $path =~ s|\\|/|g if ON_WINDOWS; # convert \ to / in path if on windows
$path =~ m|template/([^/]+)/([^/]+)|;
my $lang = $1;
my $flavor = $2;
@@ -60,13 +61,9 @@
chdir $path; # relative path
# We load a %safe list of acceptable exceptions.
- if (!-r "filterexceptions.pl") {
- ok(0, "$path has templates but no filterexceptions.pl file. --ERROR");
- next;
- }
- else {
+ if (-r "filterexceptions.pl") {
do "filterexceptions.pl";
- if ($^O eq 'MSWin32') {
+ if (ON_WINDOWS) {
# filterexceptions.pl uses / separated paths, while
# find_actual_files returns \ separated ones on Windows.
# Here, we convert the filter exception hash to use \.
@@ -85,17 +82,19 @@
# us to flag which members were not found, and report that as a warning,
# thereby keeping the lists clean.
foreach my $file (keys %safe) {
- my $list = $safe{$file};
- $safe{$file} = {};
- foreach my $directive (@$list) {
- $safe{$file}{$directive} = 0;
+ if (ref $safe{$file} eq 'ARRAY') {
+ my $list = $safe{$file};
+ $safe{$file} = {};
+ foreach my $directive (@$list) {
+ $safe{$file}{$directive} = 0;
+ }
}
}
foreach my $file (@testitems) {
# There are some files we don't check, because there is no need to
# filter their contents due to their content-type.
- if ($file =~ /\.(txt|png)\.tmpl$/) {
+ if ($file =~ /\.(pm|txt|png)\.tmpl$/) {
ok(1, "($lang/$flavor) $file is filter-safe");
next;
}
@@ -109,7 +108,7 @@
# /g means we execute this loop for every match
# /s means we ignore linefeeds in the regexp matches
- while ($slurp =~ /\[%(.*?)%\]/gs) {
+ while ($slurp =~ /\[%(?:-|\+|~|=)?(.*?)(?:-|\+|~|=)?%\]/gs) {
my $directive = $1;
my @lineno = ($` =~ m/\n/gs);
@@ -154,11 +153,11 @@
my ($file, $directive) = @_;
# Comments
- return 1 if $directive =~ /^[+-]?#/;
+ return 1 if $directive =~ /^#/;
- # Remove any leading/trailing + or - and whitespace.
- $directive =~ s/^[+-]?\s*//;
- $directive =~ s/\s*[+-]?$//;
+ # Remove any leading/trailing whitespace.
+ $directive =~ s/^\s*//;
+ $directive =~ s/\s*$//;
# Empty directives are ok; they are usually line break helpers
return 1 if $directive eq '';
@@ -211,7 +210,7 @@
return 1 if $directive =~ /^(time2str|url)\(/;
# Safe Template Toolkit virtual methods
- return 1 if $directive =~ /\.(length$|size$|push\(|delete\()/;
+ return 1 if $directive =~ /\.(length$|size$|push\(|unshift\(|delete\()/;
# Special Template Toolkit loop variable
return 1 if $directive =~ /^loop\.(index|count)$/;
@@ -222,10 +221,10 @@
# Things which are already filtered
# Note: If a single directive prints two things, and only one is
# filtered, we may not catch that case.
- return 1 if $directive =~ /FILTER\ (html|csv|js|base64|url_quote|css_class_quote|
- ics|quoteUrls|time|uri|xml|lower|html_light|
+ return 1 if $directive =~ /FILTER\ (html|csv|js|base64|css_class_quote|ics|
+ quoteUrls|time|uri|xml|lower|html_light|
obsolete|inactive|closed|unitconvert|
- txt|none)\b/x;
+ txt|html_linebreak|none)\b/x;
return 0;
}
diff --git a/Websites/bugs.webkit.org/t/009bugwords.t b/Websites/bugs.webkit.org/t/009bugwords.t
index 8abbe16..242ac47 100644
--- a/Websites/bugs.webkit.org/t/009bugwords.t
+++ b/Websites/bugs.webkit.org/t/009bugwords.t
@@ -68,7 +68,7 @@
my $lineno = scalar(@lineno) + 1;
# "a bug", "bug", "bugs"
- if (grep /(a?[\s>]bugs?[\s.:;,])/i, $text) {
+ if (grep /(a?[\s>]bugs?[\s.:;,<])/i, $text) {
# Exclude variable assignment.
unless (grep /bugs =/, $text) {
push(@errors, [$lineno, $text]);
diff --git a/Websites/bugs.webkit.org/t/010dependencies.t b/Websites/bugs.webkit.org/t/010dependencies.t
index 977c8aa..3289d09 100644
--- a/Websites/bugs.webkit.org/t/010dependencies.t
+++ b/Websites/bugs.webkit.org/t/010dependencies.t
@@ -21,8 +21,7 @@
## dependencies ##
use strict;
-
-use lib 't';
+use lib qw(. lib t);
use Support::Files;
use Test::More qw(no_plan);
@@ -30,6 +29,16 @@
my %mods;
my %deps;
+use constant MODULE_REGEX => qr/
+ (?:(?:^\s*use)
+ |
+ (?:^require)
+ )\s+
+ ['"]?
+ ([\w:\.\\]+)
+/x;
+use constant BASE_REGEX => qr/^use base qw\(([^\)]+)/;
+
# Extract all Perl modules.
foreach my $file (@Support::Files::testitems) {
if ($file =~ /^(.*)\.pm$/) {
@@ -58,18 +67,19 @@
if ($line =~ /^package\s+([^;]);/) {
$module = $1;
}
- elsif ($line =~ /^\s*(?:use|^require) *"?(Bugzilla.*?)"?(?:;|\s+qw[\(\{]|\s+\(\))/) {
- my $used = $1;
- $used =~ s#/#::#g;
- $used =~ s#\.pm$##;
- $used =~ s#\$module#[^:]+#;
- $used =~ s#\${[^}]+}#[^:]+#;
- $used =~ s#[" ]##g;
- my $exclude = "";
- if ($used eq 'Bugzilla::Auth::Login::[^:]+' ) { $exclude = 'Bugzilla::Auth::Login::Stack' }
- elsif ($used eq 'Bugzilla::Auth::Verify::[^:]+') { $exclude = 'Bugzilla::Auth::Verify::Stack' }
- elsif ($used eq 'Bugzilla::Config::[^:]+' ) { $exclude = 'Bugzilla::Config::Common' }
- push(@use, grep(/^$used$/, grep(!/^$exclude$/, keys %mods)));
+ elsif ($line =~ BASE_REGEX or $line =~ MODULE_REGEX) {
+ my $used_string = $1;
+ # "use base" can have multiple modules
+ my @used_array = split(/\s+/, $used_string);
+ foreach my $used (@used_array) {
+ next if $used !~ /^Bugzilla/;
+ $used =~ s#/#::#g;
+ $used =~ s#\.pm$##;
+ $used =~ s#\$module#[^:]+#;
+ $used =~ s#\${[^}]+}#[^:]+#;
+ $used =~ s#[" ]##g;
+ push(@use, grep(/^\Q$used\E$/, keys %mods));
+ }
}
}
close (SOURCE);
diff --git a/Websites/bugs.webkit.org/t/012throwables.t b/Websites/bugs.webkit.org/t/012throwables.t
index b846ab9..3738ad5 100644
--- a/Websites/bugs.webkit.org/t/012throwables.t
+++ b/Websites/bugs.webkit.org/t/012throwables.t
@@ -28,9 +28,9 @@
######Errors######
use strict;
+use lib qw(. lib t);
-use lib 't';
-
+use Bugzilla::Constants;
use Bugzilla::WebService::Constants;
use File::Spec;
@@ -60,7 +60,7 @@
foreach my $path (@{$actual_files{$include_path}}) {
my $file = File::Spec->catfile($include_path, $path);
$file =~ s/\s.*$//; # nuke everything after the first space
- $file =~ s|\\|/|g if $^O eq 'MSWin32'; # convert \ to / in path if on windows
+ $file =~ s|\\|/|g if ON_WINDOWS; # convert \ to / in path if on windows
$test_templates{$file} = ()
if $file =~ m#global/(code|user)-error\.html\.tmpl#;
}
@@ -117,7 +117,7 @@
# Bugzilla/Error.pm)
$lineno++;
if ($line =~
-/^[^#]*(Throw(Code|User)Error|error\s+=>)\s*\(?\s*["'](.*?)['"]/) {
+/^[^#]*\b(Throw(Code|User)Error|(user_)?error\s+=>)\s*\(?\s*["'](.*?)['"]/) {
my $errtype;
# If it's a normal ThrowCode/UserError
if ($2) {
@@ -125,9 +125,9 @@
}
# If it's an AUTH_ERROR tag
else {
- $errtype = 'code';
+ $errtype = $3 ? 'user' : 'code';
}
- my $errtag = $3;
+ my $errtag = $4;
push @{$Errors{$errtype}{$errtag}{used_in}{$file}}, $lineno;
}
}
diff --git a/Websites/bugs.webkit.org/t/Support/Files.pm b/Websites/bugs.webkit.org/t/Support/Files.pm
index dc60687..6c6e0ee 100644
--- a/Websites/bugs.webkit.org/t/Support/Files.pm
+++ b/Websites/bugs.webkit.org/t/Support/Files.pm
@@ -25,43 +25,15 @@
use File::Find;
-# exclude_deps is a hash of arrays listing the files to be excluded
-# if a module is not available
-#
@additional_files = ();
-%exclude_deps = (
- 'XML::Twig' => ['importxml.pl'],
- 'Net::LDAP' => ['Bugzilla/Auth/Verify/LDAP.pm'],
- 'Authen::Radius' => ['Bugzilla/Auth/Verify/RADIUS.pm'],
- 'Email::Reply' => ['email_in.pl'],
- 'Email::MIME::Attachment::Stripper' => ['email_in.pl']
-);
-
@files = glob('*');
find(sub { push(@files, $File::Find::name) if $_ =~ /\.pm$/;}, 'Bugzilla');
-
-sub have_pkg {
- my ($pkg) = @_;
- my ($msg, $vnum, $vstr);
- no strict 'refs';
- eval { my $p; ($p = $pkg . ".pm") =~ s!::!/!g; require $p; };
- return !($@);
-}
-
-@exclude_files = ();
-foreach $dep (keys(%exclude_deps)) {
- if (!have_pkg($dep)) {
- push @exclude_files, @{$exclude_deps{$dep}};
- }
-}
+push(@files, 'extensions/create.pl');
sub isTestingFile {
my ($file) = @_;
my $exclude;
- foreach $exclude (@exclude_files) {
- if ($file eq $exclude) { return undef; } # get rid of excluded files.
- }
if ($file =~ /\.cgi$|\.pl$|\.pm$/) {
return 1;
diff --git a/Websites/bugs.webkit.org/t/Support/Templates.pm b/Websites/bugs.webkit.org/t/Support/Templates.pm
index 40e16f1..81dc8cc 100644
--- a/Websites/bugs.webkit.org/t/Support/Templates.pm
+++ b/Websites/bugs.webkit.org/t/Support/Templates.pm
@@ -29,11 +29,14 @@
use lib 't';
use base qw(Exporter);
@Support::Templates::EXPORT =
- qw(@languages @include_paths %include_path @referenced_files
- %actual_files $num_actual_files);
-use vars qw(@languages @include_paths %include_path @referenced_files
- %actual_files $num_actual_files);
+ qw(@languages @include_paths $english_default_include_path
+ %include_path @referenced_files %actual_files $num_actual_files);
+use vars qw(@languages @include_paths $english_default_include_path
+ %include_path @referenced_files %actual_files $num_actual_files);
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(template_include_path);
use Support::Files;
use File::Find;
@@ -48,6 +51,10 @@
# All include paths
@include_paths = ();
+# English default include path
+$english_default_include_path =
+ File::Spec->catdir(bz_locations()->{'templatedir'}, 'en', 'default');
+
# Files which are referenced in the cgi files
@referenced_files = ();
@@ -57,30 +64,9 @@
# total number of actual_files
$num_actual_files = 0;
-# Scan for the template available languages and include paths
-{
- opendir(DIR, "template") || die "Can't open 'template': $!";
- my @files = grep { /^[a-z-]+$/i } readdir(DIR);
- closedir DIR;
-
- foreach my $langdir (@files) {
- next if($langdir =~ /^CVS$/i);
-
- my $path = File::Spec->catdir('template', $langdir, 'custom');
- my @dirs = ();
- push(@dirs, $path) if(-d $path);
- $path = File::Spec->catdir('template', $langdir, 'extension');
- push(@dirs, $path) if(-d $path);
- $path = File::Spec->catdir('template', $langdir, 'default');
- push(@dirs, $path) if(-d $path);
-
- next if(scalar(@dirs) == 0);
- push(@languages, $langdir);
- push(@include_paths, @dirs);
- $include_path{$langdir} = join(":",@dirs);
- }
-}
-
+# Set the template available languages and include paths
+@languages = @{ Bugzilla->languages };
+@include_paths = @{ template_include_path({ language => Bugzilla->languages }) };
my @files;
diff --git a/Websites/bugs.webkit.org/template/.cvsignore b/Websites/bugs.webkit.org/template/.cvsignore
deleted file mode 100644
index 2e42e12..0000000
--- a/Websites/bugs.webkit.org/template/.cvsignore
+++ /dev/null
@@ -1,3 +0,0 @@
-.htaccess
-de
-es
diff --git a/Websites/bugs.webkit.org/template/en/.cvsignore b/Websites/bugs.webkit.org/template/en/.cvsignore
deleted file mode 100644
index 73b2352..0000000
--- a/Websites/bugs.webkit.org/template/en/.cvsignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.htaccess
-custom
diff --git a/Websites/bugs.webkit.org/template/en/custom/attachment/content-types.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/attachment/content-types.html.tmpl
deleted file mode 100644
index 297ba89..0000000
--- a/Websites/bugs.webkit.org/template/en/custom/attachment/content-types.html.tmpl
+++ /dev/null
@@ -1,31 +0,0 @@
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Myk Melez <myk@mozilla.org>
- #%]
-
- <option value="text/plain">plain text (text/plain)</option>
- <option value="text/html">HTML source (text/html)</option>
-[%# if WEBKIT_CHANGES %]
- <option value="application/xhtml+xml">XHTML source (application/xhtml+xml)</option>
- <option value="image/svg+xml">SVG image (image/svg+xml)</option>
-[%# endif // WEBKIT_CHANGES %]
- <option value="application/xml">XML source (application/xml)</option>
- <option value="image/gif">GIF image (image/gif)</option>
- <option value="image/jpeg">JPEG image (image/jpeg)</option>
- <option value="image/png">PNG image (image/png)</option>
- <option value="application/octet-stream">binary file (application/octet-stream)</option>
diff --git a/Websites/bugs.webkit.org/template/en/custom/attachment/create.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/attachment/create.html.tmpl
index 6bf7c74..63d13dc 100644
--- a/Websites/bugs.webkit.org/template/en/custom/attachment/create.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/attachment/create.html.tmpl
@@ -26,20 +26,28 @@
[%# Define strings that will serve as the title and header of this page %]
[% title = BLOCK %]Create New Attachment for [% terms.Bug %] #[% bug.bug_id %][% END %]
[% header = BLOCK %]Create New Attachment for
- [%+ "$terms.Bug $bug.bug_id" FILTER bug_link(bug.bug_id) FILTER none %][% END %]
+ [%+ "$terms.Bug $bug.bug_id" FILTER bug_link(bug) FILTER none %][% END %]
[% subheader = BLOCK %][% bug.short_desc FILTER html %][% END %]
[% PROCESS global/header.html.tmpl
title = title
header = header
subheader = subheader
- onload="setContentTypeDisabledState(document.entryform);"
- style_urls = [ 'skins/standard/create_attachment.css' ]
- javascript_urls = [ "js/attachment.js" ]
+ style_urls = [ 'skins/standard/attachment.css' ]
+ yui = [ 'autocomplete' ]
+ javascript_urls = [ "js/attachment.js", 'js/field.js', "js/util.js", "js/TUI.js" ]
doc_section = "attachments.html"
%]
-<form name="entryform" method="post" action="attachment.cgi" enctype="multipart/form-data">
+<script type="text/javascript">
+<!--
+TUI_hide_default('attachment_text_field');
+-->
+</script>
+
+<form name="entryform" method="post" action="attachment.cgi"
+ enctype="multipart/form-data"
+ onsubmit="return validateAttachmentForm(this)">
<input type="hidden" name="bugid" value="[% bug.bug_id %]">
<input type="hidden" name="action" value="insert">
<input type="hidden" name="token" value="[% token FILTER html %]">
@@ -54,8 +62,7 @@
<em>(optional) Check each existing attachment made obsolete by your new attachment.</em><br>
[% IF attachments.size %]
[% FOREACH attachment = attachments %]
- [% IF ((attachment.isprivate == 0) || (Param("insidergroup")
- && user.in_group(Param("insidergroup")))) %]
+ [% IF ((attachment.isprivate == 0) || user.is_insider) %]
<input type="checkbox" id="[% attachment.id %]"
name="obsolete" value="[% attachment.id %]">
<a href="attachment.cgi?id=[% attachment.id %]&action=edit">[% attachment.id %]: [% attachment.description FILTER html %]</a><br>
@@ -77,16 +84,17 @@
<label for="takebug">take [% terms.bug %]</label>
[% bug_statuses = [] %]
[% FOREACH bug_status = bug.status.can_change_to %]
- [% NEXT IF bug_status.name == "UNCONFIRMED" && !bug.product_obj.votes_to_confirm %]
+ [% NEXT IF bug_status.name == "UNCONFIRMED"
+ && !bug.product_obj.allows_unconfirmed %]
[% bug_statuses.push(bug_status) IF bug_status.is_open %]
[% END %]
[% IF bug_statuses.size %]
<label for="takebug">and set the [% terms.bug %] status to</label>
<select id="bug_status" name="bug_status">
- <option value="[% bug.status.name FILTER html %]">[% get_status(bug.status.name) FILTER html %] (current)</option>
+ <option value="[% bug.status.name FILTER html %]">[% display_value("bug_status", bug.status.name) FILTER html %] (current)</option>
[% FOREACH bug_status = bug_statuses %]
[% NEXT IF bug_status.id == bug.status.id %]
- <option value="[% bug_status.name FILTER html %]">[% get_status(bug_status.name) FILTER html %]</option>
+ <option value="[% bug_status.name FILTER html %]">[% display_value("bug_status", bug_status.name) FILTER html %]</option>
[% END %]
</select>
[% END %]
@@ -107,17 +115,22 @@
%]
</td>
</tr>
- [% IF (Param("insidergroup") && user.in_group(Param("insidergroup"))) %]
+ [% IF user.is_insider %]
<tr>
<th>Privacy:</th>
<td>
- <em>If the attachment is private, check the box below.</em><br>
<input type="checkbox" name="isprivate" id="isprivate"
value="1" onClick="updateCommentPrivacy(this)">
- <label for="isprivate">Private</label>
+ <label for="isprivate">
+ Make attachment and comment private (visible only to members of
+ the <strong>[% Param('insidergroup') FILTER html %]</strong>
+ group)
+ </label>
</td>
</tr>
[% END %]
+
+ [% Hook.process('form_before_submit') %]
[%# if WEBKIT_CHANGES %]
[% IF (bug.product == "WebKit" || bug.product == "Security") %]
<tr id="legal" style="visibility: collapse;">
@@ -136,6 +149,7 @@
</tr>
[% END %]
[%# endif // WEBKIT_CHANGES %]
+
<tr>
<th> </th>
<td><input type="submit" id="create" value="Submit"></td>
@@ -144,4 +158,6 @@
</form>
+[% Hook.process('end') %]
+
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/custom/attachment/created.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/attachment/created.html.tmpl
index 049d44e..726ae3f 100644
--- a/Websites/bugs.webkit.org/template/en/custom/attachment/created.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/attachment/created.html.tmpl
@@ -26,23 +26,9 @@
[% PROCESS global/variables.none.tmpl %]
[% bug = bugs.0 %]
-[% bodyclasses = ['bz_bug',
- "bz_status_$bug.bug_status",
- "bz_component_$bug.component",
- "bz_bug_$bug.bug_id"
- ]
-%]
-[% FOREACH group = bug.groups_in %]
- [% bodyclasses.push("bz_group_$group.name") %]
-[% END %]
-
+[% PROCESS "bug/show-header.html.tmpl" %]
[% PROCESS global/header.html.tmpl
title = "Attachment $attachment.id added to $terms.Bug $attachment.bug_id"
- bodyclasses = bodyclasses
- javascript_urls = [ "js/util.js", "js/field.js",
- "js/yui/yahoo-dom-event.js", "js/yui/calendar.js" ]
- style_urls = [ "skins/standard/yui/calendar.css", "skins/standard/show_bug.css" ]
- doc_section = "bug_page.html"
%]
<dl>
diff --git a/Websites/bugs.webkit.org/template/en/custom/attachment/createformcontents.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/attachment/createformcontents.html.tmpl
new file mode 100644
index 0000000..238b942
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/custom/attachment/createformcontents.html.tmpl
@@ -0,0 +1,112 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Joel Peshkin <bugreport@peshkin.net>
+ # Erik Stambaugh <erik@dasbistro.com>
+ # Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+<tr class="attachment_data">
+ <th><label for="data">File</label>:</th>
+ <td>
+ <em>Enter the path to the file on your computer</em> (or
+ <a id="attachment_data_controller" href="javascript:TUI_toggle_class('attachment_text_field');
+ javascript:TUI_toggle_class('attachment_data')"
+ >paste text as attachment</a>).<br>
+ <input type="file" id="data" name="data" size="50" onchange="DataFieldHandler()">
+ </td>
+</tr>
+<tr class="attachment_text_field">
+ <th><label for="attach_text">File</label>:</th>
+ <td>
+ <em>Paste the text to be added as an attachment</em> (or
+ <a id="attachment_text_field_controller" href="javascript:TUI_toggle_class('attachment_text_field');
+ javascript:TUI_toggle_class('attachment_data')"
+ >attach a file</a>).<br>
+ <textarea id="attach_text" name="attach_text" cols="80" rows="15"
+ onkeyup="TextFieldHandler()" onblur="TextFieldHandler()"></textarea>
+ </td>
+</tr>
+<tr>
+ <th class="required"><label for="description">Description</label>:</th>
+ <td>
+ <em>Describe the attachment briefly.</em><br>
+ <input type="text" id="description" name="description" class="required"
+ size="60" maxlength="200">
+ </td>
+</tr>
+<tr[% ' class="expert_fields"' UNLESS bug.id %]>
+ <th>Content Type:</th>
+ <td>
+ <em>If the attachment is a patch, check the box below.</em><br>
+ <input type="checkbox" id="ispatch" name="ispatch" value="1"
+ onchange="setContentTypeDisabledState(this.form);">
+ <label for="ispatch">patch</label><br><br>
+ [%# Reset this whenever the page loads so that the JS state is up to date %]
+ <script type="text/javascript">
+ YAHOO.util.Event.onDOMReady(function() {
+ bz_fireEvent(document.getElementById('ispatch'), 'change');
+ });
+ </script>
+
+ <em>Otherwise, choose a method for determining the content type.</em><br>
+ <input type="radio" id="autodetect"
+ name="contenttypemethod" value="autodetect" checked="checked">
+ <label for="autodetect">auto-detect</label><br>
+ <input type="radio" id="list"
+ name="contenttypemethod" value="list">
+ <label for="list">select from list</label>:
+ <select name="contenttypeselection" id="contenttypeselection"
+ onchange="this.form.contenttypemethod[1].checked = true;">
+ [% PROCESS content_types %]
+ </select><br>
+ <input type="radio" id="manual"
+ name="contenttypemethod" value="manual">
+ <label for="manual">enter manually</label>:
+ <input type="text" name="contenttypeentry" id="contenttypeentry"
+ size="30" maxlength="200"
+ onchange="if (this.value) this.form.contenttypemethod[2].checked = true;">
+ </td>
+</tr>
+<tr[% ' class="expert_fields"' UNLESS bug.id %]>
+ <td> </td>
+ <td>
+ [% IF flag_types && flag_types.size > 0 %]
+ [% PROCESS "flag/list.html.tmpl" %]<br>
+ [% END %]
+ </td>
+</tr>
+
+[% BLOCK content_types %]
+[%# WEBKIT_CHANGES: Added XHTML source and SVG image. %]
+ [% mimetypes = [{type => "text/plain", desc => "plain text"},
+ {type => "text/html", desc => "HTML source"},
+ {type => "application/xhtml+xml", desc => "XHTML source"},
+ {type => "image/svg+xml", desc => "SVG image"},
+ {type => "application/xml", desc => "XML source"},
+ {type => "image/gif", desc => "GIF image"},
+ {type => "image/jpeg", desc => "JPEG image"},
+ {type => "image/png", desc => "PNG image"},
+ {type => "application/octet-stream", desc => "binary file"}]
+ %]
+ [% Hook.process("mimetypes", "attachment/createformcontents.html.tmpl") %]
+
+ [% FOREACH m = mimetypes %]
+ <option value="[% m.type FILTER html %]">[% m.desc FILTER html %] ([% m.type FILTER html %])</option>
+ [% END %]
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/custom/attachment/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/attachment/edit.html.tmpl
index 2c24df0..d6a4b7b 100644
--- a/Websites/bugs.webkit.org/template/en/custom/attachment/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/attachment/edit.html.tmpl
@@ -17,6 +17,7 @@
#
# Contributor(s): Myk Melez <myk@mozilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
#%]
[% PROCESS global/variables.none.tmpl %]
@@ -29,167 +30,23 @@
Attachment [% attachment.id %] Details for
[%+ "$terms.Bug ${attachment.bug_id}" FILTER bug_link(attachment.bug_id) FILTER none %]
[% END %]
-[% subheader = BLOCK %][% bugsummary FILTER html %][% END %]
+[% subheader = BLOCK %][% attachment.bug.short_desc FILTER html %][% END %]
[% PROCESS global/header.html.tmpl
title = title
header = header
subheader = subheader
doc_section = "attachments.html"
+ javascript_urls = ['js/attachment.js', 'js/field.js']
+ style_urls = ['skins/standard/attachment.css']
+ yui = [ 'autocomplete' ]
+ bodyclasses = "no_javascript"
%]
[%# No need to display the Diff button and iframe if the attachment is not a patch. %]
-[% patchviewerinstalled = (patchviewerinstalled && attachment.ispatch) %]
-
-<script type="text/javascript">
- <!--
- var prev_mode = 'raw';
- var current_mode = 'raw';
- var has_edited = 0;
- var has_viewed_as_diff = 0;
-// if WEBKIT_CHANGES
- var viewing_formatted_diff = false;
-// endif WEBKIT_CHANGES
- function editAsComment()
- {
- switchToMode('edit');
- has_edited = 1;
- }
- function undoEditAsComment()
- {
- switchToMode(prev_mode);
- }
- function redoEditAsComment()
- {
- switchToMode('edit');
- }
-// if WEBKIT_CHANGES
- function viewPrettyPatch()
- {
- viewing_formatted_diff = !viewing_formatted_diff;
- var src = "attachment.cgi?id=[% attachment.id %]";
- var buttonText = "View Formatted Diff";
- if (viewing_formatted_diff)
- {
- src += "&action=prettypatch"
- buttonText = "View Plain Diff";
- }
-
- document.getElementById('viewFrame').src = src;
- document.getElementById('viewPrettyPatchButton').innerHTML = buttonText;
- }
-// endif WEBKIT_CHANGES
-[% IF patchviewerinstalled %]
- function viewDiff()
- {
- switchToMode('diff');
-
- // If we have not viewed as diff before, set the view diff frame URL
- if (!has_viewed_as_diff) {
- var viewDiffFrame = document.getElementById('viewDiffFrame');
- viewDiffFrame.src =
- 'attachment.cgi?id=[% attachment.id %]&action=diff&headers=0';
- has_viewed_as_diff = 1;
- }
- }
-[% END %]
- function viewRaw()
- {
- switchToMode('raw');
- }
-
- function switchToMode(mode)
- {
- if (mode == current_mode) {
- alert('switched to same mode! This should not happen.');
- return;
- }
-
- // Switch out of current mode
- if (current_mode == 'edit') {
- hideElementById('editFrame');
- hideElementById('undoEditButton');
- } else if (current_mode == 'raw') {
- hideElementById('viewFrame');
-[%# if WEBKIT_CHANGES %]
- hideElementById('viewPrettyPatchButton');
-[%# endif // WEBKIT_CHANGES %]
-[% IF patchviewerinstalled %]
- hideElementById('viewDiffButton');
-[% END %]
- hideElementById(has_edited ? 'redoEditButton' : 'editButton');
- hideElementById('smallCommentFrame');
- } else if (current_mode == 'diff') {
-[% IF patchviewerinstalled %]
- hideElementById('viewDiffFrame');
-[% END %]
- hideElementById('viewRawButton');
- hideElementById(has_edited ? 'redoEditButton' : 'editButton');
- hideElementById('smallCommentFrame');
- }
-
- // Switch into new mode
- if (mode == 'edit') {
- showElementById('editFrame');
- showElementById('undoEditButton');
- } else if (mode == 'raw') {
- showElementById('viewFrame');
-[%# if WEBKIT_CHANGES %]
- showElementById('viewPrettyPatchButton');
-[%# endif // WEBKIT_CHANGES %]
-[% IF patchviewerinstalled %]
- showElementById('viewDiffButton');
-[% END %]
- showElementById(has_edited ? 'redoEditButton' : 'editButton');
- showElementById('smallCommentFrame');
- } else if (mode == 'diff') {
-[% IF patchviewerinstalled %]
- showElementById('viewDiffFrame');
-[% END %]
- showElementById('viewRawButton');
- showElementById(has_edited ? 'redoEditButton' : 'editButton');
- showElementById('smallCommentFrame');
- }
-
- prev_mode = current_mode;
- current_mode = mode;
- }
-
- function hideElementById(id)
- {
- var elm = document.getElementById(id);
- if (elm) {
- elm.style.display = 'none';
- }
- }
-
- function showElementById(id, val)
- {
- var elm = document.getElementById(id);
- if (elm) {
- if (!val) val = 'inline';
- elm.style.display = val;
- }
- }
-
- function normalizeComments()
- {
- // Remove the unused comment field from the document so its contents
- // do not get transmitted back to the server.
-
- var small = document.getElementById('smallCommentFrame');
- var big = document.getElementById('editFrame');
- if ( (small) && (small.style.display == 'none') )
- {
- small.parentNode.removeChild(small);
- }
- if ( (big) && (big.style.display == 'none') )
- {
- big.parentNode.removeChild(big);
- }
- }
- //-->
-</script>
+[% use_patchviewer = (feature_enabled('patch_viewer') && attachment.ispatch) %]
+[% can_edit = attachment.validate_can_edit %]
+[% editable_or_hide = can_edit ? "" : " bz_hidden_option" %]
<form method="post" action="attachment.cgi" onsubmit="normalizeComments();">
<input type="hidden" name="id" value="[% attachment.id %]">
@@ -200,65 +57,211 @@
<input type="hidden" name="token" value="[% issue_hash_token([attachment.id, attachment.modification_time]) FILTER html %]">
[% END %]
- <table class="attachment_info" width="100%">
-
- <tr>
- <td width="25%">
- <small>
- <b><label for="description">Description</label>:</b><br>
+ <div id="attachment_info" class="attachment_info [% IF can_edit %] edit[% ELSE %] read[% END%]">
+ <div id="attachment_attributes">
+ <div id="attachment_information_read_only" class="[% "bz_private" IF attachment.isprivate %]">
+ <div class="title">
+ [% "[patch]" IF attachment.ispatch%]
+ <span class="[% "bz_obsolete" IF attachment.isobsolete %]" title="[% "obsolete" IF attachment.isobsolete %]">
+ [% attachment.description FILTER html %]
+ </span>
+ [% IF can_edit %]
+ <span class="bz_edit">(<a href="javascript:toggle_attachment_details_visibility()">edit details</a>)</span>
+ [% END %]
+ </div>
+ <div class="details">
+ [% attachment.filename FILTER html %] ([% attachment.contenttype FILTER html %]),
+ [% IF attachment.datasize %]
+ [%+ attachment.datasize FILTER unitconvert %]
+ [% ELSE %]
+ <em>deleted</em>
+ [% END %], created by [%+ INCLUDE global/user.html.tmpl who = attachment.attacher %]
+ [% IF attachment.isprivate %];
+ <span class="bz_private">only visible to <strong>[% Param('insidergroup') FILTER html %]</strong> members</span>
+ [% END %]
+ </div>
+ </div>
+ <div id="attachment_information_edit">
+ <span class="bz_hide">
+ (<a href="javascript:toggle_attachment_details_visibility();">hide</a>)
+ </span>
+ <div id="attachment_description">
+ <label for="description">Description:</label>
[% INCLUDE global/textarea.html.tmpl
id = 'description'
name = 'description'
minrows = 3
cols = 25
wrap = 'soft'
+ classes = 'block' _ editable_or_hide
defaultcontent = attachment.description
- %]<br>
+ %]
+ </div>
- [% IF attachment.isurl %]
- <input type="hidden" name="filename"
- value="[% attachment.filename FILTER html %]">
- <input type="hidden" name="contenttypeentry"
- value="[% attachment.contenttype FILTER html %]">
- [% ELSE %]
- <b><label for="filename">Filename</label>:</b><br>
- <input type="text" size="20" id="filename" name="filename"
- value="[% attachment.filename FILTER html %]"><br>
- <b>Size:</b>
- [% IF attachment.datasize %]
- [%+ attachment.datasize FILTER unitconvert %]
- [% ELSE %]
- <em>deleted</em>
- [% END %]<br>
+ <div id="attachment_filename">
+ <label for="filename">Filename:</label>
+ <input type="text" size="20" class="text block[% editable_or_hide %]"
+ id="filename" name="filename"
+ value="[% attachment.filename FILTER html %]">
+ </div>
- <b><label for="contenttypeentry">MIME Type</label>:</b><br>
- <input type="text" size="20"
+ <div id="attachment_mimetype">
+ <label for="contenttypeentry">MIME Type:</label>
+ <input type="text" size="20" class="text block[% editable_or_hide %]"
id="contenttypeentry" name="contenttypeentry"
- value="[% attachment.contenttype FILTER html %]"><br>
+ value="[% attachment.contenttype FILTER html %]">
+ </div>
+
+ <div id="attachment_creator">
+ <span class="label">Creator:</span>
+ [%+ INCLUDE global/user.html.tmpl who = attachment.attacher %]
+ </div>
+
+ <div id="attachment_size">
+ <span class="label">Size:</span>
+ [% IF attachment.datasize %]
+ [%+ attachment.datasize FILTER unitconvert %]
+ [% ELSE %]
+ <em>deleted</em>
+ [% END %]
+ </div>
- <input type="checkbox" id="ispatch" name="ispatch" value="1"
- [%+ 'checked="checked"' IF attachment.ispatch %]>
- <label for="ispatch">patch</label>
- [% END %]
- <input type="checkbox" id="isobsolete" name="isobsolete" value="1"
- [%+ 'checked="checked"' IF attachment.isobsolete %]>
- <label for="isobsolete">obsolete</label>
- [% IF (Param("insidergroup") && user.in_group(Param("insidergroup"))) %]
- <input type="checkbox" id="isprivate" name="isprivate" value="1"
- [% " checked" IF attachment.isprivate %]>
- <label for="isprivate">private</label><br>
- [% END %]
- <br>
- </small>
+ <div id="attachment_ispatch">
+ <input type="checkbox" id="ispatch" name="ispatch" value="1"
+ [%+ 'checked="checked"' IF attachment.ispatch %]>
+ <label for="ispatch">patch</label>
+ </div>
- [% IF flag_types.size > 0 %]
- [% PROCESS "flag/list.html.tmpl" bug_id = attachment.bug_id
- attach_id = attachment.id %]<br>
- [% END %]
+ <div class="readonly">
+ <div class="checkboxes">
+ <div id="attachment_isobsolete">
+ <input type="checkbox" id="isobsolete" name="isobsolete" value="1"
+ [%+ 'checked="checked"' IF attachment.isobsolete %]>
+ <label for="isobsolete">obsolete</label>
+ </div>
+ [% IF user.is_insider %]
+ <div id="attachment_isprivate">
+ <input type="checkbox" id="isprivate" name="isprivate" value="1"
+ [%+ 'checked="checked"' IF attachment.isprivate %]>
+ [% IF can_edit %]
+ <label for="isprivate">private (only visible to
+ <strong>[% Param('insidergroup') FILTER html %]</strong>)
+ </label>
+ [% ELSE %]
+ <span class="label">Is Private:</span>
+ [%+ attachment.isprivate ? "yes" : "no" %]
+ [% END %]
+ </div>
+ [% END %]
+ </div>
+ </div>
+ </div>
+
+ <div id="attachment_view_window">
+ [% IF !attachment.datasize %]
+ <div><b>The content of this attachment has been deleted.</b></div>
+ [% ELSIF !Param("allow_attachment_display") %]
+ <div id="view_disabled">
+ <p><b>
+ The attachment is not viewable in your browser due to security
+ restrictions enabled by your [% terms.Bugzilla %] administrator.
+ </b></p>
+ <p><b>
+ In order to view the attachment, you first have to
+ <a href="attachment.cgi?id=[% attachment.id %]">download it</a>.
+ </b></p>
+ </div>
+ [% ELSIF attachment.is_viewable %]
+ <div>
+ [% INCLUDE global/textarea.html.tmpl
+ id = 'editFrame'
+ name = 'comment'
+ classes = 'bz_default_hidden'
+ minrows = 10
+ cols = 80
+ wrap = 'soft'
+ disabled = 'disabled'
+ defaultcontent = (attachment.contenttype.match('^text\/')) ?
+ attachment.data.replace('(.*\n|.+)', '>$1') : undef
+ %]
+ [% IF attachment.contenttype == 'text/plain' AND is_safe_url(attachment.data) %]
+ <p>
+ <a href="[% attachment.data FILTER html %]">
+ [% IF attachment.datasize < 120 %]
+ [% attachment.data FILTER html %]
+ [% ELSE %]
+ [% attachment.data FILTER truncate(80) FILTER html %]
+ ...
+ [% attachment.data.match('.*(.{20})$').0 FILTER html %]
+ [% END %]
+ </a>
+ </p>
+ [% ELSIF attachment.contenttype == "text/html" %]
+ [%# For security reasons (clickjacking, embedded scripts), we never
+ # render HTML pages from here. The source code is displayed instead. %]
+ [% INCLUDE global/textarea.html.tmpl
+ id = 'viewFrame'
+ minrows = 10
+ cols = 80
+ defaultcontent = attachment.data
+ readonly = 'readonly'
+ %]
+ [% ELSE %]
+ <iframe id="viewFrame" src="attachment.cgi?id=[% attachment.id %]">
+ <b>You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
+ <a href="attachment.cgi?id=[% attachment.id %]">View the attachment on a separate page</a>.</b>
+ </iframe>
+ [% END %]
+ <script type="text/javascript">
+ <!--
+ var patchviewerinstalled = 0;
+ var attachment_id = [% attachment.id %];
+ if (typeof document.getElementById == "function") {
+ [% IF use_patchviewer %]
+ var patchviewerinstalled = 1;
+ document.write('<iframe id="viewDiffFrame" class="bz_default_hidden"><\/iframe>');
+ [% END %]
+ [% IF user.id %]
[%# if WEBKIT_CHANGES %]
- [% IF attachment.ispatch %]
- <b><small>Bot Status:</small></b>
+ [% IF attachment.ispatch %]
+ document.write('<button type="button" id="viewPrettyPatchButton" onclick="viewPrettyPatch();">View Formatt
+ [% END %]
+[%# endif // WEBKIT_CHANGES %]
+ document.write('<button type="button" id="editButton" onclick="editAsComment(patchviewerinstalled);">Edit Attachment As Comment<\/button>');
+ document.write('<button type="button" id="undoEditButton" onclick="undoEditAsComment(patchviewerinstalled);" class="bz_default_hidden">Undo Edit As Comment<\/button>');
+ document.write('<button type="button" id="redoEditButton" onclick="redoEditAsComment(patchviewerinstalled);" class="bz_default_hidden">Redo Edit As Comment<\/button>');
+ var editFrame = document.getElementById('editFrame');
+ if (editFrame) {
+ editFrame.disabled = false;
+ }
+ [% END %]
+ [% IF use_patchviewer %]
+ document.write('<button type="button" id="viewDiffButton" onclick="viewDiff(attachment_id, patchviewerinstalled);">View Attachment As Diff<\/button>');
+ [% END %]
+ document.write('<button type="button" id="viewRawButton" onclick="viewRaw(patchviewerinstalled);" class="bz_default_hidden">View Attachment As Raw<\/button>');
+ }
+ //-->
+ </script>
+ </div>
+ [% ELSE %]
+ <div id="noview">
+ <p><b>
+ Attachment is not viewable in your browser because its MIME type
+ ([% attachment.contenttype FILTER html %]) is not one that your browser is
+ able to display.
+ </b></p>
+ <p><b>
+ <a href="attachment.cgi?id=[% attachment.id %]">Download the attachment</a>.
+ </b></p>
+ </div>
+ [% END %]
+ </div>
+ <div id="attachment_comments_and_flags">
+ [% IF user.id %]
+[%# if WEBKIT_CHANGES %]
+ [% IF attachment.ispatch %]
+ Bot Status:
<div class="statusBubble">
<iframe src="https://webkit-queues.appspot.com/status-bubble/[% attachment.id %]"
@@ -266,120 +269,64 @@
</iframe>
</div>
<br>
- [% END %]
+ [% END %]
[%# endif // WEBKIT_CHANGES %]
-
- <div id="smallCommentFrame">
- <b><small><label for="comment">Comment</label> (on the
- [%+ terms.bug %]):</small></b><br>
+ <div id="smallCommentFrame" >
+ <label for="comment">Comment (on the [% terms.bug %]):</label>
+ [% classNames = 'block' %]
+ [% classNames = "$classes bz_private" IF attachment.isprivate %]
[% INCLUDE global/textarea.html.tmpl
id = 'comment'
name = 'comment'
- minrows = 5
- cols = 25
+ minrows = 10
+ cols = 80
wrap = 'soft'
- %]<br>
+ classes = classNames
+ %]
+ </div>
+ [% END %]
+ <div id="attachment_flags">
+ [% IF attachment.flag_types.size > 0 %]
+ [% PROCESS "flag/list.html.tmpl" flag_types = attachment.flag_types
+ read_only_flags = !can_edit
+ %]
+
+ [% END %]
</div>
- <input type="submit" value="Submit" id="update"><br><br>
- <strong>Actions:</strong>
- <a href="attachment.cgi?id=[% attachment.id %]">View</a>
+ [% Hook.process('form_before_submit') %]
+
+ [% IF user.id %]
+ <div id="update_container">
+ <input type="submit" value="Submit" id="update">
+ </div>
+ [% END %]
+ </div>
+ </div>
+ </div>
+</form>
+
+<div id="attachment_actions">
+ <span class="label">Actions:</span>
+ <a href="attachment.cgi?id=[% attachment.id %]">View</a>
[%# if WEBKIT_CHANGES %]
- [% IF attachment.ispatch %]
- | <a href="attachment.cgi?id=[% attachment.id %]&action=prettypatch">Formatted Diff</a>
- [% END %]
+ [% IF attachment.ispatch %]
+ | <a href="attachment.cgi?id=[% attachment.id %]&action=prettypatch">Formatted Diff</a>
+ [% END %]
[%# endif // WEBKIT_CHANGES %]
- [% IF attachment.ispatch && patchviewerinstalled %]
- | <a href="attachment.cgi?id=[% attachment.id %]&action=diff">Diff</a>
- [% END %]
- [% IF Param("allow_attachment_deletion")
- && user.groups.admin
- && attachment.datasize > 0 %]
- | <a href="attachment.cgi?id=[% attachment.id %]&action=delete">Delete</a>
- [% END %]
- </td>
+ [% IF use_patchviewer %]
+ | <a href="attachment.cgi?id=[% attachment.id %]&action=diff">Diff</a>
+ [% END %]
+ [% IF Param("allow_attachment_deletion")
+ && user.in_group('admin')
+ && attachment.datasize > 0 %]
+ | <a href="attachment.cgi?id=[% attachment.id %]&action=delete">Delete</a>
+ [% END %]
+ [% Hook.process('action') %]
+</div>
- [% IF !attachment.datasize %]
- <td width="75%"><b>The content of this attachment has been deleted.</b></td>
- [% ELSIF attachment.isurl %]
- <td width="75%">
- <a href="[% attachment.data FILTER html %]">
- [% IF attachment.datasize < 120 %]
- [% attachment.data FILTER html %]
- [% ELSE %]
- [% attachment.data FILTER truncate(80) FILTER html %]
- ...
- [% attachment.data.match(".*(.{20})$").0 FILTER html %]
- [% END %]
- </a>
- </td>
- [% ELSIF !Param("allow_attachment_display") %]
- <td id="view_disabled" width="50%">
- <p><b>
- The attachment is not viewable in your browser due to security
- restrictions enabled by [% terms.Bugzilla %].
- </b></p>
- <p><b>
- In order to view the attachment, you first have to
- <a href="attachment.cgi?id=[% attachment.id %]">download it</a>.
- </b></p>
- </td>
- [% ELSIF attachment.is_viewable %]
- <td width="75%">
- [% INCLUDE global/textarea.html.tmpl
- id = 'editFrame'
- name = 'comment'
- style = 'height: 400px; width: 100%; display: none'
- minrows = 10
- cols = 80
- wrap = 'soft'
- defaultcontent = (attachment.contenttype.match('^text\/')) ?
- attachment.data.replace('(.*\n|.+)', '>$1') : undef
- %]
- <iframe id="viewFrame" src="attachment.cgi?id=[% attachment.id %]" style="height: 400px; width: 100%;">
- <b>You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
- <a href="attachment.cgi?id=[% attachment.id %]">View the attachment on a separate page</a>.</b>
- </iframe>
- <script type="text/javascript">
- <!--
- if (typeof document.getElementById == "function") {
-[% IF patchviewerinstalled %]
- document.write('<iframe id="viewDiffFrame" style="height: 400px; width: 100%; display: none;"><\/iframe>');
-[% END %]
-[%# if WEBKIT_CHANGES %]
-[% IF attachment.ispatch %]
- document.write('<button type="button" id="viewPrettyPatchButton" onclick="viewPrettyPatch();">View Formatted Diff<\/button>');
-[% END %]
-[%# endif // WEBKIT_CHANGES %]
- document.write('<button type="button" id="editButton" onclick="editAsComment();">Edit Attachment As Comment<\/button>');
- document.write('<button type="button" id="undoEditButton" onclick="undoEditAsComment();" style="display: none;">Undo Edit As Comment<\/button>');
- document.write('<button type="button" id="redoEditButton" onclick="redoEditAsComment();" style="display: none;">Redo Edit As Comment<\/button>');
-[% IF patchviewerinstalled %]
- document.write('<button type="button" id="viewDiffButton" onclick="viewDiff();">View Attachment As Diff<\/button>');
-[% END %]
- document.write('<button type="button" id="viewRawButton" onclick="viewRaw();" style="display: none;">View Attachment As Raw<\/button>');
- }
- //-->
- </script>
- </td>
- [% ELSE %]
- <td id="noview" width="50%">
- <p><b>
- Attachment is not viewable in your browser because its MIME type
- ([% attachment.contenttype FILTER html %]) is not one that your browser is
- able to display.
- </b></p>
- <p><b>
- <a href="attachment.cgi?id=[% attachment.id %]">Download the attachment</a>.
- </b></p>
- </td>
- [% END %]
-
- </tr>
-
- </table>
-
- Attachments on this [% terms.Bug %]:
+<div id="attachment_list">
+ Attachments on [% "$terms.bug ${attachment.bug_id}" FILTER bug_link(attachment.bug_id) FILTER none %]:
[% FOREACH a = attachments %]
[% IF a == attachment.id %]
[%+ a %]
@@ -388,9 +335,15 @@
[% END %]
[% " |" UNLESS loop.last() %]
[% END %]
-
-</form>
-
-<br>
+</div>
+[% IF can_edit %]
+ <script type="text/javascript">
+ <!--
+ YAHOO.util.Dom.removeClass( document.body, "no_javascript" );
+ toggle_attachment_details_visibility( );
+ -->
+ </script>
+[% END %]
+[% Hook.process('end') %]
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/custom/attachment/list.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/attachment/list.html.tmpl
index 61c258d..343edfe 100644
--- a/Websites/bugs.webkit.org/template/en/custom/attachment/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/attachment/list.html.tmpl
@@ -19,41 +19,46 @@
# Frédéric Buclin <LpSolit@gmail.com>
#%]
-<script type="text/javascript">
- <!--
- function toggle_display(link) {
- var table = document.getElementById("attachment_table");
- var rows = table.getElementsByTagName("tr");
- var originalHeight = table.offsetHeight; // Store current height for scrolling
+[% RETURN UNLESS attachments.size || Param("maxattachmentsize") || Param("maxlocalattachment") %]
- var toggle;
- if (link.innerHTML == "Show Obsolete") {
- toggle = ""; // This should be 'table-row', but IE 6 doesn't understand it.
- link.innerHTML = "Hide Obsolete";
- }
- else {
- toggle = "none";
- link.innerHTML = "Show Obsolete";
- }
+<script type="text/javascript">
+<!--
+function toggle_display(link) {
+ var table = document.getElementById("attachment_table");
+ var view_all = document.getElementById("view_all");
+ var hide_obsolete_url_parameter = "&hide_obsolete=1";
+ // Store current height for scrolling later
+ var originalHeight = table.offsetHeight;
+ var rows = YAHOO.util.Dom.getElementsByClassName(
+ 'bz_tr_obsolete', 'tr', table);
for (var i = 0; i < rows.length; i++) {
- if (rows[i].className.match('bz_tr_obsolete'))
- rows[i].style.display = toggle;
+ bz_toggleClass(rows[i], 'bz_default_hidden');
+ }
+
+ if (YAHOO.util.Dom.hasClass(rows[0], 'bz_default_hidden')) {
+ link.innerHTML = "Show Obsolete";
+ view_all.href = view_all.href + hide_obsolete_url_parameter
+ }
+ else {
+ link.innerHTML = "Hide Obsolete";
+ view_all.href = view_all.href.replace(hide_obsolete_url_parameter,"");
}
var newHeight = table.offsetHeight;
+ // This scrolling makes the window appear to not move at all.
window.scrollBy(0, newHeight - originalHeight);
return false;
- }
- //-->
+}
+//-->
</script>
<br>
<table id="attachment_table" cellspacing="0" cellpadding="4">
- <tr>
+ <tr id="a0">
<th colspan="[% show_attachment_flags ? 3 : 2 %]" align="left">
- <a name="a0" id="a0">Attachments</a>
+ Attachments
</th>
</tr>
@@ -66,15 +71,19 @@
[% IF attachment.isobsolete %]
[% obsolete_attachments = obsolete_attachments + 1 %]
[% END %]
- <tr class="[% "bz_private" IF attachment.isprivate %][%-%]
- [%+ "bz_tr_obsolete" IF attachment.isobsolete %]"
+ <tr id="a[% count %]" class="[% "bz_contenttype_" _ attachment.contenttype
+ FILTER css_class_quote %]
+ [% " bz_patch" IF attachment.ispatch %]
+ [% " bz_private" IF attachment.isprivate %]
+ [% " bz_tr_obsolete bz_default_hidden"
+ IF attachment.isobsolete %]"
[%# if WEBKIT_CHANGES %]
- [% IF attachment.ispatch && !attachment.isobsolete %] style="background-color: rgb(255,255,200);" [% END %]
+ [% IF attachment.ispatch && !attachment.isobsolete %] style="background-color: rgb(255,255,200);" [% END %]
[%# endif // WEBKIT_CHANGES %]
>
<td valign="top">
[% IF attachment.datasize %]
- <a name="a[% count %]" href="attachment.cgi?id=[% attachment.id %]"
+ <a href="attachment.cgi?id=[% attachment.id %]"
title="View the content of the attachment">
[% END %]
<b>[% attachment.description FILTER html FILTER obsolete(attachment.isobsolete) %]</b>
@@ -85,8 +94,6 @@
([% attachment.datasize FILTER unitconvert %],
[% IF attachment.ispatch %]
patch)
- [% ELSIF attachment.isurl %]
- url)
[% ELSE %]
[%+ attachment.contenttype FILTER html %])
[% END %]
@@ -99,10 +106,7 @@
title="Go to the comment associated with the attachment">
[%- attachment.attached FILTER time %]</a>,
- <a href="mailto:[% attachment.attacher.email FILTER html %]"
- title="Write an email to the creator of the attachment">
- [% attachment.attacher.name || attachment.attacher.login FILTER html %]
- </a>
+ [% INCLUDE global/user.html.tmpl who = attachment.attacher %]
</span>
</td>
@@ -118,11 +122,23 @@
[%# if WEBKIT_CHANGES %]
[% IF flag.type.name != 'in-rietveld' %]
[%# endif // WEBKIT_CHANGES %]
+ [% IF user.id %]
+ <span title="[% flag.setter.identity FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
+ [% ELSIF flag.setter.name %]
+ <span title="[% flag.setter.name FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
+ [% ELSE %]
[% flag.setter.nick FILTER html %]:
- [%+ flag.type.name FILTER html FILTER no_break %][% flag.status %]
- [%+ IF flag.status == "?" && flag.requestee %]
+ [% END %]
+ [%+ flag.type.name FILTER html FILTER no_break %][% flag.status %]
+ [%+ IF flag.status == "?" && flag.requestee %]
+ [% IF user.id %]
+ (<span title="[% flag.requestee.identity FILTER html %]">[% flag.requestee.nick FILTER html %]</span>)
+ [% ELSIF flag.requestee.name %]
+ (<span title="[% flag.requestee.name FILTER html %]">[% flag.requestee.nick FILTER html %]</span>)
+ [% ELSE %]
([% flag.requestee.nick FILTER html %])
- [% END %]<br>
+ [% END %]
+ [% END %]<br>
[%# if WEBKIT_CHANGES %]
[% END %]
[%# endif // WEBKIT_CHANGES %]
@@ -133,7 +149,7 @@
<td valign="top">
[%# if WEBKIT_CHANGES %]
- [% IF attachment.ispatch %]
+ [% IF attachment.ispatch && user.id %]
<a href="attachment.cgi?id=[% attachment.id %]&action=review">Review Patch</a> |
[% END %]
[%# endif // WEBKIT_CHANGES %]
@@ -143,7 +159,7 @@
| <a href="attachment.cgi?id=[% attachment.id %]&action=prettypatch">Formatted Diff</a>
[% END %]
[%# endif // WEBKIT_CHANGES %]
- [% IF attachment.ispatch && patchviewerinstalled %]
+ [% IF attachment.ispatch && feature_enabled('patch_viewer') %]
| <a href="attachment.cgi?id=[% attachment.id %]&action=diff">Diff</a>
[% END %]
[% Hook.process("action") %]
@@ -151,7 +167,7 @@
[% IF attachment.ispatch %]
[% FOREACH flag = attachment.flags %]
[% IF flag.type.name == 'in-rietveld' && flag.status == "+" %]
- | <a href="attachment.cgi?id=[% attachment.id %]&action=rietveldreview&GoAheadAndLogIn=1">Rietveld Review</a>
+ | <a href="attachment.cgi?id=[% attachment.id %]&action=rietveldreview&GoAheadAndLogIn=1">Rietve
[% END %]
[% END %]
<div class="statusBubble">
@@ -171,15 +187,20 @@
[% IF attachments.size %]
<span class="bz_attach_view_hide">
[% IF obsolete_attachments %]
- <a href="#a0" onClick="return toggle_display(this);">Hide Obsolete</a> ([% obsolete_attachments %])
+ <a href="#a0" onclick="return toggle_display(this);">Show
+ Obsolete</a> ([% obsolete_attachments %])
[% END %]
[% IF Param("allow_attachment_display") %]
- <a href="attachment.cgi?bugid=[% bugid %]&action=viewall">View All</a>
+ <a id="view_all" href="attachment.cgi?bugid=
+ [%- bugid %]&action=viewall
+ [%- "&hide_obsolete=1" IF obsolete_attachments %]">View All</a>
[% END %]
</span>
[% END %]
- <a href="attachment.cgi?bugid=[% bugid %]&action=enter">Add an attachment</a>
- (proposed patch, testcase, etc.)
+ [% IF Param("maxattachmentsize") || Param("maxlocalattachment") %]
+ <a href="attachment.cgi?bugid=[% bugid %]&action=enter">Add an attachment</a>
+ (proposed patch, testcase, etc.)
+ [% END %]
</td>
</tr>
</table>
diff --git a/Websites/bugs.webkit.org/template/en/custom/bug/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/bug/edit.html.tmpl
index 5eed28e..4ca2eda 100644
--- a/Websites/bugs.webkit.org/template/en/custom/bug/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/bug/edit.html.tmpl
@@ -40,7 +40,7 @@
*/
[% IF user.settings.quote_replies.value != 'off' %]
document.write('[<a href="#add_comment" onclick="replyToComment(' +
- id + ',' + real_id + ');">reply<' + '/a>]');
+ id + ',' + real_id + '); return false;">reply<' + '/a>]');
[% END %]
}
@@ -52,24 +52,15 @@
/* pre id="comment_name_N" */
var text_elem = document.getElementById('comment_text_'+id);
var text = getText(text_elem);
-
- /* make sure we split on all newlines -- IE or Moz use \r and \n
- * respectively.
- */
- text = text.split(/\r|\n/);
-
- for (var i=0; i < text.length; i++) {
- replytext += "> " + text[i] + "\n";
- }
-
- replytext = prefix + replytext + "\n";
+ replytext = prefix + wrapReplyText(text);
[% ELSIF user.settings.quote_replies.value == 'simple_reply' %]
replytext = prefix;
[% END %]
- [% IF Param("insidergroup") && user.in_group(Param("insidergroup")) %]
+ [% IF user.is_insider %]
if (document.getElementById('isprivate_' + real_id).checked) {
document.getElementById('newcommentprivacy').checked = 'checked';
+ updateCommentTagControl(document.getElementById('newcommentprivacy'), 'comment');
}
[% END %]
@@ -107,7 +98,7 @@
return text;
}
-[% IF user.in_group(Param('timetrackinggroup')) %]
+[% IF user.is_timetracker %]
var fRemainingTime = [% bug.remaining_time %]; // holds the original value
function adjustRemainingTime() {
// subtracts time spent from remaining time
@@ -128,27 +119,28 @@
[% END %]
- function updateCommentTagControl(checkbox, form) {
- if (checkbox.checked) {
- form.comment.className='bz_private';
- } else {
- form.comment.className='';
- }
- }
+ /* Index all classifications so we can keep track of the classification
+ * for the selected product, which could control field visibility.
+ */
+ var all_classifications = new Array([% bug.choices.product.size %]);
+ [%- FOREACH product = bug.choices.product %]
+ all_classifications['[% product.name FILTER js %]'] = '
+ [%- product.classification.name FILTER js %]';
+ [%- END %]
//-->
</script>
-<form name="changeform" method="post" action="process_bug.cgi">
+<form name="changeform" id="changeform" method="post" action="process_bug.cgi">
<input type="hidden" name="delta_ts" value="[% bug.delta_ts %]">
- <input type="hidden" name="longdesclength" value="[% bug.longdescs.size %]">
+ <input type="hidden" name="longdesclength" value="[% bug.comments.size %]">
<input type="hidden" name="id" value="[% bug.bug_id %]">
<input type="hidden" name="token" value="[% issue_hash_token([bug.id, bug.delta_ts]) FILTER html %]">
[% PROCESS section_title %]
[%# if WEBKIT_CHANGES %]
- <table id="bug_details">
+ <table id="bug_details" class="edit_form">
[%# endif // WEBKIT_CHANGES %]
<tr>
[%# 1st Column %]
@@ -191,7 +183,9 @@
[% PROCESS section_cclist %]
- [% PROCESS section_spacer %]
+ [% PROCESS section_spacer %]
+
+ [% PROCESS section_see_also %]
[% PROCESS section_customfields %]
@@ -211,90 +205,43 @@
</tr>
</table>
-
- [% PROCESS section_restrict_visibility %]
- [% IF user.in_group(Param('timetrackinggroup')) %]
- <br>
- [% PROCESS section_timetracking %]
- [% END %]
-
+ <table id="bz_big_form_parts" cellspacing="0" cellpadding="0"><tr>
+ <td>
+ [% IF user.is_timetracker %]
+ [% PROCESS section_timetracking %]
+ [% END %]
-[%# *** Attachments *** %]
+ [%# *** Attachments *** %]
- [% PROCESS attachment/list.html.tmpl
- attachments = bug.attachments
- bugid = bug.bug_id
- num_attachment_flag_types = bug.num_attachment_flag_types
- show_attachment_flags = bug.show_attachment_flags
- %]
+ [% PROCESS attachment/list.html.tmpl
+ attachments = bug.attachments
+ bugid = bug.bug_id
+ num_attachment_flag_types = bug.num_attachment_flag_types
+ show_attachment_flags = bug.show_attachment_flags
+ %]
+ [% IF user.settings.comment_box_position.value == 'before_comments' %]
+ [% PROCESS comment_box %]
+ [% END %]
+ </td>
+ <td>
+ [% PROCESS section_restrict_visibility %]
+ </td>
+ </tr></table>
-[%# *** Comments Groups *** %]
+ [%# *** Additional Comments *** %]
+ <div id="comments">
+ [% PROCESS bug/comments.html.tmpl
+ comments = bug.comments
+ mode = user.id ? "edit" : "show"
+ %]
+ </div>
- <br>
- <table cellpadding="1" cellspacing="1">
- <tr>
- <td id="comment_status_commit">
- <!-- The table keeps the commit button aligned with the box. -->
- <a name="add_comment"></a>
- [% IF user.id %]
- <table><tr><td>
- <label for="comment" accesskey="c"><b>Additional <u>C</u>omments</b></label>:
- [% IF Param("insidergroup") && user.in_group(Param("insidergroup")) %]
- <input type="checkbox" name="commentprivacy" value="1"
- id="newcommentprivacy"
- onClick="updateCommentTagControl(this, form)">
- <label for="newcommentprivacy">Private</label>
- [% END %]
- <br>
- [% INCLUDE global/textarea.html.tmpl
- name = 'comment'
- id = 'comment'
- minrows = 10
- maxrows = 25
- cols = constants.COMMENT_COLS
- %]
- <br>
- <div id="knob-buttons">
- <input type="submit" value="Commit" id="commit">
- [% IF bug.user.canmove %]
- <input type="submit" name="action" id="action" value="[% Param("move-button-text") %]">
- [% END %]
- </div>
- <table class="status" cellspacing="0" cellpadding="0">
- <tr>
- <td class="field_label">
- <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
- </td>
- <td>
- <a name="bug_status_bottom"></a>
- [% PROCESS bug/knob.html.tmpl %]
- </td>
- </tr>
- </table>
- </td></tr></table>
- [% ELSE %]
- <fieldset>
- <legend>Note</legend>
- <p>
- You need to
- <a href="[% IF Param('ssl') != 'never' %][% Param('sslbase') %][% END %]show_bug.cgi?id=[% bug.bug_id %]&GoAheadAndLogIn=1">log in</a>
- before you can comment on or make changes to this [% terms.bug %].
- </p>
- </fieldset>
- [% END %]
- [%# *** Additional Comments *** %]
- <hr>
- <div id="comments">
- [% PROCESS bug/comments.html.tmpl
- comments = bug.longdescs
- mode = user.id ? "edit" : "show"
- %]
- </div>
-
- </td>
- </tr>
- </table>
+ [% IF user.settings.comment_box_position.value == 'after_comments' %]
+ <hr>
+ [% PROCESS comment_box %]
+ [% END %]
+
</form>
[%############################################################################%]
@@ -303,17 +250,17 @@
[% BLOCK section_title %]
[%# That's the main table, which contains all editable fields. %]
- <div class="bz_alias_short_desc_container">
-
+ <div class="bz_alias_short_desc_container edit_form">
+ [% PROCESS commit_button id="_top"%]
<a href="show_bug.cgi?id=[% bug.bug_id %]">
- <b>[% terms.Bug %] [% bug.bug_id FILTER html %]</b></a> -
- <span id="summary_alias_container" class="bz_default_hidden">
+ [%-# %]<b>[% terms.Bug %] [% bug.bug_id FILTER html %]</b>
+ [%-# %]</a> -<span id="summary_alias_container" class="bz_default_hidden">
[% IF Param("usebugaliases") %]
[% IF bug.alias != "" %]
(<span id="alias_nonedit_display">[% bug.alias FILTER html %]</span>)
[% END %]
[% END %]
- <span id="short_desc_nonedit_display">[% bug.short_desc FILTER html %]</span>
+ <span id="short_desc_nonedit_display">[% bug.short_desc FILTER quoteUrls(bug) %]</span>
[% IF bug.check_can_change_field('short_desc', 0, 1) ||
bug.check_can_change_field('alias', 0, 1) %]
<small class="editme">(<a href="#" id="editme_action">edit</a>)</small>
@@ -375,23 +322,35 @@
[%# PRODUCT #%]
[%#############%]
<tr>
- <td class="field_label">
- <label for="product" accesskey="p"><b><u>P</u>roduct</b></label>:
- </td>
- [% PROCESS select selname => "product" %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.product,
+ override_legal_values = bug.choices.product
+ desc_url = 'describecomponents.cgi', value = bug.product
+ editable = bug.check_can_change_field('product', 0, 1) %]
+ </tr>
+
+ [%# Classification is here so that it can be used in value controllers
+ # and visibility controllers. It comes after product because
+ # it uses some javascript that depends on the existence of the
+ # product field.
+ #%]
+ <tr class="bz_default_hidden">
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug field = bug_fields.classification
+ override_legal_values = bug.choices.classification
+ value = bug.classification
+ editable = bug.check_can_change_field('product', 0, 1) %]
</tr>
[%###############%]
[%# Component #%]
[%###############%]
<tr>
- <td class="field_label">
- <label for="component" accesskey="m">
- <b><a href="describecomponents.cgi?product=[% bug.product FILTER url_quote %]">
- Co<u>m</u>ponent</a>:
- </b>
- </label>
- </td>
- [% PROCESS select selname => "component" %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.component, value = bug.component
+ override_legal_values = bug.choices.component
+ desc_url = "describecomponents.cgi?product=$bug.product"
+ editable = bug.check_can_change_field('component', 0, 1)
+ %]
</tr>
<tr>
<td class="field_label">
@@ -411,11 +370,19 @@
<td class="field_label">
<label for="rep_platform" accesskey="h"><b>Platform</b></label>:
</td>
- <td>
- [% PROCESS select selname => "rep_platform" no_td=> 1 %]
- [%+ PROCESS select selname => "op_sys" no_td=> 1 %]
+ <td class="field_value">
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.rep_platform,
+ no_tds = 1, value = bug.rep_platform
+ editable = bug.check_can_change_field('rep_platform', 0, 1) %]
+ [%+ INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.op_sys,
+ no_tds = 1, value = bug.op_sys
+ editable = bug.check_can_change_field('op_sys', 0, 1) %]
<script type="text/javascript">
+[%# if WEBKIT_CHANGES %]
assignToDefaultOnChange(['product']);
+[%# endif // WEBKIT_CHANGES %]
</script>
</td>
</tr>
@@ -435,9 +402,9 @@
</td>
<td id="bz_field_status">
<span id="static_bug_status">
- [% get_status(bug.bug_status) FILTER html %]
+ [% display_value("bug_status", bug.bug_status) FILTER html %]
[% IF bug.resolution %]
- [%+ get_resolution(bug.resolution) FILTER html %]
+ [%+ display_value("resolution", bug.resolution) FILTER html %]
[% IF bug.dup_id %]
of [% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
[% END %]
@@ -458,7 +425,7 @@
[% BLOCK section_details2 %]
[%###############################################################%]
- [%# Importance (priority, severity and votes) #%]
+ [%# Importance (priority and severity) #%]
[%###############################################################%]
<tr>
<td class="field_label">
@@ -468,40 +435,29 @@
[%# endif // WEBKIT_CHANGES %]
</td>
<td>
- [% PROCESS select selname => "priority" no_td=>1 %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.priority,
+ no_tds = 1, value = bug.priority
+ editable = bug.check_can_change_field('priority', 0, 1) %]
[%# if WEBKIT_CHANGES %]
- [% PROCESS select selname => "bug_severity" no_td=>1 accesskey => "e" %]
+ [%+ INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.bug_severity,
+ no_tds = 1, value = bug.bug_severity
+ accesskey = 'e'
+ editable = bug.check_can_change_field('bug_severity', 0, 1) %]
[%# endif // WEBKIT_CHANGES %]
- [% IF bug.use_votes %]
- <span id="votes_container">
- [% IF bug.votes %]
- with
- <a href="votes.cgi?action=show_bug&bug_id=[% bug.bug_id %]">
- [% bug.votes %]
- [% IF bug.votes == 1 %]
- vote
- [% ELSE %]
- votes
- [% END %]</a>
- [% END %]
- (<a href="votes.cgi?action=show_user&bug_id=
- [% bug.bug_id %]#vote_[% bug.bug_id %]">vote</a>)
- </span>
- [% END %]
+ [% Hook.process('after_importance', 'bug/edit.html.tmpl') %]
</td>
</tr>
[% IF Param("usetargetmilestone") && bug.target_milestone %]
<tr>
<td class="field_label">
- <label for="target_milestone"><b>
- [% IF bug.milestoneurl %]
- <a href="[% bug.milestoneurl FILTER html %]">
- [% END %]
+ <label for="target_milestone">
+ <a href="page.cgi?id=fields.html#target_milestone">
[%# if WEBKIT_CHANGES %]
- <u>T</u>arget Milestone[% "</a>" IF bug.milestoneurl %]
+ <u>T</u>arget Milestone</a></label>:
[%# endif // WEBKIT_CHANGES %]
- [%%]</b></label>:
</td>
[%# if WEBKIT_CHANGES %]
[% PROCESS select selname => "target_milestone" accesskey => "t" %]
@@ -525,8 +481,12 @@
[% IF bug.check_can_change_field("assigned_to", 0, 1) %]
<div id="bz_assignee_edit_container" class="bz_default_hidden">
<span>
- [% INCLUDE user_identity user=> bug.assigned_to %]
+ [% INCLUDE global/user.html.tmpl who = bug.assigned_to %]
(<a href="#" id="bz_assignee_edit_action">edit</a>)
+ [% IF bug.assigned_to.id != user.id %]
+ (<a title="Reassign to yourself"
+ href="#" id="bz_assignee_take_action">take</a>)
+ [% END %]
</span>
</div>
<div id="bz_assignee_input">
@@ -534,6 +494,7 @@
id => "assigned_to"
name => "assigned_to"
value => bug.assigned_to.login
+ classes => ["bz_userfield"]
size => 30
%]
<br>
@@ -546,10 +507,16 @@
'bz_assignee_edit_action',
'assigned_to',
'[% bug.assigned_to.login FILTER js %]' );
+ hideEditableField('bz_assignee_edit_container',
+ 'bz_assignee_input',
+ 'bz_assignee_take_action',
+ 'assigned_to',
+ '[% bug.assigned_to.login FILTER js %]',
+ '[% user.login FILTER js %]' );
initDefaultCheckbox('assignee');
</script>
[% ELSE %]
- [% INCLUDE user_identity user => bug.assigned_to %]
+ [% INCLUDE global/user.html.tmpl who = bug.assigned_to %]
[% END %]
</td>
</tr>
@@ -560,13 +527,12 @@
<label for="qa_contact" accesskey="q"><b><u>Q</u>A Contact</b></label>:
</td>
<td>
-
[% IF bug.check_can_change_field("qa_contact", 0, 1) %]
[% IF bug.qa_contact != "" %]
<div id="bz_qa_contact_edit_container" class="bz_default_hidden">
<span>
<span id="bz_qa_contact_edit_display">
- [% INCLUDE user_identity user=> bug.qa_contact %]</span>
+ [% INCLUDE global/user.html.tmpl who = bug.qa_contact %]</span>
(<a href="#" id="bz_qa_contact_edit_action">edit</a>)
</span>
</div>
@@ -577,6 +543,7 @@
name => "qa_contact"
value => bug.qa_contact.login
size => 30
+ classes => ["bz_userfield"]
emptyok => 1
%]
<br>
@@ -594,7 +561,7 @@
initDefaultCheckbox('qa_contact');
</script>
[% ELSE %]
- [% INCLUDE user_identity user => bug.qa_contact %]
+ [% INCLUDE global/user.html.tmpl who = bug.qa_contact %]
[% END %]
</td>
</tr>
@@ -605,23 +572,16 @@
[%# Block for URL Keyword and Whiteboard #%]
[%############################################################################%]
[% BLOCK section_url_keyword_whiteboard %]
-[%# *** URL Whiteboard Keywords *** %]
<tr>
- <td class="field_label">
- <label for="bug_file_loc" accesskey="u"><b>
- [% IF bug.bug_file_loc
- AND NOT bug.bug_file_loc.match("^(javascript|data)") %]
- <a href="[% bug.bug_file_loc FILTER html %]"><u>U</u>RL</a>
- [% ELSE %]
- <u>U</u>RL
- [% END %]
- [%%]</b></label>:
- </td>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.bug_file_loc
+ editable = 1
+ accesskey = "u"
+ %]
<td>
[% IF bug.check_can_change_field("bug_file_loc", 0, 1) %]
<span id="bz_url_edit_container" class="bz_default_hidden">
- [% IF bug.bug_file_loc
- AND NOT bug.bug_file_loc.match("^(javascript|data)") %]
+ [% IF is_safe_url(bug.bug_file_loc) %]
<a href="[% bug.bug_file_loc FILTER html %]" target="_blank"
title="[% bug.bug_file_loc FILTER html %]">
[% bug.bug_file_loc FILTER truncate(40) FILTER html %]</a>
@@ -632,7 +592,8 @@
[% END %]
<span id="bz_url_input_area">
[% url_output = PROCESS input no_td=1 inputname => "bug_file_loc" size => "40" colspan => 2 %]
- [% IF NOT bug.check_can_change_field("bug_file_loc", 0, 1) %]
+ [% IF NOT bug.check_can_change_field("bug_file_loc", 0, 1)
+ AND is_safe_url(bug.bug_file_loc) %]
<a href="[% bug.bug_file_loc FILTER html %]">[% url_output FILTER none %]</a>
[% ELSE %]
[% url_output FILTER none %]
@@ -665,8 +626,13 @@
<label for="keywords" accesskey="k">
<b><a href="describekeywords.cgi"><u>K</u>eywords</a></b></label>:
</td>
- [% PROCESS input inputname => "keywords" size => 40 colspan => 2
- value => bug.keywords.join(', ') %]
+ <td class="field_value" colspan="2">
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.keywords, value = bug.keywords
+ editable = bug.check_can_change_field("keywords", 0, 1),
+ no_tds = 1
+ %]
+ </td>
</tr>
[% END %]
[% END %]
@@ -676,15 +642,13 @@
[%############################################################################%]
[% BLOCK section_dependson_blocks %]
<tr>
-[%# if WEBKIT_CHANGES %]
- [% PROCESS dependencies accesskey = "d"
- dep = { title => "<u>D</u>epends on", fieldname => "dependson" } %]
-[%# endif // WEBKIT_CHANGES %]
+ [% INCLUDE dependencies
+ field = bug_fields.dependson deps = bug.depends_on_obj %]
</tr>
<tr>
- [% PROCESS dependencies accesskey = "b"
- dep = { title => "<u>B</u>locks", fieldname => "blocked" } %]
+ [% INCLUDE dependencies
+ field = bug_fields.blocked deps = bug.blocks_obj %]
<tr>
<th> </th>
@@ -707,103 +671,93 @@
[% BLOCK section_restrict_visibility %]
[% RETURN UNLESS bug.groups.size %]
- [% inallgroups = 1 %]
- [% inagroup = 0 %]
- [% emitted_description = 0 %]
+ <div class="bz_group_visibility_section">
+ [% inallgroups = 1 %]
+ [% inagroup = 0 %]
+ [% emitted_description = 0 %]
- [% FOREACH group = bug.groups %]
- [% SET inallgroups = 0 IF NOT group.ingroup %]
- [% SET inagroup = 1 IF group.ison %]
+ [% FOREACH group = bug.groups %]
+ [% SET inallgroups = 0 IF NOT group.ingroup %]
+ [% SET inagroup = 1 IF group.ison %]
- [% NEXT IF group.mandatory %]
+ [% NEXT IF group.mandatory %]
- [% IF NOT emitted_description %]
- [% emitted_description = 1 %]
- <table>
- <tr>
- <td class="field_label">
- <label id="bz_restrict_group_visibility_label"><b>Restrict Group Visibility</b>:</label>
- </td>
- <td>
- <div id="bz_restrict_group_visibility_help">
- <b>Only users in all of the selected groups can view this [% terms.bug %]:</b>
- <br>
- <small>
- (Unchecking all boxes makes this a more public [% terms.bug %].)
- </small>
- </div>
- [% END %]
+ [% IF NOT emitted_description %]
+ [% emitted_description = 1 %]
+ <div id="bz_restrict_group_visibility_help">
+ <b>Only users in all of the selected groups can view this
+ [%+ terms.bug %]:</b>
+ <p class="instructions">
+ Unchecking all boxes makes this a more public [% terms.bug %].
+ </p>
+ </div>
+ [% END %]
- [% IF group.ingroup %]
- <input type="hidden" name="defined_bit-[% group.bit %]" value="1">
- [% END %]
- <input type="checkbox" value="1" name="bit-[% group.bit %]" id="bit-[% group.bit %]"
- [% ' checked="checked"' IF group.ison %]
- [% ' disabled="disabled"' IF NOT group.ingroup %]>
- <label for="bit-[% group.bit %]">[% group.description FILTER html_light %]</label>
- <br>
- [% END %]
+ [% IF group.ingroup %]
+ <input type="hidden" name="defined_groups"
+ value="[% group.name FILTER html %]">
+ [% END %]
- [% IF emitted_description %]
- [% IF NOT inallgroups %]
- <b>Only members of a group can change the visibility of [% terms.abug %] for that group.</b>
+ <input type="checkbox" value="[% group.name FILTER html %]"
+ name="groups" id="group_[% group.bit %]"
+ [% ' checked="checked"' IF group.ison %]
+ [% ' disabled="disabled"' IF NOT group.ingroup %]>
+ <label for="group_[% group.bit %]">
+ [%- group.description FILTER html_light %]</label>
<br>
[% END %]
- </td>
- </tr>
- [% "</table>" IF NOT inagroup %]
- [% END %]
- [% IF inagroup %]
- [% IF NOT emitted_description %]
- [% emitted_description = 1 %]
- <table>
+ [% IF emitted_description %]
+ [% IF NOT inallgroups %]
+ <p class="instructions">Only members of a group can change the
+ visibility of [% terms.abug %] for that group.</p>
+ [% END %]
[% END %]
- <tr>
- <td class="field_label">
- <label id="bz_enable_role_visibility_label"><b>Enable Role Visibility</b>:</label>
- </td>
- <td>
- <div id="bz_enable_role_visibility_help">
- <b>Users in the roles selected below can always view this [% terms.bug %]:</b>
- <br>
- <small>
- (The assignee
- [% IF (Param('useqacontact')) %]
- and QA contact
- [% END %]
- can always see [% terms.abug %], and this section does not take effect unless
- the [% terms.bug %] is restricted to at least one group.)
- </small>
+
+ [% IF inagroup %]
+ <div id="bz_enable_role_visibility_help">
+ <b>Users in the roles selected below can always view
+ this [% terms.bug %]:</b>
+ </div>
+ <div id="bz_enable_role_visibility">
+ <div>
+ [% user_can_edit_accessible =
+ bug.check_can_change_field("reporter_accessible", 0, 1)
+ %]
+ [% IF user_can_edit_accessible %]
+ <input type="hidden" name="defined_reporter_accessible" value="1">
+ [% END %]
+ <input type="checkbox" value="1"
+ name="reporter_accessible" id="reporter_accessible"
+ [% " checked" IF bug.reporter_accessible %]
+ [% " disabled=\"disabled\"" UNLESS user_can_edit_accessible %]>
+ <label for="reporter_accessible">Reporter</label>
</div>
<div>
- <div>
- [% user_can_edit_accessible = bug.check_can_change_field("reporter_accessible", 0, 1) %]
- [% IF user_can_edit_accessible %]
- <input type="hidden" name="defined_reporter_accessible" value="1">
- [% END %]
- <input type="checkbox" value="1"
- name="reporter_accessible" id="reporter_accessible"
- [% " checked" IF bug.reporter_accessible %]
- [% " disabled=\"disabled\"" UNLESS user_can_edit_accessible %]>
- <label for="reporter_accessible">Reporter</label>
- </div>
- <div>
- [% user_can_edit_accessible = bug.check_can_change_field("cclist_accessible", 0, 1) %]
- [% IF user_can_edit_accessible %]
- <input type="hidden" name="defined_cclist_accessible" value="1">
- [% END %]
- <input type="checkbox" value="1"
- name="cclist_accessible" id="cclist_accessible"
- [% " checked" IF bug.cclist_accessible %]
- [% " disabled=\"disabled\"" UNLESS user_can_edit_accessible %]>
- <label for="cclist_accessible">CC List</label>
- </div>
+ [% user_can_edit_accessible =
+ bug.check_can_change_field("cclist_accessible", 0, 1)
+ %]
+ [% IF user_can_edit_accessible %]
+ <input type="hidden" name="defined_cclist_accessible" value="1">
+ [% END %]
+ <input type="checkbox" value="1"
+ name="cclist_accessible" id="cclist_accessible"
+ [% " checked" IF bug.cclist_accessible %]
+ [% " disabled=\"disabled\"" UNLESS user_can_edit_accessible %]>
+ <label for="cclist_accessible">CC List</label>
</div>
- </td>
- </tr>
- </table>
- [% END %]
+ <p class="instructions">
+ The assignee
+ [% IF (Param('useqacontact')) %]
+ and QA contact
+ [% END %]
+ can always see [% terms.abug %], and this section does not
+ take effect unless the [% terms.bug %] is restricted to at
+ least one group.
+ </p>
+ </div>
+ [% END %]
+ </div> [%# bz_group_visibility_section %]
[% END %]
[%############################################################################%]
@@ -816,7 +770,7 @@
<b>Reported</b>:
</td>
<td>
- [% bug.creation_ts FILTER time %] by [% INCLUDE user_identity user => bug.reporter %]
+ [% bug.creation_ts FILTER time %] by [% INCLUDE global/user.html.tmpl who = bug.reporter %]
</td>
</tr>
@@ -836,11 +790,10 @@
[%# Block for CC LIST #%]
[%############################################################################%]
[% BLOCK section_cclist %]
- [% IF user.id %]
<tr>
- <td class="field_label">
- <label for="newcc" accesskey="a"><b>CC List</b>:</label>
- </td>
+ <td class="field_label">
+ <label for="newcc" accesskey="a"><b>CC List</b>:</label>
+ </td>
<td>
[% IF user.id %]
[% IF NOT bug.cc || NOT bug.cc.contains(user.login) %]
@@ -869,47 +822,76 @@
including you
[% END %]
[% END %]
- <span id="cc_edit_area_showhide_container" class="bz_default_hidden">
- (<a href="#" id="cc_edit_area_showhide">edit</a>)
- </span>
- <div id="cc_edit_area">
- <div>
- <div>
- <label for="cc">
- <b>Add</b>
- </label>
- </div>
- [% INCLUDE global/userselect.html.tmpl
- id => "newcc"
- name => "newcc"
- value => ""
- size => 30
- multiple => 5
- %]
- </div>
- [% IF bug.cc %]
- <select id="cc" name="cc" multiple="multiple" size="5">
- [% FOREACH c = bug.cc %]
- <option value="[% c FILTER html %]">[% c FILTER html %]</option>
- [% END %]
- </select>
- [% IF user.id %]
- <br>
- <input type="checkbox" id="removecc" name="removecc">
- [%%]<label for="removecc">Remove selected CCs</label>
- <br>
- [% END %]
+ [% IF user.id || bug.cc.size %]
+ <span id="cc_edit_area_showhide_container" class="bz_default_hidden">
+ (<a href="#" id="cc_edit_area_showhide">[% IF user.id %]edit[% ELSE %]show[% END %]</a>)
+ </span>
[% END %]
+ <div id="cc_edit_area">
+ <br>
+ [% IF user.id %]
+ <div>
+ <div><label for="cc"><b>Add</b></label></div>
+ [% INCLUDE global/userselect.html.tmpl
+ id => "newcc"
+ name => "newcc"
+ value => ""
+ size => 30
+ classes => ["bz_userfield"]
+ multiple => 5
+ %]
+ </div>
+ [% END %]
+ [% IF bug.cc %]
+ <select id="cc" multiple="multiple" size="5"
+ [% IF bug.user.canedit %]name="cc"[% END %]>
+ [% FOREACH c = bug.cc %]
+ <option value="[% c FILTER email FILTER html %]">
+ [% c FILTER email FILTER html %]</option>
+ [% END %]
+ </select>
+ [% IF user.id && !bug.user.canedit %]
+ <input type="hidden" name="cc" value="[% user.login FILTER email FILTER html %]">
+ [% END %]
+ [% IF user.id AND (bug.user.canedit OR bug.cc.contains(user.login)) %]
+ <br>
+ <input type="checkbox" id="removecc" name="removecc">
+ <label for="removecc">
+ [% IF bug.user.canedit %]
+ Remove selected CCs
+ [% ELSE %]
+ Remove me from the CC list
+ [% END %]
+ </label>
+ <br>
+ [% END %]
+ [% END %]
</div>
- <script type="text/javascript">
- hideEditableField( 'cc_edit_area_showhide_container',
- 'cc_edit_area',
- 'cc_edit_area_showhide',
- '',
- '');
- </script>
+ [% IF user.id || bug.cc.size %]
+ <script type="text/javascript">
+ hideEditableField( 'cc_edit_area_showhide_container',
+ 'cc_edit_area',
+ 'cc_edit_area_showhide',
+ '',
+ '');
+ </script>
+ [% END %]
</td>
</tr>
+[% END %]
+
+[%############################################################################%]
+[%# Block for See Also #%]
+[%############################################################################%]
+[% BLOCK section_see_also %]
+ [% IF Param('use_see_also') || bug.see_also.size %]
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ field = bug_fields.see_also
+ value = bug.see_also
+ editable = bug.check_can_change_field('see_also', 0, 1)
+ %]
+ </tr>
[% END %]
[% END %]
@@ -928,30 +910,18 @@
[% END %]
[% IF show_bug_flags %]
<tr>
- <td class="field_label">
+ <td class="field_label flags_label">
<label><b>Flags:</b></label>
</td>
<td></td>
</tr>
<tr>
<td colspan="2">
- [% IF user.id %]
[% IF bug.flag_types.size > 0 %]
[% PROCESS "flag/list.html.tmpl" flag_no_header = 1
flag_types = bug.flag_types
any_flags_requesteeble = bug.any_flags_requesteeble %]
[% END %]
- [% ELSE %]
- [% FOREACH type = bug.flag_types %]
- [% FOREACH flag = type.flags %]
- [% flag.setter.nick FILTER html %]:
- [%+ type.name FILTER html FILTER no_break %][% flag.status %]
- [%+ IF flag.requestee %]
- ([% flag.requestee.nick FILTER html %])
- [% END %]<br>
- [% END %]
- [% END %]
- [% END %]
</td>
</tr>
[% END %]
@@ -963,14 +933,19 @@
[% BLOCK section_customfields %]
[%# *** Custom Fields *** %]
-
[% USE Bugzilla %]
[% FOREACH field = Bugzilla.active_custom_fields %]
<tr>
- [% PROCESS bug/field.html.tmpl value=bug.${field.name}
+ [% PROCESS bug/field.html.tmpl value = bug.${field.name}
editable = bug.check_can_change_field(field.name, 0, 1)
value_span = 2 %]
</tr>
+ [% IF extra_field_item %]
+ <tr>
+ <th class="field_label">[% extra_field_item.header FILTER none %]</th>
+ <td>[% extra_field_item.data FILTER none %]</td>
+ </tr>
+ [% END %]
[% END %]
[% END %]
@@ -993,37 +968,36 @@
[% BLOCK dependencies %]
- <th class="field_label">
- <label for="[% dep.fieldname %]"[% " accesskey=\"$accesskey\"" IF accesskey %]>
- [% dep.title %]</label>:
- </th>
- <td>
- <span id="[% dep.fieldname %]_input_area">
- [% IF bug.check_can_change_field(dep.fieldname, 0, 1) %]
- <input name="[% dep.fieldname %]" id="[% dep.fieldname %]"
- value="[% bug.${dep.fieldname}.join(', ') %]">
+ [% INCLUDE "bug/field-label.html.tmpl" %]
+
+ <td>
+ <span id="[% field.name FILTER html %]_input_area">
+ [% IF bug.check_can_change_field(field.name, 0, 1) %]
+ <input name="[% field.name FILTER html %]"
+ id="[% field.name FILTER html %]" class="text_input"
+ value="[% bug.${field.name}.join(', ') FILTER html %]">
[% END %]
</span>
- [% FOREACH depbug = bug.${dep.fieldname} %]
- [% depbug FILTER bug_link(depbug) FILTER none %][% " " %]
+ [% FOREACH dep_bug = deps %]
+ [% dep_bug.id FILTER bug_link(dep_bug, use_alias => 1)
+ FILTER none %][% " " %]
[% END %]
- [% IF bug.check_can_change_field(dep.fieldname, 0, 1) %]
- <span id="[% dep.fieldname %]_edit_container" class="edit_me bz_default_hidden" >
- (<a href="#" id="[% dep.fieldname %]_edit_action">edit</a>)
+ [% IF bug.check_can_change_field(field.name, 0, 1) %]
+ <span id="[% field.name FILTER html %]_edit_container"
+ class="edit_me bz_default_hidden">
+ (<a href="#" id="[% field.name FILTER html %]_edit_action">edit</a>)
</span>
<script type="text/javascript">
- hideEditableField('[% dep.fieldname %]_edit_container',
- '[% dep.fieldname %]_input_area',
- '[% dep.fieldname %]_edit_action',
- '[% dep.fieldname %]',
- "[% bug.${dep.fieldname}.join(', ') %]");
+ hideEditableField('[% field.name FILTER js %]_edit_container',
+ '[% field.name FILTER js %]_input_area',
+ '[% field.name FILTER js %]_edit_action',
+ '[% field.name FILTER js %]',
+ '[% bug.${field.name}.join(', ') FILTER js %]');
</script>
[% END %]
</td>
- [% accesskey = undef %]
-
[% END %]
[%############################################################################%]
@@ -1086,9 +1060,9 @@
[% PROCESS formattimeunit time_unit=bug.estimated_time - (bug.actual_time + bug.remaining_time) %]
</td>
<td>
- <input name="deadline" id="deadline" value="[% bug.deadline %]"
- size="10" maxlength="10"><br />
- <small>(YYYY-MM-DD)</small>
+ [% INCLUDE bug/field.html.tmpl
+ field = bug_fields.deadline, value = bug.deadline, no_tds = 1
+ editable = bug.check_can_change_field('deadline', 0, 1) %]
</td>
</tr>
<tr>
@@ -1102,28 +1076,91 @@
[% END %]
[%############################################################################%]
+[%# Block for the Additional Comments box #%]
+[%############################################################################%]
+
+[% BLOCK comment_box %]
+ <div id="add_comment" class="bz_section_additional_comments">
+ [% IF user.id %]
+ <label for="comment" accesskey="c"><b>Additional
+ <u>C</u>omments</b></label>:
+
+ [% IF user.is_insider %]
+ <input type="checkbox" name="comment_is_private" value="1"
+ id="newcommentprivacy"
+ onClick="updateCommentTagControl(this, 'comment')">
+ <label for="newcommentprivacy">
+ Make comment private (visible only to members of the
+ <strong>[% Param('insidergroup') FILTER html %]</strong> group)
+ </label>
+ [% END %]
+
+ <!-- This table keeps the submit button aligned with the box. -->
+ <table><tr><td>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'comment'
+ id = 'comment'
+ minrows = 10
+ maxrows = 25
+ cols = constants.COMMENT_COLS
+ %]
+ [% Hook.process("after_comment_textarea", 'bug/edit.html.tmpl') %]
+ <br>
+ [% PROCESS commit_button id=""%]
+
+ <table id="bug_status_bottom"
+ class="status" cellspacing="0" cellpadding="0">
+ <tr>
+ <td class="field_label">
+ <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
+ </td>
+ <td>
+ [% PROCESS bug/knob.html.tmpl %]
+ </td>
+ </tr>
+ </table>
+ </td></tr></table>
+
+ [%# For logged-out users %]
+ [% ELSE %]
+ <table>
+ <tr>
+ <td>
+ <fieldset>
+ <legend>Note</legend>
+ You need to
+ <a href="show_bug.cgi?id=
+ [%- bug.bug_id %]&GoAheadAndLogIn=1">log in</a>
+ before you can comment on or make changes to this [% terms.bug %].
+ </fieldset>
+ </td>
+ </tr>
+ </table>
+ [% END %]
+ </div>
+[% END %]
+
+[%############################################################################%]
[%# Block for SELECT fields #%]
[%############################################################################%]
[% BLOCK select %]
- [% IF NOT no_td %]
<td>
- [% END %]
- [% IF bug.check_can_change_field(selname, 0, 1) AND bug.choices.${selname}.size > 1 %]
+ [% IF bug.check_can_change_field(selname, 0, 1)
+ AND bug.choices.${selname}.size > 1 %]
<select id="[% selname %]" name="[% selname %]">
[% FOREACH x = bug.choices.${selname} %]
- <option value="[% x FILTER html %]"
- [% " selected" IF x == bug.${selname} %]>[% x FILTER html %]
+ [% NEXT IF NOT x.is_active AND x.name != bug.${selname} %]
+ <option value="[% x.name FILTER html %]"
+ [% " selected" IF x.name == bug.${selname} %]>
+ [%- x.name FILTER html %]
</option>
[% END %]
</select>
[% ELSE %]
[% bug.${selname} FILTER html %]
[% END %]
- [% IF NOT no_td %]
</td>
- [% END %]
- [% no_td = 0 %]
[% END %]
[%############################################################################%]
@@ -1136,7 +1173,7 @@
[% END %]
[% val = value ? value : bug.$inputname %]
[% IF bug.check_can_change_field(inputname, 0, 1) %]
- <input id="[% inputname %]" name="[% inputname %]"
+ <input id="[% inputname %]" name="[% inputname %]" class="text_input"
value="[% val FILTER html %]"[% " size=\"$size\"" IF size %]
[% " maxlength=\"$maxlength\"" IF maxlength %]
[% " spellcheck=\"$spellcheck\"" IF spellcheck %]>
@@ -1159,23 +1196,11 @@
[% value = undef %]
[% spellcheck = undef %]
[% END %]
-
-[%############################################################################%]
-[%# Block for user identities. Wraps the information inside of an hCard. #%]
-[%############################################################################%]
-
-[% BLOCK user_identity %]
- <span class="vcard">
- [% FILTER collapse %]
- [% IF user.name %]
- <a class="email" href="mailto:[% user.email FILTER html %]"
- title="[% user.email FILTER html %]"
- ><span class="fn">[% user.name FILTER html %]</span
- ></a>
- [% ELSE %]
- <a class="fn email" href="mailto:[% user.email FILTER html %]">
- [% user.email FILTER html %]</a>
- [% END %]
- [% END %]</span>
+[% BLOCK commit_button %]
+ [% IF user.id %]
+ <div class="knob-buttons">
+ <input type="submit" value="Save Changes"
+ id="commit[% id FILTER css_class_quote %]">
+ </div>
+ [% END %]
[% END %]
-
diff --git a/Websites/bugs.webkit.org/template/en/custom/bug/field.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/bug/field.html.tmpl
new file mode 100644
index 0000000..abcbf74
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/custom/bug/field.html.tmpl
@@ -0,0 +1,258 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Elliotte Martin <elliotte_martin@yahoo.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ # Reed Loden <reed@reedloden.com>
+ #%]
+
+[%# INTERFACE:
+ # field: a Bugzilla::Field object
+ # value: The value of the field for this bug.
+ # override_legal_values (optional): The list of legal values, for select fields.
+ # editable: Whether the field should be displayed as an editable
+ # <input> or as just the plain text of its value.
+ # allow_dont_change: display the --do_not_change-- option for select fields.
+ # value_span: A colspan for the table cell containing
+ # the field value.
+ # no_tds: boolean; if true, don't display the label <th> or the
+ # wrapping <td> for the field.
+ # bug (optional): The current Bugzilla::Bug being displayed, or a hash
+ # with default field values being displayed on a page.
+ # accesskey: set an accesskey attribute for this string
+ #%]
+
+[% SET hidden = 0 %]
+[% IF bug AND !field.is_visible_on_bug(bug) %]
+ [% SET hidden = 1 %]
+[% END %]
+
+[% IF NOT no_tds %]
+ [% PROCESS "bug/field-label.html.tmpl" %]
+ <td class="field_value [% ' bz_hidden_field' IF hidden %]"
+ id="field_container_[% field.name FILTER html %]"
+ [% " colspan=\"$value_span\"" FILTER none IF value_span %]>
+[% END %]
+[% Hook.process('start_field_column') %]
+[% IF editable %]
+ [% SWITCH field.type %]
+ [% CASE constants.FIELD_TYPE_FREETEXT %]
+ <input id="[% field.name FILTER html %]" class="text_input"
+ name="[% field.name FILTER html %]"
+ value="[% value FILTER html %]" size="40"
+[% IF accesskey %]
+ accesskey="[% accesskey FILTER html %]"
+[% END %]
+ maxlength="[% constants.MAX_FREETEXT_LENGTH FILTER none %]"
+ [% ' aria-required="true"' IF field.is_mandatory %]>
+ [% CASE constants.FIELD_TYPE_DATETIME %]
+ <input name="[% field.name FILTER html %]" size="20"
+ id="[% field.name FILTER html %]"
+ value="[% value FILTER html %]"
+ [% ' aria-required="true"' IF field.is_mandatory %]
+ onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_[% field.name FILTER html %]"
+ onclick="showCalendar('[% field.name FILTER js %]')">
+ <span>Calendar</span>
+ </button>
+
+ <div id="con_calendar_[% field.name FILTER html %]"></div>
+
+ <script type="text/javascript">
+ createCalendar('[% field.name FILTER js %]')
+ </script>
+ [% CASE constants.FIELD_TYPE_BUG_ID %]
+ <span id="[% field.name FILTER html %]_input_area">
+ <input name="[% field.name FILTER html %]" id="[% field.name FILTER html %]"
+ value="[% value FILTER html %]" size="7"
+ [% ' aria-required="true"' IF field.is_mandatory %]>
+
+ </span>
+
+ [% IF value %]
+ [% value FILTER bug_link(value, use_alias => 1) FILTER none %]
+ [% END %]
+ <span id="[% field.name FILTER html %]_edit_container" class="edit_me bz_default_hidden">
+ (<a href="#" id="[% field.name FILTER html %]_edit_action">edit</a>)
+ </span>
+ <script type="text/javascript">
+ hideEditableField('[% field.name FILTER js %]_edit_container',
+ '[% field.name FILTER js %]_input_area',
+ '[% field.name FILTER js %]_edit_action',
+ '[% field.name FILTER js %]',
+ "[% value FILTER js %]");
+ </script>
+ [% CASE [ constants.FIELD_TYPE_SINGLE_SELECT
+ constants.FIELD_TYPE_MULTI_SELECT ] %]
+ <select id="[% field.name FILTER html %]"
+ name="[% field.name FILTER html %]"
+ [% IF field.type == constants.FIELD_TYPE_MULTI_SELECT %]
+ [% SET field_size = 5 %]
+ [% IF field.legal_values.size < 5 %]
+ [% SET field_size = field.legal_values.size %]
+ [% END %]
+ size="[% field_size FILTER html %]" multiple="multiple"
+ [% ' aria-required="true"' IF field.is_mandatory %]
+ [% END %]
+ >
+ [% IF allow_dont_change %]
+ <option value="[% dontchange FILTER html %]"
+ [% ' selected="selected"' IF value == dontchange %]>
+ [% dontchange FILTER html %]
+ </option>
+ [% END %]
+ [% IF override_legal_values %]
+ [% legal_values = override_legal_values %]
+ [% ELSE %]
+ [% legal_values = field.legal_values %]
+ [% END %]
+ [% FOREACH legal_value = legal_values %]
+ [% NEXT IF NOT legal_value.is_active AND NOT value.contains(legal_value.name).size %]
+ <option value="[% legal_value.name FILTER html %]"
+ id="v[% legal_value.id FILTER html %]_
+ [%- field.name FILTER html %]"
+ [%# We always show selected values, even if they should be
+ # hidden %]
+ [% IF value.contains(legal_value.name).size %]
+ selected="selected"
+ [% ELSIF bug AND !legal_value.is_visible_on_bug(bug) %]
+ class="bz_hidden_option" disabled="disabled"
+ [% END %]>
+ [%- display_value(field.name, legal_value.name) FILTER html ~%]
+ </option>
+ [% END %]
+ </select>
+ [%# When you pass an empty multi-select in the web interface,
+ # it doesn't appear at all in the CGI object. Instead of
+ # forcing all users of process_bug to always specify every
+ # multi-select, we have this field defined if the multi-select
+ # field is defined, and then if this is passed but the multi-select
+ # isn't, we know that the multi-select was emptied.
+ %]
+ [% IF field.type == constants.FIELD_TYPE_MULTI_SELECT %]
+ <input type="hidden" name="defined_[% field.name FILTER html %]">
+ [% END %]
+
+ <script type="text/javascript">
+ <!--
+ initHidingOptionsForIE('[% field.name FILTER js %]');
+ [%+ INCLUDE "bug/field-events.js.tmpl"
+ field = field, product = bug.product_obj %]
+ //-->
+ </script>
+
+ [% CASE constants.FIELD_TYPE_TEXTAREA %]
+ [% INCLUDE global/textarea.html.tmpl
+ id = field.name name = field.name minrows = 4 maxrows = 8
+ cols = 60 defaultcontent = value mandatory = field.is_mandatory %]
+ [% CASE constants.FIELD_TYPE_BUG_URLS %]
+ [% '<ul class="bug_urls">' IF value.size %]
+ [% FOREACH bug_url = value %]
+ <li>
+ [% PROCESS bug_url_link bug_url = bug_url %]
+ <label><input type="checkbox" value="[% bug_url.name FILTER html %]"
+ name="remove_[% field.name FILTER html %]">
+ Remove</label>
+ </li>
+ [% END %]
+ [% '</ul>' IF value.size %]
+
+ [% IF Param('use_see_also') %]
+ <span id="container_showhide_[% field.name FILTER html %]"
+ class="bz_default_hidden">
+ <a href="#" id="showhide_[% field.name FILTER html %]">(add)</a>
+ </span>
+ <div id="container_[% field.name FILTER html %]">
+ <label for="[% field.name FILTER html %]">
+ <strong>Add [% terms.Bug %] URLs:</strong>
+ </label><br>
+ <input type="text" id="[% field.name FILTER html %]" size="40"
+ class="text_input" name="[% field.name FILTER html %]">
+ </div>
+ <script type="text/javascript">
+ setupEditLink('[% field.name FILTER js %]');
+ </script>
+ [% END %]
+ [% CASE constants.FIELD_TYPE_KEYWORDS %]
+ <div id="keyword_container">
+ <input type="text" id="[% field.name FILTER html %]" size="40"
+ class="text_input" name="[% field.name FILTER html %]"
+ value="[% value FILTER html %]">
+ <div id="keyword_autocomplete"></div>
+ </div>
+ <script type="text/javascript" defer="defer">
+ YAHOO.bugzilla.keyword_array = [
+ [%- FOREACH keyword = all_keywords %]
+ [%-# %]"[% keyword.name FILTER js %]"
+ [%- "," IF NOT loop.last %][% END %]];
+ YAHOO.bugzilla.keywordAutocomplete.init('[% field.name FILTER js %]',
+ 'keyword_autocomplete');
+ </script>
+ [% END %]
+[% ELSE %]
+ [% SWITCH field.type %]
+ [% CASE constants.FIELD_TYPE_TEXTAREA %]
+ <div class="uneditable_textarea">[% value FILTER html %]</div>
+ [% CASE constants.FIELD_TYPE_BUG_ID %]
+ [% IF value %]
+ [% value FILTER bug_link(value, use_alias => 1) FILTER none %]
+ [% END %]
+ [% CASE [ constants.FIELD_TYPE_SINGLE_SELECT
+ constants.FIELD_TYPE_MULTI_SELECT ] %]
+ [% FOREACH val = value %]
+ [% display_value(field.name, val) FILTER html %]
+ [% ', ' UNLESS loop.last() %]
+ [% END %]
+ [% CASE constants.FIELD_TYPE_BUG_URLS %]
+ [% '<ul class="bug_urls">' IF value.size %]
+ [% FOREACH bug_url = value %]
+ <li>
+ [% PROCESS bug_url_link bug_url = bug_url %]
+ </li>
+ [% END %]
+ [% '</ul>' IF value.size %]
+ [% CASE %]
+ [% value.join(', ') FILTER html %]
+ [% END %]
+[% END %]
+[% Hook.process('end_field_column') %]
+[% '</td>' IF NOT no_tds %]
+
+[%# for reverse relationships, we show this pseudo-field after the main field %]
+[% IF bug.id && field.is_relationship %]
+ [% extra_field_item = {} %]
+ [% extra_field_item.header = field.reverse_desc _ ":" FILTER html %]
+ [% extra_field_item.data = BLOCK %]
+ [% FOREACH depbug = bug.related_bugs(field) %]
+ [% depbug.id FILTER bug_link(depbug, use_alias => 1) FILTER none %][% " " %]
+ [% END %]
+ [% END %]
+[% ELSE %]
+ [% extra_field_item = '' %]
+[% END %]
+
+[% BLOCK bug_url_link %]
+ [% IF bug_url.isa('Bugzilla::BugUrl::Bugzilla::Local') %]
+ [% bug_url.target_bug_id FILTER bug_link(bug_url.target_bug_id, use_alias => 1) FILTER none %]
+ [% ELSE %]
+ <a href="[% bug_url.name FILTER html %]">
+ [% bug_url.name FILTER html %]</a>
+ [% END %]
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/custom/bug/format_comment.txt.tmpl b/Websites/bugs.webkit.org/template/en/custom/bug/format_comment.txt.tmpl
new file mode 100644
index 0000000..25ea9c8
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/custom/bug/format_comment.txt.tmpl
@@ -0,0 +1,60 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Marc Schumann.
+ # Portions created by Marc Schumann are Copyright (c) 2008 Marc Schumann.
+ # All rights reserved.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# NOTE: Everywhere you use this template, you must call
+ # "FILTER remove('^X')" on the result. This is unfortunately the only way
+ # to preserve leading whitespace in comments.
+ #%]
+
+[%# INTERFACE:
+ # comment: A Bugzilla::Comment object.
+ # is_bugmail: boolean; True if this comment is going into a plain-text
+ # bugmail.
+ #%]
+
+[%# Please don't use field-descs here. It can slow down Bugzilla. %]
+[% PROCESS 'global/variables.none.tmpl' %]
+
+[% SET comment_body = comment.body %]
+
+[% IF comment.type == constants.CMT_DUPE_OF %]
+X[% comment_body %]
+
+*** This [% terms.bug %] has been marked as a duplicate of [% terms.bug %] [%+ comment.extra_data %] ***
+[% ELSIF comment.type == constants.CMT_HAS_DUPE %]
+*** [% terms.Bug %] [%+ comment.extra_data %] has been marked as a duplicate of this [% terms.bug %]. ***
+[% ELSIF comment.type == constants.CMT_ATTACHMENT_CREATED %]
+Created attachment [% comment.extra_data %]
+[% IF is_bugmail %]
+ --> [% urlbase _ "attachment.cgi?id=" _ comment.extra_data _ "&action=review" %]
+[% END %]
+[%+ comment.attachment.description %]
+
+[%+ comment.body %]
+[% ELSIF comment.type == constants.CMT_ATTACHMENT_UPDATED %]
+Comment on attachment [% comment.extra_data %]
+[% IF is_bugmail %]
+ --> [% urlbase _ "attachment.cgi?id=" _ comment.extra_data %]
+[% END %]
+[%+ comment.attachment.description %]
+
+[%+ comment.body %]
+[% ELSE %]
+X[% Hook.process('type') %]
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/custom/bug/navigate.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/bug/navigate.html.tmpl
index 93feae6..c9eb08a 100644
--- a/Websites/bugs.webkit.org/template/en/custom/bug/navigate.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/bug/navigate.html.tmpl
@@ -16,17 +16,20 @@
# Rights Reserved.
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
#%]
+[% RETURN IF !bug %]
+
[% PROCESS global/variables.none.tmpl %]
[% IF bottom_navigator == 1 %]
<ul class="related_actions">
<li><a href="show_bug.cgi?format=multiple&id=
- [% bug.bug_id FILTER url_quote %]">Format For Printing</a></li>
+ [% bug.bug_id FILTER uri %]">Format For Printing</a></li>
<li> - <a href="show_bug.cgi?ctype=xml&id=
- [% bug.bug_id FILTER url_quote %]">XML</a></li>
+ [% bug.bug_id FILTER uri %]">XML</a></li>
<li> - <a href="enter_bug.cgi?cloned_bug_id=
- [% bug.bug_id FILTER url_quote %]">Clone This
+ [% bug.bug_id FILTER uri %]">Clone This
[% terms.Bug %]</a></li>
[%# Links to more things users can do with this bug. %]
[% Hook.process("links") %]
@@ -36,61 +39,63 @@
<div class="navigation">
-[% IF bug_list && bug_list.size > 0 %]
- [% this_bug_idx = lsearch(bug_list, bug.bug_id) %]
+[% SET my_search = user.recent_search_for(bug) %]
+[% IF my_search %]
+ [% SET last_bug_list = my_search.bug_list %]
+ [% SET this_bug_idx = lsearch(last_bug_list, bug.id) %]
<b>[% terms.Bug %] List:</b>
- [% IF this_bug_idx != -1 %]
- ([% this_bug_idx + 1 %] of [% bug_list.size %])
- [% END %]
-[% IF this_bug_idx != -1 %]
-[%# if WEBKIT_CHANGES %]
- <a href="show_bug.cgi?id=[% bug_list.first %]">|« First</a>
- <a href="show_bug.cgi?id=[% bug_list.last %]">Last »|</a>
-[%# endif // WEBKIT_CHANGES %]
-[% END %]
+ ([% this_bug_idx + 1 %] of [% last_bug_list.size %])
- [% IF bug.bug_id %]
- [% IF this_bug_idx != -1 %]
- [% IF this_bug_idx > 0 %]
- [% prev_bug = this_bug_idx - 1 %]
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.first FILTER uri %]&list_id=
[%# if WEBKIT_CHANGES %]
- <a href="show_bug.cgi?id=[% bug_list.$prev_bug %]">« Prev</a>
+ [%- my_search.id FILTER uri %]">|« First</a>
[%# endif // WEBKIT_CHANGES %]
- [% ELSE %]
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.last FILTER uri %]&list_id=
[%# if WEBKIT_CHANGES %]
- <i><font color="#777777">« Prev</font></i>
+ [%- my_search.id FILTER uri %]">Last »|</a>
[%# endif // WEBKIT_CHANGES %]
- [% END %]
- [% IF this_bug_idx + 1 < bug_list.size %]
- [% next_bug = this_bug_idx + 1 %]
+ [% IF this_bug_idx > 0 %]
+ [% prev_bug = this_bug_idx - 1 %]
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.$prev_bug FILTER uri %]&list_id=
[%# if WEBKIT_CHANGES %]
- <a href="show_bug.cgi?id=[% bug_list.$next_bug %]">Next »</a>
+ [%- my_search.id FILTER uri %]">« Prev</a>
[%# endif // WEBKIT_CHANGES %]
- [% ELSE %]
-[%# if WEBKIT_CHANGES %]
- <i><font color="#777777">Next »</font></i>
-[%# endif // WEBKIT_CHANGES %]
- [% END %]
- [% ELSE %]
- (This [% terms.bug %] is not in your last search results)
- [% END %]
[% ELSE %]
-
+[%# if WEBKIT_CHANGES %]
+ <i><font color="#777777">« Prev</font></i>
+[%# endif // WEBKIT_CHANGES %]
[% END %]
- <a href="buglist.cgi?regetlastlist=1">Show last search results</a>
+ [% IF this_bug_idx + 1 < last_bug_list.size %]
+ [% next_bug = this_bug_idx + 1 %]
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.$next_bug FILTER uri %]&list_id=
+[%# if WEBKIT_CHANGES %]
+ [%- my_search.id FILTER uri %]">Next »</a>
+[%# endif // WEBKIT_CHANGES %]
+ [% ELSE %]
+[%# if WEBKIT_CHANGES %]
+ <i><font color="#777777">Next »</font></i>
+[%# endif // WEBKIT_CHANGES %]
+ [% END %]
+
+ <a href="buglist.cgi?regetlastlist=
+ [%- my_search.id FILTER uri %]">Show last search results</a>
[% ELSE %]
- [%# Either !bug_list || bug_list.size <= 0 %]
[%# With no list, don't show link to search results %]
[%# if WEBKIT_CHANGES %]
- <i><font color="#777777">|« First</font></i>
- <i><font color="#777777">Last »|</font></i>
- <i><font color="#777777">« Prev</font></i>
- <i><font color="#777777">Next »</font></i>
+ <i><font color="#777777">|« First</font></i>
+ <i><font color="#777777">Last »|</font></i>
+ <i><font color="#777777">« Prev</font></i>
+ <i><font color="#777777">Next »</font></i>
[%# endif // WEBKIT_CHANGES %]
- <i><font color="#777777">No search results available</font></i>
+ <i><font color="#777777">This [% terms.bug %] is not in your last
+ search results.</font></i>
[% END %]
</div>
diff --git a/Websites/bugs.webkit.org/template/en/custom/flag/list.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/flag/list.html.tmpl
index 7ffe23f..3181465 100644
--- a/Websites/bugs.webkit.org/template/en/custom/flag/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/flag/list.html.tmpl
@@ -18,59 +18,7 @@
# Contributor(s): Myk Melez <myk@mozilla.org>
#%]
-<script type="text/javascript">
-<!--
- // Enables or disables a requestee field depending on whether or not
- // the user is requesting the corresponding flag.
- function toggleRequesteeField(flagField, no_focus)
- {
- // Convert the ID of the flag field into the ID of its corresponding
- // requestee field and then use the ID to get the field.
- var id = flagField.name.replace(/flag(_type)?-(\d+)/, "requestee$1-$2");
- var requesteeField = document.getElementById(id);
- if (!requesteeField) return;
-
- // Enable or disable the requestee field based on the value
- // of the flag field.
- if (flagField.value == "?") {
- requesteeField.disabled = false;
- if (!no_focus) requesteeField.focus();
- } else
- requesteeField.disabled = true;
- }
-
- // Disables requestee fields when the window is loaded since they shouldn't
- // be enabled until the user requests that flag type.
- function disableRequesteeFields()
- {
- var inputElements = document.getElementsByTagName("input");
- var selectElements = document.getElementsByTagName("select");
- //You cannot update Node lists, so you must create an array to combine the NodeLists
- var allElements = [];
- for( var i=0; i < inputElements.length; i++ ) {
- allElements[allElements.length] = inputElements.item(i);
- }
- for( var i=0; i < selectElements.length; i++ ) { //Combine inputs with selects
- allElements[allElements.length] = selectElements.item(i);
- }
- var inputElement, id, flagField;
- for ( var i=0 ; i<allElements.length ; i++ )
- {
- inputElement = allElements[i];
- if (inputElement.name.search(/^requestee(_type)?-(\d+)$/) != -1)
- {
- // Convert the ID of the requestee field into the ID of its corresponding
- // flag field and then use the ID to get the field.
- id = inputElement.name.replace(/requestee(_type)?-(\d+)/, "flag$1-$2");
- flagField = document.getElementById(id);
- if (flagField && flagField.value != "?")
- inputElement.disabled = true;
- }
- }
- }
- window.onload = disableRequesteeFields;
-// -->
-</script>
+[% IF user.id AND !read_only_flags %]
[%# We list flags by looping twice over the flag types relevant for the bug.
# In the first loop, we display existing flags and then, for active types,
@@ -82,6 +30,8 @@
[% DEFAULT flag_table_id = "flags" %]
+<script src="[% 'js/flag.js' FILTER mtime %]" type="text/javascript"></script>
+
<table id="[% flag_table_id FILTER html %]">
[% UNLESS flag_no_header %]
<tr>
@@ -97,9 +47,9 @@
[% END %]
[%# Step 1: Display every flag type (except inactive types with no flags). %]
- [% FOREACH type = flag_types %]
-
- [%# Step 1a: Display existing flag(s). %]
+ [% FOREACH type = flag_types -%]
+
+ [%-# Step 1a: Display existing flag(s). %]
[% FOREACH flag = type.flags %]
[%# if WEBKIT_CHANGES %]
[% IF type.name == 'in-rietveld' %]
@@ -109,7 +59,7 @@
[% END %]
[%# endif // WEBKIT_CHANGES %]
<td>
- [% flag.setter.nick FILTER html %]:
+ <span title="[% flag.setter.identity FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
</td>
<td>
<label title="[% type.description FILTER html %]"
@@ -120,9 +70,9 @@
<select id="flag-[% flag.id %]" name="flag-[% flag.id %]"
title="[% type.description FILTER html %]"
onchange="toggleRequesteeField(this);"
- class="flag_select">
+ class="flag_select flag_type-[% type.id %]">
[%# Only display statuses the user is allowed to set. %]
- [% IF user.can_request_flag(type) %]
+ [% IF user.can_request_flag(type) || flag.setter_id == user.id %]
<option value="X"></option>
[% END %]
[% IF type.is_active %]
@@ -144,6 +94,7 @@
<td>
[% IF (type.is_active && type.is_requestable && type.is_requesteeble) || flag.requestee %]
<span style="white-space: nowrap;">
+ [% SET flag_custom_list = [] %]
[% IF Param('usemenuforusers') %]
[% flag_custom_list = flag.type.grant_list %]
[% IF !(type.is_active && type.is_requestable && type.is_requesteeble) %]
@@ -152,83 +103,27 @@
nothing else. %]
[% flag_custom_list = [flag.requestee] %]
[% END %]
- [% INCLUDE global/userselect.html.tmpl
- name => "requestee-$flag.id"
- id => "requestee-$flag.id"
- value => flag.requestee.login
- multiple => 0
- emptyok => 1
- custom_userlist => flag_custom_list
- %]
- [% ELSE %]
- (<input type="text" size="30" maxlength="255"
- id="requestee-[% flag.id %]"
- name="requestee-[% flag.id %]"
- [% IF flag.status == "?" && flag.requestee %]
- value="[% flag.requestee.login FILTER html %]"
- [% END %]>)
[% END %]
+ [% INCLUDE global/userselect.html.tmpl
+ name => "requestee-$flag.id"
+ id => "requestee-$flag.id"
+ value => flag.requestee.login
+ multiple => 0
+ emptyok => 1
+ classes => ["requestee"]
+ custom_userlist => flag_custom_list
+ %]
</span>
[% END %]
</td>
[% END %]
</tr>
- [% END %]
-
- [%# Step 1b: Display UI for setting flag. %]
+ [% END -%]
+
+ [%-# Step 1b: Display UI for setting flag. %]
[% IF (!type.flags || type.flags.size == 0) && type.is_active %]
-[%# if WEBKIT_CHANGES %]
- [% IF type.name == 'in-rietveld' %]
- <tr style='display:none'>
- [% ELSE %]
- <tr>
- [% END %]
-[%# endif // WEBKIT_CHANGES %]
- <td> </td>
- <td>
- <label title="[% type.description FILTER html %]"
- for="flag_type-[% type.id %]">
- [%- type.name FILTER html FILTER no_break %]</label>
- </td>
- <td>
- <select id="flag_type-[% type.id %]" name="flag_type-[% type.id %]"
- title="[% type.description FILTER html %]"
- [% " disabled=\"disabled\"" UNLESS (type.is_requestable && user.can_request_flag(type)) || user.can_set_flag(type) %]
- onchange="toggleRequesteeField(this);"
- class="flag_select">
- <option value="X"></option>
- [% IF type.is_requestable && user.can_request_flag(type) %]
- <option value="?">?</option>
- [% END %]
- [% IF user.can_set_flag(type) %]
- <option value="+">+</option>
- <option value="-">-</option>
- [% END %]
- </select>
- </td>
- [% IF any_flags_requesteeble %]
- <td>
- [% IF type.is_requestable && type.is_requesteeble %]
- <span style="white-space: nowrap;">
- [% IF Param('usemenuforusers') %]
- [% INCLUDE global/userselect.html.tmpl
- name => "requestee_type-$type.id"
- id => "requestee_type-$type.id"
- multiple => type.is_multiplicable * 3
- emptyok => !type.is_multiplicable
- value => ""
- custom_userlist => type.grant_list
- %]
- [% ELSE %]
- (<input type="text" size="30" maxlength="255"
- id="requestee_type-[% type.id %]"
- name="requestee_type-[% type.id %]">)
- [% END %]
- </span>
- [% END %]
- </td>
- [% END %]
- </tr>
+
+ [% PROCESS flag_row first_cell_empty = 1 addl_text = "" %]
[% END %]
[% END %]
@@ -239,51 +134,95 @@
<tr><td colspan="3"><hr></td></tr>
[% separator_displayed = 1 %]
[% END %]
- <tr>
- <td colspan="2">
- addl. <label title="[% type.description FILTER html %]"
- for="flag_type-[% type.id %]">
- [%- type.name FILTER html FILTER no_break %]</label>
- </td>
- <td>
- <select id="flag_type-[% type.id %]" name="flag_type-[% type.id %]"
- title="[% type.description FILTER html %]"
- [% " disabled=\"disabled\"" UNLESS (type.is_requestable && user.can_request_flag(type)) || user.can_set_flag(type) %]
- onchange="toggleRequesteeField(this);"
- class="flag_select">
- <option value="X"></option>
- [% IF type.is_requestable && user.can_request_flag(type) %]
- <option value="?">?</option>
- [% END %]
- [% IF user.can_set_flag(type) %]
- <option value="+">+</option>
- <option value="-">-</option>
- [% END %]
- </select>
- </td>
- [% IF any_flags_requesteeble %]
- <td>
- [% IF type.is_requestable && type.is_requesteeble %]
- <span style="white-space: nowrap;">
- [% IF Param('usemenuforusers') %]
- [% INCLUDE global/userselect.html.tmpl
- name => "requestee_type-$type.id"
- id => "requestee_type-$type.id"
- multiple => type.is_multiplicable * 3
- emptyok => !type.is_multiplicable
- value => ""
- custom_userlist => type.grant_list
- %]
- [% ELSE %]
- (<input type="text" size="30" maxlength="255"
- id="requestee_type-[% type.id %]"
- name="requestee_type-[% type.id %]">)
- [% END %]
- </span>
- [% END %]
- </td>
+
+ [% PROCESS flag_row first_cell_empty = 0 addl_text = "addl." %]
+ [% END %]
+</table>
+
+[% ELSE %]
+ [%# The user is logged out. Display flags as read-only. %]
+ [% header_displayed = 0 %]
+ [% FOREACH type = flag_types %]
+ [% FOREACH flag = type.flags %]
+ [% IF !flag_no_header AND !header_displayed %]
+ <p><b>Flags:</b></p>
+ [% header_displayed = 1 %]
[% END %]
- </tr>
+ [% IF flag.setter.name %]
+ <span title="[% flag.setter.name FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
+ [% ELSE %]
+ [% flag.setter.nick FILTER html %]:
+ [% END %]
+ [%+ type.name FILTER html FILTER no_break %][% flag.status %]
+ [% IF flag.requestee %]
+ [% IF flag.requestee.name %]
+ (<span title="[% flag.requestee.name FILTER html %]">[% flag.requestee.nick FILTER html %]</span>)
+ [% ELSE %]
+ ([% flag.requestee.nick FILTER html %])
+ [% END %]
+ [% END %]<br>
+ [% END %]
+ [% END %]
+[% END %]
+
+[%# Display a table row for unset flags %]
+
+[% BLOCK flag_row %]
+[%# if WEBKIT_CHANGES %]
+ [% IF type.name == 'in-rietveld' %]
+ <tr style='display:none'>
+ [% ELSE %]
+ <tr>
+ [% END %]
+[%# endif // WEBKIT_CHANGES %]
+ [% IF first_cell_empty %]
+ <td> </td>
+ <td>
+ [% ELSE %]
+ <td colspan="2">
[% END %]
-</table>
+ [% addl_text FILTER html %]
+ <label title="[% type.description FILTER html %]" for="flag_type-[% type.id %]">
+ [%- type.name FILTER html FILTER no_break %]</label>
+ </td>
+ <td>
+ <select id="flag_type-[% type.id %]" name="flag_type-[% type.id %]"
+ title="[% type.description FILTER html %]"
+ [% " disabled=\"disabled\"" UNLESS (type.is_requestable && user.can_request_flag(type)) || user.can_set_flag(type) %]
+ onchange="toggleRequesteeField(this);"
+ class="flag_select flag_type-[% type.id %]">
+ <option value="X"></option>
+ [% IF type.is_requestable && user.can_request_flag(type) %]
+ <option value="?">?</option>
+ [% END %]
+ [% IF user.can_set_flag(type) %]
+ <option value="+">+</option>
+ <option value="-">-</option>
+ [% END %]
+ </select>
+ </td>
+ [% IF any_flags_requesteeble %]
+ <td>
+ [% IF type.is_requestable && type.is_requesteeble %]
+ <span style="white-space: nowrap;">
+ [% SET grant_list = [] %]
+ [% IF Param('usemenuforusers') %]
+ [% grant_list = type.grant_list %]
+ [% END %]
+ [% INCLUDE global/userselect.html.tmpl
+ name => "requestee_type-$type.id"
+ id => "requestee_type-$type.id"
+ multiple => type.is_multiplicable * 3
+ emptyok => !type.is_multiplicable
+ value => ""
+ custom_userlist => grant_list
+ classes => ["requestee"]
+ %]
+
+ </span>
+ [% END %]
+ </td>
+ [% END %]
+ </tr>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/custom/global/choose-product.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/global/choose-product.html.tmpl
index b562940..e4f074a 100644
--- a/Websites/bugs.webkit.org/template/en/custom/global/choose-product.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/global/choose-product.html.tmpl
@@ -31,15 +31,17 @@
[% IF target == "enter_bug.cgi" %]
[% title = "Enter $terms.Bug" %]
- [% subheader = BLOCK %]First, you must pick a product on which to enter [% terms.abug %]. [% END %]
+ [% h2 = BLOCK %]First, you must pick a product on which to enter [% terms.abug %]: [% END %]
[% ELSIF target == "describecomponents.cgi" %]
- [% title = "$terms.Bugzilla Component Descriptions" %]
- [% subheader = "Please specify the product whose components you want described." %]
+ [% title = "Browse" %]
+ [% h2 = "Select a product category to browse:" %]
[% END %]
[% DEFAULT title = "Choose a Product" %]
[% PROCESS global/header.html.tmpl %]
+<h2>[% h2 FILTER html %]</h2>
+
<table>
[% FOREACH c = classifications %]
@@ -53,9 +55,9 @@
[% FOREACH p = c.products %]
<tr>
<th align="right" valign="top">
- <a href="[% target %]?product=[% p.name FILTER url_quote -%]
- [%- IF cloned_bug_id %]&cloned_bug_id=[% cloned_bug_id FILTER url_quote %][% END -%]
- [%- IF format %]&format=[% format FILTER url_quote %][% END %]">
+ <a href="[% target %]?product=[% p.name FILTER uri -%]
+ [%- IF cloned_bug_id %]&cloned_bug_id=[% cloned_bug_id FILTER uri %][% END -%]
+ [%- IF format %]&format=[% format FILTER uri %][% END %]">
[% p.name FILTER html FILTER no_break %]</a>:
</th>
diff --git a/Websites/bugs.webkit.org/template/en/custom/global/header.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/global/header.html.tmpl
index 66c24bb..707cbd2 100644
--- a/Websites/bugs.webkit.org/template/en/custom/global/header.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/global/header.html.tmpl
@@ -46,8 +46,41 @@
header_addl_info = ""
onload = ""
style_urls = []
+ yui = []
%]
+[% SET yui_css = {
+ autocomplete => 1,
+ calendar => 1,
+ datatable => 1,
+ button => 1,
+} %]
+
+[%# Note: This is simple dependency resolution--you can't have dependencies
+ # that depend on each other. You have to specify all of a module's deps,
+ # if that module is going to be specified in "yui".
+ #%]
+[% SET yui_deps = {
+ autocomplete => ['json', 'connection', 'datasource'],
+ datatable => ['json', 'connection', 'datasource', 'element'],
+} %]
+
+[%# When using certain YUI modules, we need to process certain
+ # extra JS templates.
+ #%]
+[% SET yui_templates = {
+ datatable => ['global/value-descs.js.tmpl'],
+} %]
+
+[%# These are JS URLs that are *always* on the page and come before
+ # every other JS URL.
+ #%]
+[% SET starting_js_urls = [
+ "js/yui/yahoo-dom-event/yahoo-dom-event.js",
+ "js/yui/cookie/cookie-min.js",
+] %]
+
+
[%# We should be able to set the default value of the header variable
# to the value of the title variable using the DEFAULT directive,
# but that doesn't work if a caller sets header to the empty string
@@ -59,105 +92,48 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
-<html>
+<html lang="en">
<head>
+ [% Hook.process("start") %]
<title>[% title %]</title>
+ [% IF Param('utf8') %]
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ [% END %]
+
[%# Migration note: contents of the old Param 'headerhtml' would go here %]
[% PROCESS "global/site-navigation.html.tmpl" %]
[% PROCESS 'global/setting-descs.none.tmpl' %]
- [%# Set up the skin CSS cascade:
- # 1. Standard Bugzilla stylesheet set (persistent)
- # 2. Standard Bugzilla stylesheet set (selectable)
- # 3. All third-party "skin" stylesheet sets (selectable)
- # 4. Page-specific styles
- # 5. Custom Bugzilla stylesheet set (persistent)
- # "Selectable" skin file sets may be either preferred or alternate.
- # Exactly one is preferred, determined by the "skin" user preference.
- #%]
- [% IF user.settings.skin.value != 'standard' %]
- [% user_skin = user.settings.skin.value %]
- [% END %]
- [% style_urls.unshift('skins/standard/global.css') %]
+ [% SET yui = yui_resolve_deps(yui, yui_deps) %]
+ [% SET css_sets = css_files(style_urls, yui, yui_css) %]
[%# CSS cascade, part 1: Standard Bugzilla stylesheet set (persistent).
# Always present.
#%]
- [% FOREACH style_url = style_urls %]
- <link href="[% style_url FILTER html %]"
- rel="stylesheet"
- type="text/css">
- [% END %]
- <!--[if IE]>
- [%# Internet Explorer treats [if IE] HTML comments as uncommented.
- # Use it to import CSS fixes so that Bugzilla looks decent on IE, too.
- #%]
- <link href="skins/standard/IE-fixes.css"
- rel="stylesheet"
- type="text/css">
- <![endif]-->
-
- [%# CSS cascade, part 2: Standard Bugzilla stylesheet set (selectable)
- # Present if skin selection is enabled.
+ [%# This allows people to switch back to the "Classic" skin if they
+ # are in another skin.
#%]
- [% IF user.settings.skin.is_enabled %]
- [% FOREACH style_url = style_urls %]
- <link href="[% style_url FILTER html %]"
- rel="[% 'alternate ' IF user_skin %]stylesheet"
- title="[% setting_descs.standard FILTER html %]"
- type="text/css">
- [% END %]
- <!--[if IE]>
- [%# Internet Explorer treats [if IE] HTML comments as uncommented.
- # Use it to import CSS fixes so that Bugzilla looks decent on IE,
- # too.
- #%]
- <link href="skins/standard/IE-fixes.css"
- rel="[% 'alternate ' IF user_skin %]stylesheet"
- title="[% setting_descs.standard FILTER html %]"
- type="text/css">
- <![endif]-->
+ <link href="[% 'skins/standard/global.css' FILTER mtime FILTER html %]"
+ rel="alternate stylesheet"
+ title="[% setting_descs.standard FILTER html %]">
+ [% FOREACH style_url = css_sets.standard %]
+ [% PROCESS format_css_link css_set_name = 'standard' %]
[% END %]
- [%# CSS cascade, part 3: Third-party stylesheet set (selectable).
- # All third-party skins are present if skin selection is enabled.
- # The admin-selected skin is always present.
+ [%# CSS cascade, part 2 & 3: Third-party stylesheet set (selected and
+ # selectable). All third-party skins are present as alternate
+ # stylesheets, even if they are not currently in use.
#%]
- [% FOREACH contrib_skin = user.settings.skin.legal_values %]
- [% NEXT IF contrib_skin == 'standard' %]
- [% NEXT UNLESS contrib_skin == user_skin
- OR user.settings.skin.is_enabled %]
- [% contrib_skin = contrib_skin FILTER url_quote %]
- [% IF contrib_skin.match('\.css$') %]
- [%# 1st skin variant: single-file stylesheet %]
- <link href="[% "skins/contrib/$contrib_skin" %]"
- rel="[% 'alternate ' UNLESS contrib_skin == user_skin %]stylesheet"
- title="[% contrib_skin FILTER html %]"
- type="text/css">
- [% ELSE %]
- [%# 2nd skin variant: stylesheet set %]
- [% FOREACH style_url = style_urls %]
- [% IF style_url.match('^skins/standard/') %]
- <link href="[% style_url.replace('^skins/standard/',
- "skins/contrib/$contrib_skin/") %]"
- rel="[% 'alternate ' UNLESS contrib_skin == user_skin %]stylesheet"
- title="[% contrib_skin FILTER html %]"
- type="text/css">
- [% END %]
- [% END %]
- <!--[if IE]>
- [%# Internet Explorer treats [if IE] HTML comments as uncommented.
- # Use it to import CSS fixes so that Bugzilla looks decent on IE,
- # too.
- #%]
- <link href="skins/contrib/[% contrib_skin FILTER html %]/IE-fixes.css"
- rel="[% 'alternate ' UNLESS contrib_skin == user_skin %]stylesheet"
- title="[% contrib_skin FILTER html %]"
- type="text/css">
- <![endif]-->
+ [% FOREACH style_url = css_sets.skin %]
+ [% PROCESS format_css_link css_set_name = user.settings.skin.value %]
+ [% END %]
+
+ [% FOREACH alternate_skin = css_sets.alternate.keys %]
+ [% FOREACH style_url = css_sets.alternate.$alternate_skin %]
+ [% PROCESS format_css_link css_set_name = alternate_skin %]
[% END %]
[% END %]
@@ -173,49 +149,95 @@
# Always present. Site administrators may override all other style
# definitions, including skins, using custom stylesheets.
#%]
- [% FOREACH style_url = style_urls %]
- [% IF style_url.match('^skins/standard/') %]
- <link href="[% style_url.replace('^skins/standard/', "skins/custom/")
- FILTER html %]" rel="stylesheet" type="text/css">
- [% END %]
- [% END %]
- <!--[if IE]>
- [%# Internet Explorer treats [if IE] HTML comments as uncommented.
- # Use it to import CSS fixes so that Bugzilla looks decent on IE, too.
- #%]
- <link href="skins/custom/IE-fixes.css"
- rel="stylesheet"
- type="text/css">
- <![endif]-->
-
-
- [% IF javascript %]
- <script type="text/javascript">
- [% javascript %]
- </script>
+ [% FOREACH style_url = css_sets.custom %]
+ [% PROCESS format_css_link css_set_name = 'standard' %]
[% END %]
- [% IF javascript_urls %]
- [% FOREACH javascript_url = javascript_urls %]
- <script src="[% javascript_url FILTER html %]" type="text/javascript"></script>
- [% END %]
- [% IF javascript_urls.grep('yui/').size %]
- <script type="text/javascript">
- YAHOO.namespace('bugzilla');
- if( YAHOO.env.ua.gecko )
- YAHOO.util.Event._simpleRemove(window, "unload", YAHOO.util.Event._unload);
- </script>
- [% END %]
+ [%# YUI Scripts %]
+ [% FOREACH yui_name = yui %]
+ [% starting_js_urls.push("js/yui/$yui_name/${yui_name}-min.js") %]
+ [% END %]
+ [% starting_js_urls.push('js/global.js') %]
+
+ [% FOREACH javascript_url = starting_js_urls %]
+ [% PROCESS format_js_link %]
[% END %]
-[%# if WEBKIT_CHANGES %]
+ <script type="text/javascript">
+ <!--
+ YAHOO.namespace('bugzilla');
+ YAHOO.util.Event.addListener = function (el, sType, fn, obj, overrideContext) {
+ if ( ("onpagehide" in window || YAHOO.env.ua.gecko) && sType === "unload") { sType = "pagehide"; };
+ var capture = ((sType == "focusin" || sType == "focusout") && !YAHOO.env.ua.ie) ? true : false;
+ return this._addListener(el, this._getType(sType), fn, obj, overrideContext, capture);
+ };
+ if ( "onpagehide" in window || YAHOO.env.ua.gecko) {
+ YAHOO.util.Event._simpleRemove(window, "unload",
+ YAHOO.util.Event._unload);
+ }
+ [%# The language selector needs javascript to set its cookie,
+ # so it is hidden in HTML/CSS by the "bz_default_hidden" class.
+ # If the browser can run javascript, it will then "unhide"
+ # the language selector using the following code.
+ #%]
+ function unhide_language_selector() {
+ YAHOO.util.Dom.removeClass(
+ 'lang_links_container', 'bz_default_hidden'
+ );
+ }
+ YAHOO.util.Event.onDOMReady(unhide_language_selector);
+
+ [%# Make some Bugzilla information available to all scripts.
+ # We don't import every parameter and constant because we
+ # don't want to add a lot of uncached JS to every page.
+ #%]
+ var BUGZILLA = {
+ param: {
+ cookiepath: '[% Param('cookiepath') FILTER js %]',
+ maxusermatches: [% Param('maxusermatches') FILTER js %]
+ },
+ constant: {
+ COMMENT_COLS: [% constants.COMMENT_COLS FILTER js %]
+ },
+ string: {
+ [%# Please keep these in alphabetical order. %]
+
+ attach_desc_required:
+ 'You must enter a Description for this attachment.',
+ component_required:
+ 'You must select a Component for this [% terms.bug %].',
+ description_required:
+ 'You must enter a Description for this [% terms.bug %].',
+ short_desc_required:
+ 'You must enter a Summary for this [% terms.bug %].',
+ version_required:
+ 'You must select a Version for this [% terms.bug %].'
+ }
+ };
+
+ [% FOREACH yui_name = yui %]
+ [% FOREACH yui_template = yui_templates.$yui_name %]
+ [% INCLUDE $yui_template %]
+ [% END %]
+ [% END %]
+ [% IF javascript %]
+ [% javascript %]
+ [% END %]
+ // -->
+ </script>
+
+ [% FOREACH javascript_url = javascript_urls %]
+ [% PROCESS format_js_link %]
+ [% END %]
+
+[%# if WEBKIT_CHANGES #%]
[%# this puts the live bookmark up on firefox for the RSS feed %]
[% IF rsslink %]
<link rel="alternate"
type="application/rss+xml" title="RSS 1.0"
href="[% rsslink FILTER html %]">
[% END %]
-[%# endif // WEBKIT_CHANGES %]
+[%# endif // WEBKIT_CHANGES #%]
[%# Required for the 'Autodiscovery' feature in Firefox 2 and IE 7. %]
<link rel="search" type="application/opensearchdescription+xml"
@@ -232,39 +254,56 @@
class="[% urlbase.replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') %]
[% FOREACH class = bodyclasses %]
[% ' ' %][% class FILTER css_class_quote %]
- [% END %]">
+ [% END %] yui-skin-sam">
[%# Migration note: the following file corresponds to the old Param
# 'bannerhtml'
#%]
-
<div id="header">
+[%# if WEBKIT_CHANGES #%]
[%# INCLUDE global/banner.html.tmpl #%]
+[%# endif // WEBKIT_CHANGES #%]
<table border="0" cellspacing="0" cellpadding="0" id="titles">
<tr>
<td id="title">
- <p>WebKit [% terms.Bugzilla %]
-[%# if WEBKIT_CHANGES %]
- [%# " – $header" IF header %]</p>
-[%# endif // WEBKIT_CHANGES %]
+[%# if WEBKIT_CHANGES #%]
+ [%# " – $header" IF header #%]</p>
+[%# endif // WEBKIT_CHANGES #%]
</td>
-[%# WEBKIT_CHANGES: Removed subheader and header_addl_info output %]
+[%# WEBKIT_CHANGES: Removed subheader and header_addl_info output #%]
</tr>
</table>
-[%# if WEBKIT_CHANGES %]
+<table id="lang_links_container" cellpadding="0" cellspacing="0"
+ class="bz_default_hidden"><tr><td>
+[% IF Bugzilla.languages.size > 1 %]
+ <ul class="links">
+ [% FOREACH lang = Bugzilla.languages.sort %]
+ <li>[% IF NOT loop.first %]<span class="separator"> | </span>[% END %]
+ [% IF lang == current_language %]
+ <span class="lang_current">[% lang FILTER html FILTER upper %]</span>
+ [% ELSE %]
+ <a href="#" onclick="set_language('[% lang FILTER none %]');">
+ [%- lang FILTER html FILTER upper %]</a>
+ [% END %]
+ </li>
+ [% END %]
+ </ul>
+[% END %]
+</td></tr></table>
+
+[%# if WEBKIT_CHANGES #%]
[% IF header || subheader %]
<div id="bug_title">[% header %][% ": $subheader" IF subheader %]</div>
[% END %]
-[%# endif // WEBKIT_CHANGES %]
+[%# endif // WEBKIT_CHANGES #%]
[% PROCESS "global/common-links.html.tmpl" qs_suffix = "_top" %]
-
-</div>
+</div> [%# header %]
<div id="bugzilla-body">
@@ -275,3 +314,41 @@
[% IF message %]
<div id="message">[% message %]</div>
[% END %]
+
+[% BLOCK format_css_link %]
+ [% IF style_url.match('/IE-fixes\.css') %]
+ <!--[if lte IE 7]>
+ [%# Internet Explorer treats [if IE] HTML comments as uncommented.
+ # We use it to import CSS fixes so that Bugzilla looks decent on IE 7
+ # and below.
+ #%]
+ [% END %]
+
+ [% IF css_set_name == 'standard'
+ OR css_set_name == user.settings.skin.value
+ %]
+ [% SET css_rel = 'stylesheet' %]
+ [% SET css_set_display_name = setting_descs.${user.settings.skin.value}
+ || user.settings.skin.value %]
+ [% ELSE %]
+ [% SET css_rel = 'alternate stylesheet' %]
+ [% SET css_set_display_name = setting_descs.$css_set_name || css_set_name %]
+ [% END %]
+
+ [% IF css_set_name == 'standard' %]
+ [% SET css_title_link = '' %]
+ [% ELSE %]
+ [% css_title_link = BLOCK ~%]
+ title="[% css_set_display_name FILTER html %]"
+ [% END %]
+ [% END %]
+
+ <link href="[% style_url FILTER html %]" rel="[% css_rel FILTER none %]"
+ type="text/css" [% css_title_link FILTER none %]>
+
+ [% '<![endif]-->' IF style_url.match('/IE-fixes\.css') %]
+[% END %]
+
+[% BLOCK format_js_link %]
+ <script type="text/javascript" src="[% javascript_url FILTER mtime FILTER html %]"></script>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/custom/list/list.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/list/list.html.tmpl
index 9b1a9a4..36d5514 100644
--- a/Websites/bugs.webkit.org/template/en/custom/list/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/list/list.html.tmpl
@@ -28,54 +28,48 @@
[%# Template Initialization #%]
[%############################################################################%]
-[% PROCESS global/variables.none.tmpl %]
+[% PROCESS "global/field-descs.none.tmpl" %]
[% title = "$terms.Bug List" %]
[% IF searchname || defaultsavename %]
[% title = title _ ": " _ (searchname OR defaultsavename) FILTER html %]
[% END %]
-[% qorder = order FILTER url_quote IF order %]
+[% qorder = order FILTER uri IF order %]
[%############################################################################%]
[%# Page Header #%]
[%############################################################################%]
-[%# if WEBKIT_CHANGES %]
+[%# if WEBKIT_CHANGES #%]
[% PROCESS global/header.html.tmpl
title = title
style = style
rsslink = "buglist.cgi?$urlquerypart&title=$title&ctype=rss"
- javascript_urls = [ "js/util.js", "js/field.js",
- "js/yui/yahoo-dom-event.js", "js/yui/calendar.js" ]
- style_urls = [ "skins/standard/buglist.css", "skins/standard/yui/calendar.css" ]
+ yui = [ 'autocomplete', 'calendar' ]
+ javascript_urls = [ "js/util.js", "js/field.js" ]
+ style_urls = [ "skins/standard/buglist.css" ]
doc_section = "query.html#list"
%]
-[%# endif // WEBKIT_CHANGES %]
+[%# endif // WEBKIT_CHANGES #%]
-<div class="bz_query_head" align="center">
+<div class="bz_query_head">
<span class="bz_query_timestamp">
- [% IF Param('timezone') %]
- <b>[% time2str("%a %b %e %Y %T %Z", currenttime, Param('timezone')) %]</b><br>
- [% ELSE %]
- <b>[% time2str("%a %b %e %Y %T", currenttime) %]</b><br>
- [% END %]
+ [% currenttime FILTER time('%a %b %e %Y %T %Z') FILTER html %]<br>
</span>
[% IF debug %]
- <p class="bz_query_debug">
- [% FOREACH debugline = debugdata %]
- [% debugline FILTER html %]<br>
- [% END %]
- </p>
<p class="bz_query">[% query FILTER html %]</p>
+ [% IF query_explain.defined %]
+ <pre class="bz_query_explain">[% query_explain FILTER html %]</pre>
+ [% END %]
[% END %]
[% IF user.settings.display_quips.value == 'on' %]
[% DEFAULT quip = "$terms.Bugzilla would like to put a random quip here, but no one has entered any." %]
<span class="bz_quip">
- <a href="quips.cgi"><i>[% quip FILTER html %]</i></a>
+ <a href="quips.cgi"><em>[% quip FILTER html %]</em></a>
</span>
[% END %]
@@ -88,6 +82,29 @@
</h2>
[% END %]
+[% SET shown_types = [
+ 'notequals', 'regexp', 'notregexp', 'lessthan', 'lessthaneq',
+ 'greaterthan', 'greaterthaneq', 'changedbefore', 'changedafter',
+ 'changedfrom', 'changedto', 'changedby', 'notsubstring', 'nowords',
+ 'nowordssubstr', 'notmatches',
+] %]
+<ul class="search_description">
+[% FOREACH desc_item = search_description %]
+ <li>
+ <strong>[% field_descs.${desc_item.field} FILTER html %]:</strong>
+ [% IF shown_types.contains(desc_item.type) || debug %]
+ ([% search_descs.${desc_item.type} FILTER html %])
+ [% END %]
+ [% FOREACH val IN desc_item.value.split(',') %]
+ [%+ display_value(desc_item.field, val) FILTER html %][% ',' UNLESS loop.last %]
+ [% END %]
+ [% IF debug %]
+ (<code>[% desc_item.term FILTER html %]</code>)
+ [% END %]
+ </li>
+[% END %]
+</ul>
+
<hr>
[%############################################################################%]
@@ -95,9 +112,7 @@
[%############################################################################%]
[% IF bugs.size > 9 %]
- <span class="bz_result_count">
- [% bugs.size %] [%+ terms.bugs %] found.
- </span>
+ [% PROCESS num_results %]
[% END %]
[%############################################################################%]
@@ -119,15 +134,19 @@
[%# Succeeding Status Line #%]
[%############################################################################%]
-<span class="bz_result_count">
- [% IF bugs.size == 0 %]
- [% terms.zeroSearchResults %].
- [% ELSIF bugs.size == 1 %]
- One [% terms.bug %] found.
- [% ELSE %]
- [% bugs.size %] [%+ terms.bugs %] found.
- [% END %]
-</span>
+[% PROCESS num_results %]
+
+[% IF bugs.size == 0 %]
+ <ul class="zero_result_links">
+ <li>[% PROCESS enter_bug_link %]</li>
+ [% IF one_product.defined %]
+ <li><a href="enter_bug.cgi">File a new [% terms.bug %] in a
+ different product</a></li>
+ [% END %]
+ <li><a href="[% PROCESS edit_search_url %]">Edit this search</a></li>
+ <li><a href="query.cgi">Start a new search</a></li>
+ </ul>
+[% END %]
<br>
@@ -165,11 +184,19 @@
<input type="submit" value="XML" id="xml">
</form>
- [% IF user.in_group(Param('timetrackinggroup')) %]
+ [% IF user.is_timetracker %]
<form method="post" action="summarize_time.cgi">
<input type="hidden" name="id" value="[% buglist_joined FILTER html %]">
<input type="submit" id="timesummary" value="Time Summary">
</form>
+ [% IF time_summary_limited %]
+ <small>
+ Time Summary will only include the [% terms.bugs %] shown above. In order to
+ to see a time summary for all [% terms.bugs %] found by the search, you can
+ <a href="buglist.cgi?[% urlquerypart FILTER html %]
+ [%- "&order=$qorder" FILTER html IF order %]&limit=0">
+ Show all search results</a>.</small>
+ [% END %]
[% END %]
</td>
@@ -177,7 +204,7 @@
<td valign="middle" class="bz_query_links">
<a href="buglist.cgi?
- [% urlquerypart FILTER html %]&ctype=csv">CSV</a> |
+ [% urlquerypart FILTER html %]&ctype=csv&human=1">CSV</a> |
<a href="buglist.cgi?
[% urlquerypart FILTER html %]&title=
[%# if WEBKIT_CHANGES %]
@@ -187,7 +214,7 @@
[% urlquerypart FILTER html %]&ctype=ics">iCalendar</a> |
<a href="colchange.cgi?
[% urlquerypart FILTER html %]&query_based_on=
- [% defaultsavename OR searchname FILTER url_quote %]">Change Columns</a> |
+ [% defaultsavename OR searchname FILTER uri %]">Change Columns</a> |
[% IF bugs.size > 1 && caneditbugs && !dotweak %]
<a href="buglist.cgi?[% urlquerypart FILTER html %]
@@ -196,7 +223,7 @@
|
[% END %]
- [% IF bugowners %]
+ [% IF bugowners && user.id %]
<a href="mailto:
[% bugowners FILTER html %]">Send Mail to [% terms.Bug %] Assignees</a> |
[% END %]
@@ -207,19 +234,15 @@
[% END %]
<td valign="middle" class="bz_query_edit">
- [% editqueryname = searchname OR defaultsavename OR '' %]
- <a href="query.cgi?[% urlquerypart FILTER html %]
- [% IF editqueryname != '' %]&known_name=
- [% editqueryname FILTER url_quote %]
- [% END %]">Edit Search</a>
+ <a href="[% PROCESS edit_search_url %]">Edit Search</a>
</td>
[% IF searchtype == "saved" %]
<td valign="middle" nowrap="nowrap" class="bz_query_forget">
|
<a href="buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=
- [% searchname FILTER url_quote %]&token=
- [% issue_hash_token([search_id, searchname]) FILTER url_quote %]">
+ [% searchname FILTER uri %]&token=
+ [% issue_hash_token([search_id, searchname]) FILTER uri %]">
Forget Search '[% searchname FILTER html %]'</a>
</td>
[% ELSE %]
@@ -232,18 +255,18 @@
value="[% urlquerypart FILTER html %][% "&order=$qorder" FILTER html IF order %]">
<input type="hidden" name="cmdtype" value="doit">
<input type="hidden" name="remtype" value="asnamed">
+ <input type="hidden" name="token" value="[% issue_hash_token(['savedsearch']) FILTER html %]">
<input type="text" id="save_newqueryname" name="newqueryname" size="20"
- value="[% defaultsavename FILTER html %]">
+ title="New query name" value="[% defaultsavename FILTER html %]">
</form>
</td>
[% END %]
</tr>
</table>
-[% IF cgi.param('product').size == 1 && cgi.param('product') != "" %]
+[% IF one_product.defined && bugs.size %]
<p class="bz_query_single_product">
- <a href="enter_bug.cgi?product=[% cgi.param('product') FILTER url_quote %]">File
- a new [% terms.bug %] in the "[% cgi.param('product') FILTER html %]" product</a>
+ [% PROCESS enter_bug_link %]
</p>
[% END %]
@@ -252,3 +275,44 @@
[%############################################################################%]
[% PROCESS global/footer.html.tmpl %]
+
+[%##########%]
+[%# Blocks #%]
+[%##########%]
+
+[% BLOCK edit_search_url %]
+ [% editqueryname = searchname OR defaultsavename OR '' %]
+ query.cgi?[% urlquerypart FILTER html %]
+ [%- IF editqueryname != '' %]&known_name=
+ [%- editqueryname FILTER uri %]
+ [% END %]
+[% END %]
+
+[% BLOCK enter_bug_link %]
+ <a href="enter_bug.cgi
+ [%- IF one_product.defined %]?product=
+ [%- one_product.name FILTER uri %][% END %]">File
+ a new [% terms.bug %]
+ [% IF one_product.defined %]
+ in the "[% one_product.name FILTER html %]" product
+ [% END %]</a>
+[% END %]
+
+[% BLOCK num_results %]
+ <span class="bz_result_count">
+ [% IF bugs.size == 0 %]
+ <span class="zero_results">[% terms.zeroSearchResults %].</span>
+ [% ELSIF default_limited AND bugs.size >= Param('default_search_limit') %]
+ This result was limited to [% Param('default_search_limit') FILTER html %]
+ [%+ terms.bugs %].
+ <a href="buglist.cgi?[% urlquerypart FILTER html %]
+ [%- "&order=$qorder" FILTER html IF order %]&limit=0">See
+ all search results for this query</a>.
+ [% time_summary_limited = 1 %]
+ [% ELSIF bugs.size == 1 %]
+ One [% terms.bug %] found.
+ [% ELSE %]
+ [% bugs.size %] [%+ terms.bugs %] found.
+ [% END %]
+ </span>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/custom/request/email.txt.tmpl b/Websites/bugs.webkit.org/template/en/custom/request/email.txt.tmpl
index b3f942b..408ad63 100644
--- a/Websites/bugs.webkit.org/template/en/custom/request/email.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/request/email.txt.tmpl
@@ -24,30 +24,34 @@
[% bugidsummary = bug.bug_id _ ': ' _ bug.short_desc %]
[% attidsummary = attachment.id _ ': ' _ attachment.description %]
+[% flagtype_name = flag ? flag.type.name : old_flag.type.name %]
[% statuses = { '+' => "granted" , '-' => 'denied' , 'X' => "canceled" ,
'?' => "asked" } %]
[% to_identity = "" %]
[% on_behalf_of = 0 %]
-[% IF flag.status == '?' %]
+[% action = flag.status || 'X' %]
+
+[% IF flag && flag.status == '?' %]
[% subject_status = "requested" %]
- [% IF flag.setter.id == user.id %]
+ [% IF flag.setter_id == user.id %]
[% to_identity = flag.requestee.identity _ " for" %]
[% ELSE %]
[% on_behalf_of = 1 %]
[% IF flag.requestee %][% to_identity = " to " _ flag.requestee.identity %][% END %]
[% END %]
[% ELSE %]
- [% IF flag.requester %]
- [% to_identity = flag.requester.identity _ "'s request for" %]
+ [% IF old_flag && old_flag.status == '?' %]
+ [% to_identity = old_flag.setter.identity _ "'s request for" %]
[% END %]
- [% subject_status = statuses.${flag.status} %]
+ [% subject_status = statuses.$action %]
[% END %]
From: [% Param('mailfrom') %]
To: [% to %]
-Subject: [% flag.type.name %] [%+ subject_status %]: [[% terms.Bug %] [%+ bug.bug_id %]] [% bug.short_desc %]
+Subject: [% flagtype_name %] [%+ subject_status %]: [[% terms.Bug %] [%+ bug.bug_id %]] [% bug.short_desc %]
[%- IF attachment %] :
- [Attachment [% attachment.id %]] [% attachment.description %][% END %]
+ [Attachment [% attachment.id %]] [% attachment.description FILTER clean_text %][% END %]
+Date: [% date %]
X-Bugzilla-Type: request
[%+ threadingmarker %]
@@ -55,10 +59,10 @@
[%- FILTER bullet = wrap(80) -%]
[% IF on_behalf_of %]
-[% user.identity %] has reassigned [% flag.setter.identity %]'s request for [% flag.type.name %]
+[% user.identity %] has reassigned [% flag.setter.identity %]'s request for [% flagtype_name %]
[% to_identity %]:
[% ELSE %]
-[% user.identity %] has [% statuses.${flag.status} %] [%+ to_identity %] [%+ flag.type.name %]:
+[% user.identity %] has [% statuses.$action %] [%+ to_identity %] [%+ flagtype_name %]:
[% END %]
[% terms.Bug %] [%+ bugidsummary %]
@@ -69,12 +73,18 @@
[% FILTER bullet = wrap(80) %]
Attachment [% attidsummary %]
[%- END %]
+[%# if WEBKIT_CHANGES #%]
[%+ urlbase %]attachment.cgi?id=[% attachment.id %]&action=review
+[%# endif // WEBKIT_CHANGES #%]
[%- END %]
+
+[%- Hook.process('after_summary') -%]
+
[%- FILTER bullet = wrap(80) %]
[% USE Bugzilla %]
-[% IF Bugzilla.cgi.param("comment") && Bugzilla.cgi.param("comment").length > 0 %]
+[%-# .defined is necessary to avoid a taint issue in Perl < 5.10.1, see bug 509794. %]
+[% IF Bugzilla.cgi.param("comment").defined && Bugzilla.cgi.param("comment").length > 0 %]
------- Additional Comments from [% user.identity %]
[%+ Bugzilla.cgi.param("comment") %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/custom/request/queue.html.tmpl b/Websites/bugs.webkit.org/template/en/custom/request/queue.html.tmpl
index 7a14852..9d22359 100644
--- a/Websites/bugs.webkit.org/template/en/custom/request/queue.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/custom/request/queue.html.tmpl
@@ -23,18 +23,43 @@
[% USE Bugzilla %]
[% cgi = Bugzilla.cgi %]
-[% PROCESS "global/js-products.html.tmpl" %]
-
[% PROCESS global/header.html.tmpl
title="Request Queue"
style = "
table.requests th { text-align: left; }
table#filtering th { text-align: right; }
"
- onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, 'Any');"
- javascript_urls=["js/productform.js"]
+ onload="var f = document.request_form; selectProduct(f.product, f.component, null, null, 'Any');"
+ javascript_urls=["js/productform.js", "js/field.js"]
+ style_urls = ['skins/standard/buglist.css']
+ yui = ['autocomplete']
%]
+<script type="text/javascript">
+ var useclassification = false; // No classification level in use
+ var first_load = true; // Is this the first time we load the page?
+ var last_sel = []; // Caches last selection
+ var cpts = new Array();
+ [% n = 1 %]
+ [% IF Param('useclassification') %]
+ [% FOREACH clas = user.get_selectable_classifications %]
+ [% FOREACH prod = user.get_selectable_products(clas.id) %]
+ [%+ PROCESS js_comp %]
+ [% END %]
+ [% END %]
+ [% ELSE %]
+ [% FOREACH prod = user.get_selectable_products %]
+ [%+ PROCESS js_comp %]
+ [% END %]
+ [% END %]
+</script>
+
+[% BLOCK js_comp %]
+ cpts['[% n %]'] = [
+ [%- FOREACH comp = prod.components %]'[% comp.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%]];
+ [% n = n+1 %]
+[% END %]
+
<p>
When you are logged in, only requests made by you or addressed to you
are shown by default. You can change the criteria using the form below.
@@ -42,22 +67,44 @@
to some group are shown by default.
</p>
-<form action="request.cgi" method="get">
+<form id="request_form" name="request_form" action="request.cgi" method="get">
<input type="hidden" name="action" value="queue">
<table id="filtering">
<tr>
<th>Requester:</th>
- <td><input type="text" name="requester" value="[% cgi.param('requester') FILTER html %]" size="20"
- title="Requester's email address"></td>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ id => "requester"
+ name => "requester"
+ value => cgi.param('requester')
+ size => 20
+ emptyok => 1
+ field_title => "Requester's email address"
+ %]
+ </td>
<th>Product:</th>
<td>
<select name="product" onchange="selectProduct(this, this.form.component, null, null, 'Any');">
<option value="">Any</option>
- [% FOREACH prod = products %]
- <option value="[% prod.name FILTER html %]"
- [% "selected" IF cgi.param('product') == prod.name %]>
- [% prod.name FILTER html %]</option>
+ [% IF Param('useclassification') %]
+ [% FOREACH c = user.get_selectable_classifications %]
+ <optgroup label="[% c.name FILTER html %]">
+ [% FOREACH p = user.get_selectable_products(c.id) %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF cgi.param('product') == p.name %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
+ </optgroup>
+ [% END %]
+ [% ELSE %]
+ [% FOREACH p = user.get_selectable_products %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF cgi.param('product') == p.name %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
[% END %]
</select>
</td>
@@ -83,8 +130,17 @@
</tr>
<tr>
<th>Requestee:</th>
- <td><input type="text" name="requestee" value="[% cgi.param('requestee') FILTER html %]" size="20"
- title="Requestee's email address or "-" (hyphen) for requests with no requestee"></td>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ id => "requestee"
+ name => "requestee"
+ value => cgi.param('requestee')
+ size => 20
+ emptyok => 1
+ hyphenok => 1
+ field_title => "Requestee's email address or \"-\" (hyphen) for requests with no requestee"
+ %]
+ </td>
<th>Component:</th>
<td>
<select name="component">
@@ -136,28 +192,32 @@
</p>
[% ELSE %]
[% FOREACH request = requests %]
- [% IF loop.first %] [% PROCESS start_new_table %] [% END %]
- [% IF request.$group_field != group_value %]
+ [% IF request.$group_field != group_value || loop.first %]
[% group_value = request.$group_field %]
- [% UNLESS loop.first %]
- </table>
- [% PROCESS start_new_table %]
- [% END %]
+ [% PROCESS display_buglist UNLESS loop.first %]
+ [% PROCESS start_new_table %]
[% END %]
+ [% buglist.${request.bug_id} = 1 %]
<tr>
[% FOREACH column = display_columns %]
[% NEXT IF column == group_field || excluded_columns.contains(column) %]
- <td>[% PROCESS "display_$column" %]</td>
+ <td>
+ [% PROCESS "display_$column" %]
+ [% Hook.process('after_column') %]
+ </td>
[% END %]
</tr>
[% END %]
- </table>
+ [% PROCESS display_buglist %]
[% END %]
[% PROCESS global/footer.html.tmpl %]
[% BLOCK start_new_table %]
- <h3>[% column_headers.$group_field %]: [% (request.$group_field || "None") FILTER html %]</h3>
+ [% buglist = {} %]
+
+ <h3>[% column_headers.$group_field %]:
+ [%+ (request.$group_field || "None") FILTER email FILTER html %]</h3>
<table class="requests" cellspacing="0" cellpadding="4" border="1">
<tr>
[% FOREACH column = display_columns %]
@@ -176,15 +236,16 @@
[% END %]
[% BLOCK display_bug %]
- <a href="show_bug.cgi?id=[% request.bug_id %]">
+ <a href="show_bug.cgi?id=[% request.bug_id %]"
+ [%- ' class="bz_secure"' IF request.restricted %]>
[% request.bug_id %]: [%+ request.bug_summary FILTER html %]</a>
[% END %]
[% BLOCK display_attachment %]
[% IF request.attach_id %]
-[%# if WEBKIT_CHANGES %]
+[%# if WEBKIT_CHANGES #%]
<a href="attachment.cgi?id=[% request.attach_id %]&action=review">
-[%# endif // WEBKIT_CHANGES %]
+[%# endif // WEBKIT_CHANGES #%]
[% request.attach_id %]: [%+ request.attach_summary FILTER html %]</a>
[% ELSE %]
N/A
@@ -192,14 +253,21 @@
[% END %]
[% BLOCK display_requestee %]
- [% request.requestee FILTER html %]
+ [% request.requestee FILTER email FILTER html %]
[% END %]
[% BLOCK display_requester %]
- [% request.requester FILTER html %]
+ [% request.requester FILTER email FILTER html %]
[% END %]
[% BLOCK display_created %]
[% request.created FILTER time %]
[% END %]
+[% BLOCK display_buglist %]
+ </table>
+ [% NEXT UNLESS buglist.keys.size %]
+ <a href="buglist.cgi?bug_id=
+ [%- buglist.keys.nsort.join(",") FILTER html %]">(view as
+ [%+ terms.bug %] list)</a>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/account/auth/login-small.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/auth/login-small.html.tmpl
index 19aaca1..fbe40fb 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/auth/login-small.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/auth/login-small.html.tmpl
@@ -23,68 +23,106 @@
[%# Use the current script name. If an empty name is returned,
# then we are accessing the home page. %]
-[% script_name = cgi.url(Relative => 1) %]
-[% IF !script_name %]
- [% script_name = "index.cgi" %]
+[% login_target = cgi.url("-relative" => 1, "-query" => 1) %]
+[% IF !login_target OR login_target.match("^token.cgi") %]
+ [% login_target = "index.cgi" %]
[% END %]
-[%# If SSL is in use, use 'sslbase', else use 'urlbase'. %]
-[% IF Param("sslbase") != "" && Param("ssl") != "never" %]
- [% script_name = Param("sslbase") _ script_name %]
-[% ELSE %]
- [% script_name = Param("urlbase") _ script_name %]
-[% END %]
+[% login_target = urlbase _ login_target %]
-<form name="login" action="[% script_name FILTER html %]" method="POST">
- <table id="login-small">
- <tr>
- <th align="right"><label for="Bugzilla_login">Login:</label></th>
- <td><input size="20" id="Bugzilla_login" name="Bugzilla_login">
- [% Param('emailsuffix') FILTER html %]</td>
- </tr>
- <tr>
- <th align="right"><label for="Bugzilla_password">Password:</label></th>
- <td>
- <input type="password" size="20" id="Bugzilla_password" name="Bugzilla_password">
- </td>
- </tr>
+<li id="mini_login_container[% qs_suffix %]">
+ <span class="separator">| </span>
+ [% connector = "?" %]
+ [% IF cgi.request_method == "GET" AND cgi.query_string %]
+ [% connector = "&" %]
+ [% END %]
+ [% script_name = login_target _ connector _ "GoAheadAndLogIn=1" %]
+ <a id="login_link[% qs_suffix %]" href="[% script_name FILTER html %]"
+ onclick="return show_mini_login_form('[% qs_suffix %]')">Log In</a>
- [% IF Param('rememberlogin') == 'defaulton' ||
- Param('rememberlogin') == 'defaultoff' %]
- <tr>
- <th> </th>
- <td>
- <input type="checkbox" id="Bugzilla_remember" name="Bugzilla_remember" value="on"
+ [% Hook.process('additional_methods') %]
+
+ <form action="[% login_target FILTER html %]" method="POST"
+ class="mini_login bz_default_hidden"
+ id="mini_login[% qs_suffix FILTER html %]"
+ onsubmit="return check_mini_login_fields( '[% qs_suffix FILTER html %]' );"
+ >
+ <input id="Bugzilla_login[% qs_suffix FILTER html %]"
+ class="bz_login"
+ name="Bugzilla_login"
+ title="Login"
+ onfocus="mini_login_on_focus('[% qs_suffix FILTER js %]')"
+ >
+ <input class="bz_password"
+ id="Bugzilla_password[% qs_suffix FILTER html %]"
+ name="Bugzilla_password"
+ type="password"
+ title="Password"
+ >
+ <input class="bz_password bz_default_hidden bz_mini_login_help" type="text"
+ id="Bugzilla_password_dummy[% qs_suffix %]" value="password"
+ title="Password"
+ onfocus="mini_login_on_focus('[% qs_suffix FILTER js %]')"
+ >
+ [% IF Param('rememberlogin') == 'defaulton' ||
+ Param('rememberlogin') == 'defaultoff'
+ %]
+ <input type="checkbox" id="Bugzilla_remember[% qs_suffix %]"
+ name="Bugzilla_remember" value="on" class="bz_remember"
[%+ "checked" IF Param('rememberlogin') == "defaulton" %]>
- <label for="Bugzilla_remember">Remember my Login</label>
- </td>
- </tr>
+ <label for="Bugzilla_remember[% qs_suffix %]">Remember</label>
[% END %]
-
- [% IF Param('loginnetmask') < 32 %]
- <tr>
- <th> </th>
- <td>
- <input type="checkbox" id="Bugzilla_restrictlogin" name="Bugzilla_restrictlogin"
- checked="checked">
- <label for="Bugzilla_restrictlogin">Restrict this session to this IP address
- (using this option improves security)</label>
- </td>
- </tr>
- [% END %]
-
- <tr>
- <td><input type="submit" name="GoAheadAndLogIn" value="Login"
- id="log_in"></td>
-
- [%# For now, password change requests only apply to the DB
- # verification method #%]
-
- [% IF user.authorizer.can_change_password %]
- <td>[ <a href="index.cgi?GoAheadAndLogIn=1#forgot">Forgot my Password</a> ]</td>
- [% END %]
- </tr>
- </table>
-
-</form>
-
+ <input type="submit" name="GoAheadAndLogIn" value="Log in"
+ id="log_in[% qs_suffix %]">
+ <script type="text/javascript">
+ mini_login_constants = {
+ "login" : "login",
+ "warning" : "You must set the login and password before logging in."
+ };
+ [%# We need this event to fire after autocomplete, because it does
+ # something different depending on whether or not there's already
+ # data in the login and password box.
+ # However, autocomplete happens at all sorts of different times in
+ # different browsers (before or after onDOMReady, before or after
+ # window.onload, in almost all combinations you can imagine).
+ # The only good solution I found is to time the event 200
+ # milliseconds after window.onload for WebKit (doing it immediately
+ # at onload works in Chrome but not in Safari, but I can't detect
+ # them separately using YUI), and right after onDOMReady in Gecko.
+ # The WebKit solution is also fairly guaranteed to work on any
+ # browser (it's just strange, since the fields only populate 200 ms
+ # after the page loads), so it's the default. IE doesn't even
+ # recognize our forms as login forms, so I made it use the Gecko
+ # method also (since it's nicer visually). Opera never autocompletes
+ # forms without user interaction, so it also uses the Gecko method.
+ #%]
+ if (YAHOO.env.ua.gecko || YAHOO.env.ua.ie || YAHOO.env.ua.opera) {
+ YAHOO.util.Event.onDOMReady(function() {
+ init_mini_login_form('[% qs_suffix FILTER html %]');
+ });
+ }
+ else {
+ YAHOO.util.Event.on(window, 'load', function () {
+ window.setTimeout(function() {
+ init_mini_login_form('[% qs_suffix FILTER html %]');
+ }, 200);
+ });
+ }
+ </script>
+ <a href="#" onclick="return hide_mini_login_form('[% qs_suffix %]')">[x]</a>
+ </form>
+</li>
+<li id="forgot_container[% qs_suffix %]">
+ <span class="separator">| </span>
+ <a id="forgot_link[% qs_suffix %]" href="[% script_name FILTER html %]#forgot"
+ onclick="return show_forgot_form('[% qs_suffix %]')">Forgot Password</a>
+ <form action="token.cgi" method="post" id="forgot_form[% qs_suffix %]"
+ class="mini_forgot bz_default_hidden">
+ <label for="login[% qs_suffix FILTER html %]">Login:</label>
+ <input type="text" name="loginname" size="20" id="login[% qs_suffix FILTER html %]">
+ <input id="forgot_button[% qs_suffix %]" value="Reset Password"
+ type="submit">
+ <input type="hidden" name="a" value="reqpw">
+ <a href="#" onclick="return hide_forgot_form('[% qs_suffix %]')">[x]</a>
+ </form>
+</li>
diff --git a/Websites/bugs.webkit.org/template/en/default/account/auth/login.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/auth/login.html.tmpl
index e8f8fa1..122ef6f 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/auth/login.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/auth/login.html.tmpl
@@ -69,17 +69,15 @@
</tr>
[% END %]
- [% IF Param('loginnetmask') < 32 %]
- <tr>
- <th> </th>
- <td>
- <input type="checkbox" id="Bugzilla_restrictlogin" name="Bugzilla_restrictlogin"
- checked="checked">
- <label for="Bugzilla_restrictlogin">Restrict this session to this IP address
- (using this option improves security)</label>
- </td>
- </tr>
- [% END %]
+ <tr>
+ <th> </th>
+ <td>
+ <input type="checkbox" id="Bugzilla_restrictlogin" name="Bugzilla_restrictlogin"
+ checked="checked">
+ <label for="Bugzilla_restrictlogin">Restrict this session to this IP address
+ (using this option improves security)</label>
+ </td>
+ </tr>
</table>
[% PROCESS "global/hidden-fields.html.tmpl"
@@ -93,6 +91,8 @@
</p>
</form>
+[% Hook.process('additional_methods') %]
+
[%# Allow the user to create a new account, or request a token to change
# their password, assuming that our auth method allows that.
#%]
@@ -109,14 +109,13 @@
[% IF user.authorizer.can_change_password %]
<hr>
- <a name="forgot"></a>
- <form method="get" action="token.cgi">
+ <form id="forgot" method="get" action="token.cgi">
<input type="hidden" name="a" value="reqpw">
If you have an account, but have forgotten your password,
enter your login name below and submit a request
to change your password.<br>
<input size="35" name="loginname">
- <input type="submit" id="request" value="Submit Request">
+ <input type="submit" id="request" value="Reset Password">
</form>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/account/cancel-token.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/account/cancel-token.txt.tmpl
index 155c441..6619ded 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/cancel-token.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/cancel-token.txt.tmpl
@@ -37,7 +37,7 @@
Token: [% token %]
Token Type: [% tokentype %]
User: [% emailaddress %]
- Issue Date: [% issuedate %]
+ Issue Date: [% issuedate FILTER time("%Y-%m-%d %H:%M:%S %Z", timezone) %]
Event Data: [% eventdata %]
Canceled Because: [% PROCESS cancelactionmessage %]
diff --git a/Websites/bugs.webkit.org/template/en/default/account/create.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/create.html.tmpl
index db5df1b..5acd9f5 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/create.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/create.html.tmpl
@@ -40,7 +40,7 @@
[% IF Param('emailsuffix') == '' %]
a legitimate email address.
[% ELSE %]
- an accountname which when combined with [% Param('emailsuffix') %]
+ an account name which when combined with [% Param('emailsuffix') %]
corresponds to an address where you receive email.
[% END %]
You will receive an email at this address to confirm the creation of your
@@ -73,6 +73,7 @@
</tr>
</table>
<br>
+ <input type="hidden" id="token" name="token" value="[% issue_hash_token(['create_account']) FILTER html %]">
<input type="submit" id="send" value="Send">
</form>
diff --git a/Websites/bugs.webkit.org/template/en/default/account/email/change-new.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/account/email/change-new.txt.tmpl
index e7f32e8..b40ab98 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/email/change-new.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/email/change-new.txt.tmpl
@@ -20,7 +20,6 @@
[% PROCESS global/variables.none.tmpl %]
-[% expiration_ts = token_ts + (max_token_age * 86400) %]
From: [% Param('mailfrom') %]
To: [% emailaddress %]
Subject: [% terms.Bugzilla %] Change Email Address Request
@@ -31,12 +30,12 @@
To confirm the change, visit the following link:
-[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cfmem
+[%+ urlbase %]token.cgi?t=[% token FILTER uri %]&a=cfmem
If you are not the person who made this request, or you wish to cancel
this request, visit the following link:
-[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cxlem
+[%+ urlbase %]token.cgi?t=[% token FILTER uri %]&a=cxlem
-If you do nothing, the request will lapse after [%+ max_token_age %] days
-(on [%+ time2str("%B %o, %Y at %H:%M %Z", expiration_ts) %]).
+If you do nothing, the request will lapse after [% constants.MAX_TOKEN_AGE %] days
+(on [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %]).
diff --git a/Websites/bugs.webkit.org/template/en/default/account/email/change-old.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/account/email/change-old.txt.tmpl
index cf00ebb..ee66c0f 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/email/change-old.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/email/change-old.txt.tmpl
@@ -25,7 +25,6 @@
[% PROCESS global/variables.none.tmpl %]
-[% expiration_ts = token_ts + (max_token_age * 86400) %]
From: [% Param('mailfrom') %]
To: [% emailaddress %]
Subject: [% terms.Bugzilla %] Change Email Address Request
@@ -40,8 +39,8 @@
If you are not the person who made this request, or you wish to cancel
this request, visit the following link:
-[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cxlem
+[%+ urlbase %]token.cgi?t=[% token FILTER uri %]&a=cxlem
If you do nothing, and [%+ newemailaddress %] confirms this request,
-the change will be made permanent after [%+ max_token_age %] days
-(on [%+ time2str("%B %o, %Y at %H:%M %Z", expiration_ts) %]).
+the change will be made permanent after [% constants.MAX_TOKEN_AGE %] days
+(on [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %]).
diff --git a/Websites/bugs.webkit.org/template/en/default/account/email/confirm-new.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/email/confirm-new.html.tmpl
index 437c1b7..a677db7 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/email/confirm-new.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/email/confirm-new.html.tmpl
@@ -16,7 +16,7 @@
[%# INTERFACE:
# token: string. The token to be used in the user account creation.
# email: email address of the user account.
- # date: creation date of the token.
+ # expiration_ts: expiration date of the token.
#%]
[% title = BLOCK %]Create a new user account for '[% email FILTER html %]'[% END %]
@@ -24,12 +24,11 @@
title = title
onload = "document.forms['confirm_account_form'].realname.focus();" %]
-[% expiration_ts = date + (constants.MAX_TOKEN_AGE * 86400) %]
-<div>
+<p>
To create your account, you must enter a password in the form below.
Your email address and Real Name (if provided) will be shown with
changes you make.
-</div>
+</p>
<form id="confirm_account_form" method="post" action="token.cgi">
<input type="hidden" name="t" value="[% token FILTER html %]">
@@ -45,7 +44,10 @@
</tr>
<tr>
<th align="right"><label for="passwd1">Type your password</label>:</th>
- <td><input type="password" id="passwd1" name="passwd1" value=""></td>
+ <td>
+ <input type="password" id="passwd1" name="passwd1" value="">
+ (minimum [% constants.USER_PASSWORD_MIN_LENGTH FILTER none %] characters)
+ </td>
</tr>
<tr>
<th align="right"><label for="passwd2">Confirm your password</label>:</th>
@@ -53,14 +55,14 @@
</tr>
<tr>
<th align="right"> </th>
- <td><input type="submit" id="confirm" value="Send"></td>
+ <td><input type="submit" id="confirm" value="Create"></td>
</tr>
</table>
</form>
<p>
This account will not be created if this form is not completed by
- <u>[%+ time2str("%B %o, %Y at %H:%M %Z", expiration_ts) %]</u>.
+ <u>[% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %]</u>.
</p>
<p>
diff --git a/Websites/bugs.webkit.org/template/en/default/account/email/request-new.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/account/email/request-new.txt.tmpl
index 36cd947..8fb3692 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/email/request-new.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/email/request-new.txt.tmpl
@@ -15,13 +15,12 @@
[%# INTERFACE:
# token: random string used to authenticate the transaction.
- # token_ts: creation date of the token.
+ # expiration_ts: expiration date of the token.
# email: email address of the new account.
#%]
[% PROCESS global/variables.none.tmpl %]
-[% expiration_ts = token_ts + (constants.MAX_TOKEN_AGE * 86400) %]
From: [% Param('mailfrom') %]
To: [% email %]
Subject: [% terms.Bugzilla %]: confirm account creation
@@ -31,11 +30,11 @@
using your email address ([% email %]).
To continue creating an account using this email address, visit the
-following link by [%+ time2str("%B %o, %Y at %H:%M %Z", expiration_ts) %]:
+following link by [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %]:
-[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=request_new_account
+[%+ urlbase %]token.cgi?t=[% token FILTER uri %]&a=request_new_account
-If you did not receive this email before [%+ time2str("%B %o, %Y at %H:%M %Z", expiration_ts) %] or
+If you did not receive this email before [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %] or
you wish to create an account using a different email address you can begin
again by going to:
@@ -51,7 +50,7 @@
If you do not wish to create an account, or if this request was made in
error you can do nothing or visit the following link:
-[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cancel_new_account
+[%+ urlbase %]token.cgi?t=[% token FILTER uri %]&a=cancel_new_account
If the above links do not work, or you have any other issues regarding
your account, please contact administration at [% Param('maintainer') %].
diff --git a/Websites/bugs.webkit.org/template/en/default/account/password/forgotten-password.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/account/password/forgotten-password.txt.tmpl
index f8687cb..e014658 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/password/forgotten-password.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/password/forgotten-password.txt.tmpl
@@ -20,7 +20,6 @@
[% PROCESS global/variables.none.tmpl %]
-[% expiration_ts = token_ts + (max_token_age * 86400) %]
From: [% Param('mailfrom') %]
To: [% emailaddress %]
Subject: [% terms.Bugzilla %] Change Password Request
@@ -29,13 +28,13 @@
You have (or someone impersonating you has) requested to change your
[%+ terms.Bugzilla %] password. To complete the change, visit the following link:
-[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cfmpw
+[%+ urlbase %]token.cgi?t=[% token FILTER uri %]&a=cfmpw
If you are not the person who made this request, or you wish to cancel
this request, visit the following link:
-[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cxlpw
+[%+ urlbase %]token.cgi?t=[% token FILTER uri %]&a=cxlpw
-If you do nothing, the request will lapse after [%+ max_token_age +%] days (at
-precisely [%+ time2str("%H:%M on the %o of %B, %Y", expiration_ts) -%]) or when you
+If you do nothing, the request will lapse after [% constants.MAX_TOKEN_AGE %] days
+(on [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z", timezone) %]) or when you
log in successfully.
diff --git a/Websites/bugs.webkit.org/template/en/default/account/password/set-forgotten-password.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/password/set-forgotten-password.html.tmpl
index 7035868..a2ae517 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/password/set-forgotten-password.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/password/set-forgotten-password.html.tmpl
@@ -32,14 +32,15 @@
<tr>
<th align="right">New Password:</th>
<td>
- <input type="password" name="password" size="16" maxlength="16">
+ <input type="password" name="password">
+ (minimum [% constants.USER_PASSWORD_MIN_LENGTH FILTER none %] characters)
</td>
</tr>
<tr>
<th align="right">New Password Again:</th>
<td>
- <input type="password" name="matchpassword" size="16" maxlength="16">
+ <input type="password" name="matchpassword">
</td>
</tr>
diff --git a/Websites/bugs.webkit.org/template/en/default/account/prefs/account.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/prefs/account.html.tmpl
index 15f0702..0457ff8 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/prefs/account.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/prefs/account.html.tmpl
@@ -33,9 +33,8 @@
<tr>
<th align="right">Password:</th>
<td>
- <input type="hidden" name="Bugzilla_login"
- value="[% user.login FILTER html %]">
- <input type="password" name="Bugzilla_password">
+ <input type="hidden" name="old_login" value="[% user.login FILTER html %]">
+ <input type="password" name="old_password">
</td>
</tr>
<tr>
diff --git a/Websites/bugs.webkit.org/template/en/default/account/prefs/email.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/prefs/email.html.tmpl
index ad9b370..96a111b 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/prefs/email.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/prefs/email.html.tmpl
@@ -31,14 +31,10 @@
# below), keyed by reasonname (e.g. comments; again, see
# below). The value is a boolean - true if the user is
# receiving mail for that reason when in that role.
- # Also references the 'supportwatchers' Param.
#%]
[% PROCESS global/variables.none.tmpl %]
-[% useqacontact = Param('useqacontact') %]
-[% usevotes = Param('usevotes') %]
-
<p>
If you don't like getting a notification for "trivial"
changes to [% terms.bugs %], you can use the settings below to
@@ -81,8 +77,8 @@
[% prefname = "email-$constants.REL_ANY-$constants.EVT_FLAG_REQUESTED" %]
<input type="checkbox" name="[% prefname %]" id="[% prefname %]"
value="1"
- [% " checked" IF
- mail.${constants.REL_ANY}.${constants.EVT_FLAG_REQUESTED} %]>
+ [% " checked"
+ IF user.mail_settings.${constants.REL_ANY}.${constants.EVT_FLAG_REQUESTED} %]>
<label for="[% prefname %]">Email me when someone asks me to set a flag</label>
<br>
</td>
@@ -93,8 +89,8 @@
[% prefname = "email-$constants.REL_ANY-$constants.EVT_REQUESTED_FLAG" %]
<input type="checkbox" name="[% prefname %]" id="[% prefname %]"
value="1"
- [% " checked" IF
- mail.${constants.REL_ANY}.${constants.EVT_REQUESTED_FLAG} %]>
+ [% " checked"
+ IF user.mail_settings.${constants.REL_ANY}.${constants.EVT_REQUESTED_FLAG} %]>
<label for="[% prefname %]">Email me when someone sets a flag I asked for</label>
<br>
</td>
@@ -119,6 +115,8 @@
[% events = [
{ id = constants.EVT_ADDED_REMOVED,
description = "I'm added to or removed from this capacity" },
+ { id = constants.EVT_BUG_CREATED,
+ description = "A new $terms.bug is created" },
{ id = constants.EVT_OPENED_CLOSED,
description = "The $terms.bug is resolved or reopened" },
{ id = constants.EVT_PROJ_MANAGEMENT,
@@ -149,21 +147,28 @@
[% relationships = [
{ id = constants.REL_ASSIGNEE,
description = "Assignee" },
- { id = constants.REL_QA,
- description = "QA Contact" },
{ id = constants.REL_REPORTER,
description = "Reporter" },
{ id = constants.REL_CC,
description = "CCed" },
- { id = constants.REL_VOTER,
- description = "Voter" },
] %]
+[% IF Param('useqacontact') %]
+ [% relationships.push({ id = constants.REL_QA,
+ description = "QA Contact" }) %]
+[% END %]
+
+
+[%# This is up here so that the "relationships" hook can modify it. %]
+[% no_added_removed = [constants.REL_REPORTER] %]
+
+[% Hook.process('relationships') %]
+
+[% num_columns = relationships.size %]
+
<table class="bz_emailprefs" border="1">
<tr>
- <td colspan="[% (useqacontact AND usevotes) ? '5' :
- ((useqacontact OR usevotes) ? '4' : '3') %]"
- align="center" width="50%">
+ <td colspan="[% num_columns FILTER html %]" align="center" width="50%">
<b>When my relationship to this [% terms.bug %] is:</b>
</td>
<td rowspan="2" width="40%">
@@ -173,8 +178,6 @@
<tr>
[% FOREACH relationship = relationships %]
- [% NEXT IF (relationship.id == constants.REL_QA AND NOT useqacontact) OR
- (relationship.id == constants.REL_VOTER AND NOT usevotes) %]
<th align="center" width="9%">
[% relationship.description FILTER html %]
</th>
@@ -185,18 +188,16 @@
[% count = loop.count() %]
<tr class="bz_row_[% count % 2 == 1 ? "odd" : "even" %]">
[% FOREACH relationship = relationships %]
- [% NEXT IF (relationship.id == constants.REL_QA AND NOT useqacontact) OR
- (relationship.id == constants.REL_VOTER AND NOT usevotes) %]
<td align="center">
<input type="checkbox"
name="email-[% relationship.id %]-[% event.id %]"
value="1"
[%# The combinations don't always make sense; disable a couple %]
[% IF event.id == constants.EVT_ADDED_REMOVED AND
- (relationship.id == constants.REL_REPORTER OR
- relationship.id == constants.REL_VOTER) %]
+ no_added_removed.contains(relationship.id)
+ %]
disabled
- [% ELSIF mail.${relationship.id}.${event.id} %]
+ [% ELSIF user.mail_settings.${relationship.id}.${event.id} %]
checked
[% END %]>
</td>
@@ -208,8 +209,7 @@
[% END %]
<tr>
- <td colspan="[% (useqacontact AND usevotes) ? '5' :
- ((useqacontact OR usevotes) ? '4' : '3') %]"
+ <td colspan="[% num_columns FILTER html %]"
align="center" width="50%">
</td>
@@ -222,13 +222,11 @@
[% count = loop.count() %]
<tr class="bz_row_[% count % 2 == 1 ? "odd" : "even" %]">
[% FOREACH relationship = relationships %]
- [% NEXT IF (relationship.id == constants.REL_QA AND NOT useqacontact) OR
- (relationship.id == constants.REL_VOTER AND NOT usevotes) %]
<td align="center">
<input type="checkbox"
name="neg-email-[% relationship.id %]-[% event.id %]"
value="1"
- [% " checked" IF NOT mail.${relationship.id}.${event.id} %]>
+ [% " checked" IF NOT user.mail_settings.${relationship.id}.${event.id} %]>
</td>
[% END %]
<td>
@@ -239,30 +237,6 @@
</table>
-[%# Add hidden form fields for fields not used %]
-[% FOREACH event = events %]
- [% FOREACH relationship = relationships %]
- [% IF (relationship.id == constants.REL_QA AND NOT useqacontact) OR
- (relationship.id == constants.REL_VOTER AND NOT usevotes) %]
- <input type="hidden"
- name="email-[% relationship.id %]-[% event.id %]"
- value="[% mail.${relationship.id}.${event.id} ? "1" : "0" %]">
- [% END %]
- [% END %]
-[% END %]
-
-[% FOREACH event = neg_events %]
- [% FOREACH relationship = relationships %]
- [% IF (relationship.id == constants.REL_QA AND NOT useqacontact) OR
- (relationship.id == constants.REL_VOTER AND NOT usevotes) %]
- <input type="hidden"
- name="neg-email-[% relationship.id %]-[% event.id %]"
- value="[% mail.${relationship.id}.${event.id} ? "0" : "1" %]">
- [% END %]
- [% END %]
-[% END %]
-
-[% IF Param('supportwatchers') %]
<hr>
<b>User Watching</b>
@@ -290,11 +264,17 @@
[% END %]
</p>
-<p><a name="new_watched_by_you" id="new_watched_by_you">Add users to my watch list (comma separated list)</a>:
- <input size="60" name="new_watchedusers" value="">
+<p id="new_watched_by_you">Add users to my watch list (comma separated list):
+ [% INCLUDE global/userselect.html.tmpl
+ id => "new_watchedusers"
+ name => "new_watchedusers"
+ value => ""
+ size => 60
+ multiple => 5
+ %]
</p>
-<p><a name="watching_you" id="watching_you">Users watching you</a>:<br>
+<p id="watching_you">Users watching you:<br>
[% IF watchers.size %]
[% FOREACH watcher = watchers %]
[% watcher FILTER html %] <br>
@@ -304,8 +284,6 @@
[% END %]
</p>
-[% END %]
-
<hr>
<br>
diff --git a/Websites/bugs.webkit.org/template/en/default/account/prefs/permissions.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/prefs/permissions.html.tmpl
index 4a097e2..5e8dc9c 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/prefs/permissions.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/prefs/permissions.html.tmpl
@@ -65,7 +65,7 @@
There are no permission bits set on your account.
[% END %]
- [% IF user.groups.editusers %]
+ [% IF user.in_group('editusers') %]
<br>
You have editusers privileges. You can turn on and off
all permissions for all users.
@@ -83,7 +83,7 @@
</table>
[% END %]
- [% IF user.groups.bz_sudoers %]
+ [% IF user.in_group('bz_sudoers') %]
<br>
You are a member of the <b>bz_sudoers</b> group, so you can
<a href="relogin.cgi?action=prepare-sudo">impersonate someone else</a>.
diff --git a/Websites/bugs.webkit.org/template/en/default/account/prefs/prefs.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/prefs/prefs.html.tmpl
index 71e411d..2e8b561 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/prefs/prefs.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/prefs/prefs.html.tmpl
@@ -40,8 +40,9 @@
title = "User Preferences"
subheader = filtered_login
style_urls = ['skins/standard/admin.css']
- javascript_urls = ['js/util.js']
+ javascript_urls = ['js/util.js', 'js/field.js']
doc_section = "userpreferences.html"
+ yui = ['autocomplete']
%]
[% tabs = [{ name => "settings", label => "General Preferences",
@@ -50,11 +51,13 @@
link => "userprefs.cgi?tab=email", saveable => "1" },
{ name => "saved-searches", label => "Saved Searches",
link => "userprefs.cgi?tab=saved-searches", saveable => "1" },
- { name => "account", label => "Name and Password",
+ { name => "account", label => "Account Information",
link => "userprefs.cgi?tab=account", saveable => "1" },
{ name => "permissions", label => "Permissions",
link => "userprefs.cgi?tab=permissions", saveable => "0" } ] %]
+[% Hook.process('tabs') %]
+
[% FOREACH tab IN tabs %]
[% IF tab.name == current_tab_name %]
[% current_tab = tab %]
diff --git a/Websites/bugs.webkit.org/template/en/default/account/prefs/saved-searches.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/prefs/saved-searches.html.tmpl
index cf458e8..1b78592 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/prefs/saved-searches.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/prefs/saved-searches.html.tmpl
@@ -31,9 +31,9 @@
var checkbox = document.getElementById(group.name.replace(/share_(\d+)/, "force_$1"));
if (bz_isValueInArray(bless_groups, group.value)) {
- checkbox.disabled = false;
+ YAHOO.util.Dom.removeClass(checkbox.parentNode, "bz_default_hidden");
} else {
- checkbox.disabled = true;
+ YAHOO.util.Dom.addClass(checkbox.parentNode, "bz_default_hidden");
checkbox.checked = false;
}
} //-->
@@ -71,7 +71,7 @@
<tr>
<td>My [% terms.Bugs %]</td>
<td>
- [% filtered_username = user.login FILTER url_quote %]
+ [% filtered_username = user.login FILTER uri %]
<a href="[% Param('mybugstemplate').replace('%userid%', filtered_username) %]">Run</a>
</td>
<td>
@@ -96,19 +96,20 @@
<tr>
<td>[% q.name FILTER html %]</td>
<td>
- <a href="buglist.cgi?cmdtype=runnamed&namedcmd=[% q.name FILTER url_quote %]">Run</a>
+ <a href="buglist.cgi?cmdtype=dorem&remaction=run&namedcmd=[% q.name FILTER uri %]
+ [% IF q.shared_with_group.id %]&sharer_id=[% user.id FILTER uri %][% END %]">Run</a>
</td>
<td>
<a href="query.cgi?[% q.edit_link FILTER html %]&known_name=
- [% q.name FILTER url_quote %]">Edit</a>
+ [% q.name FILTER uri %]">Edit</a>
</td>
<td>
[% IF q.used_in_whine %]
Remove from <a href="editwhines.cgi">whining</a> first
[% ELSE %]
<a href="buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=
- [% q.name FILTER url_quote %]&token=
- [% issue_hash_token([q.id, q.name]) FILTER url_quote %]">Forget</a>
+ [% q.name FILTER uri %]&token=
+ [% issue_hash_token([q.id, q.name]) FILTER uri %]">Forget</a>
[% END %]
</td>
<td align="center">
@@ -131,12 +132,12 @@
[% END %]
</select>
[% IF user.can_bless %]
- <input type="checkbox" id="force_[% q.id FILTER html %]"
- name="force_[% q.id FILTER html %]" value="1"
- [% " disabled"
- IF !bless_group_ids.grep("^$q.shared_with_group.id\$").0
- %]>
- <label for="force_[% q.id FILTER html %]">Add to footer</label>
+ <span [% IF !bless_group_ids.grep("^$q.shared_with_group.id\$").0
+ %]class="bz_default_hidden"[% END %]>
+ <input type="checkbox" id="force_[% q.id FILTER html %]"
+ name="force_[% q.id FILTER html %]" value="1">
+ <label for="force_[% q.id FILTER html %]">Add to footer</label>
+ </span>
[% END %]
[% IF q.shared_with_users %]
(shared with [% q.shared_with_users FILTER html %]
@@ -154,9 +155,9 @@
[% END %]
</blockquote>
-<p>You may use these searches saved and shared by others:</p>
+[% IF user.queries_available.size %]
+ <p>You may use these searches saved and shared by others:</p>
-<blockquote>
<table border="1" cellpadding="3">
<tr>
<th>
@@ -179,21 +180,19 @@
Footer
</th>
</tr>
- [% found_shared_query = 0 %]
[% FOREACH q = user.queries_available %]
- [% found_shared_query = 1 %]
<tr>
<td>[% q.name FILTER html %]</td>
<td>[% q.user.identity FILTER html %]</td>
<td>[% q.shared_with_group.name FILTER html %]</td>
<td>
<a href="buglist.cgi?cmdtype=dorem&remaction=run&namedcmd=
- [% q.name FILTER url_quote %]&sharer_id=
- [% q.user.id FILTER url_quote %]">Run</a>
+ [% q.name FILTER uri %]&sharer_id=
+ [% q.user.id FILTER uri %]">Run</a>
</td>
<td>
<a href="query.cgi?[% q.edit_link FILTER html %]&known_name=
- [% q.name FILTER url_quote %]">Edit</a>
+ [% q.name FILTER uri %]">Edit</a>
</td>
<td align="center">
<input type="checkbox"
@@ -204,12 +203,7 @@
</td>
</tr>
[% END %]
- [% IF !found_shared_query %]
- <tr>
- <td colspan="6" style="text-align: center">
- <None>
- </td>
- </tr>
- [% END %]
</table>
-</blockquote>
+[% ELSE %]
+ <p>No searches are shared with you by other users.</p>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/account/profile-activity.html.tmpl b/Websites/bugs.webkit.org/template/en/default/account/profile-activity.html.tmpl
index 3ef904d..ee00875f 100644
--- a/Websites/bugs.webkit.org/template/en/default/account/profile-activity.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/account/profile-activity.html.tmpl
@@ -56,6 +56,7 @@
}
{name => 'what'
heading => 'What'
+ content_use_field => 1
}
{name => 'removed'
heading => 'Removed'
@@ -72,9 +73,9 @@
%]
<p><a href="editusers.cgi?action=edit&userid=
- [%- otheruser.id FILTER url_quote %]"
+ [%- otheruser.id FILTER uri %]"
title="Edit user '[% otheruser.login FILTER html %]'">Edit this user</a> or
- <a title="Search For Users" href="editusers.cgi">find other accounts</a>
+ <a title="Search For Users" href="editusers.cgi">search for other accounts</a>
[% IF listselectionvalues.matchtype != 'exact' %]
or go <a title="Return to the user list"
href="editusers.cgi?action=list[% INCLUDE listselectionurlparams %]">back
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/admin.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/admin.html.tmpl
index c681767..98f729b 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/admin.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/admin.html.tmpl
@@ -26,7 +26,7 @@
%]
<div>
- This page is only accessible to empowered users. You can access administrive pages
+ This page is only accessible to empowered users. You can access administrative pages
from here (based on your privileges), letting you configure different aspects of
this installation. Note: some sections may not be accessible to you and are marked
using a lighter color.
@@ -36,59 +36,62 @@
<tr>
<td class="admin_links">
<dl>
- [% class = user.groups.tweakparams ? "" : "forbidden" %]
- <dt class="[% class %]"><a href="editparams.cgi">Parameters</a></dt>
+ [% class = user.in_group('tweakparams') ? "" : "forbidden" %]
+ <dt id="parameters" class="[% class %]"><a href="editparams.cgi">Parameters</a></dt>
<dd class="[% class %]">Set core parameters of the installation. That's the
place where you specify the URL to access this installation, determine how
users authenticate, choose which [% terms.bug %] fields to display, select
the mail transfer agent to send email notifications, choose which group of
users can use charts and share queries, and much more.</dd>
- <dt class="[% class %]"><a href="editsettings.cgi">Default Preferences</a></dt>
+ <dt id="preferences" class="[% class %]"><a href="editsettings.cgi">Default Preferences</a></dt>
<dd class="[% class %]">Set the default user preferences. These are the values
which will be used by default for all users. Users will be able to edit their
own preferences from the <a href="userprefs.cgi?tab=settings">Preferences</a>.</dd>
- [% class = user.groups.editcomponents ? "" : "forbidden" %]
- <dt class="[% class %]"><a href="sanitycheck.cgi">Sanity Check</a></dt>
+ [% class = user.in_group('editcomponents') ? "" : "forbidden" %]
+ <dt id="sanitycheck" class="[% class %]"><a href="sanitycheck.cgi">Sanity Check</a></dt>
<dd class="[% class %]">Run sanity checks to locate problems in your database.
This may take several tens of minutes depending on the size of your installation.
You can also automate this check by running <tt>sanitycheck.pl</tt> from a cron job.
A notification will be sent per email to the specified user if errors are detected.</dd>
- [% class = (user.groups.editusers || user.can_bless) ? "" : "forbidden" %]
- <dt class="[% class %]"><a href="editusers.cgi">Users</a></dt>
+ [% class = (user.in_group('editusers') || user.can_bless) ? "" : "forbidden" %]
+ <dt id="users" class="[% class %]"><a href="editusers.cgi">Users</a></dt>
<dd class="[% class %]">Create new user accounts or edit existing ones. You can
also add and remove users from groups (also known as "user privileges").</dd>
- [% class = (Param('useclassification') && user.groups.editclassifications) ? "" : "forbidden" %]
- <dt class="[% class %]"><a href="editclassifications.cgi">Classifications</a></dt>
+ [% class = (Param('useclassification') && user.in_group('editclassifications')) ? "" : "forbidden" %]
+ <dt id="classifications" class="[% class %]"><a href="editclassifications.cgi">Classifications</a></dt>
<dd class="[% class %]">If your installation has to manage many products at once,
it's a good idea to group these products into distinct categories. This lets users
find information more easily when doing searches or when filing new [% terms.bugs %].</dd>
- [% class = (user.groups.editcomponents
+ [% class = (user.in_group('editcomponents')
|| user.get_products_by_permission("editcomponents").size) ? "" : "forbidden" %]
- <dt class="[% class %]"><a href="editproducts.cgi">Products</a></dt>
+ <dt id="products" class="[% class %]"><a href="editproducts.cgi">Products</a></dt>
<dd class="[% class %]">Edit all aspects of products, including group restrictions
which let you define who can access [% terms.bugs %] being in these products. You
can also edit some specific attributes of products such as
<a href="editcomponents.cgi">components</a>, <a href="editversions.cgi">versions</a>
and <a href="editmilestones.cgi">milestones</a> directly.</dd>
- [% class = user.groups.editcomponents ? "" : "forbidden" %]
- <dt class="[% class %]"><a href="editflagtypes.cgi">Flags</a></dt>
+ [% class = (user.in_group('editcomponents')
+ || user.get_products_by_permission('editcomponents').size) ? "" : "forbidden" %]
+ <dt id="flags" class="[% class %]"><a href="editflagtypes.cgi">Flags</a></dt>
<dd class="[% class %]">A flag is a custom 4-states attribute of [% terms.bugs %]
and/or attachments. These states are: granted, denied, requested and undefined.
You can set as many flags as desired per [% terms.bug %], and define which users
are allowed to edit them.</dd>
+
+ [% Hook.process('end_links_left') %]
</dl>
</td>
<td class="admin_links">
<dl>
- [% class = user.groups.admin ? "" : "forbidden" %]
- <dt class="[% class %]"><a href="editfields.cgi">Custom Fields</a></dt>
+ [% class = user.in_group('admin') ? "" : "forbidden" %]
+ <dt id="custom_fields" class="[% class %]"><a href="editfields.cgi">Custom Fields</a></dt>
<dd class="[% class %]">[% terms.Bugzilla %] lets you define fields which are
not implemented by default, based on your local and specific requirements.
These fields can then be used as any other field, meaning that you can set
@@ -97,32 +100,34 @@
interface more complex and harder to use. Be sure you have investigated other ways
to satisfy your needs before doing this.</dd>
- <dt class="[% class %]"><a href="editvalues.cgi">Field Values</a></dt>
+ <dt id="field_values" class="[% class %]"><a href="editvalues.cgi">Field Values</a></dt>
<dd class="[% class %]">Define legal values for fields whose values must belong
to some given list. This is also the place where you define legal values for some
types of custom fields.</dd>
- <dt class="[% class %]"><a href="editworkflow.cgi">[%terms.Bug %] Status Workflow</a></dt>
+ <dt id="status_workflow" class="[% class %]"><a href="editworkflow.cgi">[%terms.Bug %] Status Workflow</a></dt>
<dd class="[% class %]">Customize your workflow and choose initial [% terms.bug %]
statuses available on [% terms.bug %] creation and allowed [% terms.bug %] status
transitions when editing existing [% terms.bugs %].</dd>
- [% class = user.groups.creategroups ? "" : "forbidden" %]
- <dt class="[% class %]"><a href="editgroups.cgi">Groups</a></dt>
+ [% class = user.in_group('creategroups') ? "" : "forbidden" %]
+ <dt id="groups" class="[% class %]"><a href="editgroups.cgi">Groups</a></dt>
<dd class="[% class %]">Define groups which will be used in the installation.
They can either be used to define new user privileges or to restrict the access
to some [% terms.bugs %].</dd>
- [% class = user.groups.editkeywords ? "" : "forbidden" %]
- <dt class="[% class %]"><a href="editkeywords.cgi">Keywords</a></dt>
+ [% class = user.in_group('editkeywords') ? "" : "forbidden" %]
+ <dt id="keywords" class="[% class %]"><a href="editkeywords.cgi">Keywords</a></dt>
<dd class="[% class %]">Set keywords to be used with [% terms.bugs %]. Keywords
are an easy way to "tag" [% terms.bugs %] to let you find them more easily later.</dd>
- [% class = user.groups.bz_canusewhines ? "" : "forbidden" %]
- <dt class="[% class %]"><a href="editwhines.cgi">Whining</a></dt>
+ [% class = user.in_group('bz_canusewhines') ? "" : "forbidden" %]
+ <dt id="whining" class="[% class %]"><a href="editwhines.cgi">Whining</a></dt>
<dd class="[% class %]">Set queries which will be run at some specified date
and time, and get the result of these queries directly per email. This is a
good way to create reminders and to keep track of the activity in your installation.</dd>
+
+ [% Hook.process('end_links_right') %]
</dl>
</td>
</tr>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/classifications/add.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/classifications/add.html.tmpl
index 7254779..1a6941f 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/classifications/add.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/classifications/add.html.tmpl
@@ -24,26 +24,9 @@
<form method=post action="editclassifications.cgi">
<table border=0 cellpadding=4 cellspacing=0>
- <tr>
- <th align="right">Classification:</th>
- <td><input size=64 maxlength=64 name="classification"></td>
- </tr>
- <tr>
- <th align="right">Description:</th>
- <td>
- [% INCLUDE global/textarea.html.tmpl
- name = 'description'
- minrows = 4
- cols = 64
- wrap = 'virtual'
- %]
- </td>
- </tr>
- <tr>
- <th align="right"><label for="sortkey">Sortkey:</label></th>
- <td><input id="sortkey" size="20" maxlength="20" name="sortkey"
- value=""></td>
- </tr>
+
+ [% PROCESS "admin/classifications/edit-common.html.tmpl" %]
+
</table>
<hr>
<input type=submit value="Add">
@@ -51,7 +34,6 @@
<input type="hidden" name="token" value="[% token FILTER html %]">
</FORM>
-<p>Back to the <a href="./">main [% terms.bugs %] page</a>
-or <a href="editclassifications.cgi"> edit</a> more classifications.
+[% PROCESS admin/classifications/footer.html.tmpl %]
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/classifications/del.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/classifications/del.html.tmpl
index 8e48768..5a3800f 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/classifications/del.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/classifications/del.html.tmpl
@@ -58,7 +58,6 @@
<input type="hidden" name="token" value="[% token FILTER html %]">
</form>
-<p>Back to the <a href="./">main [% terms.bugs %] page</a>
-or <a href="editclassifications.cgi"> edit</a> more classifications.</p>
+[% PROCESS admin/classifications/footer.html.tmpl %]
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/classifications/edit-common.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/classifications/edit-common.html.tmpl
new file mode 100644
index 0000000..e0db008
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/admin/classifications/edit-common.html.tmpl
@@ -0,0 +1,47 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Tiago Rodrigues de Mello <timello@linux.vnet.ibm.com>
+ #%]
+
+[%# INTERFACE:
+ # classification: Bugzilla::Classifiation object.
+ #%]
+
+<tr>
+ <th align="right">Classification:</th>
+ <td><input size=64 maxlength=64 name="classification"
+ value="[% classification.name FILTER html %]"></td>
+</tr>
+<tr>
+ <th align="right">Description:</th>
+ <td>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'description'
+ minrows = 4
+ cols = 64
+ defaultcontent = classification.description
+ %]
+ </td>
+</tr>
+<tr>
+ <th align="right"><label for="sortkey">Sortkey:</label></th>
+ <td><input id="sortkey" size="20" maxlength="20" name="sortkey"
+ value="[%- classification.sortkey FILTER html %]"></td>
+</tr>
+
+[% Hook.process('rows') %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/classifications/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/classifications/edit.html.tmpl
index b3ef22b..17d04de 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/classifications/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/classifications/edit.html.tmpl
@@ -24,30 +24,12 @@
<form method=post action="editclassifications.cgi">
<table border=0 cellpadding=4 cellspacing=0>
- <tr>
- <th align="right">Classification:</th>
- <td><input size=64 maxlength=64 name="classification"
- value="[% classification.name FILTER html %]"></td>
- </tr>
- <tr>
- <th align="right">Description:</th>
- <td>
- [% INCLUDE global/textarea.html.tmpl
- name = 'description'
- minrows = 4
- cols = 64
- defaultcontent = classification.description
- %]
- </td>
- </tr>
- <tr>
- <th align="right"><label for="sortkey">Sortkey:</label></th>
- <td><input id="sortkey" size="20" maxlength="20" name="sortkey" value="
- [%- classification.sortkey FILTER html %]"></td>
- </tr>
+
+ [% PROCESS "admin/classifications/edit-common.html.tmpl" %]
+
<tr valign=top>
<th align="right">
- <a href="editproducts.cgi?classification=[% classification.name FILTER url_quote %]">
+ <a href="editproducts.cgi?classification=[% classification.name FILTER uri %]">
Edit Products</a>:
</th>
<td>
@@ -80,7 +62,6 @@
<input type=submit value="Update">
</form>
-<p>Back to the <a href="./">main [% terms.bugs %] page</a>
-or <a href="editclassifications.cgi"> edit</a> more classifications.
+[% PROCESS admin/classifications/footer.html.tmpl %]
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/classifications/footer.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/classifications/footer.html.tmpl
new file mode 100644
index 0000000..db983aa
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/admin/classifications/footer.html.tmpl
@@ -0,0 +1,24 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Nitish Bezzala <nbezzala@yahoo.com>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>
+or <a href="editclassifications.cgi"> edit</a> more classifications.</p>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/classifications/reclassify.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/classifications/reclassify.html.tmpl
index 08b85af..146a1ac 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/classifications/reclassify.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/classifications/reclassify.html.tmpl
@@ -84,8 +84,7 @@
<input type="hidden" name="token" value="[% token FILTER html %]">
</form>
-<p>Back to the <a href="./">main [% terms.bugs %] page</a>,
-or <a href="editclassifications.cgi"> edit</a> more classifications.
+[% PROCESS admin/classifications/footer.html.tmpl %]
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/classifications/select.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/classifications/select.html.tmpl
index d6b352d..bc78cbb 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/classifications/select.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/classifications/select.html.tmpl
@@ -33,7 +33,7 @@
[% FOREACH cl = classifications %]
<tr>
- <td valign="top"><a href="editclassifications.cgi?action=edit&classification=[% cl.name FILTER url_quote %]"><b>[% cl.name FILTER html %]</b></a></td>
+ <td valign="top"><a href="editclassifications.cgi?action=edit&classification=[% cl.name FILTER uri %]"><b>[% cl.name FILTER html %]</b></a></td>
<td valign="top">
[% IF cl.description %]
[% cl.description FILTER html_light %]
@@ -45,14 +45,14 @@
[% IF (cl.id == 1) %]
<td valign="top">[% cl.product_count FILTER html %]</td>
[% ELSE %]
- <td valign="top"><a href="editclassifications.cgi?action=reclassify&classification=[% cl.name FILTER url_quote %]">reclassify ([% cl.product_count FILTER html %])</a></td>
+ <td valign="top"><a href="editclassifications.cgi?action=reclassify&classification=[% cl.name FILTER uri %]">reclassify ([% cl.product_count FILTER html %])</a></td>
[% END %]
[%# don't allow user to delete the default id. %]
[% IF (cl.id == 1) %]
<td valign="top"> </td>
[% ELSE %]
- <td valign="top"><a href="editclassifications.cgi?action=del&classification=[% cl.name FILTER url_quote %]">delete</a></td>
+ <td valign="top"><a href="editclassifications.cgi?action=del&classification=[% cl.name FILTER uri %]">delete</a></td>
[% END %]
</tr>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/components/confirm-delete.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/components/confirm-delete.html.tmpl
index 53bba1e..da934f4 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/components/confirm-delete.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/components/confirm-delete.html.tmpl
@@ -82,16 +82,16 @@
</tr>
<tr>
- <TD VALIGN="top">Closed for [% terms.bugs %]:</TD>
- <TD VALIGN="top">[% IF product.disallow_new %]Yes[% ELSE %]No[% END %]</td>
+ <TD VALIGN="top">Open for [% terms.bugs %]:</TD>
+ <TD VALIGN="top">[% IF product.is_active && comp.isactive %]Yes[% ELSE %]No[% END %]</td>
</tr>
<tr>
<td valign="top">[% terms.Bugs %]:</td>
<td valign="top">
[% IF comp.bug_count %]
<a title="List of [% terms.bugs %] for component '[% comp.name FILTER html %]'"
- href="buglist.cgi?component=[% comp.name FILTER url_quote %]&product=
- [%- product.name FILTER url_quote %]">[% comp.bug_count %]</a>
+ href="buglist.cgi?component=[% comp.name FILTER uri %]&product=
+ [%- product.name FILTER uri %]">[% comp.bug_count %]</a>
[% ELSE %]
None
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/components/create.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/components/create.html.tmpl
index 86411ad..c3b691d 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/components/create.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/components/create.html.tmpl
@@ -26,70 +26,16 @@
[% title = BLOCK %]Add component to the [% product.name FILTER html %] product[% END %]
[% PROCESS global/header.html.tmpl
+ yui = [ 'autocomplete' ]
+ javascript_urls = [ "js/field.js" ]
title = title
%]
<form method="post" action="editcomponents.cgi">
<table border="0" cellpadding="4" cellspacing="0">
- <tr>
- <th align="right">Component:</th>
- <td><input size="64" maxlength="64" name="component" value=""></td>
- </tr>
- <tr>
- <th align="right">Description:</th>
- <td>
- [% INCLUDE global/textarea.html.tmpl
- name = 'description'
- minrows = 4
- cols = 64
- wrap = 'virtual'
- %]
- </td>
- </tr>
- <tr>
- <th align="right"><label for="initialowner">Default Assignee:</label></th>
- <td>
- [% INCLUDE global/userselect.html.tmpl
- name => "initialowner"
- id => "initialowner"
- value => ""
- size => 64
- %]
- </td>
- </tr>
-[% IF Param('useqacontact') %]
- <tr>
- <th align="right">
- <label for="initialqacontact">Default QA Contact:</label></th>
- <td>
- [% INCLUDE global/userselect.html.tmpl
- name => "initialqacontact"
- id => "initialqacontact"
- value => ""
- size => 64
- emptyok => 1
- %]
- </td>
- </tr>
-[% END %]
- <tr>
- <th align="right">
- <label for="initialcc">Default CC List:</label>
- </th>
- <td>
- [% INCLUDE global/userselect.html.tmpl
- name => "initialcc"
- id => "initialcc"
- value => ""
- size => 64
- multiple => 5
- %]
- <br>
- [% IF !Param("usemenuforusers") %]
- <em>Enter user names for the CC list as a comma-separated list.</em>
- [% END %]
- </td>
- </tr>
+
+ [% PROCESS "admin/components/edit-common.html.tmpl" %]
+
</table>
<hr>
<input type="submit" id="create" value="Add">
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/components/edit-common.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/components/edit-common.html.tmpl
new file mode 100644
index 0000000..3e489af
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/admin/components/edit-common.html.tmpl
@@ -0,0 +1,85 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Tiago Rodrigues de Mello <timello@linux.vnet.ibm.com>
+ #%]
+
+[%# INTERFACE:
+ # comp: object; Bugzilla::Component object.
+ #%]
+
+<tr>
+ <th class="field_label"><label for="component">Component:</label></th>
+ <td><input size="64" maxlength="64" name="component" id="component"
+ value="[%- comp.name FILTER html %]"></td>
+</tr>
+<tr>
+ <th class="field_label"><label for="[% desc_name FILTER html %]">Component Description:</label></th>
+ <td>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'description'
+ id = 'description'
+ minrows = 4
+ cols = 64
+ wrap = 'virtual'
+ defaultcontent = comp.description
+ %]
+ </td>
+</tr>
+<tr>
+ <th class="field_label"><label for="initialowner">Default Assignee:</label></th>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ name => "initialowner"
+ id => "initialowner"
+ value => comp.default_assignee.login
+ size => 64
+ %]
+ </td>
+</tr>
+[% IF Param('useqacontact') %]
+ <tr>
+ <th class="field_label"><label for="initialqacontact">Default QA contact:</label></th>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ name => "initialqacontact"
+ id => "initialqacontact"
+ value => comp.default_qa_contact.login
+ size => 64
+ emptyok => 1
+ %]
+ </td>
+ </tr>
+[% END %]
+<tr>
+ <th class="field_label"><label for="initialcc">Default CC List:</label></th>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ name => "initialcc"
+ id => "initialcc"
+ value => initial_cc_names
+ size => 64
+ multiple => 5
+ %]
+ <br>
+ [% IF !Param("usemenuforusers") %]
+ <em>Enter user names for the CC list as a comma-separated list.</em>
+ [% END %]
+ </td>
+</tr>
+
+[% Hook.process('rows') %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/components/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/components/edit.html.tmpl
index 9ddb8ca..5236186 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/components/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/components/edit.html.tmpl
@@ -33,81 +33,28 @@
[% END %]
[% PROCESS global/header.html.tmpl
title = title
+ yui = [ 'autocomplete' ]
+ javascript_urls = [ "js/field.js" ]
%]
<form method="post" action="editcomponents.cgi">
<table border="0" cellpadding="4" cellspacing="0">
+ [% PROCESS "admin/components/edit-common.html.tmpl" %]
+
<tr>
- <td valign="top">Component:</td>
- <td><input size="64" maxlength="64" name="component" value="
- [%- comp.name FILTER html %]"></td>
+ <th class="field_label"><label for="isactive">Enabled For [% terms.Bugs %]:</label></th>
+ <td><input id="isactive" name="isactive" type="checkbox" value="1"
+ [% 'checked="checked"' IF comp.isactive %]></td>
</tr>
<tr>
- <td valign="top">Component Description:</td>
- <td>
- [% INCLUDE global/textarea.html.tmpl
- name = 'description'
- minrows = 4
- cols = 64
- wrap = 'virtual'
- defaultcontent = comp.description
- %]
- </td>
- </tr>
- <tr>
- <td valign="top"><label for="initialowner">Default Assignee:</label></td>
- <td>
- [% INCLUDE global/userselect.html.tmpl
- name => "initialowner"
- id => "initialowner"
- value => comp.default_assignee.login
- size => 64
- %]
- </td>
-
-[% IF Param('useqacontact') %]
- </tr>
- <tr>
- <td valign="top"><label for="initialqacontact">Default QA contact:</label></td>
- <td>
- [% INCLUDE global/userselect.html.tmpl
- name => "initialqacontact"
- id => "initialqacontact"
- value => comp.default_qa_contact.login
- size => 64
- emptyok => 1
- %]
- </td>
-[% END %]
-
- </tr>
- <tr>
- <td valign="top">
- <label for="initialcc">Default CC List:</label>
- </td>
- <td>
- [% INCLUDE global/userselect.html.tmpl
- name => "initialcc"
- id => "initialcc"
- value => initial_cc_names
- size => 64
- multiple => 5
- %]
- <br>
- [% IF !Param("usemenuforusers") %]
- <em>Enter user names for the CC list as a comma-separated list.</em>
- [% END %]
- </td>
- </tr>
- <tr>
- <td>[% terms.Bugs %]:</td>
+ <th class="field_label">[% terms.Bugs %]:</th>
<td>
[% IF comp.bug_count > 0 %]
<a title="[% terms.Bugs %] in component '[% comp.name FILTER html %]'"
href="buglist.cgi?component=
- [%- comp.name FILTER url_quote %]&product=
- [%- product.name FILTER url_quote %]">[% comp.bug_count %]</a>
+ [%- comp.name FILTER uri %]&product=
+ [%- product.name FILTER uri %]">[% comp.bug_count %]</a>
[% ELSE %]
None
[% END %]
@@ -122,8 +69,8 @@
<input type="hidden" name="token" value="[% token FILTER html %]">
<input type="submit" value="Save Changes" id="update"> or <a
href="editcomponents.cgi?action=del&product=
- [%- product.name FILTER url_quote %]&component=
- [%- comp.name FILTER url_quote %]">Delete</a> this component.
+ [%- product.name FILTER uri %]&component=
+ [%- comp.name FILTER uri %]">Delete</a> this component.
</form>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/components/footer.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/components/footer.html.tmpl
index b2e105e..ec1869b 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/components/footer.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/components/footer.html.tmpl
@@ -33,7 +33,7 @@
component <a
title="Edit Component '[% comp.name FILTER html %]'"
href="editcomponents.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]&component=[% comp.name FILTER url_quote %]">
+ [%- product.name FILTER uri %]&component=[% comp.name FILTER uri %]">
'[% comp.name FILTER html %]'</a>
or edit
[% END %]
@@ -42,13 +42,13 @@
other components of product <a
title="Choose a component from product '[% product.name FILTER html %]' to edit"
href="editcomponents.cgi?product=
- [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>,
+ [%- product.name FILTER uri %]">'[% product.name FILTER html %]'</a>,
or edit
[% END %]
product <a
title="Edit Product '[% product.name FILTER html %]'"
href="editproducts.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>.
+ [%- product.name FILTER uri %]">'[% product.name FILTER html %]'</a>.
</p>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/components/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/components/list.html.tmpl
index 990b079..b45b975 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/components/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/components/list.html.tmpl
@@ -34,11 +34,11 @@
%]
[% edit_contentlink = BLOCK %]editcomponents.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]&component=%%name%%[% END %]
+ [%- product.name FILTER uri %]&component=%%name%%[% END %]
[% delete_contentlink = BLOCK %]editcomponents.cgi?action=del&product=
- [%- product.name FILTER url_quote %]&component=%%name%%[% END %]
+ [%- product.name FILTER uri %]&component=%%name%%[% END %]
[% bug_count_contentlink = BLOCK %]buglist.cgi?component=%%name%%&product=
- [%- product.name FILTER url_quote %][% END %]
+ [%- product.name FILTER uri %][% END %]
[% columns = [
@@ -56,6 +56,11 @@
name => "initialowner"
heading => "Default Assignee"
},
+ {
+ name => "isactive"
+ heading => "Active"
+ yesno_field => 1
+ },
]
%]
@@ -86,38 +91,37 @@
}) %]
[%# Overrides the initialowner and the initialqacontact with right values %]
-[% overrides.initialowner = [] %]
-[% overrides.initialqacontact = [] %]
+[% overrides.initialowner = {} %]
+[% overrides.initialqacontact = {} %]
-[% FOREACH component = product.components %]
- [% overrides.initialowner.push({
- match_value => component.name
- match_field => 'name'
+[%# "component" is a reserved word in Template Toolkit. %]
+[% FOREACH my_component = product.components %]
+ [% overrides.initialowner.name.${my_component.name} = {
override_content => 1
- content => component.default_assignee.login
- })
+ content => my_component.default_assignee.login
+ }
%]
- [% overrides.initialqacontact.push({
- match_value => component.name
- match_field => 'name'
+ [% overrides.initialqacontact.name.${my_component.name} = {
override_content => 1
- content => component.default_qa_contact.login
- })
+ content => my_component.default_qa_contact.login
+ }
%]
[% END %]
+[% Hook.process('before_table') %]
+
[% PROCESS admin/table.html.tmpl
columns = columns
data = product.components
overrides = overrides
%]
-<p><a href="editcomponents.cgi?action=add&product=[% product.name FILTER url_quote %]">Add</a>
+<p><a href="editcomponents.cgi?action=add&product=[% product.name FILTER uri %]">Add</a>
a new component to product '[% product.name FILTER html %]'</p>
[% IF ! showbugcounts %]
- <p><a href="editcomponents.cgi?product=[% product.name FILTER url_quote %]&showbugcounts=1">
+ <p><a href="editcomponents.cgi?product=[% product.name FILTER uri %]&showbugcounts=1">
Redisplay table with [% terms.bug %] counts (slower)</a></p>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/cf-js.js.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/cf-js.js.tmpl
new file mode 100644
index 0000000..cc1a4e4
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/cf-js.js.tmpl
@@ -0,0 +1,77 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is NASA.
+ # Portions created by NASA are Copyright (C) 2008
+ # San Jose State University Foundation. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+// Disable a checkbox based on the state of another one.
+function toggleCheckbox(this_checkbox, other_checkbox_id) {
+ var other_checkbox = document.getElementById(other_checkbox_id);
+ other_checkbox.disabled = !this_checkbox.checked;
+}
+
+var select_values = new Array();
+[% USE Bugzilla %]
+[% FOREACH sel_field = Bugzilla.fields({ is_select => 1 }) %]
+ select_values[[% sel_field.id FILTER js %]] = [
+ [% FOREACH legal_value = sel_field.legal_values %]
+ [%# Prefix components with the name of their product so that admins
+ know which component we're talking about. #%]
+ [% IF sel_field.name == 'component' %]
+ [% SET value_name = display_value('product', legal_value.product.name) _ ': '
+ _ display_value(sel_field.name, legal_value.name) %]
+ [% ELSE %]
+ [% SET value_name = display_value(sel_field.name, legal_value.name) %]
+ [% END %]
+ [[% legal_value.id FILTER js %], '[% value_name FILTER js %]'][% ',' UNLESS loop.last %]
+ [% END %]
+ ];
+[% END %]
+
+function onChangeType(type_field) {
+ var value_field = document.getElementById('value_field_id');
+ if (type_field.value == [% constants.FIELD_TYPE_SINGLE_SELECT %]
+ || type_field.value == [% constants.FIELD_TYPE_MULTI_SELECT %])
+ {
+ value_field.disabled = false;
+ }
+ else {
+ value_field.disabled = true;
+ }
+
+ var reverse_desc = document.getElementById('reverse_desc');
+ if (type_field.value == [% constants.FIELD_TYPE_BUG_ID %])
+ {
+ reverse_desc.disabled = false;
+ }
+ else {
+ reverse_desc.disabled = true;
+ reverse_desc.value = '';
+ }
+}
+
+function onChangeVisibilityField() {
+ var vis_field = document.getElementById('visibility_field_id');
+ var vis_value = document.getElementById('visibility_values');
+
+ if (vis_field.value) {
+ var values = select_values[vis_field.value];
+ bz_populateSelectFromArray(vis_value, values);
+ }
+ else {
+ bz_clearOptions(vis_value);
+ }
+}
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/create.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/create.html.tmpl
index 5dd50ce..f89f979 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/create.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/create.html.tmpl
@@ -19,20 +19,21 @@
[% PROCESS "global/field-descs.none.tmpl" %]
+[% javascript = BLOCK %]
+ [% INCLUDE "admin/custom_fields/cf-js.js.tmpl" %]
+[% END %]
+
[% PROCESS global/header.html.tmpl
title = "Add a new Custom Field"
onload = "document.getElementById('new_bugmail').disabled = true;"
+ javascript_urls = [ 'js/util.js' ]
doc_section = "custom-fields.html#add-custom-fields"
+ style_urls = ['skins/standard/admin.css']
%]
+[%# set initial editability of fields such as Reverse Relationship Description %]
<script type="text/javascript">
- <!--
- // Disable a checkbox based on the state of another one.
- function toggleCheckbox(this_checkbox, other_checkbox_id) {
- var other_checkbox = document.getElementById(other_checkbox_id);
- other_checkbox.disabled = !this_checkbox.checked;
- }
- //-->
+YAHOO.util.Event.onDOMReady(function() {onChangeType(document.getElementById('type'))});
</script>
<p>
@@ -53,14 +54,14 @@
</ul>
<form id="add_field" action="editfields.cgi" method="GET">
- <table border="0" cellspacing="0" cellpadding="5">
+ <table border="0" cellspacing="0" cellpadding="5" id="edit_custom_field">
<tr>
- <th align="right"><label for="name">Name:</label></th>
+ <th class="narrow_label"><label for="name">Name:</label></th>
<td>
<input type="text" id="name" name="name" value="cf_" size="40" maxlength="64">
</td>
- <th align="right">
+ <th>
<label for="enter_bug">Can be set on [% terms.bug %] creation:</label>
</th>
<td>
@@ -69,18 +70,18 @@
</td>
</tr>
<tr>
- <th align="right"><label for="desc">Description:</label></th>
+ <th class="narrow_label"><label for="desc">Description:</label></th>
<td><input type="text" id="desc" name="desc" value="" size="40"></td>
- <th align="right">
+ <th>
<label for="new_bugmail">Displayed in [% terms.bug %]mail for new [% terms.bugs %]:</label>
</th>
<td><input type="checkbox" id="new_bugmail" name="new_bugmail" value="1"></td>
</tr>
<tr>
- <th align="right"><label for="type">Type:</label></th>
+ <th class="narrow_label"><label for="type">Type:</label></th>
<td>
- <select id="type" name="type">
+ <select id="type" name="type" onchange="onChangeType(this)">
[% FOREACH type = field_types.keys %]
[% NEXT IF type == constants.FIELD_TYPE_UNKNOWN %]
<option value="[% type FILTER html %]">[% field_types.$type FILTER html %]</option>
@@ -88,17 +89,77 @@
</select>
</td>
- <th align="right"><label for="obsolete">Is obsolete:</label></th>
+ <th><label for="obsolete">Is obsolete:</label></th>
<td><input type="checkbox" id="obsolete" name="obsolete" value="1"></td>
</tr>
<tr>
- <th align="right"><label for="sortkey">Sortkey:</label></th>
+ <th class="narrow_label"><label for="sortkey">Sortkey:</label></th>
<td>
<input type="text" id="sortkey" name="sortkey" size="6" maxlength="6">
</td>
- <th> </th>
- <td> </td>
+ <th align="right"><label for="is_mandatory">Is mandatory:</label></th>
+ <td><input type="checkbox" id="is_mandatory" name="is_mandatory" value="1"></td>
+ </tr>
+
+ <tr>
+ <th class="narrow_label">
+ <label for="reverse_desc">Reverse Relationship Description:</label>
+ </th>
+ <td>
+ <input type="text" id="reverse_desc" name="reverse_desc" value="" size="40" disabled="disabled">
+ <br/>
+ Use this label for the list of [% terms.bugs %] that link to
+ [%+ terms.abug %] with this
+ [%+ field_types.${constants.FIELD_TYPE_BUG_ID} FILTER html %]
+ field. For example, if the description is "Is a duplicate of",
+ the reverse description would be "Duplicates of this [% terms.bug %]".
+ Leave blank to disable the list for this field.
+ </td>
+ <th>
+ <label for="visibility_field_id">Field only appears when:</label>
+ </th>
+ <td>
+ <select name="visibility_field_id" id="visibility_field_id"
+ onchange="onChangeVisibilityField()">
+ <option></option>
+ [% FOREACH sel_field = Bugzilla.fields({ is_select => 1 }) %]
+ <option value="[% sel_field.id FILTER html %]">
+ [% sel_field.description FILTER html %]
+ ([% sel_field.name FILTER html %])
+ </option>
+ [% END %]
+ </select>
+ <label for="visibility_values">
+ <strong>is set to any of:</strong>
+ </label>
+ <select multiple="multiple" size="5" name="visibility_values"
+ id="visibility_values" class="field_value">
+ <option value=""></option>
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2"> </td>
+ <th>
+ <label for="value_field_id">
+ Field that controls the values<br>
+ that appear in this field:
+ </label>
+ </th>
+
+ <td>
+ <select disabled="disabled" name="value_field_id" id="value_field_id">
+ <option></option>
+ [% FOREACH sel_field = Bugzilla.fields({ is_select => 1 }) %]
+ <option value="[% sel_field.id FILTER html %]">
+ [% sel_field.description FILTER html %]
+ ([% sel_field.name FILTER html %])
+ </option>
+ [% END %]
+ </select>
+ </td>
</tr>
</table>
<p>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/edit.html.tmpl
index 02334ab..5ce2b7f 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/edit.html.tmpl
@@ -14,7 +14,7 @@
#%]
[%# INTERFACE:
- # none
+ # field: Bugzila::Field; the current field being edited
#%]
[% PROCESS "global/field-descs.none.tmpl" %]
@@ -23,34 +23,30 @@
Edit the Custom Field '[% field.name FILTER html %]' ([% field.description FILTER html %])
[% END %]
+[% javascript = BLOCK %]
+ [% INCLUDE "admin/custom_fields/cf-js.js.tmpl" %]
+[% END %]
+
[% PROCESS global/header.html.tmpl
title = title
onload = "toggleCheckbox(document.getElementById('enter_bug'), 'new_bugmail');"
+ javascript_urls = [ 'js/util.js' ]
doc_section = "custom-fields.html#edit-custom-fields"
+ style_urls = ['skins/standard/admin.css']
%]
-<script type="text/javascript">
- <!--
- // Disable a checkbox based on the state of another one.
- function toggleCheckbox(this_checkbox, other_checkbox_id) {
- var other_checkbox = document.getElementById(other_checkbox_id);
- other_checkbox.disabled = !this_checkbox.checked;
- }
- //-->
-</script>
-
<p>
Descriptions are a very short string describing the field and will be used as
the label for this field in the user interface.
</p>
<form id="edit_field" action="editfields.cgi" method="GET">
- <table border="0" cellspacing="0" cellpadding="5">
+ <table border="0" cellspacing="0" cellpadding="5" id="edit_custom_field">
<tr>
- <th align="right">Name:</th>
+ <th class="narrow_label">Name:</th>
<td>[% field.name FILTER html %]</td>
- <th align="right">
+ <th>
<label for="enter_bug">Can be set on [% terms.bug %] creation:</label>
</th>
<td><input type="checkbox" id="enter_bug" name="enter_bug" value="1"
@@ -58,42 +54,116 @@
onchange="toggleCheckbox(this, 'new_bugmail');"></td>
</tr>
<tr>
- <th align="right"><label for="desc">Description:</label></th>
+ <th class="narrow_label"><label for="desc">Description:</label></th>
<td><input type="text" id="desc" name="desc" size="40"
value="[% field.description FILTER html %]"></td>
- <th align="right">
+ <th>
<label for="new_bugmail">Displayed in [% terms.bug %]mail for new [% terms.bugs %]:</label>
</th>
<td><input type="checkbox" id="new_bugmail" name="new_bugmail" value="1"
[%- " checked" IF field.mailhead %]></td>
</tr>
<tr>
- <th align="right">Type:</th>
+ <th class="narrow_label">Type:</th>
<td>[% field_types.${field.type} FILTER html %]</td>
- <th align="right"><label for="obsolete">Is obsolete:</label></th>
+ <th><label for="obsolete">Is obsolete:</label></th>
<td><input type="checkbox" id="obsolete" name="obsolete" value="1"
[%- " checked" IF field.obsolete %]></td>
</tr>
<tr>
- <th align="right"><label for="sortkey">Sortkey:</label></th>
+ <th class="narrow_label"><label for="sortkey">Sortkey:</label></th>
<td>
<input type="text" id="sortkey" name="sortkey" size="6" maxlength="6"
value="[% field.sortkey FILTER html %]">
</td>
-
- <th> </th>
- <td> </td>
+ <th align="right"><label for="is_mandatory">Is mandatory:</label></th>
+ <td><input type="checkbox" id="is_mandatory" name="is_mandatory" value="1"
+ [%- ' checked="checked"' IF field.is_mandatory %]></td>
</tr>
- [% IF field.type == constants.FIELD_TYPE_SINGLE_SELECT
- || field.type == constants.FIELD_TYPE_MULTI_SELECT %]
+ <tr>
+ [% IF field.type == constants.FIELD_TYPE_BUG_ID %]
+ <th class="narrow_label">
+ <label for="reverse_desc">Reverse Relationship Description:</label>
+ </th>
+ <td>
+ <input type="text" id="reverse_desc" name="reverse_desc" size="40"
+ value="[% field.reverse_desc FILTER html %]">
+ <br/>
+ Use this label for the list of [% terms.bugs %] that link to
+ [%+ terms.abug %] with this
+ [%+ field_types.${constants.FIELD_TYPE_BUG_ID} FILTER html %] field.
+ For example, if the description is "Is a duplicate of",
+ the reverse description would be "Duplicates of this [% terms.bug %]".
+ Leave blank to disable the list for this field.
+ </td>
+ [% ELSE %]
+ <td colspan="2"> </td>
+ [% END %]
+ <th>
+ <label for="visibility_field_id">Field only appears when:</label>
+ </th>
+ <td>
+ <select name="visibility_field_id" id="visibility_field_id"
+ onchange="onChangeVisibilityField()">
+ <option></option>
+ [% FOREACH sel_field = Bugzilla.fields({ is_select => 1 }) %]
+ [% NEXT IF sel_field.id == field.id %]
+ <option value="[% sel_field.id FILTER html %]"
+ [% ' selected="selected"'
+ IF sel_field.id == field.visibility_field.id %]>
+ [% sel_field.description FILTER html %]
+ ([% sel_field.name FILTER html %])
+ </option>
+ [% END %]
+ </select>
+ <label for="visibility_values">
+ <strong>is set to any of:</strong>
+ </label>
+ <select multiple="multiple" size="5" name="visibility_values"
+ id="visibility_values" class="field_value">
+ [% FOREACH value = field.visibility_field.legal_values %]
+ <option value="[% value.id FILTER html %]"
+ [% " selected" IF field.visibility_values.contains(value) %]>
+ [% IF field.visibility_field.name == 'component' %]
+ [% display_value('product', value.product.name) FILTER html %]:
+ [% END %]
+ [%+ display_value(field.visibility_field.name, value.name) FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ </td>
+ </tr>
+ [% IF field.is_select %]
<tr>
<th> </th>
- <td colspan="3">
- <a href="editvalues.cgi?field=[% field.name FILTER url_quote %]">Edit
+ <td>
+ <a href="editvalues.cgi?field=[% field.name FILTER uri %]">Edit
legal values for this field</a>.
</td>
+
+ <th>
+ <label for="value_field_id">
+ Field that controls the values<br>
+ that appear in this field:
+ </label>
+ </th>
+
+ <td>
+ <select name="value_field_id" id="value_field_id">
+ <option></option>
+ [% FOREACH sel_field = Bugzilla.fields({ is_select => 1 }) %]
+ [% NEXT IF sel_field.id == field.id %]
+ <option value="[% sel_field.id FILTER html %]"
+ [% ' selected="selected"'
+ IF sel_field.id == field.value_field.id %]>
+ [% sel_field.description FILTER html %]
+ ([% sel_field.name FILTER html %])
+ </option>
+ [% END %]
+ </select>
+ </td>
</tr>
[% END %]
</table>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/list.html.tmpl
index 6f2e68b..689aa05 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/custom_fields/list.html.tmpl
@@ -57,6 +57,10 @@
heading => "Is Obsolete"
},
{
+ name => "is_mandatory"
+ heading => "Is Mandatory"
+ },
+ {
name => "action"
heading => "Action"
content => ""
@@ -65,30 +69,28 @@
%]
[% USE Bugzilla %]
-[% custom_fields = Bugzilla.get_fields({ custom => 1 }) %]
+[% custom_fields = Bugzilla.fields({ custom => 1 }) %]
[%# We want to display the type name of fields, not their type ID. %]
-[% overrides.type = [] %]
+[% overrides.type = {} %]
[% FOREACH field_type = field_types.keys %]
- [% overrides.type.push({
- match_value => field_type
- match_field => 'type'
+ [% overrides.type.type.$field_type = {
override_content => 1
- content => field_types.${field_type}
- })
+ content => field_types.$field_type
+ }
%]
[% END %]
-[% overrides.action = [ {
- match_value => 1
- match_field => 'obsolete'
- override_content => 1
- content => "Delete"
- override_contentlink => 1
- contentlink => delete_contentlink
- } ]
+[% overrides.action.obsolete = {
+ "1" => {
+ override_content => 1
+ content => "Delete"
+ override_contentlink => 1
+ contentlink => delete_contentlink
+ }
+ }
%]
[% PROCESS admin/table.html.tmpl
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
index 3320ae4..0881541 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
@@ -14,18 +14,14 @@
#%]
[%# INTERFACE:
- # value: string; The field value being deleted.
- # bug_count: number; The number of bugs that have this field value.
- # value_count: number; The number of values left for this field, including
- # this value.
+ # value: Bugzilla::Field::Choice; The field value being deleted.
+ # value_count: number; The number of values available for this field.
# field: object; the field the value is being deleted from.
- # param_name: string; The name of the parameter (defaultxxx) associated
- # with the field.
#%]
[% title = BLOCK %]
- Delete Value '[% value FILTER html %]' from the '[% field.description FILTER html %]'
- ([% field.name FILTER html %]) field
+ Delete Value '[% value.name FILTER html %]' from the
+ '[% field.description FILTER html %]' ([% field.name FILTER html %]) field
[% END %]
[% PROCESS global/header.html.tmpl
@@ -44,15 +40,18 @@
</tr>
<tr>
<td valign="top">Field Value:</td>
- <td valign="top">[% value FILTER html %]</td>
+ <td valign="top">[% value.name FILTER html %]</td>
</tr>
<tr>
<td valign="top">[% terms.Bugs %]:</td>
<td valign="top">
-[% IF bug_count %]
- <a title="List of [% terms.bugs %] where '[% field.description FILTER html %]' is '
- [% value FILTER html %]'"
- href="buglist.cgi?[% field.name FILTER url_quote %]=[%- value FILTER url_quote %]">[% bug_count FILTER html %]</a>
+[% IF value.bug_count %]
+ <a title="List of [% terms.bugs %] where '
+ [%- field.description FILTER html %]' is '
+ [%- value.name FILTER html %]'"
+ href="buglist.cgi?[% field.name FILTER uri %]=
+ [%- value.name FILTER uri %]">
+ [%- value.bug_count FILTER html %]</a>
[% ELSE %]
None
[% END %]
@@ -62,44 +61,82 @@
<h2>Confirmation</h2>
-[% IF (param_name.defined && Param(param_name) == value) || bug_count || (value_count == 1) %]
+[% IF value.is_default || value.bug_count || (value_count == 1)
+ || value.controls_visibility_of_fields.size
+ || value.controlled_values_array.size
+%]
- <p>Sorry, but the '[% value FILTER html %]' value cannot be deleted
- from the '[% field.description FILTER html %]' field for the following reason(s):</p>
+ <p>Sorry, but the '[% value.name FILTER html %]' value cannot be deleted
+ from the '[% field.description FILTER html %]' field for the following
+ reason(s):</p>
<ul class="warningmessages">
- [% IF param_name.defined && Param(param_name) == value %]
- <li>'[% value FILTER html %]' is the default value for
- the '[% field.description FILTER html %]' field.
- [% IF user.groups.tweakparams %]
- You first have to <a href="editparams.cgi?section=bugfields#
- [%- param_name FILTER url_quote %]">change the default value</a> for
- this field before you can delete this value.
- [% END %]
+ [% IF value.is_default %]
+ <li>'[% value.name FILTER html %]' is the default value for
+ the '[% field.description FILTER html %]' field.
+ [% IF user.in_group('tweakparams') %]
+ You first have to <a href="editparams.cgi?section=bugfields">change
+ the default value</a> for this field before you can delete
+ this value.
+ [% END %]
+ </li>
[% END %]
- [% IF bug_count %]
- <li>There
- [% IF bug_count > 1 %]
- are [% bug_count FILTER html %] [%+ terms.bugs %]
- [% ELSE %]
- is 1 [% terms.bug %]
- [% END %]
- with this field value. You must change the field value on
- <a title="List of [% terms.bugs %] where '[% field.description FILTER html %]' is '[% value FILTER html %]'"
- href="buglist.cgi?[% field.name FILTER url_quote %]=[% value FILTER url_quote %]">
- [% IF bug_count > 1 %]
+ [% IF value.bug_count %]
+ <li>
+ [% IF value.bug_count > 1 %]
+ There are [% value.bug_count FILTER html %] [%+ terms.bugs %]
+ with this field value.
+ [% ELSE %]
+ There is 1 [% terms.bug %] with this field value.
+ [% END %]
+ You must change the field value on
+ <a title="List of [% terms.bugs %] where '
+ [%- field.description FILTER html %]' is '
+ [%- value.name FILTER html %]'"
+ href="buglist.cgi?[% field.name FILTER uri %]=
+ [%- value.name FILTER uri %]">
+ [% IF value.bug_count > 1 %]
those [% terms.bugs %]
[% ELSE %]
that [% terms.bug %]
[% END %]
</a>
to another value before you can delete this value.
+ </li>
[% END %]
[% IF value_count == 1 %]
- <li>'[% value FILTER html %]' is the last value for
- '[%- field.description FILTER html %]', and so it can not be deleted.
+ <li>'[% value.name FILTER html %]' is the last value for
+ '[%- field.description FILTER html %]', and so it can not be deleted.
+ </li>
+ [% END %]
+
+ [% IF value.controls_visibility_of_fields.size %]
+ <li>This value controls the visibility of the following fields:<br>
+ [% FOREACH field = value.controls_visibility_of_fields %]
+ <a href="editfields.cgi?action=edit&name=
+ [%- field.name FILTER uri %]">
+ [%- field.description FILTER html %]
+ ([% field.name FILTER html %])</a><br>
+ [% END %]
+ </li>
+ [% END %]
+
+ [% IF value.controlled_values_array.size %]
+ <li>This value controls the visibility of the following values in
+ other fields:<br>
+ [% FOREACH field_name = value.controlled_values.keys %]
+ [% FOREACH controlled = value.controlled_values.${field_name} %]
+ <a href="editvalues.cgi?action=edit&field=
+ [%- controlled.field.name FILTER uri %]&value=
+ [%- controlled.name FILTER uri %]">
+ [% controlled.field.description FILTER html %]
+ ([% controlled.field.name FILTER html %]):
+ [%+ controlled.name FILTER html %]</a><br>
+ [% END %]
+ [% END %]
+ </li>
[% END %]
</ul>
@@ -111,7 +148,7 @@
<input type="submit" value="Yes, delete" id="delete">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="field" value="[% field.name FILTER html %]">
- <input type="hidden" name="value" value="[% value FILTER html %]">
+ <input type="hidden" name="value" value="[% value.name FILTER html %]">
<input type="hidden" name="token" value="[% token FILTER html %]">
</form>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/create.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/create.html.tmpl
index fe906bf..0198314 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/create.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/create.html.tmpl
@@ -26,26 +26,29 @@
%]
<p>
- This page allows you to add a new value for the '[% field.description FILTER html %]' field.
+ This page allows you to add a new value for the
+ '[% field.description FILTER html %]' field.
</p>
<form method="post" action="editvalues.cgi">
<table border="0" cellpadding="4" cellspacing="0">
<tr>
<th align="right"><label for="value">Value:</label></th>
- <td><input id="value" size="30" maxlength="60" name="value"
- value=""></td>
+ <td>
+ <input id="value" name="value" size="30"
+ maxlength="[% constants.MAX_FIELD_VALUE_SIZE FILTER none %]">
+ </td>
</tr>
<tr>
<th align="right"><label for="sortkey">Sortkey:</label></th>
- <td><input id="sortkey" size="10" maxlength="20" name="sortkey"
- value=""></td>
+ <td><input id="sortkey" name="sortkey" size="6" maxlength="6"></td>
</tr>
[% IF field.name == "bug_status" %]
<tr>
<th align="right"><label for="is_open">Status Type:</label></th>
<td>
- <input type="radio" id="open_status" name="is_open" value="1" checked="checked">
+ <input type="radio" id="open_status" name="is_open" value="1"
+ checked="checked">
<label for="open_status">Open</label><br>
<input type="radio" id="closed_status" name="is_open" value="0">
<label for="closed_status">Closed (requires a Resolution)</label>
@@ -59,6 +62,30 @@
</td>
</tr>
[% END %]
+ [% IF field.value_field %]
+ <tr>
+ <th align="right">
+ <label for="visibility_value_id">Only appears when
+ [%+ field.value_field.description FILTER html %] is set to:
+ </label>
+ </th>
+ <td>
+ <select name="visibility_value_id" id="visibility_value_id">
+ <option></option>
+ [% FOREACH field_value = field.value_field.legal_values %]
+ [% NEXT IF field_value.name == '' %]
+ <option value="[% field_value.id FILTER none %]">
+ [% IF field.value_field.name == 'component' %]
+ [% field_value.product.name FILTER html %]:
+ [% END %]
+ [%- field_value.name FILTER html -%]
+ </option>
+ [% END %]
+ </select>
+ <small>(Leave unset to have this value always appear.)</small>
+ </td>
+ </tr>
+ [% END %]
</table>
<input type="submit" id="create" value="Add">
<input type="hidden" name="action" value="new">
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/edit.html.tmpl
index 98b480b..9c42ce6 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/edit.html.tmpl
@@ -14,16 +14,15 @@
#%]
[%# INTERFACE:
- # value: string; The field value we are editing.
- # sortkey: number; Sortkey of the field value we are editing.
- # field: object; The field this value belongs to.
+ # value: Bugzilla::Field::Choice; The field value we are editing.
+ # field: Bugzilla::Field; The field this value belongs to.
#%]
[% PROCESS global/variables.none.tmpl %]
[% title = BLOCK %]
- Edit Value '[% value FILTER html %]' for the '[% field.description FILTER html %]'
- ([% field.name FILTER html %]) field
+ Edit Value '[% value.name FILTER html %]' for the
+ '[% field.description FILTER html %]' ([% field.name FILTER html %]) field
[% END %]
[% PROCESS global/header.html.tmpl
title = title
@@ -33,32 +32,76 @@
<table border="0" cellpadding="4" cellspacing="0">
<tr>
- <th valign="top"><label for="value">Field Value:</label></th>
+ <th valign="top" align="right">
+ <label for="value_new">Field Value:</label>
+ </th>
<td>
- [% IF is_static %]
- <input type="hidden" name="value" value="[% value FILTER html %]">
- [% value FILTER html %]
+ [% IF value.is_static %]
+ <input type="hidden" name="value_new" id="value_new"
+ value="[% value.name FILTER html %]">
+ [%- value.name FILTER html %]
[% ELSE %]
- <input id="value" size="20" maxlength="60" name="value" value="
- [%- value FILTER html %]">
+ <input id="value_new" name="value_new" size="20"
+ maxlength="[% constants.MAX_FIELD_VALUE_SIZE FILTER none %]"
+ value="[% value.name FILTER html %]">
[% END %]
</td>
</tr>
<tr>
<th align="right"><label for="sortkey">Sortkey:</label></th>
- <td><input id="sortkey" size="20" maxlength="20" name="sortkey" value="
- [%- sortkey FILTER html %]"></td>
+ <td><input id="sortkey" size="6" maxlength="6" name="sortkey"
+ value="[%- value.sortkey FILTER html %]"></td>
</tr>
[% IF field.name == "bug_status" %]
<tr>
<th align="right"><label for="is_open">Status Type:</label></th>
- <td>[% IF is_open %]Open[% ELSE %]Closed[% END %]</td>
+ <td>[% IF value.is_open %]Open[% ELSE %]Closed[% END %]</td>
</tr>
[% END %]
+ [% IF field.value_field %]
+ <tr>
+ <th align="right">
+ <label for="visibility_value_id">Only appears when
+ [%+ field.value_field.description FILTER html %] is set to:
+ </label>
+ </th>
+ <td>
+ <select name="visibility_value_id" id="visibility_value_id">
+ <option></option>
+ [% FOREACH field_value = field.value_field.legal_values %]
+ [% NEXT IF field_value.name == '' %]
+ <option value="[% field_value.id FILTER none %]"
+ [% ' selected="selected"'
+ IF field_value.id == value.visibility_value.id %]>
+ [% IF field.value_field.name == 'component' %]
+ [% field_value.product.name FILTER html %]:
+ [% END %]
+ [% field_value.name FILTER html -%]
+ </option>
+ [% END %]
+ </select>
+ <small>(Leave unset to have this value always appear.)</small>
+ </td>
+ </tr>
+ [% END %]
+ <tr>
+ <th align="right"><label for="is_active">Enabled for [% terms.bugs %]:</label></th>
+ <td><input id="is_active" name="is_active" type="checkbox" value="1"
+ [%+ 'checked="checked"' IF value.is_active %]
+ [%+ 'disabled="disabled"' IF value.is_default OR value.is_static %]>
+ [% IF value.is_default %]
+ This value is selected as default in the parameters for this field. It cannot be disabled.
+ [% ELSIF value.is_static %]
+ This value is non-deletable and cannot be disabled.
+ [% END %]
+ [% IF !(value.is_default OR value.is_static) %]
+ <input id="defined_is_active" name="defined_is_active"
+ type="hidden" value="1">
+ [% END %]
+ </td>
+ </tr>
</table>
-
- <input type="hidden" name="valueold" value="[% value FILTER html %]">
- <input type="hidden" name="sortkeyold" value="[% sortkey FILTER html %]">
+ <input type="hidden" name="value" value="[% value.name FILTER html %]">
<input type="hidden" name="action" value="update">
<input type="hidden" name="field" value="[% field.name FILTER html %]">
<input type="hidden" name="token" value="[% token FILTER html %]">
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/footer.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/footer.html.tmpl
index dcb6dbc..7d4a41d 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/footer.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/footer.html.tmpl
@@ -32,22 +32,23 @@
[% UNLESS no_add_link %]
<a title="Add a value for the '[% field.description FILTER html %]' field."
href="editvalues.cgi?action=add&field=
- [%- field.name FILTER url_quote %]">Add</a> a value.
+ [%- field.name FILTER uri %]">Add</a> a value.
[% END %]
-[% IF value && !no_edit_link %]
+[% IF value.defined && !no_edit_link %]
Edit value <a
- title="Edit value '[% value FILTER html %]' for the '
+ title="Edit value '[% value.name FILTER html %]' for the '
[%- field.name FILTER html %]' field"
href="editvalues.cgi?action=edit&field=
- [%- field.name FILTER url_quote %]&value=[% value FILTER url_quote %]">
- '[% value FILTER html %]'</a>.
+ [%- field.name FILTER uri %]&value=
+ [%- value.name FILTER uri %]">
+ '[% value.name FILTER html %]'</a>.
[% END %]
[% UNLESS no_edit_other_link %]
Edit other values for the <a
href="editvalues.cgi?field=
- [%- field.name FILTER url_quote %]">'[% field.description FILTER html %]'</a> field.
+ [%- field.name FILTER uri %]">'[% field.description FILTER html %]'</a> field.
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/list.html.tmpl
index d14bbc2..2b6aedb 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/fieldvalues/list.html.tmpl
@@ -35,9 +35,9 @@
%]
[% edit_contentlink = BLOCK %]editvalues.cgi?action=edit&field=
- [%- field.name FILTER url_quote %]&value=%%name%%[% END %]
+ [%- field.name FILTER uri %]&value=%%name%%[% END %]
[% delete_contentlink = BLOCK %]editvalues.cgi?action=del&field=
- [%- field.name FILTER url_quote %]&value=%%name%%[% END %]
+ [%- field.name FILTER uri %]&value=%%name%%[% END %]
[% columns = [
@@ -51,6 +51,11 @@
heading => "Sortkey"
},
{
+ name => "isactive"
+ heading => "Enabled for $terms.bugs"
+ yesno_field => 1
+ },
+ {
name => "action"
heading => "Action"
content => "Delete"
@@ -58,34 +63,27 @@
} ]
%]
-[% IF default.defined %]
- [% overrides.action = [ {
- match_value => "$default"
- match_field => 'name'
- override_content => 1
- content => "(Default value)"
- override_contentlink => 1
- contentlink => undef
- } ]
- %]
-[% END %]
-[% IF static.size %]
- [% UNLESS overrides.action.size %]
- [% overrides.action = [] %]
- [% END %]
-
- [% FOREACH static_value = static %]
- [% overrides.action.push({
- match_value => "$static_value"
- match_field => 'name'
+[% SET overrides.action = {} %]
+[% FOREACH check_value = values %]
+ [% IF check_value.is_static %]
+ [% overrides.action.name.${check_value.name} = {
override_content => 1
content => "(Non-deletable value)"
override_contentlink => 1
contentlink => undef
- })
+ }
+ %]
+ [% ELSIF check_value.is_default %]
+ [% overrides.action.name.${check_value.name} = {
+ override_content => 1
+ content => "(Default value)"
+ override_contentlink => 1
+ contentlink => undef
+ }
%]
[% END %]
+
[% END %]
[% PROCESS admin/table.html.tmpl
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/flag-type/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/flag-type/edit.html.tmpl
index ebebf50..2cb985a 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/flag-type/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/flag-type/edit.html.tmpl
@@ -17,26 +17,22 @@
#
# Contributor(s): Myk Melez <myk@mozilla.org>
# Mark Bickford <markhb@maine.rr.com>
+ # Frédéric Buclin <LpSolit@gmail.com>
#%]
[% PROCESS global/variables.none.tmpl %]
[% PROCESS "global/js-products.html.tmpl" %]
-[% IF type.target_type == "bug" %]
- [% title = BLOCK %]Create Flag Type for [% terms.Bugs %][% END %]
- [% typeLabelLowerPlural = BLOCK %][% terms.bugs %][% END %]
- [% typeLabelLowerSingular = BLOCK %][% terms.bug %][% END %]
+[% IF action == "insert" %]
+ [% title = BLOCK %]
+ Create Flag Type for [% type.target_type == "bug" ? terms.Bugs : "Attachments" %]
+ [% IF type.id %]
+ Based on [% type.name FILTER html %]
+ [% END %]
+ [% END %]
+ [% doc_section = "flags-overview.html#flags-create" %]
[% ELSE %]
- [% title = "Create Flag Type for Attachments" %]
- [% typeLabelLowerPlural = BLOCK %]attachments[% END %]
- [% typeLabelLowerSingular = BLOCK %]attachment[% END %]
-[% END %]
-
-[% doc_section = "flags-overview.html#flags-create" %]
-[% IF last_action == "copy" %]
- [% title = BLOCK %]Create Flag Type Based on [% type.name FILTER html %][% END %]
-[% ELSIF last_action == "edit" %]
[% title = BLOCK %]Edit Flag Type [% type.name FILTER html %][% END %]
[% doc_section = "flags-overview.html#flags-edit" %]
[% END %]
@@ -47,21 +43,24 @@
table#form th { text-align: right; vertical-align: baseline; white-space: nowrap; }
table#form td { text-align: left; vertical-align: baseline; }
"
- onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, '__Any__');"
+ onload="var f = document.forms['flagtype_properties'];
+ selectProduct(f.product, f.component, null, null, '__Any__');"
javascript_urls=["js/productform.js"]
doc_section = doc_section
%]
-<form method="post" action="editflagtypes.cgi">
- <input type="hidden" name="action" value="[% action %]">
+<form id="flagtype_properties" method="post" action="editflagtypes.cgi">
+ <input type="hidden" name="action" value="[% action FILTER html %]">
+ <input type="hidden" name="can_fully_edit" value="[% can_fully_edit FILTER html %]">
<input type="hidden" name="id" value="[% type.id %]">
<input type="hidden" name="token" value="[% token FILTER html %]">
- <input type="hidden" name="target_type" value="[% type.target_type %]">
- [% FOREACH category = type.inclusions %]
- <input type="hidden" name="inclusions" value="[% category.value FILTER html %]">
+ <input type="hidden" name="target_type" value="[% type.target_type FILTER html %]">
+ <input type="hidden" name="check_clusions" value="[% check_clusions FILTER none %]">
+ [% FOREACH category = inclusions.values %]
+ <input type="hidden" name="inclusions" value="[% category FILTER html %]">
[% END %]
- [% FOREACH category = type.exclusions %]
- <input type="hidden" name="exclusions" value="[% category.value FILTER html %]">
+ [% FOREACH category = exclusions.values %]
+ <input type="hidden" name="exclusions" value="[% category FILTER html %]">
[% END %]
[%# Add a hidden button at the top of the form so that the user pressing "return"
@@ -72,21 +71,22 @@
<tr>
<th>Name:</th>
<td>
- a short name identifying this type<br>
- <input type="text" name="name" value="[% type.name FILTER html %]"
- size="50" maxlength="50">
+ a short name identifying this type.<br>
+ <input type="text" name="name" value="[% type.name FILTER html %]" size="50"
+ maxlength="50" [%- ' disabled="disabled"' UNLESS can_fully_edit %]>
</td>
</tr>
<tr>
<th>Description:</th>
<td>
- a comprehensive description of this type<br>
+ a comprehensive description of this type.<br>
[% INCLUDE global/textarea.html.tmpl
name = 'description'
minrows = 4
cols = 80
defaultcontent = type.description
+ disabled = !can_fully_edit
%]
</td>
</tr>
@@ -95,9 +95,15 @@
<th>Category:</th>
<td>
- the products/components to which [% typeLabelLowerPlural %] must
- (inclusions) or must not (exclusions) belong in order for users
- to be able to set flags of this type for them
+ the products/components to which [% type.target_type == "bug" ? terms.bugs : "attachments" %]
+ must (inclusions) or must not (exclusions) belong in order for users
+ to be able to set flags of this type for them.
+ [% UNLESS can_fully_edit %]
+ <p class="warning">This flagtype also applies to some products you are not allowed
+ to edit (and so which are not displayed in the lists below). Your limited privileges
+ means you are only allowed to add and remove this flagtype to/from products you can
+ edit, but not to edit other properties of the flagtype.</p>
+ [% END %]
<table>
<tr>
<td style="vertical-align: top;">
@@ -105,31 +111,31 @@
<select name="product" onchange="selectProduct(this, this.form.component, null, null, '__Any__');">
<option value="">__Any__</option>
[% FOREACH prod = products %]
- <option value="[% prod.name FILTER html %]"
- [% "selected" IF type.product.name == prod.name %]>
- [% prod.name FILTER html %]</option>
+ <option value="[% prod.name FILTER html %]">[% prod.name FILTER html %]</option>
[% END %]
</select><br>
<select name="component">
<option value="">__Any__</option>
[% FOREACH comp = components %]
- <option value="[% comp FILTER html %]"
- [% "selected" IF type.component.name == comp %]>
- [% comp FILTER html %]</option>
+ <option value="[% comp FILTER html %]">[% comp FILTER html %]</option>
[% END %]
</select><br>
- <input type="submit" name="categoryAction-include" value="Include">
- <input type="submit" name="categoryAction-exclude" value="Exclude">
+ <input type="submit" id="categoryAction-include"
+ name="categoryAction-include" value="Include">
+ <input type="submit" id="categoryAction-exclude"
+ name="categoryAction-exclude" value="Exclude">
</td>
<td style="vertical-align: top;">
<b>Inclusions:</b><br>
- [% PROCESS "global/select-menu.html.tmpl" name="inclusion_to_remove" multiple="1" size="7" options=type.inclusions %]<br>
- <input type="submit" name="categoryAction-removeInclusion" value="Remove Inclusion">
+ [% PROCESS category_select name="inclusion_to_remove" categories = inclusions %]<br>
+ <input type="submit" id="categoryAction-removeInclusion"
+ name="categoryAction-removeInclusion" value="Remove Inclusion">
</td>
<td style="vertical-align: top;">
<b>Exclusions:</b><br>
- [% PROCESS "global/select-menu.html.tmpl" name="exclusion_to_remove" multiple="1" size="7" options=type.exclusions %]<br>
- <input type="submit" name="categoryAction-removeExclusion" value="Remove Exclusion">
+ [% PROCESS category_select name="exclusion_to_remove" categories = exclusions %]<br>
+ <input type="submit" id="categoryAction-removeExclusion"
+ name="categoryAction-removeExclusion" value="Remove Exclusion">
</td>
</tr>
</table>
@@ -139,11 +145,12 @@
<tr>
<th>Sort Key:</th>
<td>
- a number between 1 and 32767 by which this type will be sorted
- when displayed to users in a list; ignore if you don't care
- what order the types appear in or if you want them to appear
- in alphabetical order<br>
- <input type="text" name="sortkey" value="[% type.sortkey || 1 %]" size="5" maxlength="5">
+ a number between 1 and [% constants.MAX_SMALLINT FILTER none %] by which
+ this type will be sorted when displayed to users in a list; ignore if you
+ don't care what order the types appear in or if you want them to appear
+ in alphabetical order.<br>
+ <input type="text" name="sortkey" value="[% type.sortkey || 1 %]" size="5" maxlength="5"
+ [%- ' disabled="disabled"' UNLESS can_fully_edit %]>
</td>
</tr>
@@ -151,6 +158,7 @@
<th> </th>
<td>
<input type="checkbox" id="is_active" name="is_active"
+ [%- ' disabled="disabled"' UNLESS can_fully_edit %]
[% " checked" IF type.is_active || !type.is_active.defined %]>
<label for="is_active">active (flags of this type appear in the UI and can be set)</label>
</td>
@@ -160,6 +168,7 @@
<th> </th>
<td>
<input type="checkbox" id="is_requestable" name="is_requestable"
+ [%- ' disabled="disabled"' UNLESS can_fully_edit %]
[% " checked" IF type.is_requestable || !type.is_requestable.defined %]>
<label for="is_requestable">requestable (users can ask for flags of this type to be set)</label>
</td>
@@ -176,7 +185,8 @@
<kbd>[% Param('emailsuffix') %]</kbd> will <em>not</em> be appended
to these addresses, so you should add it explicitly if so desired.
[% END %]<br>
- <input type="text" name="cc_list" value="[% type.cc_list FILTER html %]" size="80" maxlength="200">
+ <input type="text" name="cc_list" value="[% type.cc_list FILTER html %]" size="80"
+ maxlength="200" [%- ' disabled="disabled"' UNLESS can_fully_edit %]>
</td>
</tr>
@@ -184,6 +194,7 @@
<th> </th>
<td>
<input type="checkbox" id="is_requesteeble" name="is_requesteeble"
+ [%- ' disabled="disabled"' UNLESS can_fully_edit %]
[% " checked" IF type.is_requesteeble || !type.is_requesteeble.defined %]>
<label for="is_requesteeble">specifically requestable (users can ask specific other users
to set flags of this type as opposed to just asking the wind)</label>
@@ -194,9 +205,10 @@
<th> </th>
<td>
<input type="checkbox" id="is_multiplicable" name="is_multiplicable"
+ [%- ' disabled="disabled"' UNLESS can_fully_edit %]
[% " checked" IF type.is_multiplicable || !type.is_multiplicable.defined %]>
<label for="is_multiplicable">multiplicable (multiple flags of this type can be set on
- the same [% typeLabelLowerSingular %])</label>
+ the same [% type.target_type == "bug" ? terms.bug : "attachment" %])</label>
</td>
</tr>
@@ -204,8 +216,8 @@
<th>Grant Group:</th>
<td>
the group allowed to grant/deny flags of this type
- (to allow all users to grant/deny these flags, select no group)<br>
- [% PROCESS select selname = "grant_group" %]
+ (to allow all users to grant/deny these flags, select no group).<br>
+ [% PROCESS group_select selname = "grant_group" %]
</td>
</tr>
@@ -213,19 +225,16 @@
<th>Request Group:</th>
<td>
if flags of this type are requestable, the group allowed to request them
- (to allow all users to request these flags, select no group)<br>
+ (to allow all users to request these flags, select no group).<br>
Note that the request group alone has no effect if the grant group is not defined!<br>
- [% PROCESS select selname = "request_group" %]
+ [% PROCESS group_select selname = "request_group" %]
</td>
</tr>
<tr>
- <th></th>
+ <th> </th>
<td>
- <input type="submit" id="save" value="
- [%- IF (last_action == "enter" || last_action == "copy") %]Create
- [%- ELSE %]Save Changes
- [%- END %]">
+ <input type="submit" id="save" value="[% action == "insert" ? "Create" : "Save Changes" %]">
</td>
</tr>
@@ -240,13 +249,32 @@
[%# Block for SELECT fields #%]
[%############################################################################%]
-[% BLOCK select %]
- <select name="[% selname %]" id="[% selname %]">
+[% BLOCK group_select %]
+ <select name="[% selname %]" id="[% selname %]" [%- ' disabled="disabled"' UNLESS can_fully_edit %]>
<option value="">(no group)</option>
+ [% group_found = 0 %]
[% FOREACH group = groups %]
<option value="[% group.name FILTER html %]"
- [% " selected" IF (type.${selname} && type.${selname}.name == group.name) %]>
- [%- group.name FILTER html %]
+ [% IF type.${selname} && type.${selname}.name == group.name %]
+ [% ' selected="selected"' %]
+ [% group_found = 1 %]
+ [% END %]>
+ [%- group.name FILTER html ~%]
+ </option>
+ [% END %]
+ [% IF !group_found && type.${selname}.name %]
+ <option value="[% type.${selname}.name FILTER html %]" selected="selected">
+ [%- type.${selname}.name FILTER html ~%]
+ </option>
+ [% END %]
+ </select>
+[% END %]
+
+[% BLOCK category_select %]
+ <select name="[% name FILTER html %]" multiple="multiple" size="7">
+ [% FOREACH option = categories.keys.sort %]
+ <option value="[% categories.$option FILTER html %]">
+ [% option FILTER html %]
</option>
[% END %]
</select>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/flag-type/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/flag-type/list.html.tmpl
index d4bba94..220db89 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/flag-type/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/flag-type/list.html.tmpl
@@ -16,6 +16,7 @@
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
#%]
[% PROCESS global/variables.none.tmpl %]
@@ -30,7 +31,7 @@
.inactive { color: #787878; }
.multiplicable { display: block; }
"
- onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, '__All__');"
+ onload="var f = document.flagtype_form; selectProduct(f.product, f.component, null, null, '__All__');"
javascript_urls=["js/productform.js"]
doc_section = "flags-overview.html#flag-types"
%]
@@ -55,7 +56,7 @@
which are available to at least one component of the product are shown.
</p>
-<form action="editflagtypes.cgi" method="get">
+<form id="flagtype_form" name="flagtype_form" action="editflagtypes.cgi" method="get">
<table>
<tr>
<th><label for="product">Product:</label></th>
@@ -80,6 +81,11 @@
[% END %]
</select>
</td>
+ <td>
+ <input type="checkbox" id="show_flag_counts" name="show_flag_counts" value="1"
+ [%+ 'checked="checked"' IF show_flag_counts %]>
+ <label for="show_flag_counts">Show flag counts</label>
+ </td>
<td><input type="submit" id="submit" value="Filter"></td>
</tr>
</table>
@@ -114,6 +120,11 @@
<th>Properties</th>
<th>Grant group</th>
<th>Request group</th>
+ [% IF show_flag_counts %]
+ <th>Flags</th>
+ [%# Note to translators: translate the strings in quotes only. %]
+ [% state_desc = {granted = 'granted' denied = 'denied' pending = 'pending'} %]
+ [% END %]
<th>Actions</th>
</tr>
@@ -136,6 +147,21 @@
</td>
<td>[% IF type.grant_group %][% type.grant_group.name FILTER html %][% END %]</td>
<td>[% IF type.request_group %][% type.request_group.name FILTER html %][% END %]</td>
+ [% IF show_flag_counts %]
+ <td>
+ [% FOREACH state = ['granted', 'pending', 'denied'] %]
+ [% bug_list = bug_lists.${type.id}.$state || [] %]
+ [% IF bug_list.size %]
+ <a href="buglist.cgi?bug_id=[% bug_list.unique.nsort.join(",") FILTER html %]">
+ [% bug_list.size FILTER html %] [%+ state_desc.$state FILTER html %]
+ </a>
+ <br>
+ [% ELSE %]
+ 0 [% state_desc.$state FILTER html %]<br>
+ [% END %]
+ [% END %]
+ </td>
+ [% END %]
<td>
<a href="editflagtypes.cgi?action=copy&id=[% type.id %]">Copy</a>
| <a href="editflagtypes.cgi?action=confirmdelete&id=[% type.id %]">Delete</a>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/groups/confirm-remove.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/groups/confirm-remove.html.tmpl
index cdb070d..54d9616 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/groups/confirm-remove.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/groups/confirm-remove.html.tmpl
@@ -59,7 +59,7 @@
<input type="hidden" name="action" value="remove_regexp">
<input name="token" type="hidden" value="[% token FILTER html %]">
- <input name="confirm" type="submit" value="Confirm">
+ <input id="confirm" name="confirm" type="submit" value="Confirm">
<p>Or <a href="editgroups.cgi">return to the Edit Groups page</a>.</p>
</form>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/groups/delete.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/groups/delete.html.tmpl
index e847ada..b93c84b 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/groups/delete.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/groups/delete.html.tmpl
@@ -19,18 +19,14 @@
# Joel Peshkin <bugreport@peshkin.net>
# Jacob Steenhagen <jake@bugzilla.org>
# Vlad Dascalu <jocuri@softhome.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
#%]
[%# INTERFACE:
- # gid: number. The group ID.
- # name: string. The name of the group.
- # description: string. The description of the group.
- # hasusers: boolean int. True if the group includes users in it.
- # hasbugs: boolean int. True if the group includes bugs in it.
- # hasproduct: boolean int. True if the group is binded to a product.
- # hasflags: boolean int. True if the group is used by a flag type.
- # shared_queries: int. Number of saved searches being shared with this group.
- # buglist: string. The list of bugs included in this group.
+ # group: A Bugzilla::Group object representing the group that is
+ # about to be deleted.
+ # shared_queries: int; The number of queries being shared with this
+ # group.
#%]
@@ -46,29 +42,43 @@
<th>Description</th>
</tr>
<tr>
- <td>[% gid FILTER html %]</td>
- <td>[% name FILTER html %]</td>
- <td>[% description FILTER html_light %]</td>
+ <td>[% group.id FILTER html %]</td>
+ <td>[% group.name FILTER html %]</td>
+ <td>[% group.description FILTER html_light %]</td>
</tr>
</table>
<form method="post" action="editgroups.cgi">
- [% IF hasusers %]
- <p><b>One or more users belong to this group. You cannot delete
- this group while there are users in it.</b>
+ [% IF group.members_non_inherited.size %]
+ <p><b>[% group.members_non_inherited.size FILTER html %] users belong
+ directly to this group. You cannot delete this group while there are
+ users in it.</b>
- <br><a href="editusers.cgi?action=list&groupid=[% gid FILTER html %]&grouprestrict=1">Show
- me which users</a> - <input type="checkbox" name="removeusers">Remove
- all users from this group for me.</p>
+ <br><a href="editusers.cgi?action=list&groupid=
+ [%- group.id FILTER uri %]&grouprestrict=1">Show
+ me which users</a> - <label><input type="checkbox" name="removeusers">Remove
+ all users from this group for me.</label></p>
[% END %]
- [% IF hasbugs %]
- <p><b>One or more [% terms.bug %] reports are visible only to this group.
- You cannot delete this group while any [% terms.bugs %] are using it.</b>
+ [% IF group.granted_by_direct(constants.GROUP_MEMBERSHIP).size %]
+ <p><b>Members of this group inherit membership in the following groups:</b></p>
+ <ul>
+ [% FOREACH grantor = group.granted_by_direct(constants.GROUP_MEMBERSHIP) %]
+ <li>[% grantor.name FILTER html %]</li>
+ [% END %]
+ </ul>
+ [% END %]
- <br><a href="buglist.cgi?bug_id=[% buglist FILTER html %]">Show me
- which [% terms.bugs %]</a> - <input type="checkbox" name="removebugs">Remove
- all [% terms.bugs %] from this group restriction for me.</p>
+ [% IF group.bugs.size %]
+ <p><b>[% group.bugs.size FILTER html %] [%+ terms.bug %] reports are
+ visible only to this group. You cannot delete this group while any
+ [%+ terms.bugs %] are using it.</b>
+
+ <br><a href="buglist.cgi?field0-0-0=bug_group&type0-0-0=equals&value0-0-0=
+ [%- group.name FILTER uri %]">Show me
+ which [% terms.bugs %]</a> -
+ <label><input type="checkbox" name="removebugs">Remove
+ all [% terms.bugs %] from this group restriction for me.</label></p>
<p><b>NOTE:</b> It's quite possible to make confidential [% terms.bugs %]
public by checking this box. It is <B>strongly</B> suggested
@@ -76,21 +86,63 @@
the box.</p>
[% END %]
- [% IF hasproduct %]
- <p><b>This group is tied to the <U>[% name FILTER html %]</U> product.
- You cannot delete this group while it is tied to a product.</b>
+ [% IF group.products.size %]
+ <p><b>This group is tied to the following products:</b></p>
+ [% SET any_hidden = 0 %]
+ <ul>
+ [% FOREACH data = group.products %]
- <br><input type="checkbox" name="unbind">Delete this group anyway,
- and make the product <U>[% name FILTER html %]</U> publicly visible.</p>
+ [% SET active = [] %]
+ [% FOREACH control = data.controls.keys.sort %]
+ [% NEXT IF !data.controls.$control %]
+ [% IF control == 'othercontrol' OR control == 'membercontrol' %]
+ [% SWITCH data.controls.$control %]
+ [% CASE constants.CONTROLMAPMANDATORY %]
+ [% SET type = "Mandatory" %]
+ [% CASE constants.CONTROLMAPSHOWN %]
+ [% SET type = "Shown" %]
+ [% CASE constants.CONTROLMAPDEFAULT %]
+ [% SET type = "Default" %]
+ [% END %]
+ [% active.push("$control: $type") %]
+ [% ELSE %]
+ [% active.push(control) %]
+ [% END %]
+ [% END %]
+
+ [% SET hidden = 0 %]
+ [% IF data.controls.othercontrol == constants.CONTROLMAPMANDATORY
+ AND data.controls.membercontrol == constants.CONTROLMAPMANDATORY
+ AND data.controls.entry
+ %]
+ [% SET hidden = 1 %]
+ [% END %]
+
+ <li><a href="editproducts.cgi?action=editgroupcontrols&product=
+ [%- data.product.name FILTER uri %]">
+ [%- data.product.name FILTER html %]</a>
+ ([% active.join(', ') FILTER html %])
+ [% IF hidden %]
+ <strong>WARNING: This product is currently hidden.
+ Deleting this group will make this product publicly visible.
+ </strong>
+ [% END %]</li>
+ [% END %]
+ </ul>
+
+ <p><label><input type="checkbox" name="unbind">Delete this group anyway,
+ and remove these controls.</label></p>
[% END %]
- [% IF hasflags %]
+ [% IF group.flag_types.size %]
<p><b>This group restricts who can make changes to flags of certain types.
You cannot delete this group while there are flag types using it.</b>
- <br><a href="editflagtypes.cgi?action=list&group=[% gid FILTER html %]">Show
- me which types</a> - <input type="checkbox" name="removeflags">Remove all
- flag types from this group for me.</p>
+ <br><a href="editflagtypes.cgi?action=list&group=
+ [%- group.id FILTER uri %]">Show
+ me which types</a> -
+ <label><input type="checkbox" name="removeflags">Remove all
+ flag types from this group for me.</label></p>
[% END %]
[% IF shared_queries %]
@@ -115,7 +167,9 @@
<h2>Confirmation</h2>
<p>Do you really want to delete this group?</p>
- [% IF (hasusers || hasbugs || hasproduct || hasflags) %]
+ [% IF group.users.size || group.bugs.size || group.products.size
+ || group.flags.size
+ %]
<p><b>You must check all of the above boxes or correct the
indicated problems first before you can proceed.</b></p>
[% END %]
@@ -123,7 +177,7 @@
<p>
<input type="submit" id="delete" value="Yes, delete">
<input type="hidden" name="action" value="delete">
- <input type="hidden" name="group" value="[% gid FILTER html %]">
+ <input type="hidden" name="group" value="[% group.id FILTER html %]">
<input type="hidden" name="token" value="[% token FILTER html %]">
</p>
</form>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/groups/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/groups/edit.html.tmpl
index 17d8ca1..9403c07 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/groups/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/groups/edit.html.tmpl
@@ -184,7 +184,7 @@
</table>
[% END %]
- <input type="submit" value="Update Group">
+ <input type="submit" id="update-group" value="Update Group">
<input type="hidden" name="token" value="[% token FILTER html %]">
</form>
@@ -197,10 +197,10 @@
<table><tr><td>
<form method="post" action="editgroups.cgi">
<fieldset>
- <legend>Remove all explict memberships from users whose login names
+ <legend>Remove all explicit memberships from users whose login names
match the following regular expression:</legend>
<input type="text" size="20" name="regexp">
- <input type="submit" value="Remove Memberships">
+ <input type="submit" id="remove-membership" value="Remove Memberships">
<p>If you leave the field blank, all explicit memberships in
this group will be removed.</p>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/groups/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/groups/list.html.tmpl
index 029e5f0..1d137dc 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/groups/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/groups/list.html.tmpl
@@ -66,68 +66,60 @@
]
%]
-[% overrides.is_active_bug_group = [ {
- match_value => "0"
- match_field => 'is_active_bug_group'
- override_content => 1
- content => " "
- },
- {
- match_value => "1"
- match_field => 'is_active_bug_group'
- override_content => 1
- content => "X"
- }]
- overrides.userregexp = [ {
- match_value => ""
- match_field => 'userregexp'
- override_content => 1
- content => " "
- }]
- overrides.action = [ {
- match_value => Param("chartgroup")
- match_field => 'name'
- override_content => 1
- content => "(used as the 'chartgroup')"
- },
- {
- match_value => Param("insidergroup")
- match_field => 'name'
- override_content => 1
- content => "(used as the 'insidergroup')"
- },
- {
- match_value => Param("timetrackinggroup")
- match_field => 'name'
- override_content => 1
- content => "(used as the 'timetrackinggroup')"
- },
- {
- match_value => Param("querysharegroup")
- match_field => 'name'
- override_content => 1
- content => "(used as the 'querysharegroup')"
- },
- {
- match_value => "1"
- match_field => 'isbuggroup'
- override_content => 1
- content => "Delete"
- override_contentlink => 1
- contentlink => del_contentlink
- }]
- overrides.type = [ {
- match_value => "0"
- match_field => 'isbuggroup'
- override_content => 1
- content => "system"
- },
- {
- match_value => "1"
- match_field => 'isbuggroup'
- override_content => 1
- content => "user"
- }]
+[% overrides.is_active_bug_group = {
+ 'is_active_bug_group' => {
+ "0" => {
+ override_content => 1
+ content => " "
+ }
+ "1" => {
+ override_content => 1
+ content => "X"
+ }
+ }
+ }
+
+ overrides.userregexp = {
+ 'userregexp' => {
+ "" => {
+ override_content => 1
+ content => " "
+ }
+ }
+ }
+%]
+
+[% FOREACH group IN ["chartgroup", "insidergroup", "timetrackinggroup", "querysharegroup"] %]
+ [% special_group = Param(group) %]
+
+ [% IF special_group %]
+ [% overrides.action.name.$special_group = {
+ override_content => 1
+ content => "(used as the '$group')"
+ }
+ %]
+ [% END %]
+[% END %]
+
+[% overrides.action.isbuggroup = {
+ "1" => {
+ override_content => 1
+ content => "Delete"
+ override_contentlink => 1
+ contentlink => del_contentlink
+ }
+ }
+
+ overrides.type.isbuggroup = {
+ "0" => {
+ override_content => 1
+ content => "system"
+ }
+ "1" => {
+ override_content => 1
+ content => "user"
+ }
+ }
%]
[% PROCESS admin/table.html.tmpl
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/keywords/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/keywords/edit.html.tmpl
index c4b9a64..65a6229 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/keywords/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/keywords/edit.html.tmpl
@@ -53,7 +53,7 @@
<th align="right">[% terms.Bugs %]:</th>
<td>
[% IF keyword.bug_count > 0 %]
- <a href="buglist.cgi?keywords=[% keyword.name FILTER url_quote %]">
+ <a href="buglist.cgi?keywords=[% keyword.name FILTER uri %]">
[% keyword.bug_count FILTER html %]</a>
[% ELSE %]
none
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/milestones/confirm-delete.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/milestones/confirm-delete.html.tmpl
index ea89b80..068e8e2 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/milestones/confirm-delete.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/milestones/confirm-delete.html.tmpl
@@ -52,8 +52,8 @@
[% IF milestone.bug_count %]
<a title="List of [% terms.bugs %] targetted at milestone '
[% milestone.name FILTER html %]'"
- href="buglist.cgi?target_milestone=[% milestone.name FILTER url_quote %]&product=
- [%- product.name FILTER url_quote %]">
+ href="buglist.cgi?target_milestone=[% milestone.name FILTER uri %]&product=
+ [%- product.name FILTER uri %]">
[% milestone.bug_count FILTER none %]</a>
[% ELSE %]
None
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/milestones/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/milestones/edit.html.tmpl
index dfe9d1b..ef4b7fc 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/milestones/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/milestones/edit.html.tmpl
@@ -38,16 +38,20 @@
<table border="0" cellpadding="4" cellspacing="0">
<tr>
- <th valign="top"><label for="milestone">Milestone:</label></th>
+ <th class="field_label"><label for="milestone">Milestone:</label></th>
<td><input id="milestone" size="20" maxlength="20" name="milestone" value="
[%- milestone.name FILTER html %]"></td>
</tr>
<tr>
- <th align="right"><label for="sortkey">Sortkey:</label></th>
+ <th class="field_label"><label for="sortkey">Sortkey:</label></th>
<td><input id="sortkey" size="20" maxlength="20" name="sortkey" value="
[%- milestone.sortkey FILTER html %]"></td>
</tr>
-
+ <tr>
+ <th class="field_label"><label for="isactive">Enabled For [% terms.Bugs %]:</label></th>
+ <td><input id="isactive" name="isactive" type="checkbox" value="1"
+ [% 'checked="checked"' IF milestone.isactive %]></td>
+ </tr>
</table>
<input type="hidden" name="milestoneold" value="[% milestone.name FILTER html %]">
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/milestones/footer.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/milestones/footer.html.tmpl
index e91e5f9..1cae69e 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/milestones/footer.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/milestones/footer.html.tmpl
@@ -40,7 +40,7 @@
[% UNLESS no_add_milestone_link %]
<a title="Add a milestone to product '[% product.name FILTER html %]'"
href="editmilestones.cgi?action=add&product=
- [%- product.name FILTER url_quote %]">Add</a> a milestone.
+ [%- product.name FILTER uri %]">Add</a> a milestone.
[% END %]
[% IF milestone.name && !no_edit_milestone_link %]
@@ -48,20 +48,20 @@
title="Edit Milestone '[% milestone.name FILTER html %]' of product '
[%- product.name FILTER html %]'"
href="editmilestones.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]&milestone=
- [%- milestone.name FILTER url_quote %]">
+ [%- product.name FILTER uri %]&milestone=
+ [%- milestone.name FILTER uri %]">
'[% milestone.name FILTER html %]'</a>.
[% END %]
[% UNLESS no_edit_other_milestones_link %]
Edit other milestones of product <a
href="editmilestones.cgi?product=
- [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>.
+ [%- product.name FILTER uri %]">'[% product.name FILTER html %]'</a>.
[% END %]
Edit product <a
href="editproducts.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>.
+ [%- product.name FILTER uri %]">'[% product.name FILTER html %]'</a>.
</p>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/milestones/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/milestones/list.html.tmpl
index 00e5bd9..6392f56 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/milestones/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/milestones/list.html.tmpl
@@ -37,11 +37,11 @@
%]
[% edit_contentlink = BLOCK %]editmilestones.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]&milestone=%%name%%[% END %]
+ [%- product.name FILTER uri %]&milestone=%%name%%[% END %]
[% delete_contentlink = BLOCK %]editmilestones.cgi?action=del&product=
- [%- product.name FILTER url_quote %]&milestone=%%name%%[% END %]
+ [%- product.name FILTER uri %]&milestone=%%name%%[% END %]
[% bug_count_contentlink = BLOCK %]buglist.cgi?target_milestone=%%name%%&product=
- [%- product.name FILTER url_quote %][% END %]
+ [%- product.name FILTER uri %][% END %]
[% columns = [
@@ -53,6 +53,11 @@
{
name => "sortkey"
heading => "Sortkey"
+ },
+ {
+ name => "isactive"
+ heading => "Active"
+ yesno_field => 1
}
]
%]
@@ -77,18 +82,17 @@
})
%]
-[%# We want to override the usual 'Delete' link for the default
- milestone %]
-[% overrides.action = [ {
- match_value => product.default_milestone
- match_field => 'name'
+[%# We want to override the usual 'Delete' link for the default milestone %]
+[% overrides.action.name.${product.default_milestone} = {
override_content => 1
content => "(Default milestone)"
override_contentlink => 1
contentlink => undef
- } ]
+ }
%]
+[% Hook.process('before_table') %]
+
[% PROCESS admin/table.html.tmpl
columns = columns
data = product.milestones
@@ -97,7 +101,7 @@
[% IF ! showbugcounts %]
- <p><a href="editmilestones.cgi?product=[% product.name FILTER url_quote %]&showbugcounts=1">
+ <p><a href="editmilestones.cgi?product=[% product.name FILTER uri %]&showbugcounts=1">
Redisplay table with [% terms.bug %] counts (slower)</a></p>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/admin.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/admin.html.tmpl
index 639ea66..dd83ebb 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/admin.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/admin.html.tmpl
@@ -37,9 +37,5 @@
"$terms.Bugzilla will issue a warning in case you'd run into inconsistencies " _
"when you're about to do so, but such deletions remain kinda scary. " _
"So, you have to turn on this option before any such deletions " _
- "will ever happen.",
-
- supportwatchers => "Support one user watching (ie getting copies of all related " _
- "email about) another's ${terms.bugs}. Useful for people going on " _
- "vacation, and QA folks watching particular developers' ${terms.bugs}." }
+ "will ever happen." }
%]
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/advanced.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/advanced.html.tmpl
new file mode 100644
index 0000000..a8e8a29
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/advanced.html.tmpl
@@ -0,0 +1,81 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%
+ title = "Advanced"
+ desc = "Settings for advanced configurations."
+%]
+
+[% sts_desc = BLOCK %]
+ Enables the sending of the
+ <a href="http://en.wikipedia.org/wiki/Strict_Transport_Security">Strict-Transport-Security</a>
+ header along with HTTP responses on SSL connections. This adds greater
+ security to your SSL connections by forcing the browser to always
+ access your domain over SSL and never accept an invalid certificate.
+ However, it should only be used if you have the <code>ssl_redirect</code>
+ parameter turned on, [% terms.Bugzilla %] is the only thing running
+ on its domain (i.e., your <code>urlbase</code> is something like
+ <code>http://bugzilla.example.com/</code>), and you never plan to disable
+ the <code>ssl_redirect</code> parameter.
+ <ul>
+ <li>
+ off - Don't send the Strict-Transport-Security header with requests.
+ </li>
+ <li>
+ this_domain_only - Send the Strict-Transport-Security header with all
+ requests, but only support it for the current domain.
+ </li>
+ <li>
+ include_subdomains - Send the Strict-Transport-Security header along
+ with the <code>includeSubDomains</code> flag, which will apply the
+ security change to all subdomains. This is especially useful when
+ combined with an <code>attachment_base</code> that exists as (a)
+ subdomain(s) under the main [% terms.Bugzilla %] domain.
+ </li>
+ </ul>
+[% END %]
+
+[% param_descs = {
+ cookiedomain =>
+ "If your website is at 'www.foo.com', setting this to"
+ _ " '.foo.com' will also allow 'bar.foo.com' to access"
+ _ " $terms.Bugzilla cookies. This is useful if you have more than"
+ _ " one hostname pointing at the same web server, and you"
+ _ " want them to share the $terms.Bugzilla cookie.",
+
+ inbound_proxies =>
+ "When inbound traffic to $terms.Bugzilla goes through a proxy,"
+ _ " $terms.Bugzilla thinks that the IP address of every single"
+ _ " user is the IP address of the proxy. If you enter a comma-separated"
+ _ " list of IPs in this parameter, then $terms.Bugzilla will trust any"
+ _ " <code>X-Forwarded-For</code> header sent from those IPs,"
+ _ " and use the value of that header as the end user's IP address.",
+
+ proxy_url =>
+ "$terms.Bugzilla may have to access the web to get notifications about"
+ _ " new releases (see the <tt>upgrade_notification</tt> parameter)."
+ _ " If your $terms.Bugzilla server is behind a proxy, it may be"
+ _ " necessary to enter its URL if the web server cannot access the"
+ _ " HTTP_PROXY environment variable. If you have to authenticate,"
+ _ " use the <code>http://user:pass@proxy_url/</code> syntax.",
+
+ strict_transport_security => sts_desc,
+} %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/attachment.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/attachment.html.tmpl
index 47d29bf..69f62e9 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/attachment.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/attachment.html.tmpl
@@ -45,7 +45,7 @@
_ " That is, a different domain name that resolves to this exact"
_ " same $terms.Bugzilla installation.</p>"
_ "<p>Note that if you have set the"
- _ " <a href=\"editparams.cgi?section=core#cookiedomain\"><tt>cookiedomain</tt>"
+ _ " <a href=\"editparams.cgi?section=advanced#cookiedomain_desc\"><tt>cookiedomain</tt>"
_" parameter</a>, you should set <tt>attachment_base</tt> to use a"
_ " domain that would <em>not</em> be matched by"
_ " <tt>cookiedomain</tt>.</p>"
@@ -60,27 +60,16 @@
allow_attachment_deletion => "If this option is on, administrators will be able to delete " _
"the content of attachments.",
- allow_attach_url => "If this option is on, it will be possible to " _
- "specify a URL when creating an attachment and " _
- "treat the URL itself as if it were an attachment.",
+ maxattachmentsize => "The maximum size (in kilobytes) of attachments to be stored " _
+ "in the database. If a file larger than this size is attached " _
+ "to ${terms.abug}, $terms.Bugzilla will look at the " _
+ "<a href='#maxlocalattachment'><tt>maxlocalattachment</tt> parameter</a> " _
+ "to determine if the file can be stored locally on the web server. " _
+ "If the file size exceeds both limits, then the attachment is rejected. " _
+ "Settings both parameters to 0 will prevent attaching files to ${terms.bugs}.",
- maxpatchsize => "The maximum size (in kilobytes) of patches. $terms.Bugzilla will not " _
- "accept patches greater than this number of kilobytes in size. " _
- "To accept patches of any size (subject to the limitations of " _
- "your server software), set this value to zero.",
-
- maxattachmentsize => "The maximum size (in kilobytes) of non-patch attachments. " _
- "$terms.Bugzilla will not accept attachments greater than this number " _
- "of kilobytes in size. To accept attachments of any size " _
- "(subject to the limitations of your server software), set this " _
- "value to zero.",
-
- maxlocalattachment => "The maximum size (in megabytes) of attachments identified by " _
- "the user as 'Big Files' to be stored locally on the webserver. " _
- "If set to zero, attachments will never be kept on the local " _
- "filesystem.",
-
- convert_uncompressed_images => "If this option is on, attachments with content type image/bmp " _
- "will be converted to image/png and compressed before uploading to " _
- "the database to conserve disk space." }
+ maxlocalattachment => "The maximum size (in megabytes) of attachments to be stored " _
+ "locally on the web server. If set to a value lower than the " _
+ "<a href='#maxattachmentsize'><tt>maxattachmentsize</tt> parameter</a>, " _
+ "attachments will never be kept on the local filesystem." }
%]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/auth.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/auth.html.tmpl
index 94a748b..2e11dff 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/auth.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/auth.html.tmpl
@@ -70,8 +70,8 @@
<dt>RADIUS</dt>
<dd>
RADIUS authentication using a RADIUS server.
- This method is experimental; please see the
- $terms.Bugzilla documentation for more information.
+ Please see the $terms.Bugzilla documentation for
+ more information.
Using this method requires
<a href=\"?section=radius\">additional
parameters</a> to be set.
@@ -103,11 +103,6 @@
</li>
</ul>",
- loginnetmask => "The number of bits for the netmask used if a user chooses to " _
- "allow a login to be valid for more than a single IP. Setting " _
- "this to 32 disables this feature.<br> " _
- "Note that enabling this may decrease the security of your system.",
-
requirelogin => "If this option is set, all access to the system beyond the " _
"front page will require a login. No anonymous users will " _
"be permitted.",
@@ -125,10 +120,22 @@
"the <tt>emailregexp</tt> param to only allow local usernames, " _
"but you want the mail to be delivered to username@my.local.hostname.",
- createemailregexp => "This defines the regexp to use for email addresses that are " _
+ createemailregexp => "This defines the (case-insensitive) regexp to use for email addresses that are " _
"permitted to self-register using a 'New Account' feature. The " _
"default (.*) permits any account matching the emailregexp " _
"to be created. If this parameter is left blank, no users " _
"will be permitted to create their own accounts and all accounts " _
- "will have to be created by an administrator." }
+ "will have to be created by an administrator.",
+
+ password_complexity =>
+ "Set the complexity required for passwords. In all cases must the passwords " _
+ "be at least ${constants.USER_PASSWORD_MIN_LENGTH} characters long." _
+ "<ul><li>no_constraints - No complexity required.</li>" _
+ "<li>mixed_letters - Passwords must contain at least one UPPER and one lower " _
+ "case letter.</li>" _
+ "<li>letters_numbers - Passwords must contain at least one UPPER and one " _
+ "lower case letter and a number.</li>" _
+ "<li>letters_numbers_specialchars - Passwords must contain at least one " _
+ "UPPER or one lower case letter, a number and a special character.</li></ul>"
+ }
%]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/bugchange.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/bugchange.html.tmpl
index 458bc4e..15d4f1e 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/bugchange.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/bugchange.html.tmpl
@@ -23,6 +23,10 @@
desc = "Set up $terms.bug change policies"
%]
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% accept_status = display_value('bug_status', 'IN_PROGRESS') FILTER html %]
+
[% param_descs = {
duplicate_or_move_bug_status => "When $terms.abug is marked as a duplicate of another one " _
"or is moved to another installation, use this $terms.bug status."
@@ -37,18 +41,14 @@
"If off, then all $terms.bugs initially have the default " _
"milestone for the product being filed in.",
- musthavemilestoneonaccept => "If you are using Target Milestone, do you want to require that " _
- "the milestone be set in order for a user to ACCEPT a ${terms.bug}?",
-
- commentonclearresolution => "If this option is on, the user needs to enter a short comment if " _
- "the ${terms.bug}'s resolution is cleared.",
+ musthavemilestoneonaccept =>
+ "If you are using ${field_descs.target_milestone}, do you want to require"
+ _ " that the milestone be set in order for a user to set"
+ _ " ${terms.abug}'s status to ${accept_status}?",
commentonchange_resolution => "If this option is on, the user needs to enter a short " _
"comment if the resolution of the $terms.bug changes.",
- commentonreassignbycomponent => "If this option is on, the user needs to enter a short comment if " _
- "the $terms.bug is reassigned by component.",
-
commentonduplicate => "If this option is on, the user needs to enter a short comment " _
"if the $terms.bug is marked as duplicate.",
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/bugfields.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/bugfields.html.tmpl
index bdd9ad8..58b08f6 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/bugfields.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/bugfields.html.tmpl
@@ -28,23 +28,21 @@
"specific classification. But you must have 'editclassification' " _
"permissions enabled in order to edit classifications.",
- showallproducts => "If this is on and useclassification is set, $terms.Bugzilla will add a " _
- "'All' link in the 'New $terms.Bug' page to list all available products.",
-
usetargetmilestone => "Do you wish to use the Target Milestone field?",
useqacontact => "Do you wish to use the QA Contact field?",
usestatuswhiteboard => "Do you wish to use the Status Whiteboard field?",
- usevotes => "Do you wish to allow users to vote for ${terms.bugs}? Note that in order " _
- "for this to be effective, you will have to change the maximum " _
- "votes allowed in a product to be non-zero in " _
- "<a href=\"editproducts.cgi\">the product edit page</a>.",
-
usebugaliases => "Do you wish to use $terms.bug aliases, which allow you to assign " _
"$terms.bugs an easy-to-remember name by which you can refer to them?",
+ use_see_also =>
+ "Do you wish to use the See Also field? It allows you refer to"
+ _ " $terms.bugs in other installations. Even if you disable this field,"
+ _ " $terms.bug relationships (URLs) already set on $terms.bugs will"
+ _ " still appear and can be removed.",
+
defaultpriority => "This is the priority that newly entered $terms.bugs are set to.",
defaultseverity => "This is the severity that newly entered $terms.bugs are set to.",
@@ -60,4 +58,4 @@
"You can leave this empty: " _
"$terms.Bugzilla will then use the operating system that the browser " _
"reports to be running on as the default." }
-%]
\ No newline at end of file
+%]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/bugmove.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/bugmove.html.tmpl
deleted file mode 100644
index 911bc33..0000000
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/bugmove.html.tmpl
+++ /dev/null
@@ -1,49 +0,0 @@
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Dave Miller <justdave@bugzilla.org>
- # Frédéric Buclin <LpSolit@gmail.com>
- #%]
-[%
- title = "$terms.Bug Moving"
- desc = "Set up parameters to move $terms.bugs to/from another installation"
-%]
-
-[% param_descs = {
- "move-enabled" => "If this is on, $terms.Bugzilla will allow certain people " _
- "to move $terms.bugs to the defined database.",
-
- "move-button-text" => "The text written on the Move button. Explain where the $terms.bug is " _
- "being moved to.",
-
- "move-to-url" => "The URL of the database we allow some of our $terms.bugs to be moved to.",
-
- "move-to-address" => "To move ${terms.bugs}, an email is sent to the target database. This is " _
- "the email address that database uses to listen for incoming ${terms.bugs}.",
-
- "moved-from-address" => "To move ${terms.bugs}, an email is sent to the target database. This is " _
- "the email address from which this mail, and error messages are sent.",
-
- movers => "A list of people with permission to move $terms.bugs and reopen moved " _
- "${terms.bugs} (in case the move operation fails).",
-
- "moved-default-product" => "$terms.Bugs moved from other databases to here are assigned " _
- "to this product.",
-
- "moved-default-component" => "$terms.Bugs moved from other databases to here are assigned " _
- "to this component." }
-%]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/common.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/common.html.tmpl
index c23c2ca..d86da0d 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/common.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/common.html.tmpl
@@ -22,11 +22,13 @@
# panel: hash representing the current panel.
#%]
+[% PROCESS "global/field-descs.none.tmpl" %]
+
[% sortlist_separator = '---' %]
<dl>
[% FOREACH param = panel.param_list %]
- <dt><a name="[% param.name FILTER html %]">[% param.name FILTER html %]</a></dt>
+ <dt id="[% param.name FILTER html %]_desc">[% param.name FILTER html %]</dt>
<dd>[% panel.param_descs.${param.name} FILTER none %]
<p>
[% IF param.type == "t" %]
@@ -60,62 +62,65 @@
[% END %]
</select>
[% ELSIF param.type == "o" %]
- <script type="text/javascript"><!--
- document.write("<span style=\"display: none\">");
- // -->
- </script>
<input id="input_[% param.name FILTER html %]" size="80"
name="[% param.name FILTER html %]"
value="[% Param(param.name) FILTER html %]"><br>
- <script type="text/javascript"><!--
- document.write("<\/span>");
- // -->
- </script>
[% boxSize = 7 %]
[% boxSize = 3 + param.choices.size IF param.choices.size < 7 %]
[% plist = Param(param.name).split(',') %]
- <script type="text/javascript"><!--
- document.write(
- '<table>' +
- ' <tr>' +
- ' <td rowspan="2">' +
- ' <select id="select_[% param.name FILTER html %]"' +
- ' size="[% boxSize FILTER html %]"' +
- ' name="select_[% param.name FILTER html %]">' +
- [% FOREACH item = plist %]
- ' <option value="[% item FILTER html %]">[% item FILTER html %]<\/option>' +
- [% END %]
- ' <option class="sortlist_separator"' +
- ' disabled="disabled"' +
- ' value="[% sortlist_separator %]">active↑ ↓inactive<\/option>' +
- [% FOREACH item = param.choices %]
- [% IF lsearch(plist, item) == -1 %]
- ' <option value="[% item FILTER html %]">[% item FILTER html %]<\/option>' +
- [% END %]
- [% END %]
- ' <\/select>' +
- ' <\/td>' +
- ' <td style="vertical-align: bottom">' +
- ' <button type="button"' +
- ' onClick="sortedList_moveItem(\'[% param.name FILTER html %]\', -1, \'[% sortlist_separator %]\');">↑<\/button>' +
- ' <\/td>' +
- ' <\/tr>' +
- ' <tr>' +
- ' <td style="vertical-align: top">' +
- ' <button type="button"' +
- ' onClick="sortedList_moveItem(\'[% param.name FILTER html %]\', +1, \'[% sortlist_separator %]\');">↓<\/button>' +
- ' <\/td>' +
- ' <\/tr>' +
- '<\/table>');
- // -->
+ <table id="table_[% param.name FILTER html %]" class="bz_default_hidden">
+ <tr>
+ <td rowspan="2">
+ <select id="select_[% param.name FILTER html %]"
+ name="select_[% param.name FILTER html %]"
+ size="[% boxSize FILTER html %]">
+ [% FOREACH item = plist %]
+ <option value="[% item FILTER html %]">[% item FILTER html %]</option>
+ [% END %]
+ <option class="sortlist_separator" disabled="disabled"
+ value="[% sortlist_separator %]">active↑ ↓inactive</option>
+ [% FOREACH item = param.choices %]
+ [% IF lsearch(plist, item) == -1 %]
+ <option value="[% item FILTER html %]">[% item FILTER html %]</option>
+ [% END %]
+ [% END %]
+ </select>
+ </td>
+ <td style="vertical-align: bottom">
+ <button type="button"
+ onClick="sortedList_moveItem('[% param.name FILTER html %]', -1, '[% sortlist_separator %]');">↑</button>
+ </td>
+ </tr>
+
+ <tr>
+ <td style="vertical-align: top">
+ <button type="button"
+ onClick="sortedList_moveItem('[% param.name FILTER html %]', +1, '[% sortlist_separator %]');">↓</button>
+ </td>
+ </tr>
+ </table>
+
+ <script type="text/javascript">
+ bz_toggleClass("input_[% param.name FILTER html %]", "bz_default_hidden");
+ bz_toggleClass("table_[% param.name FILTER html %]", "bz_default_hidden");
</script>
[% ELSIF param.type == "s" %]
<select name="[% param.name FILTER html %]" id="[% param.name FILTER html %]">
[% FOREACH item = param.choices %]
<option value="[% item FILTER html %]"
[% " selected=\"selected\"" IF item == Param(param.name) %]>
- [% item FILTER html %]
+ [% IF param.name == "defaultseverity" %]
+ [% display_value("bug_severity", item) FILTER html %]
+ [% ELSIF param.name == "defaultplatform" %]
+ [% display_value("rep_platform", item) FILTER html %]
+ [% ELSIF param.name == "defaultopsys" %]
+ [% display_value("op_sys", item) FILTER html %]
+ [% ELSIF param.name == "duplicate_or_move_bug_status" %]
+ [% display_value("bug_status", item) FILTER html %]
+ [% ELSE %]
+ [% item FILTER html %]
+ [% END %]
</option>
[% END %]
</select>
@@ -125,11 +130,13 @@
</font>
[% END %]
</p>
- <p>
- <input type="checkbox" name="reset-[% param.name FILTER html %]"
- id="reset-[% param.name FILTER html %]">
- <label for="reset-[% param.name FILTER html %]">Reset</label>
- </p>
+ [% UNLESS param.no_reset %]
+ <p>
+ <input type="checkbox" name="reset-[% param.name FILTER html %]"
+ id="reset-[% param.name FILTER html %]">
+ <label for="reset-[% param.name FILTER html %]">Reset</label>
+ </p>
+ [% END %]
<hr>
</dd>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/core.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/core.html.tmpl
index ae1d995f..b1578f4 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/core.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/core.html.tmpl
@@ -25,32 +25,18 @@
%]
[% param_descs = {
- maintainer => "The email address of the person who maintains this installation " _
- "of ${terms.Bugzilla}.",
-
urlbase => "The URL that is the common initial leading part of all $terms.Bugzilla " _
"URLs.",
- docs_urlbase => "The URL that is the common initial leading part of all " _
- "$terms.Bugzilla documentation URLs. " _
- "It may be an absolute URL, or a URL relative to urlbase " _
- "above. " _
- "Leave this empty to suppress links to the documentation." _
- "'%lang%' will be replaced by user's preferred language " _
- "(if available).",
-
sslbase => "The URL that is the common initial leading part of all HTTPS " _
"(SSL) $terms.Bugzilla URLs.",
- ssl => "Controls when $terms.Bugzilla should enforce sessions to use HTTPS by " _
- "using <tt>sslbase</tt>.",
-
- cookiedomain => "The domain for $terms.Bugzilla cookies. Normally blank. " _
- "If your website is at 'www.foo.com', setting this to " _
- "'.foo.com' will also allow 'bar.foo.com' to access " _
- "$terms.Bugzilla cookies. This is useful if you have more than " _
- "one hostname pointing at the same web server, and you " _
- "want them to share the $terms.Bugzilla cookie.",
+ ssl_redirect =>
+ "When this is enabled, $terms.Bugzilla will ensure that every page is"
+ _ " accessed over SSL, by redirecting any plain HTTP requests to HTTPS"
+ _ " using the <tt>sslbase</tt> parameter. Also, when this is enabled,"
+ _ " $terms.Bugzilla will send out links using <tt>sslbase</tt> in emails"
+ _ " instead of <tt>urlbase</tt>.",
cookiepath => "Path, relative to your web document root, to which to restrict " _
"$terms.Bugzilla cookies. Normally this is the URI portion of your URL " _
@@ -59,56 +45,4 @@
"this parameter to /bugzilla/. Setting it to / will allow " _
"all sites served by this web server or virtual host to read " _
"$terms.Bugzilla cookies.",
-
- timezone => "The timezone that your database server lives in, " _
- "such as UTC, PDT or JST. If set to '', " _
- "then the timezone will not be displayed with the timestamps.",
-
- utf8 => "Use UTF-8 (Unicode) encoding for all text in ${terms.Bugzilla}. New " _
- "installations should set this to true to avoid character encoding " _
- "problems. <strong>Existing databases should set this to true " _
- " only after the data has been converted from existing legacy " _
- " character encodings to UTF-8, using the " _
- " <kbd>contrib/recode.pl</kbd> script</strong>. <br><br> Note " _
- " that if you turn this parameter from "off" to " _
- " "on", you must re-run checksetup.pl immediately " _
- " afterward.",
-
- shutdownhtml => "If this field is non-empty, then $terms.Bugzilla will be completely " _
- "disabled and this text will be displayed instead of all the " _
- "$terms.Bugzilla pages.",
-
- announcehtml => "If this field is non-empty, then $terms.Bugzilla will " _
- "display whatever is in this field at the top of every " _
- "HTML page. The HTML you put in this field is not " _
- "wrapped or enclosed in anything; you might want to " _
- "wrap it inside a <tt><div></tt>. Give the div " _
- "<em>id=message</em> to get green text inside a " _
- "red box, or <em>class=bz_private</em> for dark " _
- "red on a red background. Anything defined in " _
- "<tt>skins/standard/global.css</tt> or " _
- "<tt>skins/custom/global.css</tt> will work. To get " _
- "centered text, use <em>style=\"text-align: " _
- "center;\"</em>.",
-
- proxy_url => "$terms.Bugzilla may have to access the web to get notifications about new " _
- "releases, see the <tt>upgrade_notification</tt> parameter. In case you are " _
- "behind a proxy, it may be necessary to enter its URL if the web server " _
- "group cannot access the HTTP_PROXY environment variable. If you have to " _
- "authenticate, use the <code>http://user:pass@proxy_url/</code> syntax.",
-
- upgrade_notification => "<p>$terms.Bugzilla can inform you when a new release is available. " _
- "The notification will appear on the $terms.Bugzilla homepage, " _
- "for administrators only.</p>" _
- "<ul><li>'development_snapshot' notifies you about the latest " _
- "release on the trunk, i.e. development snapshots.</li>" _
- "<li>'latest_stable_release' notifies you about the most recent release " _
- "available on the most recent stable branch. This branch may be " _
- "different from the branch your installation is based on.</li>" _
- "<li>'stable_branch_release' notifies you only about new releases " _
- "corresponding to the branch your installation is based on. " _
- "If you are running a release candidate, you will get " _
- "a notification for newer release candidates too.</li>" _
- "<li>'disabled' will never notify you about new releases and no " _
- "connection will be established to a remote server.</li></ul>" }
-%]
+} %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/dependencygraph.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/dependencygraph.html.tmpl
index 181cced..4cf22d5 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/dependencygraph.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/dependencygraph.html.tmpl
@@ -44,6 +44,6 @@
The default value is a publicly-accessible webdot server. If you change
this value, make certain that the webdot server can read files from your
webdot directory. On Apache you do this by editing the .htaccess file,
- for other systems the needed measures may vary. You can run checksetup.pl
+ for other systems the needed measures may vary. You can run <kbd>checksetup.pl</kbd>
to recreate the .htaccess file if it has been lost."}
-%]
\ No newline at end of file
+%]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/editparams.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/editparams.html.tmpl
index a35ec0f..fd38d65 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/editparams.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/editparams.html.tmpl
@@ -27,7 +27,7 @@
[% PROCESS global/variables.none.tmpl %]
[% curpanel = -1 %]
-[% panels = panels.sort('sortkey') %]
+[% panels = panels.nsort('sortkey') %]
[% FOREACH panel = panels %]
[% PROCESS "admin/params/${panel.name}.html.tmpl"
@@ -56,7 +56,7 @@
title = title
message = message
style_urls = ['skins/standard/params.css']
- javascript_urls = ['js/params.js']
+ javascript_urls = ['js/params.js', 'js/util.js']
doc_section = "parameters.html"
%]
@@ -73,12 +73,13 @@
[% FOREACH panel = panels %]
<tr>
[% IF panel.current %]
+ [% Hook.process("current_panel") %]
<td class="selected_section">
<span title="[% panel.desc FILTER html %]">[% panel.title FILTER html %]</span>
</td>
[% ELSE %]
<td>
- <a href="editparams.cgi?section=[% panel.name FILTER url_quote %]"
+ <a href="editparams.cgi?section=[% panel.name FILTER uri %]"
title="[% panel.desc FILTER html %]">[% panel.title FILTER html %]</a>
</td>
[% END %]
@@ -111,8 +112,7 @@
<input type="hidden" name="section" value="[% current_panel.name FILTER html %]">
<input type="hidden" name="action" value="save">
<input type="hidden" name="token" value="[% token FILTER html %]">
- <input type="reset" value="Reset form">
- <input type="submit" name="action" value="Save Changes">
+ <input type="submit" id="save-params" value="Save Changes">
</form>
[% END %]
</td>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/general.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/general.html.tmpl
new file mode 100644
index 0000000..c19cf14
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/general.html.tmpl
@@ -0,0 +1,86 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Dave Miller <justdave@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[%
+ title = "General"
+ desc = "Miscellaneous general settings that are not required."
+%]
+
+[% param_descs = {
+ maintainer =>
+ "The email address of the person who maintains this installation "
+ _ " of ${terms.Bugzilla}.",
+
+ docs_urlbase =>
+ "The URL that is the common initial leading part of all"
+ _ " $terms.Bugzilla documentation URLs. It may be an absolute URL,"
+ _ " or a URL relative to the <tt>urlbase</tt> parameter. Leave this"
+ _ " empty to suppress links to the documentation."
+ _ "'%lang%' will be replaced by user's preferred language (if"
+ _ " documentation is available in that language).",
+
+ utf8 =>
+ "Use UTF-8 (Unicode) encoding for all text in ${terms.Bugzilla}. New"
+ _ " installations should set this to true to avoid character encoding"
+ _ " problems. <strong>Existing databases should set this to true"
+ _ " only after the data has been converted from existing legacy"
+ _ " character encodings to UTF-8, using the <kbd>contrib/recode.pl</kbd>"
+ _ " script</strong>."
+ _ " <p>Note that if you turn this parameter from "off" to"
+ _ " "on", you must re-run <kbd>checksetup.pl</kbd> immediately"
+ _ " afterward.</p>",
+
+ shutdownhtml =>
+ "If this field is non-empty, then $terms.Bugzilla will be completely"
+ _ " disabled and this text will be displayed instead of all the"
+ _ " $terms.Bugzilla pages.",
+
+ announcehtml =>
+ "If this field is non-empty, then $terms.Bugzilla will"
+ _ " display whatever is in this field at the top of every"
+ _ " HTML page. The HTML you put in this field is not wrapped or"
+ _ " enclosed in anything. You might want to wrap it inside a"
+ _ "<tt><div></tt>. Give the div <em>id=\"message\"</em> to get"
+ _ " green text inside a red box, or <em>class=\"bz_private\"</em> for"
+ _ " dark red on a red background. Anything defined in "
+ _ " <tt>skins/standard/global.css</tt> or <tt>skins/custom/global.css</tt>"
+ _ " will work. To get centered text, use <em>style=\"text-align: "
+ _ " center;\"</em>.",
+
+ upgrade_notification =>
+ "$terms.Bugzilla can inform you when a new release is available."
+ _ " The notification will appear on the $terms.Bugzilla homepage,"
+ _ " for administrators only."
+ _ " <ul><li>'development_snapshot' notifies you about the development "
+ _ " snapshot that has been released.</li>"
+ _ " <li>'latest_stable_release' notifies you about the most recent"
+ _ " release available on the most recent stable branch. This branch"
+ _ " may be different from the branch your installation is based on.</li>"
+ _ " <li>'stable_branch_release' notifies you only about new releases"
+ _ " corresponding to the branch your installation is based on."
+ _ " If you are running a release candidate, you will get a notification"
+ _ " for newer release candidates too.</li>"
+ _ " <li>'disabled' will never notify you about new releases and no"
+ _ " connection will be established to a remote server.</li></ul>"
+ _ " <p>Note that if your $terms.Bugzilla server requires a proxy to"
+ _ " access the Internet, you may also need to set the <tt>proxy_url</tt>"
+ _ " parameter in the Advanced section.</p>",
+} %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/groupsecurity.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/groupsecurity.html.tmpl
index 2330c44..783099a 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/groupsecurity.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/groupsecurity.html.tmpl
@@ -27,12 +27,6 @@
makeproductgroups => "If this is on, $terms.Bugzilla will associate $terms.abug group " _
"with each product in the database, and use it for querying ${terms.bugs}.",
- useentrygroupdefault => "If this is on, $terms.Bugzilla will use product $terms.bug groups " _
- "by default to restrict who can enter ${terms.bugs}. If this is on, " _
- "users can see any product to which they have entry access in search menus. " _
- "If this is off, users can see any product to which they have not " _
- "been excluded by a mandatory restriction.",
-
chartgroup => "The name of the group of users who can use the 'New Charts' " _
"feature. Administrators should ensure that the public categories " _
"and series definitions do not divulge confidential information " _
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/index.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/index.html.tmpl
index bfa3e2c..de6a56f 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/index.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/index.html.tmpl
@@ -16,6 +16,7 @@
# Rights Reserved.
#
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+ # Nitish Bezzala <nbezzala@yahoo.com>
#%]
<p>
@@ -32,11 +33,13 @@
[% FOREACH panel = panels %]
[% FOREACH param = panel.param_list.sort('name') %]
<tr>
- <td>[% param.name FILTER html %]</td>
<td>
<a href="editparams.cgi?section=
- [%- panel.name FILTER url_quote %]#[% param.name FILTER url_quote %]">
- [% panel.title FILTER html %]</a>
+ [%- panel.name FILTER uri %]#[% param.name FILTER uri %]_desc">
+ [% param.name FILTER html %]</a>
+ </td>
+ <td>
+ [% panel.title FILTER html %]
</td>
</tr>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/mta.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/mta.html.tmpl
index 800fbad..05c4485 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/mta.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/mta.html.tmpl
@@ -45,13 +45,15 @@
mailfrom => "The email address of the $terms.Bugzilla mail daemon. Some email systems " _
"require this to be a valid email address.",
- sendmailnow => "Sites using anything older than version 8.12 of 'sendmail' " _
- "can achieve a significant performance increase in the " _
- "UI -- at the cost of delaying the sending of mail -- by " _
- "disabling this parameter. Sites using 'sendmail' 8.12 or " _
- "higher should leave this on, as they will see no benefit from " _
- "turning it off. Sites using an MTA other than 'sendmail' " _
- "<b>must</b> leave it on, or no $terms.bug mail will be sent.",
+ use_mailer_queue => "In a large $terms.Bugzilla installation, updating"
+ _ " $terms.bugs can be very slow, because $terms.Bugzilla sends all"
+ _ " email at once. If you enable this parameter, $terms.Bugzilla will"
+ _ " queue all mail and then send it in the background. This requires"
+ _ " that you have installed certain Perl modules (as listed by"
+ _ " <kbd>checksetup.pl</kbd> for this feature), and that you are"
+ _ " running the <code>jobqueue.pl</code> daemon (otherwise your mail"
+ _ " won't get sent). This affects all mail sent by $terms.Bugzilla,"
+ _ " not just $terms.bug updates.",
smtpserver => "The SMTP server address (if using SMTP for mail delivery).",
@@ -66,7 +68,7 @@
" $terms.Bugzilla and your SMTP server. You can use this to" _
" troubleshoot email problems.",
- whinedays => "The number of days that we'll let a $terms.bug sit untouched in a NEW " _
+ whinedays => "The number of days that we'll let a $terms.bug sit untouched in a CONFIRMED " _
"state before our cronjob will whine at the owner.<br> " _
"Set to 0 to disable whining.",
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/query.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/query.html.tmpl
index 8d6aba4..255c75a 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/query.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/query.html.tmpl
@@ -32,7 +32,7 @@
</li>
<li>
moderated - quips can be entered, but need to be approved
- by an admin before they will be shown.
+ by a moderator before they will be shown.
</li>
<li>
closed - no new additions to the quips list are allowed.
@@ -51,10 +51,27 @@
"access the advanced query page. It's in URL parameter " _
"format, which makes it hard to read. Sorry!",
- quicksearch_comment_cutoff => "The maximum number of search terms for a QuickSearch " _
- "to search comments. If the QuickSearch query contains " _
- "more terms than this value, QuickSearch will not search comments.",
+ search_allow_no_criteria =>
+ "Unless the code explicitly allows all $terms.bugs to be returned, this " _
+ "parameter permits to block the execution of queries with no criteria. " _
+ "When turned off, a query must have some criteria specified to limit " _
+ "the number of $terms.bugs returned to the user. When turned on, a user " _
+ "is allowed to run a query with no criteria and get all $terms.bugs he can " _
+ "see in his list. Turning this parameter on is not recommended on large " _
+ "installations.",
- specific_search_allow_empty_words => "Whether to allow a search on the 'Find a Specific " _
- "Bug' page with an empty 'Words' field." }
-%]
+ default_search_limit =>
+ "By default, $terms.Bugzilla limits searches done in the web"
+ _ " interface to returning only this many results, for performance"
+ _ " reasons. (This only affects the HTML format of search results--CSV,"
+ _ " XML, and other formats are exempted.) Users can click a link on the"
+ _ " search result page to see all the results."
+ _ "<p>Usually you should not have to change this--the default value"
+ _ " should be acceptable for almost most installations.</p>",
+
+ max_search_results =>
+ "The maximum number of $terms.bugs that a search can"
+ _ " <strong>ever</strong> return. Tabular and graphical reports"
+ _ " are exempted from this limit, however.",
+
+} %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/radius.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/radius.html.tmpl
index ef2282d..f12e581 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/radius.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/radius.html.tmpl
@@ -26,13 +26,13 @@
"(e.g. <code>radius.company.com</code>, or " _
"<code>radius.company.com:portnum</code>).<br>" _
"Required only if " _
- "<a href=\"?section=auth#user_verify_class\">the " _
+ "<a href=\"?section=auth#user_verify_class_desc\">the " _
"<code>user_verify_class</code> parameter</a> contains " _
"<code>RADIUS</code>.",
RADIUS_secret => "Your RADIUS server's secret.<br>" _
"Required only if " _
- "<a href=\"?section=auth#user_verify_class\">the " _
+ "<a href=\"?section=auth#user_verify_class_desc\">the " _
"<code>user_verify_class</code> parameter</a> contains " _
"<code>RADIUS</code>.",
@@ -40,14 +40,14 @@
"data with your RADIUS server. " _
"If unspecified, <code>127.0.0.1</code> will be used.<br>" _
"Useful only if " _
- "<a href=\"?section=auth#user_verify_class\">the " _
+ "<a href=\"?section=auth#user_verify_class_desc\">the " _
"<code>user_verify_class</code> parameter</a> " _
"contains <code>RADIUS</code>.",
RADIUS_email_suffix => "Suffix to append to a RADIUS user name to form an " _
"e-mail address.<br>" _
"Useful only if " _
- "<a href=\"?section=auth#user_verify_class\">the " _
+ "<a href=\"?section=auth#user_verify_class_desc\">the " _
"<code>user_verify_class</code> parameter</a> " _
"contains <code>RADIUS</code>.",
}
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/params/usermatch.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/params/usermatch.html.tmpl
index 178a728..d574edc 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/params/usermatch.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/params/usermatch.html.tmpl
@@ -29,11 +29,8 @@
"needs to be selected. This option should not be enabled on " _
"sites where there are a large number of users.",
- usermatchmode => "Allow match strings to be entered for user names when entering " _
- "and editing ${terms.bugs}.<p> " _
- "'off' disables matching,<br> " _
- "'wildcard' allows only wildcards,<br> " _
- "and 'search' allows both wildcards and substring (freetext) matches.",
+ ajax_user_autocompletion => "If this option is set, typing characters in a certain user " _
+ "fields will display a list of matches that can be selected from.",
maxusermatches => "Search for no more than this many matches.<br> " _
"If set to '1', no users will be displayed on ambiguous matches. " _
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/products/confirm-delete.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/products/confirm-delete.html.tmpl
index e5e982b..aa728df 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/products/confirm-delete.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/products/confirm-delete.html.tmpl
@@ -31,14 +31,6 @@
style_urls = ['skins/standard/admin.css']
%]
-[% IF classification %]
- [% classification_url_part = BLOCK %]&classification=
- [%- classification.name FILTER url_quote %]
- [%- END %]
-[% ELSE %]
- [% classification_url_part = "" %]
-[% END %]
-
<table border="1" cellpadding="4" cellspacing="0">
<tr bgcolor="#6666FF">
<th valign="top" align="left">Field</th>
@@ -66,8 +58,7 @@
<tr>
<td valign="top">Product:</td>
<td valign="top">
- <a href="editproducts.cgi?product=[% product.name FILTER url_quote %]
- [%- classification_url_part %]">
+ <a href="editproducts.cgi?product=[% product.name FILTER uri %]">
[% product.name FILTER html %]
</a>
</td>
@@ -102,10 +93,10 @@
<tr>
<td>Closed for [% terms.bugs %]:</td>
<td>
- [% IF product.disallownew %]
- closed
- [% ELSE %]
+ [% IF product.is_active %]
open
+ [% ELSE %]
+ closed
[% END %]
</td>
</tr>
@@ -113,8 +104,7 @@
<tr>
<td>
[% IF product.components.size > 0 %]
- <a href="editcomponents.cgi?product=[% product.name FILTER url_quote %]
- [%- classification_url_part %]"
+ <a href="editcomponents.cgi?product=[% product.name FILTER uri %]"
title="Edit components for product '[% product.name FILTER html %]'">
Components:
</a>
@@ -148,8 +138,7 @@
<tr>
<td>
[% IF product.versions.size > 0 %]
- <a href="editversions.cgi?product=[%- product.name FILTER url_quote %]
- [%- classification_url_part %]">
+ <a href="editversions.cgi?product=[%- product.name FILTER uri %]">
Versions:
</a>
[% ELSE %]
@@ -172,8 +161,7 @@
<tr>
<td valign="top">
[% IF product.milestones.size > 0 %]
- <a href="editmilestones.cgi?product=[%- product.name FILTER url_quote %]
- [%- classification_url_part -%]">
+ <a href="editmilestones.cgi?product=[%- product.name FILTER uri %]">
Milestones:
</a>
[% ELSE %]
@@ -196,10 +184,8 @@
<td>[% terms.Bugs %]:</td>
<td>
[% IF product.bug_count %]
- <a href="buglist.cgi?product=[%- product.name FILTER url_quote %]
- [%- classification_url_part %]"
- title="List of [% terms.bugs %] for product '
- [%- product.name FILTER html %]'">
+ <a href="buglist.cgi?product=[% product.name FILTER uri %]"
+ title="List of [% terms.bugs %] for product '[% product.name FILTER html %]'">
[% product.bug_count FILTER html %]
</a>
[% ELSE %]
@@ -262,12 +248,15 @@
<p>Do you really want to delete this product?</p>
<form method="post" action="editproducts.cgi">
+ <input type="checkbox" id="delete_series" name="delete_series" value=1>
+ <label for="delete_series">
+ Delete all related series (you can also delete them later, by visiting
+ the <a href="chart.cgi?category=[% product.name FILTER html %]">New Charts page</a>.)
+ </label><p>
<input type="submit" id="delete" value="Yes, delete">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="product" value="[% product.name FILTER html %]">
<input type="hidden" name="token" value="[% token FILTER html %]">
- <input type="hidden" name="classification"
- value="[% classification.name FILTER html %]">
</form>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/products/create.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/products/create.html.tmpl
index e1cd381..3af81fb 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/products/create.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/products/create.html.tmpl
@@ -25,14 +25,14 @@
[% PROCESS global/header.html.tmpl
title = title
style_urls = ['skins/standard/admin.css']
+ javascript_urls = ['js/util.js']
%]
[% DEFAULT
- product.votesperuser = "0",
- product.maxvotesperbug = "10000",
- product.votestoconfirm = "0",
+ product.is_active = 1,
version = "unspecified",
- product.defaultmilestone = "---"
+ product.defaultmilestone = constants.DEFAULT_MILESTONE
+ product.allows_unconfirmed = 1
%]
<form method="post" action="editproducts.cgi">
@@ -48,13 +48,13 @@
</tr>
<tr>
<th align="right">Create chart datasets for this product:</th>
- <td><input type="checkbox" name="createseries" value="1"></td>
+ <td>
+ <input type="checkbox" name="createseries" value="1" checked="checked">
+ </td>
</tr>
</table>
- <input type="submit" value="Add">
- <input type="hidden" name="subcategory" value="-All-">
- <input type="hidden" name="open_name" value="All Open">
+ <input type="submit" id="add-product" value="Add">
<input type="hidden" name="action" value="new">
<input type="hidden" name="token" value="[% token FILTER html %]">
<input type="hidden" name="classification"
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/products/edit-common.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/products/edit-common.html.tmpl
index c05a878..4812707 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/products/edit-common.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/products/edit-common.html.tmpl
@@ -45,12 +45,6 @@
[% IF Param('usetargetmilestone') -%]
<tr>
- <th align="right">URL describing milestones for this product:</th>
- <td><input type="text" size="64" maxlength="255" name="milestoneurl"
- value="[% product.milestoneurl FILTER html %]">
- </td>
- </tr>
- <tr>
<th align="right">Default milestone:</th>
<td>
[% IF product.milestones.size %]
@@ -70,45 +64,20 @@
[% END %]
<tr>
- <th align="right">Closed for [% terms.bug %] entry:</th>
- <td><input type="checkbox" name="disallownew" value="1"
- [% IF product.disallownew == "1" %]
- checked="checked"[% END %]>
+ <th align="right">Open for [% terms.bug %] entry:</th>
+ <td><input type="checkbox" name="is_active" value="1"
+ [% ' checked="checked"' IF product.is_active %]>
+ </td>
+</tr>
+<tr>
+ <th align="right">
+ <label for="allows_unconfirmed">Enable the
+ [%+ display_value('bug_status', 'UNCONFIRMED') FILTER html %] status
+ in this product:</label>
+ </th>
+ <td><input type="checkbox" id="allows_unconfirmed" name="allows_unconfirmed"
+ [% ' checked="checked"' IF product.allows_unconfirmed %]>
</td>
</tr>
-[% IF !Param('usevotes') %]
-<tr class="param_disabled">
- <td colspan="2"
- style="font-family: arial; font-style: italic; font-size: 0.7em; text-align: center;">
- The 'usevotes' parameter is currently 'off'. These voting
- settings will take effect when the parameter is next enabled.</td>
-</tr>
-[% END %]
-<tr [% IF !Param('usevotes') %]class="param_disabled" [% END %]>
- <th align="right">Maximum votes per person:</th>
- <td><input size="5" maxlength="5" name="votesperuser"
- value="[% product.votesperuser FILTER html %]">
- </td>
-</tr>
-<tr [% IF !Param('usevotes') %]class="param_disabled" [% END %]>
- <th align="right">
- Maximum votes a person can put on a single [% terms.bug %]:
- </th>
- <td><input size="5" maxlength="5" name="maxvotesperbug"
- value="[% product.maxvotesperbug FILTER html %]">
- </td>
-</tr>
-<tr [% IF !Param('usevotes') %]class="param_disabled" [% END %]>
- <th align="right">
- Confirmation threshold:
- </th>
- <td>
- Enter the number of votes [% terms.abug %] in this product needs to
- automatically get out of the
- <a href="page.cgi?id=fields.html#status">[% get_status("UNCONFIRMED") FILTER html %]</a>
- state.<br>
- <input size="5" maxlength="5" name="votestoconfirm"
- value="[% product.votestoconfirm FILTER html %]">
- </td>
-</tr>
+[% Hook.process('rows') %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/products/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/products/edit.html.tmpl
index 2d346c6..3bd78a9 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/products/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/products/edit.html.tmpl
@@ -29,6 +29,7 @@
[% PROCESS global/header.html.tmpl
title = title
style_urls = ['skins/standard/admin.css']
+ javascript_urls = ['js/util.js']
%]
[% group_control = {${constants.CONTROLMAPNA} => 'NA',
@@ -44,7 +45,7 @@
<tr>
<th align="right" valign="top">
- <a href="editcomponents.cgi?product=[% product.name FILTER url_quote %]">
+ <a href="editcomponents.cgi?product=[% product.name FILTER uri %]">
Edit components:
</a>
</th>
@@ -66,7 +67,7 @@
</tr>
<tr>
<th align="right" valign="top">
- <a href="editversions.cgi?product=[% product.name FILTER url_quote %]">Edit
+ <a href="editversions.cgi?product=[% product.name FILTER uri %]">Edit
versions:</a>
</th>
<td>
@@ -83,7 +84,7 @@
[% IF Param('usetargetmilestone') %]
<tr>
<th align="right" valign="top">
- <a href="editmilestones.cgi?product=[% product.name FILTER url_quote %]">
+ <a href="editmilestones.cgi?product=[% product.name FILTER uri %]">
Edit milestones:</a>
</th>
<td>
@@ -101,14 +102,13 @@
<tr>
<th align="right" valign="top">
<a href="editproducts.cgi?action=editgroupcontrols&product=
- [%- product.name FILTER url_quote %]&classification=
- [%- classification.name FILTER url_quote %]">
+ [%- product.name FILTER uri %]">
Edit Group Access Controls:
</a>
</th>
<td>
[% IF product.group_controls.size %]
- [% FOREACH g = product.group_controls.values %]
+ [% FOREACH g = product.group_controls.values.sort("name") %]
<b>[% g.group.name FILTER html %]:</b>
[% IF g.group.isactive %]
[% group_control.${g.membercontrol} FILTER html %]/
@@ -130,7 +130,7 @@
</tr>
<tr>
<th align="right">[% terms.Bugs %]:</th>
- <td><a href="buglist.cgi?product=[% product.name FILTER url_quote %]">
+ <td><a href="buglist.cgi?product=[% product.name FILTER uri %]">
[% product.bug_count FILTER html %]</a></td>
</tr>
</table>
@@ -139,9 +139,7 @@
value="[% product.name FILTER html %]">
<input type="hidden" name="action" value="update">
<input type="hidden" name="token" value="[% token FILTER html %]">
- <input type="hidden" name="classification"
- value="[% classification.name FILTER html %]">
- <input type="submit" name="submit" value="Save Changes">
+ <input type="submit" id="update-product" value="Save Changes">
</form>
[% PROCESS "admin/products/footer.html.tmpl"
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/products/footer.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/products/footer.html.tmpl
index 4b8fe05..78e1864 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/products/footer.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/products/footer.html.tmpl
@@ -30,10 +30,10 @@
[% IF Param('useclassification') && classification %]
[% classification_url_part = BLOCK %]&classification=
- [%- classification.name FILTER url_quote %]
+ [%- classification.name FILTER uri %]
[% END %]
[% classification_url_part_start = BLOCK %]classification=
- [%- classification.name FILTER url_quote %]
+ [%- classification.name FILTER uri %]
[% END %]
[% classification_text = BLOCK %]
of classification '[% classification.name FILTER html %]'
@@ -61,9 +61,7 @@
Edit product <a
title="Edit Product '[% product.name FILTER html %]'
[%- classification_text %]"
- href="editproducts.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]
- [%- classification_url_part %]">
+ href="editproducts.cgi?action=edit&product=[% product.name FILTER uri %]">
'[% product.name FILTER html %]'</a>.
[% END %]
@@ -77,7 +75,8 @@
[% END %]
-[% IF Param('useclassification') && classification %]
+[% IF Param('useclassification') && classification
+ && user.in_group('editclassifications') %]
Edit classification <a href="editclassifications.cgi?action=edit
[%- classification_url_part %]">'
[%- classification.name FILTER html %]'</a>.
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/products/groupcontrol/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/products/groupcontrol/edit.html.tmpl
index c793ff6..8c634eb 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/products/groupcontrol/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/products/groupcontrol/edit.html.tmpl
@@ -31,8 +31,6 @@
<input type="hidden" name="action" value="updategroupcontrols">
<input type="hidden" name="product" value="[% product.name FILTER html %]">
<input type="hidden" name="token" value="[% token FILTER html %]">
- <input type="hidden" name="classification"
- value="[% classification.name FILTER html %]">
<table id="form" cellspacing="0" cellpadding="4" border="1">
<tr bgcolor="#6666ff">
@@ -46,23 +44,23 @@
<th>editbugs</th>
<th>[% terms.Bugs %]</th>
</tr>
- [% FOREACH group = groups %]
- [% IF group.isactive == 0 AND group.bugcount > 0 %]
+ [% FOREACH group = product.group_controls(1).values.sort("name") %]
+ [% IF !group.group.isactive AND group.bug_count %]
<tr bgcolor="#bbbbbb">
<td>
- [% group.name FILTER html %]
+ [% group.group.name FILTER html %]
</td>
<td align="center" colspan=7>
Disabled
</td>
<td>
- [% group.bugcount %]
+ [% group.bug_count FILTER html %]
</td>
<tr>
- [% ELSIF group.isactive != 0 %]
+ [% ELSIF group.group.is_active %]
<tr>
<td>
- [% group.name FILTER html %]
+ [% group.group.name FILTER html %]
</td>
<td>
<input type=checkbox value=1 name=entry_[% group.id %]
@@ -70,48 +68,48 @@
</td>
<td>
<select name="membercontrol_[% group.id %]">
- <option value=[% const.CONTROLMAPNA %]
+ <option value=[% constants.CONTROLMAPNA %]
[% " selected=\"selected\""
- IF group.membercontrol == const.CONTROLMAPNA %]
+ IF group.membercontrol == constants.CONTROLMAPNA %]
>NA
</option>
- <option value=[% const.CONTROLMAPSHOWN %]
+ <option value=[% constants.CONTROLMAPSHOWN %]
[% " selected=\"selected\""
- IF group.membercontrol == const.CONTROLMAPSHOWN %]
+ IF group.membercontrol == constants.CONTROLMAPSHOWN %]
>Shown
</option>
- <option value=[% const.CONTROLMAPDEFAULT %]
+ <option value=[% constants.CONTROLMAPDEFAULT %]
[% " selected=\"selected\""
- IF group.membercontrol == const.CONTROLMAPDEFAULT %]
+ IF group.membercontrol == constants.CONTROLMAPDEFAULT %]
>Default
</option>
- <option value=[% const.CONTROLMAPMANDATORY %]
+ <option value=[% constants.CONTROLMAPMANDATORY %]
[% " selected=\"selected\""
- IF group.membercontrol == const.CONTROLMAPMANDATORY %]
+ IF group.membercontrol == constants.CONTROLMAPMANDATORY %]
>Mandatory
</option>
</select>
</td>
<td>
<select name="othercontrol_[% group.id %]">
- <option value=[% const.CONTROLMAPNA %]
+ <option value=[% constants.CONTROLMAPNA %]
[% " selected=\"selected\""
- IF group.othercontrol == const.CONTROLMAPNA %]
+ IF group.othercontrol == constants.CONTROLMAPNA %]
>NA
</option>
- <option value=[% const.CONTROLMAPSHOWN %]
+ <option value=[% constants.CONTROLMAPSHOWN %]
[% " selected=\"selected\""
- IF group.othercontrol == const.CONTROLMAPSHOWN %]
+ IF group.othercontrol == constants.CONTROLMAPSHOWN %]
>Shown
</option>
- <option value=[% const.CONTROLMAPDEFAULT %]
+ <option value=[% constants.CONTROLMAPDEFAULT %]
[% " selected=\"selected\""
- IF group.othercontrol == const.CONTROLMAPDEFAULT %]
+ IF group.othercontrol == constants.CONTROLMAPDEFAULT %]
>Default
</option>
- <option value=[% const.CONTROLMAPMANDATORY %]
+ <option value=[% constants.CONTROLMAPMANDATORY %]
[% " selected=\"selected\""
- IF group.othercontrol == const.CONTROLMAPMANDATORY %]
+ IF group.othercontrol == constants.CONTROLMAPMANDATORY %]
>Mandatory
</option>
</select>
@@ -133,7 +131,7 @@
[% " checked=\"checked\"" IF group.editbugs %]>
</td>
<td>
- [% group.bugcount %]
+ [% group.bug_count || 0 FILTER html %]
</td>
</tr>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/products/groupcontrol/updated.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/products/groupcontrol/updated.html.tmpl
index 52456a47..353ce5c 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/products/groupcontrol/updated.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/products/groupcontrol/updated.html.tmpl
@@ -15,10 +15,8 @@
#%]
[%# INTERFACE:
- # removed_na: array of hashes; groups not applicable for the product.
- # added_mandatory: array of hashes; groups mandatory for the product.
- # classification: Bugzilla::Classification object; product classification.
- # product: Bugzilla::Product object; the product.
+ # product: Bugzilla::Product object; the product.
+ # changes: Hashref with changes made to the product group controls.
#%]
[% title = BLOCK %]
@@ -29,16 +27,16 @@
title = title
%]
<p>
-[% IF removed_na.size > 0 %]
- [% FOREACH g = removed_na %]
+[% IF changes._group_controls.now_na.size %]
+ [% FOREACH g = changes._group_controls.now_na %]
Removing [% terms.bugs %] from group '[% g.name FILTER html %]' which
no longer applies to this product<p>
[% g.bug_count FILTER html %] [%+ terms.bugs %] removed<p>
[% END %]
[% END %]
-[% IF added_mandatory.size > 0 %]
- [% FOREACH g = added_mandatory %]
+[% IF changes._group_controls.now_mandatory.size %]
+ [% FOREACH g = changes._group_controls.now_mandatory %]
Adding [% terms.bugs %] to group '[% g.name FILTER html %]' which is
mandatory for this product<p>
[% g.bug_count FILTER html %] [%+ terms.bugs %] added<p>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/products/list-classifications.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/products/list-classifications.html.tmpl
index 4eddad3..161cc4b 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/products/list-classifications.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/products/list-classifications.html.tmpl
@@ -27,18 +27,11 @@
title = "Select Classification"
%]
-[% edit_contentlink = BLOCK %]
- editproducts.cgi?classification=%%name%%
-[% END %]
-[% add_contentlink = BLOCK %]
- editproducts.cgi?action=add&classification=%%name%%
-[% END %]
-
[% columns = [
{
name => "name"
heading => "Edit products of..."
- contentlink => edit_contentlink
+ contentlink => 'editproducts.cgi?classification=%%name%%'
},
{
name => "description"
@@ -57,10 +50,12 @@
[% columns.push({
heading => "Action..."
content => "Add product"
- contentlink => add_contentlink })
+ contentlink => 'editproducts.cgi?action=add&classification=%%name%%' })
%]
[% END %]
+[% Hook.process('before_table') %]
+
[% PROCESS admin/table.html.tmpl
columns = columns
data = classifications
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/products/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/products/list.html.tmpl
index 3f15769..0641c62 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/products/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/products/list.html.tmpl
@@ -26,7 +26,7 @@
[% IF classification %]
[% classification_url_part = BLOCK %]&classification=
- [%- classification.name FILTER url_quote %]
+ [%- classification.name FILTER uri %]
[%- END %]
[% classification_title = BLOCK %]
in classification '[% classification.name FILTER html %]'
@@ -37,23 +37,11 @@
title = "Select product $classification_title"
%]
-[% edit_contentlink = BLOCK %]
- editproducts.cgi?action=edit&product=%%name%%
- [%- classification_url_part %]
-[% END %]
-[% delete_contentlink = BLOCK %]
- editproducts.cgi?action=del&product=%%name%%
- [%- classification_url_part %]
-[% END %]
-[% bug_count_contentlink = BLOCK %]buglist.cgi?product=%%name%%
- [%- classification_url_part %][% END %]
-
-
[% columns = [
{
name => "name"
heading => "Edit product..."
- contentlink => edit_contentlink
+ contentlink => 'editproducts.cgi?action=edit&product=%%name%%'
},
{
name => "description"
@@ -61,25 +49,11 @@
allow_html_content => 1
},
{
- name => "disallow_new"
+ name => "is_active"
heading => "Open For New $terms.Bugs"
+ yesno_field => 1
},
- {
- name => "votesperuser"
- heading => "Votes Per User"
- align => 'right'
- },
- {
- name => "maxvotesperbug"
- heading => "Maximum Votes Per $terms.Bug"
- align => 'right'
- },
- {
- name => "votestoconfirm"
- heading => "Votes To Confirm"
- align => 'right'
- } ]
-%]
+] %]
[% IF showbugcounts %]
@@ -87,7 +61,7 @@
name => "bug_count"
heading => "$terms.Bug Count"
align => 'right'
- contentlink => bug_count_contentlink
+ contentlink => 'buglist.cgi?product=%%name%%'
})
%]
@@ -96,23 +70,11 @@
[% columns.push({
heading => "Action"
content => "Delete"
- contentlink => delete_contentlink
+ contentlink => 'editproducts.cgi?action=del&product=%%name%%'
})
%]
-[% overrides.disallow_new = [ {
- match_value => "1"
- match_field => 'disallow_new'
- override_content => 1
- content => "No"
- },
- {
- match_value => 0
- match_field => 'disallow_new'
- override_content => 1
- content => "Yes"
- }]
-%]
+[% Hook.process('before_table') %]
[% PROCESS admin/table.html.tmpl
columns = columns
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/products/updated.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/products/updated.html.tmpl
index 4d5f518..d93022a 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/products/updated.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/products/updated.html.tmpl
@@ -16,38 +16,18 @@
# Rights Reserved.
#
# Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
#%]
[%# INTERFACE:
- #
- # old_product : Bugzilla::Product Object; old product.
# product : Bugzilla::Product Object; new product.
- #
# classification: Bugzilla::Classification Object; The product classification (may be empty or missing)
- #
- # checkvotes: boolean; is true if vote related fields have changed. If so,
- # then the following parameters will be specified:
- #
- # toomanyvotes: list of hashes, each one with an 'id' and a 'name' hash key
- # detailing the bug id and the username of users who had too
- # many votes for a bug
- #
- # toomanytotalvotes: list of hashes, each one with an 'id' and a 'name' hash key
- # detailing the bug id and the username of users who had
- # too many total votes
- #
- # confirmedbugs: list of bug ids, which were confirmed by votes
- #
- # changer: string; login of the user making the changes, used for mailing
- # bug changes if necessary
- #
+ # changes: hashref with all changes made to the product. Each key is an edited field,
+ # and its value is an arrayref of the form [old values, new values].
#%]
[% IF classification %]
- [% classification_url_part = BLOCK %]&classification=
- [%- classification.name FILTER url_quote %]
- [% END %]
- [% classification_text = BLOCK %]
+ [% classification_text = BLOCK %]
of classification '[% classification.name FILTER html %]'
[% END %]
[% END %]
@@ -58,152 +38,68 @@
title = title
style_urls = ['skins/standard/admin.css']
%]
-[% updated = 0 %]
-[% IF product.name != old_product.name %]
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% IF changes.name.defined %]
<p>
- Updated product name from '[% old_product.name FILTER html %]' to
- <a href="editproducts.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]
- [%- classification_url_part FILTER none %]">[% product.name FILTER html %]</a>.
+ Updated product name from '[% changes.name.0 FILTER html %]' to
+ '<a href="editproducts.cgi?action=edit&product=
+ [%- product.name FILTER uri %]">[% product.name FILTER html %]</a>'.
</p>
- [% updated = 1 %]
[% END %]
-[% IF product.description != old_product.description %]
+[% IF changes.description.defined %]
<p>
Updated description to:
</p>
<p style="margin: 1em 3em 1em 3em">[% product.description FILTER html_light %]</p>
- [% updated = 1 %]
[% END %]
-[% IF product.disallow_new != old_product.disallow_new %]
+[% IF changes.isactive.defined %]
<p>
Product is now
- [% IF product.disallow_new %]
- closed to
+ [% IF product.is_active %]
+ open for
[% ELSE %]
- open for
+ closed to
[% END %]
new [% terms.bugs %].
</p>
- [% updated = 1 %]
[% END %]
-[% IF product.milestone_url != old_product.milestone_url %]
+[% IF changes.defaultmilestone.defined %]
<p>
- Updated milestone URL
- [% IF old_product.milestone_url != '' %]
- from<br> <a href="[%- old_product.milestone_url FILTER html %]">
- [%- old_product.milestone_url FILTER html %]</a>
- [% END %]
- to
- [% IF product.milestone_url != '' %]
- <br><a href="[%- product.milestone_url FILTER html %]">
- [%- product.milestone_url FILTER html %]</a>.
- [% ELSE %]
- be empty.
- [% END %]
- </p>
- [% updated = 1 %]
-[% END %]
-
-[% IF product.default_milestone != old_product.default_milestone %]
- <p>
- Updated default milestone from '[% old_product.default_milestone FILTER html %]' to
+ Updated default milestone from '[% changes.defaultmilestone.0 FILTER html %]' to
'[% product.default_milestone FILTER html %]'.
</p>
- [% updated = 1 %]
-[% END %]
-
-[% IF product.votes_per_user != old_product.votes_per_user %]
- <p>
- Updated votes per user from
- [%+ old_product.votes_per_user FILTER html %] to
- [%+ product.votes_per_user FILTER html %].
- </p>
- [% updated = 1 %]
[% END %]
-[% IF product.max_votes_per_bug != old_product.max_votes_per_bug %]
+[% IF changes.allows_unconfirmed.defined %]
<p>
- Updated maximum votes per [% terms.bug %] from
- [%+ old_product.max_votes_per_bug FILTER html %] to
- [%+ product.max_votes_per_bug FILTER html %].
+ [% IF product.allows_unconfirmed %]
+ The product now allows the
+ [%+ display_value('bug_status', 'UNCONFIRMED') FILTER html %] status.
+ [% ELSE %]
+ The product no longer allows the
+ [%+ display_value('bug_status', 'UNCONFIRMED') FILTER html %] status.
+ Note that any
+ <a href="buglist.cgi?product=
+ [%- product.name FILTER uri %]&bug_status=UNCONFIRMED">
+ [%- terms.bugs %] that currently have the
+ [%+ display_value('bug_status', 'UNCONFIRMED') FILTER html %] status</a>
+ will remain in that status until they are edited.
+ [% END %]
</p>
- [% updated = 1 %]
[% END %]
-[% IF product.votes_to_confirm != old_product.votes_to_confirm %]
- <p>
- Updated number of votes needed to confirm a [% terms.bug %] from
- [%+ old_product.votes_to_confirm FILTER html %] to
- [%+ product.votes_to_confirm FILTER html %].
- </p>
- [% updated = 1 %]
-[% END %]
+[% Hook.process('changes') %]
-[% UNLESS updated %]
+[% IF !changes.keys.size %]
<p>Nothing changed for product '[% product.name FILTER html %]'.</p>
[% END %]
-[%# Note that this display of changed votes and/or confirmed bugs is
- not very scalable. We could have a _lot_, and we just list them all.
- One day we should limit this perhaps, or have a more scalable display %]
-
-
-[% IF checkvotes %]
- <hr>
-
- <p>Checking existing votes in this product for anybody who now
- has too many votes for [% terms.abug %]...<br>
- [% IF toomanyvotes.size > 0 %]
- [% FOREACH detail = toomanyvotes %]
- →removed votes for [% terms.bug %] <a href="show_bug.cgi?id=
- [%- detail.id FILTER url_quote %]">
- [%- detail.id FILTER html %]</a> from [% detail.name FILTER html %]<br>
- [% END %]
- [% ELSE %]
- →there were none.
- [% END %]
- </p>
-
- <p>Checking existing votes in this product for anybody
- who now has too many total votes...<br>
- [% IF toomanytotalvotes.size > 0 %]
- [% FOREACH detail = toomanytotalvotes %]
- →removed votes for [% terms.bug %] <a href="show_bug.cgi?id=
- [%- detail.id FILTER url_quote %]">
- [%- detail.id FILTER html %]</a> from [% detail.name FILTER html %]<br>
- [% END %]
- [% ELSE %]
- →there were none.
- [% END %]
- </p>
-
- <p>Checking unconfirmed [% terms.bugs %] in this product for any which now have
- sufficient votes...<br>
- [% IF confirmedbugs.size > 0 %]
- [% FOREACH id = confirmedbugs %]
-
- [%# This is INCLUDED instead of PROCESSED to avoid variables getting
- overwritten, which happens otherwise %]
- [% INCLUDE bug/process/results.html.tmpl
- type = 'votes'
- mailrecipients = { 'changer' => changer }
- header_done = 1
- id = id
- %]
- [% END %]
- [% ELSE %]
- →there were none.
- [% END %]
- </p>
-
-[% END %]
-
[% PROCESS admin/products/footer.html.tmpl %]
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/sanitycheck/messages.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/sanitycheck/messages.html.tmpl
index 2847a35..88264d8 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/sanitycheck/messages.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/sanitycheck/messages.html.tmpl
@@ -34,7 +34,8 @@
[% errortext FILTER html %]: [% INCLUDE bug_list badbugs = badbugs %]
[% ELSIF san_tag == "bug_check_repair" %]
- <a href="sanitycheck.cgi?[% param FILTER url_quote %]=1">[% text FILTER html %]</a>.
+ <a href="sanitycheck.cgi?[% param FILTER uri %]=1&token=
+ [%- issue_hash_token(['sanitycheck']) FILTER uri %]">[% text FILTER html %]</a>.
[% ELSIF san_tag == "bug_check_creation_date" %]
Checking for [% terms.bugs %] with no creation date (which makes them invisible).
@@ -81,12 +82,6 @@
[% ELSIF san_tag == "bug_check_status_everconfirmed_error_text2" %]
[% terms.Bugs %] with confirmed status but don't have everconfirmed set
- [% ELSIF san_tag == "bug_check_votes_everconfirmed" %]
- Checking votes/everconfirmed
-
- [% ELSIF san_tag == "bug_check_votes_everconfirmed_error_text" %]
- [% terms.Bugs %] that have enough votes to be confirmed but haven't been
-
[% ELSIF san_tag == "bug_check_control_values" %]
Checking for bad values in group_control_map
@@ -142,11 +137,13 @@
[% END %]
[% ELSIF san_tag == "cross_check_attachment_has_references" %]
- <a href="sanitycheck.cgi?remove_invalid_attach_references=1">Remove
+ <a href="sanitycheck.cgi?remove_invalid_attach_references=1&token=
+ [%- issue_hash_token(['sanitycheck']) FILTER uri %]">Remove
invalid references to non existent attachments.</a>
[% ELSIF san_tag == "cross_check_bug_has_references" %]
- <a href="sanitycheck.cgi?remove_invalid_bug_references=1">Remove
+ <a href="sanitycheck.cgi?remove_invalid_bug_references=1&token=
+ [%- issue_hash_token(['sanitycheck']) FILTER uri %]">Remove
invalid references to non existent [% terms.bugs %].</a>
[% ELSIF san_tag == "double_cross_check_to" %]
@@ -169,6 +166,12 @@
[% END %]
[% END %]
+ [% ELSIF san_tag == "everconfirmed_start" %]
+ OK, now fixing everconfirmed.
+
+ [% ELSIF san_tag == "everconfirmed_end" %]
+ everconfirmed fixed.
+
[% ELSIF san_tag == "flag_check_start" %]
Checking for flags being in the wrong product/component.
@@ -186,7 +189,8 @@
[%+ PROCESS bug_link bug_id = bug_id %].
[% ELSIF san_tag == "flag_fix" %]
- <a href="sanitycheck.cgi?remove_invalid_flags=1">Click
+ <a href="sanitycheck.cgi?remove_invalid_flags=1&token=
+ [%- issue_hash_token(['sanitycheck']) FILTER uri %]">Click
here to delete invalid flags</a>
[% ELSIF san_tag == "group_control_map_entries_creation" %]
@@ -222,23 +226,6 @@
[% ELSIF san_tag == "keyword_check_duplicated_ids" %]
Duplicate keyword IDs found in [% PROCESS bug_link bug_id = id %].
- [% ELSIF san_tag == "keyword_cache_start" %]
- Checking cached keywords.
-
- [% ELSIF san_tag == "keyword_cache_alert" %]
- [% badbugs.size FILTER none %] [%+ terms.bugs %] found with
- incorrect keyword cache: [% INCLUDE bug_list badbugs = badbugs %]
-
- [% ELSIF san_tag == "keyword_cache_fixing" %]
- OK, now fixing keyword cache.
-
- [% ELSIF san_tag == "keyword_cache_fixed" %]
- Keyword cache fixed.
-
- [% ELSIF san_tag == "keyword_cache_rebuild" %]
- <a href="sanitycheck.cgi?rebuildkeywordcache=1">Click here to
- rebuild the keyword cache</a>.
-
[% ELSIF san_tag == "profile_login_start" %]
Checking profile logins.
@@ -267,26 +254,8 @@
half an hour: [% INCLUDE bug_list badbugs = badbugs %]
[% ELSIF san_tag == "unsent_bugmail_fix" %]
- <a href="sanitycheck.cgi?rescanallBugMail=1">Send these mails</a>.
-
- [% ELSIF san_tag == "vote_cache_rebuild_start" %]
- OK, now rebuilding vote cache.
-
- [% ELSIF san_tag == "vote_cache_rebuild_end" %]
- Vote cache has been rebuilt.
-
- [% ELSIF san_tag == "vote_cache_rebuild_fix" %]
- <a href="sanitycheck.cgi?rebuildvotecache=1">Click here to
- rebuild the vote cache</a>
-
- [% ELSIF san_tag == "vote_cache_alert" %]
- Bad vote cache for [% PROCESS bug_link bug_id = id %]
-
- [% ELSIF san_tag == "vote_count_start" %]
- Checking cached vote counts.
-
- [% ELSIF san_tag == "vote_count_alert" %]
- Bad vote sum for [% terms.bug %] [%+ id FILTER html %].
+ <a href="sanitycheck.cgi?rescanallBugMail=1&token=
+ [%- issue_hash_token(['sanitycheck']) FILTER uri %]">Send these mails</a>.
[% ELSIF san_tag == "whines_obsolete_target_deletion_start" %]
OK, now removing non-existent users/groups from whines.
@@ -304,14 +273,31 @@
[% END %]
[% ELSIF san_tag == "whines_obsolete_target_fix" %]
- <a href="sanitycheck.cgi?remove_old_whine_targets=1">Click here to
+ <a href="sanitycheck.cgi?remove_old_whine_targets=1&token=
+ [%- issue_hash_token(['sanitycheck']) FILTER uri %]">Click here to
remove old users/groups</a>
+ [% ELSE %]
+ [% message = Hook.process("statuses") %]
+
+ [% IF message %]
+ [% message FILTER none %]
+ [% ELSE %]
+ The status message string <code>[% san_tag FILTER html %]</code>
+ was not found. Please send email to [% Param("maintainer") %] describing
+ the steps taken to obtain this message.
+ [% END %]
+
[% END %]
[% END %]
-[% san_message FILTER html %]
-
+[% USE Bugzilla %]
+[% IF Bugzilla.usage_mode == constants.USAGE_MODE_CMDLINE %]
+ [% san_message FILTER none %]
+[% ELSE %]
+ [%# Avoid the txt filter in message.txt.tmpl. %]
+ [% san_message FILTER html %]
+[% END %]
[% BLOCK bug_list %]
[% FOREACH bug_id = badbugs %]
@@ -319,12 +305,12 @@
# which itself calls this template again, generating a recursion error.
# I doubt having a tooltip with the bug status and summary is so
# important here anyway, as you can click the "(as buglist)" link. %]
- <a href="show_bug.cgi?id=[% bug_id FILTER url_quote %]">[% bug_id FILTER html %]</a>
+ <a href="show_bug.cgi?id=[% bug_id FILTER uri %]">[% bug_id FILTER html %]</a>
[% ", " IF !loop.last %]
[% END %]
- (<a href="buglist.cgi?bug_id=[% badbugs.join(",") FILTER url_quote %]">as [% terms.bug %] list</a>).
+ (<a href="buglist.cgi?bug_id=[% badbugs.join(",") FILTER uri %]">as [% terms.bug %] list</a>).
[% END %]
[% BLOCK bug_link %]
- <a href="show_bug.cgi?id=[% bug_id FILTER url_quote %]">[% terms.bug %] [%+ bug_id FILTER html %]</a>
+ <a href="show_bug.cgi?id=[% bug_id FILTER uri %]">[% terms.bug %] [%+ bug_id FILTER html %]</a>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/sudo.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/sudo.html.tmpl
index 680bcfb..676959c 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/sudo.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/sudo.html.tmpl
@@ -83,8 +83,7 @@
password</label>:
<input type="hidden" name="Bugzilla_login" value="
[%- user.login FILTER html %]">
- <input type="password" id="Bugzilla_password" name="Bugzilla_password"
- maxlength="20" size="20">
+ <input type="password" id="Bugzilla_password" name="Bugzilla_password" size="20">
<br>
This is done for two reasons. First of all, it is done to reduce
the chances of someone doing large amounts of damage using your
@@ -95,7 +94,7 @@
<p>
Click the button to begin the session:
- <input type="submit" value="Begin Session">
+ <input type="submit" id="begin_sudo" value="Begin Session">
<input type="hidden" name="action" value="begin-sudo">
<input type="hidden" name="token" value="[% token FILTER html %]">
</p>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/table.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/table.html.tmpl
index 303aba7..40c66f1 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/table.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/table.html.tmpl
@@ -50,14 +50,26 @@
# keys are column names from columns subhashes name field.
#
# overrides:
- # Provides a method for overriding individual table cells. This is
- # a hash, whose key is the column name, so the column must be
- # named for one of it's cells to be overwritten. The hash value is
- # an array. Each item in this array is a hash specifying
- # row-matching criteria, and any overridden values. The
- # row-matching criteria consist of keys:
- # match_field: The name of the row value we want to match
- # match_value: The value to match against
+ # Example:
+ # overrides { # first hash
+ # column_name_to_be_overwriten => { # second hash
+ # name_of_row_to_match_against => { # third hash
+ # value_to_match_against => { # fourth hash
+ # content => "some contents"
+ # override_content => 1
+ # }
+ # }
+ # }
+ # }
+ #
+ # Provides a method for overriding individual table cells. This is a hash
+ # (1), whose key is the column name, so the column must be named for
+ # one of it's cells to be overwritten. The hash value is another hash
+ # (2). The keys of that second hash are the name of the row to match
+ # against. The second hash then again points to another hash. Within this
+ # third hash (3), the keys represent values to match against. The item
+ # contains a fourth hash (4) specifying overridden values.
+ #
# Each column value mentioned in the 'columns' documentation above
# can be overwritten (apart from name and heading). To override a
# table-cell value 'xxx', specify a new 'xxx' value, and specify a
@@ -98,30 +110,30 @@
yesno_field = c.yesno_field
%]
- [%# Are there any specific overrides for this column? %]
- [% FOREACH override = overrides.${c.name} %]
+ [%# Get any specific "important" overrides for this c.name and row.name ? %]
+ [% SET important = overrides.${c.name}.name.${row.name} %]
- [%# Is the override for this row? %]
- [% IF override.match_value == row.${override.match_field} %]
+ [% IF important %]
- [% SET contentlink = override.contentlink
- IF override.override_contentlink %]
- [% SET content = override.content
- IF override.override_content %]
- [% SET content_use_field = override.content_use_field
- IF override.override_content_use_field %]
- [% SET align = override.align
- IF override.override_align %]
- [% SET class = override.class
- IF override.override_class %]
- [% SET allow_html_content = override.allow_html_content
- IF override.override_allow_html_content %]
- [% SET yesno_field = override.yesno_field
- IF override.override_yesno_field %]
+ [% FOREACH key IN important.keys %]
+ [% SET ${key} = important.${key} %]
+ [% END %]
- [% LAST %]
+ [% ELSE %]
- [% END %]
+ [%# Are there any specific overrides for this column? %]
+ [% FOREACH match_field = overrides.${c.name}.keys %]
+
+ [% override = overrides.${c.name}.${match_field}.${row.$match_field} %]
+ [% NEXT UNLESS override %]
+
+ [% FOREACH key IN override.keys %]
+ [% SET ${key} = override.${key} %]
+ [% END %]
+
+ [% LAST %]
+
+ [% END %]
[% END %]
<td [% IF align %] align="[% align FILTER html %]" [% END %]
@@ -131,8 +143,8 @@
[% link_uri = contentlink %]
[% WHILE link_uri.search('%%(.+?)%%')%]
[% FOREACH m = link_uri.match('%%(.+?)%%') %]
- [% IF row.$m %]
- [% replacement_value = FILTER url_quote; row.$m; END %]
+ [% IF row.$m.defined %]
+ [% replacement_value = FILTER uri; row.$m; END %]
[% ELSE %]
[% replacement_value = "" %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/users/confirm-delete.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/users/confirm-delete.html.tmpl
index 3852839..8f1a3d2 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/users/confirm-delete.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/users/confirm-delete.html.tmpl
@@ -33,7 +33,6 @@
# namedquery_group_map: number of named queries the user has shared
# profiles_activity: number of changes made to other users' profiles
# series: number of series the viewed user has created
- # votes: number of bugs the viewed user has voted on
# watch.watched: number of users the viewed user is being watched
# by
# watch.watcher: number of users the viewed user is watching
@@ -69,8 +68,8 @@
<td>
[% IF otheruser.groups.size %]
<ul>
- [% FOREACH group = otheruser.groups.keys %]
- <li>[% group FILTER html %]</li>
+ [% FOREACH group = otheruser.groups %]
+ <li>[% group.name FILTER html %]</li>
[% END %]
</ul>
[% ELSE %]
@@ -113,7 +112,7 @@
<li>
[% otheruser.login FILTER html %]
<a href="buglist.cgi?field0-0-0=attachments.submitter&type0-0-0=equals&value0-0-0=
- [%- otheruser.login FILTER url_quote %]">has submitted
+ [%- otheruser.login FILTER uri %]">has submitted
[% IF attachments == 1 %]
one attachment
[% ELSE %]
@@ -133,7 +132,7 @@
<li>
[% otheruser.login FILTER html %]
<a href="buglist.cgi?emailreporter1=1&emailtype1=exact&email1=
- [%- otheruser.login FILTER url_quote %]">has reported
+ [%- otheruser.login FILTER uri %]">has reported
[% IF reporter == 1 %]
one [% terms.bug %]
[% ELSE %]
@@ -171,7 +170,7 @@
<li>
[% otheruser.login FILTER html %] has
<a href="buglist.cgi?field0-0-0=setters.login_name&type0-0-0=equals&value0-0-0=
- [%- otheruser.login FILTER url_quote %]">set
+ [%- otheruser.login FILTER uri %]">set
or requested
[% IF flags.setter == 1 %]
a flag
@@ -192,7 +191,7 @@
<li>
[% otheruser.login FILTER html %] has
<a href="buglist.cgi?emaillongdesc1=1&emailtype1=exact&email1=
- [%- otheruser.login FILTER url_quote %]">commented
+ [%- otheruser.login FILTER uri %]">commented
[% IF longdescs == 1 %]
once on [% terms.abug %]
[% ELSE %]
@@ -226,8 +225,9 @@
[% END %]
[% IF assignee_or_qa || cc || component_cc || email_setting || flags.requestee ||
- namedqueries || profile_setting || quips || series || votes || watch.watched ||
- watch.watcher || whine_events || whine_schedules %]
+ namedqueries || profile_setting || quips || series || watch.watched ||
+ watch.watcher || whine_events || whine_schedules || otheruser.has_audit_entries ||
+ other_safe %]
<div class="warningmessages">
<p>The following deletions are <b>safe</b> and will not generate
referential integrity inconsistencies.</p>
@@ -237,7 +237,7 @@
<li>
[% otheruser.login FILTER html %]
<a href="buglist.cgi?emailassigned_to1=1&emailqa_contact1=1&emailtype1=exact&email1=
- [%- otheruser.login FILTER url_quote %]">is
+ [%- otheruser.login FILTER uri %]">is
the assignee or the QA contact of
[% IF assignee_or_qa == 1 %]
one [% terms.bug %]
@@ -252,7 +252,7 @@
<li>
[% otheruser.login FILTER html %]
<a href="buglist.cgi?emailcc1=1&emailtype1=exact&email1=
- [%- otheruser.login FILTER url_quote %]">is
+ [%- otheruser.login FILTER uri %]">is
on the CC list of
[% IF cc == 1 %]
[%+ terms.abug %]
@@ -283,7 +283,7 @@
<li>
[% otheruser.login FILTER html %] has been
<a href="buglist.cgi?field0-0-0=requestees.login_name&type0-0-0=equals&value0-0-0=
- [%- otheruser.login FILTER url_quote %]">asked
+ [%- otheruser.login FILTER uri %]">asked
to set
[% IF flags.requestee == 1 %]
a flag
@@ -372,23 +372,6 @@
will have no author anymore, but will remain available.
</li>
[% END %]
- [% IF votes %]
- <li>
- [% otheruser.login FILTER html %] has voted on
- [% IF votes == 1 %]
- [%+ terms.abug %]
- [% ELSE %]
- [%+ votes %] [%+ terms.bugs %]
- [% END %].
- If you delete the user account,
- [% IF votes == 1 %]
- this vote
- [% ELSE %]
- these votes
- [% END %]
- will be deleted along with the user account.
- </li>
- [% END %]
[% IF watch.watched || watch.watcher %]
<li>
[% otheruser.login FILTER html %]
@@ -445,6 +428,15 @@
but the whines themselves will be left unaltered.
</li>
[% END %]
+ [% IF otheruser.has_audit_entries %]
+ <li>
+ The user has performed audited administrative tasks
+ that are logged in the database.
+ If you delete this user account, the audit log entries
+ will no longer be indentifiable.
+ </li>
+ [% END %]
+ [% Hook.process('warn_safe') %]
</ul>
</div>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/users/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/users/list.html.tmpl
index 4788e52..3f745a4 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/users/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/users/list.html.tmpl
@@ -63,36 +63,33 @@
[% END %]
[%# Disabled users are crossed out. Missing realnames are noticed in red. %]
-[% overrides.login_name = [] %]
-[% overrides.realname = [] %]
+[% overrides.login_name = {} %]
+[% overrides.realname = {} %]
[% FOREACH thisuser = users %]
[% IF !thisuser.realname %]
[%# We cannot pass one class now and one class later. %]
- [% SET classes = (thisuser.disabledtext ? "bz_inactive missing" : "missing") %]
- [% overrides.realname.push({
- match_value => "$thisuser.login_name"
- match_field => 'login_name'
- content => "missing"
- override_content => 1
- class => "$classes"
- override_class => 1 })
+ [% SET classes = (thisuser.is_enabled ? "missing" : "bz_inactive missing") %]
+ [% overrides.realname.login_name.${thisuser.login_name} = {
+ content => "missing"
+ override_content => 1
+ class => "$classes"
+ override_class => 1
+ }
+ %]
+ [% ELSIF !thisuser.is_enabled %]
+ [% overrides.realname.login_name.${thisuser.login_name} = {
+ class => "bz_inactive"
+ override_class => 1
+ }
%]
[% END %]
- [% IF thisuser.disabledtext %]
- [% overrides.login_name.push({
- match_value => "$thisuser.login_name"
- match_field => 'login_name'
- class => "bz_inactive"
- override_class => 1 })
- %]
-
- [% overrides.realname.push({
- match_value => "$thisuser.login_name"
- match_field => 'login_name'
- class => "bz_inactive"
- override_class => 1 })
+ [% IF !thisuser.is_enabled %]
+ [% overrides.login_name.login_name.${thisuser.login_name} = {
+ class => "bz_inactive"
+ override_class => 1
+ }
%]
[% END %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/users/listselectvars.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/users/listselectvars.html.tmpl
index a6eae57..a2be91d 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/users/listselectvars.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/users/listselectvars.html.tmpl
@@ -20,8 +20,8 @@
[% BLOCK listselectionurlparams %]
[% FOREACH field = listselectionvalues.keys %]&
- [% field FILTER url_quote %]=
- [% listselectionvalues.$field FILTER url_quote %]
+ [% field FILTER uri %]=
+ [% listselectionvalues.$field FILTER uri %]
[% END %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/users/responsibilities.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/users/responsibilities.html.tmpl
index bbf121a..1e11f80 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/users/responsibilities.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/users/responsibilities.html.tmpl
@@ -29,14 +29,15 @@
<th>Component</th>
<th>Default Assignee</th>
<th>Default QA Contact</th>
+ <th>Default CC</th>
</tr>
[% FOREACH component = item.components %]
<tr>
<td>
[% IF user.in_group("editcomponents", component.product_id) %]
<a href="editcomponents.cgi?action=edit&product=
- [% item.product.name FILTER url_quote %]&component=
- [% component.name FILTER url_quote %]">
+ [% item.product.name FILTER uri %]&component=
+ [% component.name FILTER uri %]">
[% END %]
[% component.name FILTER html %]
[% IF user.in_group("editcomponents", component.product_id) %]
@@ -48,6 +49,9 @@
[% component.$responsibility.id == otheruser.id ? "X" : " " %]
</td>
[% END %]
+ <td class="center">
+ [% component.initial_cc.contains(otheruser) ? "X" : " " %]
+ </td>
</tr>
[% END %]
</tbody>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/users/search.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/users/search.html.tmpl
index 82e0afd..36ca48e 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/users/search.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/users/search.html.tmpl
@@ -62,6 +62,9 @@
[% END %]
</select></p>
[% END %]
+
+[% Hook.process('end') %]
+
</form>
[% IF editusers %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/users/userdata.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/users/userdata.html.tmpl
index feee4c5..f3f0e5a 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/users/userdata.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/users/userdata.html.tmpl
@@ -27,10 +27,10 @@
<input size="64" maxlength="255" name="login"
id="login" value="[% otheruser.login FILTER html %]" />
[% IF editform %]
- [% IF !otheruser.groups.bz_sudo_protect %]
+ [% IF !otheruser.in_group('bz_sudo_protect') %]
<br />
<a href="relogin.cgi?action=prepare-sudo&target_login=
- [%- otheruser.login FILTER url_quote %]">Impersonate this user</a>
+ [%- otheruser.login FILTER uri %]">Impersonate this user</a>
[% END %]
[% END %]
[% ELSE %]
@@ -38,6 +38,19 @@
[% END %]
</td>
</tr>
+[% IF default_authorizer.extern_id_used %]
+ <tr>
+ <th><label for="extern_id">External Login ID:</label></th>
+ <td>
+ [% IF editusers %]
+ <input size="64" maxlength="64" name="extern_id"
+ id="extern_id" value="[% otheruser.extern_id FILTER html %]">
+ [% ELSE %]
+ [% otheruser.extern_id FILTER html %]
+ [% END %]
+ </td>
+ </tr>
+[% END %]
<tr>
<th><label for="name">Real name:</label></th>
<td>
@@ -61,9 +74,8 @@
<tr>
<th><label for="password">Password:</label></th>
<td>
- <input type="password" size="16" maxlength="16" name="password"
- autocomplete="off"
- id="password" value="" />
+ <input type="password" size="16" name="password" id="password"
+ value="" autocomplete="off" />
[% IF editform %]<br />
(Enter new password to change.)
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/versions/confirm-delete.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/versions/confirm-delete.html.tmpl
index 88ffceb..39091d5f 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/versions/confirm-delete.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/versions/confirm-delete.html.tmpl
@@ -52,8 +52,8 @@
[% IF version.bug_count %]
<a title="List of [% terms.bugs %] targetted at version '
[%- version.name FILTER html %]'"
- href="buglist.cgi?version=[% version.name FILTER url_quote %]&product=
- [%- product.name FILTER url_quote %]">
+ href="buglist.cgi?version=[% version.name FILTER uri %]&product=
+ [%- product.name FILTER uri %]">
[%- version.bug_count FILTER none %]</a>
[% ELSE %]
None
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/versions/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/versions/edit.html.tmpl
index 2a7c784..497d67e 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/versions/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/versions/edit.html.tmpl
@@ -37,11 +37,15 @@
<table border="0" cellpadding="4" cellspacing="0">
<tr>
- <th valign="top"><label for="version">Version:</label></th>
+ <th class="field_label"><label for="version">Version:</label></th>
<td><input id="version" size="64" maxlength="64" name="version" value="
[%- version.name FILTER html %]"></td>
</tr>
-
+ <tr>
+ <th class="field_label"><label for="isactive">Enabled For [% terms.Bugs %]:</label></th>
+ <td><input id="isactive" name="isactive" type="checkbox" value="1"
+ [% 'checked="checked"' IF version.isactive %]></td>
+ </tr>
</table>
<input type="hidden" name="versionold" value="[% version.name FILTER html %]">
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/versions/footer.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/versions/footer.html.tmpl
index 8d96a12..ae26e57 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/versions/footer.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/versions/footer.html.tmpl
@@ -38,7 +38,7 @@
[% UNLESS no_add_version_link %]
<a title="Add a version to product '[% product.name FILTER html %]'"
href="editversions.cgi?action=add&product=
- [%- product.name FILTER url_quote %]">Add</a> a version.
+ [%- product.name FILTER uri %]">Add</a> a version.
[% END %]
[% IF version.name && !no_edit_version_link %]
@@ -46,20 +46,20 @@
title="Edit Version '[% version.name FILTER html %]' of product '
[%- product.name FILTER html %]'"
href="editversions.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]&version=
- [%- version.name FILTER url_quote %]">
+ [%- product.name FILTER uri %]&version=
+ [%- version.name FILTER uri %]">
'[% version.name FILTER html %]'</a>.
[% END %]
[% UNLESS no_edit_other_versions_link %]
Edit other versions of product <a
href="editversions.cgi?product=
- [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>.
+ [%- product.name FILTER uri %]">'[% product.name FILTER html %]'</a>.
[% END %]
Edit product <a
href="editproducts.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]">'[% product.name FILTER html %]'</a>.
+ [%- product.name FILTER uri %]">'[% product.name FILTER html %]'</a>.
</p>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/versions/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/versions/list.html.tmpl
index 45e3333..69435d2 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/versions/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/versions/list.html.tmpl
@@ -33,11 +33,11 @@
%]
[% edit_contentlink = BLOCK %]editversions.cgi?action=edit&product=
- [%- product.name FILTER url_quote %]&version=%%name%%[% END %]
+ [%- product.name FILTER uri %]&version=%%name%%[% END %]
[% delete_contentlink = BLOCK %]editversions.cgi?action=del&product=
- [%- product.name FILTER url_quote %]&version=%%name%%[% END %]
+ [%- product.name FILTER uri %]&version=%%name%%[% END %]
[% bug_count_contentlink = BLOCK %]buglist.cgi?version=%%name%%&product=
- [%- product.name FILTER url_quote %][% END %]
+ [%- product.name FILTER uri %][% END %]
[% columns = [
@@ -45,6 +45,11 @@
name => "name"
heading => "Edit version..."
contentlink => edit_contentlink
+ },
+ {
+ name => "isactive"
+ heading => "Active"
+ yesno_field => 1
}
]
%]
@@ -68,6 +73,8 @@
})
%]
+[% Hook.process('before_table') %]
+
[% PROCESS admin/table.html.tmpl
columns = columns
data = product.versions
@@ -75,7 +82,7 @@
[% IF ! showbugcounts %]
- <p><a href="editversions.cgi?product=[% product.name FILTER url_quote %]&showbugcounts=1">
+ <p><a href="editversions.cgi?product=[% product.name FILTER uri %]&showbugcounts=1">
Redisplay table with [% terms.bug %] counts (slower)</a></p>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/workflow/comment.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/workflow/comment.html.tmpl
index 2fa78f0..109d98b 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/workflow/comment.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/workflow/comment.html.tmpl
@@ -49,7 +49,7 @@
<th> </th>
[% FOREACH status = statuses %]
<th class="col-header[% status.is_open ? " open-status" : " closed-status" %]">
- [% status.name FILTER html %]
+ [% display_value("bug_status", status.name) FILTER html %]
</th>
[% END %]
</tr>
@@ -59,7 +59,7 @@
[% FOREACH status = p.merge(statuses) %]
<tr class="highlight">
<th align="right" class="[% status.is_open ? "open-status" : "closed-status" %]">
- [% status.name FILTER html %]
+ [% display_value("bug_status", status.name) FILTER html %]
</th>
[% FOREACH new_status = statuses %]
@@ -82,7 +82,7 @@
<p align="center">
<input type="hidden" name="action" value="update_comment">
<input type="hidden" name="token" value="[% token FILTER html %]">
- <input type="submit" value="Commit Changes"> -
+ <input type="submit" id="update_comment" value="Commit Changes"> -
<a href="editworkflow.cgi?action=edit_comment">Cancel Changes</a> -
<a href="editworkflow.cgi">View Current Workflow</a>
</p>
diff --git a/Websites/bugs.webkit.org/template/en/default/admin/workflow/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/admin/workflow/edit.html.tmpl
index 1328ce0..5f2b212 100644
--- a/Websites/bugs.webkit.org/template/en/default/admin/workflow/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/admin/workflow/edit.html.tmpl
@@ -35,8 +35,8 @@
<p>
This page allows you to define which status transitions are valid in your workflow.
For compatibility with older versions of [% terms.Bugzilla %], reopening [% terms.abug %]
- will only display either [% get_status("UNCONFIRMED") FILTER html %] or
- [%+ get_status("REOPENED") FILTER html %] (if allowed by your workflow) but not
+ will only display either [% display_value("bug_status", "UNCONFIRMED") FILTER html %] or
+ [%+ display_value("bug_status", "REOPENED") FILTER html %] (if allowed by your workflow) but not
both. The decision depends on whether the [% terms.bug %] has ever been confirmed or not.
So it is a good idea to allow both transitions and let [% terms.Bugzilla %] select the
correct one.
@@ -54,7 +54,7 @@
<th> </th>
[% FOREACH status = statuses %]
<th class="col-header[% status.is_open ? " open-status" : " closed-status" %]">
- [% status.name FILTER html %]
+ [% display_value("bug_status", status.name) FILTER html %]
</th>
[% END %]
</tr>
@@ -64,7 +64,7 @@
[% FOREACH status = p.merge(statuses) %]
<tr class="highlight">
<th align="right" class="[% status.is_open ? "open-status" : "closed-status" %]">
- [% status.name FILTER html %]
+ [% display_value("bug_status", status.name) FILTER html %]
</th>
[% FOREACH new_status = statuses %]
@@ -89,7 +89,7 @@
<p>
When [% terms.abug %] is marked as a duplicate of another one or is moved
to another installation, the [% terms.bug %] status is automatically set to
- <b>[% Param("duplicate_or_move_bug_status") FILTER html %]</b>. All transitions to
+ <b>[% display_value("bug_status", Param("duplicate_or_move_bug_status")) FILTER html %]</b>. All transitions to
this [% terms.bug %] status must then be valid (this is the reason why you cannot edit
them above).<br>
Note: you can change this setting by visiting the
@@ -100,7 +100,7 @@
<p align="center">
<input type="hidden" name="action" value="update">
<input type="hidden" name="token" value="[% token FILTER html %]">
- <input type="submit" value="Commit Changes"> -
+ <input type="submit" id="update_workflow" value="Commit Changes"> -
<a href="editworkflow.cgi">Cancel Changes</a> -
<a href="editworkflow.cgi?action=edit_comment">View Comments Required on Status Transitions</a>
</p>
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/cancel-create-dupe.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/cancel-create-dupe.html.tmpl
deleted file mode 100644
index f838955..0000000
--- a/Websites/bugs.webkit.org/template/en/default/attachment/cancel-create-dupe.html.tmpl
+++ /dev/null
@@ -1,48 +0,0 @@
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Olav Vitters.
- #
- # Contributor(s): Olav Vitters <olav@bkor.dhs.org>
- # David Lawrence <dkl@redhat.com>
- #%]
-
-[%# INTERFACE:
- # bugid: integer. ID of the bug report that this attachment relates to.
- # attachid: integer. ID of the previous attachment recently created.
- #%]
-
-[% PROCESS "global/field-descs.none.tmpl" %]
-
-[% PROCESS global/header.html.tmpl
- title = "Already filed attachment"
-%]
-
-[% USE Bugzilla %]
-
-<table cellpadding="20">
- <tr>
- <td bgcolor="#ff0000">
- <font size="+2">
- You already used the form to file
- <a href="[% urlbase FILTER html %]attachment.cgi?id=[% attachid FILTER url_quote %]&action=edit">attachment [% attachid FILTER url_quote %]</a>.
- </font>
- </td>
- </tr>
-</table>
-
-<p>
- You can either <a href="[% urlbase FILTER html %]attachment.cgi?bugid=[% bugid FILTER url_quote %]&action=enter">
- create a new attachment</a> or [% "go back to $terms.bug $bugid" FILTER bug_link(bugid) FILTER none %].
-<p>
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/content-types.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/content-types.html.tmpl
deleted file mode 100644
index 471222a..0000000
--- a/Websites/bugs.webkit.org/template/en/default/attachment/content-types.html.tmpl
+++ /dev/null
@@ -1,27 +0,0 @@
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Myk Melez <myk@mozilla.org>
- #%]
-
- <option value="text/plain">plain text (text/plain)</option>
- <option value="text/html">HTML source (text/html)</option>
- <option value="application/xml">XML source (application/xml)</option>
- <option value="image/gif">GIF image (image/gif)</option>
- <option value="image/jpeg">JPEG image (image/jpeg)</option>
- <option value="image/png">PNG image (image/png)</option>
- <option value="application/octet-stream">binary file (application/octet-stream)</option>
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/create.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/create.html.tmpl
index 1064815..863d83a 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/create.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/create.html.tmpl
@@ -26,20 +26,28 @@
[%# Define strings that will serve as the title and header of this page %]
[% title = BLOCK %]Create New Attachment for [% terms.Bug %] #[% bug.bug_id %][% END %]
[% header = BLOCK %]Create New Attachment for
- [%+ "$terms.Bug $bug.bug_id" FILTER bug_link(bug.bug_id) FILTER none %][% END %]
+ [%+ "$terms.Bug $bug.bug_id" FILTER bug_link(bug) FILTER none %][% END %]
[% subheader = BLOCK %][% bug.short_desc FILTER html %][% END %]
[% PROCESS global/header.html.tmpl
title = title
header = header
subheader = subheader
- onload="setContentTypeDisabledState(document.entryform);"
- style_urls = [ 'skins/standard/create_attachment.css' ]
- javascript_urls = [ "js/attachment.js" ]
+ style_urls = [ 'skins/standard/attachment.css' ]
+ yui = [ 'autocomplete' ]
+ javascript_urls = [ "js/attachment.js", 'js/field.js', "js/util.js", "js/TUI.js" ]
doc_section = "attachments.html"
%]
-<form name="entryform" method="post" action="attachment.cgi" enctype="multipart/form-data">
+<script type="text/javascript">
+<!--
+TUI_hide_default('attachment_text_field');
+-->
+</script>
+
+<form name="entryform" method="post" action="attachment.cgi"
+ enctype="multipart/form-data"
+ onsubmit="return validateAttachmentForm(this)">
<input type="hidden" name="bugid" value="[% bug.bug_id %]">
<input type="hidden" name="action" value="insert">
<input type="hidden" name="token" value="[% token FILTER html %]">
@@ -54,8 +62,7 @@
<em>(optional) Check each existing attachment made obsolete by your new attachment.</em><br>
[% IF attachments.size %]
[% FOREACH attachment = attachments %]
- [% IF ((attachment.isprivate == 0) || (Param("insidergroup")
- && user.in_group(Param("insidergroup")))) %]
+ [% IF ((attachment.isprivate == 0) || user.is_insider) %]
<input type="checkbox" id="[% attachment.id %]"
name="obsolete" value="[% attachment.id %]">
<a href="attachment.cgi?id=[% attachment.id %]&action=edit">[% attachment.id %]: [% attachment.description FILTER html %]</a><br>
@@ -77,16 +84,17 @@
<label for="takebug">take [% terms.bug %]</label>
[% bug_statuses = [] %]
[% FOREACH bug_status = bug.status.can_change_to %]
- [% NEXT IF bug_status.name == "UNCONFIRMED" && !bug.product_obj.votes_to_confirm %]
+ [% NEXT IF bug_status.name == "UNCONFIRMED"
+ && !bug.product_obj.allows_unconfirmed %]
[% bug_statuses.push(bug_status) IF bug_status.is_open %]
[% END %]
[% IF bug_statuses.size %]
<label for="takebug">and set the [% terms.bug %] status to</label>
<select id="bug_status" name="bug_status">
- <option value="[% bug.status.name FILTER html %]">[% get_status(bug.status.name) FILTER html %] (current)</option>
+ <option value="[% bug.status.name FILTER html %]">[% display_value("bug_status", bug.status.name) FILTER html %] (current)</option>
[% FOREACH bug_status = bug_statuses %]
[% NEXT IF bug_status.id == bug.status.id %]
- <option value="[% bug_status.name FILTER html %]">[% get_status(bug_status.name) FILTER html %]</option>
+ <option value="[% bug_status.name FILTER html %]">[% display_value("bug_status", bug_status.name) FILTER html %]</option>
[% END %]
</select>
[% END %]
@@ -107,17 +115,23 @@
%]
</td>
</tr>
- [% IF (Param("insidergroup") && user.in_group(Param("insidergroup"))) %]
+ [% IF user.is_insider %]
<tr>
<th>Privacy:</th>
<td>
- <em>If the attachment is private, check the box below.</em><br>
<input type="checkbox" name="isprivate" id="isprivate"
value="1" onClick="updateCommentPrivacy(this)">
- <label for="isprivate">Private</label>
+ <label for="isprivate">
+ Make attachment and comment private (visible only to members of
+ the <strong>[% Param('insidergroup') FILTER html %]</strong>
+ group)
+ </label>
</td>
</tr>
[% END %]
+
+ [% Hook.process('form_before_submit') %]
+
<tr>
<th> </th>
<td><input type="submit" id="create" value="Submit"></td>
@@ -126,4 +140,6 @@
</form>
+[% Hook.process('end') %]
+
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/created.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/created.html.tmpl
index faf1b87..da2fec8 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/created.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/created.html.tmpl
@@ -26,23 +26,9 @@
[% PROCESS global/variables.none.tmpl %]
[% bug = bugs.0 %]
-[% bodyclasses = ['bz_bug',
- "bz_status_$bug.bug_status",
- "bz_component_$bug.component",
- "bz_bug_$bug.bug_id"
- ]
-%]
-[% FOREACH group = bug.groups_in %]
- [% bodyclasses.push("bz_group_$group.name") %]
-[% END %]
-
+[% PROCESS "bug/show-header.html.tmpl" %]
[% PROCESS global/header.html.tmpl
title = "Attachment $attachment.id added to $terms.Bug $attachment.bug_id"
- bodyclasses = bodyclasses
- javascript_urls = [ "js/util.js", "js/field.js",
- "js/yui/yahoo-dom-event.js", "js/yui/calendar.js" ]
- style_urls = [ "skins/standard/yui/calendar.css", "skins/standard/show_bug.css" ]
- doc_section = "bug_page.html"
%]
<dl>
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/createformcontents.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/createformcontents.html.tmpl
index 956b7bc..5b04382 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/createformcontents.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/createformcontents.html.tmpl
@@ -21,54 +21,48 @@
# Marc Schumann <wurblzap@gmail.com>
#%]
-<tr>
+<tr class="attachment_data">
<th><label for="data">File</label>:</th>
<td>
- <em>Enter the path to the file on your computer.</em><br>
- <input type="file" id="data" name="data" size="50"
- [% IF Param("allow_attach_url") %]
- onchange="DataFieldHandler()"
- [% END %]
- >
+ <em>Enter the path to the file on your computer</em> (or
+ <a id="attachment_data_controller" href="javascript:TUI_toggle_class('attachment_text_field');
+ javascript:TUI_toggle_class('attachment_data')"
+ >paste text as attachment</a>).<br>
+ <input type="file" id="data" name="data" size="50" onchange="DataFieldHandler()">
</td>
</tr>
-[% IF Param("maxlocalattachment") %]
-<tr>
- <th>BigFile:</th>
+<tr class="attachment_text_field">
+ <th><label for="attach_text">File</label>:</th>
<td>
- <input type="checkbox" id="bigfile"
- name="bigfile" value="bigfile">
- <label for="bigfile">
- Big File - Stored locally and may be purged
- </label>
+ <em>Paste the text to be added as an attachment</em> (or
+ <a id="attachment_text_field_controller" href="javascript:TUI_toggle_class('attachment_text_field');
+ javascript:TUI_toggle_class('attachment_data')"
+ >attach a file</a>).<br>
+ <textarea id="attach_text" name="attach_text" cols="80" rows="15"
+ onkeyup="TextFieldHandler()" onblur="TextFieldHandler()"></textarea>
</td>
</tr>
-[% END %]
-[% IF Param("allow_attach_url") %]
<tr>
- <th><label for="attachurl">AttachURL</label>:</th>
- <td>
- <em>URL to be attached instead.</em><br>
- <input type="text" id="attachurl" name="attachurl" size="60"
- maxlength="2000"
- onkeyup="URLFieldHandler()" onblur="URLFieldHandler()">
- </td>
-</tr>
-[% END %]
-<tr>
- <th><label for="description">Description</label>:</th>
+ <th class="required"><label for="description">Description</label>:</th>
<td>
<em>Describe the attachment briefly.</em><br>
- <input type="text" id="description" name="description" size="60" maxlength="200">
+ <input type="text" id="description" name="description" class="required"
+ size="60" maxlength="200">
</td>
</tr>
-<tr>
+<tr[% ' class="expert_fields"' UNLESS bug.id %]>
<th>Content Type:</th>
<td>
<em>If the attachment is a patch, check the box below.</em><br>
<input type="checkbox" id="ispatch" name="ispatch" value="1"
onchange="setContentTypeDisabledState(this.form);">
<label for="ispatch">patch</label><br><br>
+ [%# Reset this whenever the page loads so that the JS state is up to date %]
+ <script type="text/javascript">
+ YAHOO.util.Event.onDOMReady(function() {
+ bz_fireEvent(document.getElementById('ispatch'), 'change');
+ });
+ </script>
<em>Otherwise, choose a method for determining the content type.</em><br>
<input type="radio" id="autodetect"
@@ -79,7 +73,7 @@
<label for="list">select from list</label>:
<select name="contenttypeselection" id="contenttypeselection"
onchange="this.form.contenttypemethod[1].checked = true;">
- [% PROCESS "attachment/content-types.html.tmpl" %]
+ [% PROCESS content_types %]
</select><br>
<input type="radio" id="manual"
name="contenttypemethod" value="manual">
@@ -89,11 +83,27 @@
onchange="if (this.value) this.form.contenttypemethod[2].checked = true;">
</td>
</tr>
-<tr>
+<tr[% ' class="expert_fields"' UNLESS bug.id %]>
<td> </td>
<td>
[% IF flag_types && flag_types.size > 0 %]
- [% PROCESS "flag/list.html.tmpl" bug_id=bugid attach_id=attachid %]<br>
+ [% PROCESS "flag/list.html.tmpl" %]<br>
[% END %]
</td>
</tr>
+
+[% BLOCK content_types %]
+ [% mimetypes = [{type => "text/plain", desc => "plain text"},
+ {type => "text/html", desc => "HTML source"},
+ {type => "application/xml", desc => "XML source"},
+ {type => "image/gif", desc => "GIF image"},
+ {type => "image/jpeg", desc => "JPEG image"},
+ {type => "image/png", desc => "PNG image"},
+ {type => "application/octet-stream", desc => "binary file"}]
+ %]
+ [% Hook.process("mimetypes", "attachment/createformcontents.html.tmpl") %]
+
+ [% FOREACH m = mimetypes %]
+ <option value="[% m.type FILTER html %]">[% m.desc FILTER html %] ([% m.type FILTER html %])</option>
+ [% END %]
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/diff-file.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/diff-file.html.tmpl
index 85dd220..a742a84 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/diff-file.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/diff-file.html.tmpl
@@ -16,6 +16,7 @@
# Rights Reserved.
#
# Contributor(s): John Keiser <jkeiser@netscape.com>
+ # Frédéric Buclin <LpSolit@gmail.com>
#%]
[%# This line is really long for a reason: to get rid of any possible textnodes
@@ -52,7 +53,7 @@
[% FOREACH section = sections %]
[% section_num = section_num + 1 %]
<tr><th colspan="4" class="section_head">
- <table cellpadding="0" cellspacing="0">
+ <table id="[% file.filename FILTER html %]_sec[% section_num %]" cellpadding="0" cellspacing="0">
<tr><th width="95%" align="left">
[% IF file.is_add %]
Added
@@ -78,7 +79,7 @@
[% section.func_info FILTER html IF section.func_info %]
[% END %]
</th><th>
- <a name="[% file.filename FILTER html %]_sec[% section_num %]" href="#[% file.filename FILTER html %]_sec[% section_num %]">Link Here</a>
+ <a href="#[% file.filename FILTER html %]_sec[% section_num %]">Link Here</a>
</th></tr></table>
</th></tr>
[% current_line_old = section.old_start %]
@@ -99,30 +100,31 @@
[% IF group.plus.size %]
[% IF group.minus.size %]
[% i = 0 %]
- [%# We need to store them in external variables. %]
- [% curr_new = current_line_new %]
- [% curr_old = current_line_old %]
[% WHILE (i < group.plus.size || i < group.minus.size) %]
+ [%# WHILE cannot loop more than 1000 times by default, so we break it every 500 times. %]
[% currentloop = 0 %]
[% WHILE currentloop < 500 && (i < group.plus.size || i < group.minus.size) %]
<tr>
- <td class="num">[% curr_old %]</td>
+ [% IF i < group.minus.size %]
+ <td class="num">[% current_line_old + i %]</td>
<td class="changed"><pre>[% group.minus.$i FILTER html %]</pre></td>
- <td class="num">[% curr_new %]</td>
+ [% ELSIF i == group.minus.size %]
+ [% rowspan = group.plus.size - group.minus.size %]
+ <td class="num"[% IF rowspan > 1 %] rowspan="[% rowspan FILTER none %]"[% END %]></td>
+ <td class="changed"[% IF rowspan > 1 %] rowspan="[% rowspan FILTER none %]"[% END %]></td>
+ [% END %]
+
+ [% IF i < group.plus.size %]
+ <td class="num">[% current_line_new + i %]</td>
<td class="changed"><pre>[% group.plus.$i FILTER html %]</pre></td>
+ [% ELSIF i == group.plus.size %]
+ [% rowspan = group.minus.size - group.plus.size %]
+ <td class="num"[% IF rowspan > 1 %] rowspan="[% rowspan FILTER none %]"[% END %]></td>
+ <td class="changed"[% IF rowspan > 1 %] rowspan="[% rowspan FILTER none %]"[% END %]></td>
+ [% END %]
</tr>
[% currentloop = currentloop + 1 %]
[% i = i + 1 %]
- [% IF i < group.minus.size %]
- [% curr_old = curr_old + 1 %]
- [% ELSE %]
- [% curr_old = "" %]
- [% END %]
- [% IF i < group.plus.size %]
- [% curr_new = curr_new + 1 %]
- [% ELSE %]
- [% curr_new = "" %]
- [% END %]
[% END %]
[% END %]
[% current_line_old = current_line_old + group.minus.size %]
@@ -136,7 +138,10 @@
</tr>
[% ELSE %]
<tr>
- <td class="num"></td><td></td>
+ [% IF loop.first %]
+ <td class="num"[% IF group.plus.size > 1 %] rowspan="[% group.plus.size %]"[% END %]></td>
+ <td[% IF group.plus.size > 1 %] rowspan="[% group.plus.size %]"[% END %]></td>
+ [% END %]
<td class="num">[% current_line_new %]</td>
<td class="added"><pre>[% line FILTER html %]</pre></td>
</tr>
@@ -156,7 +161,10 @@
<tr>
<td class="num">[% current_line_old %]</td>
<td class="removed"><pre>[% line FILTER html %]</pre></td>
- <td class="num"></td><td></td>
+ [% IF loop.first %]
+ <td class="num"[% IF group.minus.size > 1 %] rowspan="[% group.minus.size %]"[% END %]></td>
+ <td[% IF group.minus.size > 1 %] rowspan="[% group.minus.size %]"[% END %]></td>
+ [% END %]
</tr>
[% END %]
[% current_line_old = current_line_old + 1 %]
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/diff-footer.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/diff-footer.html.tmpl
index d263d9b..49c662a 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/diff-footer.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/diff-footer.html.tmpl
@@ -24,6 +24,9 @@
<br>
+ [% PROCESS global/variables.none.tmpl %]
+ <span>Return to [% "$terms.bug $bugid" FILTER bug_link(bugid) FILTER none %]</span>
+
[% PROCESS global/footer.html.tmpl %]
[% ELSE %]
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/diff-header.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/diff-header.html.tmpl
index c6b14d9..c13b2e7 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/diff-header.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/diff-header.html.tmpl
@@ -30,176 +30,6 @@
[% END %]
[% END %]
-[% style = BLOCK %]
-.file_head {
- font-weight: bold;
- font-size: 1em;
- background-color: #c3c3c3;
- border: 1px solid black;
-}
-
-.file_head a {
- text-decoration: none;
- font-family: monospace;
- font-size: 1.1em;
-}
-
-.file_collapse {
- display: none;
-}
-
-.section_head {
- background-color: #f0f0f0;
- border: 1px solid black;
- text-align: left;
-}
-
-table.file_table {
- table-layout: fixed;
- width: 100%;
- empty-cells: show;
- border-spacing: 0px;
- border-collapse: collapse;
- /* draw border below last open context section in listing */
- border-bottom: 1px solid black;
-}
-
-tbody.file pre {
- display: inline;
- white-space: pre-wrap; /* CSS 3 & CSS 2.1 */
- white-space: -moz-pre-wrap; /* Gecko < 1.9.1 */
- white-space: -o-pre-wrap; /* Opera 7 */
- font-size: 0.9em;
-}
-
-tbody.file pre:empty {
- display: block;
-}
-
-.changed {
- background-color: lightblue;
-}
-
-.added {
- background-color: lightgreen;
-}
-
-.removed {
- background-color: #FFCC99;
-}
-
-.num {
- background-color: #ffe9ae;
- text-align:right;
- padding: 0 0.3em;
- width: 3em;
-}
-
-.warning {
- color: red
-}
-[% END %]
-
-[%# SCRIPT FUNCTIONS %]
-[% javascript = BLOCK %]
- function collapse_all() {
- var elem = document.checkboxform.firstChild;
- while (elem != null) {
- if (elem.firstChild != null) {
- var tbody = elem.firstChild.nextSibling;
- if (tbody.className == 'file') {
- tbody.className = 'file_collapse';
- twisty = get_twisty_from_tbody(tbody);
- twisty.firstChild.nodeValue = '(+)';
- twisty.nextSibling.checked = false;
- }
- }
- elem = elem.nextSibling;
- }
- return false;
- }
-
- function expand_all() {
- var elem = document.checkboxform.firstChild;
- while (elem != null) {
- if (elem.firstChild != null) {
- var tbody = elem.firstChild.nextSibling;
- if (tbody.className == 'file_collapse') {
- tbody.className = 'file';
- twisty = get_twisty_from_tbody(tbody);
- twisty.firstChild.nodeValue = '(-)';
- twisty.nextSibling.checked = true;
- }
- }
- elem = elem.nextSibling;
- }
- return false;
- }
-
- var current_restore_elem;
-
- function restore_all() {
- current_restore_elem = null;
- incremental_restore();
- }
-
- function incremental_restore() {
- if (!document.checkboxform.restore_indicator.checked) {
- return;
- }
- var next_restore_elem;
- if (current_restore_elem) {
- next_restore_elem = current_restore_elem.nextSibling;
- } else {
- next_restore_elem = document.checkboxform.firstChild;
- }
- while (next_restore_elem != null) {
- current_restore_elem = next_restore_elem;
- if (current_restore_elem.firstChild != null) {
- restore_elem(current_restore_elem.firstChild.nextSibling);
- }
- next_restore_elem = current_restore_elem.nextSibling;
- }
- }
-
- function restore_elem(elem, alertme) {
- if (elem.className == 'file_collapse') {
- twisty = get_twisty_from_tbody(elem);
- if (twisty.nextSibling.checked) {
- elem.className = 'file';
- twisty.firstChild.nodeValue = '(-)';
- }
- } else if (elem.className == 'file') {
- twisty = get_twisty_from_tbody(elem);
- if (!twisty.nextSibling.checked) {
- elem.className = 'file_collapse';
- twisty.firstChild.nodeValue = '(+)';
- }
- }
- }
-
- function twisty_click(twisty) {
- tbody = get_tbody_from_twisty(twisty);
- if (tbody.className == 'file') {
- tbody.className = 'file_collapse';
- twisty.firstChild.nodeValue = '(+)';
- twisty.nextSibling.checked = false;
- } else {
- tbody.className = 'file';
- twisty.firstChild.nodeValue = '(-)';
- twisty.nextSibling.checked = true;
- }
- return false;
- }
-
- function get_tbody_from_twisty(twisty) {
- return twisty.parentNode.parentNode.parentNode.nextSibling;
- }
- function get_twisty_from_tbody(tbody) {
- return tbody.previousSibling.firstChild.nextSibling.firstChild.firstChild;
- }
-[% END %]
-
[% onload = 'restore_all(); document.checkboxform.restore_indicator.checked = true' %]
[% BLOCK viewurl %]attachment.cgi?id=[% id %][% END %]
@@ -221,18 +51,16 @@
[% subheader = BLOCK %]
[% bugsummary FILTER html %]
[% END %]
- [% PROCESS global/header.html.tmpl doc_section = "attachments.html#patchviewer" %]
+ [% PROCESS global/header.html.tmpl doc_section = "attachments.html#patchviewer"
+ javascript_urls = "js/attachment.js"
+ style_urls = ['skins/standard/attachment.css'] %]
[% ELSE %]
<html>
<head>
- <style type="text/css">
- [% style %]
- </style>
- <script type="text/javascript">
- <!--
- [% javascript %]
- -->
- </script>
+ <link href="[% 'skins/standard/attachment.css' FILTER mtime %]"
+ rel="stylesheet" type="text/css">
+ <script src="[% 'js/attachment.js' FILTER mtime %]"
+ type="text/javascript"></script>
</head>
<body onload="[% onload FILTER html %]">
[% END %]
@@ -243,7 +71,8 @@
[% IF headers %]
<a href="[% PROCESS viewurl id=attachid %]">View</a>
| <a href="[% PROCESS editurl id=attachid %]">Details</a>
- | <a href="[% PROCESS diffurl id=attachid %]&context=[% context FILTER url_quote %]&collapsed=[% collapsed FILTER url_quote %]&headers=[% headers FILTER url_quote %]&format=raw">Raw Unified</a>
+ | <a href="[% PROCESS diffurl id=attachid %]&context=[% context FILTER uri %]&collapsed=[% collapsed FILTER uri %]&headers=[% headers FILTER uri %]&format=raw">Raw Unified</a>
+ | Return to [% "$terms.bug $bugid" FILTER bug_link(bugid) FILTER none %]
[% END %]
[% IF other_patches.size > 0 %]
[% IF headers %] |[%END%]
@@ -267,6 +96,7 @@
[% ELSE %]
[% IF headers %]
<a href="attachment.cgi?oldid=[% oldid %]&newid=[% newid %]&action=interdiff&format=raw">Raw Unified</a>
+ | Return to [% "$terms.bug $bugid" FILTER bug_link(bugid) FILTER none %]
|
[% END %]
[% END %]
@@ -287,12 +117,12 @@
[% IF context == "patch" %]
(<strong>Patch</strong> /
[% ELSE %]
- (<a href="[% PROCESS diffurl id=attachid %]&headers=[% headers FILTER url_quote %]">Patch</a> /
+ (<a href="[% PROCESS diffurl id=attachid %]&headers=[% headers FILTER uri %]">Patch</a> /
[% END %]
[% IF context == "file" %]
<strong>File</strong> /
[% ELSE %]
- <a href="[% PROCESS diffurl id=attachid %]&headers=[% headers FILTER url_quote %]&context=file">File</a> /
+ <a href="[% PROCESS diffurl id=attachid %]&headers=[% headers FILTER uri %]&context=file">File</a> /
[% END %]
[% IF context == "patch" || context == "file" %]
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/edit.html.tmpl
index 1b00df9..95ad4d3 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/edit.html.tmpl
@@ -17,6 +17,7 @@
#
# Contributor(s): Myk Melez <myk@mozilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
#%]
[% PROCESS global/variables.none.tmpl %]
@@ -29,142 +30,23 @@
Attachment [% attachment.id %] Details for
[%+ "$terms.Bug ${attachment.bug_id}" FILTER bug_link(attachment.bug_id) FILTER none %]
[% END %]
-[% subheader = BLOCK %][% bugsummary FILTER html %][% END %]
+[% subheader = BLOCK %][% attachment.bug.short_desc FILTER html %][% END %]
[% PROCESS global/header.html.tmpl
title = title
header = header
subheader = subheader
doc_section = "attachments.html"
+ javascript_urls = ['js/attachment.js', 'js/field.js']
+ style_urls = ['skins/standard/attachment.css']
+ yui = [ 'autocomplete' ]
+ bodyclasses = "no_javascript"
%]
[%# No need to display the Diff button and iframe if the attachment is not a patch. %]
-[% patchviewerinstalled = (patchviewerinstalled && attachment.ispatch) %]
-
-<script type="text/javascript">
- <!--
- var prev_mode = 'raw';
- var current_mode = 'raw';
- var has_edited = 0;
- var has_viewed_as_diff = 0;
- function editAsComment()
- {
- switchToMode('edit');
- has_edited = 1;
- }
- function undoEditAsComment()
- {
- switchToMode(prev_mode);
- }
- function redoEditAsComment()
- {
- switchToMode('edit');
- }
-[% IF patchviewerinstalled %]
- function viewDiff()
- {
- switchToMode('diff');
-
- // If we have not viewed as diff before, set the view diff frame URL
- if (!has_viewed_as_diff) {
- var viewDiffFrame = document.getElementById('viewDiffFrame');
- viewDiffFrame.src =
- 'attachment.cgi?id=[% attachment.id %]&action=diff&headers=0';
- has_viewed_as_diff = 1;
- }
- }
-[% END %]
- function viewRaw()
- {
- switchToMode('raw');
- }
-
- function switchToMode(mode)
- {
- if (mode == current_mode) {
- alert('switched to same mode! This should not happen.');
- return;
- }
-
- // Switch out of current mode
- if (current_mode == 'edit') {
- hideElementById('editFrame');
- hideElementById('undoEditButton');
- } else if (current_mode == 'raw') {
- hideElementById('viewFrame');
-[% IF patchviewerinstalled %]
- hideElementById('viewDiffButton');
-[% END %]
- hideElementById(has_edited ? 'redoEditButton' : 'editButton');
- hideElementById('smallCommentFrame');
- } else if (current_mode == 'diff') {
-[% IF patchviewerinstalled %]
- hideElementById('viewDiffFrame');
-[% END %]
- hideElementById('viewRawButton');
- hideElementById(has_edited ? 'redoEditButton' : 'editButton');
- hideElementById('smallCommentFrame');
- }
-
- // Switch into new mode
- if (mode == 'edit') {
- showElementById('editFrame');
- showElementById('undoEditButton');
- } else if (mode == 'raw') {
- showElementById('viewFrame');
-[% IF patchviewerinstalled %]
- showElementById('viewDiffButton');
-[% END %]
- showElementById(has_edited ? 'redoEditButton' : 'editButton');
- showElementById('smallCommentFrame');
- } else if (mode == 'diff') {
-[% IF patchviewerinstalled %]
- showElementById('viewDiffFrame');
-[% END %]
- showElementById('viewRawButton');
- showElementById(has_edited ? 'redoEditButton' : 'editButton');
- showElementById('smallCommentFrame');
- }
-
- prev_mode = current_mode;
- current_mode = mode;
- }
-
- function hideElementById(id)
- {
- var elm = document.getElementById(id);
- if (elm) {
- elm.style.display = 'none';
- }
- }
-
- function showElementById(id, val)
- {
- var elm = document.getElementById(id);
- if (elm) {
- if (!val) val = 'inline';
- elm.style.display = val;
- }
- }
-
- function normalizeComments()
- {
- // Remove the unused comment field from the document so its contents
- // do not get transmitted back to the server.
-
- var small = document.getElementById('smallCommentFrame');
- var big = document.getElementById('editFrame');
- if ( (small) && (small.style.display == 'none') )
- {
- small.parentNode.removeChild(small);
- }
- if ( (big) && (big.style.display == 'none') )
- {
- big.parentNode.removeChild(big);
- }
- }
- //-->
-</script>
+[% use_patchviewer = (feature_enabled('patch_viewer') && attachment.ispatch) %]
+[% can_edit = attachment.validate_can_edit %]
+[% editable_or_hide = can_edit ? "" : " bz_hidden_option" %]
<form method="post" action="attachment.cgi" onsubmit="normalizeComments();">
<input type="hidden" name="id" value="[% attachment.id %]">
@@ -175,163 +57,254 @@
<input type="hidden" name="token" value="[% issue_hash_token([attachment.id, attachment.modification_time]) FILTER html %]">
[% END %]
- <table class="attachment_info" width="100%">
-
- <tr>
- <td width="25%">
- <small>
- <b><label for="description">Description</label>:</b><br>
+ <div id="attachment_info" class="attachment_info [% IF can_edit %] edit[% ELSE %] read[% END%]">
+ <div id="attachment_attributes">
+ <div id="attachment_information_read_only" class="[% "bz_private" IF attachment.isprivate %]">
+ <div class="title">
+ [% "[patch]" IF attachment.ispatch%]
+ <span class="[% "bz_obsolete" IF attachment.isobsolete %]" title="[% "obsolete" IF attachment.isobsolete %]">
+ [% attachment.description FILTER html %]
+ </span>
+ [% IF can_edit %]
+ <span class="bz_edit">(<a href="javascript:toggle_attachment_details_visibility()">edit details</a>)</span>
+ [% END %]
+ </div>
+ <div class="details">
+ [% attachment.filename FILTER html %] ([% attachment.contenttype FILTER html %]),
+ [% IF attachment.datasize %]
+ [%+ attachment.datasize FILTER unitconvert %]
+ [% ELSE %]
+ <em>deleted</em>
+ [% END %], created by [%+ INCLUDE global/user.html.tmpl who = attachment.attacher %]
+ [% IF attachment.isprivate %];
+ <span class="bz_private">only visible to <strong>[% Param('insidergroup') FILTER html %]</strong> members</span>
+ [% END %]
+ </div>
+ </div>
+ <div id="attachment_information_edit">
+ <span class="bz_hide">
+ (<a href="javascript:toggle_attachment_details_visibility();">hide</a>)
+ </span>
+ <div id="attachment_description">
+ <label for="description">Description:</label>
[% INCLUDE global/textarea.html.tmpl
id = 'description'
name = 'description'
minrows = 3
cols = 25
wrap = 'soft'
+ classes = 'block' _ editable_or_hide
defaultcontent = attachment.description
- %]<br>
+ %]
+ </div>
- [% IF attachment.isurl %]
- <input type="hidden" name="filename"
- value="[% attachment.filename FILTER html %]">
- <input type="hidden" name="contenttypeentry"
- value="[% attachment.contenttype FILTER html %]">
- [% ELSE %]
- <b><label for="filename">Filename</label>:</b><br>
- <input type="text" size="20" id="filename" name="filename"
- value="[% attachment.filename FILTER html %]"><br>
- <b>Size:</b>
- [% IF attachment.datasize %]
- [%+ attachment.datasize FILTER unitconvert %]
- [% ELSE %]
- <em>deleted</em>
- [% END %]<br>
+ <div id="attachment_filename">
+ <label for="filename">Filename:</label>
+ <input type="text" size="20" class="text block[% editable_or_hide %]"
+ id="filename" name="filename"
+ value="[% attachment.filename FILTER html %]">
+ </div>
- <b><label for="contenttypeentry">MIME Type</label>:</b><br>
- <input type="text" size="20"
+ <div id="attachment_mimetype">
+ <label for="contenttypeentry">MIME Type:</label>
+ <input type="text" size="20" class="text block[% editable_or_hide %]"
id="contenttypeentry" name="contenttypeentry"
- value="[% attachment.contenttype FILTER html %]"><br>
+ value="[% attachment.contenttype FILTER html %]">
+ </div>
+
+ <div id="attachment_creator">
+ <span class="label">Creator:</span>
+ [%+ INCLUDE global/user.html.tmpl who = attachment.attacher %]
+ </div>
+
+ <div id="attachment_size">
+ <span class="label">Size:</span>
+ [% IF attachment.datasize %]
+ [%+ attachment.datasize FILTER unitconvert %]
+ [% ELSE %]
+ <em>deleted</em>
+ [% END %]
+ </div>
- <input type="checkbox" id="ispatch" name="ispatch" value="1"
- [%+ 'checked="checked"' IF attachment.ispatch %]>
- <label for="ispatch">patch</label>
+ <div id="attachment_ispatch">
+ <input type="checkbox" id="ispatch" name="ispatch" value="1"
+ [%+ 'checked="checked"' IF attachment.ispatch %]>
+ <label for="ispatch">patch</label>
+ </div>
+
+ <div class="readonly">
+ <div class="checkboxes">
+ <div id="attachment_isobsolete">
+ <input type="checkbox" id="isobsolete" name="isobsolete" value="1"
+ [%+ 'checked="checked"' IF attachment.isobsolete %]>
+ <label for="isobsolete">obsolete</label>
+ </div>
+
+ [% IF user.is_insider %]
+ <div id="attachment_isprivate">
+ <input type="checkbox" id="isprivate" name="isprivate" value="1"
+ [%+ 'checked="checked"' IF attachment.isprivate %]>
+ [% IF can_edit %]
+ <label for="isprivate">private (only visible to
+ <strong>[% Param('insidergroup') FILTER html %]</strong>)
+ </label>
+ [% ELSE %]
+ <span class="label">Is Private:</span>
+ [%+ attachment.isprivate ? "yes" : "no" %]
+ [% END %]
+ </div>
+ [% END %]
+ </div>
+ </div>
+ </div>
+
+ <div id="attachment_view_window">
+ [% IF !attachment.datasize %]
+ <div><b>The content of this attachment has been deleted.</b></div>
+ [% ELSIF !Param("allow_attachment_display") %]
+ <div id="view_disabled">
+ <p><b>
+ The attachment is not viewable in your browser due to security
+ restrictions enabled by your [% terms.Bugzilla %] administrator.
+ </b></p>
+ <p><b>
+ In order to view the attachment, you first have to
+ <a href="attachment.cgi?id=[% attachment.id %]">download it</a>.
+ </b></p>
+ </div>
+ [% ELSIF attachment.is_viewable %]
+ <div>
+ [% INCLUDE global/textarea.html.tmpl
+ id = 'editFrame'
+ name = 'comment'
+ classes = 'bz_default_hidden'
+ minrows = 10
+ cols = 80
+ wrap = 'soft'
+ disabled = 'disabled'
+ defaultcontent = (attachment.contenttype.match('^text\/')) ?
+ attachment.data.replace('(.*\n|.+)', '>$1') : undef
+ %]
+ [% IF attachment.contenttype == 'text/plain' AND is_safe_url(attachment.data) %]
+ <p>
+ <a href="[% attachment.data FILTER html %]">
+ [% IF attachment.datasize < 120 %]
+ [% attachment.data FILTER html %]
+ [% ELSE %]
+ [% attachment.data FILTER truncate(80) FILTER html %]
+ ...
+ [% attachment.data.match('.*(.{20})$').0 FILTER html %]
+ [% END %]
+ </a>
+ </p>
+ [% ELSIF attachment.contenttype == "text/html" %]
+ [%# For security reasons (clickjacking, embedded scripts), we never
+ # render HTML pages from here. The source code is displayed instead. %]
+ [% INCLUDE global/textarea.html.tmpl
+ id = 'viewFrame'
+ minrows = 10
+ cols = 80
+ defaultcontent = attachment.data
+ readonly = 'readonly'
+ %]
+ [% ELSE %]
+ <iframe id="viewFrame" src="attachment.cgi?id=[% attachment.id %]">
+ <b>You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
+ <a href="attachment.cgi?id=[% attachment.id %]">View the attachment on a separate page</a>.</b>
+ </iframe>
+ [% END %]
+ <script type="text/javascript">
+ <!--
+ var patchviewerinstalled = 0;
+ var attachment_id = [% attachment.id %];
+ if (typeof document.getElementById == "function") {
+ [% IF use_patchviewer %]
+ var patchviewerinstalled = 1;
+ document.write('<iframe id="viewDiffFrame" class="bz_default_hidden"><\/iframe>');
+ [% END %]
+ [% IF user.id %]
+ document.write('<button type="button" id="editButton" onclick="editAsComment(patchviewerinstalled);">Edit Attachment As Comment<\/button>');
+ document.write('<button type="button" id="undoEditButton" onclick="undoEditAsComment(patchviewerinstalled);" class="bz_default_hidden">Undo Edit As Comment<\/button>');
+ document.write('<button type="button" id="redoEditButton" onclick="redoEditAsComment(patchviewerinstalled);" class="bz_default_hidden">Redo Edit As Comment<\/button>');
+ var editFrame = document.getElementById('editFrame');
+ if (editFrame) {
+ editFrame.disabled = false;
+ }
+ [% END %]
+ [% IF use_patchviewer %]
+ document.write('<button type="button" id="viewDiffButton" onclick="viewDiff(attachment_id, patchviewerinstalled);">View Attachment As Diff<\/button>');
+ [% END %]
+ document.write('<button type="button" id="viewRawButton" onclick="viewRaw(patchviewerinstalled);" class="bz_default_hidden">View Attachment As Raw<\/button>');
+ }
+ //-->
+ </script>
+ </div>
+ [% ELSE %]
+ <div id="noview">
+ <p><b>
+ Attachment is not viewable in your browser because its MIME type
+ ([% attachment.contenttype FILTER html %]) is not one that your browser is
+ able to display.
+ </b></p>
+ <p><b>
+ <a href="attachment.cgi?id=[% attachment.id %]">Download the attachment</a>.
+ </b></p>
+ </div>
[% END %]
- <input type="checkbox" id="isobsolete" name="isobsolete" value="1"
- [%+ 'checked="checked"' IF attachment.isobsolete %]>
- <label for="isobsolete">obsolete</label>
- [% IF (Param("insidergroup") && user.in_group(Param("insidergroup"))) %]
- <input type="checkbox" id="isprivate" name="isprivate" value="1"
- [% " checked" IF attachment.isprivate %]>
- <label for="isprivate">private</label><br>
- [% END %]
- <br>
- </small>
-
- [% IF flag_types.size > 0 %]
- [% PROCESS "flag/list.html.tmpl" bug_id = attachment.bug_id
- attach_id = attachment.id %]<br>
- [% END %]
-
- <div id="smallCommentFrame">
- <b><small><label for="comment">Comment</label> (on the
- [%+ terms.bug %]):</small></b><br>
+ </div>
+ <div id="attachment_comments_and_flags">
+ [% IF user.id %]
+ <div id="smallCommentFrame" >
+ <label for="comment">Comment (on the [% terms.bug %]):</label>
+ [% classNames = 'block' %]
+ [% classNames = "$classes bz_private" IF attachment.isprivate %]
[% INCLUDE global/textarea.html.tmpl
id = 'comment'
name = 'comment'
- minrows = 5
- cols = 25
+ minrows = 10
+ cols = 80
wrap = 'soft'
- %]<br>
+ classes = classNames
+ %]
+ </div>
+ [% END %]
+ <div id="attachment_flags">
+ [% IF attachment.flag_types.size > 0 %]
+ [% PROCESS "flag/list.html.tmpl" flag_types = attachment.flag_types
+ read_only_flags = !can_edit
+ %]
+
+ [% END %]
</div>
- <input type="submit" value="Submit" id="update"><br><br>
- <strong>Actions:</strong>
- <a href="attachment.cgi?id=[% attachment.id %]">View</a>
- [% IF attachment.ispatch && patchviewerinstalled %]
- | <a href="attachment.cgi?id=[% attachment.id %]&action=diff">Diff</a>
- [% END %]
- [% IF Param("allow_attachment_deletion")
- && user.groups.admin
- && attachment.datasize > 0 %]
- | <a href="attachment.cgi?id=[% attachment.id %]&action=delete">Delete</a>
- [% END %]
- </td>
+ [% Hook.process('form_before_submit') %]
- [% IF !attachment.datasize %]
- <td width="75%"><b>The content of this attachment has been deleted.</b></td>
- [% ELSIF attachment.isurl %]
- <td width="75%">
- <a href="[% attachment.data FILTER html %]">
- [% IF attachment.datasize < 120 %]
- [% attachment.data FILTER html %]
- [% ELSE %]
- [% attachment.data FILTER truncate(80) FILTER html %]
- ...
- [% attachment.data.match(".*(.{20})$").0 FILTER html %]
- [% END %]
- </a>
- </td>
- [% ELSIF !Param("allow_attachment_display") %]
- <td id="view_disabled" width="50%">
- <p><b>
- The attachment is not viewable in your browser due to security
- restrictions enabled by [% terms.Bugzilla %].
- </b></p>
- <p><b>
- In order to view the attachment, you first have to
- <a href="attachment.cgi?id=[% attachment.id %]">download it</a>.
- </b></p>
- </td>
- [% ELSIF attachment.is_viewable %]
- <td width="75%">
- [% INCLUDE global/textarea.html.tmpl
- id = 'editFrame'
- name = 'comment'
- style = 'height: 400px; width: 100%; display: none'
- minrows = 10
- cols = 80
- wrap = 'soft'
- defaultcontent = (attachment.contenttype.match('^text\/')) ?
- attachment.data.replace('(.*\n|.+)', '>$1') : undef
- %]
- <iframe id="viewFrame" src="attachment.cgi?id=[% attachment.id %]" style="height: 400px; width: 100%;">
- <b>You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
- <a href="attachment.cgi?id=[% attachment.id %]">View the attachment on a separate page</a>.</b>
- </iframe>
- <script type="text/javascript">
- <!--
- if (typeof document.getElementById == "function") {
-[% IF patchviewerinstalled %]
- document.write('<iframe id="viewDiffFrame" style="height: 400px; width: 100%; display: none;"><\/iframe>');
-[% END %]
- document.write('<button type="button" id="editButton" onclick="editAsComment();">Edit Attachment As Comment<\/button>');
- document.write('<button type="button" id="undoEditButton" onclick="undoEditAsComment();" style="display: none;">Undo Edit As Comment<\/button>');
- document.write('<button type="button" id="redoEditButton" onclick="redoEditAsComment();" style="display: none;">Redo Edit As Comment<\/button>');
-[% IF patchviewerinstalled %]
- document.write('<button type="button" id="viewDiffButton" onclick="viewDiff();">View Attachment As Diff<\/button>');
-[% END %]
- document.write('<button type="button" id="viewRawButton" onclick="viewRaw();" style="display: none;">View Attachment As Raw<\/button>');
- }
- //-->
- </script>
- </td>
- [% ELSE %]
- <td id="noview" width="50%">
- <p><b>
- Attachment is not viewable in your browser because its MIME type
- ([% attachment.contenttype FILTER html %]) is not one that your browser is
- able to display.
- </b></p>
- <p><b>
- <a href="attachment.cgi?id=[% attachment.id %]">Download the attachment</a>.
- </b></p>
- </td>
- [% END %]
+ [% IF user.id %]
+ <div id="update_container">
+ <input type="submit" value="Submit" id="update">
+ </div>
+ [% END %]
+ </div>
+ </div>
+ </div>
+</form>
- </tr>
+<div id="attachment_actions">
+ <span class="label">Actions:</span>
+ <a href="attachment.cgi?id=[% attachment.id %]">View</a>
+ [% IF use_patchviewer %]
+ | <a href="attachment.cgi?id=[% attachment.id %]&action=diff">Diff</a>
+ [% END %]
+ [% IF Param("allow_attachment_deletion")
+ && user.in_group('admin')
+ && attachment.datasize > 0 %]
+ | <a href="attachment.cgi?id=[% attachment.id %]&action=delete">Delete</a>
+ [% END %]
+ [% Hook.process('action') %]
+</div>
- </table>
-
- Attachments on this [% terms.Bug %]:
+<div id="attachment_list">
+ Attachments on [% "$terms.bug ${attachment.bug_id}" FILTER bug_link(attachment.bug_id) FILTER none %]:
[% FOREACH a = attachments %]
[% IF a == attachment.id %]
[%+ a %]
@@ -340,9 +313,15 @@
[% END %]
[% " |" UNLESS loop.last() %]
[% END %]
-
-</form>
-
-<br>
+</div>
+[% IF can_edit %]
+ <script type="text/javascript">
+ <!--
+ YAHOO.util.Dom.removeClass( document.body, "no_javascript" );
+ toggle_attachment_details_visibility( );
+ -->
+ </script>
+[% END %]
+[% Hook.process('end') %]
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/list.html.tmpl
index 546041e..fa8e477 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/list.html.tmpl
@@ -19,41 +19,46 @@
# Frédéric Buclin <LpSolit@gmail.com>
#%]
-<script type="text/javascript">
- <!--
- function toggle_display(link) {
- var table = document.getElementById("attachment_table");
- var rows = table.getElementsByTagName("tr");
- var originalHeight = table.offsetHeight; // Store current height for scrolling
+[% RETURN UNLESS attachments.size || Param("maxattachmentsize") || Param("maxlocalattachment") %]
- var toggle;
- if (link.innerHTML == "Show Obsolete") {
- toggle = ""; // This should be 'table-row', but IE 6 doesn't understand it.
- link.innerHTML = "Hide Obsolete";
- }
- else {
- toggle = "none";
- link.innerHTML = "Show Obsolete";
- }
+<script type="text/javascript">
+<!--
+function toggle_display(link) {
+ var table = document.getElementById("attachment_table");
+ var view_all = document.getElementById("view_all");
+ var hide_obsolete_url_parameter = "&hide_obsolete=1";
+ // Store current height for scrolling later
+ var originalHeight = table.offsetHeight;
+ var rows = YAHOO.util.Dom.getElementsByClassName(
+ 'bz_tr_obsolete', 'tr', table);
for (var i = 0; i < rows.length; i++) {
- if (rows[i].className.match('bz_tr_obsolete'))
- rows[i].style.display = toggle;
+ bz_toggleClass(rows[i], 'bz_default_hidden');
+ }
+
+ if (YAHOO.util.Dom.hasClass(rows[0], 'bz_default_hidden')) {
+ link.innerHTML = "Show Obsolete";
+ view_all.href = view_all.href + hide_obsolete_url_parameter
+ }
+ else {
+ link.innerHTML = "Hide Obsolete";
+ view_all.href = view_all.href.replace(hide_obsolete_url_parameter,"");
}
var newHeight = table.offsetHeight;
+ // This scrolling makes the window appear to not move at all.
window.scrollBy(0, newHeight - originalHeight);
return false;
- }
- //-->
+}
+//-->
</script>
<br>
<table id="attachment_table" cellspacing="0" cellpadding="4">
- <tr>
+ <tr id="a0">
<th colspan="[% show_attachment_flags ? 3 : 2 %]" align="left">
- <a name="a0" id="a0">Attachments</a>
+ Attachments
</th>
</tr>
@@ -66,11 +71,15 @@
[% IF attachment.isobsolete %]
[% obsolete_attachments = obsolete_attachments + 1 %]
[% END %]
- <tr class="[% "bz_private" IF attachment.isprivate %][%-%]
- [%+ "bz_tr_obsolete" IF attachment.isobsolete %]">
+ <tr id="a[% count %]" class="[% "bz_contenttype_" _ attachment.contenttype
+ FILTER css_class_quote %]
+ [% " bz_patch" IF attachment.ispatch %]
+ [% " bz_private" IF attachment.isprivate %]
+ [% " bz_tr_obsolete bz_default_hidden"
+ IF attachment.isobsolete %]">
<td valign="top">
[% IF attachment.datasize %]
- <a name="a[% count %]" href="attachment.cgi?id=[% attachment.id %]"
+ <a href="attachment.cgi?id=[% attachment.id %]"
title="View the content of the attachment">
[% END %]
<b>[% attachment.description FILTER html FILTER obsolete(attachment.isobsolete) %]</b>
@@ -81,8 +90,6 @@
([% attachment.datasize FILTER unitconvert %],
[% IF attachment.ispatch %]
patch)
- [% ELSIF attachment.isurl %]
- url)
[% ELSE %]
[%+ attachment.contenttype FILTER html %])
[% END %]
@@ -95,10 +102,7 @@
title="Go to the comment associated with the attachment">
[%- attachment.attached FILTER time %]</a>,
- <a href="mailto:[% attachment.attacher.email FILTER html %]"
- title="Write an email to the creator of the attachment">
- [% attachment.attacher.name || attachment.attacher.login FILTER html %]
- </a>
+ [% INCLUDE global/user.html.tmpl who = attachment.attacher %]
</span>
</td>
@@ -108,10 +112,22 @@
<i>no flags</i>
[% ELSE %]
[% FOREACH flag = attachment.flags %]
- [% flag.setter.nick FILTER html %]:
+ [% IF user.id %]
+ <span title="[% flag.setter.identity FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
+ [% ELSIF flag.setter.name %]
+ <span title="[% flag.setter.name FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
+ [% ELSE %]
+ [% flag.setter.nick FILTER html %]:
+ [% END %]
[%+ flag.type.name FILTER html FILTER no_break %][% flag.status %]
[%+ IF flag.status == "?" && flag.requestee %]
- ([% flag.requestee.nick FILTER html %])
+ [% IF user.id %]
+ (<span title="[% flag.requestee.identity FILTER html %]">[% flag.requestee.nick FILTER html %]</span>)
+ [% ELSIF flag.requestee.name %]
+ (<span title="[% flag.requestee.name FILTER html %]">[% flag.requestee.nick FILTER html %]</span>)
+ [% ELSE %]
+ ([% flag.requestee.nick FILTER html %])
+ [% END %]
[% END %]<br>
[% END %]
[% END %]
@@ -120,7 +136,7 @@
<td valign="top">
<a href="attachment.cgi?id=[% attachment.id %]&action=edit">Details</a>
- [% IF attachment.ispatch && patchviewerinstalled %]
+ [% IF attachment.ispatch && feature_enabled('patch_viewer') %]
| <a href="attachment.cgi?id=[% attachment.id %]&action=diff">Diff</a>
[% END %]
[% Hook.process("action") %]
@@ -134,15 +150,20 @@
[% IF attachments.size %]
<span class="bz_attach_view_hide">
[% IF obsolete_attachments %]
- <a href="#a0" onClick="return toggle_display(this);">Hide Obsolete</a> ([% obsolete_attachments %])
+ <a href="#a0" onclick="return toggle_display(this);">Show
+ Obsolete</a> ([% obsolete_attachments %])
[% END %]
[% IF Param("allow_attachment_display") %]
- <a href="attachment.cgi?bugid=[% bugid %]&action=viewall">View All</a>
+ <a id="view_all" href="attachment.cgi?bugid=
+ [%- bugid %]&action=viewall
+ [%- "&hide_obsolete=1" IF obsolete_attachments %]">View All</a>
[% END %]
</span>
[% END %]
- <a href="attachment.cgi?bugid=[% bugid %]&action=enter">Add an attachment</a>
- (proposed patch, testcase, etc.)
+ [% IF Param("maxattachmentsize") || Param("maxlocalattachment") %]
+ <a href="attachment.cgi?bugid=[% bugid %]&action=enter">Add an attachment</a>
+ (proposed patch, testcase, etc.)
+ [% END %]
</td>
</tr>
</table>
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/midair.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/midair.html.tmpl
index f0883b5..f7f40fd 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/midair.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/midair.html.tmpl
@@ -51,7 +51,7 @@
<p>
Your comment was:<br>
<blockquote><pre class="bz_comment_text">
- [% cgi.param("comment") FILTER wrap_comment FILTER html %]
+ [% cgi.param("comment") FILTER html %]
</pre></blockquote>
</p>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/show-multiple.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/show-multiple.html.tmpl
index 36088c9..91768c0 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/show-multiple.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/show-multiple.html.tmpl
@@ -21,7 +21,7 @@
[% PROCESS global/variables.none.tmpl %]
[% filtered_summary = bugsummary FILTER html %]
[% header = BLOCK %]View All Attachments for
- [%+ "$terms.Bug $bug.bug_id" FILTER bug_link(bug.bug_id) FILTER none %][% END %]
+ [%+ "$terms.Bug $bug.id" FILTER bug_link(bug) FILTER none %][% END %]
[% title = BLOCK %]
View All Attachments for [% terms.Bug %] [%+ bug.bug_id FILTER html %]
@@ -31,8 +31,14 @@
title = title
header = header
subheader = filtered_summary
+ style_urls = ['skins/standard/attachment.css']
%]
-
+[% IF hide_obsolete %]
+ <div id="hidden_obsolete_message">
+ Obsolete attachments are hidden. To view all attachments (including obsolete)
+ <a href="attachment.cgi?bugid=[% bug.id FILTER html %]&action=viewall">click here</a>.
+ </div>
+[% END %]
<br>
[% FOREACH a = attachments %]
@@ -82,10 +88,22 @@
</table>
[% IF a.is_viewable %]
- <iframe src="attachment.cgi?id=[% a.id %]" width="75%" height="350">
- <b>You cannot view the attachment on this page because your browser does not support IFRAMEs.
- <a href="attachment.cgi?id=[% a.id %]">View the attachment on a separate page</a>.</b>
- </iframe>
+ [% IF a.contenttype == "text/html" %]
+ [%# For security reasons (clickjacking, embedded scripts), we never
+ # render HTML pages from here. The source code is displayed instead. %]
+ [% INCLUDE global/textarea.html.tmpl
+ minrows = 10
+ cols = 80
+ defaultcontent = a.data
+ readonly = 'readonly'
+ classes = 'viewall_frame'
+ %]
+ [% ELSE %]
+ <iframe src="attachment.cgi?id=[% a.id %]" class="viewall_frame">
+ <b>You cannot view the attachment on this page because your browser does not support IFRAMEs.
+ <a href="attachment.cgi?id=[% a.id %]">View the attachment on a separate page</a>.</b>
+ </iframe>
+ [% END %]
[% ELSE %]
<p><b>
Attachment cannot be viewed because its MIME type is not text/*, image/*, or application/vnd.mozilla.*.
diff --git a/Websites/bugs.webkit.org/template/en/default/attachment/updated.html.tmpl b/Websites/bugs.webkit.org/template/en/default/attachment/updated.html.tmpl
index bc22b46..9a74f5c 100644
--- a/Websites/bugs.webkit.org/template/en/default/attachment/updated.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/attachment/updated.html.tmpl
@@ -25,28 +25,10 @@
[% PROCESS global/variables.none.tmpl %]
[% bug = bugs.0 %]
-[% filtered_desc = bug.short_desc FILTER html %]
-[% filtered_timestamp = bug.delta_ts FILTER time %]
-[% bodyclasses = ['bz_bug',
- "bz_status_$bug.bug_status",
- "bz_component_$bug.component",
- "bz_bug_$bug.bug_id"
- ]
-%]
-[% FOREACH group = bug.groups_in %]
- [% bodyclasses.push("bz_group_$group.name") %]
-[% END %]
+[% PROCESS "bug/show-header.html.tmpl" %]
[% PROCESS global/header.html.tmpl
title = "Changes Submitted to Attachment $attachment.id of $terms.Bug $attachment.bug_id"
- header = "$terms.Bug $attachment.bug_id"
- subheader = filtered_desc
- header_addl_info = "Last modified: $filtered_timestamp"
- bodyclasses = bodyclasses
- javascript_urls = [ "js/util.js", "js/field.js",
- "js/yui/yahoo-dom-event.js", "js/yui/calendar.js" ]
- style_urls = [ "skins/standard/yui/calendar.css", "skins/standard/show_bug.css" ]
- doc_section = "bug_page.html"
%]
<dl>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/activity/show.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/activity/show.html.tmpl
index a457df0..67ac689 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/activity/show.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/activity/show.html.tmpl
@@ -35,14 +35,14 @@
%]
<p>
- [% "Back to $terms.bug $bug.bug_id" FILTER bug_link(bug.bug_id) FILTER none %]
+ [% "Back to $terms.bug $bug.bug_id" FILTER bug_link(bug) FILTER none %]
</p>
[% PROCESS bug/activity/table.html.tmpl %]
[% IF operations.size > 0 %]
<p>
- [% "Back to $terms.bug $bug.bug_id" FILTER bug_link(bug.bug_id) FILTER none %]
+ [% "Back to $terms.bug $bug.bug_id" FILTER bug_link(bug) FILTER none %]
</p>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/activity/table.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/activity/table.html.tmpl
index b676eb1..a9aca0a 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/activity/table.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/activity/table.html.tmpl
@@ -17,6 +17,7 @@
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
# David D. Kilzer <ddkilzer@kilzer.net>
+ # Reed Loden <reed@reedloden.com>
#%]
[%# INTERFACE:
@@ -33,7 +34,6 @@
# it was affected by an old Bugzilla bug.)
#%]
-[% PROCESS global/variables.none.tmpl %]
[% PROCESS "global/field-descs.none.tmpl" %]
[% PROCESS bug/time.html.tmpl %]
@@ -61,7 +61,7 @@
[% FOREACH operation = operations %]
<tr>
<td rowspan="[% operation.changes.size %]" valign="top">
- [% operation.who FILTER html %]
+ [% operation.who FILTER email FILTER html %]
</td>
<td rowspan="[% operation.changes.size %]" valign="top">
[% operation.when FILTER time %]
@@ -73,50 +73,47 @@
<a href="attachment.cgi?id=[% change.attachid %]">
Attachment #[% change.attachid %]</a>
[% END %]
- [%+ change.field %]
- </td>
- <td>
- [% IF change.removed.defined %]
- [% IF change.fieldname == 'estimated_time' ||
- change.fieldname == 'remaining_time' ||
- change.fieldname == 'work_time' %]
- [% PROCESS formattimeunit time_unit=change.removed %]
- [% ELSIF change.fieldname == 'bug_status' %]
- [% get_status(change.removed) FILTER html %]
- [% ELSIF change.fieldname == 'resolution' %]
- [% get_resolution(change.removed) FILTER html %]
- [% ELSIF change.fieldname == 'blocked' ||
- change.fieldname == 'dependson' %]
- [% change.removed FILTER bug_list_link FILTER none %]
- [% ELSE %]
- [% change.removed FILTER html %]
- [% END %]
+ [% IF change.comment.defined %]
+ [% comment_desc = field_descs.${change.fieldname} %]
+ [% comment_num = "Comment $change.comment.count" FILTER bug_link(bug.bug_id, comment_num => change.comment.count) %]
+ [% comment_desc.replace('^(Comment )?', "$comment_num ") FILTER none %]
[% ELSE %]
-
+ [%+ field_descs.${change.fieldname} FILTER html %]
[% END %]
</td>
- <td>
- [% IF change.added.defined %]
- [% IF change.fieldname == 'estimated_time' ||
- change.fieldname == 'remaining_time' ||
- change.fieldname == 'work_time' %]
- [% PROCESS formattimeunit time_unit=change.added %]
- [% ELSIF change.fieldname == 'bug_status' %]
- [% get_status(change.added) FILTER html %]
- [% ELSIF change.fieldname == 'resolution' %]
- [% get_resolution(change.added) FILTER html %]
- [% ELSIF change.fieldname == 'blocked' ||
- change.fieldname == 'dependson' %]
- [% change.added FILTER bug_list_link FILTER none %]
- [% ELSE %]
- [% change.added FILTER html %]
- [% END %]
- [% ELSE %]
-
- [% END %]
- </td>
+ [% PROCESS change_column change_type = change.removed %]
+ [% PROCESS change_column change_type = change.added %]
[% END %]
</tr>
[% END %]
</table>
+[% ELSE %]
+ <p>
+ No changes have been made to this [% terms.bug %] yet.
+ </p>
+[% END %]
+
+[% BLOCK change_column %]
+ <td>
+ [% IF change_type.defined %]
+ [% IF change.fieldname == 'estimated_time' ||
+ change.fieldname == 'remaining_time' ||
+ change.fieldname == 'work_time' %]
+ [% PROCESS formattimeunit time_unit=change_type %]
+ [% ELSIF change.fieldname == 'blocked' ||
+ change.fieldname == 'dependson' %]
+ [% change_type FILTER bug_list_link FILTER none %]
+ [% ELSIF change.fieldname == 'assigned_to' ||
+ change.fieldname == 'reporter' ||
+ change.fieldname == 'qa_contact' ||
+ change.fieldname == 'cc' ||
+ change.fieldname == 'flagtypes.name' %]
+ [% display_value(change.fieldname, change_type) FILTER email FILTER html %]
+ [% ELSE %]
+ [% display_value(change.fieldname, change_type) FILTER html %]
+ [% END %]
+ [% ELSE %]
+
+ [% END %]
+ </td>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/comments.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/comments.html.tmpl
index bf9326e..170c693 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/comments.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/comments.html.tmpl
@@ -22,78 +22,10 @@
[% PROCESS bug/time.html.tmpl %]
- <script type="text/javascript">
- <!--
- function updateCommentPrivacy(checkbox, id) {
- var comment_elem = document.getElementById('comment_text_'+id).parentNode;
- if (checkbox.checked) {
- if (!comment_elem.className.match('bz_private')) {
- comment_elem.className = comment_elem.className.concat(' bz_private');
- }
- }
- else {
- comment_elem.className =
- comment_elem.className.replace(/(\s*|^)bz_private(\s*|$)/, '$2');
- }
- }
-
- /* The functions below expand and collapse comments */
-
- function toggle_comment_display(link, comment_id) {
- var comment = document.getElementById('comment_text_' + comment_id);
- var re = new RegExp(/\bcollapsed\b/);
- if (comment.className.match(re))
- expand_comment(link, comment);
- else
- collapse_comment(link, comment);
- }
-
- function toggle_all_comments(action) {
- var num_comments = [% comments.size FILTER html %];
-
- // If for some given ID the comment doesn't exist, this doesn't mean
- // there are no more comments, but that the comment is private and
- // the user is not allowed to view it.
-
- for (var id = 0; id < num_comments; id++) {
- var comment = document.getElementById('comment_text_' + id);
- if (!comment)
- continue;
-
- var link = document.getElementById('comment_link_' + id);
- if (action == 'collapse')
- collapse_comment(link, comment);
- else
- expand_comment(link, comment);
- }
- }
-
- function collapse_comment(link, comment) {
- link.innerHTML = "(+)";
- link.title = "Expand the comment.";
- comment.className = "collapsed";
- }
-
- function expand_comment(link, comment) {
- link.innerHTML = "(-)";
- link.title = "Collapse the comment";
- comment.className = "";
- }
-
- /* This way, we are sure that browsers which do not support JS
- * won't display this link */
-
- function addCollapseLink(count) {
- document.write(' <a href="#" id="comment_link_' + count +
- '" onclick="toggle_comment_display(this, ' + count +
- '); return false;" title="Collapse the comment.">(-)</a> ');
- }
- //-->
- </script>
-
+<script src="[% 'js/comments.js' FILTER mtime %]" type="text/javascript">
+</script>
[% DEFAULT start_at = 0 mode = "show" %]
-[% isinsider = Param("insidergroup") && user.in_group(Param("insidergroup")) %]
[% sort_order = user.settings.comment_sort_order.value %]
[%# NOTE: (start_at > 0) means we came here from a midair collision,
@@ -120,11 +52,10 @@
[% END %]
[% END %]
-[% IF mode == "edit" %]
- <a href="#" onclick="toggle_all_comments('collapse'); return false;">Collapse All Comments</a> -
- <a href="#" onclick="toggle_all_comments('expand'); return false;">Expand All Comments</a>
- <hr>
-[% END %]
+<!-- This auto-sizes the comments and positions the collapse/expand links
+ to the right. -->
+<table class="bz_comment_table" cellpadding="0" cellspacing="0"><tr>
+<td>
[% FOREACH comment = comments %]
[% IF count >= start_at %]
@@ -134,73 +65,106 @@
[% count = count + increment %]
[% END %]
+[% IF user.settings.comment_box_position.value == "before_comments" && user.id %]
+ <div class="bz_add_comment">
+ <a href="#"
+ onclick="return goto_add_comments();">
+ Add Comment</a>
+ </div>
+[% END %]
+
[%# Note: this template is used in multiple places; if you use this hook,
# make sure you are aware of this fact.
#%]
[% Hook.process("aftercomments") %]
+</td>
+<td>
+ [% IF mode == "edit" %]
+ <ul class="bz_collapse_expand_comments">
+ <li><a href="#" onclick="toggle_all_comments('collapse');
+ return false;">Collapse All Comments</a></li>
+ <li><a href="#" onclick="toggle_all_comments('expand');
+ return false;">Expand All Comments</a></li>
+ [% IF user.settings.comment_box_position.value == "after_comments" && user.id %]
+ <li class="bz_add_comment"><a href="#"
+ onclick="return goto_add_comments('bug_status_bottom');">
+ Add Comment</a></li>
+ [% END %]
+ </ul>
+ [% END %]
+</td>
+</tr></table>
+
[%############################################################################%]
[%# Block for individual comments #%]
[%############################################################################%]
[% BLOCK a_comment %]
- [% IF NOT comment.isprivate || isinsider %]
- <div class="bz_comment[% " bz_private" IF comment.isprivate %]
+ [% RETURN IF comment.is_private AND ! user.is_insider %]
+ [% comment_text = comment.body_full %]
+ [% RETURN IF comment_text == '' AND (comment.work_time - 0) != 0 AND !user.is_timetracker %]
+
+ <div id="c[% count %]" class="bz_comment[% " bz_private" IF comment.is_private %]
[% " bz_comment_hilite" IF marks.$count %]
[% " bz_first_comment" IF count == description %]">
[% IF count == description %]
[% class_name = "bz_first_comment_head" %]
- [% comment_label = "" %]
- [% comment_link = "Description" %]
- [% decoration = "" %]
+ [% comment_label = "Description" %]
[% ELSE %]
[% class_name = "bz_comment_head" %]
- [% comment_label = "Comment" %]
- [% comment_link = "#" _ count %]
- [% decoration = '<span class="comment_rule">-------</span>' %]
+ [% comment_label = "Comment " _ count %]
[% END %]
- <span class="[% class_name FILTER html %]">
- [%# Do not filter decoration as it's a real HTML tag. No XSS risk. %]
- [% decoration FILTER none %]
- <i>[% comment_label FILTER html %]
- <a name="c[% count %]" href="show_bug.cgi?id=[% bug.bug_id %]#c[% count %]">
- [% comment_link FILTER html %]</a> From
- <span class="vcard">
- <a class="fn email" href="mailto:[% comment.author.email FILTER html %]">
- [% (comment.author.name || comment.author.login) FILTER html %]</a>
- </span>
- [% FOREACH group = comment.author.direct_group_membership %]
- [% NEXT UNLESS group.icon_url %]
- <img src="[% group.icon_url FILTER html %]"
- alt="[% group.name FILTER html %]"
- title="[% group.name FILTER html %] - [% group.description FILTER html %]">
- [% END %]
-
- [%+ comment.time FILTER time %]</i>
+ <div class="[% class_name FILTER html %]">
[% IF mode == "edit" %]
- <script type="text/javascript"><!--
- addCollapseLink([% count %]);
- addReplyLink([% count %], [% comment.id %]); //-->
- </script>
+ <span class="bz_comment_actions">
+ <script type="text/javascript"><!--
+ addReplyLink([% count %], [% comment.id %]);
+ addCollapseLink([% count %], 'Toggle comment display'); // -->
+ </script>
+ </span>
[% END %]
- [%+ decoration FILTER none %]
- </span>
- [% IF mode == "edit" && isinsider %]
- <i>
- <input type="hidden" value="1"
- name="defined_isprivate_[% comment.id %]">
- <input type="checkbox"
- name="isprivate_[% comment.id %]" value="1"
- id="isprivate_[% comment.id %]"
- onClick="updateCommentPrivacy(this, [% count %])"
- [% " checked=\"checked\"" IF comment.isprivate %]>
- <label for="isprivate_[% comment.id %]">Private</label>
- </i>
- [% END %]
- [% IF user.in_group(Param('timetrackinggroup')) &&
+ [% IF mode == "edit" && user.is_insider %]
+ <div class="bz_private_checkbox">
+ <input type="hidden" value="1"
+ name="defined_isprivate_[% comment.id %]">
+ <input type="checkbox"
+ name="isprivate_[% comment.id %]" value="1"
+ id="isprivate_[% comment.id %]"
+ onClick="updateCommentPrivacy(this, [% count %])"
+ [% " checked=\"checked\"" IF comment.is_private %]>
+ <label for="isprivate_[% comment.id %]">Private</label>
+ </div>
+ [% END %]
+
+ <span class="bz_comment_number">
+ <a
+ href="show_bug.cgi?id=[% bug.bug_id %]#c[% count %]">
+ [%- comment_label FILTER html %]</a>
+ </span>
+
+ <span class="bz_comment_user">
+ [% INCLUDE global/user.html.tmpl who = comment.author %]
+ </span>
+
+ <span class="bz_comment_user_images">
+ [% FOREACH group = comment.author.direct_group_membership %]
+ [% NEXT UNLESS group.icon_url %]
+ <img src="[% group.icon_url FILTER html %]"
+ alt="[% group.name FILTER html %]"
+ title="[% group.name FILTER html %] - [% group.description FILTER html %]">
+ [% END %]
+ </span>
+
+ <span class="bz_comment_time">
+ [%+ comment.creation_ts FILTER time %]
+ </span>
+ </div>
+
+ [% IF user.is_timetracker &&
(comment.work_time > 0 || comment.work_time < 0) %]
<br>
Additional hours worked:
@@ -210,15 +174,9 @@
[%# Don't indent the <pre> block, since then the spaces are displayed in the
# generated HTML
#%]
-[% IF comment.already_wrapped %]
- [% wrapped_comment = comment.body %]
-[% ELSE %]
- [% wrapped_comment = comment.body FILTER wrap_comment %]
-[% END %]
<pre class="bz_comment_text"
[% ' id="comment_text_' _ count _ '"' IF mode == "edit" %]>
- [%- wrapped_comment FILTER quoteUrls(bug.bug_id) -%]
+ [%- comment_text FILTER quoteUrls(bug, comment) -%]
</pre>
</div>
- [% END %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/create/confirm-create-dupe.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/create/confirm-create-dupe.html.tmpl
deleted file mode 100644
index b0a5cdd..0000000
--- a/Websites/bugs.webkit.org/template/en/default/bug/create/confirm-create-dupe.html.tmpl
+++ /dev/null
@@ -1,57 +0,0 @@
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Olav Vitters.
- #
- # Contributor(s): Olav Vitters <olav@bkor.dhs.org>
- #%]
-
-[%# INTERFACE:
- # bugid: integer. ID of the bug previously used to create a bug.
- # allow_override: boolean int. Is 1 if the user may submit the bug again.
- #%]
-
-[% PROCESS "global/field-descs.none.tmpl" %]
-
-[% PROCESS global/header.html.tmpl
- title = "Already filed $terms.bug"
-%]
-
-[% USE Bugzilla %]
-
-<table cellpadding="20">
- <tr>
- <td bgcolor="#ff0000">
- <font size="+2">
- You already used the form to file [% "$terms.bug $bugid" FILTER bug_link(bugid) FILTER none %].
- </font>
- </td>
- </tr>
-</table>
-
-<p><font size="big">You are highly encouraged to visit [% "$terms.bug $bugid"
-FILTER bug_link(bugid) FILTER none %].</font></p>
-
-[% IF allow_override %]
- <p>If you are sure you used the same form to submit a new [% terms.bug %],
- click 'File [% terms.bug %] again'.<p>
-
- <form name="create" id="create" method="post" action="post_bug.cgi"
- [%- IF Bugzilla.cgi.param("data") %] enctype="multipart/form-data"[% END %]>
- [% PROCESS "global/hidden-fields.html.tmpl"
- exclude="^(Bugzilla_login|Bugzilla_password|ignore_token)$" %]
- <input type="hidden" name="ignore_token" value="[% bugid FILTER html %]">
- <input type="submit" value="File [% terms.bug %] again" id="file_bug_again">
- </form>
-[% END %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/create/create-guided.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/create/create-guided.html.tmpl
index 9f2a21b..d103146 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/create/create-guided.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/create/create-guided.html.tmpl
@@ -34,6 +34,8 @@
style = "#somebugs { width: 100%; height: 500px }"
%]
+[% style = "" %]
+
<p>
<font color="red">
This is a template used on mozilla.org. This template, and the
@@ -67,8 +69,7 @@
}
</script>
-<a name="step1"></a>
-<h3>Step 1 of 3 - has your [% terms.bug %] already been reported?</h3>
+<h3 id="step1">Step 1 of 3 - has your [% terms.bug %] already been reported?</h3>
<p>
<font color="red">Please don't skip this step - half of all
@@ -83,7 +84,7 @@
[% ELSIF product.name == "Thunderbird" %]
[% productstring = "product=Mozilla%20Application%20Suite&product=Thunderbird" %]
[% ELSE %]
- [% productstring = BLOCK %]product=[% product.name FILTER url_quote %][% END %]
+ [% productstring = BLOCK %]product=[% product.name FILTER uri %][% END %]
[% END %]
<p>
@@ -136,8 +137,7 @@
</p>
-<a name="step2"></a>
-<h3>Step 2 of 3 - give information</h3>
+<h3 id="step2">Step 2 of 3 - give information</h3>
<p>
If you've tried a few searches and your [% terms.bug %] really isn't in
@@ -207,7 +207,7 @@
To pick the right component, you could use the same one as
similar [% terms.bugs %] you found in your search, or read the full list of
<a target="_blank" href="describecomponents.cgi?product=
- [% product.name FILTER url_quote %]">component
+ [% product.name FILTER uri %]">component
descriptions</a> (opens in new window) if you need more help.
</p>
</td>
@@ -471,11 +471,12 @@
</p>
</td>
</tr>
+
+ [% Hook.process('form') %]
</table>
-<a name="step3"></a>
-<h3>Step 3 of 3 - submit the [% terms.bug %] report</h3>
+<h3 id="step3">Step 3 of 3 - submit the [% terms.bug %] report</h3>
<p>
<input type="submit" id="report" value=" Submit [% terms.Bug %] Report "
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/create/create.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/create/create.html.tmpl
index ee819a2..f3dd680 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/create/create.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/create/create.html.tmpl
@@ -30,11 +30,13 @@
[% PROCESS global/header.html.tmpl
title = title
- style_urls = [ 'skins/standard/create_attachment.css',
- 'skins/standard/yui/calendar.css' ]
+ yui = [ 'autocomplete', 'calendar', 'datatable', 'button' ]
+ style_urls = [ 'skins/standard/attachment.css',
+ 'skins/standard/enter_bug.css' ]
javascript_urls = [ "js/attachment.js", "js/util.js",
- "js/yui/yahoo-dom-event.js", "js/yui/calendar.js",
- "js/field.js" ]
+ "js/field.js", "js/TUI.js", "js/bug.js" ]
+ onload = "set_assign_to(); hideElementById('attachment_true');
+ showElementById('attachment_false'); showElementById('btn_no_attachment');"
%]
<script type="text/javascript">
@@ -52,6 +54,7 @@
[% END %]
[% count = 0 %]
[%- FOREACH c = product.components %]
+ [% NEXT IF NOT c.is_active %]
components[[% count %]] = "[% c.name FILTER js %]";
comp_desc[[% count %]] = "[% c.description FILTER html_light FILTER js %]";
initialowners[[% count %]] = "[% c.default_assignee.login FILTER js %]";
@@ -128,9 +131,10 @@
if (inputElement.name.search(/^flag_type-(\d+)$/) != -1) {
var id = inputElement.name.replace(/^flag_type-(\d+)$/, "$1");
inputElement.disabled = true;
- // Also disable the requestee field, if it exists.
+ // Also hide the requestee field, if it exists.
inputElement = document.getElementById("requestee_type-" + id);
- if (inputElement) inputElement.disabled = true;
+ if (inputElement)
+ YAHOO.util.Dom.addClass(inputElement.parentNode, 'bz_default_hidden');
}
}
// Now enable flags available for the selected component.
@@ -147,57 +151,76 @@
}
}
-function handleWantsAttachment(wants_attachment) {
- if (wants_attachment) {
- document.getElementById('attachment_false').style.display = 'none';
- document.getElementById('attachment_true').style.display = 'block';
- }
- else {
- document.getElementById('attachment_false').style.display = 'block';
- document.getElementById('attachment_true').style.display = 'none';
- clearAttachmentFields();
- }
-}
+var status_comment_required = new Array();
+[% FOREACH status = bug_status %]
+ status_comment_required['[% status.name FILTER js %]'] =
+ [% status.comment_required_on_change_from() ? 'true' : 'false' %]
+[% END %]
+TUI_alternates['expert_fields'] = 'Show Advanced Fields';
+// Hide the Advanced Fields by default, unless the user has a cookie
+// that specifies otherwise.
+TUI_hide_default('expert_fields');
+// Also hide the "Paste text as attachment" textarea by default.
+TUI_hide_default('attachment_text_field');
-->
</script>
<form name="Create" id="Create" method="post" action="post_bug.cgi"
- enctype="multipart/form-data">
+ class="enter_bug_form" enctype="multipart/form-data"
+ onsubmit="return validateEnterBug(this)">
<input type="hidden" name="product" value="[% product.name FILTER html %]">
<input type="hidden" name="token" value="[% token FILTER html %]">
-<table cellspacing="4" cellpadding="2" border="0">
+<table>
<tbody>
<tr>
<td colspan="4">
[%# Migration note: The following file corresponds to the old Param
# 'entryheaderhtml'
#%]
- [% INCLUDE 'bug/create/user-message.html.tmpl' %]
+ [% PROCESS 'bug/create/user-message.html.tmpl' %]
</td>
</tr>
<tr>
- <td colspan="4"> </td>
+ <td colspan="2">
+ <a id="expert_fields_controller" class="controller bz_default_hidden"
+ href="javascript:TUI_toggle_class('expert_fields')">Hide
+ Advanced Fields</a>
+ [%# Show the link if the browser supports JS %]
+ <script type="text/javascript">
+ YAHOO.util.Dom.removeClass('expert_fields_controller',
+ 'bz_default_hidden');
+ </script>
+ </td>
+ <td colspan="2">
+ (<span class="required_star">*</span> =
+ <span class="required_explanation">Required Field</span>)
+ </td>
</tr>
<tr>
- <th>Product:</th>
- <td width="10%">[% product.name FILTER html %]</td>
-
- <th>Reporter:</th>
- <td width="100%">[% user.login FILTER html %]</td>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.product, editable = 0,
+ value = product.name %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.reporter, editable = 0,
+ value = user.login %]
</tr>
[%# We can't use the select block in these two cases for various reasons. %]
<tr>
- <th>
- <a href="describecomponents.cgi?product=[% product.name FILTER url_quote %]">
- Component</a>:
- </th>
- <td>
- <select name="component" onchange="set_assign_to();" size="7">
+ [% component_desc_url = BLOCK -%]
+ describecomponents.cgi?product=[% product.name FILTER uri %]
+ [% END %]
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.component editable = 1
+ desc_url = component_desc_url
+ %]
+ <td id="field_container_component">
+ <select name="component" id="component" onchange="set_assign_to();"
+ size="7" aria-required="true" class="required">
[%# Build the lists of assignees and QA contacts if "usemenuforusers" is enabled. %]
[% IF Param("usemenuforusers") %]
[% assignees_list = user.get_userlist.clone %]
@@ -205,8 +228,15 @@
[% END %]
[%- FOREACH c = product.components %]
+ [% NEXT IF NOT c.is_active %]
<option value="[% c.name FILTER html %]"
- [% " selected=\"selected\"" IF c.name == default.component_ %]>
+ id="v[% c.id FILTER html %]_component"
+ [% IF c.name == default.component_ %]
+ [%# This is for bug/field.html.tmpl, for visibility-related
+ # controls. %]
+ [% default.component_id = c.id %]
+ selected="selected"
+ [% END %]>
[% c.name FILTER html -%]
</option>
[% IF Param("usemenuforusers") %]
@@ -217,9 +247,16 @@
[% END %]
[%- END %]
</select>
+
+ <script type="text/javascript">
+ <!--
+ [%+ INCLUDE "bug/field-events.js.tmpl"
+ field = bug_fields.component, product = product %]
+ //-->
+ </script>
</td>
- <td colspan="2">
+ <td colspan="2" id="comp_desc_container">
[%# Enclose the fieldset in a nested table so that its width changes based
# on the length on the component description. %]
<table>
@@ -236,58 +273,41 @@
</tr>
<tr>
- <th rowspan="3">Version:</th>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.version editable = 1 rowspan = 3
+ %]
<td rowspan="3">
- <select name="version" size="5">
+ <select name="version" id="version" size="5">
[%- FOREACH v = version %]
- <option value="[% v FILTER html %]"
- [% ' selected="selected"' IF v == default.version %]>[% v FILTER html -%]
+ [% NEXT IF NOT v.is_active %]
+ <option value="[% v.name FILTER html %]"
+ [% ' selected="selected"' IF v.name == default.version %]>[% v.name FILTER html -%]
</option>
[%- END %]
</select>
</td>
- [% sel = { description => 'Severity', name => 'bug_severity' } %]
- [% INCLUDE select %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.bug_severity, editable = 1,
+ value = default.bug_severity %]
</tr>
<tr>
- [% sel = { description => 'Platform', name => 'rep_platform' } %]
- [% INCLUDE select %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.rep_platform, editable = 1,
+ value = default.rep_platform %]
</tr>
<tr>
- [% sel = { description => 'OS', name => 'op_sys' } %]
- [% INCLUDE select %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.op_sys, editable = 1,
+ value = default.op_sys %]
</tr>
-</tbody>
-
-<tbody class="expert_fields">
- <tr>
- [% IF Param('usetargetmilestone') && Param('letsubmitterchoosemilestone') %]
- [% sel = { description => 'Target Milestone', name => 'target_milestone' } %]
- [% INCLUDE select %]
- [% ELSE %]
- <td colspan="2"> </td>
- [% END %]
-
- [% IF Param('letsubmitterchoosepriority') %]
- [% sel = { description => 'Priority', name => 'priority' } %]
- [% INCLUDE select %]
- [% ELSE %]
- <td colspan="2">
- <input type="hidden" name="priority" value="[% default.priority FILTER html %]">
- </td>
- [% END %]
- </tr>
-</tbody>
-
-[% IF !Param('defaultplatform') || !Param('defaultopsys') %]
- <tbody>
+ [% IF !Param('defaultplatform') || !Param('defaultopsys') %]
<tr>
- <th> </th>
- <td colspan="3" class="comment">
- We've made a guess at your
+ <th colspan="3"> </th>
+ <td id="os_guess_note" class="comment">
+ <div>We've made a guess at your
[% IF Param('defaultplatform') %]
operating system. Please check it
[% ELSIF Param('defaultopsys') %]
@@ -295,11 +315,29 @@
[% ELSE %]
operating system and platform. Please check them
[% END %]
- and make any corrections if necessary.
+ and make any corrections if necessary.</div>
</td>
</tr>
- </tbody>
-[% END %]
+ [% END %]
+</tbody>
+
+<tbody class="expert_fields">
+ <tr>
+ [% IF Param('usetargetmilestone') && Param('letsubmitterchoosemilestone') %]
+ [% INCLUDE select field = bug_fields.target_milestone %]
+ [% ELSE %]
+ <td colspan="2"> </td>
+ [% END %]
+
+ [% IF Param('letsubmitterchoosepriority') %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.priority, editable = 1,
+ value = default.priority %]
+ [% ELSE %]
+ <td colspan="2"> </td>
+ [% END %]
+ </tr>
+</tbody>
<tbody class="expert_fields">
<tr>
@@ -307,20 +345,15 @@
</tr>
<tr>
-[% IF bug_status.size <= 1 %]
- <input type="hidden" name="bug_status"
- value="[% default.bug_status FILTER html %]">
- <th>Initial State:</th>
- <td>[% get_status(default.bug_status) FILTER html %]</td>
-[% ELSE %]
- [% sel = { description => 'Initial State', name => 'bug_status' } %]
- [% INCLUDE select %]
-[% END %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.bug_status,
+ editable = (bug_status.size > 1), value = default.bug_status
+ override_legal_values = bug_status %]
<td> </td>
[%# Calculate the number of rows we can use for flags %]
[% num_rows = 6 + (Param("useqacontact") ? 1 : 0) +
- (user.in_group(Param('timetrackinggroup')) ? 3 : 0) +
+ (user.is_timetracker ? 3 : 0) +
(Param("usebugaliases") ? 1 : 0)
%]
@@ -346,9 +379,12 @@
</tr>
<tr>
- <th><a href="page.cgi?id=fields.html#assigned_to">Assign To</a>:</th>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.assigned_to editable = 1
+ %]
<td colspan="2">
[% INCLUDE global/userselect.html.tmpl
+ id => "assigned_to"
name => "assigned_to"
value => assigned_to
disabled => assigned_to_disabled
@@ -362,9 +398,12 @@
[% IF Param("useqacontact") %]
<tr>
- <th>QA Contact:</th>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.qa_contact editable = 1
+ %]
<td colspan="2">
[% INCLUDE global/userselect.html.tmpl
+ id => "qa_contact"
name => "qa_contact"
value => qa_contact
disabled => qa_contact_disabled
@@ -378,9 +417,12 @@
[% END %]
<tr>
- <th>CC:</th>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.cc editable = 1
+ %]
<td colspan="2">
[% INCLUDE global/userselect.html.tmpl
+ id => "cc"
name => "cc"
value => cc
disabled => cc_disabled
@@ -391,13 +433,9 @@
</tr>
<tr>
- <th>Default CC:</th>
+ <th>Default [% field_descs.cc FILTER html %]:</th>
<td colspan="2">
<div id="initial_cc">
- <!-- This has to happen after everything above renders,
- and onload doesn't work. So this is as good a place
- as any to put it. -->
- <script type="text/javascript">set_assign_to();</script>
</div>
</td>
</tr>
@@ -406,19 +444,19 @@
<td colspan="3"> </td>
</tr>
-[% IF user.in_group(Param('timetrackinggroup')) %]
+[% IF user.is_timetracker %]
<tr>
- <th>Estimated Hours:</th>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.estimated_time editable = 1
+ %]
<td colspan="2">
- <input name="estimated_time" size="6" maxlength="6" value="0.0">
+ <input name="estimated_time" size="6" maxlength="6" value="[% estimated_time FILTER html %]">
</td>
</tr>
<tr>
- <th>Deadline:</th>
- <td colspan="2">
- <input name="deadline" size="10" maxlength="10" value="[% deadline FILTER html %]">
- <small>(YYYY-MM-DD)</small>
- </td>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.deadline, value = deadline,
+ editable = 1, value_span = 2 %]
</tr>
<tr>
@@ -428,18 +466,22 @@
[% IF Param("usebugaliases") %]
<tr>
- <th>Alias:</th>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.alias editable = 1
+ %]
<td colspan="2">
- <input name="alias" size="20">
+ <input name="alias" size="20" value="[% alias FILTER html %]">
</td>
</tr>
[% END %]
<tr>
- <th>URL:</th>
- <td colspan="2">
- <input name="bug_file_loc" size="40"
- value="[% bug_file_loc FILTER html %]">
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.bug_file_loc editable = 1
+ %]
+ <td colspan="2" class="field_value">
+ <input name="bug_file_loc" id="bug_file_loc" class="text_input"
+ size="40" value="[% bug_file_loc FILTER html %]">
</td>
</tr>
</tbody>
@@ -450,22 +492,66 @@
[% FOREACH field = Bugzilla.active_custom_fields %]
[% NEXT UNLESS field.enter_bug %]
[% SET value = ${field.name}.defined ? ${field.name} : "" %]
- <tr>
- [% PROCESS bug/field.html.tmpl editable=1 value_span=3 %]
+ <tr [% 'class="expert_fields"' IF !field.is_mandatory %]>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = field, value = value, editable = 1,
+ value_span = 3 %]
+ </tr>
+ [% END %]
+</tbody>
+
+<tbody>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.short_desc editable = 1
+ %]
+ <td colspan="3" class="field_value">
+ <input name="short_desc" size="70" value="[% short_desc FILTER html %]"
+ maxlength="255" spellcheck="true" aria-required="true"
+ class="required text_input" id="short_desc">
+ </td>
+ </tr>
+
+ [% IF feature_enabled('jsonrpc') AND !cloned_bug_id %]
+ <tr id="possible_duplicates_container" class="bz_default_hidden">
+ <th>Possible<br>Duplicates:</th>
+ <td colspan="3">
+ <div id="possible_duplicates"></div>
+ <script type="text/javascript">
+ var dt_columns = [
+ { key: "id", label: "[% field_descs.bug_id FILTER js %]",
+ formatter: YAHOO.bugzilla.dupTable.formatBugLink },
+ { key: "summary",
+ label: "[% field_descs.short_desc FILTER js %]",
+ formatter: "text" },
+ { key: "status",
+ label: "[% field_descs.bug_status FILTER js %]",
+ formatter: YAHOO.bugzilla.dupTable.formatStatus },
+ { key: "update_token", label: '',
+ formatter: YAHOO.bugzilla.dupTable.formatCcButton }
+ ];
+ YAHOO.bugzilla.dupTable.addCcMessage = "Add Me to the CC List";
+ YAHOO.bugzilla.dupTable.init({
+ container: 'possible_duplicates',
+ columns: dt_columns,
+ product_name: '[% product.name FILTER js %]',
+ summary_field: 'short_desc',
+ options: {
+ MSG_LOADING: 'Searching for possible duplicates...',
+ MSG_EMPTY: 'No possible duplicates found.',
+ SUMMARY: 'Possible Duplicates'
+ }
+ });
+ </script>
+ </td>
</tr>
[% END %]
<tr>
- <th>Summary:</th>
- <td colspan="3">
- <input name="short_desc" size="70" value="[% short_desc FILTER html %]"
- maxlength="255" spellcheck="true">
- </td>
- </tr>
-
- <tr>
<th>Description:</th>
<td colspan="3">
+
[% defaultcontent = BLOCK %]
[% IF cloned_bug_id %]
+++ This [% terms.bug %] was initially created as a clone of [% terms.Bug %] #[% cloned_bug_id %] +++
@@ -488,40 +574,33 @@
</td>
</tr>
- [% IF Param("insidergroup") && user.in_group(Param("insidergroup")) %]
- <tr>
+ [% IF user.is_insider %]
+ <tr class="expert_fields">
<th> </th>
<td colspan="3">
- <input type="checkbox" id="commentprivacy" name="commentprivacy"
- [% " checked=\"checked\"" IF commentprivacy %]>
- <label for="commentprivacy">
- Initial Description is Private
+ <input type="checkbox" id="comment_is_private" name="comment_is_private"
+ [% ' checked="checked"' IF comment_is_private %]
+ onClick="updateCommentTagControl(this, 'comment')">
+ <label for="comment_is_private">
+ Make description and any new attachment private (visible only to members
+ of the <strong>[% Param('insidergroup') FILTER html %]</strong> group)
</label>
</td>
</tr>
[% END %]
-</tbody>
-<tbody class="expert_fields">
+ [% IF Param("maxattachmentsize") || Param("maxlocalattachment") %]
<tr>
<th>Attachment:</th>
<td colspan="3">
- <script type="text/javascript">
- <!--
- document.write( '<div id="attachment_false">'
- + '<input type="button" value="Add an attachment" '
- + 'onClick="handleWantsAttachment(true)"> '
- + '<em style="display: none">This button has no '
- + 'functionality for you because your browser does '
- + 'not support CSS or does not use it.<\/em>'
- + '<\/div>'
- + '<div id="attachment_true" style="display: none">'
- + '<input type="button" '
- + 'value="Don\'t add an attachment " '
- + 'onClick="handleWantsAttachment(false)">');
- //-->
- </script>
+ <div id="attachment_false" class="bz_default_hidden">
+ <input type="button" value="Add an attachment" onClick="handleWantsAttachment(true)">
+ </div>
+
+ <div id="attachment_true">
+ <input type="button" id="btn_no_attachment" value="Don't add an attachment"
+ class="bz_default_hidden" onClick="handleWantsAttachment(false)">
<fieldset>
<legend>Add an attachment</legend>
<table class="attachment_entry">
@@ -531,33 +610,36 @@
flag_table_id ="attachment_flags" %]
</table>
</fieldset>
- <script type="text/javascript">
- <!--
- document.write('<\/div>');
- //-->
- </script>
+ </div>
</td>
</tr>
+ [% END %]
+</tbody>
+<tbody class="expert_fields">
[% IF user.in_group('editbugs', product.id) %]
[% IF use_keywords %]
<tr>
- <th><a href="describekeywords.cgi">Keywords</a>:</th>
- <td colspan="3">
- <input id="keywords" name="keywords" size="40"
- value="[% keywords FILTER html %]"> (optional)
- </td>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.keywords, editable = 1,
+ value = keywords, desc_url = "describekeywords.cgi",
+ value_span = 2
+ %]
</tr>
[% END %]
<tr>
- <th>Depends on:</th>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.dependson editable = 1
+ %]
<td colspan="3">
<input name="dependson" accesskey="d" value="[% dependson FILTER html %]">
</td>
</tr>
<tr>
- <th>Blocks:</th>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.blocked editable = 1
+ %]
<td colspan="3">
<input name="blocked" accesskey="b" value="[% blocked FILTER html %]">
</td>
@@ -565,14 +647,15 @@
[% END %]
</tbody>
-<tbody>
- [% IF group.size %]
+<tbody class="expert_fields">
+ [% IF product.groups_available.size %]
<tr>
<th> </th>
<td colspan="3">
<br>
<strong>
- Only users in all of the selected groups can view this [% terms.bug %]:
+ Only users in all of the selected groups can view this
+ [%+ terms.bug %]:
</strong>
<br>
<font size="-1">
@@ -582,31 +665,32 @@
<br>
<!-- Checkboxes -->
- [% FOREACH g = group %]
-
- <input type="checkbox" id="bit-[% g.bit %]"
- name="bit-[% g.bit %]" value="1"
- [% " checked=\"checked\"" IF g.checked %]>
- <label for="bit-[% g.bit %]">[% g.description FILTER html_light %]</label><br>
+ <input type="hidden" name="defined_groups" value="1">
+ [% FOREACH group = product.groups_available %]
+ <input type="checkbox" id="group_[% group.id FILTER html %]"
+ name="groups" value="[% group.name FILTER html %]"
+ [% ' checked="checked"' IF default.groups.contains(group.name)
+ OR group.is_default %]>
+ <label for="group_[% group.id FILTER html %]">
+ [%- group.description FILTER html_light %]</label><br>
[% END %]
</td>
</tr>
[% END %]
+</tbody>
+<tbody>
[%# Form controls for entering additional data about the bug being created. %]
[% Hook.process("form") %]
<tr>
<th> </th>
<td colspan="3">
- <input type="submit" id="commit" value="Commit"
- onclick="if (this.form.short_desc.value == '')
- { alert('Please enter a summary sentence for this [% terms.bug %].');
- return false; } return true;">
+ <input type="submit" id="commit" value="Submit [% terms.Bug %]">
<input type="submit" name="maketemplate" id="maketemplate"
value="Remember values as bookmarkable template"
- class="expert_fields">
+ onclick="bz_no_validate_enter_bug=true" class="expert_fields">
</td>
</tr>
</tbody>
@@ -624,22 +708,19 @@
[%############################################################################%]
[% BLOCK select %]
- [% IF sel.description %]
- <th>
- <a href="page.cgi?id=fields.html#[% sel.name %]">[% sel.description %]</a>:
- </th>
- [% END %]
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = field editable = 1
+ %]
<td>
- <select name="[% sel.name %]">
- [%- FOREACH x = ${sel.name} %]
- <option value="[% x FILTER html %]"
- [% " selected=\"selected\"" IF x == default.${sel.name} %]>
- [% IF sel.name == "bug_status" %]
- [% get_status(x) FILTER html %]
- [% ELSE %]
- [% x FILTER html %]
- [% END %]</option>
+ <select name="[% field.name FILTER html %]"
+ id="[% field.name FILTER html %]">
+ [%- FOREACH x = ${field.name} %]
+ [% NEXT IF NOT x.is_active %]
+ <option value="[% x.name FILTER html %]"
+ [% " selected=\"selected\"" IF x.name == default.${field.name} %]>
+ [% display_value(field.name, x.name) FILTER html %]
+ </option>
[% END %]
</select>
</td>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/create/created.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/create/created.html.tmpl
index 6226fdc..d9eaccb 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/create/created.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/create/created.html.tmpl
@@ -24,23 +24,16 @@
# type: string; type of change for this bug, either 'created' if this bug
# was created or 'dep' if it was added as a dependent/blocker
# id: integer; the ID of the bug
- # mailrecipients: hash; contains the BugMail recipients, for details on
- # this contents, see template bug/process/bugmail.html.tmpl
# bug: object; Bugzilla::Bug object of the bug that was created (used in
# template bug/edit.html.tmpl
- # bug_list: array of integers; sorted bug list (used in template
- # bug/navigate.html.tmpl)
#%]
[% PROCESS global/variables.none.tmpl %]
+[% PROCESS "bug/show-header.html.tmpl" %]
[% PROCESS global/header.html.tmpl
- title = "$terms.Bug $id Submitted"
- javascript_urls = [ "js/util.js", "js/field.js",
- "js/yui/yahoo-dom-event.js", "js/yui/calendar.js" ]
- style_urls = [ "skins/standard/yui/calendar.css", "skins/standard/show_bug.css" ]
-
-
+ title = "$terms.Bug $id Submitted – $filtered_desc"
+ header = "$terms.Bug $id Submitted"
%]
[% header_done = 1 %]
@@ -49,8 +42,7 @@
[% PROCESS bug/process/results.html.tmpl
type = item.type
id = item.id
- mail = item.mail
- mailrecipients = mailrecipients
+ sent_bugmail = item
%]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/dependency-graph.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/dependency-graph.html.tmpl
index 37dcde0..78fc4a8 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/dependency-graph.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/dependency-graph.html.tmpl
@@ -21,7 +21,6 @@
[%# INTERFACE:
# bug_id: integer. The number of the bug(s).
# multiple_bugs: boolean. True if bug_id contains > 1 bug number.
- # doall: boolean. True if we are displaying every bug in the database.
# showsummary: boolean. True if we are showing bug summaries.
# rankdir: string. "TB" if we are ranking top-to-bottom,
"LR" if left-to-right.
@@ -37,7 +36,7 @@
header = title
%]
-[% IF NOT multiple_bugs AND NOT doall %]
+[% IF NOT multiple_bugs %]
[% filtered_desc = short_desc FILTER html %]
[% title = "$title for $terms.bug $bug_id"
header = "$header for $terms.bug <a href=\"show_bug.cgi?id=$bug_id\">$bug_id</a>"
@@ -82,8 +81,6 @@
Restrict to [% terms.bugs %] having a direct relationship with entered [% terms.bugs %]</option>
<option value="web" [% 'selected="selected"' IF display == "web" %]>
Show all [% terms.bugs %] having any relationship with entered [% terms.bugs %]</option>
- <option value="doall" [% 'selected="selected"' IF display == "doall" %]>
- Show every [% terms.bug %] in the system with dependencies</option>
</select>
</td>
</tr>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/dependency-tree.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/dependency-tree.html.tmpl
index cc49d2f..10279f9 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/dependency-tree.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/dependency-tree.html.tmpl
@@ -64,14 +64,14 @@
[% IF ids.size %]
depends on
[% ELSE %]
- does not depend on any [% terms.bugs %].
+ does not depend on any [% 'open ' IF hide_resolved %][% terms.bugs %].
[% END %]
[% ELSIF type == 2 %]
[% tree_name = "blocked_tree" %]
[% IF ids.size %]
blocks
[% ELSE %]
- does not block any [% terms.bugs %].
+ does not block any [% 'open ' IF hide_resolved %][% terms.bugs %].
[% END %]
[% END %]
[% IF ids.size %]
@@ -83,7 +83,7 @@
[% IF ids.size %]
([% IF maxdepth -%]Up to [% maxdepth %] level[% "s" IF maxdepth > 1 %] deep | [% END -%]
<a href="buglist.cgi?bug_id=[% ids.join(",") %]">view as [% terms.bug %] list</a>
- [% IF user.groups.editbugs && ids.size > 1 %]
+ [% IF user.in_group('editbugs') && ids.size > 1 %]
| <a href="buglist.cgi?bug_id=[% ids.join(",") %]&tweak=1">change several</a>
[% END %])
<ul class="tree">
@@ -129,7 +129,7 @@
[% extra_class = " b_open" %]
[% extra_args = 'onclick="return doToggle(this, event)"' %]
[% END %]
- <a name="b[% bugid %]"
+ <a id="b[% bugid %]"
class="b [%+ extra_class FILTER none %]"
title="Click to expand or contract this portion of the tree. Hold down the Ctrl key while clicking to expand or contract all subtrees."
[% extra_args FILTER none %]> </a>
@@ -144,7 +144,7 @@
<span class="summ_text">[%+ bug.short_desc FILTER html %]</span>
<span class="summ_info">[[% INCLUDE buginfo %]]</span>
</a>
- <a href="showdependencytree.cgi?id=[% bugid FILTER url_quote %]"
+ <a href="showdependencytree.cgi?id=[% bugid FILTER uri %]"
class="tree_link">
<img src="skins/standard/dependency-tree/tree.png"
title="See dependency tree for [% terms.bug %] [%+ bugid FILTER html %]">
@@ -153,8 +153,8 @@
[% END %]
[% BLOCK buginfo %]
- [% get_status(bug.bug_status) FILTER html -%] [%+ get_resolution(bug.resolution) FILTER html %];
- [%-%] assigned to [% bug.assigned_to.login FILTER html %]
+ [% display_value("bug_status", bug.bug_status) FILTER html -%] [%+ display_value("resolution", bug.resolution) FILTER html %];
+ [%-%] assigned to [% bug.assigned_to.login FILTER email FILTER html %]
[%-%][% "; Target: " _ bug.target_milestone IF bug.target_milestone %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/edit.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/edit.html.tmpl
index baf5833..bdee838 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/edit.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/edit.html.tmpl
@@ -40,7 +40,7 @@
*/
[% IF user.settings.quote_replies.value != 'off' %]
document.write('[<a href="#add_comment" onclick="replyToComment(' +
- id + ',' + real_id + ');">reply<' + '/a>]');
+ id + ',' + real_id + '); return false;">reply<' + '/a>]');
[% END %]
}
@@ -52,24 +52,15 @@
/* pre id="comment_name_N" */
var text_elem = document.getElementById('comment_text_'+id);
var text = getText(text_elem);
-
- /* make sure we split on all newlines -- IE or Moz use \r and \n
- * respectively.
- */
- text = text.split(/\r|\n/);
-
- for (var i=0; i < text.length; i++) {
- replytext += "> " + text[i] + "\n";
- }
-
- replytext = prefix + replytext + "\n";
+ replytext = prefix + wrapReplyText(text);
[% ELSIF user.settings.quote_replies.value == 'simple_reply' %]
replytext = prefix;
[% END %]
- [% IF Param("insidergroup") && user.in_group(Param("insidergroup")) %]
+ [% IF user.is_insider %]
if (document.getElementById('isprivate_' + real_id).checked) {
document.getElementById('newcommentprivacy').checked = 'checked';
+ updateCommentTagControl(document.getElementById('newcommentprivacy'), 'comment');
}
[% END %]
@@ -107,7 +98,7 @@
return text;
}
-[% IF user.in_group(Param('timetrackinggroup')) %]
+[% IF user.is_timetracker %]
var fRemainingTime = [% bug.remaining_time %]; // holds the original value
function adjustRemainingTime() {
// subtracts time spent from remaining time
@@ -128,26 +119,27 @@
[% END %]
- function updateCommentTagControl(checkbox, form) {
- if (checkbox.checked) {
- form.comment.className='bz_private';
- } else {
- form.comment.className='';
- }
- }
+ /* Index all classifications so we can keep track of the classification
+ * for the selected product, which could control field visibility.
+ */
+ var all_classifications = new Array([% bug.choices.product.size %]);
+ [%- FOREACH product = bug.choices.product %]
+ all_classifications['[% product.name FILTER js %]'] = '
+ [%- product.classification.name FILTER js %]';
+ [%- END %]
//-->
</script>
-<form name="changeform" method="post" action="process_bug.cgi">
+<form name="changeform" id="changeform" method="post" action="process_bug.cgi">
<input type="hidden" name="delta_ts" value="[% bug.delta_ts %]">
- <input type="hidden" name="longdesclength" value="[% bug.longdescs.size %]">
+ <input type="hidden" name="longdesclength" value="[% bug.comments.size %]">
<input type="hidden" name="id" value="[% bug.bug_id %]">
<input type="hidden" name="token" value="[% issue_hash_token([bug.id, bug.delta_ts]) FILTER html %]">
[% PROCESS section_title %]
- <table>
+ <table class="edit_form">
<tr>
[%# 1st Column %]
<td id="bz_show_bug_column_1" class="bz_show_bug_column">
@@ -189,7 +181,9 @@
[% PROCESS section_cclist %]
- [% PROCESS section_spacer %]
+ [% PROCESS section_spacer %]
+
+ [% PROCESS section_see_also %]
[% PROCESS section_customfields %]
@@ -209,90 +203,43 @@
</tr>
</table>
-
- [% PROCESS section_restrict_visibility %]
- [% IF user.in_group(Param('timetrackinggroup')) %]
- <br>
- [% PROCESS section_timetracking %]
- [% END %]
-
+ <table id="bz_big_form_parts" cellspacing="0" cellpadding="0"><tr>
+ <td>
+ [% IF user.is_timetracker %]
+ [% PROCESS section_timetracking %]
+ [% END %]
-[%# *** Attachments *** %]
+ [%# *** Attachments *** %]
- [% PROCESS attachment/list.html.tmpl
- attachments = bug.attachments
- bugid = bug.bug_id
- num_attachment_flag_types = bug.num_attachment_flag_types
- show_attachment_flags = bug.show_attachment_flags
- %]
+ [% PROCESS attachment/list.html.tmpl
+ attachments = bug.attachments
+ bugid = bug.bug_id
+ num_attachment_flag_types = bug.num_attachment_flag_types
+ show_attachment_flags = bug.show_attachment_flags
+ %]
+ [% IF user.settings.comment_box_position.value == 'before_comments' %]
+ [% PROCESS comment_box %]
+ [% END %]
+ </td>
+ <td>
+ [% PROCESS section_restrict_visibility %]
+ </td>
+ </tr></table>
-[%# *** Comments Groups *** %]
+ [%# *** Additional Comments *** %]
+ <div id="comments">
+ [% PROCESS bug/comments.html.tmpl
+ comments = bug.comments
+ mode = user.id ? "edit" : "show"
+ %]
+ </div>
- <br>
- <table cellpadding="1" cellspacing="1">
- <tr>
- <td id="comment_status_commit">
- <!-- The table keeps the commit button aligned with the box. -->
- <a name="add_comment"></a>
- [% IF user.id %]
- <table><tr><td>
- <label for="comment" accesskey="c"><b>Additional <u>C</u>omments</b></label>:
- [% IF Param("insidergroup") && user.in_group(Param("insidergroup")) %]
- <input type="checkbox" name="commentprivacy" value="1"
- id="newcommentprivacy"
- onClick="updateCommentTagControl(this, form)">
- <label for="newcommentprivacy">Private</label>
- [% END %]
- <br>
- [% INCLUDE global/textarea.html.tmpl
- name = 'comment'
- id = 'comment'
- minrows = 10
- maxrows = 25
- cols = constants.COMMENT_COLS
- %]
- <br>
- <div id="knob-buttons">
- <input type="submit" value="Commit" id="commit">
- [% IF bug.user.canmove %]
- <input type="submit" name="action" id="action" value="[% Param("move-button-text") %]">
- [% END %]
- </div>
- <table class="status" cellspacing="0" cellpadding="0">
- <tr>
- <td class="field_label">
- <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
- </td>
- <td>
- <a name="bug_status_bottom"></a>
- [% PROCESS bug/knob.html.tmpl %]
- </td>
- </tr>
- </table>
- </td></tr></table>
- [% ELSE %]
- <fieldset>
- <legend>Note</legend>
- <p>
- You need to
- <a href="[% IF Param('ssl') != 'never' %][% Param('sslbase') %][% END %]show_bug.cgi?id=[% bug.bug_id %]&GoAheadAndLogIn=1">log in</a>
- before you can comment on or make changes to this [% terms.bug %].
- </p>
- </fieldset>
- [% END %]
- [%# *** Additional Comments *** %]
- <hr>
- <div id="comments">
- [% PROCESS bug/comments.html.tmpl
- comments = bug.longdescs
- mode = user.id ? "edit" : "show"
- %]
- </div>
-
- </td>
- </tr>
- </table>
+ [% IF user.settings.comment_box_position.value == 'after_comments' %]
+ <hr>
+ [% PROCESS comment_box %]
+ [% END %]
+
</form>
[%############################################################################%]
@@ -301,17 +248,17 @@
[% BLOCK section_title %]
[%# That's the main table, which contains all editable fields. %]
- <div class="bz_alias_short_desc_container">
-
+ <div class="bz_alias_short_desc_container edit_form">
+ [% PROCESS commit_button id="_top"%]
<a href="show_bug.cgi?id=[% bug.bug_id %]">
- <b>[% terms.Bug %] [% bug.bug_id FILTER html %]</b></a> -
- <span id="summary_alias_container" class="bz_default_hidden">
+ [%-# %]<b>[% terms.Bug %] [% bug.bug_id FILTER html %]</b>
+ [%-# %]</a> -<span id="summary_alias_container" class="bz_default_hidden">
[% IF Param("usebugaliases") %]
[% IF bug.alias != "" %]
(<span id="alias_nonedit_display">[% bug.alias FILTER html %]</span>)
[% END %]
[% END %]
- <span id="short_desc_nonedit_display">[% bug.short_desc FILTER html %]</span>
+ <span id="short_desc_nonedit_display">[% bug.short_desc FILTER quoteUrls(bug) %]</span>
[% IF bug.check_can_change_field('short_desc', 0, 1) ||
bug.check_can_change_field('alias', 0, 1) %]
<small class="editme">(<a href="#" id="editme_action">edit</a>)</small>
@@ -373,23 +320,35 @@
[%# PRODUCT #%]
[%#############%]
<tr>
- <td class="field_label">
- <label for="product" accesskey="p"><b><u>P</u>roduct</b></label>:
- </td>
- [% PROCESS select selname => "product" %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.product,
+ override_legal_values = bug.choices.product
+ desc_url = 'describecomponents.cgi', value = bug.product
+ editable = bug.check_can_change_field('product', 0, 1) %]
+ </tr>
+
+ [%# Classification is here so that it can be used in value controllers
+ # and visibility controllers. It comes after product because
+ # it uses some javascript that depends on the existence of the
+ # product field.
+ #%]
+ <tr class="bz_default_hidden">
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug field = bug_fields.classification
+ override_legal_values = bug.choices.classification
+ value = bug.classification
+ editable = bug.check_can_change_field('product', 0, 1) %]
</tr>
[%###############%]
[%# Component #%]
[%###############%]
<tr>
- <td class="field_label">
- <label for="component" accesskey="m">
- <b><a href="describecomponents.cgi?product=[% bug.product FILTER url_quote %]">
- Co<u>m</u>ponent</a>:
- </b>
- </label>
- </td>
- [% PROCESS select selname => "component" %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.component, value = bug.component
+ override_legal_values = bug.choices.component
+ desc_url = "describecomponents.cgi?product=$bug.product"
+ editable = bug.check_can_change_field('component', 0, 1)
+ %]
</tr>
<tr>
<td class="field_label">
@@ -405,9 +364,15 @@
<td class="field_label">
<label for="rep_platform" accesskey="h"><b>Platform</b></label>:
</td>
- <td>
- [% PROCESS select selname => "rep_platform" no_td=> 1 %]
- [%+ PROCESS select selname => "op_sys" no_td=> 1 %]
+ <td class="field_value">
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.rep_platform,
+ no_tds = 1, value = bug.rep_platform
+ editable = bug.check_can_change_field('rep_platform', 0, 1) %]
+ [%+ INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.op_sys,
+ no_tds = 1, value = bug.op_sys
+ editable = bug.check_can_change_field('op_sys', 0, 1) %]
<script type="text/javascript">
assignToDefaultOnChange(['product', 'component']);
</script>
@@ -429,9 +394,9 @@
</td>
<td id="bz_field_status">
<span id="static_bug_status">
- [% get_status(bug.bug_status) FILTER html %]
+ [% display_value("bug_status", bug.bug_status) FILTER html %]
[% IF bug.resolution %]
- [%+ get_resolution(bug.resolution) FILTER html %]
+ [%+ display_value("resolution", bug.resolution) FILTER html %]
[% IF bug.dup_id %]
of [% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
[% END %]
@@ -452,7 +417,7 @@
[% BLOCK section_details2 %]
[%###############################################################%]
- [%# Importance (priority, severity and votes) #%]
+ [%# Importance (priority and severity) #%]
[%###############################################################%]
<tr>
<td class="field_label">
@@ -460,36 +425,24 @@
<b><a href="page.cgi?id=fields.html#importance"><u>I</u>mportance</a></b></label>:
</td>
<td>
- [% PROCESS select selname => "priority" no_td=>1 %]
- [% PROCESS select selname = "bug_severity" no_td=>1 %]
- [% IF bug.use_votes %]
- <span id="votes_container">
- [% IF bug.votes %]
- with
- <a href="votes.cgi?action=show_bug&bug_id=[% bug.bug_id %]">
- [% bug.votes %]
- [% IF bug.votes == 1 %]
- vote
- [% ELSE %]
- votes
- [% END %]</a>
- [% END %]
- (<a href="votes.cgi?action=show_user&bug_id=
- [% bug.bug_id %]#vote_[% bug.bug_id %]">vote</a>)
- </span>
- [% END %]
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.priority,
+ no_tds = 1, value = bug.priority
+ editable = bug.check_can_change_field('priority', 0, 1) %]
+ [%+ INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.bug_severity,
+ no_tds = 1, value = bug.bug_severity
+ editable = bug.check_can_change_field('bug_severity', 0, 1) %]
+ [% Hook.process('after_importance', 'bug/edit.html.tmpl') %]
</td>
</tr>
[% IF Param("usetargetmilestone") && bug.target_milestone %]
<tr>
<td class="field_label">
- <label for="target_milestone"><b>
- [% IF bug.milestoneurl %]
- <a href="[% bug.milestoneurl FILTER html %]">
- [% END %]
- Target Milestone[% "</a>" IF bug.milestoneurl %]
- [%%]</b></label>:
+ <label for="target_milestone">
+ <a href="page.cgi?id=fields.html#target_milestone">
+ Target Milestone</a></label>:
</td>
[% PROCESS select selname = "target_milestone" %]
</tr>
@@ -511,8 +464,12 @@
[% IF bug.check_can_change_field("assigned_to", 0, 1) %]
<div id="bz_assignee_edit_container" class="bz_default_hidden">
<span>
- [% INCLUDE user_identity user=> bug.assigned_to %]
+ [% INCLUDE global/user.html.tmpl who = bug.assigned_to %]
(<a href="#" id="bz_assignee_edit_action">edit</a>)
+ [% IF bug.assigned_to.id != user.id %]
+ (<a title="Reassign to yourself"
+ href="#" id="bz_assignee_take_action">take</a>)
+ [% END %]
</span>
</div>
<div id="bz_assignee_input">
@@ -520,6 +477,7 @@
id => "assigned_to"
name => "assigned_to"
value => bug.assigned_to.login
+ classes => ["bz_userfield"]
size => 30
%]
<br>
@@ -532,10 +490,16 @@
'bz_assignee_edit_action',
'assigned_to',
'[% bug.assigned_to.login FILTER js %]' );
+ hideEditableField('bz_assignee_edit_container',
+ 'bz_assignee_input',
+ 'bz_assignee_take_action',
+ 'assigned_to',
+ '[% bug.assigned_to.login FILTER js %]',
+ '[% user.login FILTER js %]' );
initDefaultCheckbox('assignee');
</script>
[% ELSE %]
- [% INCLUDE user_identity user => bug.assigned_to %]
+ [% INCLUDE global/user.html.tmpl who = bug.assigned_to %]
[% END %]
</td>
</tr>
@@ -546,13 +510,12 @@
<label for="qa_contact" accesskey="q"><b><u>Q</u>A Contact</b></label>:
</td>
<td>
-
[% IF bug.check_can_change_field("qa_contact", 0, 1) %]
[% IF bug.qa_contact != "" %]
<div id="bz_qa_contact_edit_container" class="bz_default_hidden">
<span>
<span id="bz_qa_contact_edit_display">
- [% INCLUDE user_identity user=> bug.qa_contact %]</span>
+ [% INCLUDE global/user.html.tmpl who = bug.qa_contact %]</span>
(<a href="#" id="bz_qa_contact_edit_action">edit</a>)
</span>
</div>
@@ -563,6 +526,7 @@
name => "qa_contact"
value => bug.qa_contact.login
size => 30
+ classes => ["bz_userfield"]
emptyok => 1
%]
<br>
@@ -580,7 +544,7 @@
initDefaultCheckbox('qa_contact');
</script>
[% ELSE %]
- [% INCLUDE user_identity user => bug.qa_contact %]
+ [% INCLUDE global/user.html.tmpl who = bug.qa_contact %]
[% END %]
</td>
</tr>
@@ -591,23 +555,16 @@
[%# Block for URL Keyword and Whiteboard #%]
[%############################################################################%]
[% BLOCK section_url_keyword_whiteboard %]
-[%# *** URL Whiteboard Keywords *** %]
<tr>
- <td class="field_label">
- <label for="bug_file_loc" accesskey="u"><b>
- [% IF bug.bug_file_loc
- AND NOT bug.bug_file_loc.match("^(javascript|data)") %]
- <a href="[% bug.bug_file_loc FILTER html %]"><u>U</u>RL</a>
- [% ELSE %]
- <u>U</u>RL
- [% END %]
- [%%]</b></label>:
- </td>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.bug_file_loc
+ editable = 1
+ accesskey = "u"
+ %]
<td>
[% IF bug.check_can_change_field("bug_file_loc", 0, 1) %]
<span id="bz_url_edit_container" class="bz_default_hidden">
- [% IF bug.bug_file_loc
- AND NOT bug.bug_file_loc.match("^(javascript|data)") %]
+ [% IF is_safe_url(bug.bug_file_loc) %]
<a href="[% bug.bug_file_loc FILTER html %]" target="_blank"
title="[% bug.bug_file_loc FILTER html %]">
[% bug.bug_file_loc FILTER truncate(40) FILTER html %]</a>
@@ -618,7 +575,8 @@
[% END %]
<span id="bz_url_input_area">
[% url_output = PROCESS input no_td=1 inputname => "bug_file_loc" size => "40" colspan => 2 %]
- [% IF NOT bug.check_can_change_field("bug_file_loc", 0, 1) %]
+ [% IF NOT bug.check_can_change_field("bug_file_loc", 0, 1)
+ AND is_safe_url(bug.bug_file_loc) %]
<a href="[% bug.bug_file_loc FILTER html %]">[% url_output FILTER none %]</a>
[% ELSE %]
[% url_output FILTER none %]
@@ -651,8 +609,13 @@
<label for="keywords" accesskey="k">
<b><a href="describekeywords.cgi"><u>K</u>eywords</a></b></label>:
</td>
- [% PROCESS input inputname => "keywords" size => 40 colspan => 2
- value => bug.keywords.join(', ') %]
+ <td class="field_value" colspan="2">
+ [% INCLUDE bug/field.html.tmpl
+ bug = bug, field = bug_fields.keywords, value = bug.keywords
+ editable = bug.check_can_change_field("keywords", 0, 1),
+ no_tds = 1
+ %]
+ </td>
</tr>
[% END %]
[% END %]
@@ -662,13 +625,13 @@
[%############################################################################%]
[% BLOCK section_dependson_blocks %]
<tr>
- [% PROCESS dependencies
- dep = { title => "Depends on", fieldname => "dependson" } %]
+ [% INCLUDE dependencies
+ field = bug_fields.dependson deps = bug.depends_on_obj %]
</tr>
<tr>
- [% PROCESS dependencies accesskey = "b"
- dep = { title => "<u>B</u>locks", fieldname => "blocked" } %]
+ [% INCLUDE dependencies
+ field = bug_fields.blocked deps = bug.blocks_obj %]
<tr>
<th> </th>
@@ -691,103 +654,93 @@
[% BLOCK section_restrict_visibility %]
[% RETURN UNLESS bug.groups.size %]
- [% inallgroups = 1 %]
- [% inagroup = 0 %]
- [% emitted_description = 0 %]
+ <div class="bz_group_visibility_section">
+ [% inallgroups = 1 %]
+ [% inagroup = 0 %]
+ [% emitted_description = 0 %]
- [% FOREACH group = bug.groups %]
- [% SET inallgroups = 0 IF NOT group.ingroup %]
- [% SET inagroup = 1 IF group.ison %]
+ [% FOREACH group = bug.groups %]
+ [% SET inallgroups = 0 IF NOT group.ingroup %]
+ [% SET inagroup = 1 IF group.ison %]
- [% NEXT IF group.mandatory %]
+ [% NEXT IF group.mandatory %]
- [% IF NOT emitted_description %]
- [% emitted_description = 1 %]
- <table>
- <tr>
- <td class="field_label">
- <label id="bz_restrict_group_visibility_label"><b>Restrict Group Visibility</b>:</label>
- </td>
- <td>
- <div id="bz_restrict_group_visibility_help">
- <b>Only users in all of the selected groups can view this [% terms.bug %]:</b>
- <br>
- <small>
- (Unchecking all boxes makes this a more public [% terms.bug %].)
- </small>
- </div>
- [% END %]
+ [% IF NOT emitted_description %]
+ [% emitted_description = 1 %]
+ <div id="bz_restrict_group_visibility_help">
+ <b>Only users in all of the selected groups can view this
+ [%+ terms.bug %]:</b>
+ <p class="instructions">
+ Unchecking all boxes makes this a more public [% terms.bug %].
+ </p>
+ </div>
+ [% END %]
- [% IF group.ingroup %]
- <input type="hidden" name="defined_bit-[% group.bit %]" value="1">
- [% END %]
- <input type="checkbox" value="1" name="bit-[% group.bit %]" id="bit-[% group.bit %]"
- [% ' checked="checked"' IF group.ison %]
- [% ' disabled="disabled"' IF NOT group.ingroup %]>
- <label for="bit-[% group.bit %]">[% group.description FILTER html_light %]</label>
- <br>
- [% END %]
+ [% IF group.ingroup %]
+ <input type="hidden" name="defined_groups"
+ value="[% group.name FILTER html %]">
+ [% END %]
- [% IF emitted_description %]
- [% IF NOT inallgroups %]
- <b>Only members of a group can change the visibility of [% terms.abug %] for that group.</b>
+ <input type="checkbox" value="[% group.name FILTER html %]"
+ name="groups" id="group_[% group.bit %]"
+ [% ' checked="checked"' IF group.ison %]
+ [% ' disabled="disabled"' IF NOT group.ingroup %]>
+ <label for="group_[% group.bit %]">
+ [%- group.description FILTER html_light %]</label>
<br>
[% END %]
- </td>
- </tr>
- [% "</table>" IF NOT inagroup %]
- [% END %]
- [% IF inagroup %]
- [% IF NOT emitted_description %]
- [% emitted_description = 1 %]
- <table>
+ [% IF emitted_description %]
+ [% IF NOT inallgroups %]
+ <p class="instructions">Only members of a group can change the
+ visibility of [% terms.abug %] for that group.</p>
+ [% END %]
[% END %]
- <tr>
- <td class="field_label">
- <label id="bz_enable_role_visibility_label"><b>Enable Role Visibility</b>:</label>
- </td>
- <td>
- <div id="bz_enable_role_visibility_help">
- <b>Users in the roles selected below can always view this [% terms.bug %]:</b>
- <br>
- <small>
- (The assignee
- [% IF (Param('useqacontact')) %]
- and QA contact
- [% END %]
- can always see [% terms.abug %], and this section does not take effect unless
- the [% terms.bug %] is restricted to at least one group.)
- </small>
+
+ [% IF inagroup %]
+ <div id="bz_enable_role_visibility_help">
+ <b>Users in the roles selected below can always view
+ this [% terms.bug %]:</b>
+ </div>
+ <div id="bz_enable_role_visibility">
+ <div>
+ [% user_can_edit_accessible =
+ bug.check_can_change_field("reporter_accessible", 0, 1)
+ %]
+ [% IF user_can_edit_accessible %]
+ <input type="hidden" name="defined_reporter_accessible" value="1">
+ [% END %]
+ <input type="checkbox" value="1"
+ name="reporter_accessible" id="reporter_accessible"
+ [% " checked" IF bug.reporter_accessible %]
+ [% " disabled=\"disabled\"" UNLESS user_can_edit_accessible %]>
+ <label for="reporter_accessible">Reporter</label>
</div>
<div>
- <div>
- [% user_can_edit_accessible = bug.check_can_change_field("reporter_accessible", 0, 1) %]
- [% IF user_can_edit_accessible %]
- <input type="hidden" name="defined_reporter_accessible" value="1">
- [% END %]
- <input type="checkbox" value="1"
- name="reporter_accessible" id="reporter_accessible"
- [% " checked" IF bug.reporter_accessible %]
- [% " disabled=\"disabled\"" UNLESS user_can_edit_accessible %]>
- <label for="reporter_accessible">Reporter</label>
- </div>
- <div>
- [% user_can_edit_accessible = bug.check_can_change_field("cclist_accessible", 0, 1) %]
- [% IF user_can_edit_accessible %]
- <input type="hidden" name="defined_cclist_accessible" value="1">
- [% END %]
- <input type="checkbox" value="1"
- name="cclist_accessible" id="cclist_accessible"
- [% " checked" IF bug.cclist_accessible %]
- [% " disabled=\"disabled\"" UNLESS user_can_edit_accessible %]>
- <label for="cclist_accessible">CC List</label>
- </div>
+ [% user_can_edit_accessible =
+ bug.check_can_change_field("cclist_accessible", 0, 1)
+ %]
+ [% IF user_can_edit_accessible %]
+ <input type="hidden" name="defined_cclist_accessible" value="1">
+ [% END %]
+ <input type="checkbox" value="1"
+ name="cclist_accessible" id="cclist_accessible"
+ [% " checked" IF bug.cclist_accessible %]
+ [% " disabled=\"disabled\"" UNLESS user_can_edit_accessible %]>
+ <label for="cclist_accessible">CC List</label>
</div>
- </td>
- </tr>
- </table>
- [% END %]
+ <p class="instructions">
+ The assignee
+ [% IF (Param('useqacontact')) %]
+ and QA contact
+ [% END %]
+ can always see [% terms.abug %], and this section does not
+ take effect unless the [% terms.bug %] is restricted to at
+ least one group.
+ </p>
+ </div>
+ [% END %]
+ </div> [%# bz_group_visibility_section %]
[% END %]
[%############################################################################%]
@@ -800,7 +753,7 @@
<b>Reported</b>:
</td>
<td>
- [% bug.creation_ts FILTER time %] by [% INCLUDE user_identity user => bug.reporter %]
+ [% bug.creation_ts FILTER time %] by [% INCLUDE global/user.html.tmpl who = bug.reporter %]
</td>
</tr>
@@ -820,11 +773,10 @@
[%# Block for CC LIST #%]
[%############################################################################%]
[% BLOCK section_cclist %]
- [% IF user.id %]
<tr>
- <td class="field_label">
- <label for="newcc" accesskey="a"><b>CC List</b>:</label>
- </td>
+ <td class="field_label">
+ <label for="newcc" accesskey="a"><b>CC List</b>:</label>
+ </td>
<td>
[% IF user.id %]
[% IF NOT bug.cc || NOT bug.cc.contains(user.login) %]
@@ -853,47 +805,76 @@
including you
[% END %]
[% END %]
- <span id="cc_edit_area_showhide_container" class="bz_default_hidden">
- (<a href="#" id="cc_edit_area_showhide">edit</a>)
- </span>
- <div id="cc_edit_area">
- <div>
- <div>
- <label for="cc">
- <b>Add</b>
- </label>
- </div>
- [% INCLUDE global/userselect.html.tmpl
- id => "newcc"
- name => "newcc"
- value => ""
- size => 30
- multiple => 5
- %]
- </div>
- [% IF bug.cc %]
- <select id="cc" name="cc" multiple="multiple" size="5">
- [% FOREACH c = bug.cc %]
- <option value="[% c FILTER html %]">[% c FILTER html %]</option>
- [% END %]
- </select>
- [% IF user.id %]
- <br>
- <input type="checkbox" id="removecc" name="removecc">
- [%%]<label for="removecc">Remove selected CCs</label>
- <br>
- [% END %]
+ [% IF user.id || bug.cc.size %]
+ <span id="cc_edit_area_showhide_container" class="bz_default_hidden">
+ (<a href="#" id="cc_edit_area_showhide">[% IF user.id %]edit[% ELSE %]show[% END %]</a>)
+ </span>
[% END %]
+ <div id="cc_edit_area">
+ <br>
+ [% IF user.id %]
+ <div>
+ <div><label for="cc"><b>Add</b></label></div>
+ [% INCLUDE global/userselect.html.tmpl
+ id => "newcc"
+ name => "newcc"
+ value => ""
+ size => 30
+ classes => ["bz_userfield"]
+ multiple => 5
+ %]
+ </div>
+ [% END %]
+ [% IF bug.cc %]
+ <select id="cc" multiple="multiple" size="5"
+ [% IF bug.user.canedit %]name="cc"[% END %]>
+ [% FOREACH c = bug.cc %]
+ <option value="[% c FILTER email FILTER html %]">
+ [% c FILTER email FILTER html %]</option>
+ [% END %]
+ </select>
+ [% IF user.id && !bug.user.canedit %]
+ <input type="hidden" name="cc" value="[% user.login FILTER email FILTER html %]">
+ [% END %]
+ [% IF user.id AND (bug.user.canedit OR bug.cc.contains(user.login)) %]
+ <br>
+ <input type="checkbox" id="removecc" name="removecc">
+ <label for="removecc">
+ [% IF bug.user.canedit %]
+ Remove selected CCs
+ [% ELSE %]
+ Remove me from the CC list
+ [% END %]
+ </label>
+ <br>
+ [% END %]
+ [% END %]
</div>
- <script type="text/javascript">
- hideEditableField( 'cc_edit_area_showhide_container',
- 'cc_edit_area',
- 'cc_edit_area_showhide',
- '',
- '');
- </script>
+ [% IF user.id || bug.cc.size %]
+ <script type="text/javascript">
+ hideEditableField( 'cc_edit_area_showhide_container',
+ 'cc_edit_area',
+ 'cc_edit_area_showhide',
+ '',
+ '');
+ </script>
+ [% END %]
</td>
</tr>
+[% END %]
+
+[%############################################################################%]
+[%# Block for See Also #%]
+[%############################################################################%]
+[% BLOCK section_see_also %]
+ [% IF Param('use_see_also') || bug.see_also.size %]
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ field = bug_fields.see_also
+ value = bug.see_also
+ editable = bug.check_can_change_field('see_also', 0, 1)
+ %]
+ </tr>
[% END %]
[% END %]
@@ -912,30 +893,18 @@
[% END %]
[% IF show_bug_flags %]
<tr>
- <td class="field_label">
+ <td class="field_label flags_label">
<label><b>Flags:</b></label>
</td>
<td></td>
</tr>
<tr>
<td colspan="2">
- [% IF user.id %]
[% IF bug.flag_types.size > 0 %]
[% PROCESS "flag/list.html.tmpl" flag_no_header = 1
flag_types = bug.flag_types
any_flags_requesteeble = bug.any_flags_requesteeble %]
[% END %]
- [% ELSE %]
- [% FOREACH type = bug.flag_types %]
- [% FOREACH flag = type.flags %]
- [% flag.setter.nick FILTER html %]:
- [%+ type.name FILTER html FILTER no_break %][% flag.status %]
- [%+ IF flag.requestee %]
- ([% flag.requestee.nick FILTER html %])
- [% END %]<br>
- [% END %]
- [% END %]
- [% END %]
</td>
</tr>
[% END %]
@@ -947,14 +916,19 @@
[% BLOCK section_customfields %]
[%# *** Custom Fields *** %]
-
[% USE Bugzilla %]
[% FOREACH field = Bugzilla.active_custom_fields %]
<tr>
- [% PROCESS bug/field.html.tmpl value=bug.${field.name}
+ [% PROCESS bug/field.html.tmpl value = bug.${field.name}
editable = bug.check_can_change_field(field.name, 0, 1)
value_span = 2 %]
</tr>
+ [% IF extra_field_item %]
+ <tr>
+ <th class="field_label">[% extra_field_item.header FILTER none %]</th>
+ <td>[% extra_field_item.data FILTER none %]</td>
+ </tr>
+ [% END %]
[% END %]
[% END %]
@@ -977,37 +951,36 @@
[% BLOCK dependencies %]
- <th class="field_label">
- <label for="[% dep.fieldname %]"[% " accesskey=\"$accesskey\"" IF accesskey %]>
- [% dep.title %]</label>:
- </th>
- <td>
- <span id="[% dep.fieldname %]_input_area">
- [% IF bug.check_can_change_field(dep.fieldname, 0, 1) %]
- <input name="[% dep.fieldname %]" id="[% dep.fieldname %]"
- value="[% bug.${dep.fieldname}.join(', ') %]">
+ [% INCLUDE "bug/field-label.html.tmpl" %]
+
+ <td>
+ <span id="[% field.name FILTER html %]_input_area">
+ [% IF bug.check_can_change_field(field.name, 0, 1) %]
+ <input name="[% field.name FILTER html %]"
+ id="[% field.name FILTER html %]" class="text_input"
+ value="[% bug.${field.name}.join(', ') FILTER html %]">
[% END %]
</span>
- [% FOREACH depbug = bug.${dep.fieldname} %]
- [% depbug FILTER bug_link(depbug) FILTER none %][% " " %]
+ [% FOREACH dep_bug = deps %]
+ [% dep_bug.id FILTER bug_link(dep_bug, use_alias => 1)
+ FILTER none %][% " " %]
[% END %]
- [% IF bug.check_can_change_field(dep.fieldname, 0, 1) %]
- <span id="[% dep.fieldname %]_edit_container" class="edit_me bz_default_hidden" >
- (<a href="#" id="[% dep.fieldname %]_edit_action">edit</a>)
+ [% IF bug.check_can_change_field(field.name, 0, 1) %]
+ <span id="[% field.name FILTER html %]_edit_container"
+ class="edit_me bz_default_hidden">
+ (<a href="#" id="[% field.name FILTER html %]_edit_action">edit</a>)
</span>
<script type="text/javascript">
- hideEditableField('[% dep.fieldname %]_edit_container',
- '[% dep.fieldname %]_input_area',
- '[% dep.fieldname %]_edit_action',
- '[% dep.fieldname %]',
- "[% bug.${dep.fieldname}.join(', ') %]");
+ hideEditableField('[% field.name FILTER js %]_edit_container',
+ '[% field.name FILTER js %]_input_area',
+ '[% field.name FILTER js %]_edit_action',
+ '[% field.name FILTER js %]',
+ '[% bug.${field.name}.join(', ') FILTER js %]');
</script>
[% END %]
</td>
- [% accesskey = undef %]
-
[% END %]
[%############################################################################%]
@@ -1070,9 +1043,9 @@
[% PROCESS formattimeunit time_unit=bug.estimated_time - (bug.actual_time + bug.remaining_time) %]
</td>
<td>
- <input name="deadline" id="deadline" value="[% bug.deadline %]"
- size="10" maxlength="10"><br />
- <small>(YYYY-MM-DD)</small>
+ [% INCLUDE bug/field.html.tmpl
+ field = bug_fields.deadline, value = bug.deadline, no_tds = 1
+ editable = bug.check_can_change_field('deadline', 0, 1) %]
</td>
</tr>
<tr>
@@ -1086,28 +1059,91 @@
[% END %]
[%############################################################################%]
+[%# Block for the Additional Comments box #%]
+[%############################################################################%]
+
+[% BLOCK comment_box %]
+ <div id="add_comment" class="bz_section_additional_comments">
+ [% IF user.id %]
+ <label for="comment" accesskey="c"><b>Additional
+ <u>C</u>omments</b></label>:
+
+ [% IF user.is_insider %]
+ <input type="checkbox" name="comment_is_private" value="1"
+ id="newcommentprivacy"
+ onClick="updateCommentTagControl(this, 'comment')">
+ <label for="newcommentprivacy">
+ Make comment private (visible only to members of the
+ <strong>[% Param('insidergroup') FILTER html %]</strong> group)
+ </label>
+ [% END %]
+
+ <!-- This table keeps the submit button aligned with the box. -->
+ <table><tr><td>
+ [% INCLUDE global/textarea.html.tmpl
+ name = 'comment'
+ id = 'comment'
+ minrows = 10
+ maxrows = 25
+ cols = constants.COMMENT_COLS
+ %]
+ [% Hook.process("after_comment_textarea", 'bug/edit.html.tmpl') %]
+ <br>
+ [% PROCESS commit_button id=""%]
+
+ <table id="bug_status_bottom"
+ class="status" cellspacing="0" cellpadding="0">
+ <tr>
+ <td class="field_label">
+ <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
+ </td>
+ <td>
+ [% PROCESS bug/knob.html.tmpl %]
+ </td>
+ </tr>
+ </table>
+ </td></tr></table>
+
+ [%# For logged-out users %]
+ [% ELSE %]
+ <table>
+ <tr>
+ <td>
+ <fieldset>
+ <legend>Note</legend>
+ You need to
+ <a href="show_bug.cgi?id=
+ [%- bug.bug_id %]&GoAheadAndLogIn=1">log in</a>
+ before you can comment on or make changes to this [% terms.bug %].
+ </fieldset>
+ </td>
+ </tr>
+ </table>
+ [% END %]
+ </div>
+[% END %]
+
+[%############################################################################%]
[%# Block for SELECT fields #%]
[%############################################################################%]
[% BLOCK select %]
- [% IF NOT no_td %]
<td>
- [% END %]
- [% IF bug.check_can_change_field(selname, 0, 1) AND bug.choices.${selname}.size > 1 %]
+ [% IF bug.check_can_change_field(selname, 0, 1)
+ AND bug.choices.${selname}.size > 1 %]
<select id="[% selname %]" name="[% selname %]">
[% FOREACH x = bug.choices.${selname} %]
- <option value="[% x FILTER html %]"
- [% " selected" IF x == bug.${selname} %]>[% x FILTER html %]
+ [% NEXT IF NOT x.is_active AND x.name != bug.${selname} %]
+ <option value="[% x.name FILTER html %]"
+ [% " selected" IF x.name == bug.${selname} %]>
+ [%- x.name FILTER html %]
</option>
[% END %]
</select>
[% ELSE %]
[% bug.${selname} FILTER html %]
[% END %]
- [% IF NOT no_td %]
</td>
- [% END %]
- [% no_td = 0 %]
[% END %]
[%############################################################################%]
@@ -1120,7 +1156,7 @@
[% END %]
[% val = value ? value : bug.$inputname %]
[% IF bug.check_can_change_field(inputname, 0, 1) %]
- <input id="[% inputname %]" name="[% inputname %]"
+ <input id="[% inputname %]" name="[% inputname %]" class="text_input"
value="[% val FILTER html %]"[% " size=\"$size\"" IF size %]
[% " maxlength=\"$maxlength\"" IF maxlength %]
[% " spellcheck=\"$spellcheck\"" IF spellcheck %]>
@@ -1143,23 +1179,11 @@
[% value = undef %]
[% spellcheck = undef %]
[% END %]
-
-[%############################################################################%]
-[%# Block for user identities. Wraps the information inside of an hCard. #%]
-[%############################################################################%]
-
-[% BLOCK user_identity %]
- <span class="vcard">
- [% FILTER collapse %]
- [% IF user.name %]
- <a class="email" href="mailto:[% user.email FILTER html %]"
- title="[% user.email FILTER html %]"
- ><span class="fn">[% user.name FILTER html %]</span
- ></a>
- [% ELSE %]
- <a class="fn email" href="mailto:[% user.email FILTER html %]">
- [% user.email FILTER html %]</a>
- [% END %]
- [% END %]</span>
+[% BLOCK commit_button %]
+ [% IF user.id %]
+ <div class="knob-buttons">
+ <input type="submit" value="Save Changes"
+ id="commit[% id FILTER css_class_quote %]">
+ </div>
+ [% END %]
[% END %]
-
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/field-events.js.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/field-events.js.tmpl
new file mode 100644
index 0000000..13ec18d
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/bug/field-events.js.tmpl
@@ -0,0 +1,57 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is the San Jose State
+ # University Foundation. Portions created by the Initial Developer
+ # are Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # field: a Bugzilla::Field object
+ # product: (optional) a Bugzilla::Product object. When specified,
+ # components are restricted to this product.
+ #%]
+
+[% FOREACH controlled_field = field.controls_visibility_of %]
+ showFieldWhen('[% controlled_field.name FILTER js %]',
+ '[% field.name FILTER js %]', [
+ [%- FOREACH visibility_value = controlled_field.visibility_values -%]
+ '[%- visibility_value.name FILTER js -%]'[% "," UNLESS loop.last %]
+ [%- END %]
+ ]);
+[% END %]
+
+[% legal_values = [] %]
+[% IF field.name == "component" AND product %]
+ [% legal_values = product.components %]
+[% ELSE %]
+ [% legal_values = field.legal_values %]
+[% END %]
+
+[% FOREACH legal_value = legal_values %]
+ [% FOREACH controlled_field = legal_value.controlled_values.keys %]
+ [% SET cont_ids = [] %]
+ [% FOREACH val = legal_value.controlled_values.$controlled_field %]
+ [% cont_ids.push(val.id) %]
+ [% END %]
+ [% NEXT IF !cont_ids.size %]
+ showValueWhen('[% controlled_field FILTER js %]',
+ [[% cont_ids.join(',') FILTER js %]],
+ '[% field.name FILTER js %]',
+ [% legal_value.id FILTER js %]);
+ [% END %]
+[% END %]
+[% IF field.name == 'classification' %]
+ YAHOO.util.Event.on('product', 'change', setClassification);
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/field-help.none.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/field-help.none.tmpl
new file mode 100644
index 0000000..7ae9991
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/bug/field-help.none.tmpl
@@ -0,0 +1,241 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# This file describes both bug fields and search fields.
+ # As calling this template once per label is very expensive,
+ # it is called only by Template.pm. %]
+
+[% RETURN UNLESS in_template_var %]
+
+[% vars.help_html = {
+
+# Note that all these keys here are in alphabetical order, though
+# search-specific fields are at the bottom.
+
+##############
+# Bug Fields #
+##############
+
+alias =>
+ "A short, unique name assigned to $terms.abug in order to assist with
+ looking it up and referring to it in other places in ${terms.Bugzilla}.",
+
+assigned_to =>
+ "The person in charge of resolving the ${terms.bug}.",
+
+blocked =>
+ "This $terms.bug must be resolved before the $terms.bugs listed in this
+ field can be resolved.",
+
+bug_file_loc =>
+ "$terms.Bugs can have a URL associated with them - for example, a"
+ _ " pointer to a web site where the problem is seen.",
+
+bug_id =>
+ "The numeric id of $terms.abug, unique within this entire installation"
+ _ " of ${terms.Bugzilla}.",
+
+bug_severity =>
+ "How severe the $terms.bug is, or whether it's an enhancement.",
+
+bug_status =>
+ "$terms.Abug may be in any of a number of states.",
+
+cc =>
+ "Users who may not have a direct role to play on this $terms.bug, but who
+ are interested in its progress.",
+
+classification =>
+ "$terms.Bugs are categorised into Classifications, Products and"
+ _ " Components. classifications is the top-level categorisation.",
+
+component =>
+ "Components are second-level categories; each belongs to a"
+ _ " particular Product. Select a Product to narrow down this list.",
+
+creation_ts =>
+ "When the $terms.bug was filed.",
+
+deadline =>
+ "The date that this $terms.bug must be resolved by, entered in YYYY-MM-DD
+ format.",
+
+delta_ts =>
+ "When this $terms.bug was last updated.",
+
+dependson =>
+ "The $terms.bugs listed here must be resolved before this $terms.bug
+ can be resolved.",
+
+estimated_time =>
+ "The amount of time that has been estimated it will take to resolve
+ this ${terms.bug}.",
+
+keywords =>
+ "You can add keywords from a defined list to $terms.bugs, in order"
+ _ " to tag and group them.",
+
+longdesc =>
+ "$terms.Bugs have comments added to them by $terms.Bugzilla users."
+ _ " You can search for some text in those comments.",
+
+op_sys =>
+ "The operating system the $terms.bug was observed on.",
+
+percentage_complete =>
+ "How close to 100% done this $terms.bug is, by comparing its
+ $vars.field_descs.work_time to its ${vars.field_descs.estimated_time}.",
+
+priority =>
+ "Engineers prioritize their $terms.bugs using this field.",
+
+# Note that this has extra text added below if useclassification is on.
+product =>
+ "$terms.Bugs are categorised into Products and Components.",
+
+qa_contact =>
+ "The person responsible for confirming this $terms.bug if it is"
+ _ " unconfirmed, and for verifying the fix once the $terms.bug"
+ _ " has been resolved.",
+
+remaining_time =>
+ "The number of hours of work left on this $terms.bug, calculated by
+ subtracting the $vars.field_descs.work_time from the
+ ${vars.field_descs.estimated_time}.",
+
+rep_platform =>
+ "The hardware platform the $terms.bug was observed on.",
+
+reporter =>
+ "The person who filed this ${terms.bug}.",
+
+resolution =>
+ "If $terms.abug is in a resolved state, then one of these reasons"
+ _ " will be given for its resolution.",
+
+see_also =>
+ "This allows you to refer to $terms.bugs in other installations.
+ You can enter a URL to $terms.abug in the 'Add $terms.Bug URLs'
+ field to note that that $terms.bug is related to this one. You can
+ enter multiple URLs at once by separating them with a comma.
+
+ <p>You should normally use this field to refer to $terms.bugs in
+ <em>other</em> installations. For $terms.bugs in this
+ installation, it is better to use the $vars.field_descs.dependson and
+ $vars.field_descs.blocked fields.</p>",
+
+short_desc =>
+ "The $terms.bug summary is a short sentence which succinctly"
+ _ " describes what the $terms.bug is about.",
+
+status_whiteboard =>
+ "Each $terms.bug has a free-form single line text entry box for"
+ _ " adding tags and status information.",
+
+target_milestone =>
+ "The $vars.field_descs.target_milestone field is used to define when the"
+ _ " engineer the $terms.bug is assigned to expects to fix it.",
+
+version =>
+ "The version field defines the version of the software the"
+ _ " $terms.bug was found in.",
+
+votes =>
+ "Some $terms.bugs can be voted for, and you can limit your search to"
+ _ " $terms.bugs with more than a certain number of votes.",
+
+work_time =>
+ "The total amount of time spent on this $terms.bug so far.",
+
+##########################
+# Search-specific fields #
+##########################
+
+chfield =>
+ "You can search for specific types of change - this field defines"
+ _" which field you are interested in changes for.",
+
+# Duplicated to chfieldto below, also.
+chfieldfrom =>
+ "Specify the start and end dates either in YYYY-MM-DD format
+ optionally followed by HH:mm, in 24 hour clock), or in relative
+ dates such as 1h, 2d, 3w, 4m, 5y, which respectively mean one hour,
+ two days, three weeks, four months, or five years ago. 0d is last
+ midnight, and 0h, 0w, 0m, 0y is the beginning of this hour, week,
+ month, or year.",
+
+chfieldvalue =>
+ "The value the field defined above changed to during that time.",
+
+content =>
+ "This is a field available in searches that does a Google-like
+ 'full-text' search on the $vars.field_descs.short_desc and
+ $vars.field_descs.longdesc fields.",
+
+# Duplicated to email2 below, also.
+email1 =>
+ "Every $terms.bug has people associated with it in different"
+ _ " roles. Here, you can search on what people are in what role.",
+
+} %]
+
+[% vars.help_html.email2 = vars.help_html.email1 %]
+[% vars.help_html.chfieldto = vars.help_html.chfieldfrom %]
+[% vars.help_html.deadlinefrom = vars.help_html.deadline %]
+[% vars.help_html.deadlineto = vars.help_html.deadline %]
+
+[% help_all_note = BLOCK %]
+ <strong>Note:</strong> When searching, selecting the option "All"
+ only finds [% terms.bugs %] whose value for this field is literally
+ the word "All".
+[% END %]
+[% FOREACH all_field = ['op_sys', 'rep_platform'] %]
+ [% vars.help_html.$all_field = vars.help_html.$all_field _ ' ' _ help_all_note %]
+[% END %]
+
+[% IF Param('useclassification') %]
+ [% vars.help_html.product = vars.help_html.product
+ _ " Select a Classification to narrow down this list." %]
+[% END %]
+
+[% FOREACH help_field = bug_fields.keys %]
+
+ [%# Add help for custom fields. %]
+ [% IF !vars.help_html.${help_field}.defined %]
+ [% SET field_type = bug_fields.${help_field}.type %]
+ [% field_type_desc = BLOCK -%]
+ [% field_types.$field_type FILTER html %]
+ [%- END %]
+ [% vars.help_html.${help_field} =
+ "A custom $field_type_desc field in this installation"
+ _ " of ${terms.Bugzilla}." %]
+ [% END %]
+
+ [%# Add help for the search types, for query.cgi. %]
+ [% type_desc = BLOCK %]
+ The type of [% vars.field_descs.${help_field} FILTER html %] search you
+ would like.
+ [% END %]
+ [% SET type_name = help_field _ '_type' %]
+ [% vars.help_html.$type_name = type_desc %]
+[% END %]
+
+[% Hook.process("end") %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/field-label.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/field-label.html.tmpl
new file mode 100644
index 0000000..ecb4931
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/bug/field-label.html.tmpl
@@ -0,0 +1,53 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # field: a Bugzilla::Field object
+ # desc_url: An alternate link to help for the field.
+ # hidden: True if the field label should start hidden.
+ # rowspan: a "rowspan" value for the label's <th>.
+ # tag_name: the tag to use to surround the label
+ # accesskey: access key for the label
+ #%]
+
+[% DEFAULT tag_name = "th" %]
+<[% tag_name FILTER html %] class="field_label [% ' bz_hidden_field' IF hidden %]
+ [%- ' required' IF field.is_mandatory && NOT bug.id %]"
+ id="field_label_[% field.name FILTER html %]"
+ [% IF rowspan %] rowspan="[% rowspan FILTER html %]"[% END %]>
+
+ [% IF editable %]
+ <label for="[% field.name FILTER html %]"[% IF accesskey %] accesskey="[% accesskey FILTER html %]"[% END %]>
+ [% END %]
+
+ <a
+ [% IF help_html.${field.name}.defined %]
+ title="[% help_html.${field.name} FILTER txt FILTER collapse FILTER html %]"
+ class="field_help_link"
+ [% END %]
+ [% IF desc_url %]
+ href="[% desc_url FILTER html %]"
+ [% ELSE %]
+ href="page.cgi?id=fields.html#[% field.name FILTER uri %]"
+ [% END %]
+ >[%- field_descs.${field.name} FILTER html %]:</a>
+
+ [% '</label>' IF editable %]
+</[% tag_name FILTER html %]>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/field.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/field.html.tmpl
index 664cbee..58f1b0c 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/field.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/field.html.tmpl
@@ -17,37 +17,51 @@
#
# Contributor(s): Myk Melez <myk@mozilla.org>
# Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Elliotte Martin <elliotte_martin@yahoo.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ # Reed Loden <reed@reedloden.com>
#%]
[%# INTERFACE:
# field: a Bugzilla::Field object
# value: The value of the field for this bug.
+ # override_legal_values (optional): The list of legal values, for select fields.
# editable: Whether the field should be displayed as an editable
# <input> or as just the plain text of its value.
# allow_dont_change: display the --do_not_change-- option for select fields.
# value_span: A colspan for the table cell containing
# the field value.
+ # no_tds: boolean; if true, don't display the label <th> or the
+ # wrapping <td> for the field.
+ # bug (optional): The current Bugzilla::Bug being displayed, or a hash
+ # with default field values being displayed on a page.
#%]
-<th class="field_label">
- [% IF editable %]
- <label for="[% field.name FILTER html %]">
- [% END %]
- [% field_descs.${field.name} FILTER html %]:
- [% '</label>' IF editable %]
-</th>
+[% SET hidden = 0 %]
+[% IF bug AND !field.is_visible_on_bug(bug) %]
+ [% SET hidden = 1 %]
+[% END %]
-<td class="field_value" [% "colspan=\"$value_span\"" FILTER none IF value_span %]>
+[% IF NOT no_tds %]
+ [% PROCESS "bug/field-label.html.tmpl" %]
+ <td class="field_value [% ' bz_hidden_field' IF hidden %]"
+ id="field_container_[% field.name FILTER html %]"
+ [% " colspan=\"$value_span\"" FILTER none IF value_span %]>
+[% END %]
+[% Hook.process('start_field_column') %]
[% IF editable %]
[% SWITCH field.type %]
[% CASE constants.FIELD_TYPE_FREETEXT %]
- <input id="[% field.name FILTER html %]" name="[% field.name FILTER html %]"
+ <input id="[% field.name FILTER html %]" class="text_input"
+ name="[% field.name FILTER html %]"
value="[% value FILTER html %]" size="40"
- maxlength="[% constants.MAX_FREETEXT_LENGTH FILTER none %]">
+ maxlength="[% constants.MAX_FREETEXT_LENGTH FILTER none %]"
+ [% ' aria-required="true"' IF field.is_mandatory %]>
[% CASE constants.FIELD_TYPE_DATETIME %]
<input name="[% field.name FILTER html %]" size="20"
id="[% field.name FILTER html %]"
value="[% value FILTER html %]"
+ [% ' aria-required="true"' IF field.is_mandatory %]
onchange="updateCalendarFromField(this)">
<button type="button" class="calendar_button"
id="button_calendar_[% field.name FILTER html %]"
@@ -55,12 +69,32 @@
<span>Calendar</span>
</button>
- <div id="con_calendar_[% field.name FILTER html %]"
- class="yui-skin-sam"></div>
+ <div id="con_calendar_[% field.name FILTER html %]"></div>
<script type="text/javascript">
createCalendar('[% field.name FILTER js %]')
</script>
+ [% CASE constants.FIELD_TYPE_BUG_ID %]
+ <span id="[% field.name FILTER html %]_input_area">
+ <input name="[% field.name FILTER html %]" id="[% field.name FILTER html %]"
+ value="[% value FILTER html %]" size="7"
+ [% ' aria-required="true"' IF field.is_mandatory %]>
+
+ </span>
+
+ [% IF value %]
+ [% value FILTER bug_link(value, use_alias => 1) FILTER none %]
+ [% END %]
+ <span id="[% field.name FILTER html %]_edit_container" class="edit_me bz_default_hidden">
+ (<a href="#" id="[% field.name FILTER html %]_edit_action">edit</a>)
+ </span>
+ <script type="text/javascript">
+ hideEditableField('[% field.name FILTER js %]_edit_container',
+ '[% field.name FILTER js %]_input_area',
+ '[% field.name FILTER js %]_edit_action',
+ '[% field.name FILTER js %]',
+ "[% value FILTER js %]");
+ </script>
[% CASE [ constants.FIELD_TYPE_SINGLE_SELECT
constants.FIELD_TYPE_MULTI_SELECT ] %]
<select id="[% field.name FILTER html %]"
@@ -71,6 +105,7 @@
[% SET field_size = field.legal_values.size %]
[% END %]
size="[% field_size FILTER html %]" multiple="multiple"
+ [% ' aria-required="true"' IF field.is_mandatory %]
[% END %]
>
[% IF allow_dont_change %]
@@ -79,10 +114,25 @@
[% dontchange FILTER html %]
</option>
[% END %]
- [% FOREACH legal_value = field.legal_values %]
- <option value="[% legal_value FILTER html %]"
- [%- " selected=\"selected\"" IF value.contains(legal_value).size %]>
- [%- legal_value FILTER html %]</option>
+ [% IF override_legal_values %]
+ [% legal_values = override_legal_values %]
+ [% ELSE %]
+ [% legal_values = field.legal_values %]
+ [% END %]
+ [% FOREACH legal_value = legal_values %]
+ [% NEXT IF NOT legal_value.is_active AND NOT value.contains(legal_value.name).size %]
+ <option value="[% legal_value.name FILTER html %]"
+ id="v[% legal_value.id FILTER html %]_
+ [%- field.name FILTER html %]"
+ [%# We always show selected values, even if they should be
+ # hidden %]
+ [% IF value.contains(legal_value.name).size %]
+ selected="selected"
+ [% ELSIF bug AND !legal_value.is_visible_on_bug(bug) %]
+ class="bz_hidden_option" disabled="disabled"
+ [% END %]>
+ [%- display_value(field.name, legal_value.name) FILTER html ~%]
+ </option>
[% END %]
</select>
[%# When you pass an empty multi-select in the web interface,
@@ -95,15 +145,110 @@
[% IF field.type == constants.FIELD_TYPE_MULTI_SELECT %]
<input type="hidden" name="defined_[% field.name FILTER html %]">
[% END %]
+
+ <script type="text/javascript">
+ <!--
+ initHidingOptionsForIE('[% field.name FILTER js %]');
+ [%+ INCLUDE "bug/field-events.js.tmpl"
+ field = field, product = bug.product_obj %]
+ //-->
+ </script>
+
[% CASE constants.FIELD_TYPE_TEXTAREA %]
[% INCLUDE global/textarea.html.tmpl
id = field.name name = field.name minrows = 4 maxrows = 8
- cols = 60 defaultcontent = value %]
+ cols = 60 defaultcontent = value mandatory = field.is_mandatory %]
+ [% CASE constants.FIELD_TYPE_BUG_URLS %]
+ [% '<ul class="bug_urls">' IF value.size %]
+ [% FOREACH bug_url = value %]
+ <li>
+ [% PROCESS bug_url_link bug_url = bug_url %]
+ <label><input type="checkbox" value="[% bug_url.name FILTER html %]"
+ name="remove_[% field.name FILTER html %]">
+ Remove</label>
+ </li>
+ [% END %]
+ [% '</ul>' IF value.size %]
+
+ [% IF Param('use_see_also') %]
+ <span id="container_showhide_[% field.name FILTER html %]"
+ class="bz_default_hidden">
+ <a href="#" id="showhide_[% field.name FILTER html %]">(add)</a>
+ </span>
+ <div id="container_[% field.name FILTER html %]">
+ <label for="[% field.name FILTER html %]">
+ <strong>Add [% terms.Bug %] URLs:</strong>
+ </label><br>
+ <input type="text" id="[% field.name FILTER html %]" size="40"
+ class="text_input" name="[% field.name FILTER html %]">
+ </div>
+ <script type="text/javascript">
+ setupEditLink('[% field.name FILTER js %]');
+ </script>
+ [% END %]
+ [% CASE constants.FIELD_TYPE_KEYWORDS %]
+ <div id="keyword_container">
+ <input type="text" id="[% field.name FILTER html %]" size="40"
+ class="text_input" name="[% field.name FILTER html %]"
+ value="[% value FILTER html %]">
+ <div id="keyword_autocomplete"></div>
+ </div>
+ <script type="text/javascript" defer="defer">
+ YAHOO.bugzilla.keyword_array = [
+ [%- FOREACH keyword = all_keywords %]
+ [%-# %]"[% keyword.name FILTER js %]"
+ [%- "," IF NOT loop.last %][% END %]];
+ YAHOO.bugzilla.keywordAutocomplete.init('[% field.name FILTER js %]',
+ 'keyword_autocomplete');
+ </script>
[% END %]
-[% ELSIF field.type == constants.FIELD_TYPE_TEXTAREA %]
- <div class="uneditable_textarea">[% value FILTER wrap_comment(60)
- FILTER html %]</div>
[% ELSE %]
- [% value.join(', ') FILTER html %]
+ [% SWITCH field.type %]
+ [% CASE constants.FIELD_TYPE_TEXTAREA %]
+ <div class="uneditable_textarea">[% value FILTER html %]</div>
+ [% CASE constants.FIELD_TYPE_BUG_ID %]
+ [% IF value %]
+ [% value FILTER bug_link(value, use_alias => 1) FILTER none %]
+ [% END %]
+ [% CASE [ constants.FIELD_TYPE_SINGLE_SELECT
+ constants.FIELD_TYPE_MULTI_SELECT ] %]
+ [% FOREACH val = value %]
+ [% display_value(field.name, val) FILTER html %]
+ [% ', ' UNLESS loop.last() %]
+ [% END %]
+ [% CASE constants.FIELD_TYPE_BUG_URLS %]
+ [% '<ul class="bug_urls">' IF value.size %]
+ [% FOREACH bug_url = value %]
+ <li>
+ [% PROCESS bug_url_link bug_url = bug_url %]
+ </li>
+ [% END %]
+ [% '</ul>' IF value.size %]
+ [% CASE %]
+ [% value.join(', ') FILTER html %]
+ [% END %]
[% END %]
-</td>
+[% Hook.process('end_field_column') %]
+[% '</td>' IF NOT no_tds %]
+
+[%# for reverse relationships, we show this pseudo-field after the main field %]
+[% IF bug.id && field.is_relationship %]
+ [% extra_field_item = {} %]
+ [% extra_field_item.header = field.reverse_desc _ ":" FILTER html %]
+ [% extra_field_item.data = BLOCK %]
+ [% FOREACH depbug = bug.related_bugs(field) %]
+ [% depbug.id FILTER bug_link(depbug, use_alias => 1) FILTER none %][% " " %]
+ [% END %]
+ [% END %]
+[% ELSE %]
+ [% extra_field_item = '' %]
+[% END %]
+
+[% BLOCK bug_url_link %]
+ [% IF bug_url.isa('Bugzilla::BugUrl::Bugzilla::Local') %]
+ [% bug_url.target_bug_id FILTER bug_link(bug_url.target_bug_id, use_alias => 1) FILTER none %]
+ [% ELSE %]
+ <a href="[% bug_url.name FILTER html %]">
+ [% bug_url.name FILTER html %]</a>
+ [% END %]
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/format_comment.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/format_comment.txt.tmpl
new file mode 100644
index 0000000..1567980
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/bug/format_comment.txt.tmpl
@@ -0,0 +1,60 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Marc Schumann.
+ # Portions created by Marc Schumann are Copyright (c) 2008 Marc Schumann.
+ # All rights reserved.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# NOTE: Everywhere you use this template, you must call
+ # "FILTER remove('^X')" on the result. This is unfortunately the only way
+ # to preserve leading whitespace in comments.
+ #%]
+
+[%# INTERFACE:
+ # comment: A Bugzilla::Comment object.
+ # is_bugmail: boolean; True if this comment is going into a plain-text
+ # bugmail.
+ #%]
+
+[%# Please don't use field-descs here. It can slow down Bugzilla. %]
+[% PROCESS 'global/variables.none.tmpl' %]
+
+[% SET comment_body = comment.body %]
+
+[% IF comment.type == constants.CMT_DUPE_OF %]
+X[% comment_body %]
+
+*** This [% terms.bug %] has been marked as a duplicate of [% terms.bug %] [%+ comment.extra_data %] ***
+[% ELSIF comment.type == constants.CMT_HAS_DUPE %]
+*** [% terms.Bug %] [%+ comment.extra_data %] has been marked as a duplicate of this [% terms.bug %]. ***
+[% ELSIF comment.type == constants.CMT_ATTACHMENT_CREATED %]
+Created attachment [% comment.extra_data %]
+[% IF is_bugmail %]
+ --> [% urlbase _ "attachment.cgi?id=" _ comment.extra_data _ "&action=edit" %]
+[% END %]
+[%+ comment.attachment.description %]
+
+[%+ comment.body %]
+[% ELSIF comment.type == constants.CMT_ATTACHMENT_UPDATED %]
+Comment on attachment [% comment.extra_data %]
+[% IF is_bugmail %]
+ --> [% urlbase _ "attachment.cgi?id=" _ comment.extra_data %]
+[% END %]
+[%+ comment.attachment.description %]
+
+[%+ comment.body %]
+[% ELSE %]
+X[% Hook.process('type') %]
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/knob.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/knob.html.tmpl
index 4cf6031..ac14e6d 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/knob.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/knob.html.tmpl
@@ -23,71 +23,33 @@
[% PROCESS global/variables.none.tmpl %]
<div id="status">
- [% initial_action_shown = 0 %]
- [% show_resolution = 0 %]
- [% bug_status_select_displayed = 0 %]
+ [% PROCESS bug/field.html.tmpl
+ no_tds = 1
+ field = bug_fields.bug_status
+ value = bug.bug_status
+ override_legal_values = bug.choices.bug_status
+ editable = bug.choices.bug_status.size > 1
+ %]
- [% closed_status_array = [] %]
- [%# These actions are based on the current custom workflow. %]
- [% FOREACH bug_status = bug.status.can_change_to %]
- [% NEXT IF bug.isunconfirmed && bug_status.is_open && !bug.user.canconfirm %]
- [% NEXT IF bug.isopened && !bug.isunconfirmed && bug_status.is_open && !bug.user.canedit %]
- [% NEXT IF (!bug_status.is_open || !bug.isopened) && !bug.user.canedit && !bug.user.isreporter %]
- [%# Special hack to only display UNCO or REOP when reopening, but not both;
- # for compatibility with older versions. %]
- [% NEXT IF !bug.isopened && (bug.everconfirmed && bug_status.name == "UNCONFIRMED"
- || !bug.everconfirmed && bug_status.name == "REOPENED") %]
- [% IF NOT bug_status_select_displayed %]
- <select name="bug_status" id="bug_status">
- [% bug_status_select_displayed = 1 %]
- [% END %]
- [% PROCESS initial_action %]
- [% NEXT IF bug_status.name == bug.bug_status %]
- <option value="[% bug_status.name FILTER html %]">
- [% get_status(bug_status.name) FILTER html %]
- </option>
- [% IF !bug_status.is_open %]
- [% show_resolution = 1 %]
- [% filtered_status = bug_status.name FILTER js %]
- [% closed_status_array.push( filtered_status ) %]
- [% END %]
+ [% IF bug.resolution
+ OR bug.check_can_change_field('resolution', bug.resolution, 1)
+ %]
+ <noscript><br>resolved as </noscript>
[% END %]
- [%# These actions are special and are independent of the workflow. %]
- [% IF bug.user.canedit || bug.user.isreporter %]
- [% IF NOT bug_status_select_displayed %]
- <select name="bug_status" id="bug_status">
- [% bug_status_select_displayed = 1 %]
- [% END %]
- [% IF bug.isopened %]
- [% IF bug.resolution %]
- [% PROCESS initial_action %]
- [% END %]
- [% ELSIF bug.resolution != "MOVED" || bug.user.canmove %]
- [% PROCESS initial_action %]
- [% show_resolution = 1 %]
- [% END %]
- [% END %]
- [% IF bug_status_select_displayed %]
- </select>
- [% ELSE %]
- [% get_status(bug.bug_status) FILTER html %]
- [% IF bug.resolution %]
- [%+ get_resolution(bug.resolution) FILTER html %]
- [% IF bug.dup_id %]
- <span id="duplicate_display">of
- [% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]</span>
- [% END %]
- [% END %]
- [% END %]
- [% IF bug.user.canedit || bug.user.isreporter %]
- [% IF show_resolution %]
- <noscript><br>resolved as </noscript>
- <span id="resolution_settings">[% PROCESS select_resolution %]</span>
- [% END %]
+ <span id="resolution_settings">
+ [% PROCESS bug/field.html.tmpl
+ no_tds = 1
+ field = bug_fields.resolution
+ value = bug.resolution
+ override_legal_values = bug.choices.resolution
+ editable = bug.check_can_change_field('resolution', bug.resolution, 1)
+ %]
+ </span>
+
+ [% IF bug.check_can_change_field('dup_id', 0, 1) %]
<noscript><br> duplicate</noscript>
-
- <span id="duplicate_settings">of
+ <span id="duplicate_settings">of
<span id="dup_id_container" class="bz_default_hidden">
[% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
(<a href="#" id="dup_id_edit_action">edit</a>)
@@ -95,15 +57,27 @@
><input id="dup_id" name="dup_id" size="6"
value="[% bug.dup_id FILTER html %]">
</span>
+ [% IF bug.dup_id %]
+ <noscript>[% bug.dup_id FILTER bug_link(bug.dup_id) FILTER none %]</noscript>
+ [% END %]
<div id="dup_id_discoverable" class="bz_default_hidden">
<a href="#" id="dup_id_discoverable_action">Mark as Duplicate</a>
</div>
+ [% ELSIF bug.dup_id %]
+ <noscript><br> duplicate</noscript>
+ <span id="duplicate_display">of
+ [% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]</span>
[% END %]
</div>
+
<script type="text/javascript">
- var close_status_array = new Array("[% closed_status_array.join('", "') FILTER replace(',$', '')
- FILTER none %]");
- YAHOO.util.Dom.setStyle('dup_id_discoverable', 'display', 'block');
+ var close_status_array = [
+ [% FOREACH status = bug.choices.bug_status %]
+ [% NEXT IF status.is_open %]
+ '[% status.name FILTER js %]'[% ',' UNLESS loop.last %]
+ [% END %]
+ ];
+ YAHOO.util.Dom.removeClass('dup_id_discoverable', 'bz_default_hidden');
hideEditableField( "dup_id_container", "dup_id", 'dup_id_edit_action',
'dup_id', '[% bug.dup_id FILTER js %]' )
showHideStatusItems( "", ['[% "is_duplicate" IF bug.dup_id %]',
@@ -120,31 +94,7 @@
YAHOO.util.Event.addListener( window, 'load', showHideStatusItems,
['[% "is_duplicate" IF bug.dup_id %]',
'[% bug.bug_status FILTER js %]'] );
+
+ [% INCLUDE "bug/field-events.js.tmpl" field = select_fields.bug_status %]
+ [% INCLUDE "bug/field-events.js.tmpl" field = select_fields.resolution %]
</script>
-
-[%# Common actions %]
-
-[% BLOCK initial_action %]
- [% IF !initial_action_shown %]
- <option selected value="[% bug.bug_status FILTER html %]">
- [% get_status(bug.bug_status) FILTER html %]
- </option>
- [% IF !bug.isopened %]
- [% show_resolution = 1 %]
- [% filtered_status = bug.bug_status FILTER js %]
- [% closed_status_array.push(filtered_status) %]
- [% END %]
- [% initial_action_shown = 1 %]
- [% END %]
-[% END %]
-
-[% BLOCK select_resolution %]
- <select name="resolution" id="resolution">
- [% FOREACH r = bug.choices.resolution %]
- [% NEXT IF r == "MOVED" && bug.resolution != "MOVED" %]
- <option value="[% r FILTER html %]"
- [% "selected" IF r == bug.resolution %]>
- [% get_resolution(r) FILTER html %]</option>
- [% END %]
- </select>
-[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/link.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/link.html.tmpl
new file mode 100644
index 0000000..b138668
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/bug/link.html.tmpl
@@ -0,0 +1,61 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # bug: a Bugzilla::Bug object
+ # link_text: the text that we're highlighting.
+ # use_alias: boolean; If true, we display the bug's alias as the link
+ # text instead of link_text.
+ # comment_num: If defined, make this a link to that comment on the bug.
+ # full_url: boolean; If true, generate links that include the full
+ # urlbase. (This is for emails, mostly.)
+ #%]
+
+[% IF !bug %]
+ <missing>
+ [% RETURN %]
+[% END %]
+
+[%# We use "FILTER none" here because link_title is filtered down below. %]
+[% link_title = BLOCK %]
+ [% display_value('bug_status', bug.bug_status) FILTER none %]
+ [%+ display_value('resolution', bug.resolution) FILTER none %]
+[% END %]
+
+[% IF user.can_see_bug(bug) %]
+ [% link_title = link_title _ ' - ' _ bug.short_desc %]
+
+ [% IF use_alias && bug.alias %]
+ [% link_text = bug.alias %]
+ [% END %]
+[% END %]
+
+[% SET anchor = '' %]
+[% IF comment_num.defined %]
+ [% anchor = "#c$comment_num" %]
+[% END %]
+
+<a class="bz_bug_link
+ bz_status_[% bug.bug_status FILTER css_class_quote %]
+ [% ' bz_closed' IF !bug.isopened %]"
+ title="[% link_title FILTER collapse FILTER html %]"
+ href="[% urlbase FILTER html IF full_url %]show_bug.cgi?id=
+ [%~ bug.id FILTER uri %][% anchor FILTER html %]">
+ [%~ link_text FILTER html %]</a>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/navigate.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/navigate.html.tmpl
index 7b8f3c8..46b92ae 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/navigate.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/navigate.html.tmpl
@@ -16,17 +16,20 @@
# Rights Reserved.
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
#%]
+[% RETURN IF !bug %]
+
[% PROCESS global/variables.none.tmpl %]
[% IF bottom_navigator == 1 %]
<ul class="related_actions">
<li><a href="show_bug.cgi?format=multiple&id=
- [% bug.bug_id FILTER url_quote %]">Format For Printing</a></li>
+ [% bug.bug_id FILTER uri %]">Format For Printing</a></li>
<li> - <a href="show_bug.cgi?ctype=xml&id=
- [% bug.bug_id FILTER url_quote %]">XML</a></li>
+ [% bug.bug_id FILTER uri %]">XML</a></li>
<li> - <a href="enter_bug.cgi?cloned_bug_id=
- [% bug.bug_id FILTER url_quote %]">Clone This
+ [% bug.bug_id FILTER uri %]">Clone This
[% terms.Bug %]</a></li>
[%# Links to more things users can do with this bug. %]
[% Hook.process("links") %]
@@ -36,49 +39,49 @@
<div class="navigation">
-[% IF bug_list && bug_list.size > 0 %]
- [% this_bug_idx = lsearch(bug_list, bug.bug_id) %]
+[% SET my_search = user.recent_search_for(bug) %]
+[% IF my_search %]
+ [% SET last_bug_list = my_search.bug_list %]
+ [% SET this_bug_idx = lsearch(last_bug_list, bug.id) %]
<b>[% terms.Bug %] List:</b>
- [% IF this_bug_idx != -1 %]
- ([% this_bug_idx + 1 %] of [% bug_list.size %])
- [% END %]
-[% IF this_bug_idx != -1 %]
- <a href="show_bug.cgi?id=[% bug_list.first %]">First</a>
- <a href="show_bug.cgi?id=[% bug_list.last %]">Last</a>
-[% END %]
+ ([% this_bug_idx + 1 %] of [% last_bug_list.size %])
- [% IF bug.bug_id %]
- [% IF this_bug_idx != -1 %]
- [% IF this_bug_idx > 0 %]
- [% prev_bug = this_bug_idx - 1 %]
- <a href="show_bug.cgi?id=[% bug_list.$prev_bug %]">Prev</a>
- [% ELSE %]
- <i><font color="#777777">Prev</font></i>
- [% END %]
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.first FILTER uri %]&list_id=
+ [%- my_search.id FILTER uri %]">First</a>
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.last FILTER uri %]&list_id=
+ [%- my_search.id FILTER uri %]">Last</a>
- [% IF this_bug_idx + 1 < bug_list.size %]
- [% next_bug = this_bug_idx + 1 %]
- <a href="show_bug.cgi?id=[% bug_list.$next_bug %]">Next</a>
- [% ELSE %]
- <i><font color="#777777">Next</font></i>
- [% END %]
- [% ELSE %]
- (This [% terms.bug %] is not in your last search results)
- [% END %]
+ [% IF this_bug_idx > 0 %]
+ [% prev_bug = this_bug_idx - 1 %]
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.$prev_bug FILTER uri %]&list_id=
+ [%- my_search.id FILTER uri %]">Prev</a>
[% ELSE %]
-
+ <i><font color="#777777">Prev</font></i>
[% END %]
- <a href="buglist.cgi?regetlastlist=1">Show last search results</a>
+ [% IF this_bug_idx + 1 < last_bug_list.size %]
+ [% next_bug = this_bug_idx + 1 %]
+ <a href="show_bug.cgi?id=
+ [%- last_bug_list.$next_bug FILTER uri %]&list_id=
+ [%- my_search.id FILTER uri %]">Next</a>
+ [% ELSE %]
+ <i><font color="#777777">Next</font></i>
+ [% END %]
+
+ <a href="buglist.cgi?regetlastlist=
+ [%- my_search.id FILTER uri %]">Show last search results</a>
[% ELSE %]
- [%# Either !bug_list || bug_list.size <= 0 %]
[%# With no list, don't show link to search results %]
<i><font color="#777777">First</font></i>
<i><font color="#777777">Last</font></i>
<i><font color="#777777">Prev</font></i>
<i><font color="#777777">Next</font></i>
- <i><font color="#777777">No search results available</font></i>
+ <i><font color="#777777">This [% terms.bug %] is not in your last
+ search results.</font></i>
[% END %]
</div>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/process/bugmail.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/process/bugmail.html.tmpl
index 7129922..b0132a2 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/process/bugmail.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/process/bugmail.html.tmpl
@@ -20,34 +20,21 @@
#%]
[%# INTERFACE:
- # mailing_bugid: string. ID of the bug this mail is concerning.
- # mailrecipients: hash. People involved in this change. Hash has up to five
- # elements:
- # changer: string. The login name of the user who made the
- # change.
- #
- # For bug changes where people need to be notified:
- # owner: string. The login name of the bug assignee.
- # reporter: string. The login name of the bug reporter.
- # qacontact: string. The login name of the bug's QA contact.
- # Optional.
- # cc: list of strings. The login names of those on the CC
- # list.
+ # mailing_bugid: The bug ID that email is being sent for.
+ # sent_bugmail: The results of Bugzilla::BugMail::Send().
#%]
[% PROCESS global/variables.none.tmpl %]
-[% mail = SendBugMail(mailing_bugid, mailrecipients) %]
-
<dl>
[% PROCESS emails
description = "Email sent to"
- names = mail.sent
+ names = sent_bugmail.sent
%]
[% PROCESS emails
description = "Excluding"
- names = mail.excluded
+ names = sent_bugmail.excluded
%]
</dl>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/process/header.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/process/header.html.tmpl
index 4aecfc1..6b608b9 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/process/header.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/process/header.html.tmpl
@@ -26,6 +26,8 @@
[% USE Bugzilla %]
+[% PROCESS "bug/show-header.html.tmpl" %]
+
[% IF title_tag == "bug_processed" %]
[% title = BLOCK %]
[% IF Bugzilla.cgi.param('id') %]
@@ -37,13 +39,8 @@
[% END %]
[% ELSIF title_tag == "mid_air" %]
[% title = "Mid-air collision!" %]
-[% ELSIF title_tag == "change_votes" %]
- [% title = "Change Votes" %]
[% END %]
-[% PROCESS global/header.html.tmpl
- javascript_urls = [ "js/util.js", "js/field.js",
- "js/yui/yahoo-dom-event.js", "js/yui/calendar.js" ]
- style_urls = [ "skins/standard/yui/calendar.css", "skins/standard/show_bug.css" ]
- doc_section = "bug_page.html"
-%]
+[% Hook.process('title') %]
+
+[% PROCESS global/header.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/process/midair.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/process/midair.html.tmpl
index d7e980e..157cb44 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/process/midair.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/process/midair.html.tmpl
@@ -45,7 +45,7 @@
<p>
Someone else has made changes to
- [%+ "$terms.bug $bug.id" FILTER bug_link(bug.id) FILTER none %]
+ [%+ "$terms.bug $bug.id" FILTER bug_link(bug) FILTER none %]
at the same time you were trying to.
The changes made were:
</p>
@@ -67,7 +67,7 @@
<p>
Your comment was:<br>
<blockquote><pre class="bz_comment_text">
- [% cgi.param("comment") FILTER wrap_comment FILTER html %]
+ [% cgi.param("comment") FILTER html %]
</pre></blockquote>
</p>
[% END %]
@@ -88,9 +88,23 @@
[% ", except for the added comment(s)" IF comments.size > start_at %].
</form>
</li>
+ [% IF cgi.param("comment") %]
+ <li>
+ <form method="post" action="process_bug.cgi">
+ <input type="hidden" name="id" value="[% cgi.param("id") FILTER html %]">
+ <input type="hidden" name="delta_ts" value="[% bug.delta_ts FILTER html %]">
+ <input type="hidden" name="comment" value="[% cgi.param("comment") FILTER html %]">
+ <input type="hidden" name="comment_is_private"
+ value="[% cgi.param("comment_is_private") FILTER html %]">
+ <input type="hidden" name="longdesclength" value="[% bug.comments.size %]">
+ <input type="hidden" name="token" value="[% cgi.param("token") FILTER html %]">
+ <input type="submit" id="process_comment" value="Submit only my new comment">
+ </form>
+ </li>
+ [% END %]
<li>
Throw away my changes, and
- [%+ "revisit $terms.bug $bug.id" FILTER bug_link(bug.id) FILTER none %]
+ [%+ "revisit $terms.bug $bug.id" FILTER bug_link(bug) FILTER none %]
</li>
</ul>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/process/results.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/process/results.html.tmpl
index d2adca8..c62a7a5 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/process/results.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/process/results.html.tmpl
@@ -24,8 +24,6 @@
# type: string; the type of change/check that was made: "bug" when a bug
# is changed, "dupe" when a duplication notation is added to a bug,
# and "dep" when a bug is checked for changes to its dependencies.
- #
- # mailrecipients: hash; BugMail recipient params. Optional.
#%]
[% PROCESS global/variables.none.tmpl %]
@@ -44,12 +42,13 @@
'bug' => "Changes submitted for $link" ,
'dupe' => "Duplicate notation added to $link" ,
'dep' => "Checking for dependency changes on $link" ,
- 'votes' => "$Link confirmed by number of votes" ,
'created' => "$Link has been added to the database" ,
'move' => "$Link has been moved to another database" ,
}
%]
+[% Hook.process('title') %]
+
<dl>
<dt>[% title.$type %]</dt>
<dd>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/process/verify-new-product.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/process/verify-new-product.html.tmpl
index 2eeb927..c02c264 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/process/verify-new-product.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/process/verify-new-product.html.tmpl
@@ -40,8 +40,9 @@
[% SET exclude_items = ['version', 'component', 'target_milestone'] %]
[% IF verify_bug_groups %]
- [% exclude_items.push('bit-\d+') %]
+ [% exclude_items.push('groups', 'defined_groups') %]
[% END %]
+[% Hook.process('exclude') %]
[% PROCESS "global/hidden-fields.html.tmpl"
exclude = '^' _ exclude_items.join('|') _ '$' %]
@@ -110,6 +111,7 @@
size=10 %]
</td>
[% END %]
+ [% Hook.process('field') %]
</tr>
</table>
@@ -122,9 +124,9 @@
[%+ terms.Bugs %] will no longer be restricted to these groups and may become
public if no other group applies:<br>
[% FOREACH group = old_groups %]
- <input type="checkbox" id="bit-[% group.id FILTER html %]"
- name="bit-[% group.id FILTER html %]" disabled="disabled" value="1">
- <label for="bit-[% group.id FILTER html %]">
+ <input type="checkbox" id="group_[% group.id FILTER html %]"
+ name="groups" disabled="disabled" value="[% group.name FILTER html %]">
+ <label for="group_[% group.id FILTER html %]">
[% group.name FILTER html %]: [% group.description FILTER html %]
</label>
<br>
@@ -152,16 +154,15 @@
<p>These groups are optional. You can decide to restrict [% terms.bugs %] to
one or more of the following groups:<br>
[% FOREACH group = optional_groups %]
- <input type="hidden" name="defined_bit-[% group.group.id FILTER html %]"
- value="1">
- <input type="checkbox" id="bit-[% group.group.id FILTER html %]"
- name="bit-[% group.group.id FILTER html %]"
- [%+ ((group.membercontrol == constants.CONTROLMAPDEFAULT && user.in_group(group.group.name))
+ <input type="hidden" name="defined_groups"
+ value="[% group.group.name FILTER html %]">
+ <input type="checkbox" id="group_[% group.group.id FILTER html %]"
+ name="groups"
+ [% ' checked="checked"' IF ((group.membercontrol == constants.CONTROLMAPDEFAULT && user.in_group(group.group.name))
|| (group.othercontrol == constants.CONTROLMAPDEFAULT && !user.in_group(group.group.name))
- || cgi.param("bit-$group.group.id") == 1) ?
- 'checked="checked"' : ''
- %] value="1">
- <label for="bit-[% group.group.id FILTER html %]">
+ || cgi.param("groups").contains(group.group.name)) %]
+ value="[% group.group.name FILTER html %]">
+ <label for="group_[% group.group.id FILTER html %]">
[% group.group.name FILTER html %]: [% group.group.description FILTER html %]
</label>
<br>
@@ -173,9 +174,10 @@
<p>These groups are mandatory and [% terms.bugs %] will be automatically
restricted to these groups:<br>
[% FOREACH group = mandatory_groups %]
- <input type="checkbox" id="bit-[% group.group.id FILTER html %]" checked="checked"
- name="bit-[% group.group.id FILTER html %]" value="1" disabled="disabled">
- <label for="bit-[% group.group.id FILTER html %]">
+ <input type="checkbox" id="group_[% group.group.id FILTER html %]"
+ checked="checked" disabled="disabled"
+ name="groups" value="[% group.group.name FILTER html %]">
+ <label for="group_[% group.group.id FILTER html %]">
[% group.group.name FILTER html %]: [% group.group.description FILTER html %]
</label>
<br>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/show-header.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/show-header.html.tmpl
new file mode 100644
index 0000000..5457091
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/bug/show-header.html.tmpl
@@ -0,0 +1,63 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Vaskin Kissoyan <vkissoyan@yahoo.com>
+ # Bradley Baetz <bbaetz@student.usyd.edu.au>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# This template should be called with PROCESS before processing
+ # "global/header.html.tmpl" in any template that is going to load the
+ # bug form. It expects only a "bug" object, and can even manage to get
+ # along without that. Some of these variables are just defaults that will
+ # be overridden by the calling templates.
+ #%]
+
+[% filtered_desc = bug.short_desc FILTER html %]
+[% subheader = filtered_desc %]
+[% filtered_timestamp = bug.delta_ts FILTER time %]
+[% title = "$terms.Bug $bug.bug_id – $filtered_desc" %]
+[% header = "$terms.Bug $bug.bug_id" %]
+[% header_addl_info = "Last modified: $filtered_timestamp" %]
+[% yui = ['autocomplete', 'calendar'] %]
+[% javascript_urls = [ "js/util.js", "js/field.js" ] %]
+[% IF bug.defined %]
+ [% unfiltered_title = "$terms.Bug $bug.bug_id – $bug.short_desc" %]
+ [% javascript = BLOCK %]
+ if( !document.location.href.match(/show_bug\.cgi/) && history && history.replaceState ) {
+ history.replaceState( null,
+ "[% unfiltered_title FILTER js %]",
+ "show_bug.cgi?id=[% bug.bug_id FILTER js %]" );
+ document.title = "[% unfiltered_title FILTER js %]";
+ }
+ [% javascript FILTER none %]
+ [% END %]
+[% END %]
+[% style_urls = [ "skins/standard/show_bug.css" ] %]
+[% doc_section = "bug_page.html" %]
+[% bodyclasses = ['bz_bug',
+ "bz_status_$bug.bug_status",
+ "bz_product_$bug.product",
+ "bz_component_$bug.component",
+ "bz_bug_$bug.bug_id",
+ ] %]
+[% FOREACH group = bug.groups_in %]
+ [% bodyclasses.push("bz_group_$group.name") %]
+[% END %]
+
+[% Hook.process('end') %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/show-multiple.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/show-multiple.html.tmpl
index 173d98e..7c2b534 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/show-multiple.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/show-multiple.html.tmpl
@@ -66,7 +66,7 @@
[% terms.Bug %]
<a href="show_bug.cgi?id=[% bug.bug_id FILTER html %]">[% bug.bug_id FILTER html %]</a>
[% IF Param("usebugaliases") AND bug.alias AND NOT bug.error %]
- (<a href="show_bug.cgi?id=[% bug.alias FILTER url_quote %]">
+ (<a href="show_bug.cgi?id=[% bug.alias FILTER uri %]">
[% bug.alias FILTER html %]</a>)
[% END %]
</h1>
@@ -129,8 +129,8 @@
<tr>
<th>[% field_descs.bug_status FILTER html %]:</th>
<td>
- [% get_status(bug.bug_status) FILTER html %]
- [%+ get_resolution(bug.resolution) FILTER html %]
+ [% display_value("bug_status", bug.bug_status) FILTER html %]
+ [%+ display_value("resolution", bug.resolution) FILTER html %]
</td>
[% PROCESS rightcell %]
@@ -139,7 +139,7 @@
<tr>
<th>[% field_descs.bug_severity FILTER html %]:</th>
<td class="bz_[% bug.bug_severity FILTER css_class_quote -%]">
- [% bug.bug_severity FILTER html %]
+ [% display_value("bug_severity", bug.bug_severity) FILTER html %]
</td>
[% PROCESS rightcell %]
@@ -163,11 +163,23 @@
<tr>
<th>[% field_descs.bug_file_loc FILTER html %]:</th>
<td colspan="3">
- [% IF bug.bug_file_loc.match("^(javascript|data)") %]
- [% bug.bug_file_loc FILTER html %]
- [% ELSE %]
+ [% IF is_safe_url(bug.bug_file_loc) %]
<a href="[% bug.bug_file_loc FILTER html %]">
[% bug.bug_file_loc FILTER html %]</a>
+ [% ELSE %]
+ [% bug.bug_file_loc FILTER html %]
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+
+ [% IF bug.see_also.size %]
+ <tr>
+ <th>[% field_descs.see_also FILTER html %]:</th>
+ <td colspan="3">
+ [% FOREACH see_also = bug.see_also %]
+ <a href="[% see_also.name FILTER html %]">[% see_also.name FILTER html %]</a>
+ [% "<br>" IF not loop.last() %]
[% END %]
</td>
</tr>
@@ -186,6 +198,13 @@
[% PROCESS bug/field.html.tmpl value=bug.${field.name} editable=0 %]
[%# Even-numbered fields get a closing <tr> %]
[% '</tr>' IF !(field_counter % 2) %]
+ [% IF extra_field_item %]
+ [% field_counter = field_counter + 1 %]
+ [% '<tr>' IF field_counter % 2 %]
+ <th>[% extra_field_item.header FILTER none %]</th>
+ <td>[% extra_field_item.data FILTER none %]</td>
+ [% '</tr>' IF !(field_counter % 2) %]
+ [% END %]
[% END %]
[%# And we have to finish the row if we ended on an odd number. %]
[% '<th></th><td></td></tr>' IF field_counter % 2 %]
@@ -195,7 +214,7 @@
[% PROCESS dependencies name = "blocked" %]
[% END %]
- [% IF user.in_group(Param("timetrackinggroup")) %]
+ [% IF user.is_timetracker %]
<tr>
<th>Time tracking:</th>
<td colspan="3">
@@ -289,7 +308,7 @@
<br>
[% PROCESS bug/comments.html.tmpl
- comments = bug.longdescs %]
+ comments = bug.comments %]
[% END %]
@@ -301,7 +320,7 @@
[% BLOCK row %]
<tr>
<th>[% field_descs.${cell} FILTER html %]:</th>
- <td[% " colspan=3" IF fullrow %]>[% bug.${cell} FILTER html %]</td>
+ <td[% " colspan=3" IF fullrow %]>[% display_value(cell, bug.${cell}) FILTER html %]</td>
[% PROCESS rightcell IF !fullrow %]
</tr>
[% fullrow = 0 %]
@@ -336,12 +355,12 @@
<th class="rightcell">[% field_descs.cc FILTER html %]:</th>
<td>
[% FOREACH c = bug.cc %]
- [% c FILTER html %][% ", " IF not loop.last() %]
+ [% c FILTER email FILTER html %][% ", " IF not loop.last() %]
[% END %]
[% ELSIF name == "reporter" || name == "assigned_to"
|| name == "qa_contact" %]
<th class="rightcell">[% field_descs.${name} FILTER html %]:</th>
- <td>[% bug.${name}.identity FILTER html %]</td>
+ <td>[% bug.${name}.identity FILTER email FILTER html %]</td>
[% ELSIF name == "flags" %]
<th class="rightcell">Flags:</th>
<td>
@@ -357,7 +376,7 @@
</td>
[% ELSIF name != "" %]
<th class="rightcell">[% field_descs.${name} FILTER html %]:</th>
- <td>[% bug.${name} FILTER html %]</td>
+ <td>[% display_value(name, bug.${name}) FILTER html %]</td>
[% ELSE %]
<td> </td>
<td> </td>
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/show.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/show.html.tmpl
index cf61274..8d8e63a 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/show.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/show.html.tmpl
@@ -27,28 +27,8 @@
[% bug = bugs.0 %]
[% IF !header_done %]
- [% filtered_desc = bug.short_desc FILTER html %]
- [% filtered_timestamp = bug.delta_ts FILTER time %]
- [% bodyclasses = ['bz_bug',
- "bz_status_$bug.bug_status",
- "bz_component_$bug.component",
- "bz_bug_$bug.bug_id"
- ]
- %]
- [% FOREACH group = bug.groups_in %]
- [% bodyclasses.push("bz_group_$group.name") %]
- [% END %]
- [% PROCESS global/header.html.tmpl
- title = "$terms.Bug $bug.bug_id – $filtered_desc"
- header = "$terms.Bug $bug.bug_id"
- subheader = filtered_desc
- header_addl_info = "Last modified: $filtered_timestamp"
- bodyclasses = bodyclasses
- javascript_urls = [ "js/util.js", "js/field.js",
- "js/yui/yahoo-dom-event.js", "js/yui/calendar.js" ]
- style_urls = [ "skins/standard/yui/calendar.css", "skins/standard/show_bug.css" ]
- doc_section = "bug_page.html"
- %]
+ [% PROCESS "bug/show-header.html.tmpl" %]
+ [% PROCESS global/header.html.tmpl %]
[% END %]
[% IF nextbug %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/show.xml.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/show.xml.tmpl
index c59b2be..dae207f 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/show.xml.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/show.xml.tmpl
@@ -25,9 +25,13 @@
<bugzilla version="[% constants.BUGZILLA_VERSION %]"
urlbase="[% urlbase FILTER xml %]"
+ [%# Note that the maintainer's email is not filtered,
+ # intentionally. Even logged-out users should be able
+ # to see that, since it will be in error messages anyway.
+ %]
maintainer="[% Param('maintainer') FILTER xml %]"
[% IF user.id %]
- exporter="[% user.email FILTER xml %]"
+ exporter="[% user.email FILTER email FILTER xml %]"
[% END %]
>
@@ -53,70 +57,56 @@
[% IF displayfields.group %]
[% FOREACH g = bug.groups %]
[% NEXT UNLESS g.ison %]
- <group>[% g.name FILTER xml %]</group>
+ <group id="[% g.bit FILTER xml %]">[% g.name FILTER xml %]</group>
[% END %]
[% END %]
[%# Bug Flags %]
- [% FOREACH type = bug.flag_types %]
- [% FOREACH flag = type.flags %]
- <flag name="[% type.name FILTER xml %]"
- id="[% flag.id FILTER xml %]"
- status="[% flag.status FILTER xml %]"
- setter="[% flag.setter.login FILTER xml %]"
- [% IF flag.requestee %]
- requestee="[% flag.requestee.login FILTER xml %]"
- [% END %]
- />
- [% END %]
- [% END %]
+ [% PROCESS section_flags obj => bug %]
+
[% IF displayfields.long_desc %]
- [% FOREACH c = bug.longdescs %]
- [% NEXT IF c.isprivate && !user.in_group(Param("insidergroup")) %]
- <long_desc isprivate="[% c.isprivate FILTER xml %]">
- <who name="[% c.author.name FILTER xml %]">[% c.author.email FILTER xml %]</who>
- <bug_when>[% c.time FILTER time FILTER xml %]</bug_when>
- [% IF user.in_group(Param('timetrackinggroup')) && (c.work_time - 0 != 0) %]
+ [% FOREACH c = bug.comments %]
+ [% NEXT IF c.is_private && !user.is_insider %]
+ <long_desc isprivate="[% c.is_private FILTER xml %]">
+ <commentid>[% c.id FILTER xml %]</commentid>
+ [% IF c.is_about_attachment %]
+ <attachid>[% c.extra_data FILTER xml %]</attachid>
+ [% END %]
+ <who name="[% c.author.name FILTER xml %]">[% c.author.email FILTER email FILTER xml %]</who>
+ <bug_when>[% c.creation_ts FILTER time("%Y-%m-%d %T %z") FILTER xml %]</bug_when>
+ [% IF user.is_timetracker && (c.work_time - 0 != 0) %]
<work_time>[% PROCESS formattimeunit time_unit = c.work_time FILTER xml %]</work_time>
[% END %]
- <thetext>[% c.body FILTER xml %]</thetext>
+ <thetext>[% c.body_full FILTER xml %]</thetext>
</long_desc>
[% END %]
[% END %]
[% IF displayfields.attachment %]
[% FOREACH a = bug.attachments %]
- [% NEXT IF a.isprivate && !user.in_group(Param("insidergroup")) %]
+ [% NEXT IF a.isprivate && !user.is_insider %]
<attachment
isobsolete="[% a.isobsolete FILTER xml %]"
ispatch="[% a.ispatch FILTER xml %]"
isprivate="[% a.isprivate FILTER xml %]"
>
<attachid>[% a.id %]</attachid>
- <date>[% a.attached FILTER time FILTER xml %]</date>
+ <date>[% a.attached FILTER time("%Y-%m-%d %T %z") FILTER xml %]</date>
+ <delta_ts>[% a.modification_time FILTER time("%Y-%m-%d %T %z") FILTER xml %]</delta_ts>
<desc>[% a.description FILTER xml %]</desc>
<filename>[% a.filename FILTER xml %]</filename>
<type>[% a.contenttype FILTER xml %]</type>
<size>[% a.datasize FILTER xml %]</size>
- <attacher>[% a.attacher.email FILTER xml %]</attacher>
+ <attacher>[% a.attacher.email FILTER email FILTER xml %]</attacher>
[%# This is here so automated clients can still use attachment.cgi %]
[% IF displayfields.token && user.id %]
<token>[% issue_hash_token([a.id, a.modification_time]) FILTER xml %]</token>
[% END %]
[% IF displayfields.attachmentdata %]
<data encoding="base64">[% a.data FILTER base64 %]</data>
- [% END %]
-
- [% FOREACH flag = a.flags %]
- <flag name="[% flag.type.name FILTER xml %]"
- id="[% flag.id FILTER xml %]"
- status="[% flag.status FILTER xml %]"
- setter="[% flag.setter.email FILTER xml %]"
- [% IF flag.status == "?" && flag.requestee %]
- requestee="[% flag.requestee.email FILTER xml %]"
- [% END %]
- />
[% END %]
+
+ [% PROCESS section_flags obj => a %]
</attachment>
[% END %]
[% END %]
@@ -130,7 +120,13 @@
</bugzilla>
[% BLOCK bug_field %]
- [% FOREACH val = bug.$field %]
+ [% field_values = bug.$field %]
+ [%# Work around TT bug https://rt.cpan.org/Public/Bug/Display.html?id=9802 %]
+ [% IF bug.$field.size == 1 %]
+ [% field_values = [bug.$field.first] %]
+ [% END %]
+
+ [% FOREACH val = field_values %]
[%# We need to handle some fields differently. This should become
# nicer once we have custfields, and a type attribute for the fields
#%]
@@ -138,10 +134,31 @@
[% IF field == 'reporter' OR field == 'assigned_to' OR
field == 'qa_contact' %]
[% name = val.name %]
- [% val = val.email %]
+ [% val = val.email FILTER email %]
+ [% ELSIF field == 'cc' %]
+ [% val = val FILTER email %]
[% ELSIF field == 'creation_ts' OR field == 'delta_ts' %]
- [% val = val FILTER time %]
+ [% val = val FILTER time("%Y-%m-%d %T %z") %]
+ [% ELSIF field == "see_also" %]
+ [% val = val.name %]
[% END %]
- <[% field %][% IF name != '' %] name="[% name FILTER xml %]"[% END -%]>[% val FILTER xml %]</[% field %]>
+ <[% field %][% IF name != '' %] name="[% name FILTER xml %]"[% END -%]>
+ [%- val FILTER xml %]</[% field %]>
+ [% END %]
+[% END %]
+
+[% BLOCK section_flags %]
+ [% RETURN UNLESS displayfields.flag %]
+
+ [% FOREACH flag = obj.flags %]
+ <flag name="[% flag.type.name FILTER xml %]"
+ id="[% flag.id FILTER xml %]"
+ type_id="[% flag.type_id FILTER xml %]"
+ status="[% flag.status FILTER xml %]"
+ setter="[% flag.setter.email FILTER email FILTER xml %]"
+ [% IF flag.status == "?" && flag.requestee %]
+ requestee="[% flag.requestee.email FILTER email FILTER xml %]"
+ [% END %]
+ />
[% END %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/bug/summarize-time.html.tmpl b/Websites/bugs.webkit.org/template/en/default/bug/summarize-time.html.tmpl
index b8bd073..21c26e8 100644
--- a/Websites/bugs.webkit.org/template/en/default/bug/summarize-time.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/bug/summarize-time.html.tmpl
@@ -14,8 +14,6 @@
# Frédéric Buclin <LpSolit@gmail.com>
#%]
-[% USE date %]
-
[% PROCESS "global/field-descs.none.tmpl" %]
[% title = "Time Summary " %]
@@ -34,13 +32,15 @@
header = header
style_urls = ["skins/standard/summarize-time.css"]
doc_section = "timetracking.html"
+ yui = ['calendar']
+ javascript_urls = [ "js/util.js", "js/field.js" ]
%]
[% INCLUDE query_form %]
[% IF do_report %]
- [% global.grand_total = 0 %]
+ [% global.grand_total = 0 global.estimated = 0 global.remaining = 0 %]
[% FOREACH workdata = part_list %]
[%# parts contains date ranges (from, to). %]
@@ -61,6 +61,16 @@
[% END %]
[% END %]
+ [% IF detailed %]
+ <h4 style="margin: 0">
+ Total of [% global.remaining FILTER format("%.2f") %]h remains from
+ original estimate of [% global.estimated FILTER format("%.2f") %]h
+ [% IF global.deadline %]
+ (deadline [% global.deadline FILTER html %])
+ [% END %]
+ </h4>
+ [% END %]
+
[% IF monthly %]
<h4 style="margin: 0">Total of [% global.grand_total FILTER format("%.2f") %] hours worked</h4>
<hr noshade size="1">
@@ -103,6 +113,7 @@
[% col = 0 subtotal = 0%]
[% FOREACH bugdata=ownerdata.nsort("bug_id") %]
[% bug_id = bugdata.bug_id %]
+ [% INCLUDE calc_bug_total id=bug_id %]
[% global.bug_count.$bug_id = 1 %]
[% IF detailed %]
[% INCLUDE bug_header cid=col id=bug_id bugdata=bugdata extra=1 %]
@@ -141,6 +152,7 @@
[% BLOCK do_one_bug %]
[% subtotal = 0.00 cid = 0 %]
+ [% INCLUDE calc_bug_total id=id %]
[% global.bug_count.$id = 1 %]
[% INCLUDE bug_header id=id %]
@@ -176,14 +188,47 @@
<td width="80" valign="top">
<b>[% "$terms.Bug $id" FILTER bug_link(id) FILTER none %]</b>
</td>
- <td width="100"><b>[% get_status(bugs.$id.bug_status) FILTER html %]</b></td>
+ <td width="100"><b>[% display_value("bug_status", bugs.$id.bug_status) FILTER html %]</b></td>
<td colspan="2">[% bugs.$id.short_desc FILTER html %]</td>
[% IF extra %]
<td align="right" valign="top">[% bugdata.total_time FILTER html %]</td>
[% END %]
</tr>
+ [% IF detailed %]
+ <tr class="bug_header[% '2' IF cid % 2 %]">
+ <td> </td>
+ <td colspan="3">
+ <table width="100%" cellpadding="0" cellspacing="0">
+ <tr>
+ <td width="33%">
+ Estimated: [% bugs.$id.estimated_time FILTER format("%.2f") %]h
+ </td>
+ <td width="33%">
+ Remaining: [% bugs.$id.remaining_time FILTER format("%.2f") %]h
+ </td>
+ <td width="33%">
+ Deadline: [% bugs.$id.deadline || "<b>Not set</b>" %]
+ </td>
+ </tr>
+ </table>
+ </td>
+ [% IF extra %]
+ <td> </td>
+ [% END %]
+ </tr>
+ [% END %]
[% END %]
+[% BLOCK calc_bug_total %]
+ [% IF !global.bug_count.$id %]
+ [% global.estimated = global.estimated + bugs.$id.estimated_time %]
+ [% global.remaining = global.remaining + bugs.$id.remaining_time %]
+ [% IF !global.deadline || bugs.$id.deadline &&
+ global.deadline.replace("-", "") < bugs.$id.deadline.replace("-", "") %]
+ [% SET global.deadline = bugs.$id.deadline %]
+ [% END %]
+ [% END %]
+[% END %]
[% BLOCK inactive_report %]
<h3>Inactive [% terms.bugs %]</h3>
@@ -238,11 +283,23 @@
for="start_date">Period <u>s</u>tarting</label></b>:
</td><td colspan="3">
<input type="text" id="start_date" name="start_date" size="11"
- align="right" value="[% start_date FILTER html %]" maxlength="10">
+ align="right" value="[% start_date FILTER html %]" maxlength="10"
+ onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_start_date"
+ onclick="showCalendar('start_date')"><span>Calendar</span>
+ </button>
+ <div id="con_calendar_start_date"></div>
<b>and <label accesskey="e" for="end_date"><u>e</u>nding</label></b>:
<input type="text" name="end_date" size="11" id="end_date"
- align="right" value ="[% end_date FILTER html %]" maxlength="10">
+ align="right" value ="[% end_date FILTER html %]" maxlength="10"
+ onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_end_date"
+ onclick="showCalendar('end_date')"><span>Calendar</span>
+ </button>
+ <div id="con_calendar_end_date"></div>
</td><td align="right">
<input type="submit" id="summarize" value="Summarize">
</td></tr>
@@ -286,7 +343,9 @@
</form>
<script type="text/javascript">
<!--
- document.forms['summary'].start_date.focus()
+ createCalendar('start_date');
+ createCalendar('end_date');
+ document.forms['summary'].start_date.focus();
//--></script>
<hr noshade size=1>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/config.js.tmpl b/Websites/bugs.webkit.org/template/en/default/config.js.tmpl
index 6661700..0d63583 100644
--- a/Websites/bugs.webkit.org/template/en/default/config.js.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/config.js.tmpl
@@ -61,7 +61,7 @@
// =============
[% FOREACH cf = custom_fields %]
-var [% cf.name FILTER js %] = [ [% FOREACH x = cf.legal_values %]'[% x FILTER js %]', [% END %] ];
+var [% cf.name FILTER js %] = [ [% FOREACH x = cf.legal_values %]'[% x.name FILTER js %]', [% END %] ];
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/config.rdf.tmpl b/Websites/bugs.webkit.org/template/en/default/config.rdf.tmpl
index 2850a5a..15f784c 100644
--- a/Websites/bugs.webkit.org/template/en/default/config.rdf.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/config.rdf.tmpl
@@ -19,6 +19,10 @@
# Frédéric Buclin <LpSolit@gmail.com>
#%]
+[%# The url to the installation is going to be displayed many times.
+ # So we cache it here for better performance.
+ %]
+[% escaped_urlbase = BLOCK %][% urlbase FILTER xml %][% END %]
<?xml version="1.0"[% IF Param('utf8') %] encoding="UTF-8"[% END %]?>
<!-- Note: this interface is experimental and under development.
- We may and probably will make breaking changes to it in the future. -->
@@ -27,7 +31,7 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:bz="http://www.bugzilla.org/rdf#">
-<bz:installation rdf:about="[% urlbase FILTER xml %]">
+<bz:installation rdf:about="[% escaped_urlbase %]">
<bz:install_version>[% constants.BUGZILLA_VERSION FILTER html %]</bz:install_version>
<bz:maintainer>[% Param('maintainer') FILTER html %]</bz:maintainer>
@@ -107,7 +111,7 @@
<bz:[% cf.name FILTER html %]>
<Seq>
[% FOREACH item = cf.legal_values %]
- <li>[% item FILTER html %]</li>
+ <li>[% item.name FILTER html %]</li>
[% END %]
</Seq>
</bz:[% cf.name FILTER html %]>
@@ -118,14 +122,15 @@
<Seq>
[% FOREACH product = products %]
<li>
- <bz:product rdf:about="[% urlbase FILTER xml %]product.cgi?name=[% product.name FILTER url_quote %]">
+ <bz:product rdf:about="[% escaped_urlbase %]product.cgi?name=[% product.name FILTER uri %]">
<bz:name>[% product.name FILTER html %]</bz:name>
+ <bz:allows_unconfirmed>[% product.allows_unconfirmed FILTER html %]</bz:allows_unconfirmed>
<bz:components>
<Seq>
[% FOREACH component = product.components %]
- <li resource="[% urlbase FILTER xml %]component.cgi?name=[% component.name FILTER url_quote
- %]&product=[% product.name FILTER url_quote %]"/>
+ <li resource="[% escaped_urlbase %]component.cgi?name=[% component.name FILTER uri
+ %]&product=[% product.name FILTER uri %]"/>
[% END %]
</Seq>
</bz:components>
@@ -133,7 +138,7 @@
<bz:versions>
<Seq>
[% FOREACH version = product.versions %]
- <li resource="[% urlbase FILTER xml %]version.cgi?name=[% version.name FILTER url_quote %]"/>
+ <li resource="[% escaped_urlbase %]version.cgi?name=[% version.name FILTER uri %]"/>
[% END %]
</Seq>
</bz:versions>
@@ -142,7 +147,7 @@
<bz:target_milestones>
<Seq>
[% FOREACH milestone = product.milestones %]
- <li resource="[% urlbase FILTER xml %]milestone.cgi?name=[% milestone.name FILTER url_quote %]"/>
+ <li resource="[% escaped_urlbase %]milestone.cgi?name=[% milestone.name FILTER uri %]"/>
[% END %]
</Seq>
</bz:target_milestones>
@@ -160,20 +165,22 @@
[% FOREACH product = products %]
[% FOREACH component = product.components %]
<li>
- <bz:component rdf:about="[% urlbase FILTER xml %]component.cgi?name=[% component.name FILTER url_quote
- %]&product=[% product.name FILTER url_quote %]">
+ <bz:component rdf:about="[% escaped_urlbase %]component.cgi?name=[% component.name FILTER uri
+ %]&product=[% product.name FILTER uri %]">
<bz:name>[% component.name FILTER html %]</bz:name>
- <bz:flag_types>
- <Seq>
- [% flag_types = component.flag_types.bug.merge(component.flag_types.attachment) %]
- [% FOREACH flag_type = flag_types %]
- [% NEXT UNLESS flag_type.is_active %]
- [% all_visible_flag_types.${flag_type.id} = flag_type %]
- <li resource="[% urlbase FILTER xml %]flags.cgi?id=[% flag_type.id FILTER url_quote
- %]&name=[% flag_type.name FILTER url_quote %]" />
- [% END %]
- </Seq>
- </bz:flag_types>
+ [% IF show_flags %]
+ <bz:flag_types>
+ <Seq>
+ [% flag_types = component.flag_types.bug.merge(component.flag_types.attachment) %]
+ [% FOREACH flag_type = flag_types %]
+ [% NEXT UNLESS flag_type.is_active %]
+ [% all_visible_flag_types.${flag_type.id} = flag_type %]
+ <li resource="[% escaped_urlbase %]flag.cgi?id=[% flag_type.id FILTER uri
+ %]&name=[% flag_type.name FILTER uri %]" />
+ [% END %]
+ </Seq>
+ </bz:flag_types>
+ [% END %]
</bz:component>
</li>
[% END %]
@@ -186,7 +193,7 @@
[% FOREACH product = products %]
[% FOREACH version = product.versions %]
<li>
- <bz:version rdf:about="[% urlbase FILTER xml %]version.cgi?name=[% version.name FILTER url_quote %]">
+ <bz:version rdf:about="[% escaped_urlbase %]version.cgi?name=[% version.name FILTER uri %]">
<bz:name>[% version.name FILTER html %]</bz:name>
</bz:version>
</li>
@@ -201,7 +208,7 @@
[% FOREACH product = products %]
[% FOREACH milestone = product.milestones %]
<li>
- <bz:target_milestone rdf:about="[% urlbase FILTER xml %]milestone.cgi?name=[% milestone.name FILTER url_quote %]">
+ <bz:target_milestone rdf:about="[% escaped_urlbase %]milestone.cgi?name=[% milestone.name FILTER uri %]">
<bz:name>[% milestone.name FILTER html %]</bz:name>
</bz:target_milestone>
</li>
@@ -211,31 +218,37 @@
</bz:target_milestones>
[% END %]
- <bz:flag_types>
- <Seq>
- [% FOREACH flag_type = all_visible_flag_types.values.sort('name') %]
- <li>
- <bz:flag_type rdf:about="[% urlbase FILTER xml %]flag.cgi?id=[% flag_type.id FILTER url_quote
- %]&name=[% flag_type.name FILTER url_quote %]">
- <bz:id>[% flag_type.id FILTER html %]</bz:id>
- <bz:name>[% flag_type.name FILTER html %]</bz:name>
- <bz:description>[% flag_type.description FILTER html %]</bz:description>
- <bz:type>[% flag_type.target_type FILTER html %]</bz:type>
- <bz:requestable>[% flag_type.is_requestable FILTER html %]</bz:requestable>
- <bz:specifically_requestable>[% flag_type.is_requesteeble FILTER html %]</bz:specifically_requestable>
- <bz:multiplicable>[% flag_type.is_multiplicable FILTER html %]</bz:multiplicable>
- </bz:flag_type>
- </li>
- [% END %]
- </Seq>
- </bz:flag_types>
+ [% IF show_flags %]
+ <bz:flag_types>
+ <Seq>
+ [% FOREACH flag_type = all_visible_flag_types.values.sort('name') %]
+ <li>
+ <bz:flag_type rdf:about="[% escaped_urlbase %]flag.cgi?id=[% flag_type.id FILTER uri
+ %]&name=[% flag_type.name FILTER uri %]">
+ <bz:id>[% flag_type.id FILTER html %]</bz:id>
+ <bz:name>[% flag_type.name FILTER html %]</bz:name>
+ <bz:description>[% flag_type.description FILTER html %]</bz:description>
+ <bz:type>[% flag_type.target_type FILTER html %]</bz:type>
+ <bz:requestable>[% flag_type.is_requestable FILTER html %]</bz:requestable>
+ <bz:specifically_requestable>[% flag_type.is_requesteeble FILTER html %]</bz:specifically_requestable>
+ <bz:multiplicable>[% flag_type.is_multiplicable FILTER html %]</bz:multiplicable>
+ [% IF user.in_group("editcomponents") %]
+ <bz:grant_group>[% flag_type.grant_group.name FILTER html %]</bz:grant_group>
+ <bz:request_group>[% flag_type.request_group.name FILTER html %]</bz:request_group>
+ [% END %]
+ </bz:flag_type>
+ </li>
+ [% END %]
+ </Seq>
+ </bz:flag_types>
+ [% END %]
<bz:fields>
<Seq>
[% PROCESS "global/field-descs.none.tmpl" %]
[% FOREACH item = field %]
<li>
- <bz:field rdf:about="[% urlbase FILTER xml %]field.cgi?name=[% item.name FILTER url_quote %]">
+ <bz:field rdf:about="[% escaped_urlbase %]field.cgi?name=[% item.name FILTER uri %]">
<bz:name>[% item.name FILTER html %]</bz:name>
<bz:description>[% (field_descs.${item.name} OR item.description) FILTER html %]</bz:description>
[%-# These values are meaningful for custom fields only. %]
diff --git a/Websites/bugs.webkit.org/template/en/default/email/bugmail-common.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/email/bugmail-common.txt.tmpl
new file mode 100644
index 0000000..b6cadaf
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/email/bugmail-common.txt.tmpl
@@ -0,0 +1,38 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Guy Pyrzak
+ # Portions created by the Initial Developer are Copyright (C) 2010 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Guy Pyrzak <guy.pyrzak@gmail.com>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% field_label = field_descs.${change.field_name} %]
+[% old_value = display_value(change.field_name, change.old) %]
+[% new_value = display_value(change.field_name, change.new) %]
+
+[% IF change.field_name == "estimated_time" || change.field_name == "remaining_time" %]
+ [% old_value = old_value FILTER format('%.2f') %]
+ [% new_value = new_value FILTER format('%.2f') %]
+[% END %]
+
+[% IF change.attach_id %]
+ [% field_label = field_label.replace('^(Attachment )?', "Attachment #${change.attach_id} ") %]
+[% END %]
+
+[% IF change.field_name == 'longdescs.isprivate' %]
+ [% field_label = field_label.replace('^(Comment )?', "Comment #${change.num} ") %]
+[% END %]
+
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/template/en/default/email/bugmail-header.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/email/bugmail-header.txt.tmpl
new file mode 100644
index 0000000..94559a9
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/email/bugmail-header.txt.tmpl
@@ -0,0 +1,47 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): André Batosti <batosti@async.com.br>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+[% PROCESS "global/reason-descs.none.tmpl" %]
+[% isnew = bug.lastdiffed ? 0 : 1 %]
+
+From: [% Param('mailfrom') %]
+To: [% to_user.email %]
+Subject: [[% terms.Bug %] [%+ bug.id %]] [% 'New: ' IF isnew %][%+ bug.short_desc %]
+Date: [% date %]
+X-Bugzilla-Reason: [% reasonsheader %]
+X-Bugzilla-Type: [% isnew ? 'new' : 'changed' %]
+X-Bugzilla-Watch-Reason: [% reasonswatchheader %]
+[% IF Param('useclassification') %]
+X-Bugzilla-Classification: [% bug.classification %]
+[% END %]
+X-Bugzilla-Product: [% bug.product %]
+X-Bugzilla-Component: [% bug.component %]
+X-Bugzilla-Keywords: [% bug.keywords %]
+X-Bugzilla-Severity: [% bug.bug_severity %]
+X-Bugzilla-Who: [% changer.login %]
+X-Bugzilla-Status: [% bug.bug_status %]
+X-Bugzilla-Priority: [% bug.priority %]
+X-Bugzilla-Assigned-To: [% bug.assigned_to.login %]
+X-Bugzilla-Target-Milestone: [% bug.target_milestone %]
+X-Bugzilla-Changed-Fields: [% changedfields.join(" ") %]
+[%+ threadingmarker %]
diff --git a/Websites/bugs.webkit.org/template/en/default/email/bugmail.html.tmpl b/Websites/bugs.webkit.org/template/en/default/email/bugmail.html.tmpl
new file mode 100644
index 0000000..e42b556
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/email/bugmail.html.tmpl
@@ -0,0 +1,128 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Guy Pyrzak
+ # Portions created by the Initial Developer are Copyright (C) 2010 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Guy Pyrzak <guy.pyrzak@gmail.com>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+[% PROCESS "global/reason-descs.none.tmpl" %]
+
+[% isnew = bug.lastdiffed ? 0 : 1 %]
+<html>
+ <head>
+ <base href="[% urlbase FILTER html %]" />
+ </head>
+ <body>
+ [% PROCESS generate_diffs %]
+ <p>
+ [% FOREACH comment = new_comments.reverse %]
+ <div>
+ [% IF comment.count %]
+ <b>[% "Comment # ${comment.count}" FILTER bug_link( bug,
+ {comment_num => comment.count, full_url => 1}) FILTER none %]
+ from [% INCLUDE global/user.html.tmpl who = comment.author %]</b>
+ [% END %]
+ <pre>[% comment.body_full({ wrap => 1 }) FILTER quoteUrls(bug, comment) %]</pre>
+ </div>
+ [% END %]
+ </p>
+ <hr>
+ <span>You are receiving this mail because:</span>
+
+ <ul>
+ [% FOREACH reason = reasons %]
+ [% IF reason_descs.$reason %]
+ <li>[% reason_descs.$reason FILTER html %]</li>
+ [% END %]
+ [% END %]
+ [% FOREACH reason = reasons_watch %]
+ [% IF watch_reason_descs.$reason %]
+ <li>[% watch_reason_descs.$reason FILTER html %]</li>
+ [% END %]
+ [% END %]
+ </ul>
+ </body>
+</html>
+
+[% BLOCK generate_diffs %]
+ [% SET in_table = 0 %]
+ [% last_changer = 0 %]
+ [% FOREACH change = diffs %]
+ [% IF !isnew && changer.id != last_changer %]
+ [% last_changer = changer.id %]
+ [% IF in_table == 1 %]
+ </table>
+ [% SET in_table = 0 %]
+ [% END %]
+ [% IF change.blocker %]
+ [% "${terms.Bug} ${bug.id}" FILTER bug_link(bug, full_url => 1) FILTER none %] depends
+ on [% "${terms.bug} ${change.blocker.id}"
+ FILTER bug_link(change.blocker, full_url => 1) FILTER none %],
+ which changed state.
+ [% ELSE %]
+ [% INCLUDE global/user.html.tmpl who = change.who %]
+ changed [% "${terms.Bug} ${bug.id}" FILTER bug_link(bug, full_url => 1) FILTER none %]
+ [% END %]
+ <br>
+ [% IF in_table == 0 %]
+ <table border="1" cellspacing="0" cellpadding="8">
+ [% SET in_table = 1 %]
+ [% END %]
+ <tr>
+ <th>What</th>
+ <th>Removed</th>
+ <th>Added</th>
+ </tr>
+ [% END %]
+
+ [% PROCESS "email/bugmail-common.txt.tmpl" %]
+ [% IF in_table == 0 %]
+ <table border="1" cellspacing="0" cellpadding="8">
+ [% SET in_table = 1 %]
+ [% END %]
+ [% IF isnew %]
+ <tr>
+ <th>[% field_label FILTER html %]</th>
+ <td>
+ [% IF change.field_name == "bug_id" %]
+ [% new_value FILTER bug_link(bug, full_url => 1) FILTER none %]
+ [% ELSE %]
+ [% new_value FILTER html %]
+ [% END %]
+ </td>
+ </tr>
+ [% ELSE %]
+ <tr>
+ <td style="text-align:right;">[% field_label FILTER html %]</td>
+ <td>
+ [% IF old_value %]
+ [% old_value FILTER html %]
+ [% ELSE %]
+
+ [% END%]
+ </td>
+ <td>
+ [% IF new_value %]
+ [% new_value FILTER html %]
+ [% ELSE %]
+
+ [% END%]
+ </td>
+ </tr>
+ [% END %]
+ [% END %]
+ [% '</table>' IF in_table %]
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/email/bugmail.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/email/bugmail.txt.tmpl
new file mode 100644
index 0000000..0b349fb
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/email/bugmail.txt.tmpl
@@ -0,0 +1,76 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): André Batosti <batosti@async.com.br>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+[% PROCESS "global/reason-descs.none.tmpl" %]
+
+[% isnew = bug.lastdiffed ? 0 : 1 %]
+
+[%+ PROCESS generate_diffs -%]
+
+[% FOREACH comment = new_comments %]
+
+[%- IF comment.count %]
+--- Comment #[% comment.count %] from [% comment.author.identity %] ---
+[% END %]
+[%+ comment.body_full({ is_bugmail => 1, wrap => 1 }) %]
+[% END %]
+
+-- [%# Protect the trailing space of the signature marker %]
+You are receiving this mail because:
+[% SET reason_lines = [] %]
+[% FOREACH reason = reasons %]
+ [% reason_lines.push(reason_descs.$reason) IF reason_descs.$reason %]
+[% END %]
+[% FOREACH reason = reasons_watch %]
+ [% reason_lines.push(watch_reason_descs.$reason)
+ IF watch_reason_descs.$reason %]
+[% END %]
+[%+ reason_lines.join("\n") %]
+
+[% BLOCK generate_diffs %]
+ [% urlbase %]show_bug.cgi?id=[% bug.id %]
+
+[%+ last_changer = 0 %]
+ [% FOREACH change = diffs %]
+ [% IF !isnew && changer.id != last_changer %]
+ [% last_changer = changer.id %]
+ [% IF change.blocker %]
+ [% terms.Bug %] [%+ bug.id %] depends on [% terms.bug %] [%+ change.blocker.id %], which changed state.
+
+[%+ terms.Bug %] [%+ change.blocker.id %] Summary: [% change.blocker.short_desc %]
+[%+ urlbase %]show_bug.cgi?id=[% change.blocker.id %]
+ [% ELSE %]
+ [%~ changer.identity %] changed:
+ [% END %]
+
+ What |Removed |Added
+----------------------------------------------------------------------------
+[%+ END %][%# End of IF. This indentation is intentional! ~%]
+ [% PROCESS "email/bugmail-common.txt.tmpl"%]
+ [%~ IF isnew %]
+ [% format_columns(2, field_label _ ":", new_value) -%]
+ [% ELSE %]
+ [% format_columns(3, field_label, old_value, new_value) -%]
+ [% END %]
+ [% END -%]
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/email/lockout.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/email/lockout.txt.tmpl
new file mode 100644
index 0000000..ac65257
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/email/lockout.txt.tmpl
@@ -0,0 +1,39 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is the Mozilla Corporation.
+ # Portions created by the Initial Developer are Copyright (C) 2008
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% Param('mailfrom') %]
+To: [% Param('maintainer') %]
+Subject: [[% terms.Bugzilla %]] Account Lock-Out: [% locked_user.login %] ([% attempts.0.ip_addr %])
+X-Bugzilla-Type: admin
+
+The IP address [% attempts.0.ip_addr %] failed too many login attempts (
+[%- constants.MAX_LOGIN_ATTEMPTS +%]) for
+the account [% locked_user.login %].
+
+The login attempts occurred at these times:
+
+[% FOREACH login = attempts %]
+ [%+ login.login_time FILTER time %]
+[% END %]
+
+This IP will be able to log in again using this account at
+[%+ unlock_at FILTER time %].
diff --git a/Websites/bugs.webkit.org/template/en/default/email/newchangedmail.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/email/newchangedmail.txt.tmpl
deleted file mode 100644
index 7c0e30a..0000000
--- a/Websites/bugs.webkit.org/template/en/default/email/newchangedmail.txt.tmpl
+++ /dev/null
@@ -1,79 +0,0 @@
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): André Batosti <batosti@async.com.br>
- #%]
-
-[% PROCESS "global/variables.none.tmpl" %]
-From: [% Param('mailfrom') %]
-To: [% to %]
-Subject: [[% terms.Bug %] [%+ bugid %]] [% 'New: ' IF isnew %][%+ summary %]
-X-Bugzilla-Reason: [% reasonsheader %]
-X-Bugzilla-Type: newchanged
-X-Bugzilla-Watch-Reason: [% reasonswatchheader %]
-[% IF Param('useclassification') %]
-X-Bugzilla-Classification: [% classification %]
-[% END %]
-X-Bugzilla-Product: [% product %]
-X-Bugzilla-Component: [% comp %]
-X-Bugzilla-Keywords: [% keywords %]
-X-Bugzilla-Severity: [% severity %]
-X-Bugzilla-Who: [% changer %]
-X-Bugzilla-Status: [% status %]
-X-Bugzilla-Priority: [% priority %]
-X-Bugzilla-Assigned-To: [% assignedto %]
-X-Bugzilla-Target-Milestone: [% targetmilestone %]
-X-Bugzilla-Changed-Fields: [% changedfields %]
-[%+ threadingmarker %]
-
-[%+ urlbase %]show_bug.cgi?id=[% bugid %]
-
-[%+ diffs %]
-
---
-Configure [% terms.bug %]mail: [% urlbase %]userprefs.cgi?tab=email
-------- You are receiving this mail because: -------
-[% FOREACH relationship = reasons %]
- [% SWITCH relationship %]
- [% CASE constants.REL_ASSIGNEE %]
-You are the assignee for the [% terms.bug %].
- [% CASE constants.REL_REPORTER %]
-You reported the [% terms.bug %].
- [% CASE constants.REL_QA %]
-You are the QA contact for the [% terms.bug %].
- [% CASE constants.REL_CC %]
-You are on the CC list for the [% terms.bug %].
- [% CASE constants.REL_VOTER %]
-You are a voter for the [% terms.bug %].
- [% CASE constants.REL_GLOBAL_WATCHER %]
-You are watching all [% terms.bug %] changes.
- [% END %]
-[% END %]
-[% FOREACH relationship = reasons_watch %]
- [% SWITCH relationship %]
- [% CASE constants.REL_ASSIGNEE %]
-You are watching the assignee of the [% terms.bug %].
- [% CASE constants.REL_REPORTER %]
-You are watching the reporter.
- [% CASE constants.REL_QA %]
-You are watching the QA contact of the [% terms.bug %].
- [% CASE constants.REL_CC %]
-You are watching someone on the CC list of the [% terms.bug %].
- [% CASE constants.REL_VOTER %]
-You are watching a voter for the [% terms.bug %].
- [% END %]
-[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/email/sanitycheck.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/email/sanitycheck.txt.tmpl
index 9c19269..8826d4e 100644
--- a/Websites/bugs.webkit.org/template/en/default/email/sanitycheck.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/email/sanitycheck.txt.tmpl
@@ -33,4 +33,4 @@
No errors have been found.
[% END %]
-[% output FILTER txt %]
+[% output %]
diff --git a/Websites/bugs.webkit.org/template/en/default/email/whine.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/email/whine.txt.tmpl
index caf43eb..32d8da8 100644
--- a/Websites/bugs.webkit.org/template/en/default/email/whine.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/email/whine.txt.tmpl
@@ -30,31 +30,30 @@
[% terms.bug %] tracking system ([% urlbase %]) that require
attention.
-All of these [% terms.bugs %] are in the [% get_status("NEW") %] or
-[% get_status("REOPENED") %] state, and have not been
-touched in [% Param("whinedays") %] days or more.
+All of these [% terms.bugs %] are in the [% display_value("bug_status", "CONFIRMED") %]
+state, and have not been touched in [% Param("whinedays") %] days or more.
You need to take a look at them, and decide on an initial action.
Generally, this means one of three things:
-(1) You decide this [% terms.bug %] is really quick to deal with (like, it's [% get_resolution("INVALID") %]),
+(1) You decide this [% terms.bug %] is really quick to deal with (like, it's [% display_value("resolution", "INVALID") %]),
and so you get rid of it immediately.
(2) You decide the [% terms.bug %] doesn't belong to you, and you reassign it to
someone else. (Hint: if you don't know who to reassign it to, make
sure that the Component field seems reasonable, and then use the
- "Reassign [% terms.bug %] to default assignee of selected component" option.)
+ "Reset Assignee to default" option.)
(3) You decide the [% terms.bug %] belongs to you, but you can't solve it this moment.
- Just use the "Accept [% terms.bug %]" command.
+ Accept the [% terms.bug %] by setting the status to [% display_value("bug_status", "IN_PROGRESS") %].
-To get a list of all [% get_status("NEW") %]/[% get_status("REOPENED") %] [%+ terms.bugs %], you can use this URL (bookmark
+To get a list of all [% display_value("bug_status", "CONFIRMED") %] [%+ terms.bugs %], you can use this URL (bookmark
it if you like!):
- [% urlbase %]buglist.cgi?bug_status=NEW&bug_status=REOPENED&assigned_to=[% email %]
+ [% urlbase %]buglist.cgi?bug_status=CONFIRMED&assigned_to=[% email %]
Or, you can use the general query page, at
[%+ urlbase %]query.cgi
-Appended below are the individual URLs to get to all of your [% get_status("NEW") %] [%+ terms.bugs %]
+Appended below are the individual URLs to get to all of your [% display_value("bug_status", "CONFIRMED") %] [%+ terms.bugs %]
that haven't been touched for [% Param("whinedays") %] days or more.
You will get this message once a day until you've dealt with these [% terms.bugs %]!
diff --git a/Websites/bugs.webkit.org/template/en/default/extensions/config.pm.tmpl b/Websites/bugs.webkit.org/template/en/default/extensions/config.pm.tmpl
new file mode 100644
index 0000000..6997ec1
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/extensions/config.pm.tmpl
@@ -0,0 +1,41 @@
+[%# -*- mode: perl -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # name: string; The name of the extension.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS extensions/license.txt.tmpl %]
+
+package B[% %]ugzilla::Extension::[% name %];
+use strict;
+
+use constant NAME => '[% name %]';
+
+use constant REQUIRED_MODULES => [
+];
+
+use constant OPTIONAL_MODULES => [
+];
+
+__PACKAGE__->NAME;
diff --git a/Websites/bugs.webkit.org/template/en/default/extensions/extension.pm.tmpl b/Websites/bugs.webkit.org/template/en/default/extensions/extension.pm.tmpl
new file mode 100644
index 0000000..2492271
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/extensions/extension.pm.tmpl
@@ -0,0 +1,46 @@
+[%# -*- mode: perl -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # name: string; The name of the extension.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS extensions/license.txt.tmpl %]
+
+package B[% %]ugzilla::Extension::[% name %];
+use strict;
+use base qw(B[% %]ugzilla::Extension);
+
+# This code for this is in [% path %]/lib/Util.pm
+use B[% %]ugzilla::Extension::[% name %]::Util;
+
+our $VERSION = '0.01';
+
+# See the documentation of B[% %]ugzilla::Hook ("perldoc B[% %]ugzilla::Hook"
+# in the bugzilla directory) for a list of all available hooks.
+sub install_update_db {
+ my ($self, $args) = @_;
+
+}
+
+__PACKAGE__->NAME;
diff --git a/Websites/bugs.webkit.org/template/en/default/extensions/hook-readme.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/extensions/hook-readme.txt.tmpl
new file mode 100644
index 0000000..efceec1
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/extensions/hook-readme.txt.tmpl
@@ -0,0 +1,27 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+Template hooks go in this directory. Template hooks are called in normal
+[%+ terms.Bugzilla %] templates like [[% '%' %] Hook.process('some-hook') %].
+More information about them can be found in the documentation of
+B[% %]ugzilla::Extension. (Do "perldoc B[% %]ugzilla::Extension" from the main
+[%+ terms.Bugzilla %] directory to see that documentation.)
diff --git a/Websites/bugs.webkit.org/template/en/default/extensions/license.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/extensions/license.txt.tmpl
new file mode 100644
index 0000000..964e075
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/extensions/license.txt.tmpl
@@ -0,0 +1,47 @@
+[%# -*- mode: perl -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # name: string; The name of the extension.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the [% name %] [%+ terms.Bugzilla %] Extension.
+#
+# The Initial Developer of the Original Code is YOUR NAME
+# Portions created by the Initial Developer are Copyright (C) [% year %] the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# YOUR NAME <YOUR EMAIL ADDRESS>
diff --git a/Websites/bugs.webkit.org/template/en/default/extensions/name-readme.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/extensions/name-readme.txt.tmpl
new file mode 100644
index 0000000..6d25c83
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/extensions/name-readme.txt.tmpl
@@ -0,0 +1,38 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+Normal templates go in this directory. You can load them in your
+code like this:
+
+use B[% %]ugzilla::Error;
+my $template = B[% %]ugzilla->template;
+$template->process('[% name FILTER lower %]/some-template.html.tmpl')
+ or ThrowTemplateError($template->error());
+
+That would be how to load a file called some-template.html.tmpl that
+was in this directory.
+
+Note that you have to be careful that the full path of your template
+never conflicts with a template that exists in [% terms.Bugzilla %] or in
+another extension, or your template might override that template. That's why
+we created this directory called '[% name FILTER lower %]' for you, so you
+can put your templates in here to help avoid conflicts.
diff --git a/Websites/bugs.webkit.org/template/en/default/extensions/util.pm.tmpl b/Websites/bugs.webkit.org/template/en/default/extensions/util.pm.tmpl
new file mode 100644
index 0000000..32076a6
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/extensions/util.pm.tmpl
@@ -0,0 +1,42 @@
+[%# -*- mode: perl -*- %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # name: string; The name of the extension.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS extensions/license.txt.tmpl %]
+
+package B[% %]ugzilla::Extension::[% name %]::Util;
+use strict;
+use base qw(Exporter);
+our @EXPORT = qw(
+
+);
+
+# This file can be loaded by your extension via
+# "use B[% %]ugzilla::Extension::[% name %]::Util". You can put functions
+# used by your extension in here. (Make sure you also list them in
+# @EXPORT.)
+
+1;
diff --git a/Websites/bugs.webkit.org/template/en/default/extensions/web-readme.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/extensions/web-readme.txt.tmpl
new file mode 100644
index 0000000..55e5939
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/extensions/web-readme.txt.tmpl
@@ -0,0 +1,29 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+Web-accessible files, like JavaScript, CSS, and images go in this
+directory. You can reference them directly in your HTML. For example,
+if you have a file called "style.css" and your extension is called
+"Foo", you would put it in "extensions/Foo/web/style.css", and then
+you could link to it in HTML like:
+
+<link href="extensions/Foo/web/style.css" rel="stylesheet" type="text/css">
diff --git a/Websites/bugs.webkit.org/template/en/default/filterexceptions.pl b/Websites/bugs.webkit.org/template/en/default/filterexceptions.pl
index d5b4125..8680573 100644
--- a/Websites/bugs.webkit.org/template/en/default/filterexceptions.pl
+++ b/Websites/bugs.webkit.org/template/en/default/filterexceptions.pl
@@ -34,7 +34,7 @@
# [% foo.push() %]
# TT loop variables - [% loop.count %]
# Already-filtered stuff - [% wibble FILTER html %]
-# where the filter is one of html|csv|js|url_quote|quoteUrls|time|uri|xml|none
+# where the filter is one of html|csv|js|quoteUrls|time|uri|xml|none
%::safe = (
@@ -57,27 +57,9 @@
'type.id',
],
-'search/boolean-charts.html.tmpl' => [
- '"field${chartnum}-${rownum}-${colnum}"',
- '"value${chartnum}-${rownum}-${colnum}"',
- '"type${chartnum}-${rownum}-${colnum}"',
- 'field.name',
- 'type.name',
- 'type.description',
- '"${chartnum}-${rownum}-${newor}"',
- '"${chartnum}-${newand}-0"',
- 'newchart',
- 'jsmagic',
-],
-
'search/form.html.tmpl' => [
- 'qv.value',
'qv.name',
'qv.description',
- 'field.name',
- 'field.description',
- 'field.accesskey',
- 'sel.name',
],
'search/search-specific.html.tmpl' => [
@@ -96,24 +78,6 @@
'request.attach_id',
],
-'reports/components.html.tmpl' => [
- 'numcols',
-],
-
-'reports/duplicates-table.html.tmpl' => [
- 'column.name',
- 'column.description',
- 'bug.count',
- 'bug.delta',
-],
-
-'reports/duplicates.html.tmpl' => [
- 'bug_ids_string',
- 'maxrows',
- 'changedsince',
- 'reverse',
-],
-
'reports/keywords.html.tmpl' => [
'keyword.bug_count',
],
@@ -124,7 +88,6 @@
],
'reports/report-table.html.tmpl' => [
- '"&$tbl_vals" IF tbl_vals',
'"&$col_vals" IF col_vals',
'"&$row_vals" IF row_vals',
'classes.$row_idx.$col_idx',
@@ -147,13 +110,6 @@
'cumulate',
],
-'reports/duplicates.rdf.tmpl' => [
- 'template_version',
- 'bug.id',
- 'bug.count',
- 'bug.delta',
-],
-
'reports/chart.html.tmpl' => [
'width',
'height',
@@ -184,10 +140,6 @@
'default.series_id',
],
-'list/change-columns.html.tmpl' => [
- 'column',
-],
-
'list/edit-multiple.html.tmpl' => [
'group.id',
'menuname',
@@ -265,12 +217,7 @@
],
'global/site-navigation.html.tmpl' => [
- 'bug_list.first',
- 'bug_list.$prev_bug',
- 'bug_list.$next_bug',
- 'bug_list.last',
'bug.bug_id',
- 'bug.votes',
],
'bug/comments.html.tmpl' => [
@@ -297,32 +244,18 @@
],
'bug/edit.html.tmpl' => [
- 'bug.deadline',
'bug.remaining_time',
'bug.delta_ts',
'bug.bug_id',
- 'bug.votes',
'group.bit',
- 'dep.title',
- 'dep.fieldname',
- 'bug.${dep.fieldname}.join(\', \')',
'selname',
- '" accesskey=\"$accesskey\"" IF accesskey',
'inputname',
'" colspan=\"$colspan\"" IF colspan',
'" size=\"$size\"" IF size',
'" maxlength=\"$maxlength\"" IF maxlength',
- 'flag.status',
'" spellcheck=\"$spellcheck\"" IF spellcheck',
],
-'bug/navigate.html.tmpl' => [
- 'bug_list.first',
- 'bug_list.last',
- 'bug_list.$prev_bug',
- 'bug_list.$next_bug',
-],
-
'bug/show-multiple.html.tmpl' => [
'attachment.id',
'flag.status',
@@ -343,6 +276,10 @@
'subtotal FILTER format("%.2f")',
'work_time FILTER format("%.2f")',
'global.total FILTER format("%.2f")',
+ 'global.remaining FILTER format("%.2f")',
+ 'global.estimated FILTER format("%.2f")',
+ 'bugs.$id.remaining_time FILTER format("%.2f")',
+ 'bugs.$id.estimated_time FILTER format("%.2f")',
],
@@ -353,19 +290,6 @@
FILTER format("%d")',
],
-'bug/votes/list-for-bug.html.tmpl' => [
- 'voter.vote_count',
- 'total',
-],
-
-'bug/votes/list-for-user.html.tmpl' => [
- 'product.maxperbug',
- 'bug.id',
- 'bug.count',
- 'product.total',
- 'product.maxvotes',
-],
-
'bug/process/results.html.tmpl' => [
'title.$type',
'"$terms.Bug $id" FILTER bug_link(id)',
@@ -373,9 +297,6 @@
],
'bug/create/create.html.tmpl' => [
- 'g.bit',
- 'sel.name',
- 'sel.description',
'cloned_bug_id',
],
@@ -387,7 +308,6 @@
'bug/activity/table.html.tmpl' => [
'change.attachid',
- 'change.field',
],
'attachment/create.html.tmpl' => [
@@ -403,7 +323,8 @@
'attachment/edit.html.tmpl' => [
'attachment.id',
'attachment.bug_id',
- 'a',
+ 'a',
+ 'editable_or_hide',
],
'attachment/list.html.tmpl' => [
@@ -432,8 +353,6 @@
'bugid',
'oldid',
'newid',
- 'style',
- 'javascript',
'patch.id',
],
@@ -446,8 +365,6 @@
'section_num',
'current_line_old',
'current_line_new',
- 'curr_old',
- 'curr_new'
],
'admin/admin.html.tmpl' => [
@@ -458,6 +375,12 @@
'link_uri'
],
+'admin/custom_fields/cf-js.js.tmpl' => [
+ 'constants.FIELD_TYPE_SINGLE_SELECT',
+ 'constants.FIELD_TYPE_MULTI_SELECT',
+ 'constants.FIELD_TYPE_BUG_ID',
+],
+
'admin/params/common.html.tmpl' => [
'sortlist_separator',
],
@@ -467,22 +390,17 @@
],
'admin/products/groupcontrol/edit.html.tmpl' => [
- 'group.bugcount',
- 'group.id',
- 'const.CONTROLMAPNA',
- 'const.CONTROLMAPSHOWN',
- 'const.CONTROLMAPDEFAULT',
- 'const.CONTROLMAPMANDATORY',
+ 'group.id',
+ 'constants.CONTROLMAPNA',
+ 'constants.CONTROLMAPSHOWN',
+ 'constants.CONTROLMAPDEFAULT',
+ 'constants.CONTROLMAPMANDATORY',
],
'admin/products/list.html.tmpl' => [
'classification_url_part',
],
-'admin/products/confirm-delete.html.tmpl' => [
- 'classification_url_part',
-],
-
'admin/products/footer.html.tmpl' => [
'classification_url_part',
'classification_text',
@@ -494,12 +412,8 @@
],
'admin/flag-type/edit.html.tmpl' => [
- 'action',
'type.id',
- 'type.target_type',
'type.sortkey || 1',
- 'typeLabelLowerPlural',
- 'typeLabelLowerSingular',
'selname',
],
@@ -526,7 +440,6 @@
'flags.setter',
'longdescs',
'quips',
- 'votes',
'series',
'watch.watched',
'watch.watcher',
@@ -554,8 +467,8 @@
'new_status.id',
],
-'account/login.html.tmpl' => [
- 'target',
+'account/auth/login-small.html.tmpl' => [
+ 'qs_suffix',
],
'account/prefs/email.html.tmpl' => [
@@ -573,4 +486,8 @@
'group.id',
],
+'config.rdf.tmpl' => [
+ 'escaped_urlbase',
+],
+
);
diff --git a/Websites/bugs.webkit.org/template/en/default/flag/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/flag/list.html.tmpl
index 5d7f78d..4467e81 100644
--- a/Websites/bugs.webkit.org/template/en/default/flag/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/flag/list.html.tmpl
@@ -18,59 +18,7 @@
# Contributor(s): Myk Melez <myk@mozilla.org>
#%]
-<script type="text/javascript">
-<!--
- // Enables or disables a requestee field depending on whether or not
- // the user is requesting the corresponding flag.
- function toggleRequesteeField(flagField, no_focus)
- {
- // Convert the ID of the flag field into the ID of its corresponding
- // requestee field and then use the ID to get the field.
- var id = flagField.name.replace(/flag(_type)?-(\d+)/, "requestee$1-$2");
- var requesteeField = document.getElementById(id);
- if (!requesteeField) return;
-
- // Enable or disable the requestee field based on the value
- // of the flag field.
- if (flagField.value == "?") {
- requesteeField.disabled = false;
- if (!no_focus) requesteeField.focus();
- } else
- requesteeField.disabled = true;
- }
-
- // Disables requestee fields when the window is loaded since they shouldn't
- // be enabled until the user requests that flag type.
- function disableRequesteeFields()
- {
- var inputElements = document.getElementsByTagName("input");
- var selectElements = document.getElementsByTagName("select");
- //You cannot update Node lists, so you must create an array to combine the NodeLists
- var allElements = [];
- for( var i=0; i < inputElements.length; i++ ) {
- allElements[allElements.length] = inputElements.item(i);
- }
- for( var i=0; i < selectElements.length; i++ ) { //Combine inputs with selects
- allElements[allElements.length] = selectElements.item(i);
- }
- var inputElement, id, flagField;
- for ( var i=0 ; i<allElements.length ; i++ )
- {
- inputElement = allElements[i];
- if (inputElement.name.search(/^requestee(_type)?-(\d+)$/) != -1)
- {
- // Convert the ID of the requestee field into the ID of its corresponding
- // flag field and then use the ID to get the field.
- id = inputElement.name.replace(/requestee(_type)?-(\d+)/, "flag$1-$2");
- flagField = document.getElementById(id);
- if (flagField && flagField.value != "?")
- inputElement.disabled = true;
- }
- }
- }
- window.onload = disableRequesteeFields;
-// -->
-</script>
+[% IF user.id AND !read_only_flags %]
[%# We list flags by looping twice over the flag types relevant for the bug.
# In the first loop, we display existing flags and then, for active types,
@@ -82,6 +30,8 @@
[% DEFAULT flag_table_id = "flags" %]
+<script src="[% 'js/flag.js' FILTER mtime %]" type="text/javascript"></script>
+
<table id="[% flag_table_id FILTER html %]">
[% UNLESS flag_no_header %]
<tr>
@@ -97,13 +47,13 @@
[% END %]
[%# Step 1: Display every flag type (except inactive types with no flags). %]
- [% FOREACH type = flag_types %]
-
- [%# Step 1a: Display existing flag(s). %]
+ [% FOREACH type = flag_types -%]
+
+ [%-# Step 1a: Display existing flag(s). %]
[% FOREACH flag = type.flags %]
<tr>
<td>
- [% flag.setter.nick FILTER html %]:
+ <span title="[% flag.setter.identity FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
</td>
<td>
<label title="[% type.description FILTER html %]"
@@ -114,9 +64,9 @@
<select id="flag-[% flag.id %]" name="flag-[% flag.id %]"
title="[% type.description FILTER html %]"
onchange="toggleRequesteeField(this);"
- class="flag_select">
+ class="flag_select flag_type-[% type.id %]">
[%# Only display statuses the user is allowed to set. %]
- [% IF user.can_request_flag(type) %]
+ [% IF user.can_request_flag(type) || flag.setter_id == user.id %]
<option value="X"></option>
[% END %]
[% IF type.is_active %]
@@ -138,6 +88,7 @@
<td>
[% IF (type.is_active && type.is_requestable && type.is_requesteeble) || flag.requestee %]
<span style="white-space: nowrap;">
+ [% SET flag_custom_list = [] %]
[% IF Param('usemenuforusers') %]
[% flag_custom_list = flag.type.grant_list %]
[% IF !(type.is_active && type.is_requestable && type.is_requesteeble) %]
@@ -146,77 +97,27 @@
nothing else. %]
[% flag_custom_list = [flag.requestee] %]
[% END %]
- [% INCLUDE global/userselect.html.tmpl
- name => "requestee-$flag.id"
- id => "requestee-$flag.id"
- value => flag.requestee.login
- multiple => 0
- emptyok => 1
- custom_userlist => flag_custom_list
- %]
- [% ELSE %]
- (<input type="text" size="30" maxlength="255"
- id="requestee-[% flag.id %]"
- name="requestee-[% flag.id %]"
- [% IF flag.status == "?" && flag.requestee %]
- value="[% flag.requestee.login FILTER html %]"
- [% END %]>)
[% END %]
+ [% INCLUDE global/userselect.html.tmpl
+ name => "requestee-$flag.id"
+ id => "requestee-$flag.id"
+ value => flag.requestee.login
+ multiple => 0
+ emptyok => 1
+ classes => ["requestee"]
+ custom_userlist => flag_custom_list
+ %]
</span>
[% END %]
</td>
[% END %]
</tr>
- [% END %]
-
- [%# Step 1b: Display UI for setting flag. %]
+ [% END -%]
+
+ [%-# Step 1b: Display UI for setting flag. %]
[% IF (!type.flags || type.flags.size == 0) && type.is_active %]
- <tr>
- <td> </td>
- <td>
- <label title="[% type.description FILTER html %]"
- for="flag_type-[% type.id %]">
- [%- type.name FILTER html FILTER no_break %]</label>
- </td>
- <td>
- <select id="flag_type-[% type.id %]" name="flag_type-[% type.id %]"
- title="[% type.description FILTER html %]"
- [% " disabled=\"disabled\"" UNLESS (type.is_requestable && user.can_request_flag(type)) || user.can_set_flag(type) %]
- onchange="toggleRequesteeField(this);"
- class="flag_select">
- <option value="X"></option>
- [% IF type.is_requestable && user.can_request_flag(type) %]
- <option value="?">?</option>
- [% END %]
- [% IF user.can_set_flag(type) %]
- <option value="+">+</option>
- <option value="-">-</option>
- [% END %]
- </select>
- </td>
- [% IF any_flags_requesteeble %]
- <td>
- [% IF type.is_requestable && type.is_requesteeble %]
- <span style="white-space: nowrap;">
- [% IF Param('usemenuforusers') %]
- [% INCLUDE global/userselect.html.tmpl
- name => "requestee_type-$type.id"
- id => "requestee_type-$type.id"
- multiple => type.is_multiplicable * 3
- emptyok => !type.is_multiplicable
- value => ""
- custom_userlist => type.grant_list
- %]
- [% ELSE %]
- (<input type="text" size="30" maxlength="255"
- id="requestee_type-[% type.id %]"
- name="requestee_type-[% type.id %]">)
- [% END %]
- </span>
- [% END %]
- </td>
- [% END %]
- </tr>
+
+ [% PROCESS flag_row first_cell_empty = 1 addl_text = "" %]
[% END %]
[% END %]
@@ -227,51 +128,89 @@
<tr><td colspan="3"><hr></td></tr>
[% separator_displayed = 1 %]
[% END %]
- <tr>
- <td colspan="2">
- addl. <label title="[% type.description FILTER html %]"
- for="flag_type-[% type.id %]">
- [%- type.name FILTER html FILTER no_break %]</label>
- </td>
- <td>
- <select id="flag_type-[% type.id %]" name="flag_type-[% type.id %]"
- title="[% type.description FILTER html %]"
- [% " disabled=\"disabled\"" UNLESS (type.is_requestable && user.can_request_flag(type)) || user.can_set_flag(type) %]
- onchange="toggleRequesteeField(this);"
- class="flag_select">
- <option value="X"></option>
- [% IF type.is_requestable && user.can_request_flag(type) %]
- <option value="?">?</option>
- [% END %]
- [% IF user.can_set_flag(type) %]
- <option value="+">+</option>
- <option value="-">-</option>
- [% END %]
- </select>
- </td>
- [% IF any_flags_requesteeble %]
- <td>
- [% IF type.is_requestable && type.is_requesteeble %]
- <span style="white-space: nowrap;">
- [% IF Param('usemenuforusers') %]
- [% INCLUDE global/userselect.html.tmpl
- name => "requestee_type-$type.id"
- id => "requestee_type-$type.id"
- multiple => type.is_multiplicable * 3
- emptyok => !type.is_multiplicable
- value => ""
- custom_userlist => type.grant_list
- %]
- [% ELSE %]
- (<input type="text" size="30" maxlength="255"
- id="requestee_type-[% type.id %]"
- name="requestee_type-[% type.id %]">)
- [% END %]
- </span>
- [% END %]
- </td>
+
+ [% PROCESS flag_row first_cell_empty = 0 addl_text = "addl." %]
+ [% END %]
+</table>
+
+[% ELSE %]
+ [%# The user is logged out. Display flags as read-only. %]
+ [% header_displayed = 0 %]
+ [% FOREACH type = flag_types %]
+ [% FOREACH flag = type.flags %]
+ [% IF !flag_no_header AND !header_displayed %]
+ <p><b>Flags:</b></p>
+ [% header_displayed = 1 %]
[% END %]
- </tr>
+ [% IF flag.setter.name %]
+ <span title="[% flag.setter.name FILTER html %]">[% flag.setter.nick FILTER html %]</span>:
+ [% ELSE %]
+ [% flag.setter.nick FILTER html %]:
+ [% END %]
+ [%+ type.name FILTER html FILTER no_break %][% flag.status %]
+ [% IF flag.requestee %]
+ [% IF flag.requestee.name %]
+ (<span title="[% flag.requestee.name FILTER html %]">[% flag.requestee.nick FILTER html %]</span>)
+ [% ELSE %]
+ ([% flag.requestee.nick FILTER html %])
+ [% END %]
+ [% END %]<br>
+ [% END %]
+ [% END %]
+[% END %]
+
+[%# Display a table row for unset flags %]
+
+[% BLOCK flag_row %]
+ <tr>
+ [% IF first_cell_empty %]
+ <td> </td>
+ <td>
+ [% ELSE %]
+ <td colspan="2">
[% END %]
-</table>
+ [% addl_text FILTER html %]
+ <label title="[% type.description FILTER html %]" for="flag_type-[% type.id %]">
+ [%- type.name FILTER html FILTER no_break %]</label>
+ </td>
+ <td>
+ <select id="flag_type-[% type.id %]" name="flag_type-[% type.id %]"
+ title="[% type.description FILTER html %]"
+ [% " disabled=\"disabled\"" UNLESS (type.is_requestable && user.can_request_flag(type)) || user.can_set_flag(type) %]
+ onchange="toggleRequesteeField(this);"
+ class="flag_select flag_type-[% type.id %]">
+ <option value="X"></option>
+ [% IF type.is_requestable && user.can_request_flag(type) %]
+ <option value="?">?</option>
+ [% END %]
+ [% IF user.can_set_flag(type) %]
+ <option value="+">+</option>
+ <option value="-">-</option>
+ [% END %]
+ </select>
+ </td>
+ [% IF any_flags_requesteeble %]
+ <td>
+ [% IF type.is_requestable && type.is_requesteeble %]
+ <span style="white-space: nowrap;">
+ [% SET grant_list = [] %]
+ [% IF Param('usemenuforusers') %]
+ [% grant_list = type.grant_list %]
+ [% END %]
+ [% INCLUDE global/userselect.html.tmpl
+ name => "requestee_type-$type.id"
+ id => "requestee_type-$type.id"
+ multiple => type.is_multiplicable * 3
+ emptyok => !type.is_multiplicable
+ value => ""
+ custom_userlist => grant_list
+ classes => ["requestee"]
+ %]
+
+ </span>
+ [% END %]
+ </td>
+ [% END %]
+ </tr>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/choose-classification.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/choose-classification.html.tmpl
index 6a37719..fbac484 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/choose-classification.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/choose-classification.html.tmpl
@@ -29,13 +29,11 @@
[% PROCESS global/header.html.tmpl %]
<table>
-
-[% IF Param('showallproducts') %]
<tr>
<th align="right">
- <a href="[% target FILTER url_quote %]?classification=__all
- [% IF cloned_bug_id %]&cloned_bug_id=[% cloned_bug_id FILTER url_quote %][% END -%]
- [%- IF format %]&format=[% format FILTER url_quote %][% END %]">
+ <a href="[% target FILTER uri %]?classification=__all
+ [% IF cloned_bug_id %]&cloned_bug_id=[% cloned_bug_id FILTER uri %][% END -%]
+ [%- IF format %]&format=[% format FILTER uri %][% END %]">
All</a>:
</th>
@@ -44,14 +42,13 @@
<tr>
<th colspan="2"> </th>
</tr>
-[% END %]
[% FOREACH class = classifications %]
<tr>
<th align="right">
- <a href="[% target FILTER url_quote %]?classification=[% class.name FILTER url_quote -%]
- [%- IF cloned_bug_id %]&cloned_bug_id=[% cloned_bug_id FILTER url_quote %][% END -%]
- [%- IF format %]&format=[% format FILTER url_quote %][% END %]">
+ <a href="[% target FILTER uri %]?classification=[% class.name FILTER uri -%]
+ [%- IF cloned_bug_id %]&cloned_bug_id=[% cloned_bug_id FILTER uri %][% END -%]
+ [%- IF format %]&format=[% format FILTER uri %][% END %]">
[% class.name FILTER html %]</a>:
</th>
diff --git a/Websites/bugs.webkit.org/template/en/default/global/choose-product.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/choose-product.html.tmpl
index 0a38db6..ae00bf5 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/choose-product.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/choose-product.html.tmpl
@@ -31,15 +31,17 @@
[% IF target == "enter_bug.cgi" %]
[% title = "Enter $terms.Bug" %]
- [% subheader = BLOCK %]First, you must pick a product on which to enter [% terms.abug %]. [% END %]
+ [% h2 = BLOCK %]First, you must pick a product on which to enter [% terms.abug %]: [% END %]
[% ELSIF target == "describecomponents.cgi" %]
- [% title = "$terms.Bugzilla Component Descriptions" %]
- [% subheader = "Please specify the product whose components you want described." %]
+ [% title = "Browse" %]
+ [% h2 = "Select a product category to browse:" %]
[% END %]
[% DEFAULT title = "Choose a Product" %]
[% PROCESS global/header.html.tmpl %]
+<h2>[% h2 FILTER html %]</h2>
+
<table>
[% FOREACH c = classifications %]
@@ -53,9 +55,9 @@
[% FOREACH p = c.products %]
<tr>
<th align="right" valign="top">
- <a href="[% target %]?product=[% p.name FILTER url_quote -%]
- [%- IF cloned_bug_id %]&cloned_bug_id=[% cloned_bug_id FILTER url_quote %][% END -%]
- [%- IF format %]&format=[% format FILTER url_quote %][% END %]">
+ <a href="[% target %]?product=[% p.name FILTER uri -%]
+ [%- IF cloned_bug_id %]&cloned_bug_id=[% cloned_bug_id FILTER uri %][% END -%]
+ [%- IF format %]&format=[% format FILTER uri %][% END %]">
[% p.name FILTER html FILTER no_break %]</a>:
</th>
diff --git a/Websites/bugs.webkit.org/template/en/default/global/code-error.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/code-error.html.tmpl
index f965b7f..f09415c 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/code-error.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/code-error.html.tmpl
@@ -32,22 +32,12 @@
# in this file; if you do not wish to change it, use the "none" filter.
#%]
-[% PROCESS global/variables.none.tmpl %]
+[% PROCESS "global/field-descs.none.tmpl" %]
[% DEFAULT title = "Internal Error" %]
[% error_message = BLOCK %]
- [% IF error == "action_unrecognized" %]
- [% docslinks = {'query.html' => "Searching for $terms.bugs",
- 'query.html#list' => "$terms.Bug lists"} %]
- I don't recognize the value (<em>[% action FILTER html %]</em>)
- of the <em>action</em> variable.
-
- [% ELSIF error == "attachment_already_obsolete" %]
- Attachment #[% attach_id FILTER html %] ([% description FILTER html %])
- is already obsolete.
-
- [% ELSIF error == "auth_invalid_email" %]
+ [% IF error == "auth_invalid_email" %]
[% title = "Invalid Email Address" %]
We received an email address (<b>[% addr FILTER html %]</b>)
that didn't pass our syntax checking for a legal email address,
@@ -56,10 +46,9 @@
A legal address must contain exactly one '@',
and at least one '.' after the @.
[% ELSE %]
- [%+ Param('emailregexpdesc') %]
+ [%+ Param('emailregexpdesc') FILTER html_light %]
[% END %]
- It must also not contain any of these special characters:
- <tt>\ ( ) & < > , ; : " [ ]</tt>, or any whitespace.
+ It also must not contain any illegal characters.
[% ELSIF error == "authres_unhandled" %]
The result value of [% value FILTER html %] was not handled by
@@ -101,12 +90,12 @@
[% ELSIF error == "chart_file_open_fail" %]
Unable to open the chart datafile <tt>[% filename FILTER html %]</tt>.
-
- [% ELSIF error == "chart_lines_not_installed" %]
- [% admindocslinks = {'installation.html#install-perlmodules' => 'Installing Perl modules necessary for Charting'} %]
- Charts will not work without the Chart::Lines Perl module being installed.
- Run checksetup.pl for installation instructions.
-
+
+ [% ELSIF error == "column_alter_nonexistent_fk" %]
+ You attempted to modify the foreign key for
+ [%+ table FILTER html %].[% column FILTER html %], but there is
+ no foreign key on that column.
+
[% ELSIF error == "column_not_null_without_default" %]
Failed adding the column [% name FILTER html %]:
You cannot add a NOT NULL column with no default to an existing table
@@ -117,9 +106,26 @@
without specifying a default or something for $set_nulls_to, because
there are NULL values currently in it.
+ [% ELSIF error == "comment_extra_data_not_allowed" %]
+ You tried to set the <code>extra_data</code> field to
+ '[% extra_data FILTER html %]' but comments of type [% type FILTER html %]
+ do not accept an <code>extra_data</code> argument.
+
+ [% ELSIF error == "comment_extra_data_required" %]
+ Comments of type [% type FILTER html %] require an <code>extra_data</code>
+ argument to be set.
+
+ [% ELSIF error == "comment_extra_data_not_numeric" %]
+ You tried to set the <code>extra_data</code> field to
+ '[% extra_data FILTER html %]' but comments of type [% type FILTER html %]
+ require a numeric <code>extra_data</code> argument.
+
+ [% ELSIF error == "comment_type_invalid" %]
+ '[% type FILTER html %]' is not a valid comment type.
+
[% ELSIF error == "db_rename_conflict" %]
Name conflict: Cannot rename [% old FILTER html %] to
- [% new FILTER html %] because [% new FILTER html %] already exists.
+ [%+ new FILTER html %] because [% new FILTER html %] already exists.
[% ELSIF error == "cookies_need_value" %]
Every cookie must have a value.
@@ -136,27 +142,56 @@
address.
[% END %]
- [% ELSIF error == "extension_invalid" %]
- An error occurred processing hook [% name FILTER html %] in
- extension [% extension FILTER html %]: [% errstr FILTER html %]
+ [% ELSIF error == "extension_disabled" %]
+ [% title = "Extension Disabled" %]
+ You cannot access this page because the extension '[% name FILTER html %]'
+ is disabled.
+
+ [% ELSIF error == "extension_must_be_subclass" %]
+ <code>[% package FILTER html %]</code> from
+ <code>[% filename FILTER html %]</code> is not a subclass of
+ <code>[% class FILTER html %]</code>.
+
+ [% ELSIF error == "extension_must_return_name" %]
+ <code>[% extension FILTER html %]</code> returned
+ <code>[% returned FILTER html %]</code>, which is not a valid name
+ for an extension. Extensions must return their name, not <code>1</code>
+ or a number. See the documentation of
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Extension.html">Bugzilla::Extension</a>
+ for details.
+
+ [% ELSIF error == "extension_no_name" %]
+ We did not find a <code>NAME</code> method in
+ <code>[% package FILTER html %]</code> (loaded from
+ <code>[% filename FILTER html %]</code>). This means that
+ the extension has one or more of the following problems:
+
+ <ul>
+ <li><code>[% filename FILTER html %]</code> did not define a
+ <code>[% package FILTER html %]</code> package.</li>
+ <li><code>[% package FILTER html %]</code> did not define a
+ <code>NAME</code> method (or the <code>NAME</code> method
+ returned an empty string).</li>
+ </ul>
[% ELSIF error == "extern_id_conflict" %]
The external ID '[% extern_id FILTER html %]' already exists
in the database for '[% username FILTER html %]', but your
account source says that '[% extern_user FILTER html %]' has that ID.
- [% ELSIF error == "field_type_mismatch" %]
- Cannot seem to handle <code>[% field FILTER html %]</code>
- and <code>[% type FILTER html %]</code> together.
+ [% ELSIF error == "field_choice_must_use_type" %]
+ When you call a class method on <code>Bugzilla::Field::Choice</code>,
+ you must call <code>Bugzilla::Field::Choice->type('some_field')</code>
+ to generate the right class (you can't call class methods directly
+ on Bugzilla::Field::Choice).
[% ELSIF error == "field_not_custom" %]
'[% field.description FILTER html %]' ([% field.name FILTER html %])
is not a custom field.
- [% ELSIF error == "gd_not_installed" %]
- [% admindocslinks = {'installation.html#install-perlmodules' => 'Installing Perl modules necessary for Charting'} %]
- Charts will not work without the GD Perl module being installed.
- Run checksetup.pl for installation instructions.
+ [% ELSIF error == "field_type_not_specified" %]
+ [% title = "Field Type Not Specified" %]
+ You must specify a type when creating a custom field.
[% ELSIF error == "illegal_content_type_method" %]
Your form submission got corrupted somehow. The <em>content
@@ -168,25 +203,10 @@
[% ELSIF error == "illegal_field" %]
A legal [% field FILTER html %] was not set.
- [% ELSIF error == "inactive_group" %]
- Attempted to add [% terms.bug %] to the '[% name FILTER html %]'
- group, which is not used for [% terms.bugs %].
-
[% ELSIF error == "invalid_attach_id_to_obsolete" %]
The attachment number of one of the attachments you wanted to obsolete,
- [% attach_id FILTER html %], is invalid.
+ [%+ attach_id FILTER html %], is invalid.
- [% ELSIF error == "invalid_column_name_cookie" %]
- [% title = "Invalid Column Name" %]
- The custom sort order specified in your cookie contains an invalid
- column name <em>[% fragment FILTER html %]</em>.
- The cookie has been cleared.
-
- [% ELSIF error == "invalid_column_name_form" %]
- [% title = "Invalid Column Name" %]
- The custom sort order specified in your form submission contains an
- invalid column name <em>[% fragment FILTER html %]</em>.
-
[% ELSIF error == "invalid_customfield_type" %]
[% title = "Invalid Field Type" %]
The type <em>[% type FILTER html %]</em> is not a valid field type.
@@ -195,6 +215,12 @@
[% title = "Invalid Dimensions" %]
The width or height specified is not a positive integer.
+ [% ELSIF error == "invalid_feature" %]
+ [% title = "Invalid Feature Name" %]
+ [% feature FILTER html %] is not a valid feature name. See
+ <code>OPTIONAL_MODULES</code> in
+ <code>Bugzilla::Install::Requirements</code> for valid names.
+
[% ELSIF error == "invalid_flag_association" %]
[% title = "Invalid Flag Association" %]
Some flags do not belong to
@@ -209,6 +235,10 @@
The series_id [% series_id FILTER html %] is not valid. It may be that
this series has been deleted.
+ [% ELSIF error == "invalid_timestamp" %]
+ The entered timestamp <code>[% timestamp FILTER html %]</code> could not
+ be parsed into a valid date and time.
+
[% ELSIF error == "invalid_webservergroup" %]
There is no such group: [% group FILTER html %]. Check your $webservergroup
setting in [% constants.bz_locations.localconfig FILTER html %].
@@ -217,38 +247,32 @@
Attachment [% attach_id FILTER html %] ([% description FILTER html %])
is attached to [% terms.bug %] [%+ attach_bug_id FILTER html %],
but you tried to flag it as obsolete while creating a new attachment to
- [% terms.bug %] [%+ my_bug_id FILTER html %].
+ [%+ terms.bug %] [%+ my_bug_id FILTER html %].
- [% ELSIF error == "flags_not_available" %]
- [% title = "Flag Editing not Allowed" %]
- [% IF type == "b" %]
- Flags cannot be set or changed when
- changing several [% terms.bugs %] at once.
- [% ELSE %]
- References to existing flags when creating
- a new attachment are invalid.
- [% END %]
+ [% ELSIF error == "feature_disabled" %]
+ The [% install_string("feature_$feature") FILTER html %] feature is not
+ available in this [% terms.Bugzilla %].
+ [% IF user.in_group('admin') %]
+ If you would like to enable this feature, please run
+ <kbd>checksetup.pl</kbd> to see how to install the necessary
+ requirements for this feature.
+ [% END %]
+
+ [% ELSIF error == "flag_unexpected_object" %]
+ [% title = "Object Not Recognized" %]
+ Flags cannot be set for objects of type [% caller FILTER html %].
+ They can only be set for [% terms.bugs %] and attachments.
[% ELSIF error == "flag_requestee_disabled" %]
[% title = "Flag not Requestable from Specific Person" %]
You can't ask a specific person for
<em>[% type.name FILTER html %]</em>.
- [% ELSIF error == "flag_status_invalid" %]
- The flag status <em>[% status FILTER html %]</em>
- [% IF id %]
- for flag ID #[% id FILTER html %]
- [% END %]
- is invalid.
-
[% ELSIF error == "flag_type_inactive" %]
[% title = "Inactive Flag Type" %]
The flag type [% type FILTER html %] is inactive and cannot be used
to create new flags.
- [% ELSIF error == "flag_type_nonexistent" %]
- There is no flag type with the ID <em>[% id FILTER html %]</em>.
-
[% ELSIF error == "flag_type_target_type_invalid" %]
The target type was neither <em>[% terms.bug %]</em> nor <em>attachment</em>
but rather <em>[% target_type FILTER html %]</em>.
@@ -271,6 +295,17 @@
given.
[% END %]
+ [% ELSIF error == "jobqueue_insert_failed" %]
+ [% title = "Job Queue Failure" %]
+ Inserting a <code>[% job FILTER html %]</code> job into the Job
+ Queue failed with the following error: [% errmsg FILTER html %]
+
+ [% ELSIF error == "jobqueue_no_job_mapping" %]
+ <code>Bugzilla::JobQueue</code> has not been configured to handle
+ the job "[% job FILTER html %]". You need to add this job type
+ to the <code>JOB_MAP</code> constant in <code>Bugzilla::JobQueue</code>,
+ perhaps by using the 'job_map' hook.
+
[% ELSIF error == "ldap_bind_failed" %]
Failed to bind to the LDAP server. The error message was:
<code>[% errstr FILTER html %]</code>
@@ -287,7 +322,11 @@
[% ELSIF error == "ldap_search_error" %]
An error occurred while trying to search LDAP for
"[% username FILTER html %]":
- <code>[% errstr FILTER html %]</code>
+ [% IF errstr %]
+ <code>[% errstr FILTER html %]</code>
+ [% ELSE %]
+ Unable to find user in LDAP
+ [% END %]
[% ELSIF error == "ldap_server_not_defined" %]
The LDAP server for authentication has not been defined.
@@ -295,7 +334,7 @@
[% ELSIF error == "mail_send_error" %]
There was an error sending mail from '[% mail.header('From') FILTER html %]'
to '[% mail.header('To') FILTER html %]':
- [% msg FILTER html %]
+ [%+ msg FILTER html %]
[% ELSIF error == "missing_bug_id" %]
No [% terms.bug %] ID was given.
@@ -309,9 +348,10 @@
[% ELSIF error == "need_quipid" %]
A valid quipid is needed.
- [% ELSIF error == "no_manual_moved" %]
- You cannot set the resolution of [% terms.abug %] to MOVED without
- moving the [% terms.bug %].
+ [% ELSIF error == "object_dep_sort_loop" %]
+ There is a loop in VALIDATOR_DEPENDENCIES involving
+ '[%+ field FILTER html %]'. Here are the fields we considered:
+ [%+ considered.join(', ') FILTER html %].
[% ELSIF error == "param_invalid" %]
[% title = "Invalid Parameter" %]
@@ -320,8 +360,8 @@
[% ELSIF error == "param_must_be_numeric" %]
[% title = "Invalid Parameter" %]
- Invalid parameter passed to [% function FILTER html %].
- It must be numeric.
+ Invalid parameter <code>[% param FILTER html %]</code> passed to
+ <code>[% function FILTER html %]</code>: It must be numeric.
[% ELSIF error == "param_required" %]
[% title = "Missing Parameter" %]
@@ -329,6 +369,22 @@
a <code>[% param FILTER html %]</code> argument, and that
argument was not set.
+ [% ELSIF error == "params_required" %]
+ [% title = "Missing Parameter" %]
+ The function <code>[% function FILTER html %]</code> requires
+ that you set one of the following parameters:
+ <code>[% params.join(', ') FILTER html %]</code>
+
+ [% ELSIF error == "product_empty_group_controls" %]
+ [% title = "Missing Group Controls" %]
+ New settings must be defined to edit group controls for
+ the [% group.name FILTER html %] group.
+
+ [% ELSIF error == "product_illegal_group_control" %]
+ [% title = "Illegal Group Control" %]
+ '[% value FILTER html %]' is not a legal value for
+ the '[% field FILTER html %]' field.
+
[% ELSIF error == "protection_violation" %]
The function <code>[% function FILTER html %]</code> was called
@@ -349,12 +405,6 @@
An error occurred while preparing for a RADIUS authentication request:
<code>[% errstr FILTER html %]</code>.
- [% ELSIF error == "unknown_comparison_type" %]
- Specified comparison type is not supported.
-
- [% ELSIF error == "request_queue_group_invalid" %]
- The group field <em>[% group FILTER html %]</em> is invalid.
-
[% ELSIF error == "report_axis_invalid" %]
<em>[% val FILTER html %]</em> is not a valid value for
[%+ IF fld == "x" %]the horizontal axis
@@ -362,9 +412,16 @@
[%+ ELSIF fld == "z" %]the multiple tables/images
[%+ ELSE %]a report axis[% END %] field.
+ [% ELSIF error == "search_cp_without_op" %]
+ Search argument f[% id FILTER html %] is "CP" but there is no
+ matching "OP" before it.
+
+ [% ELSIF error == "search_invalid_joiner" %]
+ '[% joiner FILTER html %]' is not a valid joiner for a search.
+
[% ELSIF error == "setting_info_invalid" %]
To create a new setting, you must supply a setting name, a list of
- value/sortindex pairs, and the devault value.
+ value/sortindex pairs, and the default value.
[% ELSIF error == "setting_name_invalid" %]
The setting name <em>[% name FILTER html %]</em> is not a valid
@@ -379,12 +436,6 @@
The value "<code>[% value FILTER html %]</code>" is not in the list of
legal values for the <em>[% name FILTER html %]</em> setting.
- [% ELSIF error == "soap_not_installed" %]
- [% admindocslinks = {'installation.html#install-perlmodules' => 'Installing Perl modules'} %]
- The XMLRPC interface will not work without the SOAP::Lite Perl module being
- installed.
- Run checksetup.pl for installation instructions.
-
[% ELSIF error == "token_generation_error" %]
Something is seriously wrong with the token generation system.
@@ -400,12 +451,8 @@
[% ELSIF error == "undefined_field" %]
Form field [% field FILTER html %] was not defined.
- [% ELSIF error == "unknown_action" %]
- [% IF action %]
- Unknown action [% action FILTER html %]!
- [% ELSE %]
- I could not figure out what you wanted to do.
- [% END %]
+ [% ELSIF error == "unknown_method" %]
+ The requested method '[% method FILTER html %]' was not found.
[% ELSIF error == "usage_mode_invalid" %]
'[% invalid_usage_mode FILTER html %]' is not a valid usage mode.
@@ -417,11 +464,6 @@
[% ELSIF error == "not_in_transaction" %]
Attempted to end transaction without starting one first.
- [% ELSIF error == "comma_operator_deprecated" %]
- [% title = "SQL query generator internal error" %]
- There is an internal error in the SQL query generation code,
- creating queries with implicit JOIN.
-
[% ELSIF error == "invalid_post_bug_submit_action" %]
Invalid setting for post_bug_submit_action
@@ -478,14 +520,15 @@
<table cellpadding="20">
<tr>
- <td bgcolor="#ff0000">
- <font size="+2">
- [% error_message FILTER none %]
- </font>
+ <td id="error_msg" class="throw_error">
+ [% error_message FILTER none %]
</td>
</tr>
</table>
+<p>Traceback:</p>
+<pre>[% traceback FILTER html %]</pre>
+
[% IF variables %]
<pre>
Variables:
diff --git a/Websites/bugs.webkit.org/template/en/default/global/common-links.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/common-links.html.tmpl
index 8b4c284..769d41e 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/common-links.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/common-links.html.tmpl
@@ -20,10 +20,12 @@
#%]
[% DEFAULT qs_suffix = "" %]
+[% USE Bugzilla %]
<ul class="links">
<li><a href="./">Home</a></li>
<li><span class="separator">| </span><a href="enter_bug.cgi">New</a></li>
+ <li><span class="separator">| </span><a href="describecomponents.cgi">Browse</a></li>
<li><span class="separator">| </span><a href="query.cgi">Search</a></li>
<li class="form">
@@ -32,9 +34,12 @@
onsubmit="if (this.quicksearch.value == '')
{ alert('Please enter one or more search terms first.');
return false; } return true;">
- <input class="txt" type="text" id="quicksearch[% qs_suffix FILTER html %]" name="quicksearch">
- <input class="btn" type="submit" value="Find" id="find[% qs_suffix FILTER html %]">
- [%-# Work around FF bug: keep this on one line %]</form></li>
+ <input class="txt" type="text" id="quicksearch[% qs_suffix FILTER html %]" name="quicksearch"
+ title="Quick Search" value="[% quicksearch FILTER html %]">
+ <input class="btn" type="submit" value="Search"
+ id="find[% qs_suffix FILTER html %]">
+ [%-# Work around FF bug: keep this on one line %]</form>
+ <a href="page.cgi?id=quicksearch.html" title="Quicksearch Help">[?]</a></li>
<li><span class="separator">| </span><a href="report.cgi">Reports</a></li>
@@ -42,24 +47,20 @@
[% IF Param('shutdownhtml') || Bugzilla.has_flags %]
<span class="separator">| </span>
[% IF user.id %]
- <a href="request.cgi?requester=[% user.login FILTER url_quote %]&requestee=
- [% user.login FILTER url_quote %]&do_union=1&group=type&action=queue">My Requests</a>
+ <a href="request.cgi?requester=[% user.login FILTER uri %]&requestee=
+ [% user.login FILTER uri %]&do_union=1&group=type&action=queue">My Requests</a>
[% ELSE %]
<a href="request.cgi">Requests</a>
[% END %]
[% END %]
[%-# Work around FF bug: keep this on one line %]</li>
- [% IF user.id && Param('usevotes') %]
- <li><span class="separator">| </span><a href="votes.cgi?action=show_user">My Votes</a></li>
- [% END %]
-
[% IF user.login %]
<li><span class="separator">| </span><a href="userprefs.cgi">Preferences</a></li>
- [% IF user.groups.tweakparams || user.groups.editusers || user.can_bless
- || (Param('useclassification') && user.groups.editclassifications)
- || user.groups.editcomponents || user.groups.admin || user.groups.creategroups
- || user.groups.editkeywords || user.groups.bz_canusewhines
+ [% IF user.in_group('tweakparams') || user.in_group('editusers') || user.can_bless
+ || (Param('useclassification') && user.in_group('editclassifications'))
+ || user.in_group('editcomponents') || user.in_group('admin') || user.in_group('creategroups')
+ || user.in_group('editkeywords') || user.in_group('bz_canusewhines')
|| user.get_products_by_permission("editcomponents").size %]
<li><span class="separator">| </span><a href="admin.cgi">Administration</a></li>
[% END %]
@@ -69,7 +70,7 @@
<li>
<span class="separator">| </span>
[% IF user.authorizer.can_logout %]
- <a href="relogin.cgi">Log out</a>
+ <a href="index.cgi?logout=1">Log out</a>
[% ELSE %]
Logged in as
[% END %]
@@ -82,41 +83,29 @@
[% END %]
[%-# Work around FF bug: keep this on one line %]</li>
[% ELSE %]
+
+ [% PROCESS link_to_documentation %]
+
[% IF Param('createemailregexp')
&& user.authorizer.user_can_create_account %]
- <li><span class="separator">| </span><a href="createaccount.cgi">New Account</a></li>
+ <li id="new_account_container[% qs_suffix FILTER html %]">
+ <span class="separator">| </span>
+ <a href="createaccount.cgi">New Account</a>
+ </li>
[% END %]
- [% PROCESS link_to_documentation %]
-
- [% IF user.authorizer.can_login %]
- [%# Use the current script name. If an empty name is returned,
- # then we are accessing the home page. %]
-
- [% script_name = cgi.url(Relative => 1) %]
-
- [% IF cgi.request_method == "POST" OR script_name.match("relogin") %]
- [% script_name = "" %]
- [% END %]
-
- [%# If SSL is in use, use 'sslbase', else use 'urlbase'. %]
- [% IF Param("sslbase") != "" && Param("ssl") != "never" %]
- [% script_name = Param("sslbase") _ script_name %]
- [% ELSE %]
- [% script_name = Param("urlbase") _ script_name %]
- [% END %]
-
- [% IF cgi.request_method == "GET" AND cgi.query_string %]
- [% script_name = script_name _ "?" _ cgi.query_string %]
- [% script_name = script_name _ "&GoAheadAndLogIn=1" IF !cgi.query_string.match("GoAheadAndLogIn") %]
- [% ELSE %]
- [% script_name = script_name _ "?GoAheadAndLogIn=1" %]
- [% END %]
-
- <li><span class="separator">| </span><a href="[% script_name FILTER html %]">Log In</a></li>
+ [%# Only display one login form when we're on a LOGIN_REQUIRED page. That
+ # way, we're guaranteed that the user will use the form that has
+ # hidden_fields in it (the center form) instead of this one. Also, it's
+ # less confusing to have one form (as opposed to three) when you're
+ # required to log in.
+ #%]
+ [% IF user.authorizer.can_login && !Bugzilla.page_requires_login %]
+ [% PROCESS "account/auth/login-small.html.tmpl" %]
[% END %]
[% END %]
</ul>
+
[% Hook.process("link-row") %]
[% BLOCK link_to_documentation %]
[% IF doc_section && Param('docs_urlbase') %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/confirm-action.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/confirm-action.html.tmpl
index e57a83c..9f9be31 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/confirm-action.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/confirm-action.html.tmpl
@@ -27,6 +27,7 @@
style_urls = ['skins/standard/global.css'] %]
<div class="throw_error">
+<!--reason=[%reason FILTER html %]-->
[% IF reason == "expired_token" %]
Your changes have been rejected because you exceeded the time limit
of [% constants.MAX_TOKEN_AGE FILTER html %] days before submitting your
diff --git a/Websites/bugs.webkit.org/template/en/default/global/confirm-user-match.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/confirm-user-match.html.tmpl
index 5b209df..5549b51 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/confirm-user-match.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/confirm-user-match.html.tmpl
@@ -38,10 +38,26 @@
[%# use the global field descs %]
[% PROCESS "global/field-descs.none.tmpl" %]
+[%# This lists fields which use the user auto-completion feature and which
+ # are not listed in field_descs. %]
+[% field_labels = { # Used by editcomponents.cgi
+ "initialcc" => "Default CC List",
+ "initialowner" => "Default Assignee",
+ "initialqacontact" => "Default QA Contact",
+ # Used by process_bug.cgi
+ "masscc" => "CC List",
+ # Used by request.cgi
+ "requester" => "Requester",
+ "requestee" => "Requestee",
+ # Used by userprefs.cgi
+ "new_watchedusers" => "Watch List",
+
+ }
+%]
[% IF matchsuccess == 1 %]
[% PROCESS global/header.html.tmpl title="Confirm Match" %]
-[% USE Bugzilla %]
+ [% USE Bugzilla %]
<form method="post"
[% IF script -%]
@@ -70,9 +86,13 @@
[% PROCESS global/header.html.tmpl title="Match Failed" %]
<p>
[% terms.Bugzilla %] was unable to make any match at all for one or more of
- the names and/or email addresses you entered on the previous page.<br>
- Please go back and try other names or email addresses.
+ the names and/or email addresses you entered on the previous page.
+ [% IF !user.id %]
+ <b>Note: You are currently logged out. Only exact matches against e-mail
+ addresses will be performed.</b>
+ [% END %]
</p>
+ <p>Please go back and try other names or email addresses.</p>
[% END %]
<table border="0">
@@ -130,10 +150,11 @@
[% ELSE %]
matched
<b>[% query.value.users.0.identity FILTER html %]</b>
+ <input type="hidden" name="[% field.key FILTER html %]"
+ value="[% query.value.users.0.login FILTER html %]">
[% END %]
[% ELSE %]
- [% IF (query.key.length < 3) && !(Param('emailsuffix'))
- && (Param('usermatchmode') == 'search') %]
+ [% IF (query.key.length < 3) && !Param('emailsuffix') %]
<font color="#FF0000">was too short for substring match
(minimum 3 characters)</font>
[% ELSE %]
@@ -155,7 +176,10 @@
[% IF matchsuccess == 1 %]
- [% PROCESS "global/hidden-fields.html.tmpl" exclude="^Bugzilla_(login|password)$" %]
+ [% SET exclude_these =
+ matches.keys.merge(['Bugzilla_login', 'Bugzilla_password']) %]
+ [% SET exclude = '^' _ exclude_these.join('|') _ '$' %]
+ [% PROCESS "global/hidden-fields.html.tmpl" exclude = exclude %]
<p>
<input type="submit" id="continue" value="Continue">
@@ -170,14 +194,12 @@
[% BLOCK field_names %]
- [% IF field_descs.${field_name} %]
- [% field_descs.${field_name} FILTER html -%]
-
- [%-# ELSIF for things that don't belong in the field_descs hash here -%]
-
+ [% IF field_descs.$field_name %]
+ [% field_descs.$field_name FILTER html %]
+ [% ELSIF field_labels.$field_name %]
+ [% field_labels.$field_name FILTER html %]
[% ELSIF field_name.match("^requestee") %]
[% fields.${field_name}.flag_type.name %] requestee
-
[% ELSE %]
[% field_name FILTER html %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/field-descs.none.tmpl b/Websites/bugs.webkit.org/template/en/default/global/field-descs.none.tmpl
index 344dc55..21f41c8 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/field-descs.none.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/field-descs.none.tmpl
@@ -16,87 +16,41 @@
# Rights Reserved.
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Elliotte Martin <elliotte_martin@yahoo.com>
#%]
[%# Remember to PROCESS rather than INCLUDE this template. %]
[% PROCESS global/variables.none.tmpl %]
-[% field_descs = { "[Bug creation]" => "[$terms.Bug creation]",
- "actual_time" => "Actual Hours"
- "alias" => "Alias",
- "assigned_to" => "Assignee",
- "attach_data.thedata" => "Attachment data",
- "attachments.description" => "Attachment description",
- "attachments.filename" => "Attachment filename",
- "attachments.mimetype" => "Attachment mime type",
- "attachments.ispatch" => "Attachment is patch",
- "attachments.isobsolete" => "Attachment is obsolete",
- "attachments.isprivate" => "Attachment is private",
- "attachments.isurl" => "Attachment is a URL",
- "attachments.submitter" => "Attachment creator",
- "blocked" => "Blocks",
- "bug_file_loc" => "URL",
- "bug_group" => "Group",
- "bug_id" => "$terms.Bug ID",
- "bug_severity" => "Severity",
- "bug_status" => "Status",
- "changeddate" => "Changed",
- "cc" => "CC",
- "classification" => "Classification",
- "cclist_accessible" => "CC list accessible",
- "commenter" => "Commenter",
- "component_id" => "Component ID",
- "component" => "Component",
- "content" => "Content",
- "creation_ts" => "Creation date",
- "deadline" => "Deadline",
- "delta_ts" => "Changed",
- "dependson" => "Depends on",
- "dup_id" => "Duplicate",
- "estimated_time" => "Orig. Est.",
- "everconfirmed" => "Ever confirmed",
- "flagtypes.name" => "Flag",
- "keywords" => "Keywords",
- "longdesc" => "Comment",
- "longdescs.isprivate" => "Comment is private",
- "newcc" => "CC",
- "op_sys" => "OS",
- "opendate" => "Opened",
- "owner_idle_time" => "Time Since Assignee Touched",
- "percentage_complete" => "%Complete",
- "priority" => "Priority",
- "product_id" => "Product ID",
- "product" => "Product",
- "qa_contact" => "QA Contact",
- "remaining_time" => "Hours Left",
- "rep_platform" => "Hardware",
- "reporter" => "Reporter",
- "reporter_accessible" => "Reporter accessible",
- "requestees.login_name" => "Flag Requestee",
- "resolution" => "Resolution",
- "setters.login_name" => "Flag Setter",
- "setting" => "Setting",
- "settings" => "Settings",
- "short_desc" => "Summary",
- "status_whiteboard" => "Whiteboard",
- "target_milestone" => "Target Milestone",
- "version" => "Version",
- "votes" => "Votes",
- "work_time" => "Hours Worked"} %]
-
-[%# Also include any custom fields or fields which don't have a
- Description here, by copying their Description from the
- database. If you want to override this for your language
- or your installation, just use a hook. %]
-
-[% UNLESS Param('shutdownhtml') %]
- [% USE Bugzilla %]
- [% FOREACH bz_field = Bugzilla.get_fields() %]
- [% SET field_descs.${bz_field.name} = bz_field.description
- IF !field_descs.${bz_field.name}.defined %]
- [% END %]
-[% END %]
+[% SET search_descs = {
+ "noop" => "---",
+ "equals" => "is equal to",
+ "notequals" => "is not equal to",
+ "anyexact" => "is equal to any of the strings",
+ "substring" => "contains the string",
+ "casesubstring" => "contains the string (exact case)",
+ "notsubstring" => "does not contain the string",
+ "anywordssubstr" => "contains any of the strings",
+ "allwordssubstr" => "contains all of the strings",
+ "nowordssubstr" => "contains none of the strings",
+ "regexp" => "matches regular expression",
+ "notregexp" => "does not match regular expression",
+ "lessthan" => "is less than",
+ "lessthaneq" => "is less than or equal to",
+ "greaterthan" => "is greater than",
+ "greaterthaneq" => "is greater than or equal to",
+ "anywords" => "contains any of the words",
+ "allwords" => "contains all of the words",
+ "nowords" => "contains none of the words",
+ "changedbefore" => "changed before",
+ "changedafter" => "changed after",
+ "changedfrom" => "changed from",
+ "changedto" => "changed to",
+ "changedby" => "changed by",
+ "matches" => "matches",
+ "notmatches" => "does not match",
+} %]
[% field_types = { ${constants.FIELD_TYPE_UNKNOWN} => "Unknown Type",
${constants.FIELD_TYPE_FREETEXT} => "Free Text",
@@ -104,27 +58,99 @@
${constants.FIELD_TYPE_MULTI_SELECT} => "Multiple-Selection Box",
${constants.FIELD_TYPE_TEXTAREA} => "Large Text Box",
${constants.FIELD_TYPE_DATETIME} => "Date/Time",
+ ${constants.FIELD_TYPE_BUG_ID} => "$terms.Bug ID",
} %]
-[% status_descs = { "UNCONFIRMED" => "UNCONFIRMED",
- "NEW" => "NEW",
- "ASSIGNED" => "ASSIGNED",
- "REOPENED" => "REOPENED",
- "RESOLVED" => "RESOLVED",
- "VERIFIED" => "VERIFIED",
- "CLOSED" => "CLOSED" } %]
+[% IF in_template_var %]
+ [% PROCESS "global/value-descs.none.tmpl" %]
+ [% SET vars.value_descs = value_descs %]
+ [% SET vars.terms = terms %]
-[% MACRO get_status(status) GET status_descs.$status || status %]
+ [%# field_descs is loaded as a global template variable and cached
+ # across all templates--see VARIABLES in Bugzilla/Template.pm.
+ #%]
+ [% vars.field_descs = {
+ "[Bug creation]" => "[$terms.Bug creation]",
+ "actual_time" => "Actual Hours",
+ "alias" => "Alias",
+ "assigned_to" => "Assignee",
+ "assigned_to_realname" => "Assignee Real Name",
+ "attach_data.thedata" => "Attachment data",
+ "attachments.description" => "Attachment description",
+ "attachments.filename" => "Attachment filename",
+ "attachments.mimetype" => "Attachment mime type",
+ "attachments.ispatch" => "Attachment is patch",
+ "attachments.isobsolete" => "Attachment is obsolete",
+ "attachments.isprivate" => "Attachment is private",
+ "attachments.submitter" => "Attachment creator",
+ "blocked" => "Blocks",
+ "bug_file_loc" => "URL",
+ "bug_group" => "Group",
+ "bug_id" => "$terms.Bug ID",
+ "bug_severity" => "Severity",
+ "bug_status" => "Status",
+ "changeddate" => "Changed",
+ "cc" => "CC",
+ "classification" => "Classification",
+ "cclist_accessible" => "CC list accessible",
+ "commenter" => "Commenter",
+ "component_id" => "Component ID",
+ "component" => "Component",
+ "content" => "Content",
+ "creation_ts" => "Creation date",
+ "days_elapsed" => "Days since $terms.bug changed",
+ "deadline" => "Deadline",
+ "delta_ts" => "Changed",
+ "dependson" => "Depends on",
+ "dup_id" => "Duplicate",
+ "estimated_time" => "Orig. Est.",
+ "everconfirmed" => "Ever confirmed",
+ "flagtypes.name" => "Flags",
+ "keywords" => "Keywords",
+ "longdesc" => "Comment",
+ "longdescs.count" => "Number of Comments",
+ "longdescs.isprivate" => "Comment is private",
+ "newcc" => "CC",
+ "op_sys" => "OS",
+ "opendate" => "Opened",
+ "owner_idle_time" => "Time Since Assignee Touched",
+ "percentage_complete" => "%Complete",
+ "priority" => "Priority",
+ "product_id" => "Product ID",
+ "product" => "Product",
+ "qa_contact" => "QA Contact",
+ "qa_contact_realname" => "QA Contact Real Name",
+ "remaining_time" => "Hours Left",
+ "rep_platform" => "Hardware",
+ "reporter" => "Reporter",
+ "reporter_accessible" => "Reporter accessible",
+ "reporter_realname" => "Reporter Real Name",
+ "requestees.login_name" => "Flag Requestee",
+ "resolution" => "Resolution",
+ "see_also" => "See Also",
+ "setters.login_name" => "Flag Setter",
+ "setting" => "Setting",
+ "settings" => "Settings",
+ "short_desc" => "Summary",
+ "status_whiteboard" => "Whiteboard",
+ "tag.name" => "Tags",
+ "target_milestone" => "Target Milestone",
+ "version" => "Version",
+ "work_time" => "Hours Worked",
+ } %]
-[% resolution_descs = { "FIXED" => "FIXED",
- "INVALID" => "INVALID",
- "WONTFIX" => "WONTFIX",
- "DUPLICATE" => "DUPLICATE",
- "WORKSFORME" => "WORKSFORME",
- "MOVED" => "MOVED",
- "---" => "---",
- " " => " " } %]
+ [%# Also include any custom fields or fields which don't have a
+ Description here, by copying their Description from the
+ database. If you want to override this for your language
+ or your installation, just use a hook. %]
+ [% UNLESS Param('shutdownhtml') %]
+ [% FOREACH bz_field = bug_fields.values %]
+ [% SET vars.field_descs.${bz_field.name} = bz_field.description
+ IF !vars.field_descs.${bz_field.name}.defined %]
+ [% END %]
+ [% END %]
-[% MACRO get_resolution(res) GET resolution_descs.$res || res %]
+ [% PROCESS "bug/field-help.none.tmpl" %]
+[% END %]
[% Hook.process("end") %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/footer.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/footer.html.tmpl
index 205c978..661f8af 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/footer.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/footer.html.tmpl
@@ -33,7 +33,7 @@
#%]
<div id="footer">
- <div class="intro"></div>
+ <div class="intro">[% Hook.process('intro') %]</div>
[%# Migration note: the old param 'blurbhtml' goes here %]
@@ -41,9 +41,11 @@
[% PROCESS "global/useful-links.html.tmpl" %]
- <div class="outro"></div>
+ <div class="outro">[% Hook.process('outro') %]</div>
</div>
-<script defer src="/committers-autocomplete.js"></script>
+
+[% Hook.process("end") %]
+
</body>
</html>
diff --git a/Websites/bugs.webkit.org/template/en/default/global/header.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/header.html.tmpl
index a25cf70..a744988 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/header.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/header.html.tmpl
@@ -46,8 +46,41 @@
header_addl_info = ""
onload = ""
style_urls = []
+ yui = []
%]
+[% SET yui_css = {
+ autocomplete => 1,
+ calendar => 1,
+ datatable => 1,
+ button => 1,
+} %]
+
+[%# Note: This is simple dependency resolution--you can't have dependencies
+ # that depend on each other. You have to specify all of a module's deps,
+ # if that module is going to be specified in "yui".
+ #%]
+[% SET yui_deps = {
+ autocomplete => ['json', 'connection', 'datasource'],
+ datatable => ['json', 'connection', 'datasource', 'element'],
+} %]
+
+[%# When using certain YUI modules, we need to process certain
+ # extra JS templates.
+ #%]
+[% SET yui_templates = {
+ datatable => ['global/value-descs.js.tmpl'],
+} %]
+
+[%# These are JS URLs that are *always* on the page and come before
+ # every other JS URL.
+ #%]
+[% SET starting_js_urls = [
+ "js/yui/yahoo-dom-event/yahoo-dom-event.js",
+ "js/yui/cookie/cookie-min.js",
+] %]
+
+
[%# We should be able to set the default value of the header variable
# to the value of the title variable using the DEFAULT directive,
# but that doesn't work if a caller sets header to the empty string
@@ -59,105 +92,48 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
-<html>
+<html lang="en">
<head>
+ [% Hook.process("start") %]
<title>[% title %]</title>
+ [% IF Param('utf8') %]
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ [% END %]
+
[%# Migration note: contents of the old Param 'headerhtml' would go here %]
[% PROCESS "global/site-navigation.html.tmpl" %]
[% PROCESS 'global/setting-descs.none.tmpl' %]
- [%# Set up the skin CSS cascade:
- # 1. Standard Bugzilla stylesheet set (persistent)
- # 2. Standard Bugzilla stylesheet set (selectable)
- # 3. All third-party "skin" stylesheet sets (selectable)
- # 4. Page-specific styles
- # 5. Custom Bugzilla stylesheet set (persistent)
- # "Selectable" skin file sets may be either preferred or alternate.
- # Exactly one is preferred, determined by the "skin" user preference.
- #%]
- [% IF user.settings.skin.value != 'standard' %]
- [% user_skin = user.settings.skin.value %]
- [% END %]
- [% style_urls.unshift('skins/standard/global.css') %]
+ [% SET yui = yui_resolve_deps(yui, yui_deps) %]
+ [% SET css_sets = css_files(style_urls, yui, yui_css) %]
[%# CSS cascade, part 1: Standard Bugzilla stylesheet set (persistent).
# Always present.
#%]
- [% FOREACH style_url = style_urls %]
- <link href="[% style_url FILTER html %]"
- rel="stylesheet"
- type="text/css">
- [% END %]
- <!--[if IE]>
- [%# Internet Explorer treats [if IE] HTML comments as uncommented.
- # Use it to import CSS fixes so that Bugzilla looks decent on IE, too.
- #%]
- <link href="skins/standard/IE-fixes.css"
- rel="stylesheet"
- type="text/css">
- <![endif]-->
-
- [%# CSS cascade, part 2: Standard Bugzilla stylesheet set (selectable)
- # Present if skin selection is enabled.
+ [%# This allows people to switch back to the "Classic" skin if they
+ # are in another skin.
#%]
- [% IF user.settings.skin.is_enabled %]
- [% FOREACH style_url = style_urls %]
- <link href="[% style_url FILTER html %]"
- rel="[% 'alternate ' IF user_skin %]stylesheet"
- title="[% setting_descs.standard FILTER html %]"
- type="text/css">
- [% END %]
- <!--[if IE]>
- [%# Internet Explorer treats [if IE] HTML comments as uncommented.
- # Use it to import CSS fixes so that Bugzilla looks decent on IE,
- # too.
- #%]
- <link href="skins/standard/IE-fixes.css"
- rel="[% 'alternate ' IF user_skin %]stylesheet"
- title="[% setting_descs.standard FILTER html %]"
- type="text/css">
- <![endif]-->
+ <link href="[% 'skins/standard/global.css' FILTER mtime FILTER html %]"
+ rel="alternate stylesheet"
+ title="[% setting_descs.standard FILTER html %]">
+ [% FOREACH style_url = css_sets.standard %]
+ [% PROCESS format_css_link css_set_name = 'standard' %]
[% END %]
- [%# CSS cascade, part 3: Third-party stylesheet set (selectable).
- # All third-party skins are present if skin selection is enabled.
- # The admin-selected skin is always present.
+ [%# CSS cascade, part 2 & 3: Third-party stylesheet set (selected and
+ # selectable). All third-party skins are present as alternate
+ # stylesheets, even if they are not currently in use.
#%]
- [% FOREACH contrib_skin = user.settings.skin.legal_values %]
- [% NEXT IF contrib_skin == 'standard' %]
- [% NEXT UNLESS contrib_skin == user_skin
- OR user.settings.skin.is_enabled %]
- [% contrib_skin = contrib_skin FILTER url_quote %]
- [% IF contrib_skin.match('\.css$') %]
- [%# 1st skin variant: single-file stylesheet %]
- <link href="[% "skins/contrib/$contrib_skin" %]"
- rel="[% 'alternate ' UNLESS contrib_skin == user_skin %]stylesheet"
- title="[% contrib_skin FILTER html %]"
- type="text/css">
- [% ELSE %]
- [%# 2nd skin variant: stylesheet set %]
- [% FOREACH style_url = style_urls %]
- [% IF style_url.match('^skins/standard/') %]
- <link href="[% style_url.replace('^skins/standard/',
- "skins/contrib/$contrib_skin/") %]"
- rel="[% 'alternate ' UNLESS contrib_skin == user_skin %]stylesheet"
- title="[% contrib_skin FILTER html %]"
- type="text/css">
- [% END %]
- [% END %]
- <!--[if IE]>
- [%# Internet Explorer treats [if IE] HTML comments as uncommented.
- # Use it to import CSS fixes so that Bugzilla looks decent on IE,
- # too.
- #%]
- <link href="skins/contrib/[% contrib_skin FILTER html %]/IE-fixes.css"
- rel="[% 'alternate ' UNLESS contrib_skin == user_skin %]stylesheet"
- title="[% contrib_skin FILTER html %]"
- type="text/css">
- <![endif]-->
+ [% FOREACH style_url = css_sets.skin %]
+ [% PROCESS format_css_link css_set_name = user.settings.skin.value %]
+ [% END %]
+
+ [% FOREACH alternate_skin = css_sets.alternate.keys %]
+ [% FOREACH style_url = css_sets.alternate.$alternate_skin %]
+ [% PROCESS format_css_link css_set_name = alternate_skin %]
[% END %]
[% END %]
@@ -173,39 +149,85 @@
# Always present. Site administrators may override all other style
# definitions, including skins, using custom stylesheets.
#%]
- [% FOREACH style_url = style_urls %]
- [% IF style_url.match('^skins/standard/') %]
- <link href="[% style_url.replace('^skins/standard/', "skins/custom/")
- FILTER html %]" rel="stylesheet" type="text/css">
- [% END %]
- [% END %]
- <!--[if IE]>
- [%# Internet Explorer treats [if IE] HTML comments as uncommented.
- # Use it to import CSS fixes so that Bugzilla looks decent on IE, too.
- #%]
- <link href="skins/custom/IE-fixes.css"
- rel="stylesheet"
- type="text/css">
- <![endif]-->
-
-
- [% IF javascript %]
- <script type="text/javascript">
- [% javascript %]
- </script>
+ [% FOREACH style_url = css_sets.custom %]
+ [% PROCESS format_css_link css_set_name = 'standard' %]
[% END %]
- [% IF javascript_urls %]
- [% FOREACH javascript_url = javascript_urls %]
- <script src="[% javascript_url FILTER html %]" type="text/javascript"></script>
- [% END %]
- [% IF javascript_urls.grep('yui/').size %]
- <script type="text/javascript">
- YAHOO.namespace('bugzilla');
- if( YAHOO.env.ua.gecko )
- YAHOO.util.Event._simpleRemove(window, "unload", YAHOO.util.Event._unload);
- </script>
- [% END %]
+ [%# YUI Scripts %]
+ [% FOREACH yui_name = yui %]
+ [% starting_js_urls.push("js/yui/$yui_name/${yui_name}-min.js") %]
+ [% END %]
+ [% starting_js_urls.push('js/global.js') %]
+
+ [% FOREACH javascript_url = starting_js_urls %]
+ [% PROCESS format_js_link %]
+ [% END %]
+
+ <script type="text/javascript">
+ <!--
+ YAHOO.namespace('bugzilla');
+ YAHOO.util.Event.addListener = function (el, sType, fn, obj, overrideContext) {
+ if ( ("onpagehide" in window || YAHOO.env.ua.gecko) && sType === "unload") { sType = "pagehide"; };
+ var capture = ((sType == "focusin" || sType == "focusout") && !YAHOO.env.ua.ie) ? true : false;
+ return this._addListener(el, this._getType(sType), fn, obj, overrideContext, capture);
+ };
+ if ( "onpagehide" in window || YAHOO.env.ua.gecko) {
+ YAHOO.util.Event._simpleRemove(window, "unload",
+ YAHOO.util.Event._unload);
+ }
+ [%# The language selector needs javascript to set its cookie,
+ # so it is hidden in HTML/CSS by the "bz_default_hidden" class.
+ # If the browser can run javascript, it will then "unhide"
+ # the language selector using the following code.
+ #%]
+ function unhide_language_selector() {
+ YAHOO.util.Dom.removeClass(
+ 'lang_links_container', 'bz_default_hidden'
+ );
+ }
+ YAHOO.util.Event.onDOMReady(unhide_language_selector);
+
+ [%# Make some Bugzilla information available to all scripts.
+ # We don't import every parameter and constant because we
+ # don't want to add a lot of uncached JS to every page.
+ #%]
+ var BUGZILLA = {
+ param: {
+ cookiepath: '[% Param('cookiepath') FILTER js %]',
+ maxusermatches: [% Param('maxusermatches') FILTER js %]
+ },
+ constant: {
+ COMMENT_COLS: [% constants.COMMENT_COLS FILTER js %]
+ },
+ string: {
+ [%# Please keep these in alphabetical order. %]
+
+ attach_desc_required:
+ 'You must enter a Description for this attachment.',
+ component_required:
+ 'You must select a Component for this [% terms.bug %].',
+ description_required:
+ 'You must enter a Description for this [% terms.bug %].',
+ short_desc_required:
+ 'You must enter a Summary for this [% terms.bug %].',
+ version_required:
+ 'You must select a Version for this [% terms.bug %].'
+ }
+ };
+
+ [% FOREACH yui_name = yui %]
+ [% FOREACH yui_template = yui_templates.$yui_name %]
+ [% INCLUDE $yui_template %]
+ [% END %]
+ [% END %]
+ [% IF javascript %]
+ [% javascript %]
+ [% END %]
+ // -->
+ </script>
+
+ [% FOREACH javascript_url = javascript_urls %]
+ [% PROCESS format_js_link %]
[% END %]
[%# this puts the live bookmark up on firefox for the Atom feed %]
@@ -230,13 +252,12 @@
class="[% urlbase.replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') %]
[% FOREACH class = bodyclasses %]
[% ' ' %][% class FILTER css_class_quote %]
- [% END %]">
+ [% END %] yui-skin-sam">
[%# Migration note: the following file corresponds to the old Param
# 'bannerhtml'
#%]
-
<div id="header">
[% INCLUDE global/banner.html.tmpl %]
@@ -262,9 +283,26 @@
</tr>
</table>
-[% PROCESS "global/common-links.html.tmpl" qs_suffix = "_top" %]
+<table id="lang_links_container" cellpadding="0" cellspacing="0"
+ class="bz_default_hidden"><tr><td>
+[% IF Bugzilla.languages.size > 1 %]
+ <ul class="links">
+ [% FOREACH lang = Bugzilla.languages.sort %]
+ <li>[% IF NOT loop.first %]<span class="separator"> | </span>[% END %]
+ [% IF lang == current_language %]
+ <span class="lang_current">[% lang FILTER html FILTER upper %]</span>
+ [% ELSE %]
+ <a href="#" onclick="set_language('[% lang FILTER none %]');">
+ [%- lang FILTER html FILTER upper %]</a>
+ [% END %]
+ </li>
+ [% END %]
+ </ul>
+[% END %]
+</td></tr></table>
-</div>
+[% PROCESS "global/common-links.html.tmpl" qs_suffix = "_top" %]
+</div> [%# header %]
<div id="bugzilla-body">
@@ -275,3 +313,41 @@
[% IF message %]
<div id="message">[% message %]</div>
[% END %]
+
+[% BLOCK format_css_link %]
+ [% IF style_url.match('/IE-fixes\.css') %]
+ <!--[if lte IE 7]>
+ [%# Internet Explorer treats [if IE] HTML comments as uncommented.
+ # We use it to import CSS fixes so that Bugzilla looks decent on IE 7
+ # and below.
+ #%]
+ [% END %]
+
+ [% IF css_set_name == 'standard'
+ OR css_set_name == user.settings.skin.value
+ %]
+ [% SET css_rel = 'stylesheet' %]
+ [% SET css_set_display_name = setting_descs.${user.settings.skin.value}
+ || user.settings.skin.value %]
+ [% ELSE %]
+ [% SET css_rel = 'alternate stylesheet' %]
+ [% SET css_set_display_name = setting_descs.$css_set_name || css_set_name %]
+ [% END %]
+
+ [% IF css_set_name == 'standard' %]
+ [% SET css_title_link = '' %]
+ [% ELSE %]
+ [% css_title_link = BLOCK ~%]
+ title="[% css_set_display_name FILTER html %]"
+ [% END %]
+ [% END %]
+
+ <link href="[% style_url FILTER html %]" rel="[% css_rel FILTER none %]"
+ type="text/css" [% css_title_link FILTER none %]>
+
+ [% '<![endif]-->' IF style_url.match('/IE-fixes\.css') %]
+[% END %]
+
+[% BLOCK format_js_link %]
+ <script type="text/javascript" src="[% javascript_url FILTER mtime FILTER html %]"></script>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/help.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/help.html.tmpl
index 36439bc..c0ff819 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/help.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/help.html.tmpl
@@ -23,8 +23,9 @@
[% IF cgi.param("help") %]
<script type="text/javascript"> <!--
- [% FOREACH h = help_html %]
- g_helpTexts["[% h.id FILTER js %]"] = "[%- h.html FILTER js -%]";
+ [% FOREACH help_name = help_html.keys %]
+ g_helpTexts["[% help_name FILTER js %]"] =
+ "[%- help_html.$help_name FILTER js -%]";
[% END %]
// -->
</script>
diff --git a/Websites/bugs.webkit.org/template/en/default/global/hidden-fields.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/hidden-fields.html.tmpl
index 24f15c4..c141c64 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/hidden-fields.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/hidden-fields.html.tmpl
@@ -52,7 +52,7 @@
[% ELSE %]
[% FOREACH mvalue = cgi.param(field).slice(0) %]
<input type="hidden" name="[% field FILTER html %]"
- value="[% mvalue FILTER html FILTER html_linebreak %]">
+ value="[% mvalue FILTER html_linebreak %]">
[% END %]
[% END %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/messages.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/messages.html.tmpl
index 4889b9c..2567d4a 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/messages.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/messages.html.tmpl
@@ -31,16 +31,11 @@
[% message = BLOCK %]
[% IF message_tag == "account_created" %]
- [% title = "User $otheruser.login created" %]
- A new user account [% otheruser.login FILTER html %] has been created
+ The user account [% otheruser.login FILTER html %] has been created
successfully.
[% IF groups.size %]
You may want to edit the group settings now, using the form below.
[% END %]
- [% IF login_info %]
- You can now go to the <a href="index.cgi">Log In</a> page to enter
- this [% terms.Bugzilla %] installation.
- [% END %]
[% ELSIF message_tag == "account_creation_canceled" %]
[% title = "User Account Creation Canceled" %]
@@ -65,6 +60,10 @@
A new password has been set.
[% ELSIF field == 'disabledtext' %]
The disable text has been modified.
+ [% ELSIF field == 'is_enabled' %]
+ The user has been [% otheruser.is_enabled ? 'enabled' : 'disabled' %].
+ [% ELSIF field == 'extern_id' %]
+ The user's External Login ID has been modified.
[% ELSIF field == 'disable_mail' %]
[% IF otheruser.email_disabled %]
[% terms.Bug %]mail has been disabled.
@@ -114,35 +113,27 @@
The user account [% otheruser.login FILTER html %] has been deleted
successfully.
+ [% ELSIF message_tag == "account_disabled" %]
+ The user account [% account FILTER html %] is disabled, so you
+ cannot change its password.
+
[% ELSIF message_tag == "attachment_creation_failed" %]
The [% terms.bug %] was created successfully, but attachment creation
failed.
Please add your attachment by clicking the "Add an Attachment" link
below.
- [% ELSIF message_tag == "bug_confirmed_by_votes" %]
- *** This [% terms.bug %] has been confirmed by popular vote. ***
-
- [% ELSIF message_tag == "bug_duplicate_of" %]
- *** This [% terms.bug %] has been marked as a duplicate of [% terms.bug %] [%+ dupe_of FILTER html %] ***
-
- [% ELSIF message_tag == "bug_has_duplicate" %]
- *** [% terms.Bug %] [%+ dupe FILTER html %] has been marked as a duplicate of this [% terms.bug %]. ***
-
- [% ELSIF message_tag == "bug_moved_to" %]
- <p>[% terms.Bug %] moved to [% Param("move-to-url") FILTER html %].</p>
- <p>If the move succeeded, [% login FILTER html %] will receive a mail
- containing the number of the new [% terms.bug %] in the other database.
- If all went well, please mark this [% terms.bug %] verified, and paste
- in a link to the new [% terms.bug %]. Otherwise, reopen this [% terms.bug %].
+ [% ELSIF message_tag == "bug_group_description" %]
+ Access to [% terms.bugs %] in the [% product.name FILTER html %] product
[% ELSIF message_tag == "buglist_adding_field" %]
[% title = "Adding field to search page..." %]
[% link = "Click here if the page does not redisplay automatically." %]
[% ELSIF message_tag == "buglist_updated_named_query" %]
+ [% title = "Search updated" %]
Your search named <code><a
- href="buglist.cgi?cmdtype=runnamed&namedcmd=[% queryname FILTER url_quote %]"
+ href="buglist.cgi?cmdtype=runnamed&namedcmd=[% queryname FILTER uri %]"
>[% queryname FILTER html %]</a></code> has been updated.
[% ELSIF message_tag == "buglist_new_default_query" %]
@@ -150,22 +141,19 @@
also bookmark the result of any individual search.
[% ELSIF message_tag == "buglist_new_named_query" %]
+ [% title = "Search created" %]
OK, you have a new search named <code><a
- href="buglist.cgi?cmdtype=runnamed&namedcmd=[% queryname FILTER url_quote %]"
+ href="buglist.cgi?cmdtype=runnamed&namedcmd=[% queryname FILTER uri %]"
>[% queryname FILTER html %]</a></code>.
[% ELSIF message_tag == "buglist_query_gone" %]
[% title = "Search is gone" %]
- [% link = "Go back to the search page." %]
+ [% link = "Un-forget the search" %]
OK, the <b>[% namedcmd FILTER html %]</b> search is gone.
[% ELSIF message_tag == "buglist_sorted_by_relevance" %]
[% terms.Bugs %] on this list are sorted by relevance, with the most
relevant [% terms.bugs %] at the top.
- [% IF bugs.size == constants.FULLTEXT_BUGLIST_LIMIT %]
- Only the [% constants.FULLTEXT_BUGLIST_LIMIT FILTER html %]
- most relevant [% terms.bugs %] are shown.
- [% END %]
[% ELSIF message_tag == "change_columns" %]
[% title = "Change columns" %]
@@ -179,26 +167,31 @@
[% ELSIF message_tag == "classification_deleted" %]
[% title = "Classification Deleted" %]
- The <em>[% classification FILTER html %]</em> classification has been deleted.
+ The <em>[% classification.name FILTER html %]</em> classification has been deleted.
[% ELSIF message_tag == "classification_updated" %]
- [% IF updated_classification || updated_description || updated_sortkey %]
- [% title = "Classification Updated" %]
- Changes to the <em>[% classification FILTER html %]</em> classification
+ [% title = "Classification Updated" %]
+ [% IF changes.keys.size %]
+ Changes to the <em>[% classification.name FILTER html %]</em> classification
have been saved:
<ul>
- [% IF updated_classification %]
- <li>Classification name updated</li>
+ [% IF changes.name.defined %]
+ <li>Name updated to '[% classification.name FILTER html %]'</li>
[% END %]
- [% IF updated_description %]
- <li>Description updated</li>
+ [% IF changes.description.defined %]
+ [% IF classification.description %]
+ <li>Description updated to '[% classification.description FILTER html %]'</li>
+ [% ELSE %]
+ <li>Description removed</li>
+ [% END %]
[% END %]
- [% IF updated_sortkey %]
- <li>Sortkey updated</li>
+ [% IF changes.sortkey.defined %]
+ <li>Sortkey updated to '[% classification.sortkey FILTER html %]'</li>
[% END %]
+ [% Hook.process('classification_updated_fields') %]
</ul>
[% ELSE %]
- No changes made to <em>[% classification FILTER html %]</em>.
+ No changes made to <em>[% classification.name FILTER html %]</em>.
[% END %]
[% ELSIF message_tag == "component_created" %]
@@ -245,6 +238,11 @@
<li>Default CC list deleted</li>
[% END %]
[% END %]
+ [% IF changes.isactive.defined %]
+ <li>[% comp.is_active ? "Enabled" : "Disabled" %] for [% terms.bugs %]</li>
+ [% END %]
+ [% Hook.process('component_updated_fields') %]
+ </ul>
[% ELSE %]
No changes made to <em>[% comp.name FILTER html %]</em>.
[% END %]
@@ -287,44 +285,63 @@
The request to change the email address for the
account [%+ old_email FILTER html %] to
[%+ new_email FILTER html %] has been canceled.
- Your old account settings have been reinstated.
+ Your old account settings have been reinstated.
+
+ [% ELSIF message_tag == "extension_created" %]
+ An extension named [% name FILTER html %] has been created
+ in [% path FILTER html %]. Make sure you change "YOUR NAME" and
+ "YOUR EMAIL ADDRESS" in the code to your name and your email address.
[% ELSIF message_tag == "field_value_created" %]
[% title = "New Field Value Created" %]
- The value <em>[% value FILTER html %]</em> has been added as a valid choice
- for the <em>[% field.description FILTER html %]</em>
+ The value <em>[% value.name FILTER html %]</em> has been added as a
+ valid choice for the <em>[% field.description FILTER html %]</em>
(<em>[% field.name FILTER html %]</em>) field.
[% IF field.name == "bug_status" %]
- You should now visit the <a href="editworkflow.cgi">status workflow page</a>
- to include your new [% terms.bug %] status.
+ You should now visit the <a href="editworkflow.cgi">status workflow
+ page</a> to include your new [% terms.bug %] status.
[% END %]
[% ELSIF message_tag == "field_value_deleted" %]
[% title = "Field Value Deleted" %]
- The value <em>[% value FILTER html %]</em> of the
+ The value <em>[% value.name FILTER html %]</em> of the
<em>[% field.description FILTER html %]</em>
(<em>[% field.name FILTER html %]</em>) field has been deleted.
[% ELSIF message_tag == "field_value_updated" %]
[% title = "Field Value Updated" %]
- [% IF updated_value || updated_sortkey %]
- Changes to the <em>[% value FILTER html %]</em> value of the
+ [% IF changes.keys.size %]
+ The <em>[% value_old FILTER html %]</em> value of the
<em>[% field.description FILTER html %]</em>
- (<em>[% field.name FILTER html %]</em>) field have been changed:
+ (<em>[% field.name FILTER html %]</em>) field has been changed:
<ul>
- [% IF updated_value %]
- <li>Field value updated to <em>[% value FILTER html %]</em></li>
- [% IF default_value_updated %]
- (note that this value is the default for this field. All
- references to the default value will now point to this new value)
- [% END %]
+ [% IF changes.value %]
+ <li>Field value updated to
+ <em>[% changes.value.1 FILTER html %]</em>.
+ [% IF value.is_default %]
+ (Note that this value is the default for this field. All
+ references to the default value will now point to this new value.)
+ [% END %]
+ </li>
[% END %]
- [% IF updated_sortkey %]
- <li>Field value sortkey updated to <em>[% sortkey FILTER html %]</em></li>
+ [% IF changes.sortkey %]
+ <li>Sortkey updated to
+ <em>[% changes.sortkey.1 FILTER html %]</em>.</li>
+ [% END %]
+ [% IF changes.visibility_value_id %]
+ [% IF value.visibility_value.defined %]
+ <li>It only appears when
+ [%+ value.field.value_field.description FILTER html %] is set to
+ '[%+ value.visibility_value.name FILTER html %]'.</li>
+ [% ELSE %]
+ <li>It now always appears, no matter what
+ [%+ value.field.value_field.description FILTER html %] is set to.
+ </li>
+ [% END %]
[% END %]
</ul>
[% ELSE %]
- No changes made to the field value <em>[% value FILTER html %]</em>.
+ No changes made to the field value <em>[% value_old FILTER html %]</em>.
[% END %]
[% ELSIF message_tag == "flag_cleared" %]
@@ -340,10 +357,10 @@
[% field_descs.$field_name FILTER html %]
[% ELSIF message_tag == "get_resolution" %]
- [% get_resolution(resolution) FILTER html %]
+ [% display_value("resolution", resolution) FILTER html %]
[% ELSIF message_tag == "get_status" %]
- [% get_status(status) FILTER html %]
+ [% display_value("bug_status", status) FILTER html %]
[% ELSIF message_tag == "group_created" %]
[% title = "New Group Created" %]
@@ -430,6 +447,14 @@
group.
[% END %]
+ [% ELSIF message_tag == "invalid_column_name" %]
+ The custom sort order specified contains one or more invalid
+ column names: <em>[% invalid_fragments.join(', ') FILTER html %]</em>.
+ They have been removed from the sort list.
+
+ [% ELSIF message_tag == "job_queue_depth" %]
+ [% count FILTER html %] jobs in the queue.
+
[% ELSIF message_tag == "keyword_created" %]
[% title = "New Keyword Created" %]
The keyword <em>[% name FILTER html %]</em> has been created.
@@ -437,10 +462,6 @@
[% ELSIF message_tag == "keyword_deleted" %]
[% title = "Keyword Deleted" %]
The <em>[% keyword.name FILTER html %]</em> keyword has been deleted.
- <b>After you have finished editing keywords, you need to
- <a href="sanitycheck.cgi?rebuildkeywordcache=1">rebuild the keyword
- cache</a></b> (on a very large installation of [% terms.Bugzilla %],
- this can take several minutes).
[% ELSIF message_tag == "keyword_updated" %]
[% title = "Keyword Updated" %]
@@ -449,13 +470,7 @@
been saved:
<ul>
[% IF changes.name.defined %]
- <li>
- Keyword renamed to <em>[% keyword.name FILTER html %]</em>.
- <b>After you have finished editing keywords, you need to
- <a href="sanitycheck.cgi?rebuildkeywordcache=1">rebuild
- the keyword cache</a></b> (on a very large installation
- of [% terms.Bugzilla %], this can take several minutes).
- </li>
+ <li>Keyword renamed to <em>[% keyword.name FILTER html %]</em>.</li>
[% END %]
[% IF changes.description.defined %]
<li>Description updated to <em>[% keyword.description FILTER html %]</em></li>
@@ -477,6 +492,44 @@
[% title = "$terms.Bugzilla Login Changed" %]
Your [% terms.Bugzilla %] login has been changed.
+ [% ELSIF message_tag == "migrate_component_created" %]
+ Component created: [% comp.name FILTER html %]
+ (in [% product.name FILTER html %])
+
+ [% ELSIF message_tag == "migrate_creating_bugs" %]
+ Creating [% terms.bugs %]...
+
+ [% ELSIF message_tag == "migrate_field_created" %]
+ New custom field: [% field.description FILTER html %]
+ ([% field.name FILTER html %])
+
+ [% ELSIF message_tag == "migrate_product_created" %]
+ Product created: [% created.name FILTER html %]
+
+ [% ELSIF message_tag == "migrate_reading_bugs" %]
+ Reading [% terms.bugs %]...
+
+ [% ELSIF message_tag == "migrate_reading_products" %]
+ Reading products...
+
+ [% ELSIF message_tag == "migrate_reading_users" %]
+ Reading users...
+
+ [% ELSIF message_tag == "migrate_translating_bugs" %]
+ Converting [% terms.bug %] values to be appropriate for
+ [%+ terms.Bugzilla %]...
+
+ [% ELSIF message_tag == "migrate_user_created" %]
+ User created: [% created.email FILTER html %]
+ [% IF password %] Password: [% password FILTER html %][% END %]
+
+ [% ELSIF message_tag == "migrate_value_created" %]
+ [% IF product.defined %]
+ [% product.name FILTER html %]
+ [% END %]
+ [%+ field_descs.${field.name} FILTER html %] value
+ created: [% value FILTER html %]
+
[% ELSIF message_tag == "milestone_created" %]
[% title = "Milestone Created" %]
The milestone <em>[% milestone.name FILTER html %]</em> has been created.
@@ -501,6 +554,9 @@
[% IF changes.sortkey.defined %]
<li>Sortkey updated to <em>[% milestone.sortkey FILTER html %]</em>
[% END %]
+ [% IF changes.isactive.defined %]
+ <li>[% milestone.is_active ? "Enabled" : "Disabled" %] for [% terms.bugs %]</li>
+ [% END %]
</ul>
[% ELSE %]
No changes made to milestone <em>[% milestone.name FILTER html %]</em>.
@@ -512,7 +568,7 @@
[% FOREACH param = param_changed %]
Changed <em>[% param FILTER html %]</em><br>
[% IF param == 'utf8' && Param('utf8') %]
- <strong>You must now re-run checksetup.pl.</strong><br>
+ <strong>You must now re-run <kbd>checksetup.pl</kbd>.</strong><br>
[% END %]
[% END %]
[% ELSE %]
@@ -539,20 +595,74 @@
Your password has been changed.
[% ELSIF message_tag == "flag_type_created" %]
- [% title = "Flag Type Created" %]
+ [% title = BLOCK %]Flag Type '[% name FILTER html %]' Created[% END %]
The flag type <em>[% name FILTER html %]</em> has been created.
- [% ELSIF message_tag == "flag_type_changes_saved" %]
- [% title = "Flag Type Changes Saved" %]
- Your changes to the flag type <em>[% name FILTER html %]</em>
- have been saved.
+ [% ELSIF message_tag == "flag_type_updated" %]
+ [% title = BLOCK %]Flag Type '[% flagtype.name FILTER html %]' Updated[% END %]
+ [% IF changes.size %]
+ Changes to the flag type <em>[% flagtype.name FILTER html %]</em>
+ have been saved:
+ <ul>
+ [% IF changes.is_active.defined %]
+ <li>Flag type is now [% flagtype.is_active ? "active" : "inactive" %]</li>
+ [% END %]
+ [% IF changes.name.defined %]
+ <li>Flag type renamed to <em>[% flagtype.name FILTER html %]</em></li>
+ [% END %]
+ [% IF changes.description.defined %]
+ <li>Description updated to <em>[% flagtype.description FILTER html %]</em></li>
+ [% END %]
+ [% IF changes.cc_list.defined %]
+ [% IF flagtype.cc_list %]
+ <li>CC list updated to <em>[% flagtype.cc_list FILTER html %]</em></li>
+ [% ELSE %]
+ <li>CC list is now empty</li>
+ [% END %]
+ [% END %]
+ [% IF changes.sortkey.defined %]
+ <li>Sortkey updated to <em>[% flagtype.sortkey FILTER html %]</em></li>
+ [% END %]
+ [% IF changes.is_requestable.defined %]
+ <li>Flag type is [% "no longer" UNLESS flagtype.is_requestable %] requestable</li>
+ [% END %]
+ [% IF changes.is_requesteeble.defined AND flagtype.is_requestable %]
+ <li>
+ Flag type is [% "no longer" UNLESS flagtype.is_requesteeble %]
+ specifically requestable
+ </li>
+ [% END %]
+ [% IF changes.is_multiplicable.defined %]
+ <li>Flag type is [% "no longer" UNLESS flagtype.is_multiplicable %] multiplicable</li>
+ [% END %]
+ [% IF changes.grant_group_id.defined %]
+ [% IF flagtype.grant_group_id %]
+ <li>Grant group updated to <em>[% flagtype.grant_group.name FILTER html %]</em></li>
+ [% ELSE %]
+ <li>Grant group deleted</li>
+ [% END %]
+ [% END %]
+ [% IF changes.request_group_id.defined %]
+ [% IF flagtype.request_group_id %]
+ <li>Request group updated to <em>[% flagtype.request_group.name FILTER html %]</em></li>
+ [% ELSE %]
+ <li>Request group deleted</li>
+ [% END %]
+ [% END %]
+ [% IF changes.inclusions.defined || changes.exclusions.defined %]
+ <li>The inclusions and exclusions lists have been updated</li>
+ [% END %]
+ </ul>
+ [% ELSE %]
+ No changes made to flag type <em>[% flagtype.name FILTER html %]</em>.
+ [% END %]
[% ELSIF message_tag == "flag_type_deleted" %]
- [% title = "Flag Type Deleted" %]
+ [% title = BLOCK %]Flag Type '[% name FILTER html %]' Deleted[% END %]
The flag type <em>[% name FILTER html %]</em> has been deleted.
[% ELSIF message_tag == "flag_type_deactivated" %]
- [% title = "Flag Type Deactivated" %]
+ [% title = BLOCK %]Flag Type '[% flag_type.name FILTER html %]' Deactivated[% END %]
The flag type <em>[% flag_type.name FILTER html %]</em> has been deactivated.
[% ELSIF message_tag == "install_admin_get_email" %]
@@ -599,12 +709,41 @@
[% ELSIF message_tag == "install_fk_drop" %]
Dropping foreign key: [% table FILTER html %].[% column FILTER html %] -> [% fk.TABLE FILTER html %].[% fk.COLUMN FILTER html %]...
+ [% ELSIF message_tag == "install_fk_invalid" %]
+ ERROR: There are invalid values for the [% column FILTER html %] column in the [% table FILTER html %]
+ table. (These values do not exist in the [% foreign_table FILTER html %] table, in the
+ [%+ foreign_column FILTER html %] column.)
+
+ Before continuing with checksetup, you will need to fix these values,
+ either by deleting these rows from the database, or changing the values
+ of [% column FILTER html %] in [% table FILTER html %] to point to valid values in [% foreign_table FILTER html %].[% foreign_column FILTER html %].
+
+ The bad values from the [% table FILTER html %].[% column FILTER html %] column are:
+ [%+ values.join(', ') FILTER html %]
+
+ [% ELSIF message_tag == "install_fk_invalid_fixed" %]
+ WARNING: There were invalid values in [% table FILTER html %].[% column FILTER html %]
+ that have been [% IF action == 'delete' %]deleted[% ELSE %]set to NULL[% END %]:
+ [%+ values.join(', ') FILTER html %]
+
+ [% ELSIF message_tag == "install_fk_setup" %]
+ Setting up foreign keys...
+
[% ELSIF message_tag == "install_group_create" %]
Creating group [% name FILTER html %]...
+ [% ELSIF message_tag == "install_groups_setup" %]
+ Creating default groups...
+
[% ELSIF message_tag == "install_setting_new" %]
Adding a new user setting called '[% name FILTER html %]'
+ [% ELSIF message_tag == "install_setting_setup" %]
+ Setting up user preferences...
+
+ [% ELSIF message_tag == "install_success" %]
+ checksetup.pl complete.
+
[% ELSIF message_tag == "install_table_drop" %]
Dropping the '[% name FILTER html %]' table...
@@ -652,10 +791,13 @@
Verify that the file permissions in your [% terms.Bugzilla %] directory are
suitable for your system. Avoid unnecessary write access.
+ [% ELSIF message_tag == "install_workflow_init" %]
+ Setting up the default status workflow...
+
[% ELSIF message_tag == "product_created" %]
[% title = "Product Created" %]
The product <em>[% product.name FILTER html %]</em> has been created. You will need to
- <a href="editcomponents.cgi?action=add&product=[% product.name FILTER url_quote %]">
+ <a href="editcomponents.cgi?action=add&product=[% product.name FILTER uri %]">
add at least one component</a> before anyone can enter [% terms.bugs %] against this product.
[% ELSIF message_tag == "product_deleted" %]
@@ -689,6 +831,9 @@
[% ELSIF message_tag == "series_all_closed" %]
All Closed
+ [% ELSIF message_tag == "series_subcategory" %]
+ -All-
+
[% ELSIF message_tag == "sudo_started" %]
[% title = "Sudo session started" %]
The sudo session has been started. For the next 6 hours, or until you
@@ -708,10 +853,14 @@
has been created. Note that you may need to wait up to
[%+ series.frequency * 2 %] days before there will be enough data for a
chart of this series to be produced.
- <br><br>
- Go back or
- <a href="query.cgi?format=create-series">create another series</a>.
-
+
+ [% ELSIF message_tag == "series_deleted" %]
+ [% title = "Series Deleted" %]
+ The series <em>[% series.category FILTER html %] /
+ [%+ series.subcategory FILTER html %] /
+ [%+ series.name FILTER html %]</em>
+ has been deleted.
+
[% ELSIF message_tag == "shutdown" %]
[% title = "$terms.Bugzilla is Down" %]
[% Param("shutdownhtml") %]
@@ -720,6 +869,18 @@
The cookie that was remembering your login is now gone.
[% END %]
+ [% ELSIF message_tag == "tag_updated" %]
+ [% title = "Tag Updated" %]
+ The '<a href="buglist.cgi?tag=[% tag FILTER uri %]">[% tag FILTER html %]</a>'
+ tag has been
+ [% IF action == "add" %]
+ added to
+ [% ELSE %]
+ removed from
+ [% END %]
+ [%+ buglist.size > 1 ? terms.bugs : terms.bug %]
+ [%+ buglist.join(", ") FILTER html %].
+
[% ELSIF message_tag == "term" %]
[% terms.$term FILTER html %]
@@ -748,20 +909,42 @@
[% ELSIF message_tag == "version_updated" %]
[% title = "Version Updated" %]
- Version renamed as <em>[% version.name FILTER html %]</em>.
+ [% IF changes.size %]
+ Changes to the version <em>[% version.name FILTER html %]</em>
+ have been saved:
+ <ul>
+ [% IF changes.value.defined %]
+ <li>Version renamed to <em>[% version.name FILTER html %]</em></li>
+ [% END %]
+ [% IF changes.isactive.defined %]
+ <li>[% version.is_active ? "Enabled" : "Disabled" %] for [% terms.bugs %]</li>
+ [% END %]
+ </ul>
+ [% ELSE %]
+ No changes made to version <em>[% version.name FILTER html %]</em>.
+ [% END %]
+
+ [% ELSIF message_tag == "whine_query_failed" %]
+ The query '[% query_name FILTER html %]' from [% author.login FILTER html %]
+ failed: [% reason FILTER html %]
[% ELSIF message_tag == "workflow_updated" %]
The workflow has been updated.
+ [% END %]
+[% END %]
- [% ELSE %]
- [%# Give sensible error if error functions are used incorrectly.
- #%]
+[% IF !message %]
+ [% message = Hook.process('messages') %]
+[% END %]
+
+[%# Give sensible error if the message function is used incorrectly. #%]
+[% IF !message %]
+ [% message = BLOCK %]
You are using [% terms.Bugzilla %]'s messaging functions incorrectly. You
passed in the string '[% message_tag %]'. The correct use is to pass
in a tag, and define that tag in the file messages.html.tmpl.<br>
<br>
- If you are a [% terms.Bugzilla %] end-user seeing this message, please
+ If you are a [% terms.Bugzilla %] end-user seeing this message, please
save this page and send it to [% Param('maintainer') %].
-
[% END %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/per-bug-queries.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/per-bug-queries.html.tmpl
index c2fc398..9041898 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/per-bug-queries.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/per-bug-queries.html.tmpl
@@ -51,21 +51,16 @@
//-->
</script>
- [%# Get existing lists of bugs for this user %]
- [% lists_of_bugs = [] %]
- [% FOREACH q = user.queries %]
- [% NEXT UNLESS q.bug_ids_only %]
- [% lists_of_bugs.push(q.name) %]
- [% END %]
<div class="label"></div>
<ul class="links"><li class="form">
<form id="list_of_bugs" action="buglist.cgi" method="get">
<input type="hidden" name="cmdtype" value="doit">
<input type="hidden" name="remtype" value="asnamed">
<input type="hidden" name="list_of_bugs" value="1">
+ <input type="hidden" name="token" value="[% issue_hash_token(['savedsearch']) FILTER html %]">
<select id="lob_action" name="action" onchange="update_text();">
<option value="add">Add</option>
- [% IF lists_of_bugs.size %]
+ [% IF user.tags.size %]
<option value="remove">Remove</option>
[% END %]
</select>
@@ -76,15 +71,15 @@
the named tag
[% END %]
- [% IF lists_of_bugs.size %]
+ [% IF user.tags.size %]
<select id="lob_oldqueryname" name="oldqueryname">
- [% FOREACH query = lists_of_bugs %]
- <option value="[% query FILTER html %]">[% query FILTER html %]</option>
+ [% FOREACH tag = user.tags.keys %]
+ <option value="[% tag FILTER html %]">[% tag FILTER html %]</option>
[% END %]
</select>
[% END %]
<span id="lob_new_query_text">
- [% " or create and add the tag" IF lists_of_bugs.size %]
+ [% " or create and add the tag" IF user.tags.size %]
<input class="txt" type="text" id="lob_newqueryname"
size="20" maxlength="64" name="newqueryname"
onkeyup="manage_old_lists();">
diff --git a/Websites/bugs.webkit.org/template/en/default/global/reason-descs.none.tmpl b/Websites/bugs.webkit.org/template/en/default/global/reason-descs.none.tmpl
new file mode 100644
index 0000000..426085f
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/global/reason-descs.none.tmpl
@@ -0,0 +1,40 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% SET reason_descs = {
+ ${constants.REL_ASSIGNEE} => "You are the assignee for the ${terms.bug}.",
+ ${constants.REL_REPORTER} => "You reported the ${terms.bug}.",
+ ${constants.REL_QA} => "You are the QA Contact for the ${terms.bug}.",
+ ${constants.REL_CC} => "You are on the CC list for the ${terms.bug}.",
+ ${constants.REL_GLOBAL_WATCHER} => "You are watching all $terms.bug changes.",
+} %]
+
+[% SET watch_reason_descs => {
+ ${constants.REL_ASSIGNEE} =>
+ "You are watching the assignee of the ${terms.bug}.",
+ ${constants.REL_REPORTER} =>
+ "You are watching the reporter of the ${terms.bug}.",
+ ${constants.REL_QA} =>
+ "You are watching the QA Contact of the ${terms.bug}.",
+ ${constants.REL_CC} =>
+ "You are watching someone on the CC list of the ${terms.bug}.",
+} %]
+
+[% Hook.process('end') %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/setting-descs.none.tmpl b/Websites/bugs.webkit.org/template/en/default/global/setting-descs.none.tmpl
index 10290ad..a0b11f0 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/setting-descs.none.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/setting-descs.none.tmpl
@@ -43,5 +43,16 @@
"quote_replies" => "Quote the associated comment when you click on its reply link",
"quoted_reply" => "Quote the full comment",
"simple_reply" => "Reference the comment number only",
+ "comment_box_position" => "Position of the Additional Comments box",
+ "before_comments" => "Before other comments",
+ "after_comments" => "After other comments",
+ "timezone" => "Timezone used to display dates and times",
+ "local" => "Same as the server",
+ "quicksearch_fulltext" => "Include comments when performing quick searches (slower)",
+ "email_format" => "Preferred email format",
+ "html" => "HTML",
+ "text_only" => "Text Only",
}
%]
+
+[% Hook.process('settings') %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/site-navigation.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/site-navigation.html.tmpl
index 2acbcf44..06b0eaa 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/site-navigation.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/site-navigation.html.tmpl
@@ -20,7 +20,6 @@
#%]
[%# INTERFACE:
- # bug_list: list of integers. List of bug numbers of current query (if any).
# bug.bug_id: integer. Number of current bug (for navigation purposes)
#%]
@@ -32,38 +31,13 @@
[% IF NOT (cgi.user_agent("MSIE [1-6]") OR cgi.user_agent("Mozilla/4")) %]
<link rel="Top" href="[% urlbase FILTER html %]">
- [%# *** Bug List Navigation *** %]
- [% IF bug_list && bug_list.size > 0 %]
- <link rel="Up" href="buglist.cgi?regetlastlist=1">
-
- <link rel="First" href="show_bug.cgi?id=[% bug_list.first %]">
- <link rel="Last" href="show_bug.cgi?id=[% bug_list.last %]">
-
- [% IF bug && bug.bug_id %]
- [% current_bug_idx = lsearch(bug_list, bug.bug_id) %]
- [% IF current_bug_idx != -1 %]
-
- [% IF current_bug_idx > 0 %]
- [% prev_bug = current_bug_idx - 1 %]
- <link rel="Prev" href="show_bug.cgi?id=[% bug_list.$prev_bug %]">
- [% END %]
-
- [% IF current_bug_idx + 1 < bug_list.size %]
- [% next_bug = current_bug_idx + 1 %]
- <link rel="Next" href="show_bug.cgi?id=[% bug_list.$next_bug %]">
- [% END %]
-
- [% END %]
- [% END %]
- [% END %]
-
[%# *** Attachment *** %]
[% IF attachment && attachment.bug_id %]
<link rel="Up" href="show_bug.cgi?id=[% attachment.bug_id FILTER none %]">
[% END %]
- [%# *** Dependencies, Votes, Activity, Print-version *** %]
+ [%# *** Dependencies, Activity, Print-version *** %]
[% IF bug %]
<link rel="Show" title="Dependency Tree"
href="showdependencytree.cgi?id=[% bug.bug_id %]&hide_resolved=1">
@@ -72,11 +46,6 @@
href="showdependencygraph.cgi?id=[% bug.bug_id %]">
[% END %]
- [% IF bug.use_votes %]
- <link rel="Show" title="Votes ([% bug.votes %])"
- href="votes.cgi?action=show_bug&bug_id=[% bug.bug_id %]">
- [% END %]
-
<link rel="Show" title="[% terms.Bug %] Activity"
href="show_activity.cgi?id=[% bug.bug_id %]">
<link rel="Show" title="Printer-Friendly Version"
@@ -86,42 +55,36 @@
[%# *** Saved Searches *** %]
[% IF user.showmybugslink %]
- [% user_login = user.login FILTER url_quote %]
+ [% user_login = user.login FILTER uri %]
<link rel="Saved Searches" title="My [% terms.Bugs %]"
href="[% Param('mybugstemplate').replace('%userid%', user_login) %]">
[% END %]
- [% FOREACH q = user.queries %]
- <link rel="Saved Searches"
- title="[% q.name FILTER html %]"
- href="buglist.cgi?cmdtype=runnamed&namedcmd=[% q.name FILTER url_quote %]">
- [% END %]
-
[% FOREACH q = user.queries_subscribed %]
<link rel="Saved Search"
title="[% q.name FILTER html %] ([% q.user.login FILTER html %])"
href="buglist.cgi?cmdtype=dorem&remaction=run&namedcmd=
- [% q.name FILTER url_quote %]&sharer_id=
- [% q.user.id FILTER url_quote %]">
+ [% q.name FILTER uri %]&sharer_id=
+ [% q.user.id FILTER uri %]">
[% END %]
[%# *** Bugzilla Administration Tools *** %]
[% IF user.login %]
[% '<link rel="Administration" title="Parameters"
- href="editparams.cgi">' IF user.groups.tweakparams %]
+ href="editparams.cgi">' IF user.in_group('tweakparams') %]
[% '<link rel="Administration" title="Users"
- href="editusers.cgi">' IF user.groups.editusers %]
+ href="editusers.cgi">' IF user.in_group('editusers') %]
[% '<link rel="Administration" title="Products" href="editproducts.cgi">'
- IF user.groups.editcomponents || user.get_products_by_permission("editcomponents").size %]
+ IF user.in_group('editcomponents') || user.get_products_by_permission("editcomponents").size %]
[% '<link rel="Administration" title="Flag Types"
- href="editflagtypes.cgi">' IF user.groups.editcomponents %]
+ href="editflagtypes.cgi">' IF user.in_group('editcomponents') %]
[% '<link rel="Administration" title="Groups"
- href="editgroups.cgi">' IF user.groups.creategroups %]
+ href="editgroups.cgi">' IF user.in_group('creategroups') %]
[% '<link rel="Administration" title="Keywords"
- href="editkeywords.cgi">' IF user.groups.editkeywords %]
+ href="editkeywords.cgi">' IF user.in_group('editkeywords') %]
[% '<link rel="Administration" title="Whining"
- href="editwhines.cgi">' IF user.groups.bz_canusewhines %]
+ href="editwhines.cgi">' IF user.in_group('bz_canusewhines') %]
[% '<link rel="Administration" title="Sanity Check"
- href="sanitycheck.cgi">' IF user.groups.editcomponents %]
+ href="sanitycheck.cgi">' IF user.in_group('editcomponents') %]
[% END %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/tabs.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/tabs.html.tmpl
index 4f363c2..85556c4 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/tabs.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/tabs.html.tmpl
@@ -35,9 +35,13 @@
[% FOREACH tab = tabs %]
[% IF tab.name == current_tab_name %]
- <td class="selected">[% tab.label FILTER html %]</td>
+ <td id="tab_[% tab.name FILTER html %]" class="selected">
+ [% tab.label FILTER html %]</td>
[% ELSE %]
- <td class="clickable_area" onClick="document.location='[% tab.link FILTER html %]'"><a href="[% tab.link FILTER html %]">[% tab.label FILTER html %]</a></td>
+ <td id="tab_[% tab.name FILTER html %]" class="clickable_area"
+ onClick="document.location='[% tab.link FILTER html %]'">
+ <a href="[% tab.link FILTER html %]">[% tab.label FILTER html %]</a>
+ </td>
[% END %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/textarea.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/textarea.html.tmpl
index 006158b..ac7ab04 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/textarea.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/textarea.html.tmpl
@@ -19,7 +19,10 @@
# name: (optional) The "name"-attribute of the textarea.
# accesskey: (optional) The "accesskey"-attribute of the textarea.
# style: (optional) The "style"-attribute of the textarea.
+ # classes: (optional) The "class"-attribute of the textarea.
# wrap: (deprecated; optional) The "wrap"-attribute of the textarea.
+ # disabled: (optional) Disable the textarea.
+ # readonly: (optional) Prevent the textarea from being edited.
# minrows: (required) Number of rows the textarea shall have initially
# and when not having focus.
# maxrows: (optional) Number of rows the textarea shall have if
@@ -30,13 +33,18 @@
# minrows will be used.
# cols: (required) Number of columns the textarea shall have.
# defaultcontent: (optional) Default content for the textarea.
+ # mandatory: (optional) Boolean specifying whether or not the textarea
+ # is mandatory.
#%]
<textarea [% IF name %]name="[% name FILTER html %]"[% END %]
[% IF id %] id="[% id FILTER html %]"[% END %]
[% IF accesskey %] accesskey="[% accesskey FILTER html %]"[% END %]
[% IF style %] style="[% style FILTER html %]"[% END %]
+ [% IF classes %] class="[% classes FILTER html %]"[% END %]
[% IF wrap %] wrap="[% wrap FILTER html %]"[% END %]
+ [% IF disabled %] disabled="disabled"[% END %]
+ [% IF readonly %] readonly="readonly"[% END %]
[% IF defaultrows && user.settings.zoom_textareas.value == 'off' %]
rows="[% defaultrows FILTER html %]"
[% ELSE %]
@@ -45,4 +53,7 @@
cols="[% cols FILTER html %]"
[% IF maxrows && user.settings.zoom_textareas.value == 'on' %]
onFocus="this.rows=[% maxrows FILTER html %]"
+ [% END %]
+ [% IF mandatory %]
+ aria-required="true"
[% END %]>[% defaultcontent FILTER html %]</textarea>
diff --git a/Websites/bugs.webkit.org/template/en/default/global/useful-links.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/useful-links.html.tmpl
index a7c6e1a..dd4fed4 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/useful-links.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/useful-links.html.tmpl
@@ -27,7 +27,6 @@
<ul id="useful-links">
<li id="links-actions">
- <div class="label">Actions: </div>
[% PROCESS "global/common-links.html.tmpl" qs_suffix = "_bottom" %]
</li>
@@ -38,12 +37,9 @@
%]
[% print_pipe = 0 %]
<li id="links-saved">
- <div class="label">
- Saved Searches:
- </div>
<ul class="links">
[% IF user.showmybugslink %]
- [% filtered_username = user.login FILTER url_quote %]
+ [% filtered_username = user.login FILTER uri %]
<li><a href="[% Param('mybugstemplate').replace('%userid%', filtered_username) %]">My [% terms.Bugs %]</a></li>
[% print_pipe = 1 %]
[% END %]
@@ -51,7 +47,7 @@
[% FOREACH q = user.queries %]
[% IF q.link_in_footer %]
<li>[% '<span class="separator">| </span>' IF print_pipe %]
- <a href="buglist.cgi?cmdtype=runnamed&namedcmd=[% q.name FILTER url_quote %]">[% q.name FILTER html %]</a></li>
+ <a href="buglist.cgi?cmdtype=runnamed&namedcmd=[% q.name FILTER uri %]">[% q.name FILTER html %]</a></li>
[% print_pipe = 1 %]
[% END %]
[% END %]
@@ -65,8 +61,8 @@
<li>
[% '<span class="separator">| </span>' IF print_pipe %]
<a href="buglist.cgi?cmdtype=dorem&remaction=run&namedcmd=
- [% q.name FILTER url_quote %]&sharer_id=
- [% q.user.id FILTER url_quote %]"
+ [% q.name FILTER uri %]&sharer_id=
+ [% q.user.id FILTER uri %]"
class="shared"
title="Shared by [% q.user.identity FILTER html %]"
>[% q.name FILTER html FILTER no_break %]</a></li>
diff --git a/Websites/bugs.webkit.org/template/en/default/global/user-error.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/user-error.html.tmpl
index bbae9b3..3d1ac5c 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/user-error.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/user-error.html.tmpl
@@ -17,6 +17,7 @@
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
# Frédéric Buclin <LpSolit@gmail.com>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
#%]
[%# INTERFACE:
@@ -32,10 +33,10 @@
# in this file; if you do not wish to change it, use the "none" filter.
#
# Extension- or custom-specific error handling can be easily added
- # via hooks: just place your <extension>-errors.html.tmpl into
- # template/en/extension/hook/global/user-error.html.tmpl/errors/
- # Note: be aware of uniqueness of error string parameter value, since
- # nobody can guarantee the hook files processing order in the future
+ # via hooks: just place additional code into
+ # template/en/hook/global/user-error-errors.html.tmpl
+ # Note: be aware of uniqueness of error string parameter value, since
+ # nobody can guarantee the hook files processing order in the future.
#%]
[% PROCESS global/variables.none.tmpl %]
@@ -76,6 +77,12 @@
that login name.
[% END %]
+ [% ELSIF error == "account_locked" %]
+ [% title = "Account Locked" %]
+ Your IP ([% ip_addr FILTER html %]) has been locked out of this
+ account until [% unlock_at FILTER time %], as you have
+ exceeded the maximum number of login attempts.
+
[% ELSIF error == "alias_has_comma_or_space" %]
[% title = "Invalid Characters In Alias" %]
The alias you entered, <em>[% alias FILTER html %]</em>,
@@ -102,6 +109,11 @@
[% terms.Bug %] aliases cannot be longer than 20 characters.
Please choose a shorter alias.
+ [% ELSIF error == "attachment_bug_id_mismatch" %]
+ [% title = "Invalid Attachments" %]
+ You tried to perform an action on attachments from different [% terms.bugs %].
+ This operation requires all attachments to be from the same [% terms.bug %].
+
[% ELSIF error == "auth_cant_create_account" %]
[% title = "Can't create accounts" %]
This site is using an authentication scheme which does not permit
@@ -138,24 +150,32 @@
delete
[% ELSIF action == "edit" %]
add, modify or delete
- [% ELSIF action == "move" %]
- move
[% ELSIF action == "run" %]
run
[% ELSIF action == "schedule" %]
schedule
+ [% ELSIF action == "search" %]
+ search
[% ELSIF action == "use" %]
use
[% ELSIF action == "approve" %]
approve
+ [% ELSE %]
+ [%+ Hook.process('auth_failure_action') %]
[% END %]
[% IF object == "administrative_pages" %]
administrative pages
[% ELSIF object == "attachment" %]
- this attachment
+ [% IF attach_id %]
+ attachment #[% attach_id FILTER html %]
+ [% ELSE %]
+ this attachment
+ [% END %]
[% ELSIF object == "bugs" %]
[%+ terms.bugs %]
+ [% ELSIF object == "bug_fields" %]
+ the [% field_descs.$field FILTER html %] field
[% ELSIF object == "charts" %]
the "New Charts" feature
[% ELSIF object == "classifications" %]
@@ -194,14 +214,17 @@
a sudo session
[% ELSIF object == "timetracking_summaries" %]
time-tracking summary reports
- [% ELSIF object == "user" %]
- the user you specified
+ [% ELSIF object == "user" %]
+ the user [% IF userid %] with ID '[% userid FILTER html %]'
+ [% ELSE %]you specified [% END %]
[% ELSIF object == "users" %]
users
[% ELSIF object == "versions" %]
versions
[% ELSIF object == "workflow" %]
the workflow
+ [% ELSE %]
+ [%+ Hook.process('auth_failure_object') %]
[% END %].
[% Hook.process("auth_failure") %]
@@ -225,23 +248,63 @@
You are not authorized to access [% terms.bug %] #[% bug_id FILTER html %].
To see this [% terms.bug %], you must
first <a href="show_bug.cgi?id=
- [% bug_id FILTER url_quote %]&GoAheadAndLogIn=1">log
+ [% bug_id FILTER uri %]&GoAheadAndLogIn=1">log
in to an account</a> with the appropriate permissions.
+ [% ELSIF error == "bug_url_invalid" %]
+ [% title = "Invalid $terms.Bug URL" %]
+ <code>[% url FILTER html %]</code> is not a valid URL to [% terms.abug %].
+ [% IF reason == 'http' %]
+ URLs must start with "http" or "https".
+ [% ELSIF reason == 'path_only' %]
+ You must specify a full URL.
+ [% ELSIF reason == 'show_bug' %]
+ [%+ field_descs.see_also FILTER html %] URLs should point to one of:
+ <ul>
+ <li><code>show_bug.cgi</code> in a [% terms.Bugzilla %]
+ installation.</li>
+ <li>A b[% %]ug on launchpad.net</li>
+ <li>An issue on code.google.com.</li>
+ <li>A b[% %]ug on b[% %]ugs.debian.org.</li>
+ <li>An issue in a JIRA installation.</li>
+ <li>A ticket in a Trac installation.</li>
+ <li>A b[% %]ug in a MantisBT installation.</li>
+ <li>A b[% %]ug on sourceforge.net.</li>
+ </ul>
+ [% ELSIF reason == 'id' %]
+ There is no valid [% terms.bug %] id in that URL.
+ [% END %]
+
+ [% ELSIF error == "bug_url_too_long" %]
+ [% title = "Invalid $terms.Bug URL" %]
+ [% terms.Bug %] URLs can not be longer than
+ [%+ constants.MAX_BUG_URL_LENGTH FILTER none %] characters long.
+ <code>[% url FILTER html %]</code> is too long.
+
[% ELSIF error == "buglist_parameters_required" %]
[% title = "Parameters Required" %]
[% docslinks = {'query.html' => "Searching for $terms.bugs",
'query.html#list' => "$terms.Bug lists"} %]
You may not search, or create saved searches, without any search terms.
+ [% ELSIF error == "cc_remove_denied" %]
+ [% title = "Change Denied" %]
+ You do not have permission to remove other people from the CC list.
+
[% ELSIF error == "chart_too_large" %]
[% title = "Chart Too Large" %]
Sorry, but 2000 x 2000 is the maximum size for a chart.
+ [% ELSIF error == "comment_id_invalid" %]
+ [% id FILTER html %] is not a valid comment id.
+
[% ELSIF error == "comment_invalid_isprivate" %]
You tried to modify the privacy of comment id [% id FILTER html %],
but that is not a valid comment on this [% terms.bug %].
+ [% ELSIF error == "comment_is_private" %]
+ Comment id [% id FILTER html %] is private.
+
[% ELSIF error == "comment_required" %]
[% title = "Comment Required" %]
You have to specify a
@@ -256,13 +319,19 @@
[% ELSIF error == "comment_too_long" %]
[% title = "Comment Too Long" %]
- Comments cannot be longer than 65,535 characters.
+ Comments cannot be longer than
+ [%+ constants.MAX_COMMENT_LENGTH FILTER html %] characters.
[% ELSIF error == "auth_classification_not_enabled" %]
[% title = "Classification Not Enabled" %]
Sorry, classification is not enabled.
- [% ELSIF error == "classification_not_specified" %]
+ [% ELSIF error == "classification_name_too_long" %]
+ [% title = "Classification Name Too Long" %]
+ The name of a classification is limited to [% constants.MAX_CLASSIFICATION_SIZE FILTER html %]
+ characters. '[% name FILTER html %]' is too long ([% name.length %] characters).
+
+[% ELSIF error == "classification_not_specified" %]
[% title = "You Must Supply A Classification Name" %]
You must enter a classification name.
@@ -270,19 +339,10 @@
[% title = "Classification Already Exists" %]
A classification with the name '[% name FILTER html %]' already exists.
- [% ELSIF error == "classification_doesnt_exist" %]
- [% title = "Classification Does Not Exist" %]
- The classification '[% name FILTER html %]' does not exist.
-
- [% ELSIF error == "classification_doesnt_exist_for_product" %]
- [% title = "Classification Does Not Exist For Product" %]
- The classification '[% classification FILTER html %]' does not exist
- for product '[% product FILTER html %]'.
-
[% ELSIF error == "classification_invalid_sortkey" %]
[% title = "Invalid Sortkey for Classification" %]
- The sortkey <em>[% sortkey FILTER html %]</em> for the '[% name FILTER html %]'
- classification is invalid. It must be a positive integer.
+ The sortkey '[% sortkey FILTER html %]' is invalid. It must be an
+ integer between 0 and [% constants.MAX_SMALLINT FILTER html %].
[% ELSIF error == "classification_not_deletable" %]
[% title = "Default Classification Can Not Be Deleted" %]
@@ -314,8 +374,8 @@
[% ELSIF error == "component_name_too_long" %]
[% title = "Component Name Is Too Long" %]
- The name of a component is limited to 64 characters.
- '[% name FILTER html %]' is too long ([% name.length %] characters).
+ The name of a component is limited to [% constants.MAX_COMPONENT_SIZE FILTER html %]
+ characters. '[% name FILTER html %]' is too long ([% name.length %] characters).
[% ELSIF error == "component_need_initialowner" %]
[% title = "Component Requires Default Assignee" %]
@@ -402,12 +462,30 @@
does not exist or you aren't authorized to
enter [% terms.abug %] into it.
+ [% ELSIF error == "extension_create_no_name" %]
+ You must specify a name for your extension, as an argument to this script.
+
+ [% ELSIF error == "extension_first_letter_caps" %]
+ The first letter of your extension's name must be a capital letter.
+ (You specified '[% name FILTER html %]'.)
+
[% ELSIF error == "field_already_exists" %]
[% title = "Field Already Exists" %]
The field '[% field.name FILTER html %]'
([% field.description FILTER html %]) already exists. Please
choose another name.
+ [% ELSIF error == "field_cant_control_self" %]
+ [% title = "Field Can't Control Itself" %]
+ The [% field.description FILTER html %] field can't be set to control
+ itself.
+
+ [% ELSIF error == "field_control_must_be_select" %]
+ [% title = "Invalid Field Type Selected" %]
+ Only drop-down and multi-select fields can be used to control
+ the visibility/values of other fields. [% field.description FILTER html %]
+ is not the right type of field.
+
[% ELSIF error == "field_invalid_name" %]
[% title = "Invalid Field Name" %]
'[% name FILTER html %]' is not a valid name for a field.
@@ -426,69 +504,92 @@
[% title = "Missing Name for Field" %]
You must enter a name for this field.
+ [% ELSIF error == "field_value_control_select_only" %]
+ [% title = "Invalid Value Control Field" %]
+ Only Drop-Down or Multi-Select fields can have a field that
+ controls their values.
+
+ [% ELSIF error == "field_visibility_values_must_be_selected" %]
+ [% title = "Missing visibility field values" %]
+ At least one value must be selected for the visibility field
+ '[% field.name FILTER html %]'.
+
[% ELSIF error == "fieldname_invalid" %]
[% title = "Specified Field Does Not Exist" %]
- The field '[% field FILTER html %]' does not exist or
+ The field '[% field.name FILTER html %]' does not exist or
cannot be edited with this interface.
- [% ELSIF error == "fieldname_not_specified" %]
- [% title = "Field Name Not Specified" %]
- No field name specified when trying to edit field values.
-
[% ELSIF error == "fieldvalue_already_exists" %]
[% title = "Field Value Already Exists" %]
- The value '[% value FILTER html %]' already exists for the
- '[%- field.description FILTER html %]' field.
+ The value '[% value.name FILTER html %]' already exists for the
+ [%+ field.description FILTER html %] field.
- [% ELSIF error == "fieldvalue_doesnt_exist" %]
- [% title = "Specified Field Value Does Not Exist" %]
- The value '[% value FILTER html %]' does not exist for
- the '[% field FILTER html %]' field.
+ [% ELSIF error == "fieldvalue_is_controller" %]
+ [% title = "Value Controls Other Fields" %]
+ You cannot delete the [% value.field.description FILTER html %]
+ '[% value.name FILTER html %]' because
+ [% IF fields.size %]
+ it controls the visibility of the following fields:
+ [%+ fields.join(', ') FILTER html %].
+ [% END %]
+ [% ' Also, ' IF fields.size AND vals.size %]
+ [% IF vals.size %]
+ it controls the visibility of the following field values:
+ <ul>
+ [% FOREACH field_name = vals.keys %]
+ [% FOREACH val = vals.${field_name} %]
+ <li>[% val.field.name FILTER html %]:
+ '[% val.name FILTER html %]'</li>
+ [% END %]
+ [% END %]
+ </ul>
+ [% END %]
[% ELSIF error == "fieldvalue_is_default" %]
[% title = "Specified Field Value Is Default" %]
- '[% value FILTER html %]' is the default value for
- the '[% field.description FILTER html %]' field and cannot be deleted.
- [% IF user.groups.tweakparams %]
+ '[% value.name FILTER html %]' is the default value for
+ the '[% field.description FILTER html %]' field and cannot be deleted
+ or disabled.
+ [% IF user.in_group('tweakparams') %]
You have to <a href="editparams.cgi?section=bugfields#
- [%- param_name FILTER url_quote %]">change</a> the default value first.
+ [%- param_name FILTER uri %]">change</a> the default value first.
[% END %]
[% ELSIF error == "fieldvalue_name_too_long" %]
[% title = "Field Value Is Too Long" %]
- The value of a field is limited to 60 characters.
+ The value of a field is limited to
+ [%+ constants.FIELD_VALUE_MAX_SIZE FILTER none %] characters.
'[% value FILTER html %]' is too long ([% value.length %] characters).
[% ELSIF error == "fieldvalue_not_editable" %]
[% title = "Field Value Not Editable" %]
- The value '[% old_value FILTER html %]' cannot be renamed because
- it plays some special role for the '[% field.description FILTER html %]' field.
+ The value '[% old_value.name FILTER html %]' cannot be renamed because
+ it plays some special role for the '[% field.description FILTER html %]'
+ field.
[% ELSIF error == "fieldvalue_not_deletable" %]
[% title = "Field Value Not Deletable" %]
- The value '[% value FILTER html %]' cannot be removed because
- it plays some special role for the '[% field.description FILTER html %]' field.
-
- [% ELSIF error == "fieldvalue_not_specified" %]
- [% title = "Field Value Not Specified" %]
- No field value specified when trying to edit a field value.
+ The value '[% value.name FILTER html %]' cannot be removed or
+ disabled, because it plays some special role for the
+ '[% field.description FILTER html %]' field.
[% ELSIF error == "fieldvalue_reserved_word" %]
[% title = "Reserved Word Not Allowed" %]
- You cannot use the '[% value FILTER html %]' value for the
+ You cannot use the value '[% value FILTER html %]' for the
'[% field.description FILTER html %]' field. This value is used internally.
Please choose another one.
[% ELSIF error == "fieldvalue_sortkey_invalid" %]
[% title = "Invalid Field Value Sortkey" %]
- The sortkey '[% sortkey FILTER html %]' for the '[% name FILTER html %]'
- field is not a valid (positive) number.
+ The sortkey '[% sortkey FILTER html %]' for the
+ [%+ field.description FILTER html %] field is not a valid
+ (positive) number.
[% ELSIF error == "fieldvalue_still_has_bugs" %]
[% title = "You Cannot Delete This Field Value" %]
- You cannot delete the value '[% value FILTER html %]' from the
- '[% field FILTER html %]' field, because there are still
- [%+ count FILTER html %] [%+ terms.bugs %] using it.
+ You cannot delete the value '[% value.name FILTER html %]' from the
+ [%+ field.description FILTER html %] field, because there are still
+ [%+ value.bug_count FILTER html %] [%+ terms.bugs %] using it.
[% ELSIF error == "fieldvalue_undefined" %]
[% title = "Undefined Value Not Allowed" %]
@@ -498,28 +599,25 @@
[% title = "No File Specified" %]
You did not specify a file to attach.
+ [% ELSIF error == "filename_not_specified" %]
+ [% title = "No Filename Specified" %]
+ You must specify a filename for this attachment.
+
[% ELSIF error == "file_too_large" %]
[% title = "File Too Large" %]
+ [%# Convert maxlocalattachment from Mb to Kb %]
+ [% max_local = Param('maxlocalattachment') * 1024 %]
+ [% max_limit = [Param('maxattachmentsize'), max_local] %]
The file you are trying to attach is [% filesize FILTER html %]
- kilobytes (KB) in size. Non-patch attachments cannot be more than
- [%+ Param('maxattachmentsize') %] KB. <br>
+ kilobytes (KB) in size. Attachments cannot be more than
+ [%# Hack to get the max value between both limits %]
+ [%+ max_limit.nsort.last FILTER html %] KB. <br>
We recommend that you store your attachment elsewhere
- [% IF Param("allow_attach_url") %]
- and then specify the URL to this file on the attachment
- creation page in the <b>AttachURL</b> field.
- [% ELSE %]
- and then insert the URL to it in a comment, or in the URL field
- for this [% terms.bug %].
- [% END %]
+ and then paste the URL to this file on the attachment
+ creation page in the appropriate text field.
<br>Alternately, if your attachment is an image, you could convert
it to a compressible format like JPG or PNG and try again.
- [% ELSIF error == "flag_not_multiplicable" %]
- [% docslinks = {'flags-overview.html' => 'An overview on Flags',
- 'flags.html' => 'Using Flags'} %]
- You can't ask more than one person at a time for
- <em>[% type.name FILTER html %]</em>.
-
[% ELSIF error == "flag_requestee_needs_privs" %]
[% title = "Flag Requestee Needs Privileges" %]
[% requestee.identity FILTER html %] does not have permission to set the
@@ -558,6 +656,24 @@
and the user you asked isn't in that group. Please choose someone else
to ask, or ask an administrator to add the user to the group.
+ [% ELSIF error == "flag_status_invalid" %]
+ [% title = "Flag Status Invalid" %]
+ The flag status <em>[% status FILTER html %]</em>
+ [% IF id %]
+ for flag ID #[% id FILTER html %]
+ [% END %]
+ is invalid.
+
+ [% ELSIF error == "flag_type_cannot_deactivate" %]
+ [% title = "Cannot Deactivate Flag Type" %]
+ Sorry, but the flag type '[% flagtype.name FILTER html %]' also applies to some
+ products you cannot see, and so you are not allowed to deactivate it.
+
+ [% ELSIF error == "flag_type_cannot_delete" %]
+ [% title = "Flag Type Deletion Not Allowed" %]
+ Sorry, but the flag type '[% flagtype.name FILTER html %]' also applies to some
+ products you cannot see, and so you are not allowed to delete it.
+
[% ELSIF error == "flag_type_cc_list_invalid" %]
[% title = "Flag Type CC List Invalid" %]
[% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags'} %]
@@ -570,7 +686,7 @@
[% ELSIF error == "flag_type_description_invalid" %]
[% title = "Flag Type Description Invalid" %]
[% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags'} %]
- The description must be less than 32K.
+ You must enter a description for this flag type.
[% ELSIF error == "flag_type_name_invalid" %]
[% title = "Flag Type Name Invalid" %]
@@ -578,6 +694,17 @@
The name <em>[% name FILTER html %]</em> must be 1-50 characters long
and must not contain any spaces or commas.
+ [% ELSIF error == "flag_type_not_editable" %]
+ [% title = "Flag Type Not Editable" %]
+ You are not allowed to edit properties of the '[% flagtype.name FILTER html %]'
+ flag type, because this flag type is not available for the products you can administer.
+
+ [% ELSIF error == "flag_type_not_multiplicable" %]
+ [% docslinks = {'flags-overview.html' => 'An overview on Flags',
+ 'flags.html' => 'Using Flags'} %]
+ You cannot have several <em>[% type.name FILTER html %]</em> flags
+ for this [% IF attachment %] attachment [% ELSE %] [%+ terms.bug %] [% END %].
+
[% ELSIF error == "flag_update_denied" %]
[% title = "Flag Modification Denied" %]
[% admindocslinks = {'flags-overview.html#flags-admin' => 'Administering Flags',
@@ -598,30 +725,25 @@
[% ELSIF error == "flag_type_sortkey_invalid" %]
[% title = "Flag Type Sort Key Invalid" %]
- The sort key must be an integer between 0 and 32767 inclusive.
- It cannot be <em>[% sortkey FILTER html %]</em>.
+ The sort key <em>[% sortkey FILTER html %]</em> must be an integer
+ between 0 and [% constants.MAX_SMALLINT FILTER none %].
[% ELSIF error == "freetext_too_long" %]
[% title = "Text Too Long" %]
- The text you entered is too long ([% text.length FILTER html %] characters,
- above the maximum length allowed of [% constants.MAX_FREETEXT_LENGTH FILTER none %]
- characters):
- <p><em>[% text FILTER html %]</em></p>
+ The text you entered in the [% field_descs.$field FILTER html %]
+ field is too long ([% text.length FILTER html %] characters,
+ above the maximum length allowed of
+ [%+ constants.MAX_FREETEXT_LENGTH FILTER none %] characters).
[% ELSIF error == "group_cannot_delete" %]
[% title = "Cannot Delete Group" %]
- The <em>[% name FILTER html %]</em> group cannot be deleted because
+ The <em>[% group.name FILTER html %]</em> group cannot be deleted because
there are
- <a href="editgroups.cgi?action=del&group=[% gid FILTER url_quote %]">records</a>
+ <a href="editgroups.cgi?action=del&group=
+ [%- group.id FILTER uri %]">records</a>
in the database which refer to it. All references to this group must
be removed before you can remove it.
- [% ELSIF error == "group_change_denied" %]
- [% title = "Cannot Add/Remove That Group" %]
- You tried to add or remove group id [% group_id FILTER html %]
- from [% terms.bug %] [%+ bug.id FILTER html %], but you do not
- have permissions to do so.
-
[% ELSIF error == "group_exists" %]
[% title = "The group already exists" %]
The group [% name FILTER html %] already exists.
@@ -642,36 +764,32 @@
[% ELSIF error == "group_invalid_removal" %]
- You tried to remove [% terms.bug %] [%+ bug.id FILTER html %]
- from group id [% group_id FILTER html %], but [% terms.bugs %] in the
- '[% product FILTER html %]' product can not be removed from that
- group.
-
- [% ELSIF error == "group_invalid_restriction" %]
- You tried to restrict [% terms.bug %] [%+ bug.id FILTER html %] to
- to group id [% group_id FILTER html %], but [% terms.bugs %] in the
- '[% product FILTER html %]' product can not be restricted to
- that group.
+ You tried to remove [% terms.bug %] [%+ bug_id FILTER html %]
+ from the '[% name FILTER html %]' group, but either this group does not exist,
+ or you are not allowed to remove [% terms.bugs %] from this group in the
+ '[% product FILTER html %]' product.
+
+ [% ELSIF error == "group_restriction_not_allowed" %]
+ [% title = "Group Restriction Not Allowed" %]
+ You tried to restrict [% bug_id ? "$terms.bug $bug_id" : terms.abug FILTER html %]
+ to the '[% name FILTER html %]' group, but either this group does not exist,
+ or you are not allowed to restrict [% terms.bugs %] to this group in the
+ '[% product FILTER html %]' product.
[% ELSIF error == "group_not_specified" %]
[% title = "Group not specified" %]
No group was specified.
+ [% ELSIF error == "group_not_visible" %]
+ [% title = "Group Not Allowed" %]
+ You are not allowed to see members of the [% group.name FILTER html %]
+ group.
+
[% ELSIF error == "system_group_not_deletable" %]
[% title = "System Groups not deletable" %]
<em>[% name FILTER html %]</em> is a system group.
This group cannot be deleted.
- [% ELSIF error == "group_unknown" %]
- [% title = "Unknown Group" %]
- The group [% name FILTER html %] does not exist. Please specify
- a valid group name. Create it first if necessary!
-
- [% ELSIF error == "illegal_at_least_x_votes" %]
- [% title = "Your Search Makes No Sense" %]
- The <em>At least ___ votes</em> field must be a simple number.
- You entered <tt>[% value FILTER html %]</tt>, which isn't.
-
[% ELSIF error == "illegal_attachment_edit" %]
[% title = "Unauthorized Action" %]
You are not authorized to edit attachment [% attach_id FILTER html %].
@@ -681,11 +799,6 @@
You are not authorized to edit attachments on [% terms.bug %]
[%+ bug_id FILTER html %].
- [% ELSIF error == "illegal_attachment_is_patch" %]
- [% title = "Your Search Makes No Sense" %]
- The only legal values for the <em>Attachment is patch</em> field are
- 0 and 1.
-
[% ELSIF error == "illegal_bug_status_transition" %]
[% title = "Illegal $terms.Bug Status Change" %]
[% IF old.defined %]
@@ -707,9 +820,9 @@
to <em>[% newvalue.join(', ') FILTER html %]</em>
[% END %]
, but only
- [% IF privs < 3 %]
+ [% IF privs < constants.PRIVILEGES_REQUIRED_EMPOWERED %]
the assignee
- [% IF privs < 2 %] or reporter [% END %]
+ [% IF privs < constants.PRIVILEGES_REQUIRED_ASSIGNEE %] or reporter [% END %]
of the [% terms.bug %], or
[% END %]
a user with the required permissions may change that field.
@@ -721,11 +834,6 @@
but only a user allowed to edit
both related [% terms.bugs %] may change that field.
- [% ELSIF error == "illegal_changed_in_last_x_days" %]
- [% title = "Your Search Makes No Sense" %]
- The <em>Changed in last ___ days</em> field must be a simple number.
- You entered <tt>[% value FILTER html %]</tt>, which isn't.
-
[% ELSIF error == "illegal_date" %]
[% title = "Illegal Date" %]
'<tt>[% date FILTER html %]</tt>' is not a legal date.
@@ -741,11 +849,10 @@
A legal address must contain exactly one '@',
and at least one '.' after the @.
[% ELSE %]
- [%+ Param('emailregexpdesc') %]
+ [%+ Param('emailregexpdesc') FILTER html_light %]
[% END %]
- It must also not contain any of these special characters:
- <tt>\ ( ) & < > , ; : " [ ]</tt>, or any whitespace.
-
+ It also must not contain any illegal characters.
+
[% ELSIF error == "illegal_frequency" %]
[% title = "Too Frequent" %]
Unless you are an administrator, you may not create series which are
@@ -757,11 +864,6 @@
Your group control combination for group "
[% groupname FILTER html %]" is illegal.
- [% ELSIF error == "illegal_is_obsolete" %]
- [% title = "Your Search Makes No Sense" %]
- The only legal values for the <em>Attachment is obsolete</em> field are
- 0 and 1.
-
[% ELSIF error == "illegal_query_name" %]
[% title = "Illegal Search Name" %]
The name of your search cannot contain any of the following characters:
@@ -784,6 +886,11 @@
[% IF format %]
Please use the format '<tt>[% format FILTER html %]</tt>'.
[% END %]
+
+ [% ELSIF error == "illegal_regexp" %]
+ [% title = "Illegal Regular Expression" %]
+ The regular expression you provided [% value FILTER html %] is not valid.
+ The error was: [% dberror FILTER html %].
[% ELSIF error == "insufficient_data_points" %]
[% docslinks = {'reporting.html' => 'Reporting'} %]
@@ -823,9 +930,9 @@
[% title = "Invalid Content-Type" %]
The content type <em>[% contenttype FILTER html %]</em> is invalid.
Valid types must be of the form <em>foo/bar</em> where <em>foo</em>
- is either <em>application, audio, image, message, model, multipart,
- text,</em> or <em>video</em>.
-
+ is one of <em>[% constants.LEGAL_CONTENT_TYPES.join(', ') FILTER html %]</em>
+ and <em>bar</em> must not contain any special characters (such as "=", "?", ...).
+
[% ELSIF error == "invalid_context" %]
[% title = "Invalid Context" %]
The context [% context FILTER html %] is invalid (must be a number,
@@ -833,7 +940,7 @@
[% ELSIF error == "invalid_datasets" %]
[% title = "Invalid Datasets" %]
- Invalid datasets <em>[% datasets FILTER html %]</em>. Only digits,
+ Invalid datasets <em>[% datasets.join(":") FILTER html %]</em>. Only digits,
letters and colons are allowed.
[% ELSIF error == "invalid_format" %]
@@ -896,6 +1003,39 @@
[% ELSIF error == "invalid_username_or_password" %]
[% title = "Invalid Username Or Password" %]
The username or password you entered is not valid.
+ [%# People get two login attempts before being warned about
+ # being locked out.
+ #%]
+ [% IF remaining <= 2 %]
+ If you do not enter the correct password after
+ [%+ remaining FILTER html %] more attempt(s), you will be
+ locked out of this account for
+ [%+ constants.LOGIN_LOCKOUT_INTERVAL FILTER html %] minutes.
+ [% END %]
+
+ [% ELSIF error == "json_rpc_get_method_required" %]
+ When using JSON-RPC over GET, you must specify a 'method'
+ parameter. See the documentation at
+ [%+ docs_urlbase FILTER html %]api/Bugzilla/WebService/Server/JSONRPC.html
+
+ [% ELSIF error == "json_rpc_illegal_content_type" %]
+ When using JSON-RPC over POST, you cannot send data as
+ [%+ content_type FILTER html %]. Only application/json and
+ application/json-rpc are allowed.
+
+ [% ELSIF error == "json_rpc_invalid_params" %]
+ Could not parse the 'params' argument as valid JSON.
+ Error: [% err_msg FILTER html %]
+ Value: [% params FILTER html %]
+
+ [% ELSIF error == "json_rpc_invalid_callback" %]
+ You cannot use '[% callback FILTER html %]' as your 'callback' parameter.
+ For security reasons, only letters, numbers, and the following
+ characters are allowed in the 'callback' parameter: <code>[]._</code>
+
+ [% ELSIF error == "json_rpc_post_only" %]
+ For security reasons, you must use HTTP POST to call the
+ '[% method FILTER html %]' method.
[% ELSIF error == "keyword_already_exists" %]
[% title = "Keyword Already Exists" %]
@@ -913,11 +1053,6 @@
[% title = "Invalid Keyword Name" %]
You may not use commas or whitespace in a keyword name.
- [% ELSIF error == "local_file_too_large" %]
- [% title = "Local File Too Large" %]
- Local file uploads must not exceed
- [% Param('maxlocalattachment') %] MB in size.
-
[% ELSIF error == "login_needed_for_password_change" %]
[% title = "Login Name Required" %]
You must enter a login name when requesting to change your password.
@@ -927,6 +1062,19 @@
You can't use %user% without being logged in, because %user% refers
to your login name, which we don't know.
+ [% ELSIF error == "login_required" %]
+ [%# Used for non-web-based LOGIN_REQUIRED situations. %]
+ You must log in before using this part of [% terms.Bugzilla %].
+
+ [% ELSIF error == "migrate_config_created" %]
+ The file <kbd>[% file FILTER html %]</kbd> contains configuration
+ variables that must be set before continuing with the migration.
+
+ [% ELSIF error == "migrate_from_invalid" %]
+ '[% from FILTER html %]' is not a valid type of [% terms.bug %]-tracker
+ to migrate from. See the contents of the <kbd>B[% %]ugzilla/Migrate/</kbd>
+ directory for a list of valid [% terms.bug %]-trackers.
+
[% ELSIF error == "milestone_already_exists" %]
[% title = "Milestone Already Exists" %]
[% admindocslinks = {'products.html' => 'Administering products',
@@ -948,8 +1096,8 @@
[% ELSIF error == "milestone_name_too_long" %]
[% title = "Milestone Name Is Too Long" %]
- The name of a milestone is limited to 20 characters.
- '[% name FILTER html %]' is too long ([% name.length %] characters).
+ The name of a milestone is limited to [% constants.MAX_MILESTONE_SIZE FILTER html %]
+ characters. '[% name FILTER html %]' is too long ([% name.length %] characters).
[% ELSIF error == "milestone_required" %]
[% title = "Milestone Required" %]
@@ -982,10 +1130,10 @@
[% admindocslinks = {'products.html' => 'Administering products',
'components.html' => 'Creating a component'} %]
Sorry, the product <em>[% product.name FILTER html %]</em>
- has to have at least one component in order for you to
+ has to have at least one active component in order for you to
enter [% terms.abug %] into it.<br>
[% IF user.in_group("editcomponents", product.id) %]
- <a href="editcomponents.cgi?action=add&product=[% product.name FILTER url_quote %]">Create
+ <a href="editcomponents.cgi?action=add&product=[% product.name FILTER uri %]">Create
a new component</a>.
[% ELSE %]
Please contact [% Param("maintainer") %] and ask them
@@ -1015,11 +1163,6 @@
[% docslinks = {'reporting.html' => 'Reporting'} %]
You must specify one or more datasets to plot.
- [% ELSIF error == "missing_email_type" %]
- [% title = "Your Search Makes No Sense" %]
- You must specify one or more fields in which to search for
- <tt>[% email FILTER html %]</tt>.
-
[% ELSIF error == "missing_frequency" %]
[% title = "Missing Frequency" %]
[% docslinks = {'reporting.html' => 'Reporting'} %]
@@ -1046,11 +1189,6 @@
A valid resolution is required to mark [% terms.bugs %] as
[%+ status FILTER upper FILTER html %].
- [% ELSIF error == "move_bugs_disabled" %]
- [% title = BLOCK %][% terms.Bug %] Moving Disabled[% END %]
- Sorry, [% terms.bug %] moving has been disabled. If you need
- to move [% terms.abug %], please contact [% Param("maintainer") %].
-
[% ELSIF error == "missing_subcategory" %]
[% title = "Missing Subcategory" %]
You did not specify a subcategory for this series.
@@ -1059,16 +1197,20 @@
[% title = "Missing Version" %]
[% admindocslinks = {'versions.html' => 'Defining versions'} %]
Sorry, the product <em>[% product.name FILTER html %]</em>
- has to have at least one version in order for you to
+ has to have at least one active version in order for you to
enter [% terms.abug %] into it.<br>
[% IF user.in_group("editcomponents", product.id) %]
- <a href="editversions.cgi?action=add&product=[% product.name FILTER url_quote %]">Create
+ <a href="editversions.cgi?action=add&product=[% product.name FILTER uri %]">Create
a new version</a>.
[% ELSE %]
Please contact [% Param("maintainer") %] and ask them
to add a version to this product.
[% END %]
+ [% ELSIF error == "multiple_alias_not_allowed" %]
+ You cannot set aliases when modifying multiple [% terms.bugs %]
+ at once.
+
[% ELSIF error == "need_quip" %]
[% title = "Quip Required" %]
[% docslinks = {'quips.html' => 'About quips'} %]
@@ -1092,47 +1234,9 @@
to view.
[% END %]
- [% ELSIF error == "no_bug_ids" %]
- [% title = BLOCK %]No [% terms.Bugs %] Selected[% END %]
- You didn't choose any [% terms.bugs %] to
- [% IF action == "add" %] add to [% ELSE %] remove from [% END %]
- the [% tag FILTER html %] tag.
-
- [% ELSIF error == "no_bugs_in_list" %]
- [% title = "Delete Tag?" %]
- This will remove all [% terms.bugs %] from the
- [% tag FILTER html %] tag. This will delete the tag completely. Click
- <a href="buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=
- [%- tag FILTER url_quote %]">here</a> if you really want to delete it.
-
- [% ELSIF error == "no_bugs_to_remove" %]
+ [% ELSIF error == "no_tag_to_edit" %]
[% title = "No Tag Selected" %]
- You didn't select a tag from which to remove [% terms.bugs %].
-
- [% ELSIF error == "no_dupe_stats" %]
- [% title = "Cannot Find Duplicate Statistics" %]
- [% admindocslinks = {'extraconfig.html' => 'Setting up the collecstats.pl job'} %]
- There are no duplicate statistics for today ([% today FILTER html %])
- or yesterday.
-
- [% ELSIF error == "no_dupe_stats_error_today" %]
- [% title = "Error Reading Today's Dupes File" %]
- [% admindocslinks = {'extraconfig.html' => 'Setting up the collecstats.pl job'} %]
- An error occurred opening today's dupes file: [% error_msg FILTER html %].
-
- [% ELSIF error == "no_dupe_stats_error_whenever" %]
- [% title = "Error Reading Previous Dupes File" %]
- [% admindocslinks = {'extraconfig.html' => 'Setting up the collecstats.pl job'} %]
- An error occurred opening [% changedsince FILTER html %] days ago
- ([% whenever FILTER html %])'s dupes file:
- [% error_msg FILTER html %].
-
- [% ELSIF error == "no_dupe_stats_error_yesterday" %]
- [% title = "Error Reading Yesterday's Dupes File" %]
- [% admindocslinks = {'extraconfig.html' => 'Setting up the collecstats.pl job'} %]
- There are no duplicate statistics for today ([% today FILTER html %]),
- and an error
- occurred opening yesterday's dupes file: [% error_msg FILTER html %].
+ You tried to create or remove a tag with no name.
[% ELSIF error == "no_initial_bug_status" %]
[% title = "No Initial $terms.Bug Status" %]
@@ -1156,10 +1260,6 @@
Either no products have been defined to enter [% terms.bugs %] against or you have not
been given access to any.
- [% ELSIF error == "no_valid_action" %]
- [% title = "No valid action specified" %]
- Cannot edit [% field_descs.$field FILTER html %]: no valid action was specified.
-
[% ELSIF error == "number_not_numeric" %]
[% title = "Numeric Value Required" %]
The value '[% num FILTER html %]' in the
@@ -1178,22 +1278,30 @@
in the <em>[% field_descs.$field FILTER html %]</em> field
is less than the minimum allowable value of '[% min_num FILTER html %]'.
- [% ELSIF error == "object_name_not_specified" %]
- [% type = BLOCK %][% PROCESS object_name %][% END %]
+ [% ELSIF error == "object_not_specified" %]
+ [% type = BLOCK %][% INCLUDE object_name class = class %][% END %]
[% title = BLOCK %][% type FILTER ucfirst FILTER html %] Not
Specified[% END %]
You must select/enter a [% type FILTER html %].
[% ELSIF error == "object_does_not_exist" %]
- [% type = BLOCK %][% PROCESS object_name %][% END %]
+ [% type = BLOCK %][% INCLUDE object_name class = class %][% END %]
[% title = BLOCK %]Invalid [% type FILTER ucfirst FILTER html %][% END %]
- There is no [% type FILTER html %] named '[% name FILTER html %]'
+ There is no [% type FILTER html %]
+ [% IF id.defined %]
+ with the id '[% id FILTER html %]'
+ [% ELSE %]
+ named '[% name FILTER html %]'
+ [% END %]
[% IF product.defined %]
in the '[% product.name FILTER html %]' product
[% END %].
[% IF class == "Bugzilla::User" %]
Either you mis-typed the name or that user has not yet registered
for a [% terms.Bugzilla %] account.
+ [% ELSIF class == "Bugzilla::Keyword" %]
+ The legal keyword names are <a href="describekeywords.cgi">listed
+ here</a>.
[% END %]
[% ELSIF error == "old_password_incorrect" %]
@@ -1212,67 +1320,55 @@
[% title = "Passwords Don't Match" %]
The two passwords you entered did not match.
- [% ELSIF error == "password_too_long" %]
- [% title = "Password Too Long" %]
- The password must be no more than
- [%+ constants.USER_PASSWORD_MAX_LENGTH FILTER html %] characters long.
+ [% ELSIF error == "password_current_too_short" %]
+ [% title = "New Password Required" %]
+ Your password is currently less than
+ [%+ constants.USER_PASSWORD_MIN_LENGTH FILTER html %] characters long,
+ which is the new minimum length required for passwords.
+ You must <a href="token.cgi?a=reqpw&loginname=[% locked_user.email FILTER uri %]">
+ request a new password</a> in order to log in again.
[% ELSIF error == "password_too_short" %]
[% title = "Password Too Short" %]
The password must be at least
[%+ constants.USER_PASSWORD_MIN_LENGTH FILTER html %] characters long.
- [% ELSIF error == "patch_too_large" %]
- [% title = "File Too Large" %]
- The file you are trying to attach is [% filesize FILTER html %]
- kilobytes (KB) in size.
- Patches cannot be more than [% Param('maxpatchsize') %] KB in size.
- Try breaking your patch into several pieces.
+ [% ELSIF error == "password_not_complex" %]
+ [% title = "Password Fails Requirements" %]
+ [% passregex = Param('password_complexity') %]
+ The password must contain at least one:
+ <ul>
+ [% IF passregex.search('letters') %]
+ <li>UPPERCASE letter</li>
+ <li>lowercase letter</li>
+ [% END %]
+ [% IF passregex.search('numbers') %]
+ <li>digit</li>
+ [% END %]
+ [% IF passregex.search('specialchars') %]
+ <li>special character</li>
+ [% END %]
+ </ul>
[% ELSIF error == "product_access_denied" %]
- Either the product '[% product FILTER html %]' does not exist or
- you don't have access to it.
+ [% title = "Product Access Denied" %]
+ Either the product
+ [%+ IF id.defined %]
+ with the id [% id FILTER html %]
+ [% ELSE %]
+ '[% name FILTER html %]'
+ [% END %]
+ does not exist or you don't have access to it.
- [% ELSIF error == "product_doesnt_exist" %]
- [% title = "Specified Product Does Not Exist" %]
- The product '[% product FILTER html %]' does not exist.
-
- [% ELSIF error == "product_votes_per_bug_must_be_nonnegative" %]
- [% title = "Maximum Votes Must Be Non-negative" %]
- [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
- '[% maxvotesperbug FILTER html %]' is an invalid value for the
- <em>'Maximum Votes Per [% terms.Bug %]'</em> field, which should
- contain a non-negative number.
-
- [% ELSIF error == "product_votes_per_user_must_be_nonnegative" %]
- [% title = "Votes Per User Must Be Non-negative" %]
- [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
- '[% votesperuser FILTER html %]' is an invalid value for the
- <em>'Votes Per User'</em> field, which should contain a
- non-negative number.
-
- [% ELSIF error == "product_votes_to_confirm_must_be_nonnegative" %]
- [% title = "Votes To Confirm Must Be Non-negative" %]
- [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
- '[% votestoconfirm FILTER html %]' is an invalid value for the
- <em>'Votes To Confirm'</em> field, which should contain a
- non-negative number.
-
- [% ELSIF error == "product_cant_delete_description" %]
- [% title = "Cannot delete product description" %]
- [% admindocslinks = {'products.html' => 'Administering products'} %]
- Cannot delete the description for product
- '[% product FILTER html %]'.
-
- [% ELSIF error == "product_cant_delete_name" %]
- [% title = "Cannot delete product name" %]
- [% admindocslinks = {'products.html' => 'Administering products'} %]
- Cannot delete the product name for product '[% product FILTER html %]'.
+ [% ELSIF error == "product_illegal_group" %]
+ [% title = "Illegal Group" %]
+ [% group.name FILTER html %] is not an active [% terms.bug %] group
+ and so you cannot edit group controls for it.
[% ELSIF error == "product_name_already_in_use" %]
- [% title = "Product name already in use" %]
+ [% title = "Product name already exists" %]
[% admindocslinks = {'products.html' => 'Administering products'} %]
- The product name '[% product FILTER html %]' is already in use.
+ The product name '[% product FILTER html %]' already exists.
[% ELSIF error == "product_name_diff_in_case" %]
[% title = "Product name differs only in case" %]
@@ -1280,19 +1376,17 @@
The product name '[% product FILTER html %]' differs from existing
product '[% existing_product FILTER html %]' only in case.
+ [% ELSIF error == "product_name_too_long" %]
+ [% title = "Product name too long" %]
+ The name of a product is limited to [% constants.MAX_PRODUCT_SIZE FILTER html %]
+ characters. '[% name FILTER html %]' is too long ([% name.length %] characters).
+
[% ELSIF error == "product_must_define_defaultmilestone" %]
[% title = "Must define new default milestone" %]
[% admindocslinks = {'products.html' => 'Administering products',
'milestones.html' => 'About Milestones'} %]
- [% IF classification %]
- [% classification_url_part = BLOCK %]&classification=
- [%- classification FILTER url_quote %]
- [% END %]
- [% END %]
- You must <a href="editmilestones.cgi?action=add&product=
- [%- product FILTER url_quote %]
- [%- classification_url_part FILTER none %]">
- create the milestone '[% defaultmilestone FILTER html %]'</a> before
+ You must <a href="editmilestones.cgi?action=add&product=[% product FILTER uri %]">
+ create the milestone '[% milestone FILTER html %]'</a> before
it can be made the default milestone for product '[% product FILTER html %]'.
[% ELSIF error == "product_admin_denied" %]
@@ -1302,7 +1396,7 @@
[% ELSIF error == "product_blank_name" %]
[% title = "Blank Product Name Not Allowed" %]
[% admindocslinks = {'products.html' => 'Administering products'} %]
- You must enter a name for the new product.
+ You must enter a name for the product.
[% ELSIF error == "product_disabled" %]
[% title = BLOCK %]Product closed for [% terms.Bug %] Entry[% END %]
@@ -1327,31 +1421,22 @@
[% ELSIF error == "product_must_have_description" %]
[% title = "Product needs Description" %]
[% admindocslinks = {'products.html' => 'Administering products'} %]
- You must enter a description for product '[% product FILTER html %]'.
+ You must enter a description for this product.
[% ELSIF error == "product_must_have_version" %]
[% title = "Product needs Version" %]
[% admindocslinks = {'products.html' => 'Administering products',
'versions.html' => 'Administering versions'} %]
- You must enter a version for product '[% product FILTER html %]'.
+ You must enter a valid version to create a new product.
- [% ELSIF error == "product_not_specified" %]
- [% title = "No Product Specified" %]
- [% admindocslinks = {'products.html' => 'Administering products',
- 'components.html' => 'Administering components',
- 'milestones.html' => 'Administering milestones',
- 'versions.html' => 'Administering versions'} %]
- No product specified when trying to edit components, milestones, versions
- or product.
-
- [% ELSIF error == "query_name_exists" %]
- [% title = "Search Name Already In Use" %]
- The name <em>[% name FILTER html %]</em> is already used by another
- saved search. You first have to
- <a href="buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=
- [%- name FILTER url_quote %]&token=
- [% issue_hash_token([query_id, name]) FILTER url_quote %]">delete</a>
- it if you really want to use this name.
+ [% ELSIF error == "product_unknown_component" %]
+ [% title = "Unknown Component" %]
+ Product '[% product FILTER html %]' has no component
+ [% IF comp_id %]
+ with ID [% comp_id FILTER html %].
+ [% ELSE %]
+ named '[% comp FILTER html %]'.
+ [% END %]
[% ELSIF error == "query_name_missing" %]
[% title = "No Search Name Specified" %]
@@ -1360,21 +1445,45 @@
[% ELSIF error == "query_name_too_long" %]
[% title = "Query Name Too Long" %]
- The name of the query must be less than 64 characters long.
+ The name of the query must be less than [% constants.MAX_LEN_QUERY_NAME FILTER html %]
+ characters long.
+
+ [% ELSIF error == "quicksearch_invalid_query" %]
+ [% title = "Invalid Query" %]
+ Your query is invalid.
+ [% IF operators %]
+ The [% operators.shift FILTER html %] operator cannot be followed by
+ [%+ operators.shift FILTER html %].
+ [% ELSE %]
+ A query cannot start with AND or OR, nor can it end with AND, OR or NOT.
+ They are reserved operators and must be quoted if you want to look for
+ these strings.
+ [% END %]
+
+ [% ELSIF error == "quicksearch_unbalanced_quotes" %]
+ [% title = "Badly Formatted Query" %]
+ [% terms.Bugzilla %] is unable to parse your query correctly:
+ <em>[% string FILTER html %]</em>.<br>
+ If you use quotes to enclose strings, make sure both quotes are present.
+ If you really want to look for a quote in a string, type \" instead of ".
+ For instance, <em>"I'm so \"special\", really"</em> (with quotes) will be
+ interpreted as <em>I'm so "special", really</em>.
[% ELSIF error == "quicksearch_unknown_field" %]
- [% title = "Unknown QuickSearch Field" %]
- [% IF fields.unique.size == 1 %]
- Field <code>[% fields.first FILTER html %]</code> is not a known field.
- [% ELSE %]
- Fields
- [% FOREACH field = fields.unique.sort %]
- <code>[% field FILTER html %]</code>
- [% ', ' UNLESS loop.last() %]
- [% END %]
- are not known fields.
+ [% title = "QuickSearch Error" %]
+ There is a problem with your search:
+ [% FOREACH field = unknown %]
+ <p><code>[% field FILTER html %]</code> is not a valid field name.</p>
[% END %]
- The legal field names are <a href="page.cgi?id=quicksearchhack.html">listed here</a>.
+ [% FOREACH field = ambiguous.keys %]
+ <p><code>[% field FILTER html %]</code> matches more than one field:
+ [%+ ambiguous.${field}.join(', ') FILTER html %]</p>
+ [% END %]
+
+ [% IF unknown.size %]
+ <p>The legal field names are
+ <a href="page.cgi?id=quicksearch.html#fields">listed here</a>.</p>
+ [% END %]
[% ELSIF error == "reassign_to_empty" %]
[% title = "Illegal Reassignment" %]
@@ -1386,10 +1495,24 @@
To file this [% terms.bug %], you must first choose a component.
If necessary, just guess.
+ [% ELSIF error == "relationship_loop_single" %]
+ [% title = "Relationship Loop Detected" %]
+ [% field_descs.$field_name FILTER html %]
+ for [% terms.bug %] [%+ bug_id FILTER html %]
+ has a circular dependency on [% terms.bug %] [%+ dep_id FILTER html %].
+
+ [% ELSIF error == "request_queue_group_invalid" %]
+ The group field <em>[% group FILTER html %]</em> is invalid.
+
[% ELSIF error == "require_new_password" %]
[% title = "New Password Needed" %]
You cannot change your password without choosing a new one.
+ [% ELSIF error == "required_field" %]
+ [% title = "Field Must Be Set" %]
+ A value must be set for the '[% field_descs.${field.name} FILTER html %]'
+ field.
+
[% ELSIF error == "require_summary" %]
[% title = "Summary Needed" %]
You must enter a summary for this [% terms.bug %].
@@ -1415,6 +1538,16 @@
and the "matches" search can only be used with the "content"
field.
+ [% ELSIF error == "search_field_operator_invalid" %]
+ [% terms.Bugzilla %] does not support using the
+ "[%+ field_descs.$field FILTER html %]" ([% field FILTER html %])
+ field with the "[% search_descs.$operator FILTER html %]"
+ ([% operator FILTER html %]) search type.
+
+ [% ELSIF error == "see_also_self_reference" %]
+ You cannot put this [% terms.bug %] into its own
+ [%+ field_descs.see_also FILTER html %] field.
+
[% ELSIF error == "series_already_exists" %]
[% title = "Series Already Exists" %]
[% docslinks = {'reporting.html' => 'Reporting'} %]
@@ -1423,36 +1556,33 @@
[%+ series.name FILTER html %]</em>
already exists.
- [% ELSIF error == "sidebar_supports_mozilla_only" %]
- Sorry - sidebar.cgi currently only supports Mozilla based web browsers.
- <a href="http://www.mozilla.org">Upgrade today</a>. :-)
-
[% ELSIF error == "still_unresolved_bugs" %]
- [% IF dependency_count == 1 %]
- [% terms.Bug %]# <a href="show_bug.cgi?id=[% dependencies.0.bug_id FILTER none %]">[% dependencies.0.bug_id FILTER none %]</a>
- still has [% dependencies.0.dependencies FILTER html %] unresolved
- [% IF dependencies.0.dependencies == 1 %]
- dependency
- [% ELSE %]
- dependencies
- [% END %]. Show
- <a href="showdependencytree.cgi?id=[% dependencies.0.bug_id FILTER none %]&hide_resolved=1">Dependency
- Tree</a>.
+ [% title = "Unresolved Dependencies" %]
+ [% terms.Bug %] [%+ bug_id FILTER bug_link(bug_id) FILTER none %]
+ has [% dep_count FILTER none %] unresolved
+ [% IF dep_count == 1 %]
+ dependency
[% ELSE %]
- There are [% dependency_count FILTER none %] open [% terms.bugs %] which
- have unresolved dependencies.
- <br>
- [% FOREACH bug = dependencies %]
- [% terms.Bug %]# <a href="show_bug.cgi?id=[% bug.bug_id FILTER none %]">[% bug.bug_id FILTER none %]</a>
- has [% bug.dependencies FILTER html %] open
- [% IF bug.dependencies == 1 %]
- dependency.
- [% ELSE %]
- dependencies.
- [% END %]
- (<a href="showdependencytree.cgi?id=[% bug.bug_id FILTER none %]&hide_resolved=1">Dependency
- Tree</a>)<br>
- [% END %]
+ dependencies
+ [% END %].
+ They must either be resolved or removed from the
+ "[% field_descs.dependson FILTER html %]" field before you can resolve
+ this [% terms.bug %] as [% display_value("resolution", "FIXED") FILTER html %].
+
+ [% ELSIF error == "sudo_invalid_cookie" %]
+ [% title = "Invalid Sudo Cookie" %]
+ Your sudo cookie is invalid. Either it expired or you didn't start
+ a sudo session correctly. Refresh the page or load another page
+ to continue what you are doing as yourself.
+
+ [% ELSIF error == "sudo_illegal_action" %]
+ [% title = "Impersonation Not Authorized" %]
+ [% IF NOT sudoer.in_group("bz_sudoers") %]
+ You are not allowed to impersonate users.
+ [% ELSIF target_user AND target_user.in_group("bz_sudo_protect") %]
+ You are not allowed to impersonate [% target_user.identity FILTER html %].
+ [% ELSE %]
+ The user you tried to impersonate doesn't exist.
[% END %]
[% ELSIF error == "sudo_in_progress" %]
@@ -1463,35 +1593,25 @@
[% ELSIF error == "sudo_password_required" %]
[% title = "Password Required" %]
Your [% terms.Bugzilla %] password is required to begin a sudo
- session. Please <a href="relogin.cgi?action=prepare-sudo&target_login=
- [%- target_login FILTER html %]&reason=
- [%- reason FILTER html %]">go back</a> and enter your password.
+ session. Please <a href="relogin.cgi?action=prepare-sudo&target_login=
+ [%- target_login FILTER uri %]&reason=
+ [%- reason FILTER uri %]">go back</a> and enter your password.
[% ELSIF error == "sudo_preparation_required" %]
[% title = "Preparation Required" %]
You may not start a sudo session directly. Please
- <a href="relogin.cgi?action=prepare-sudo&target_login=
- [%- target_login FILTER html %]&reason=
- [%- reason FILTER html %]">start your session normally</a>.
+ <a href="relogin.cgi?action=prepare-sudo&target_login=
+ [%- target_login FILTER uri %]&reason=
+ [%- reason FILTER uri %]">start your session normally</a>.
[% ELSIF error == "sudo_protected" %]
[% title = "User Protected" %]
The user [% login FILTER html %] may not be impersonated by sudoers.
- [% ELSIF error == "too_many_votes_for_bug" %]
- [% title = "Illegal Vote" %]
- [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
- You may only use at most [% max FILTER html %] votes for a single
- [%+ terms.bug %] in the
- <tt>[% product FILTER html %]</tt> product, but you are trying to
- use [% votes FILTER html %].
-
- [% ELSIF error == "too_many_votes_for_product" %]
- [% title = "Illegal Vote" %]
- [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
- You tried to use [% votes FILTER html %] votes in the
- <tt>[% product FILTER html %]</tt> product, which exceeds the maximum of
- [%+ max FILTER html %] votes for this product.
+ [% ELSIF error == "tag_name_too_long" %]
+ [% title = "Tag Name Too Long" %]
+ The tag name must be less than [% constants.MAX_LEN_QUERY_NAME FILTER html %]
+ characters long.
[% ELSIF error == "token_does_not_exist" %]
[% title = "Token Does Not Exist" %]
@@ -1508,10 +1628,12 @@
[% END %]
token too recently to request another. Please wait a while and try again.
- [% ELSIF error == "unknown_keyword" %]
- [% title = "Unknown Keyword" %]
- <code>[% keyword FILTER html %]</code> is not a known keyword.
- The legal keyword names are <a href="describekeywords.cgi">listed here</a>.
+ [% ELSIF error == "unknown_action" %]
+ [% IF action %]
+ Unknown action [% action FILTER html %]!
+ [% ELSE %]
+ I could not figure out what you wanted to do.
+ [% END %]
[% ELSIF error == "unknown_tab" %]
[% title = "Unknown Tab" %]
@@ -1533,10 +1655,6 @@
version! You must reassign those [% terms.bugs %] to another version
before you can delete this one.
- [% ELSIF error == "version_not_specified" %]
- [% title = "No Version Specified" %]
- No version specified when trying to edit versions.
-
[% ELSIF error == "users_deletion_disabled" %]
[% title = "Deletion not activated" %]
[% admindocslinks = {'useradmin.html' => 'User administration'} %]
@@ -1553,6 +1671,16 @@
for at least one component.
For this reason, you cannot delete the account at this time.
+ [% ELSIF error == "user_access_by_id_denied" %]
+ [% title = "User Access By Id Denied" %]
+ Logged-out users cannot use the "ids" argument to this function
+ to access any user information.
+
+ [% ELSIF error == "user_access_by_match_denied" %]
+ [% title = "User-Matching Denied" %]
+ Logged-out users cannot use the "match" argument to this function
+ to access any user information.
+
[% ELSIF error == "user_login_required" %]
[% title = "Login Name Required" %]
[% admindocslinks = {'useradmin.html' => 'User administration'} %]
@@ -1563,10 +1691,16 @@
<tt>[% name FILTER html %]</tt> does not exist or you are not allowed
to see that user.
- [% ELSIF error == "votes_must_be_nonnegative" %]
- [% title = "Votes Must Be Non-negative" %]
- [% admindocslinks = {'voting.html' => 'Setting up the voting feature'} %]
- Only use non-negative numbers for your [% terms.bug %] votes.
+ [% ELSIF error == "user_match_too_many" %]
+ [% title = "No Conclusive Match" %]
+ [% terms.Bugzilla %] cannot make a conclusive match for one or more
+ of the names and/or email addresses you entered for
+ the [% fields.join(', ') FILTER html %] field(s).
+
+ [% ELSIF error == "user_not_insider" %]
+ [% title = "User Not In Insidergroup" %]
+ Sorry, but you are not allowed to (un)mark comments or attachments
+ as private.
[% ELSIF error == "wrong_token_for_cancelling_email_change" %]
[% title = "Wrong Token" %]
@@ -1584,6 +1718,16 @@
[% title = "Wrong Token" %]
That token cannot be used to create a user account.
+ [% ELSIF error == "xmlrpc_invalid_value" %]
+ "[% value FILTER html %]" is not a valid value for a
+ <[% type FILTER html %]> field. (See the XML-RPC specification
+ for details.)
+
+ [% ELSIF error == "xmlrpc_illegal_content_type" %]
+ When using XML-RPC, you cannot send data as
+ [%+ content_type FILTER html %]. Only text/xml
+ and application/xml are allowed.
+
[% ELSIF error == "zero_length_file" %]
[% title = "File Is Empty" %]
The file you are trying to attach is empty, does not exist, or you don't
@@ -1593,6 +1737,14 @@
[% title = "Illegal User ID" %]
User ID '[% userid FILTER html %]' is not valid integer.
+ [% ELSIF error == "extern_id_exists" %]
+ [% title = "Account Already Exists" %]
+ There is already an account
+ [% IF existing_login_name %]
+ ([% existing_login_name FILTER html %])
+ [% END %]
+ with the External Login ID "[% extern_id FILTER html %]".
+
[% ELSE %]
[%# Try to find hooked error messages %]
@@ -1653,11 +1805,11 @@
<p>
Alternatively, you can
<a href="buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=
- [% namedcmd FILTER url_quote %]">forget</a>
+ [% namedcmd FILTER uri %]">forget</a>
[% FOREACH q = Bugzilla.user.queries %]
[% IF q.name == namedcmd %]
- or <a href="query.cgi?[% q.url FILTER html %]">edit</a>
+ or <a href="query.cgi?[% q.url FILTER uri %]">edit</a>
[% END %]
[% END %]
@@ -1668,8 +1820,14 @@
[% PROCESS global/footer.html.tmpl %]
[% BLOCK object_name %]
- [% IF class == "Bugzilla::User" %]
+ [% IF class == "Bugzilla::Attachment" %]
+ attachment
+ [% ELSIF class == "Bugzilla::User" %]
user
+ [% ELSIF class == "Bugzilla::Classification" %]
+ classification
+ [% ELSIF class == "Bugzilla::Product" %]
+ product
[% ELSIF class == "Bugzilla::Component" %]
component
[% ELSIF class == "Bugzilla::Version" %]
@@ -1678,5 +1836,23 @@
milestone
[% ELSIF class == "Bugzilla::Status" %]
status
+ [% ELSIF class == "Bugzilla::Flag" %]
+ flag
+ [% ELSIF class == "Bugzilla::FlagType" %]
+ flagtype
+ [% ELSIF class == "Bugzilla::Field" %]
+ field
+ [% ELSIF class == "Bugzilla::Group" %]
+ group
+ [% ELSIF class == "Bugzilla::Keyword" %]
+ keyword
+ [% ELSIF class == "Bugzilla::Search::Recent" %]
+ recent search
+ [% ELSIF class == "Bugzilla::Search::Saved" %]
+ saved search
+ [% ELSIF ( matches = class.match('^Bugzilla::Field::Choice::(.+)') ) %]
+ [% SET field_name = matches.0 %]
+ [% field_descs.$field_name FILTER html %]
[% END %]
+ [% Hook.process('end_object_name', 'global/user-error.html.tmpl') %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/user.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/user.html.tmpl
new file mode 100644
index 0000000..df902b4
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/global/user.html.tmpl
@@ -0,0 +1,39 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Daniel Brooks.
+ # Portions created by the Initial Developer are Copyright (C) 2007
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Daniel Brooks <db48x@db48x.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # who: A Bugzilla::User object that we are going to represent.
+ #%]
+
+<span class="vcard">
+ [% FILTER collapse %]
+ [% IF user.id %]
+ <a class="email" href="mailto:[% who.email FILTER html %]"
+ title="[% who.identity FILTER html %]">
+ [%- END -%]
+ [% IF who.name %]
+ <span class="fn">[% who.name FILTER html %]</span>
+ [% ELSE %]
+ [% who.login FILTER email FILTER html %]
+ [% END %]
+ [% '</a>' IF user.id %]
+ [% END %]
+</span>
diff --git a/Websites/bugs.webkit.org/template/en/default/global/userselect.html.tmpl b/Websites/bugs.webkit.org/template/en/default/global/userselect.html.tmpl
index 35075ef..1d03950 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/userselect.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/userselect.html.tmpl
@@ -12,32 +12,42 @@
#
# Contributor(s): Byron Jones <bugzilla@glob.com.au>
# Frédéric Buclin <LpSolit@gmail.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
+ # Reed Loden <reed@reedloden.com>
#%]
[%# INTERFACE:
# name: mandatory; field name
# id: optional; field id
# value: optional; default field value/selection
+ # classes: optional; an array of classes to be added
# onchange: optional; onchange attribute value
# disabled: optional; if true, the field is disabled
# accesskey: optional, input only; accesskey attribute value
# size: optional, input only; size attribute value
- # emptyok: optional, select only; if true, prepend menu option to start of select
+ # emptyok: optional, select only; if true, prepend menu option for "" to start of select
+ # hyphenok: optional, select only; if true, prepend menu option for "-" to start of select
# multiple: optional, do multiselect box, value is size (height) of box
# custom_userlist: optional, specify a limited list of users to use
+ # field_title: optional, extra information to display as a tooltip
#%]
[% IF Param("usemenuforusers") %]
<select name="[% name FILTER html %]"
[% IF id %] id="[% id FILTER html %]" [% END %]
+ [% IF classes %] class="[% classes.join(' ') FILTER html %]" [% END %]
[% IF onchange %] onchange="[% onchange FILTER html %]" [% END %]
[% IF disabled %] disabled="[% disabled FILTER html %]" [% END %]
[% IF accesskey %] accesskey="[% accesskey FILTER html %]" [% END %]
[% IF multiple %] multiple="multiple" size="[% multiple FILTER html %]" [% END %]
+ [% IF field_title %] title="[% field_title FILTER html %]" [% END %]
>
[% IF emptyok %]
<option value=""></option>
[% END %]
+ [% IF hyphenok %]
+ <option value="-">-</option>
+ [% END %]
[% UNLESS custom_userlist %]
[% custom_userlist = user.get_userlist %]
@@ -69,15 +79,31 @@
[% END %]
</select>
[% ELSE %]
-<input
- name="[% name FILTER html %]"
- value="[% value FILTER html %]"
- [% IF onchange %] onchange="[% onchange FILTER html %]" [% END %]
- [% IF disabled %] disabled="[% disabled FILTER html %]" [% END %]
- [% IF accesskey %] accesskey="[% accesskey FILTER html %]" [% END %]
- [% IF size %] size="[% size FILTER html %]" [% END %]
- [% IF id %] id="[% id FILTER html %]" [% END %]
->
+ [% IF feature_enabled('jsonrpc') && Param('ajax_user_autocompletion') && id %]
+ <div id="[% id FILTER html %]_autocomplete"
+ [% IF classes %] class="[% classes.join(' ') FILTER html %]" [% END %]>
+ [% END %]
+ <input
+ name="[% name FILTER html %]"
+ value="[% value FILTER html %]"
+ [% IF classes %] class="[% classes.join(' ') FILTER html %]" [% END %]
+ [% IF onchange %] onchange="[% onchange FILTER html %]" [% END %]
+ [% IF disabled %] disabled="[% disabled FILTER html %]" [% END %]
+ [% IF accesskey %] accesskey="[% accesskey FILTER html %]" [% END %]
+ [% IF field_title %] title="[% field_title FILTER html %]" [% END %]
+ [% IF size %] size="[% size FILTER html %]" [% END %]
+ [% IF id %] id="[% id FILTER html %]" [% END %]
+ >
+ [% IF feature_enabled('jsonrpc') && Param('ajax_user_autocompletion') && id %]
+ <div id="[% id FILTER html %]_autocomplete_container"></div>
+ </div>
+ <script type="text/javascript">
+ if( typeof(YAHOO.bugzilla.userAutocomplete) !== 'undefined'
+ && YAHOO.bugzilla.userAutocomplete != null){
+ YAHOO.bugzilla.userAutocomplete.init( "[% id FILTER js %]",
+ "[% id FILTER js %]_autocomplete_container"
+ [% IF multiple %], true[% END%]);
+ }
+ </script>
+ [% END %]
[% END %]
-
-
diff --git a/Websites/bugs.webkit.org/template/en/default/global/value-descs.js.tmpl b/Websites/bugs.webkit.org/template/en/default/global/value-descs.js.tmpl
new file mode 100644
index 0000000..20d023b
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/global/value-descs.js.tmpl
@@ -0,0 +1,33 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2010
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS "global/value-descs.none.tmpl" %]
+
+BUGZILLA.value_descs = {
+ [% FOREACH vd_field = value_descs.keys %]
+ [% vd_field FILTER js %]: {
+ [% FOREACH vd_value = value_descs.${vd_field}.keys %]
+ '[% vd_value FILTER js %]':
+ '[% value_descs.${vd_field}.${vd_value} FILTER js %]'
+ [%~ ',' UNLESS loop.last %]
+ [% END %]
+ }[% ',' UNLESS loop.last %]
+ [% END %]
+};
diff --git a/Websites/bugs.webkit.org/template/en/default/global/value-descs.none.tmpl b/Websites/bugs.webkit.org/template/en/default/global/value-descs.none.tmpl
new file mode 100644
index 0000000..56c90ac
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/global/value-descs.none.tmpl
@@ -0,0 +1,36 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is BugzillaSource, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2011
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# You can use this hash to localize (translate) the values displayed
+ # for drop-down and multiple-select fields. Lines starting with "#"
+ # are comments.
+ #%]
+[% value_descs = {
+ "bug_status" => {
+ # "UNCONFIRMED" => "UNCO",
+ # "CONFIRMED" => "ITSABUG",
+ },
+
+ "resolution" => {
+ "" => "---",
+ # "FIXED" => "NO LONGER AN ISSUE",
+ # "WORKSFORME" => "NOTMYPROBLEM!",
+ },
+} %]
diff --git a/Websites/bugs.webkit.org/template/en/default/global/variables.none.tmpl b/Websites/bugs.webkit.org/template/en/default/global/variables.none.tmpl
index 4fa6089..faf1a54 100644
--- a/Websites/bugs.webkit.org/template/en/default/global/variables.none.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/global/variables.none.tmpl
@@ -32,6 +32,7 @@
"Bug" => "Bug",
"abug" => "a bug",
"Abug" => "A bug",
+ "aBug" => "a Bug",
"ABug" => "A Bug",
"bugs" => "bugs",
"Bugs" => "Bugs",
@@ -39,3 +40,5 @@
"Bugzilla" => "Bugzilla"
}
%]
+
+[% Hook.process("end") %]
diff --git a/Websites/bugs.webkit.org/template/en/default/index.html.tmpl b/Websites/bugs.webkit.org/template/en/default/index.html.tmpl
index 9e0ec8a..5b9237a 100644
--- a/Websites/bugs.webkit.org/template/en/default/index.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/index.html.tmpl
@@ -19,6 +19,7 @@
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Jacob Steenhagen <jake@bugzilla.org>
# Vitaly Harisov <vitaly@rathedg.com>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
#%]
[%# INTERFACE:
@@ -33,27 +34,43 @@
header = "Main Page"
header_addl_info = "version $constants.BUGZILLA_VERSION"
style_urls = [ 'skins/standard/index.css' ]
- onload = 'document.forms[\'f\'].quicksearch.focus();'
%]
<script type="text/javascript">
<!--
-function addSidebar() {
- if ((typeof window.sidebar == "object") && (typeof window.sidebar.addPanel == "function"))
- {
- var sidebarname=window.location.host;
- if (!/bug/i.test(sidebarname))
- sidebarname="[% terms.Bugzilla %] "+sidebarname;
- window.sidebar.addPanel (sidebarname, "[% urlbase FILTER html %]sidebar.cgi", "");
+function onLoadActions() {
+ quicksearchHelpText('quicksearch_main', 'show');
+ if( window.external.AddSearchProvider ){
+ YAHOO.util.Dom.removeClass('quicksearch_plugin', 'bz_default_hidden');
}
- else
- {
- var rv = window.confirm ("Your browser does not support the sidebar extension. " + "Would you like to upgrade now?");
- if (rv)
- document.location.href = "http://www.mozilla.org/";
+ document.getElementById('quicksearch_top').focus();
+}
+var quicksearch_message = "Enter [% terms.abug %] # or some search terms";
+
+function checkQuicksearch( form ) {
+ if (form.quicksearch.value == '' || form.quicksearch.value == quicksearch_message ) {
+ alert('Please enter one or more search terms first.');
+ return false;
+ }
+ return true;
+}
+
+function quicksearchHelpText(el_id, action){
+ var el = document.getElementById(el_id);
+ if ( action == "show") {
+ if( el.value == "" ) {
+ el.value = quicksearch_message
+ YAHOO.util.Dom.addClass(el, "quicksearch_help_text");
+ }
+ } else {
+ if( el.value == quicksearch_message ) {
+ el.value = "";
+ YAHOO.util.Dom.removeClass(el, "quicksearch_help_text");
+ }
}
}
+YAHOO.util.Event.onDOMReady(onLoadActions);
//-->
</script>
@@ -73,86 +90,103 @@
<p class="notice">This message is only shown to logged in users with admin privs.
You can configure this notification from the
- <a href="editparams.cgi?section=core#upgrade_notification">Parameters</a> page.</p>
- [% ELSIF release.error == "missing_package" %]
- <p>Missing package '[% release.package FILTER html %]'. This package is required to
- <a href="editparams.cgi?section=core#upgrade_notification">notify you about new releases</a>.</p>
+ <a href="editparams.cgi?section=general#upgrade_notification_desc">Parameters</a> page.</p>
[% ELSIF release.error == "cannot_download" %]
- <p>The local XML file '[% release.xml_file FILTER html %]' cannot be created.
- Please make sure the web server can write in this directory and that you can access
+ <p>The remote file <a href="[% constants.REMOTE_FILE FILTER html %]">
+ [%~ constants.REMOTE_FILE FILTER html %]</a> cannot be downloaded
+ (reason: [% release.reason FILTER html %]).<br>
+ Either the remote server is temporarily unavailable, or your web server cannot access
the web. If you are behind a proxy, set the
- <a href="editparams.cgi?section=core#proxy_url">proxy_url</a> parameter correctly.</p>
+ <a href="editparams.cgi?section=advanced#proxy_url_desc">proxy_url</a> parameter correctly.</p>
+ [% ELSIF release.error == "no_write" %]
+ <p>The local XML file '[% constants.LOCAL_FILE FILTER html %]' cannot be created
+ (reason: [% release.reason FILTER html %]).<br>
+ Please make sure the web server can write into this directory.
[% ELSIF release.error == "no_update" %]
- <p>The local XML file '[% release.xml_file FILTER html %]' cannot be updated.
+ <p>The local XML file '[% constants.LOCAL_FILE FILTER html %]' cannot be updated.
Please make sure the web server can edit this file.</p>
[% ELSIF release.error == "no_access" %]
- <p>The local XML file '[% release.xml_file FILTER html %]' cannot be read.
+ <p>The local XML file '[% constants.LOCAL_FILE FILTER html %]' cannot be read.
Please make sure this file has the correct rights set on it.</p>
[% ELSIF release.error == "corrupted" %]
- <p>The local XML file '[% release.xml_file FILTER html %]' has an invalid XML format.
+ <p>The local XML file '[% constants.LOCAL_FILE FILTER html %]' has an invalid XML format.
Please delete it and try accessing this page again.</p>
[% ELSIF release.error == "unknown_parameter" %]
<p>'[% Param("upgrade_notification") FILTER html %]' is not a valid notification
parameter. Please check this parameter in the
- <a href="editparams.cgi?section=core#upgrade_notification">Parameters</a> page.</p>
+ <a href="editparams.cgi?section=general#upgrade_notification_desc">Parameters</a> page.</p>
[% END %]
</div>
[% END %]
<div id="page-index">
- <div class="intro"></div>
+ <table>
+ <tr>
+ <td>
+ <h1 id="welcome"> Welcome to [% terms.Bugzilla %]</h1>
+ <div class="intro">[% Hook.process('intro') %]</div>
- <p>Welcome to [% terms.Bugzilla %]. To see what's new in this version
- of [% terms.Bugzilla %], see the
- <a href="page.cgi?id=release-notes.html">release notes</a>!
- You may also want to read the
- <a href="[% docs_urlbase FILTER html %]using.html">
- [%- terms.Bugzilla %] User's Guide</a> to find out more about
- [%+ terms.Bugzilla %] and how to use it.</p>
+ <div class="bz_common_actions">
+ <ul>
+ <li>
+ <a id="enter_bug" href="enter_bug.cgi"><span>File
+ [%= terms.aBug %]</span></a>
+ </li>
+ <li>
+ <a id="query" href="query.cgi"><span>Search</span></a>
+ </li>
+ <li>
+ <a id="account"
+ [% IF user.id %]
+ href="userprefs.cgi"><span>User Preferences</span></a>
+ [% ELSIF Param('createemailregexp')
+ && user.authorizer.user_can_create_account
+ %]
+ href="createaccount.cgi"><span>Open a New Account</span></a>
+ [% ELSE %]
+ href="?GoAheadAndLogIn=1"><span>Log In</span></a>
+ [% END %]
+ </li>
+ </ul>
+ </div>
- <p>Most common actions:</p>
- <ul>
- <li id="query"><a href="query.cgi">Search existing [% terms.bug %] reports</a></li>
- <li id="enter-bug"><a href="enter_bug.cgi">Enter a new [% terms.bug %] report</a></li>
- <li id="report"><a href="report.cgi">Summary reports and charts</a></li>
-[% IF user.id %]
- <li id="userprefs"><a href="userprefs.cgi">Change password or user preferences</a></li>
- [% IF user.authorizer.can_logout %]
- <li id="logout"><a href="relogin.cgi">Log out [% user.login FILTER html %]</a></li>
- [% END %]
-[% ELSIF user.authorizer.can_login %]
- </ul>
- [% PROCESS "account/auth/login-small.html.tmpl" %]
- <ul>
- [% IF Param('createemailregexp') && user.authorizer.user_can_create_account %]
- <li id="account"><a href="createaccount.cgi">Open a new [% terms.Bugzilla %] account</a></li>
- [% END %]
-[% END %]
- <li id="sidebar"><a href="javascript:addSidebar()">Add to Sidebar</a> (requires a Mozilla browser like Mozilla Firefox)</li>
- <li id="quick_search_plugin">
- <a href="javascript:window.external.AddSearchProvider('[% urlbase FILTER html %]search_plugin.cgi')">Install
- the Quick Search plugin</a> (requires Firefox 2 or Internet Explorer 7)
- </li>
-
-
- [%# List items of links to more things users can do on this installation. %]
- [% Hook.process("links") %]
-
- </ul>
-
- <form id="f" name="f" action="buglist.cgi" method="get"
- onsubmit="if (this.quicksearch.value == '')
- { alert('Please enter one or more search terms first.');
- return false; } return true;">
- <div>
- <p>Enter [% terms.abug %] # or some search terms:</p>
- <input id="quicksearch" type="text" name="quicksearch">
- <input id="find" type="submit" value="Find">
- <a href="page.cgi?id=quicksearch.html">[Help]</a>
- </div>
- </form>
-
- <div class="outro"></div>
+ <form id="quicksearchForm" name="quicksearchForm" action="buglist.cgi"
+ onsubmit="return checkQuicksearch(this);">
+ <div>
+ <input id="quicksearch_main" type="text" name="quicksearch"
+ title="Quick Search"
+ onfocus="quicksearchHelpText(this.id, 'hide');"
+ onblur="quicksearchHelpText(this.id, 'show');"
+ >
+ <input id="find" type="submit" value="Quick Search">
+ <ul class="additional_links" id="quicksearch_links">
+ <li>
+ <a href="page.cgi?id=quicksearch.html">Quick Search help</a>
+ </li>
+ <li class="bz_default_hidden" id="quicksearch_plugin">
+ |
+ <a href="javascript:window.external.AddSearchProvider('[% urlbase FILTER html %]search_plugin.cgi')">
+ Install the Quick Search plugin
+ </a>
+ </li>
+ </ul>
+ <ul class="additional_links">
+ <li>
+ <a href="[% docs_urlbase FILTER html %]using.html">
+ [%- terms.Bugzilla %] User's Guide</a>
+ </li>
+ <li>
+ |
+ <a href="page.cgi?id=release-notes.html">Release Notes</a>
+ </li>
+ [% Hook.process('additional_links') %]
+ </ul>
+ </div>
+ </form>
+ <div class="outro">[% Hook.process('outro') %]</div>
+ </td>
+ </tr>
+ </table>
</div>
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/list/change-columns.html.tmpl b/Websites/bugs.webkit.org/template/en/default/list/change-columns.html.tmpl
index 88ae478..ff7e5d3 100644
--- a/Websites/bugs.webkit.org/template/en/default/list/change-columns.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/list/change-columns.html.tmpl
@@ -16,12 +16,15 @@
# Rights Reserved.
#
# Contributor(s): Dave Lawrence <dkl@redhat.com>
+ # Pascal Held <paheld@gmail.com>
#%]
[% PROCESS global/variables.none.tmpl %]
[% PROCESS global/header.html.tmpl
title = "Change Columns"
+ javascript_urls = "js/change-columns.js"
+ onload = "initChangeColumns()"
%]
<p>
@@ -31,21 +34,76 @@
[% PROCESS "global/field-descs.none.tmpl" %]
[% field_descs.short_short_desc = "Summary (first 60 characters)" %]
-[% field_descs.short_desc = "Full Summary" %]
-[% field_descs.assigned_to_realname = "Assignee Realname" %]
-[% field_descs.reporter_realname = "Reporter Realname" %]
-[% field_descs.qa_contact_realname = "QA Contact Realname" %]
+[% field_descs.short_desc = "Summary (Full)" %]
+[% field_descs.assigned_to_realname = "$field_descs.assigned_to Real Name" %]
+[% field_descs.reporter_realname = "$field_descs.reporter Real Name" %]
+[% field_descs.qa_contact_realname = "$field_descs.qa_contact Real Name" %]
-<form action="colchange.cgi">
+[%# Create a mapping of field descriptions to field names, so that
+ # the "Available Columns" list can be sorted alphabetically by
+ # field description.
+ #%]
+[% SET available_columns = {} %]
+[% FOREACH column = columns.keys %]
+ [% NEXT IF collist.contains(column) %]
+ [%# We lowecase the keys so that the sort happens case-insensitively. %]
+ [% SET column_desc = field_descs.$column || column FILTER lower %]
+ [% available_columns.$column_desc = column %]
+[% END %]
+
+<form name="changecolumns" action="colchange.cgi" onsubmit="change_submit();">
<input type="hidden" name="rememberedquery" value="[% buffer FILTER html %]">
- [% FOREACH column = masterlist %]
- <input type="checkbox" id="[% column %]" name="column_[% column %]"
- [%+ "checked='checked'" IF lsearch(collist, column) != -1 %]>
- <label for="[% column %]">
- [% (field_descs.${column} || column) FILTER html %]
- </label>
- <br>
- [% END %]
+ <table>
+ <tr>
+ <th><div id="avail_header" class="bz_default_hidden">Available Columns</div></th>
+ <th></th>
+ <th colspan="2">Selected Columns</th>
+ </tr>
+ <tr>
+ <td>
+ <select name="available_columns" id="available_columns"
+ size="15" multiple="multiple" onchange="updateView();"
+ class="bz_default_hidden">
+ </select>
+ </td>
+ <td>
+ <button type="button" id="select_button" name="select"
+ class="bz_default_hidden arrow_button"
+ onclick="move_select()">→</button>
+ <br><br>
+ <button type="button" id="deselect_button" name="deselect"
+ class="bz_default_hidden arrow_button"
+ onclick="move_deselect()">←</button>
+ </td>
+ <td>
+ <select name="selected_columns" id="selected_columns"
+ size="15" multiple="multiple" onchange="updateView();">
+ [% FOREACH column = collist %]
+ <option value="[% column FILTER html %]" selected="selected">
+ [% (field_descs.${column} || column) FILTER html %]
+ </option>
+ [% END %]
+ [% FOREACH key = available_columns.keys.sort %]
+ [% SET column = available_columns.$key %]
+ <option value="[% column FILTER html %]">
+ [%# Don't display the lower-cased column description,
+ # display the correct-case one. %]
+ [% (field_descs.$column || column) FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ </td>
+ <td>
+ <button type="button" id="up_button" name="up"
+ class="bz_default_hidden arrow_button"
+ onclick="move_up()">↑</button>
+ <br><br>
+ <button type="button" id="down_button" name="down"
+ class="bz_default_hidden arrow_button"
+ onclick="move_down()">↓</button>
+ </td>
+ </tr>
+ </table>
<p>
<input id="nosplitheader" type="radio" name="splitheader" value="0"
@@ -66,11 +124,16 @@
<p>
<input type="hidden" name="saved_search"
value="[% saved_search.id FILTER html%]" >
+ <input type="hidden" name="token"
+ value="[% issue_hash_token([saved_search.id, saved_search.name]) FILTER html %]">
<input type="checkbox" id="save_columns_for_search" checked="checked"
name="save_columns_for_search" value="1">
<label for="save_columns_for_search">Save this column list only
for search '[% saved_search.name FILTER html %]'</label>
</p>
+ [% ELSE %]
+ <input type="hidden" name="token"
+ value="[% issue_hash_token(['default-list']) FILTER html %]">
[% END %]
<p>
diff --git a/Websites/bugs.webkit.org/template/en/default/list/edit-multiple.html.tmpl b/Websites/bugs.webkit.org/template/en/default/list/edit-multiple.html.tmpl
index 3e582b3..92e578e 100644
--- a/Websites/bugs.webkit.org/template/en/default/list/edit-multiple.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/list/edit-multiple.html.tmpl
@@ -19,6 +19,7 @@
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
# Guy Pyrzak <guy.pyrzak@gmail.com>
+ # Reed Loden <reed@reedloden.com>
#%]
[% PROCESS global/variables.none.tmpl %]
@@ -28,12 +29,15 @@
<input type="hidden" name="token" value="[% token FILTER html %]">
<script type="text/javascript">
- var numelements = document.forms.changeform.elements.length;
function SetCheckboxes(value) {
- var item;
- for (var i=0 ; i<numelements ; i++) {
- item = document.forms.changeform.elements[i];
- item.checked = value;
+ var elements = document.forms.changeform.getElementsByTagName('input'),
+ numelements = elements.length,
+ item, i;
+ for (i = 0; i < numelements; i++) {
+ item = elements[i];
+ if (item.type === 'checkbox' && item.name.match(/^id_/)) {
+ item.checked = value;
+ }
}
}
document.write(' <input type="button" name="uncheck_all" value="Uncheck All" onclick="SetCheckboxes(false);">');
@@ -137,7 +141,7 @@
<th><label for="bug_status">Status:</label></th>
<td colspan="3">[% PROCESS status_section %]</td>
</tr>
- [% IF user.in_group(Param("timetrackinggroup")) %]
+ [% IF user.is_timetracker %]
<tr>
<th><label for="estimated_time">Estimated Hours:</label></th>
<td>
@@ -146,13 +150,9 @@
value="[% dontchange FILTER html %]"
size="6">
</td>
- <th><label for="deadline">Deadline (YYYY-MM-DD):</label></th>
- <td>
- <input id="deadline"
- name="deadline"
- value="[% dontchange FILTER html %]"
- size="10">
- </td>
+ [% PROCESS bug/field.html.tmpl
+ field = bug_fields.deadline, value = dontchange
+ editable = 1, allow_dont_change = 1 %]
</tr>
<tr>
<th><label for="remaining_time">Remaining Hours:</label></th>
@@ -174,7 +174,7 @@
id => "assigned_to"
name => "assigned_to"
value => dontchange
- size => 32
+ size => 40
%]
<input type="checkbox" id="set_default_assignee" name="set_default_assignee" value="1">
<label for="set_default_assignee">Reset Assignee to default</label>
@@ -189,7 +189,7 @@
id => "qa_contact"
name => "qa_contact"
value => dontchange
- size => 32
+ size => 40
%]
<input type="checkbox" id="set_default_qa_contact" name="set_default_qa_contact" value="1">
<label for="set_default_qa_contact">Reset QA Contact to default</label>
@@ -201,7 +201,13 @@
<th><label for="masscc">CC List:</label></th>
<td colspan="3">
- <input id="masscc" name="masscc" size="32">
+ [% INCLUDE global/userselect.html.tmpl
+ id => "masscc"
+ name => "masscc"
+ value => ""
+ size => 40
+ multiple => 5
+ %]
<select name="ccaction">
<option value="add">Add these to the CC List</option>
<option value="remove">Remove these from the CC List</option>
@@ -213,23 +219,55 @@
[% IF use_keywords %]
<tr>
- <th>
- <label for="keywords">
- <a href="describekeywords.cgi">Keywords</a>:
- </label>
- </th>
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.keywords, editable = 1
+ desc_url = "describekeywords.cgi"
+ %]
<td colspan="3">
- <input id="keywords" name="keywords" size="32">
+ [% INCLUDE bug/field.html.tmpl
+ field = bug_fields.keywords, editable = 1, value = keywords
+ no_tds = 1
+ %]
<select name="keywordaction">
<option value="add">Add these keywords</option>
- <option value="delete">Delete these keywords</option>
- <option value="makeexact">Make the keywords be exactly this list</option>
+ <option value="remove">Delete these keywords</option>
+ <option value="set">Make the keywords be exactly this list</option>
</select>
</td>
</tr>
[% END %]
+ <tr>
+ <th>
+ <label for="dependson">
+ Depends On:
+ </label>
+ </th>
+ <td colspan="3">
+ <input id="dependson" name="dependson" size="40">
+ <select name="dependson_action">
+ <option value="add">Add these IDs</option>
+ <option value="remove">Delete these IDs</option>
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <th>
+ <label for="blocked">
+ Blocks:
+ </label>
+ </th>
+ <td colspan="3">
+ <input id="blocked" name="blocked" size="40">
+ <select name="blocked_action">
+ <option value="add">Add these IDs</option>
+ <option value="remove">Delete these IDs</option>
+ </select>
+ </td>
+ </tr>
+
[% IF Param('usestatuswhiteboard') %]
<tr>
<td align="right">
@@ -243,6 +281,8 @@
[% END %]
[% USE Bugzilla %]
+ [%# Show all legal values and all fields, ignoring visibility controls. %]
+ [% bug = 0 %]
[% FOREACH field = Bugzilla.active_custom_fields %]
<tr>
[% PROCESS bug/field.html.tmpl value = dontchange
@@ -255,7 +295,17 @@
</table>
-<b><label for="comment">Additional Comments:</label></b><br>
+<b><label for="comment">Additional Comments:</label></b>
+[% IF user.is_insider %]
+ <input type="checkbox" name="comment_is_private" value="1"
+ id="newcommentprivacy"
+ onClick="updateCommentTagControl(this, form)"/>
+ <label for="newcommentprivacy">
+ Make comment private (visible only to members of the
+ <strong>[% Param('insidergroup') FILTER html %]</strong> group)
+ </label>
+[% END %]
+<br>
[% INCLUDE global/textarea.html.tmpl
name = 'comment'
id = 'comment'
@@ -264,12 +314,22 @@
cols = constants.COMMENT_COLS
%]<br>
+[% Hook.process('before_groups') %]
+
[% IF groups.size > 0 %]
+ <script type="text/javascript">
+ function turn_off(myself, id) {
+ var other_checkbox = document.getElementById(id);
+ if (myself.checked && other_checkbox) {
+ other_checkbox.checked = false;
+ }
+ }
+ </script>
+
<b>Groups:</b><br>
<table border="1">
<tr>
- <th>Don't<br>change<br>this group<br>restriction</th>
<th>Remove<br>[% terms.bugs %]<br>from this<br>group</th>
<th>Add<br>[% terms.bugs %]<br>to this<br>group</th>
<th>Group Name:</th>
@@ -278,14 +338,17 @@
[% FOREACH group = groups %]
<tr>
<td align="center">
- <input type="radio" name="bit-[% group.id %]" value="-1" checked="checked">
- </td>
- <td align="center">
- <input type="radio" name="bit-[% group.id %]" value="0">
+ <input type="checkbox" name="defined_groups"
+ id="defined_group_[% group.id %]"
+ value="[% group.name FILTER html %]"
+ onchange="turn_off(this, 'group_[% group.id %]')">
</td>
[% IF group.is_active %]
<td align="center">
- <input type="radio" name="bit-[% group.id %]" value="1">
+ <input type="checkbox" name="groups"
+ id="group_[% group.id FILTER html %]"
+ value="[% group.name FILTER html %]"
+ onchange="turn_off(this, 'defined_group_[% group.id %]')">
</td>
[% ELSE %]
<td> </td>
@@ -308,11 +371,10 @@
[% END %]
[% END %]
-<input type="submit" id="commit" value="Commit">
-[% IF Param('move-enabled') && user.is_mover %]
- <input type="submit" name="action" id="action" value="[% Param('move-button-text') %]">
-[% END %]
+[%+ Hook.process('after_groups') %]
+
+<input type="submit" id="commit" value="Commit">
[%############################################################################%]
[%# Select Menu Block #%]
@@ -325,7 +387,7 @@
</option>
[% FOREACH menuitem = menuitems %]
[% IF property %][% menuitem = menuitem.$property %][% END %]
- <option value="[% menuitem FILTER html %]">[% menuitem FILTER html %]</option>
+ <option value="[% menuitem FILTER html %]">[% display_value(menuname, menuitem) FILTER html %]</option>
[% END %]
</select>
[% END %]
@@ -344,7 +406,7 @@
[% FOREACH bug_status = new_bug_statuses %]
<option value="[% bug_status.name FILTER html %]">
- [% get_status(bug_status.name) FILTER html %]
+ [% display_value("bug_status", bug_status.name) FILTER html %]
</option>
[% IF !bug_status.is_open %]
[% filtered_status = bug_status.name FILTER js %]
@@ -365,7 +427,7 @@
[% FOREACH r = resolutions %]
[% NEXT IF !r %]
[% NEXT IF r == "DUPLICATE" || r == "MOVED" %]
- <option value="[% r FILTER html %]">[% get_resolution(r) FILTER html %]</option>
+ <option value="[% r FILTER html %]">[% display_value("resolution", r) FILTER html %]</option>
[% END %]
</select>
</span>
diff --git a/Websites/bugs.webkit.org/template/en/default/list/list-simple.html.tmpl b/Websites/bugs.webkit.org/template/en/default/list/list-simple.html.tmpl
index 125a164..f4c3549 100644
--- a/Websites/bugs.webkit.org/template/en/default/list/list-simple.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/list/list-simple.html.tmpl
@@ -39,7 +39,8 @@
<head>
<title>[% title FILTER html %]</title>
<base href="[% urlbase FILTER html %]">
- <link href="skins/standard/buglist.css" rel="stylesheet" type="text/css">
+ <link href="[% 'skins/standard/buglist.css' FILTER mtime %]"
+ rel="stylesheet" type="text/css">
</head>
<body>
diff --git a/Websites/bugs.webkit.org/template/en/default/list/list.atom.tmpl b/Websites/bugs.webkit.org/template/en/default/list/list.atom.tmpl
index bfebbb8..ed0c660 100644
--- a/Websites/bugs.webkit.org/template/en/default/list/list.atom.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/list/list.atom.tmpl
@@ -23,8 +23,7 @@
# This is a template for generating an Atom representation of a buglist.
#%]
-[% PROCESS global/variables.none.tmpl %]
-[% USE date %]
+[% PROCESS "global/field-descs.none.tmpl" %]
[% DEFAULT title = "$terms.Bugzilla $terms.Bugs" %]
@@ -37,9 +36,8 @@
<link rel="self" type="application/atom+xml"
href="[% urlbase FILTER html %]buglist.cgi?
[%- urlquerypart FILTER xml %]"/>
- <updated>[% date.format(format=>"%Y-%m-%dT%H:%M:%SZ",
- time=>bugs.nsort('changedtime').last.changedtime,
- gmt=>1) FILTER xml %]</updated>
+ <updated>[% bugs.sort('changedtime').last.changedtime FILTER time("%Y-%m-%dT%H:%M:%SZ", "UTC")
+ FILTER xml %]</updated>
<id>[% urlbase FILTER html %]buglist.cgi?[% urlquerypart FILTER xml %]</id>
[% FOREACH bug = bugs %]
@@ -50,10 +48,9 @@
[%- bug.bug_id FILTER xml %]"/>
<id>[% urlbase FILTER xml %]show_bug.cgi?id=[% bug.bug_id FILTER xml %]</id>
<author>
- <name>[% bug.reporter_realname FILTER xml %]</name>
+ <name>[% bug.reporter_realname ? bug.reporter_realname : bug.reporter FILTER xml %]</name>
</author>
- <updated>[% date.format(format=>"%Y-%m-%dT%H:%M:%SZ",time=>bug.changedtime,
- gmt=>1) FILTER xml %]</updated>
+ <updated>[% bug.changedtime FILTER time("%Y-%m-%dT%H:%M:%SZ", "UTC") FILTER xml %]</updated>
<summary type="html">
[%# Filter out the entire block, so that we don't need to escape the html code out %]
[% FILTER xml %]
@@ -68,22 +65,22 @@
<td>[% bug.component FILTER html %]</td>
</tr><tr class="bz_feed_assignee">
<td>[% columns.assigned_to_realname.title FILTER html %]</td>
- <td>[% bug.assigned_to_realname FILTER html %]</td>
+ <td>[% bug.assigned_to_realname ? bug.assigned_to_realname : bug.assigned_to FILTER html %]</td>
</tr><tr class="bz_feed_reporter">
<td>[% columns.reporter_realname.title FILTER html %]</td>
- <td>[% bug.reporter_realname FILTER html %]</td>
+ <td>[% bug.reporter_realname ? bug.reporter_realname : bug.reporter FILTER html %]</td>
</tr><tr class="bz_feed_bug_status">
<td>[% columns.bug_status.title FILTER html %]</td>
- <td>[% bug.bug_status FILTER html %]</td>
+ <td>[% display_value("bug_status", bug.bug_status) FILTER html %]</td>
</tr><tr class="bz_feed_resolution">
<td>[% columns.resolution.title FILTER html %] </td>
- <td>[% bug.resolution FILTER html %]</td>
+ <td>[% display_value("resolution", bug.resolution) FILTER html %]</td>
</tr><tr class="bz_feed_priority">
<td>[% columns.priority.title FILTER html %]</td>
- <td>[% bug.priority FILTER html %]</td>
+ <td>[% display_value("priority", bug.priority) FILTER html %]</td>
</tr><tr class="bz_feed_severity">
<td>[% columns.bug_severity.title FILTER html %] </td>
- <td>[% bug.bug_severity FILTER html %]</td>
+ <td>[% display_value("bug_severity", bug.bug_severity) FILTER html %]</td>
[% IF Param("usetargetmilestone") %]
</tr><tr class="bz_feed_target_milestone">
<td>[% columns.target_milestone.title FILTER html %]</td>
diff --git a/Websites/bugs.webkit.org/template/en/default/list/list.csv.tmpl b/Websites/bugs.webkit.org/template/en/default/list/list.csv.tmpl
index 9ce1c73..be0a5bc 100644
--- a/Websites/bugs.webkit.org/template/en/default/list/list.csv.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/list/list.csv.tmpl
@@ -17,16 +17,23 @@
#
# Contributor(s): Myk Melez <myk@mozilla.org>
# Gervase Markham <gerv@gerv.net>
+ # miketosh
#%]
[% PROCESS "global/field-descs.none.tmpl" %]
-[% USE date %]
[% colsepchar = user.settings.csv_colsepchar.value %]
-bug_id
-[% FOREACH column = displaycolumns %]
- [% colsepchar %][% column FILTER csv %]
+[% IF human %]
+ [% field_descs.bug_id FILTER csv %]
+ [% FOREACH column = displaycolumns %]
+ [% colsepchar %][% field_descs.$column FILTER csv %]
+ [% END %]
+[% ELSE %]
+ bug_id
+ [% FOREACH column = displaycolumns %]
+ [% colsepchar %][% column FILTER csv %]
+ [% END %]
[% END %]
[% FOREACH bug = bugs %]
@@ -35,11 +42,11 @@
[% colsepchar %]
[% IF column == "opendate" OR column == "changeddate" %]
[% rawcolumn = column.replace("date", "time") %]
- [% bug.$column = date.format(bug.$rawcolumn, "%Y-%m-%d %H:%M:%S") %]
+ [% bug.$column = bug.$rawcolumn FILTER time("%Y-%m-%d %H:%M:%S") %]
[% ELSIF column == 'bug_status' %]
- [% bug.$column = get_status(bug.$column) %]
+ [% bug.$column = display_value("bug_status", bug.$column) %]
[% ELSIF column == 'resolution' %]
- [%- bug.$column = get_resolution(bug.$column) %]
+ [%- bug.$column = display_value("resolution", bug.$column) %]
[% END %]
[% bug.$column FILTER csv %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/list/list.html.tmpl b/Websites/bugs.webkit.org/template/en/default/list/list.html.tmpl
index be17414..4eeff5e 100644
--- a/Websites/bugs.webkit.org/template/en/default/list/list.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/list/list.html.tmpl
@@ -28,14 +28,14 @@
[%# Template Initialization #%]
[%############################################################################%]
-[% PROCESS global/variables.none.tmpl %]
+[% PROCESS "global/field-descs.none.tmpl" %]
[% title = "$terms.Bug List" %]
[% IF searchname || defaultsavename %]
[% title = title _ ": " _ (searchname OR defaultsavename) FILTER html %]
[% END %]
-[% qorder = order FILTER url_quote IF order %]
+[% qorder = order FILTER uri IF order %]
[%############################################################################%]
@@ -46,34 +46,28 @@
title = title
style = style
atomlink = "buglist.cgi?$urlquerypart&title=$title&ctype=atom"
- javascript_urls = [ "js/util.js", "js/field.js",
- "js/yui/yahoo-dom-event.js", "js/yui/calendar.js" ]
- style_urls = [ "skins/standard/buglist.css", "skins/standard/yui/calendar.css" ]
+ yui = [ 'autocomplete', 'calendar' ]
+ javascript_urls = [ "js/util.js", "js/field.js" ]
+ style_urls = [ "skins/standard/buglist.css" ]
doc_section = "query.html#list"
%]
-<div class="bz_query_head" align="center">
+<div class="bz_query_head">
<span class="bz_query_timestamp">
- [% IF Param('timezone') %]
- <b>[% time2str("%a %b %e %Y %T %Z", currenttime, Param('timezone')) %]</b><br>
- [% ELSE %]
- <b>[% time2str("%a %b %e %Y %T", currenttime) %]</b><br>
- [% END %]
+ [% currenttime FILTER time('%a %b %e %Y %T %Z') FILTER html %]<br>
</span>
[% IF debug %]
- <p class="bz_query_debug">
- [% FOREACH debugline = debugdata %]
- [% debugline FILTER html %]<br>
- [% END %]
- </p>
<p class="bz_query">[% query FILTER html %]</p>
+ [% IF query_explain.defined %]
+ <pre class="bz_query_explain">[% query_explain FILTER html %]</pre>
+ [% END %]
[% END %]
[% IF user.settings.display_quips.value == 'on' %]
[% DEFAULT quip = "$terms.Bugzilla would like to put a random quip here, but no one has entered any." %]
<span class="bz_quip">
- <a href="quips.cgi"><i>[% quip FILTER html %]</i></a>
+ <a href="quips.cgi"><em>[% quip FILTER html %]</em></a>
</span>
[% END %]
@@ -86,6 +80,29 @@
</h2>
[% END %]
+[% SET shown_types = [
+ 'notequals', 'regexp', 'notregexp', 'lessthan', 'lessthaneq',
+ 'greaterthan', 'greaterthaneq', 'changedbefore', 'changedafter',
+ 'changedfrom', 'changedto', 'changedby', 'notsubstring', 'nowords',
+ 'nowordssubstr', 'notmatches',
+] %]
+<ul class="search_description">
+[% FOREACH desc_item = search_description %]
+ <li>
+ <strong>[% field_descs.${desc_item.field} FILTER html %]:</strong>
+ [% IF shown_types.contains(desc_item.type) || debug %]
+ ([% search_descs.${desc_item.type} FILTER html %])
+ [% END %]
+ [% FOREACH val IN desc_item.value.split(',') %]
+ [%+ display_value(desc_item.field, val) FILTER html %][% ',' UNLESS loop.last %]
+ [% END %]
+ [% IF debug %]
+ (<code>[% desc_item.term FILTER html %]</code>)
+ [% END %]
+ </li>
+[% END %]
+</ul>
+
<hr>
[%############################################################################%]
@@ -93,9 +110,7 @@
[%############################################################################%]
[% IF bugs.size > 9 %]
- <span class="bz_result_count">
- [% bugs.size %] [%+ terms.bugs %] found.
- </span>
+ [% PROCESS num_results %]
[% END %]
[%############################################################################%]
@@ -117,15 +132,19 @@
[%# Succeeding Status Line #%]
[%############################################################################%]
-<span class="bz_result_count">
- [% IF bugs.size == 0 %]
- [% terms.zeroSearchResults %].
- [% ELSIF bugs.size == 1 %]
- One [% terms.bug %] found.
- [% ELSE %]
- [% bugs.size %] [%+ terms.bugs %] found.
- [% END %]
-</span>
+[% PROCESS num_results %]
+
+[% IF bugs.size == 0 %]
+ <ul class="zero_result_links">
+ <li>[% PROCESS enter_bug_link %]</li>
+ [% IF one_product.defined %]
+ <li><a href="enter_bug.cgi">File a new [% terms.bug %] in a
+ different product</a></li>
+ [% END %]
+ <li><a href="[% PROCESS edit_search_url %]">Edit this search</a></li>
+ <li><a href="query.cgi">Start a new search</a></li>
+ </ul>
+[% END %]
<br>
@@ -163,11 +182,19 @@
<input type="submit" value="XML" id="xml">
</form>
- [% IF user.in_group(Param('timetrackinggroup')) %]
+ [% IF user.is_timetracker %]
<form method="post" action="summarize_time.cgi">
<input type="hidden" name="id" value="[% buglist_joined FILTER html %]">
<input type="submit" id="timesummary" value="Time Summary">
</form>
+ [% IF time_summary_limited %]
+ <small>
+ Time Summary will only include the [% terms.bugs %] shown above. In order to
+ to see a time summary for all [% terms.bugs %] found by the search, you can
+ <a href="buglist.cgi?[% urlquerypart FILTER html %]
+ [%- "&order=$qorder" FILTER html IF order %]&limit=0">
+ Show all search results</a>.</small>
+ [% END %]
[% END %]
</td>
@@ -175,7 +202,7 @@
<td valign="middle" class="bz_query_links">
<a href="buglist.cgi?
- [% urlquerypart FILTER html %]&ctype=csv">CSV</a> |
+ [% urlquerypart FILTER html %]&ctype=csv&human=1">CSV</a> |
<a href="buglist.cgi?
[% urlquerypart FILTER html %]&title=
[%- title FILTER html %]&ctype=atom">Feed</a> |
@@ -183,7 +210,7 @@
[% urlquerypart FILTER html %]&ctype=ics">iCalendar</a> |
<a href="colchange.cgi?
[% urlquerypart FILTER html %]&query_based_on=
- [% defaultsavename OR searchname FILTER url_quote %]">Change Columns</a> |
+ [% defaultsavename OR searchname FILTER uri %]">Change Columns</a> |
[% IF bugs.size > 1 && caneditbugs && !dotweak %]
<a href="buglist.cgi?[% urlquerypart FILTER html %]
@@ -192,7 +219,7 @@
|
[% END %]
- [% IF bugowners %]
+ [% IF bugowners && user.id %]
<a href="mailto:
[% bugowners FILTER html %]">Send Mail to [% terms.Bug %] Assignees</a> |
[% END %]
@@ -203,19 +230,15 @@
[% END %]
<td valign="middle" class="bz_query_edit">
- [% editqueryname = searchname OR defaultsavename OR '' %]
- <a href="query.cgi?[% urlquerypart FILTER html %]
- [% IF editqueryname != '' %]&known_name=
- [% editqueryname FILTER url_quote %]
- [% END %]">Edit Search</a>
+ <a href="[% PROCESS edit_search_url %]">Edit Search</a>
</td>
[% IF searchtype == "saved" %]
<td valign="middle" nowrap="nowrap" class="bz_query_forget">
|
<a href="buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=
- [% searchname FILTER url_quote %]&token=
- [% issue_hash_token([search_id, searchname]) FILTER url_quote %]">
+ [% searchname FILTER uri %]&token=
+ [% issue_hash_token([search_id, searchname]) FILTER uri %]">
Forget Search '[% searchname FILTER html %]'</a>
</td>
[% ELSE %]
@@ -228,18 +251,18 @@
value="[% urlquerypart FILTER html %][% "&order=$qorder" FILTER html IF order %]">
<input type="hidden" name="cmdtype" value="doit">
<input type="hidden" name="remtype" value="asnamed">
+ <input type="hidden" name="token" value="[% issue_hash_token(['savedsearch']) FILTER html %]">
<input type="text" id="save_newqueryname" name="newqueryname" size="20"
- value="[% defaultsavename FILTER html %]">
+ title="New query name" value="[% defaultsavename FILTER html %]">
</form>
</td>
[% END %]
</tr>
</table>
-[% IF cgi.param('product').size == 1 && cgi.param('product') != "" %]
+[% IF one_product.defined && bugs.size %]
<p class="bz_query_single_product">
- <a href="enter_bug.cgi?product=[% cgi.param('product') FILTER url_quote %]">File
- a new [% terms.bug %] in the "[% cgi.param('product') FILTER html %]" product</a>
+ [% PROCESS enter_bug_link %]
</p>
[% END %]
@@ -248,3 +271,44 @@
[%############################################################################%]
[% PROCESS global/footer.html.tmpl %]
+
+[%##########%]
+[%# Blocks #%]
+[%##########%]
+
+[% BLOCK edit_search_url %]
+ [% editqueryname = searchname OR defaultsavename OR '' %]
+ query.cgi?[% urlquerypart FILTER html %]
+ [%- IF editqueryname != '' %]&known_name=
+ [%- editqueryname FILTER uri %]
+ [% END %]
+[% END %]
+
+[% BLOCK enter_bug_link %]
+ <a href="enter_bug.cgi
+ [%- IF one_product.defined %]?product=
+ [%- one_product.name FILTER uri %][% END %]">File
+ a new [% terms.bug %]
+ [% IF one_product.defined %]
+ in the "[% one_product.name FILTER html %]" product
+ [% END %]</a>
+[% END %]
+
+[% BLOCK num_results %]
+ <span class="bz_result_count">
+ [% IF bugs.size == 0 %]
+ <span class="zero_results">[% terms.zeroSearchResults %].</span>
+ [% ELSIF default_limited AND bugs.size >= Param('default_search_limit') %]
+ This result was limited to [% Param('default_search_limit') FILTER html %]
+ [%+ terms.bugs %].
+ <a href="buglist.cgi?[% urlquerypart FILTER html %]
+ [%- "&order=$qorder" FILTER html IF order %]&limit=0">See
+ all search results for this query</a>.
+ [% time_summary_limited = 1 %]
+ [% ELSIF bugs.size == 1 %]
+ One [% terms.bug %] found.
+ [% ELSE %]
+ [% bugs.size %] [%+ terms.bugs %] found.
+ [% END %]
+ </span>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/list/list.ics.tmpl b/Websites/bugs.webkit.org/template/en/default/list/list.ics.tmpl
index f8953d9..54e8d06 100644
--- a/Websites/bugs.webkit.org/template/en/default/list/list.ics.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/list/list.ics.tmpl
@@ -30,8 +30,9 @@
[%+ PROCESS ics_url base_url=urlbase bug_id=bug.bug_id +%]
[%+ PROCESS ics_status bug_status = bug.bug_status +%]
[%+ PROCESS ics_dtstamp +%]
+[%+ ics_priorities.${bug.priority} FILTER ics('PRIORITY') +%]
[% IF bug.changeddate %]
-[%+ time2str("%Y%m%dT%H%M%SZ", bug.changedtime, "UTC") FILTER ics('LAST-MODIFIED') +%]
+[%+ bug.changedtime FILTER time("%Y%m%dT%H%M%SZ", "UTC") FILTER ics('LAST-MODIFIED') +%]
[% END %]
[% IF bug.percentage_complete %]
[%+ bug.percentage_complete FILTER format('%d') FILTER ics('PERCENT-COMPLETE') +%]
@@ -57,19 +58,19 @@
[% END %]
[% BLOCK ics_uid %]
- [% "${bug_id}@${base_url}" FILTER url_quote FILTER ics('UID') %]
+ [% "${bug_id}@${base_url}" FILTER uri FILTER ics('UID') %]
[% END %]
[% BLOCK ics_url %]
- [% "${base_url}show_bug.cgi?id=${bug_id}" FILTER url_quote FILTER ics('URL;VALUE=URI') %]
+ [% "${base_url}show_bug.cgi?id=${bug_id}" FILTER uri FILTER ics('URL;VALUE=URI') %]
[% END %]
[% BLOCK ics_dtstart %]
- [% time2str("%Y%m%dT%H%M%SZ", bug.opentime, "UTC") FILTER ics('DTSTART') %]
+ [% bug.opentime FILTER time("%Y%m%dT%H%M%SZ", "UTC") FILTER ics('DTSTART') %]
[% END %]
[% BLOCK ics_dtstamp %]
- [% time2str("%Y%m%dT%H%M%SZ", currenttime, "UTC") FILTER ics('DTSTAMP') %]
+ [% currenttime FILTER time("%Y%m%dT%H%M%SZ", "UTC") FILTER ics('DTSTAMP') %]
[% END %]
[% BLOCK ics_status %]
@@ -81,7 +82,7 @@
[% END %]
[% END %]
[% IF NOT status %]
- [% IF bug_status == 'ASSIGNED' %]
+ [% IF bug_status == 'IN_PROGRESS' || bug_status == 'ASSIGNED' %]
[% status = 'IN-PROGRESS' %]
[% ELSE %]
[% status = 'NEEDS-ACTION' %]
diff --git a/Websites/bugs.webkit.org/template/en/default/list/list.js.tmpl b/Websites/bugs.webkit.org/template/en/default/list/list.js.tmpl
deleted file mode 100644
index 7e9664c..0000000
--- a/Websites/bugs.webkit.org/template/en/default/list/list.js.tmpl
+++ /dev/null
@@ -1,37 +0,0 @@
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Gervase Markham <gerv@gerv.net>
- #%]
-
-// Note: only publicly-accessible bugs (those not in any group) will be
-// listed when using this JavaScript format. This is to prevent malicious
-// sites stealing information about secure bugs.
-
-bugs = new Array;
-
-[% FOREACH bug = bugs %]
- bugs[[% bug.bug_id %]] = [
- [% FOREACH column = displaycolumns %]
- "[%- bug.$column FILTER js -%]"[% "," UNLESS loop.last %]
- [% END %]
- ];
-[% END %]
-
-if (window.buglistCallback) {
- buglistCallback(bugs);
-}
diff --git a/Websites/bugs.webkit.org/template/en/default/list/list.rdf.tmpl b/Websites/bugs.webkit.org/template/en/default/list/list.rdf.tmpl
index 873e4ed..d7879a6 100644
--- a/Websites/bugs.webkit.org/template/en/default/list/list.rdf.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/list/list.rdf.tmpl
@@ -27,6 +27,7 @@
<bz:result rdf:about="[% urlbase FILTER xml %]buglist.cgi?[% urlquerypart FILTER html %]">
<bz:installation rdf:resource="[% urlbase FILTER xml %]" />
+ <bz:query_timestamp>[% currenttime FILTER time('%Y-%m-%d %T %Z') FILTER html %]</bz:query_timestamp>
<bz:bugs>
<Seq>
[% FOREACH bug = bugs %]
@@ -37,7 +38,7 @@
<bz:id nc:parseType="Integer">[% bug.bug_id %]</bz:id>
[% FOREACH column = displaycolumns %]
- <bz:[% column %][% ' nc:parseType="Integer"' IF column == "votes" %]>[% bug.$column FILTER html %]</bz:[% column %]>
+ <bz:[% column %]>[% bug.$column FILTER html %]</bz:[% column %]>
[% END %]
</bz:bug>
diff --git a/Websites/bugs.webkit.org/template/en/default/list/quips.html.tmpl b/Websites/bugs.webkit.org/template/en/default/list/quips.html.tmpl
index d6000d5..512add9 100644
--- a/Websites/bugs.webkit.org/template/en/default/list/quips.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/list/quips.html.tmpl
@@ -37,7 +37,7 @@
<p>
<font color="red">
Your quip '<tt>[% added_quip FILTER html %]</tt>' has been added.
- [% IF Param("quip_list_entry_control") == "moderated" AND !user.groups.admin %]
+ [% IF Param("quip_list_entry_control") == "moderated" AND !user.in_group('bz_quip_moderators') %]
It will be used as soon as it gets approved.
[% END %]
</font>
@@ -66,13 +66,15 @@
<p>
You can extend the quip list. Type in something clever or funny or boring
(but not obscene or offensive, please) and bonk on the button.
- [% IF Param("quip_list_entry_control") == "moderated" AND !user.groups.admin %]
+ [% IF Param("quip_list_entry_control") == "moderated" AND !user.in_group('bz_quip_moderators') %]
Note that your quip has to be approved before it is used.
[% END %]
</p>
<form method="post" action="quips.cgi">
<input type="hidden" name="action" value="add">
+ <input type="hidden" name="token"
+ value="[% issue_hash_token(['create-quips']) FILTER html %]">
<input size="80" name="quip">
<p>
<input type="submit" id="add" value="Add This Quip">
@@ -84,7 +86,7 @@
[% END %]
[% IF show_quips %]
- [% IF !user.in_group('admin') %]
+ [% IF !user.in_group('bz_quip_moderators') %]
<h2>
Existing quips:
</h2>
@@ -103,6 +105,8 @@
</p>
<form name="editform" method="post" action="quips.cgi">
<input type="hidden" name="action" value="approve">
+ <input type="hidden" name="token"
+ value="[% issue_hash_token(['approve-quips']) FILTER html %]">
<table border="1">
<thead><tr>
<th>Quip</th>
@@ -119,7 +123,8 @@
[% "Unknown" IF NOT users.$userid %]
</td>
<td>
- <a href="quips.cgi?action=delete&quipid=[% quipid FILTER url_quote %]">
+ <a href="quips.cgi?action=delete&quipid=[% quipid FILTER uri %]&token=
+ [%- issue_hash_token(['quips', quipid]) FILTER uri %]">
Delete
</a>
</td>
@@ -158,7 +163,7 @@
<p>
Those who like their wisdom in large doses can
<a href="quips.cgi?action=show">view
- [% IF user.in_group('admin') %]
+ [% IF user.in_group('bz_quip_moderators') %]
and edit
[% END %]
the whole quip list</a>.
diff --git a/Websites/bugs.webkit.org/template/en/default/list/table.html.tmpl b/Websites/bugs.webkit.org/template/en/default/list/table.html.tmpl
index 811ed02..2b266d4 100644
--- a/Websites/bugs.webkit.org/template/en/default/list/table.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/list/table.html.tmpl
@@ -16,12 +16,17 @@
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
+ # Jesse Clark <jjclark1982@gmail.com>
#%]
[%############################################################################%]
[%# Initialization #%]
[%############################################################################%]
+[%# Don't display the table or do any processing if there are no bugs
+ # to display %]
+[% RETURN IF !bugs.size %]
+
[%# Columns whose titles or values should be abbreviated to make the list
# more compact. For columns whose titles should be abbreviated,
# the shortened title is included. For columns whose values should be
@@ -40,7 +45,7 @@
[% abbrev =
{
"bug_severity" => { maxlength => 3 , title => "Sev" } ,
- "priority" => { maxlength => 3 , title => "Pri" } ,
+ "priority" => { maxlength => 7 , title => "Pri" } ,
"rep_platform" => { maxlength => 3 , title => "Plt" } ,
"bug_status" => { maxlength => 4 } ,
"assigned_to" => { maxlength => 30 , ellipsis => "..." } ,
@@ -54,46 +59,40 @@
"short_short_desc" => { maxlength => 60 , ellipsis => "..." , wrap => 1 } ,
"status_whiteboard" => { title => "Whiteboard" , wrap => 1 } ,
"keywords" => { wrap => 1 } ,
+ "flagtypes.name" => { wrap => 1 } ,
"component" => { maxlength => 8 , title => "Comp" } ,
"product" => { maxlength => 8 } ,
"version" => { maxlength => 5 , title => "Vers" } ,
"op_sys" => { maxlength => 4 } ,
+ "bug_file_loc" => { maxlength => 30 } ,
"target_milestone" => { title => "TargetM" } ,
+ "longdescs.count" => { title => "# Comments" },
"percentage_complete" => { format_value => "%d %%" } ,
}
%]
[% PROCESS bug/time.html.tmpl %]
+[% Hook.process("before_table") %]
+
[%############################################################################%]
[%# Table Header #%]
[%############################################################################%]
[% tableheader = BLOCK %]
<table class="bz_buglist" cellspacing="0" cellpadding="4" width="100%">
- <colgroup>
- [% IF dotweak %]
- <col class="bz_checkbox_column">
- [% END %]
- <col class="bz_id_column">
- [% FOREACH id = displaycolumns %]
- <col class="bz_[% id FILTER css_class_quote %]_column">
- [% END %]
- </colgroup>
-
- <tr class="bz_buglist_header bz_first_buglist_header" align="left">
+ <tr class="bz_buglist_header bz_first_buglist_header">
[% IF dotweak %]
<th> </th>
[% END %]
<th colspan="[% splitheader ? 2 : 1 %]" class="first-child">
- [% desc = '' %]
- [% IF (om = order.match("^bugs\.bug_id( desc)?")) %]
- [% desc = ' desc' IF NOT om.0 %]
- [% END %]
<a href="buglist.cgi?
- [% urlquerypart FILTER html %]&order=bugs.bug_id[% desc FILTER url_quote %]
+ [% urlquerypart FILTER html %]&order=
+ [% PROCESS new_order id='bug_id' %]
[%-#%]&query_based_on=
- [% defaultsavename OR searchname FILTER url_quote %]">ID</a>
+ [% defaultsavename OR searchname FILTER uri %]">ID
+ [% PROCESS order_arrow id='bug_id' ~%]
+ </a>
</th>
[% IF splitheader %]
@@ -104,7 +103,7 @@
[% PROCESS columnheader %]
[% END %]
- </tr><tr class="bz_buglist_header" align="left">
+ </tr><tr class="bz_buglist_header">
[% IF dotweak %]
<th> </th>
[% END %]
@@ -130,42 +129,49 @@
[% BLOCK columnheader %]
<th colspan="[% splitheader ? 2 : 1 %]">
- [% IF column.name.match('\s+AS\s+') %]
- [%# For aliased columns, use their ID for sorting. %]
- [% column.sortalias = id %]
- [% ELSE %]
- [%# Other columns may sort on their name directly. %]
- [% column.sortalias = column.name %]
- [% END %]
- [% desc = '' %]
- [% IF (om = order.match("$column.sortalias( desc)?")) %]
- [% desc = ' desc' IF NOT om.0 %]
- [% END %]
- [% order = order.remove("$column.sortalias( desc)?,?") %]
<a href="buglist.cgi?[% urlquerypart FILTER html %]&order=
- [% column.sortalias FILTER url_quote %][% desc FILTER url_quote %]
- [% ",$order" FILTER url_quote IF order %]
+ [% PROCESS new_order %]
[%-#%]&query_based_on=
- [% defaultsavename OR searchname FILTER url_quote %]">
- [%- abbrev.$id.title || field_descs.$id || column.title -%]</a>
+ [% defaultsavename OR searchname FILTER uri %]">
+ [%- abbrev.$id.title || field_descs.$id || column.title -%]
+ [% PROCESS order_arrow ~%]
+ </a>
</th>
[% END %]
+[% BLOCK new_order %]
+ [% desc = '' %]
+ [% IF (om = order.match("\\b$id( DESC)?")) %]
+ [% desc = ' DESC' IF NOT om.0 %]
+ [% END %]
+ [% id _ desc FILTER uri %]
+ [% IF id != 'bug_id' AND order %]
+ [% ',' _ order.remove("\\b$id( DESC)?(,\\s*|\$)") FILTER uri %]
+ [% END %]
+[% END %]
+
+[% BLOCK order_arrow %]
+ [% IF order.match("^$id DESC") %]
+ <span class="bz_sort_order_primary">▼</span>
+ [% ELSIF order.match("^$id(,\\s*|\$)") %]
+ <span class="bz_sort_order_primary">▲</span>
+ [% ELSIF order.match("\\b$id DESC") %]
+ <span class="bz_sort_order_secondary">▼</span>
+ [% ELSIF order.match("\\b$id(,\\s*|\$)") %]
+ <span class="bz_sort_order_secondary">▲</span>
+ [% END %]
+[% END %]
[%############################################################################%]
[%# Bug Table #%]
[%############################################################################%]
+[% tableheader %]
+
[% FOREACH bug = bugs %]
[% count = loop.count() %]
- [% FLUSH IF count % 10 == 1 %]
- [%# At the beginning of every hundred bugs in the list, start a new table. %]
- [% IF count % 100 == 1 %]
- [% tableheader %]
- [% END %]
-
- <tr class="bz_bugitem
+ <tr id="b[% bug.bug_id %]" class="bz_bugitem
bz_[% bug.bug_severity FILTER css_class_quote -%]
bz_[% bug.priority FILTER css_class_quote -%]
bz_[% bug.bug_status FILTER css_class_quote -%]
@@ -176,41 +182,83 @@
">
[% IF dotweak %]
- <td>
+ <td class="bz_checkbox_column">
<input type="checkbox" name="id_[% bug.bug_id %]">
</td>
[% END %]
- <td class="first-child">
- <a name="b[% bug.bug_id %]"
- href="show_bug.cgi?id=[% bug.bug_id %]">[% bug.bug_id %]</a>
+ <td class="first-child bz_id_column">
+ <a href="show_bug.cgi?id=[% bug.bug_id %]">[% bug.bug_id %]</a>
<span style="display: none">[%+ '[SEC]' IF bug.secure_mode %]</span>
</td>
[% FOREACH column = displaycolumns %]
- <td [% 'style="white-space: nowrap"' IF NOT abbrev.$column.wrap %]>
+ <td [% 'style="white-space: nowrap"' IF NOT abbrev.$column.wrap %]
+ class="bz_[% column FILTER css_class_quote %]_column">
+ [% IF abbrev.$column.maxlength %]
+ <span title="[%- display_value(column, bug.$column) FILTER html %]">
+ [% END %]
[% IF abbrev.$column.format_value %]
[%- bug.$column FILTER format(abbrev.$column.format_value) FILTER html -%]
[% ELSIF column == 'actual_time' ||
column == 'remaining_time' ||
column == 'estimated_time' %]
[% PROCESS formattimeunit time_unit=bug.$column %]
- [% ELSIF column == 'bug_status' %]
- [%- get_status(bug.$column).truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html %]
- [% ELSIF column == 'resolution' %]
- [%- get_resolution(bug.$column).truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html %]
+ [%# Display the login name of the user if their real name is empty. %]
+ [% ELSIF column.match('_realname$') && bug.$column == '' %]
+ [% SET login_column = column.remove('_realname$') %]
+ [% bug.${login_column}.truncate(abbrev.$column.maxlength,
+ abbrev.$column.ellipsis) FILTER html %]
+ [% ELSIF column == 'short_desc' || column == "short_short_desc" %]
+ <a href="show_bug.cgi?id=[% bug.bug_id FILTER html %]">
+ [%- bug.$column.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
+ </a>
[% ELSE %]
- [%- bug.$column.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
+ [%- display_value(column, bug.$column).truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
+ [% END %]
+ [% IF abbrev.$column.maxlength %]
+ </span>
[% END %]
</td>
[% END %]
</tr>
- [%# At the end of every hundred bugs in the list, or at the end of the list,
- # end the current table.
- #%]
- [% IF loop.last() || loop.count() % 100 == 0 %]
- </table>
+ [% IF loop.last() && time_info.time_present == 1 %]
+ [% PROCESS time_summary_line %]
[% END %]
[% END %]
+
+</table>
+
+[% BLOCK time_summary_line %]
+ <tr class="bz_time_summary_line">
+ [% columns_to_span = 1 %] [%# bugID %]
+ [% IF dotweak %]
+ [% columns_to_span = columns_to_span + 1 %]
+ [% END %]
+ [% FOREACH column = displaycolumns %]
+ [% IF column == 'actual_time' ||
+ column == 'remaining_time' ||
+ column == 'estimated_time' ||
+ column == 'percentage_complete' %]
+ [% IF columns_to_span > 0 %]
+ <td class="bz_total bz_total_label" colspan="
+ [%- columns_to_span FILTER html %]"><b>Totals</b></td>
+ [% columns_to_span = 0 %]
+ [% END %]
+ [% IF column == 'percentage_complete' %]
+ <td class="bz_total">[% time_info.percentage_complete
+ FILTER format(abbrev.$column.format_value) FILTER html %]</td>
+ [% ELSE %]
+ <td class="bz_total">
+ [%- PROCESS formattimeunit time_unit=time_info.$column %]</td>
+ [% END %]
+ [% ELSIF columns_to_span == 0 %] [%# A column following the first total %]
+ <td class="bz_total"> </td>
+ [% ELSE %] [%# We haven't gotten to a time column yet, keep computing span %]
+ [% columns_to_span = columns_to_span + 1 %]
+ [% END %]
+ [% END %]
+ </tr>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/pages/bug-writing.html.tmpl b/Websites/bugs.webkit.org/template/en/default/pages/bug-writing.html.tmpl
index 035876bb..ec997be 100644
--- a/Websites/bugs.webkit.org/template/en/default/pages/bug-writing.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/pages/bug-writing.html.tmpl
@@ -78,8 +78,8 @@
it?
(e.g. Linux, Windows XP, Mac OS X.)<br>
If you know the [% terms.bug %] happens on more than one type of
- operating system, choose "All".
- If your OS isn't listed, choose Other.</p>
+ operating system, choose <em>[% display_value("op_sys", "All") FILTER html %]</em>.
+ If your OS isn't listed, choose <em>[% display_value("op_sys", "Other") FILTER html %]</em>.</p>
<p><b>Summary:</b> How would you describe the [% terms.bug %], in
approximately 60 or fewer characters?<br>
diff --git a/Websites/bugs.webkit.org/template/en/default/pages/fields.html.tmpl b/Websites/bugs.webkit.org/template/en/default/pages/fields.html.tmpl
index 90ec2d0..2794e1c 100644
--- a/Websites/bugs.webkit.org/template/en/default/pages/fields.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/pages/fields.html.tmpl
@@ -19,300 +19,220 @@
# Gervase Markham <gerv@gerv.net>
#%]
-[% PROCESS global/variables.none.tmpl %]
[% PROCESS "global/field-descs.none.tmpl" %]
-[% INCLUDE global/header.html.tmpl title = "A $terms.Bug's Life Cycle" %]
+[% PROCESS global/header.html.tmpl
+ title = "$terms.Bug Fields"
+ style_urls = ['skins/standard/page.css']
+%]
-<p>
-The <b>status</b> and <b>resolution</b> fields define and track the life
-cycle of [% terms.abug %].
-</p>
+<p>This page describes the various fields that you see
+ on [% terms.abug %].</p>
-<a name="status"></a>
-<a name="resolution"></a>
-
-<table border="1" cellpadding="4">
- <tr align="center" valign="top">
- <td width="50%">
- <h1>STATUS</h1>
+<table class="field_value_explanation">
+ <thead>
+ <tr>
+ <td id="bug_status">
+ <h2>[% field_descs.bug_status FILTER upper FILTER html %]</h2>
</td>
- <td>
- <h1>RESOLUTION</h1>
+ <td id="resolution">
+ <h2>[% field_descs.resolution FILTER upper FILTER html %]</h2>
</td>
</tr>
- <tr valign="top">
- <td>The <b>status</b> field indicates the general health of a
- [% terms.bug %]. Only certain status transitions are allowed.</td>
+ <tr>
+ <td>The [% field_descs.bug_status FILTER html %] field indicates the
+ current state of a [% terms.bug %]. Only certain status transitions
+ are allowed.</td>
- <td>The <b>resolution</b> field indicates what happened to this
- [%+ terms.bug %].</td>
+ <td>The [% field_descs.resolution FILTER html %] field indicates what
+ happened to this [%+ terms.bug %].</td>
</tr>
+ </thead>
- <tr valign="top">
+ <tbody>
+ <tr class="header_row">
+ <td colspan="2">Open [% terms.Bugs %]</td>
+ </tr>
+ <tr>
<td>
<dl>
- <dt>
- <b>[% get_status("UNCONFIRMED") FILTER html %]</b>
+ <dt class="unconfirmed">
+ [% display_value("bug_status", "UNCONFIRMED") FILTER html %]
</dt>
- <dd>
+ <dd class="unconfirmed">
This [% terms.bug %] has recently been added to the database.
- Nobody has validated that this [% terms.bug %] is true. Users
+ Nobody has confirmed that this [% terms.bug %] is valid. Users
who have the "canconfirm" permission set may confirm
- this [% terms.bug %], changing its state to [% get_status("NEW") FILTER html %]. Or, it may be
- directly resolved and marked [% get_status("RESOLVED") FILTER html %].
+ this [% terms.bug %], changing its state to
+ <b>[% display_value("bug_status", "CONFIRMED") FILTER html %]</b>.
+ Or, it may be directly resolved and marked
+ <b>[% display_value("bug_status", "RESOLVED") FILTER html %]</b>.
</dd>
- <dt>
- <b>[% get_status("NEW") FILTER html %]</b>
+ <dt class="confirmed">
+ [% display_value("bug_status", "CONFIRMED") FILTER html %]
</dt>
- <dd>
- This [% terms.bug %] has recently been added to the assignee's
- list of [% terms.bugs %] and must be processed. [% terms.Bugs %] in
- this state may be accepted, and become <b>[% get_status("ASSIGNED") FILTER html %]</b>, passed
- on to someone else, and remain <b>[% get_status("NEW") FILTER html %]</b>, or resolved and marked
- <b>[% get_status("RESOLVED") FILTER html %]</b>.
+ <dd class="confirmed">
+ This [% terms.bug %] is valid and has recently been filed.
+ [%+ terms.Bugs %] in this state become
+ <b>[% display_value("bug_status", "IN_PROGRESS") FILTER html %]</b>
+ when somebody is working on them, or become resolved and marked
+ <b>[% display_value("bug_status", "RESOLVED") FILTER html %]</b>.
</dd>
- <dt>
- <b>[% get_status("ASSIGNED") FILTER html %]</b>
+ <dt class="in_progress">
+ [% display_value("bug_status", "IN_PROGRESS") FILTER html %]
</dt>
- <dd>
- This [% terms.bug %] is not yet resolved, but is assigned to the
- proper person. From here [% terms.bugs %] can be given to another
- person and become <b>[% get_status("NEW") FILTER html %]</b>, or
- resolved and become <b>[% get_status("RESOLVED") FILTER html %]</b>.
+ <dd class="in_progress">
+ This [% terms.bug %] is not yet resolved, but is assigned to the
+ proper person who is working on the [% terms.bug %]. From here,
+ [%+ terms.bugs %] can be given to another person and become
+ <b>[% display_value("bug_status", "CONFIRMED") FILTER html %]</b>, or
+ resolved and become
+ <b>[% display_value("bug_status", "RESOLVED") FILTER html %]</b>.
</dd>
-
- <dt>
- <b>[% get_status("REOPENED") FILTER html %]</b>
- </dt>
- <dd>
- This [% terms.bug %] was once resolved, but the resolution was
- deemed incorrect. For example, a <b>[% get_resolution("WORKSFORME") FILTER html %]</b> [% terms.bug %] is
- <b>[% get_status("REOPENED") FILTER html %]</b> when more information shows up and
- the [% terms.bug %] is now reproducible. From here [% terms.bugs %] are
- either marked <b>[% get_status("ASSIGNED") FILTER html %]</b> or
- <b>[% get_status("RESOLVED") FILTER html %]</b>.
- </dd>
+
+ [% Hook.process('open-status') %]
</dl>
</td>
<td>
- <dl>
- <dd>
- No resolution yet. All [% terms.bugs %] which are in one of
- these "open" states have the resolution set to blank. All
- other [% terms.bugs %] will be marked with one of the following
- resolutions.
- </dd>
- </dl>
+ No resolution yet. All [% terms.bugs %] which are in one of
+ these "open" states have no resolution set.
</td>
</tr>
- <tr valign="top">
+ <tr class="header_row">
+ <td colspan="2">Closed [% terms.Bugs %]</td>
+ </tr>
+
+ <tr>
<td>
<dl>
- <dt>
- <b>[% get_status("RESOLVED") FILTER html %]</b>
+ <dt class="resolved">
+ [% display_value("bug_status", "RESOLVED") FILTER html %]
</dt>
- <dd>
- A resolution has been taken, and it is awaiting verification by
- QA. From here [% terms.bugs %] are either re-opened and become
- <b>[% get_status("REOPENED") FILTER html %]</b>, are marked
- <b>[% get_status("VERIFIED") FILTER html %]</b>, or are closed for
- good and marked <b>[% get_status("CLOSED") FILTER html %]</b>.
+ <dd class="resolved">
+ A resolution has been performed, and it is awaiting verification by
+ QA. From here [% terms.bugs %] are either reopened and given some
+ open status, or are verified by QA and marked
+ <b>[% display_value("bug_status", "VERIFIED") FILTER html %]</b>.
</dd>
- <dt>
- <b>[% get_status("VERIFIED") FILTER html %]</b>
+ <dt class="verified">
+ [% display_value("bug_status", "VERIFIED") FILTER html %]
</dt>
- <dd>
- QA has looked at the [% terms.bug %] and the resolution and
- agrees that the appropriate resolution has been taken. [% terms.Bugs %] remain
- in this state until the product they were reported
- against actually ships, at which point they become
- <b>[% get_status("CLOSED") FILTER html %]</b>.
+ <dd class="verified">
+ QA has looked at the [% terms.bug %] and the resolution and
+ agrees that the appropriate resolution has been taken. This is
+ the final status for [% terms.bugs %].
</dd>
-
- <dt>
- <b>[% get_status("CLOSED") FILTER html %]</b>
- </dt>
- <dd>
- The [% terms.bug %] is considered dead, the resolution is correct.
- Any zombie [% terms.bugs %] who choose to walk the earth again must
- do so by becoming <b>[% get_status("REOPENED") FILTER html %]</b>.
- </dd>
+
+ [% Hook.process('closed-status') %]
</dl>
</td>
<td>
<dl>
- <dt>
- <b>[% get_resolution("FIXED") FILTER html %]</b>
+ <dt class="fixed">
+ [% display_value("resolution", "FIXED") FILTER html %]
</dt>
- <dd>
+ <dd class="fixed">
A fix for this [% terms.bug %] is checked into the tree and
tested.
</dd>
- <dt>
- <b>[% get_resolution("INVALID") FILTER html %]</b>
+ <dt class="invalid">
+ [% display_value("resolution", "INVALID") FILTER html %]
</dt>
- <dd>
+ <dd class="invalid">
The problem described is not [% terms.abug %].
</dd>
- <dt>
- <b>[% get_resolution("WONTFIX") FILTER html %]</b>
+ <dt class="wontfix">
+ [% display_value("resolution", "WONTFIX") FILTER html %]
</dt>
- <dd>
+ <dd class="wontfix">
The problem described is [% terms.abug %] which will never be
fixed.
</dd>
- <dt>
- <b>[% get_resolution("DUPLICATE") FILTER html %]</b>
+ <dt class="duplicate">
+ [% display_value("resolution", "DUPLICATE") FILTER html %]
</dt>
- <dd>
+ <dd class="duplicate">
The problem is a duplicate of an existing [% terms.bug %].
- Marking [% terms.abug %] duplicate requires the [% terms.bug %]#
- of the duplicating [% terms.bug %] and will at least put
- that [% terms.bug %] number in the description field.
+ When [% terms.abug %] is marked as a
+ <b>[% display_value("resolution", "DUPLICATE") FILTER html %]</b>,
+ you will see which [% terms.bug %] it is a duplicate of,
+ next to the resolution.
</dd>
- <dt>
- <b>[% get_resolution("WORKSFORME") FILTER html %]</b>
+ <dt class="worksforme">
+ [% display_value("resolution", "WORKSFORME") FILTER html %]
</dt>
- <dd>
+ <dd class="worksforme">
All attempts at reproducing this [% terms.bug %] were futile,
and reading the code produces no clues as to why the described
behavior would occur. If more information appears later,
the [% terms.bug %] can be reopened.
</dd>
-
- <dt>
- <b>[% get_resolution("MOVED") FILTER html %]</b>
- </dt>
- <dd>
- The problem was specific to a related product
- whose [% terms.bugs %] are tracked in
- another [% terms.bug %] database.
- The [% terms.bug %] has been moved to that database.
- </dd>
+
+ [% Hook.process('resolution') %]
</dl>
</td>
</tr>
+ </tbody>
</table>
-<h2><a name="importance">Importance</h2>
-The importance of [% terms.abug %] is described as the combination of
-its <a href="#priority">priority</a> and <a href="#bug_severity">severity</a>,
-as described below.
+<h2>Other Fields</h2>
-<h2><a name="priority">Priority</a></h2>
-This field describes the importance and order in which [% terms.abug %]
-should be fixed. This field is utilized by the
-programmers/engineers to prioritize their work to be done. The
-available priorities range from <b>P1</b> (most important) to
-<b>P5</b> (least important).
+[% SET field_help_map = {} %]
+[% FOREACH field = bug_fields.keys %]
+ [% SET field_desc = field_descs.$field %]
+ [% field_help_map.$field_desc = { help => help_html.$field,
+ field => field } %]
+[% END %]
-<h2><a name="bug_severity">Severity</a></h2>
-This field describes the impact of [% terms.abug %].
+[%# These are fields that don't need to be documented, either because
+ # they have docs somewhere else in the UI, or they don't show up on bugs.
+ # %]
+[% SET skip_fields = [
+ 'days_elapsed',
+ 'everconfirmed',
+ 'reporter_accessible',
+ 'cclist_accessible',
+ 'bug_group',
+ 'commenter',
+ 'owner_idle_time',
+ 'bug_status',
+ 'resolution',
+] %]
-<table>
- <tr>
- <th>Blocker</th>
+<dl class="field_descriptions">
+[% FOREACH field_desc = field_help_map.keys.sort %]
+ [% SET field = field_help_map.${field_desc}.field %]
+ [% SET field_object = bug_fields.$field %]
- <td>Blocks development and/or testing work</td>
- </tr>
+ [% NEXT IF field_object.obsolete %]
+ [% NEXT IF !user.is_timetracker AND field_object.is_timetracking %]
- <tr>
- <th>Critical</th>
+ [% NEXT IF field == 'status_whiteboard' AND !Param('usestatuswhiteboard') %]
+ [% NEXT IF field == 'target_milestone' AND !Param('usetargetmilestone') %]
+ [% NEXT IF field == 'alias' AND !Param('usebugaliases') %]
- <td>crashes, loss of data, severe memory leak</td>
- </tr>
+ [%# For now we don't have help for attachment fields and so on. %]
+ [% NEXT IF field.match('\.') %]
- <tr>
- <th>Major</th>
+ [% NEXT IF skip_fields.contains(field) %]
- <td>major loss of function</td>
- </tr>
+ <dt id="[% field FILTER html %]">[% field_desc FILTER html %]</dt>
+ <dd>
+ [% SET help_text = field_help_map.${field_desc}.help %]
+ [% help_text FILTER none %]
+ </dd>
+[% END %]
+</dl>
- <tr>
- <th>Normal</th>
-
- <td>regular issue, some loss of functionality under specific circumstances</td>
- </tr>
-
-
- <tr>
- <th>Minor</th>
-
- <td>minor loss of function, or other problem where easy
- workaround is present</td>
- </tr>
-
- <tr>
- <th>Trivial</th>
-
- <td>cosmetic problem like misspelled words or misaligned
- text</td>
- </tr>
-
- <tr>
- <th>Enhancement</th>
-
- <td>Request for enhancement</td>
-</table>
-
-<h2><a name="rep_platform">Platform</a></h2>
-This is the hardware platform against which the [% terms.bug %] was
-reported. Legal platforms include:
-
-<ul>
- <li>All (happens on all platforms; cross-platform [% terms.bug %])</li>
-
- <li>Macintosh</li>
-
- <li>PC</li>
-</ul>
-<b>Note:</b> When searching, selecting the option "All" does not
-select [% terms.bugs %]
-assigned against any platform. It merely selects [% terms.bugs %] that are
-marked as occurring on all platforms, i.e. are designated "All".
-
-<h2><a name="op_sys">Operating System</a></h2>
-This is the operating system against which the [% terms.bug %] was
-reported. Legal operating systems include:
-
-<ul>
- <li>All (happens on all operating systems; cross-platform
- [% terms.bug %])</li>
-
- <li>Windows</li>
-
- <li>Mac OS</li>
-
- <li>Linux</li>
-</ul>
-Sometimes the operating system implies the platform, but not
-always. For example, Linux can run on PC and Macintosh and
-others.
-
-<h2><a name="assigned_to">Assigned To</a></h2>
-
-<p>
-This is the person in charge of resolving the [% terms.bug %]. Every time
-this field changes, the status changes to <b>[% get_status("NEW") FILTER html %]</b> to make it
-easy to see which new [% terms.bugs %] have appeared on a person's list.</p>
-
-<p>
-The default status for queries is set to [% get_status("NEW") FILTER html %],
-[%+ get_status("ASSIGNED") FILTER html %] and [% get_status("REOPENED") FILTER html %].
-When searching for [% terms.bugs %] that have been resolved or
-verified, remember to set the status field appropriately.
-</p>
-
-[% INCLUDE global/footer.html.tmpl %]
+[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/pages/linked.html.tmpl b/Websites/bugs.webkit.org/template/en/default/pages/linked.html.tmpl
index 52b1735..b5d8506 100644
--- a/Websites/bugs.webkit.org/template/en/default/pages/linked.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/pages/linked.html.tmpl
@@ -31,7 +31,7 @@
<p>
<pre class="bz_comment_text">
-[%- cgi.param("text") FILTER wrap_comment FILTER quoteUrls FILTER html -%]
+[%- cgi.param("text") FILTER quoteUrls FILTER html -%]
</pre>
</p>
@@ -46,7 +46,7 @@
<p>
<pre class="bz_comment_text">
-[%- cgi.param("text") FILTER wrap_comment FILTER quoteUrls -%]
+[%- cgi.param("text") FILTER quoteUrls -%]
</pre>
</p>
diff --git a/Websites/bugs.webkit.org/template/en/default/pages/quicksearch.html.tmpl b/Websites/bugs.webkit.org/template/en/default/pages/quicksearch.html.tmpl
index d551e7a..901f054 100644
--- a/Websites/bugs.webkit.org/template/en/default/pages/quicksearch.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/pages/quicksearch.html.tmpl
@@ -10,180 +10,332 @@
#
# The Original Code is the Bugzilla Bug Tracking System.
#
- # Contributor(s): N.N.
- # Marc Schumann <wurblzap@gmail.com>
+ # The Initial Developer of the Original Code is Everything Solved, Inc.
+ # Portions created by the Initial Developer are Copyright (C) 2009
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
#%]
-[% PROCESS global/variables.none.tmpl %]
+[% PROCESS "global/field-descs.none.tmpl" %]
[% INCLUDE global/header.html.tmpl
title = "$terms.Bugzilla QuickSearch",
- style = 'ul {margin-bottom: 2ex}
- ul li {margin-top: 2ex}
- ul li ul li {margin-top: 0}'
+ style_urls = ['skins/standard/page.css']
onload = 'document.forms[\'f\'].quicksearch.focus()'
%]
-<p style="font-size: 80%">
- If you are already familiar with the original
- <a href="query.cgi">[% terms.Bugzilla %] Search Form</a>,
- you may prefer <a href="page.cgi?id=quicksearchhack.html">this form</a>.
-</p>
+[% USE Bugzilla %]
-<p>
- Type in one or more words (or word fragments) to search for:
-</p>
+<p>Type in one or more words (or pieces of words) to search for:</p>
<form name="f" action="buglist.cgi" method="get"
onsubmit="if (this.quicksearch.value == '')
{ alert('Please enter one or more search terms first.');
return false; } return true;">
<input type="text" size="40" name="quicksearch">
- <input type="submit" value="Find" id="find">
+ <input type="submit" value="Search" id="find">
</form>
-<h2>Getting Started</h2>
-
<ul>
- <li>
- This is <strong>case-insensitive</strong> search:<br />
- <ul>
- <li><tt>table</tt>, <tt>Table</tt> and <tt>TABLE</tt> are all the same.</li>
- </ul>
+ <li><a href="#basics">The Basics</a></li>
+ <li><a href="#basic_examples">Examples of Simple Queries</a></li>
+ <li><a href="#fields">Fields You Can Search On</a></li>
+ <li><a href="#advanced_features">Advanced Features</a></li>
+ <li><a href="#shortcuts">Advanced Shortcuts</a></li>
+ <li><a href="#advanced_examples">Examples of Complex Queries</a></li>
+</ul>
+
+<h2 id="basics">The Basics</h2>
+
+<ul class="qs_help">
+ <li>If you just put a word or series of words in the search box,
+ [%+ terms.Bugzilla %] will search the
+ [%+ field_descs.product FILTER html %],
+ [%+ field_descs.component FILTER html %],
+ [%+ IF use_keywords %][%+ field_descs.keywords FILTER html %],[% END %]
+ [%+ IF Param('usebugaliases') %][% field_descs.alias FILTER html %],[% END %]
+ [%+ field_descs.short_desc FILTER html %],
+ [%+ IF Param('usestatuswhiteboard') %][% field_descs.status_whiteboard FILTER html %],[% END %]
+ and [% field_descs.longdesc FILTER html %] fields for your word or words.</li>
+
+ <li>Typing just a <strong>number</strong> in the search box will take
+ you directly to the [% terms.bug %] with that ID.
+ [% IF Param('usebugaliases') %]
+ Also, just typing the <strong>alias</strong> of [% terms.abug %]
+ will take you to that [% terms.bug %].
+ [% END %]
</li>
- <li>
- This is <strong>all words as substrings</strong>
- search.<br />
- Therefore you should <strong>use stems</strong> to get better results:
+
+ <li>Adding more terms <strong>narrows down</strong> the search, it does not
+ expand it. (In other words, [% terms.Bugzilla %] searches for
+ [%+ terms.bugs %] that match <em>all</em> your criteria, not
+ [%+ terms.bugs %] that match <em>any</em> of your criteria.)</li>
+
+ <li>Searching is <strong>case-insensitive</strong>. So <kbd>table</kbd>,
+ <kbd>Table</kbd>, and <kbd>TABLE</kbd> are all the same.</li>
+
+ <li>[% terms.Bugzilla %] does not just search for the exact word you put in,
+ but also for any word that <strong>contains</strong> that word.
+ So, for example, searching for "cat" would also find [% terms.bugs %]
+ that contain it as part of other words—for example, [% terms.abug %]
+ mentioning "<strong>cat</strong>ch" or "certifi<strong>cat</strong>e". It
+ will not find partial words in the [% field_descs.longdesc FILTER html %]
+ or [% field_descs.keywords FILTER html %] fields,
+ though—only full words are matched, there.</li>
+
+ <li>By default, only <strong>open</strong> [% terms.bugs %] are
+ searched. If you want to know how to also search closed [% terms.bugs %],
+ see the <a href="#shortcuts">Advanced Shortcuts</a> section.</li>
+
+ <li>If you want to search <strong>specific fields</strong>, you do it like
+ <kbd>field:value</kbd>, where <kbd>field</kbd> is one of the
+ <a href="#fields">field names</a> lower down in this
+ document and <kbd>value</kbd> is the value you want to search for
+ in that field. If you put commas in the <kbd>value</kbd>, then it is
+ interpreted as a list of values, and [% terms.bugs %] that match
+ <em>any</em> of those values will be searched for.</li>
+</ul>
+
+<h2 id="basic_examples">Examples of Simple Queries</h2>
+
+<p>Here are some examples of how to write some simple queries.
+ <a href="#advanced_examples">Examples for more complex queries</a> can be
+ found lower in this page.</p>
+
+<ul class="qs_help">
+ <li>All open [% terms.bugs %] where userA@company.com is in the CC list
+ (no need to mention open [% terms.bugs %], this is the default):<br>
+ <kbd>cc:userA@company.com</kbd></li>
+ <li>All unconfirmed [% terms.bugs %] in product productA (putting the
+ [%+ terms.bug %] status at the first position make it being automagically
+ considered as [% terms.abug %] status):<br>
+ <kbd>UNCONFIRMED product:productA</kbd>
+ <li>All open and closed [% terms.bugs %] reported by userB@company.com
+ (we must specify ALL as the first word, else only open [% terms.bugs %]
+ are taken into account):<br>
+ <kbd>ALL reporter:userB@company.com</kbd>
+ <li>All open [% terms.bugs %] with severity blocker or critical with the
+ target milestone set to 2.5:<br>
+ <kbd>severity:blocker,critical milestone:2.5</kbd>
+ <li>All open [% terms.bugs %] in the component Research & Development
+ with priority P1 or P2 (we must use quotes for the component as its name
+ contains whitespaces):<br>
+ <kbd>component:"Research & Development" priority:P1,P2</kbd></li>
+</ul>
+
+<h2 id="fields">Fields You Can Search On</h2>
+
+<p>You can specify any of these fields like <kbd>field:value</kbd>
+ in the search box, to search on them. You can also abbreviate
+ the field name, as long as your abbreviation matches only one field name.
+ So, for example, searching on <kbd>stat:VERIFIED</kbd> will find all
+ [%+ terms.bugs %] in the <kbd>VERIFIED</kbd> status. Some fields have
+ multiple names, and you can use any of those names to search for them.</p>
+
+[% IF Bugzilla.active_custom_fields.size %]
+ [% SET first_field = Bugzilla.active_custom_fields.0 %]
+ <p>For custom fields, they can be used and abbreviated
+ based on the part of their name <em>after</em> the <kbd>cf_</kbd>
+ if you'd like, in addition to their standard name starting with
+ <kbd>cf_</kbd>. So for example,
+ <kbd>[% first_field.name FILTER html %]</kbd> can be
+ referred to as
+ <kbd>[% first_field.name.replace('^cf_') FILTER html %]</kbd>,
+ also. However, if this causes a conflict between the standard
+ [%+ terms.Bugzilla %] field names and the custom field names, the
+ standard field names always take precedence.</p>
+[% END %]
+
+[% SET field_table = {} %]
+[% FOREACH field = quicksearch_field_names.keys %]
+ [% description = field_descs.$field %]
+ [% field_table.$description = quicksearch_field_names.${field} %]
+[% END %]
+
+
+<table cellspacing="0" cellpadding="0" border="0" class="qs_fields">
+ <thead>
+ <tr>
+ <th class="field_name">Field</th>
+ <th class="field_nickname">Field Name(s) For Search</th>
+ </tr>
+ </thead>
+ <tbody>
+ [% FOREACH desc = field_table.keys.sort %]
+ <tr>
+ <td class="field_name">[% desc FILTER html %]</td>
+ <td class="field_nickname">
+ [% FOREACH nickname = field_table.$desc %]
+ <kbd>[% nickname FILTER html %]</kbd>
+ [% ", " UNLESS loop.last %]
+ [% END %]
+ </tr>
+ [% END %]
+ </tbody>
+</table>
+
+<h2 id="advanced_features">Advanced Features</h2>
+
+<ul class="qs_help">
+ <li>If you want to search for a <strong>phrase</strong> or something that
+ contains spaces, commas, colons or quotes, you must put it in quotes, like:
+ <kbd>"yes, this is a phrase"</kbd>. You must also use quotes to search for
+ characters that would otherwise be interpreted specially by quicksearch.
+ For example, <kbd>"this|that"</kbd> would search for the literal string
+ <em>this|that</em> and would not be parsed as <kbd>"this OR that"</kbd>.
+ Also, <kbd>"-field:value"</kbd> would search for the literal phrase
+ <em>-field:value</em> and would not be parsed as
+ <kbd>"NOT field:value"</kbd>.</li>
+
+ <li>You can use <strong>AND</strong>, <strong>NOT</strong>,
+ and <strong>OR</strong> in searches.
+
+ You can also use <kbd>-</kbd> to mean "NOT", and <kbd>|</kbd> to mean "OR".
+ There is no special character for "AND", because by default any search
+ terms that are separated by a space are joined by an "AND".
+ Examples:
<ul>
<li>
- Use <tt>localiz</tt> instead of <tt>localize</tt> or
- <tt>localization</tt>.
+ <strong>NOT</strong>:<br>
+ Use <kbd><strong>-</strong><em>summary:foo</em></kbd> to exclude
+ [%+ terms.bugs %] with <kbd>foo</kbd> in the summary.<br>
+ <kbd><em>NOT summary:foo</em></kbd> would have the same effect.
</li>
- <li>
- Use <tt>bookmark</tt> instead of <tt>bookmarks</tt> or
- <tt>bookmarking</tt>.
- </li>
- </ul>
+ <li>
+ <strong>AND</strong>:<br>
+ <kbd><em>foo bar</em></kbd> searches for [% terms.bugs %] that contains
+ both <kbd>foo</kbd> and <kbd>bar</kbd>.<br>
+ <kbd><em>foo AND bar</em></kbd> would have the same effect.
+ </li>
+ <li>
+ <strong>OR</strong>:<br>
+ <kbd><em>foo<strong>|</strong>bar</em></kbd> would search
+ for [% terms.bugs %] that contain <kbd>foo</kbd> OR <kbd>bar</kbd>.<br>
+ <kbd><em>foo OR bar</em></kbd> would have the same effect.<br>
+ </li>
+ </ul>
+
+ <p>You cannot use | nor OR to enumerate possible values for a given field.
+ You must use commas instead. So <kbd>field:value1,value2</kbd> does what
+ you expect, but <kbd>field:value1|value2</kbd> would be treated as
+ <kbd>field:value1 OR value2</kbd>, which means value2 is not bound to
+ the given field.</p>
+
+ <p>OR has higher precedence than AND; AND is the top level operation.
+ For example:</p>
+ <p>Searching for <em><kbd>url|location bar|field -focus</kbd></em> means
+ (<kbd>url</kbd> OR <kbd>location</kbd>) AND (<kbd>bar</kbd> OR
+ <kbd>field</kbd>) AND (NOT <kbd>focus</kbd>)</p>
</li>
</ul>
-<h2><a name="features">Features</a></h2>
+<h2 id="shortcuts">Advanced Shortcuts</h2>
-<ul>
- <li>
- Boolean operations: “<tt>-foo</tt>” (NOT),
- “<tt>foo bar</tt>” (AND),
- “<tt>foo|bar</tt>” (OR).
- <ul>
- <li>
- <strong>NOT</strong>:<br />
- Use <tt><b>-</b><i>foo</i></tt> to exclude [% terms.bugs %]
- with <tt><i>foo</i></tt> in the summary.
- </li>
- <li>
- <strong>AND</strong>:<br />
- Space-separated words are treated as a conjunction.
- </li>
- <li>
- <strong>OR</strong>:<br />
- Within a word, "|"-separated parts denote alternatives.
- </li>
- <li>
- Besides "|", a comma can be used to separate alternatives.
- </li>
- <li>
- OR has higher precedence than AND; AND is the top level operation.
- </li>
- </ul>
- <i>Example:</i>
- <tt>url,location bar,field -focus</tt> means
- (<tt>url</tt> OR <tt>location</tt>) AND (<tt>bar</tt> OR <tt>field</tt>)
- AND (NOT <tt>focus</tt>)
- </li>
- <li>
- Use <tt>+foo</tt> to search for [% terms.bugs %] where the
- <strong>summary</strong> contains <tt>foo</tt> as a
- <strong>substring</strong>.<br/>
- Use <tt>#foo</tt> to search for [% terms.bugs %] where the
- <strong>summary</strong> contains the <strong>word</strong> <tt>foo</tt>.
- <ul>
- <li>
- <tt>+brow</tt> does not find all [% terms.bugs %] in the
- <tt>Browser</tt> product.
- </li>
- <li>
- <tt>#title</tt> does not find [% terms.bugs %] with <tt>titlebar</tt>
- or <tt>titled</tt>.
- </li>
- </ul>
- Phrases with special chars (space, comma, +, -, #, …) can be
- <strong>quoted</strong>:
- <ul>
- <li>
- <tt>"lock icon"</tt>
- </li>
- </ul>
- </li>
- <li>
- <strong>Open vs. Resolved [% terms.Bugs %]</strong>:<br />
- By default, only open (i.e. unresolved) [% terms.bugs %] are shown.
- Use <tt>+DUP</tt> as first word in your search to include duplicate
- [%+ terms.bugs %] in your search,
- <tt>FIXED</tt> to search for fixed [%+ terms.bugs %] only,
- or <tt>ALL</tt> to search all [% terms.bugs %],
- regardless of status or resolution.
- Searching for duplicates is recommended if you can't find an open
- [%+ terms.bug %] directly.
- <ul>
- <li>
- <tt>+DUP,FIXED table border</tt>
- </li>
- <li>
- <tt>ALL mouse wheel</tt>
- </li>
- </ul>
- </li>
- <li>
- <strong>Focus the Search with Products &
- Components</strong>:<br />
- To search for [% terms.bugs %] in product "Foo Bar" only, add
- <tt>:foo</tt> or <tt>:bar</tt> or both to your search.
- You can do this with any substring of a
- <a href="describecomponents.cgi">product or component</a> to focus the
- search.
- </li>
+<p>In addition to using <a href="#fields">field names</a> to search
+ specific fields, there are certain characters or words that you can
+ use as a "shortcut" for searching certain fields:</p>
+
+<table cellspacing="0" cellpadding="0" border="0" class="qs_fields">
+ <thead>
+ <tr>
+ <th class="field_name">Field</th>
+ <th class="field_nickname">Shortcut(s)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="field_name">[% field_descs.bug_status FILTER html %]</td>
+ <td class="field_nickname">
+ Make the <strong>first word</strong> of your search the name of any
+ status, or even an abbreviation of any status, and [% terms.bugs %]
+ in that status will be searched. <strong><kbd>ALL</kbd></strong>
+ is a special shortcut that means "all statuses".
+ <strong><kbd>OPEN</kbd></strong> is a special shortcut that means
+ "all open statuses".
+ </td>
+ </tr>
+ <tr>
+ <td class="field_name">[% field_descs.resolution FILTER html %]</td>
+ <td class="field_nickname">
+ Make the <strong>first word</strong> of your search the name of any
+ resolution, or even an abbreviation of any resolution, and
+ [%+ terms.bugs %] with that resolution will be searched. For example,
+ making <kbd>FIX</kbd> the first word of your search will find all
+ [%+ terms.bugs %] with a resolution of <kbd>FIXED</kbd> .
+ </tr>
+ <tr>
+ <td class="field_name">[% field_descs.priority FILTER html %]</td>
+ <td class="field_nickname">"<strong>P1</strong>" (as a word anywhere in
+ the search) means "find [% terms.bugs %] with the highest priority.
+ "P2" means the second-highest priority, and so on.
+ <p>Searching for "<strong>P1-3</strong>" will find [% terms.bugs %] in
+ any of the three highest priorities, and so on.</p>
+ </td>
+ </tr>
+ <tr>
+ <td class="field_name">[% field_descs.assigned_to FILTER html %]</td>
+ <td class="field_nickname"><strong>@</strong><em>value</em></td>
+ </tr>
+ <tr>
+ <td class="field_name">[% field_descs.product FILTER html %] or
+ [%+ field_descs.component FILTER html %]</td>
+ <td class="field_nickname"><strong>:</strong><em>value</em></td>
+ </tr>
+ [% IF use_keywords %]
+ <tr>
+ <td class="field_name">[% field_descs.keywords FILTER html %]</td>
+ <td class="field_nickname"><strong>!</strong><em>value</em></td>
+ </tr>
+ [% END %]
+ <tr>
+ [% SET key = "flagtypes.name" %]
+ <td class="field_name">[% field_descs.$key FILTER html %]</td>
+ <td class="field_nickname">
+ <em>flag</em><strong>?</strong><em>requestee</em>
+ </td>
+ </tr>
+ <tr>
+ <td class="field_name">[% field_descs.longdesc FILTER html %]
+ or [% field_descs.short_desc FILTER html %]</td>
+ <td class="field_nickname">
+ <strong>#</strong><em>value</em>
+ </td>
+ </tr>
+ [% IF Param('usestatuswhiteboard') %]
+ <tr>
+ <td class="field_name">[% field_descs.short_desc FILTER html %]
+ or [% field_descs.status_whiteboard FILTER html %]</td>
+ <td class="field_nickname"><strong>[</strong><em>value</em></td>
+ </tr>
+ [% END %]
+ </tbody>
+</table>
+
+<h2 id="advanced_examples">Examples of Complex Queries</h2>
+
+<p>It is pretty easy to write rather complex queries without too much effort.
+ For very complex queries, you have to use the
+ <a href="query.cgi?format=advanced">Advanced Search</a> form.</p>
+
+<ul class="qs_help">
+ <li>All [% terms.bugs %] reported by userA@company.com or assigned to him
+ (the initial @ is a shortcut for the assignee, see the
+ <a href="#shortcuts">Advanced Shortcuts</a> section above):<br>
+ <kbd>ALL @userA@company.com OR reporter:userA@company.com</kbd></li>
+ <li>All open [% terms.bugs %] in product productA with either severity
+ blocker, critical or major, or with priority P1, or with the blocker+
+ flag set, and which are neither assigned to userB@company.com nor to
+ userC@company.com (we make the assumption that there are only two users
+ matching userB and userC, else we would write the whole login name):<br>
+ <kbd>:productA sev:blocker,critical,major OR pri:P1 OR flag:blocker+ -assign:userB,userC</kbd></li>
+ <li>All FIXED [% terms.bugs %] with the blocker+ flag set, but without
+ the approval+ nor approval? flags set:<br>
+ <kbd>FIXED flag:blocker+ -flag:approval+ -flag:approval?</kbd></li>
+ <li>[% terms.Bugs %] with <em>That's a "unusual" issue</em> in the
+ [%+ terms.bug %] summary (double quotes are escaped using <em>\"</em>):<br>
+ <kbd>summary:"That's a \"unusual\" issue"</kbd></li>
</ul>
-<h2>More Tips</h2>
-
-<ul>
- <li>
- You can also use this tool to <strong>lookup</strong> a [% terms.bug %] by
- its number:<br />
- <ul>
- <li><tt>12345</tt></li>
- </ul>
- </li>
- <li>
- A comma-separated list of [% terms.bug %] numbers gives you a list of these
- [%+ terms.bugs %]:<br />
- <ul>
- <li><tt>12345,23456,34567</tt></li>
- </ul>
- </li>
-</ul>
-
-<p>
- By default, the following fields are searched: Summary, Keywords, Product,
- Component, Status Whiteboard. If a word looks like a part of a URL, that field
- is included in the search, too.
-</p>
-<hr>
-
-<p>
- Use the powerful <a href="query.cgi">[% terms.Bugzilla %] Search Form</a>
- for advanced queries.
-</p>
-
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/pages/quicksearchhack.html.tmpl b/Websites/bugs.webkit.org/template/en/default/pages/quicksearchhack.html.tmpl
deleted file mode 100644
index 4d96f5d..0000000
--- a/Websites/bugs.webkit.org/template/en/default/pages/quicksearchhack.html.tmpl
+++ /dev/null
@@ -1,387 +0,0 @@
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # Contributor(s): N.N.
- # Marc Schumann <wurblzap@gmail.com>
- #%]
-
-[% PROCESS global/variables.none.tmpl %]
-
-[% INCLUDE global/header.html.tmpl
- title = "$terms.Bugzilla QuickSearch (for Hackers)",
- style = 'th {text-align: left}'
- onload = 'document.forms[\'f\'].quicksearch.focus()'
- %]
-
-<p>
- Type in one or more words (or word fragments) to search for:
-</p>
-
-<form name="f" action="buglist.cgi" method="get"
- onsubmit="if (this.quicksearch.value == '')
- { alert('Please enter one or more search terms first.');
- return false; } return true;">
- <input type="text" size="40" name="quicksearch">
- <input type="submit" value="Find" id="find">
- <input type="submit" name="load" value="Load Search Form" id="load">
-</form>
-
-<p>
- This is a case-insensitive “all words as substrings” search;
- words are separated by spaces.
- By default, the following fields are relevant: Summary, Keywords,
- Product, Component, Status Whiteboard.
- If a word looks like a part of a URL, that field is included in the search,
- too.
-</p>
-<p>
- The generic format for a “word” is
- <tt>field1,…,fieldN:value1,…,valueM</tt>.
- A [% terms.bug %] qualifies if at least one of the values occurs as a
- substring in at least one of the fields.
- For example, <tt>assignee,reporter,qa:ibm,sun</tt> will give you
- [%+ terms.bugs %] where the assignee, reporter, or qa contact has a login
- that contains <tt>ibm</tt> or <tt>sun</tt>.
- If only <tt>value1,…,valueM</tt> is given, the prefix (roughly) defaults to
- <tt>summary,keywords,product,component,statuswhiteboard:</tt> as noted above.
- You can use <tt>-<i>word</i></tt> to express the logical negation of
- <tt><i>word</i></tt>.
-</p>
-<p>
- Here is a complete listing of available fields (the Shortcut column is just
- for access speed):
-</p>
-
-<table border="1">
-<thead>
-<tr>
- <th>Searched by default</th>
- <th>Shortcut</th>
- <th>Field Name</th>
- <th>Aliases</th>
- <th>Description</th>
-</tr>
-</thead>
-
-<!-- Status, Resolution, Platform, OS, Priority, Severity -->
-
-<tr>
- <td> </td>
- <td rowspan="2">
- <tt>UNCO,NEW,…,CLOS,<br>FIX,DUP,…<i>(as first word)</i></tt>
- </td>
- <td><tt>status</tt></td>
- <td> </td>
- <td>
- <a href="page.cgi?id=fields.html#status">Status</a>
- <i>(“bug_status”)</i>
- </td>
-</tr>
-<tr>
- <td> </td>
- <td><tt>resolution</tt></td>
- <td> </td>
- <td><a href="page.cgi?id=fields.html#resolution">Resolution</a></td>
-</tr>
-<tr>
- <td> </td>
- <td><i>as-is</i></td>
- <td><tt>platform</tt></td>
- <td> </td>
- <td>
- <a href="page.cgi?id=fields.html#rep_platform">Platform</a>
- <i>(“rep_platform”)</i>
- </td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>os</tt></td>
- <td><tt>opsys</tt></td>
- <td>
- <a href="page.cgi?id=fields.html#op_sys">OS</a>
- <i>(“op_sys”)</i>
- </td>
-</tr>
-<tr>
- <td> </td>
- <td><tt>p1,p2</tt> <i>or</i> <tt>p1-2</tt></td>
- <td><tt>priority</tt></td>
- <td><tt>pri</tt></td>
- <td><a href="page.cgi?id=fields.html#priority">Priority</a></td>
-</tr>
-<tr>
- <td> </td>
- <td><tt>blo,cri,…,enh</tt></td>
- <td><tt>severity</tt></td>
- <td><tt>sev</tt></td>
- <td>
- <a href="page.cgi?id=fields.html#bug_severity">Severity</a>
- <i>(“bug_severity”)</i>
- </td>
-</tr>
-
-<!-- People: AssignedTo, Reporter, QA Contact, CC, Added comment -->
-<!-- Added comment is missing!!!! -->
-
-<tr>
- <td> </td>
- <td><b>@</b><i>assignee</i></td>
- <td><tt>assignedto</tt></td>
- <td><tt>assignee</tt></td>
- <td>
- <a href="page.cgi?id=fields.html#assigned_to">Assignee</a>
- <i>(“assigned_to”)</i>
- </td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>reporter</tt></td>
- <td><tt>rep</tt></td>
- <td>Reporter (login)</td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>qa</tt></td>
- <td><tt>qacontact</tt></td>
- <td>QA Contact (login) <i>(“qa_contact”)</i></td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>cc</tt></td>
- <td> </td>
- <td>CC (login)</td>
-</tr>
-
-<!-- Product, Version, Component, Target Milestone -->
-
-<tr>
- <td><i>yes</i></td>
- <td rowspan="2"><b>:</b><i>area</i></td>
- <td><tt>product</tt></td>
- <td><tt>prod</tt></td>
- <td>Product (enum)</td>
-</tr>
-<tr>
- <td><i>yes</i></td>
- <td><tt>component</tt></td>
- <td><tt>comp</tt></td>
- <td><a href="describecomponents.cgi">Component</a></td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>version</tt></td>
- <td><tt>ver</tt></td>
- <td>Version (enum)</td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>milestone</tt></td>
- <td><tt>target, targetmilestone</tt></td>
- <td>Target Milestone <i>(“target_milestone”)</i></td>
-</tr>
-
-<!-- Summary, Description, URL, Status whiteboard, Keywords -->
-
-<tr>
- <td><i>yes</i></td>
- <td> </td>
- <td><tt>summary</tt></td>
- <td><tt>shortdesc</tt></td>
- <td>
- [% terms.Bug %] Summary (short text)
- <i>(“short_desc”)</i>
- </td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>description</tt></td>
- <td><tt>desc, longdesc<!--, comment--></tt></td>
- <!-- reserve "comment" for "added comment" login search?! -->
- <td>[% terms.Bug %] Description / Comments (long text)</td>
-</tr>
-<tr>
- <td><i>depends</i></td>
- <td> </td>
- <td><tt>url</tt></td>
- <td> </td>
- <td>URL <i>(“bug_file_loc”)</i></td>
-</tr>
-<tr>
- <td><i>yes</i></td>
- <td> </td>
- <td><tt>statuswhiteboard</tt></td>
- <td><tt>sw, whiteboard</tt></td>
- <td>Status Whiteboard <i>(“status_whiteboard”)</i></td>
-</tr>
-<tr>
- <td><i>yes</i></td>
- <td><b>!</b><i>keyword</i></td>
- <td><tt>keywords</tt></td>
- <td><tt>kw</tt></td>
- <td><a href="describekeywords.cgi">Keywords</a></td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>group</tt></td>
- <td> </td>
- <td>Group</td>
-</tr>
-
-<!-- Flags -->
-
-<tr>
- <td> </td>
- <td rowspan="2"><i>flag</i><b>?</b><i>requestee</i></td>
- <td><tt>flag</tt></td>
- <td> </td>
- <td>Flag name and status (+, - or ?)</td>
-</tr>
-<tr>
- <td> </td>
- <td><tt>requestee</tt></td>
- <td><tt>req</tt></td>
- <td>Flag requestee (login)</td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>setter</tt></td>
- <td><tt>set</tt></td>
- <td>Flag setter (login)</td>
-</tr>
-
-<!-- Attachments -->
-
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>attachmentdesc</tt></td>
- <td><tt>attachdesc</tt></td>
- <td>
- Attachment Description
- <i>(“attachments.description”)</i>
- </td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>attachmentdata</tt></td>
- <td><tt>attachdata</tt></td>
- <td>Attachment Data <i>(“attach_data.thedata”)</i></td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>attachmentmimetype</tt></td>
- <td><tt>attachmimetype</tt></td>
- <td>Attachment mime-type <i>(“attachments.mimetype”)</i></td>
-</tr>
-<tr>
- <td> </td>
- <td> </td>
- <td><tt>votes</tt></td>
- <td> </td>
- <td>
- Number of votes<br>
- (votes:<i>N</i> and votes>=<i>N</i> mean "at least N votes",
- votes><i>N</i> means "more than N votes")
- </td>
-</tr>
-</table>
-
-<p>
- Examples for some useful abbreviations:
-</p>
-<table border="1">
-<thead>
-<tr>
- <th>Syntax</th>
- <th>Semantics and Examples</th>
-</tr>
-</thead>
-
-<!--
-<tr>
- <td><i>STAT</i> <i>(as first word)</i></td>
- <td><b>status,resolution:</b> <i>STAT</i></td>
-</tr>
-<tr>
- <td></td>
- <td></td>
-</tr>
-<tr>
- <td><tt>ALL</tt> <i>(as first word)</i></td>
- <td><i>include all resolved [% terms.bugs %] in your search</i></td>
-</tr>
-<tr>
- <td><tt>+DUP,FIXED</tt> <i>(as first word)</i></td>
- <td><i>include DUPLICATE and FIXED [% terms.bugs %] in your search</i></td>
-</tr>
--->
-
-<tr>
- <td><b>:</b><i>area</i></td>
- <td><b>product,component:</b><i>area</i></td>
-</tr>
-<tr>
- <td><i>sev</i></td>
- <td><b>severity:</b><i>sev</i></td>
-</tr>
-<tr>
- <td><tt>blo,cri,maj</tt></td>
- <td><i>severe [% terms.bugs %]</i></td>
-</tr>
-<tr>
- <td><tt>enh</tt></td>
- <td><i>enhancement requests</i></td>
-</tr>
-<tr>
- <td><b>p</b><i>level</i></td>
- <td><b>priority:</b><i>level</i></td>
-</tr>
-<tr>
- <td><tt>p1</tt></td>
- <td><i>very high-priority [% terms.bugs %]</i></td>
-</tr>
-<tr>
- <td><tt>p1-2</tt></td>
- <td><i>high-priority [% terms.bugs %]</i></td>
-</tr>
-<tr>
- <td><b>@</b><i>assignee</i></td>
- <td><b>assignedto:</b><i>assignee</i></td>
-</tr>
-<tr>
- <td><b>!</b><i>keyword</i></td>
- <td><b>keywords:</b><i>keyword</i></td>
-</tr>
-<tr>
- <td><i>flag</i><b>?</b><i>requestee</i></td>
- <td><b>flag:</b><i>flag?</i> <b>requestee:</b><i>requestee</i></td>
-</tr>
-</table>
-
-<p>
- More information can be found in the
- <a href="page.cgi?id=quicksearch.html#features">“Features”</a>
- section on the <a href="page.cgi?id=quicksearch.html">introductory page</a>.
-</p>
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/pages/release-notes.html.tmpl b/Websites/bugs.webkit.org/template/en/default/pages/release-notes.html.tmpl
index 3280ce6..3cba644 100644
--- a/Websites/bugs.webkit.org/template/en/default/pages/release-notes.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/pages/release-notes.html.tmpl
@@ -15,479 +15,1221 @@
# Everything Solved. All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Frédéric Buclin <LpSolit@gmail.com>
#%]
[% PROCESS global/variables.none.tmpl %]
+[% SET title = "$terms.Bugzilla 4.2 Release Notes" %]
[% INCLUDE global/header.html.tmpl
- title = "$terms.Bugzilla 3.2.3 Release Notes"
- style_urls = ['skins/standard/release-notes.css']
+ title = title
+ style_urls = ['skins/standard/page.css']
%]
-<h2>Table of Contents</h2>
+<h1>[% title FILTER html %]</h1>
<ul class="bz_toc">
- <li><a href="#v32_introduction">Introduction</a></li>
- <li><a href="#v32_point">Updates In This 3.2.x Release</a></li>
- <li><a href="#v32_security">Security Fixes In This 3.2.x Release</a></li>
- <li><a href="#v32_req">Minimum Requirements</a></li>
- <li><a href="#v32_feat">New Features and Improvements</a></li>
- <li><a href="#v32_issues">Outstanding Issues</a></li>
- <li><a href="#v32_upgrading">How to Upgrade From An Older Version</a></li>
- <li><a href="#v32_code_changes">Code Changes Which May Affect
- Customizations</a></li>
- <li><a href="#v32_previous">Release Notes for Previous Versions</a></li>
+ <li><a href="#v42_introduction">Introduction</a></li>
+ <li><a href="#v42_point">Updates in this 4.2.x Release</a></li>
+ <li><a href="#v42_req">Minimum Requirements</a></li>
+ <li><a href="#v42_feat">New Features and Improvements</a></li>
+ <li><a href="#v42_issues">Outstanding Issues</a></li>
+ <li><a href="#v42_code_changes">Code Changes Which May Affect
+ Customizations and Extensions</a></li>
+ <li><a href="#v42_previous">Release Notes for Previous Versions</a></li>
</ul>
-<h2><a name="v32_introduction"></a>Introduction</h2>
+<h2 id="v42_introduction">Introduction</h2>
-<p>Welcome to [% terms.Bugzilla %] 3.2! This is our first major feature
- release since [% terms.Bugzilla %] 3.0, and it brings a lot of great
- improvements and polish to the [% terms.Bugzilla %] experience.</p>
+<p>Welcome to [% terms.Bugzilla %] 4.2! It has been almost a year since we
+ released [% terms.Bugzilla %] 4.0 on February 2011, and this new major
+ release comes with several new features and improvements. This release
+ contains major improvements to search, support for SQLite, improved
+ WebServices, and lots of other enhancements.</p>
-<p>If you're upgrading, make sure to read <a href="#v32_upgrading">How to
- Upgrade From An Older Version</a>. If you are upgrading from a release
- before 3.0, make sure to read the release notes for all the
- <a href="#v32_previous">previous versions</a> in between your version
- and this one, <strong>particularly the "Notes For Upgraders" section of each
- version's release notes</strong>.</p>
+<p>If you are upgrading from a release before 4.0, make sure to read the
+ release notes for all the <a href="#v42_previous">previous versions</a>
+ in between your version and this one, <strong>particularly the Upgrading
+ section of each version's release notes</strong>.</p>
-<h2><a name="v32_point">Updates in this 3.2.x Release</a></h2>
+<h2 id="v42_point">Updates in this 4.2.x Release</h2>
-<p>This section describes what's changed in the most recent b<!-- -->ug-fix
- releases of [% terms.Bugzilla %] after 3.2. We only list the
- most important fixes in each release. If you want a detailed list of
- <em>everything</em> that's changed in each version, you should use our
- <a href="http://www.bugzilla.org/status/changes.html">Change Log
- Page</a>.</p>
+<h3>4.2.1</h3>
-<h3>3.2.3</h3>
+<p>This release fixes two security issues. See the
+ <a href="http://www.bugzilla.org/security/3.6.8/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in this
+ release:</p>
<ul>
- <li>[% terms.Bugzilla %] is now compatible with MySQL 5.1.x versions 5.1.31
- and greater.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=480001">[% terms.Bug %] 480001</a>)</li>
- <li>On Windows, [% terms.Bugzilla %] sometimes would send mangled emails
- (that would often fail to send).
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=467920">[% terms.Bug %] 467920</a>)</li>
- <li><code>recode.pl</code> would sometimes crash when trying to convert
- databases from older versions of [% terms.Bugzilla %].
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=431201">[% terms.Bug %] 431201</a>)</li>
- <li>Running a saved search with Unicode characters in its name would
- cause [% terms.Bugzilla %] to crash.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=477513">[% terms.Bug %] 477513</a>)</li>
- <li>[% terms.Bugzilla %] clients like Mylyn can now update [% terms.bugs %]
- again (the [% terms.bug %] XML format now contains a "token" element that
- can be used when updating a bug).
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=476678">[% terms.Bug %] 476678</a>)</li>
- <li>For installations using the <code>shadowdb</code> parameter,
- [% terms.Bugzilla %] was accidentally writing to the "tokens" table
- in the shadow database (instead of the master database) when using the
- "Change Several [% terms.Bugs %] at Once" page.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=476943">[% terms.Bug %] 476943</a>)</li>
+ <li>Due to a regression introduced when fixing CVE-2012-0453, if an XML-RPC
+ client sets the charset as part of its Content-Type header, we were
+ incorrectly rejecting the request. The header is now correctly parsed.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=731219">[% terms.Bug %] 731219</a>)</li>
+ <li>Email notifications about status changes in blockers were incorrectly
+ formatted. Several pieces of text were missing in the emails.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=731586">[% terms.Bug %] 731586</a>)</li>
+ <li>Many [% terms.bugs %] related to the searching system have been fixed.
+ (<a href="https://bugzilla.mozilla.org/buglist.cgi?bug_id=58179,715270,730984,731163,737436,745320">
+ [% terms.Bugs %] 58179, 715270, 730984, 731163, 737436 and 745320</a>)</li>
+ <li>When using the QuickSearch box, complex queries are now parsed correctly.
+ It also behaves correctly with non-ASCII characters (such as é, ä, ü, etc.).
+ (<a href="https://bugzilla.mozilla.org/buglist.cgi?bug_id=554819,663377,730207">
+ [% terms.Bugs %] 554819, 663377 and 730207</a>)</li>
+ <li>The 'take' link besides the assignee field now works correctly when
+ the <kbd>usemenuforusers</kbd> parameter is turned on.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=734997">[% terms.Bug %] 734997</a>)</li>
+ <li>URLs in the 'Total' row at the bottom of tabular reports were broken
+ when JavaScript was enabled and a user field was used for the vertical
+ axis.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=731323">[% terms.Bug %] 731323</a>)</li>
+ <li>Some performance problems have been fixed for installations with many
+ products, components or versions.
+ (<a href="https://bugzilla.mozilla.org/buglist.cgi?bug_id=695514,731055">
+ [% terms.Bugs %] 695514 and 731055</a>)</li>
+ <li>A new hook named <kbd>buglist_column_joins</kbd> has been added to let
+ extensions alter the <kbd>Bugzilla::Search::COLUMN_JOINS</kbd> hash.
+ Now more fields can be displayed as columns in buglists, in combination
+ with the already existing <kbd>buglist_columns</kbd> hook.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=743991">[% terms.Bug %] 743991</a>)</li>
+ <li>A new hook named <kbd>error_catch</kbd> has been added to let extensions
+ alter the way errors are thrown.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=745197">[% terms.Bug %] 745197</a>)</li>
+ <li>A new hook named <kbd>admin_editusers_action</kbd> has been added to let
+ extensions alter the behavior of <kbd>editusers.cgi</kbd>. This lets you add
+ new features to this script very easily.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=730794">[% terms.Bug %] 730794</a>)</li>
</ul>
-<p>This release also contains a security fix. See the
- <a href="#v32_security">Security Fixes Section</a> for details.</p>
+<h2 id="v42_req">Minimum Requirements</h2>
-<h3>3.2.2</h3>
-
-<p>This release fixes one security issue that is critical for installations
- running 3.2.1 under mod_perl. See the
- <a href="http://www.bugzilla.org/security/3.0.7/">Security Advisory</a>
- for details.</p>
-
-<h3>3.2.1</h3>
-
-<ul>
- <li>Attachments, charts, and graphs would sometimes be garbled on Windows.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=464992">[% terms.Bug %] 464992</a>)</li>
-
- <li>Saving changes to parameters would sometimes fail silently (particularly
- on Windows when the web server didn't have the right permissions to
- update the <code>params</code> file). [% terms.Bugzilla %] will now
- throw an error in this case, telling you what is wrong.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=347707">[% terms.Bug %] 347707</a>)</li>
-
- <li>If you were using the <code>usemenuforusers</code> parameter,
- and [% terms.abug %] was assigned to (or had a QA Contact of) a disabled
- user, that field would be reset to the first user in the list when
- updating [% terms.abug %].
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=465589">[% terms.Bug %] 465589</a>)</li>
-
- <li>If you were using the <code>PROJECT</code> environment variable
- to have multiple [% terms.Bugzilla %] installations using one codebase,
- project-specific templates were being ignored.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=467324">[% terms.Bug %] 467324</a>)</li>
-
- <li>Some versions of the SOAP::Lite Perl module had a b[% %]ug that caused
- [%+ terms.Bugzilla %]'s XML-RPC service to break.
- <code>checksetup.pl</code> now checks for these bad versions and
- will reject them.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=468009">[% terms.Bug %] 468009</a>)</li>
-
- <li>The font sizes in various places were too small, when using the
- Classic skin.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=469136">[% terms.Bug %] 469136</a>)</li>
-</ul>
-
-<h2><a name="v32_security">Security Fixes In This 3.2.x Release</a></h2>
-
-<h3>3.2.3</h3>
-
-<p>This release fixes one security issue related to attachments. See the
- <a href="http://www.bugzilla.org/security/3.2.2/">Security Advisory</a>
- for details.</p>
-
-<h3>3.2.2</h3>
-
-<p>This release fixes one security issue that is critical for installations
- running 3.2.1 under mod_perl. See the
- <a href="http://www.bugzilla.org/security/3.0.7/">Security Advisory</a>
- for details.</p>
-
-<h3>3.2.1</h3>
-
-<p>This release contains several security fixes. One fix may break any
- automated scripts you have that are loading <kbd>process_bug.cgi</kbd>
- directly. We recommend that you read the entire
- <a href="http://www.bugzilla.org/security/2.22.6/">Security Advisory</a>
- for this release.</p>
-
-<h2><a name="v32_req"></a>Minimum Requirements</h2>
-
-<p>Any requirements that are new since 3.0.5 will look like
+<p>Any requirements that are new since 4.0.2 will look like
<span class="req_new">this</span>.</p>
<ul>
- <li><a href="#v32_req_perl">Perl</a></li>
- <li><a href="#v32_req_mysql">For MySQL Users</a></li>
- <li><a href="#v32_req_pg">For PostgreSQL Users</a></li>
- <li><a href="#v32_req_oracle">For Oracle Users</a></li>
- <li><a href="#v32_req_modules">Required Perl Modules</a></li>
- <li><a href="#v32_req_optional_mod">Optional Perl
- Modules</a></li>
+ <li><a href="#v42_req_perl">Perl</a></li>
+ <li><a href="#v42_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v42_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v42_req_oracle">For Oracle Users</a></li>
+ <li><a href="#v42_req_sqlite">For SQLite Users</a></li>
+ <li><a href="#v42_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v42_req_optional_mod">Optional Perl Modules</a></li>
+ <li><a href="#v42_req_apache">Optional Apache Modules</a></li>
</ul>
-<h3><a name="v32_req_perl"></a>Perl</h3>
+<h3 id="v42_req_perl">Perl</h3>
-<p>Perl <span class="req_new">v<strong>5.8.1</strong></span></p>
+<p>Perl v5.8.1</p>
-[% INCLUDE db_req db='mysql' dbd_new = 1 %]
+[% INCLUDE db_req db='mysql' db_new => 1 dbd_new => 1 %]
-[% INCLUDE db_req db='pg' %]
+[% INCLUDE db_req db='pg' db_new => 1 %]
[% INCLUDE db_req db='oracle' %]
-<h3><a name="v32_req_modules"></a>Required Perl Modules</h3>
+[% INCLUDE db_req db='sqlite' %]
-[% INCLUDE req_table reqs = REQUIRED_MODULES
- new = []
- updated = ['Template-Toolkit', 'Email-MIME',
- 'Email-MIME-Modifier', 'CGI.pm'] %]
+<h3 id="v42_req_modules">Required Perl Modules</h3>
-<h3><a name="v32_req_optional_mod"></a>Optional Perl Modules</h3>
+[% INCLUDE req_table reqs = REQUIRED_MODULES
+ new = ['Math-Random-ISAAC']
+ updated = ['URI'] %]
+
+<h3 id="v42_req_optional_mod">Optional Perl Modules</h3>
<p>The following perl modules, if installed, enable various
features of [% terms.Bugzilla %]:</p>
[% INCLUDE req_table reqs = OPTIONAL_MODULES
- new = ['Authen-SASL', 'RadiusPerl']
- updated = []
+ new = ['Encode', 'Encode-Detect']
+ updated = ['PatchReader', 'Apache-SizeLimit']
include_feature = 1 %]
-<h2><a name="v32_feat"></a>New Features and Improvements</h2>
+<h3 id="v42_req_apache">Optional Apache Modules</h3>
+
+<p>If you are using Apache as your webserver, [% terms.Bugzilla %] can
+ take advantage of some Apache features if you have the below Apache
+ modules installed and enabled. Currently,
+ <a href="#v40_feat_js_css_update">certain [% terms.Bugzilla %] features</a>
+ are enabled only if you have all of the following modules installed
+ and enabled:</p>
<ul>
- <li><a href="#v32_feat_ui">Major UI Improvements</a></li>
- <li><a href="#v32_feat_skin">New Default Skin: Dusk</a></li>
- <li><a href="#v32_feat_status">Custom Status Workflow</a></li>
- <li><a href="#v32_feat_fields">New Custom Field Types</a></li>
- <li><a href="#v32_feat_install">Easier Installation</a></li>
- <li><a href="#v32_feat_oracle">Experimental Oracle Support</a></li>
- <li><a href="#v32_feat_utf8">Improved UTF-8 Support</a></li>
- <li><a href="#v32_feat_grcons">Group Icons</a></li>
- <li><a href="#v32_feat_other">Other Enhancements and Changes</a></li>
+ <li>mod_headers</li>
+ <li>mod_expires</li>
+ <li>mod_env</li>
</ul>
-<h3><a name="v32_feat_ui"></a>Major UI Improvements</h3>
+<p>On most systems (but not on Windows), <kbd>checksetup.pl</kbd> is able to
+ tell whether or not you have these modules installed, and it will tell
+ you.</p>
-<p>[% terms.Bugzilla %] 3.2 has had some UI assistance from the NASA
- Human-Computer Interaction department and the new
- <a href="http://wiki.mozilla.org/Bugzilla:UE">[% terms.Bugzilla %]
- User Interface Team</a>.</p>
-<p>In particular, you will notice a massively redesigned [% terms.bug %]
- editing form, in addition to our <a href="#v32_feat_skin">new skin</a>.</p>
-
-<h3><a name="v32_feat_skin"></a>New Default Skin: Dusk</h3>
-
-<p>[% terms.Bugzilla %] 3.2 now ships with a skin called "Dusk" that is
- a bit more colorful than old default "Classic" skin.</p>
-
-<p>Upgrading installations will still default to the "Classic"
- skin--administrators can change the default in the Default Preferences
- control panel. Users can also choose to use the old skin in their
- Preferences (or using the View :: Page Style menu in Firefox).</p>
-
-<p>The changes that [% terms.Bugzilla %] required for Dusk made
- [%+ terms.Bugzilla %] much easier to skin. See the
- <a href="http://wiki.mozilla.org/Bugzilla:Addons#Skins">Addons page</a>
- for additional skins, or try making your own!</p>
-
-<h3><a name="v32_feat_status"></a>Custom Status Workflow</h3>
-
-<p>You can now customize the list of statuses in [% terms.Bugzilla %],
- and transitions between them.</p>
-
-<p>You can also specify that a comment must be made on certain transitions.</p>
-
-<h3><a name="v32_feat_fields"></a>New Custom Field Types</h3>
-
-<p>[% terms.Bugzilla %] 3.2 has support for three new types of
- custom fields:</p>
+<h2 id="v42_feat">New Features and Improvements</h2>
<ul>
- <li>Large Text: Adds a multi-line textbox to your [% terms.bugs %].</li>
- <li>Multiple Selection Box: Adds a box that allows you to choose
- multiple items from a list.</li>
- <li>Date/Time: Displays a date and time, along with a JavaScript
- calendar popup to make picking a date easier.</li>
+ <li><a href="#v42_feat_sqlite">Experimental SQLite Support</a></li>
+ <li><a href="#v42_feat_attach">Creating an Attachment by Pasting Text Into
+ a Text Field</a></li>
+ <li><a href="#v42_feat_email">HTML [% terms.Bug %]mail</a></li>
+ <li><a href="#v42_feat_search">Improved Searching System</a></li>
+ <li><a href="#v42_feat_product">Disabling Old Components, Versions and Milestones</a></li>
+ <li><a href="#v42_feat_custom">Displaying a Custom Field Value Based on Multiple
+ Values of Another Field</a></li>
+ <li><a href="#v42_feat_audit">Auditing of All Changes Within [% terms.Bugzilla %]</a></li>
+ <li><a href="#v42_feat_wai">Accessibility Improvements</a></li>
+ <li><a href="#v42_feat_other">Other Enhancements and Changes</a></li>
</ul>
-<h3><a name="v32_feat_install"></a>Easier Installation</h3>
+<h3 id="v42_feat_sqlite">Experimental SQLite Support</h3>
-<p>[% terms.Bugzilla %] now comes with a script called
- <kbd>install-module.pl</kbd> that can automatically download
- and install all of the required Perl modules for [% terms.Bugzilla %].
- It stores them in a directory inside your [% terms.Bugzilla %]
- installation, so you can use it even if you don't have administrator-level
- access to your machine, and without modifying your main Perl install.</p>
+<p>SQLite is now supported by [% terms.Bugzilla %] and becomes the 4th supported
+ database besides MySQL, PostgreSQL and Oracle. SQLite support must be considered
+ as experimental, at least till the next major release.</p>
-<p><kbd>checksetup.pl</kbd> will print out instructions for using
- <kbd>install-module.pl</kbd>, or you can read its
- <a href="[% docs_urlbase FILTER html %]api/install-module.html">documentation</a>.</p>
+<p>Note that use of SQLite is only recommended for small installations. Larger
+ installations should use MySQL, PostgreSQL, or Oracle.</p>
-<h3><a name="v32_feat_oracle"></a>Experimental Oracle Support</h3>
+<h3 id="v42_feat_attach">Creating an Attachment by Pasting Text Into a Text Field</h3>
-<p>[% terms.Bugzilla %] 3.2 contains experimental support for using
- Oracle as its database. Some features of [% terms.Bugzilla %] are known
- to be broken on Oracle, but hopefully will be working by our next major
- release.</p>
+<p>You can now create a new attachment simply by pasting some text into a text
+ field, in addition to the normal upload process for attachments.</p>
-<p>The [% terms.Bugzilla %] Project, as an open-source project, of course
- does not recommend the use of proprietary database solutions. However,
- if your organization requires that you use Oracle, this will allow
- you to use [% terms.Bugzilla %]!</p>
+<h3 id="v42_feat_email">HTML [% terms.Bug %]mail</h3>
-<p>The [% terms.Bugzilla %] Project thanks Oracle Corp. for their extensive
- development contributions to [% terms.Bugzilla %] which allowed this to
- happen!</p>
+<p>By default, [% terms.bug %]mails (email notifications about changes to
+ [%+ terms.bugs %]) are now sent in an HTML format that is more readable than
+ the old text format. Those who prefer the old text format can still choose it
+ in their Preferences, however.</p>
-<h3><a name="v32_feat_utf8"></a>Improved UTF-8 Support</h3>
+<h3 id="v42_feat_search">Improved Searching System</h3>
-<p>[% terms.Bugzilla %] 3.2 now has advanced UTF-8 support in its code,
- including correct handling for truncating and wrapping multi-byte
- languages. Major issues with multi-byte or unusual languages
- are now resolved, and [% terms.Bugzilla %] should now be usable
- by users in every country with little (or at least much less)
- customization.</p>
+<p>The Custom Search section in the Advanced Search page has been redesigned
+ to work in a more sensible way. Complex queries are easier to build and have
+ more sensible results, as they are built using a more intuitive logic.
+ Some very complicated queries are still impossible to generate, though.
+ Things should improve in future releases.</p>
-<h3><a name="v32_feat_grcons"></a>Group Icons</a></h3>
+<h3 id="v42_feat_product">Disabling Old Components, Versions and Milestones</h3>
-<p>Administrators can now specify that users who are in certain groups
- should have an icon appear next to their name whenever they comment.
- This is particularly useful for distinguishing developers from
- [%+ terms.bug %] reporters.</p>
+<p>Older components, versions and milestones can now be disabled. [% terms.Bugs %]
+ already using them are not affected, but these values will no longer be
+ available for new [% terms.bugs %].</p>
-<h3><a name="v32_feat_other"></a>Other Enhancements and Changes</h3>
+<h3 id="v42_feat_custom">Displaying a Custom Field Value Based on Multiple Values
+ of Another Field</h3>
-<p>These are either minor enhancements, or enhancements that have
- very short descriptions. Some of these are very useful, though!</p>
+<p>A custom field can now be displayed based on multiple values of another field.
+ (For example, one custom field could now appear in multiple products.)
+ Previously, you could only display a custom field based on a single value of
+ another field.</p>
-<h4>Enhancements For Users</h4>
+<h3 id="v42_feat_audit">Auditing of All Changes Within [% terms.Bugzilla %]</h3>
+
+<p>Most changes made through the admin interface are now logged to the database,
+ in the <kbd>audit_log</kbd> table. There is no UI to access this table yet,
+ but developers are free to create their own tools to track changes made into
+ their installation. This is only a first step, and improvements are expected
+ in future releases.</p>
+
+<h3 id="v42_feat_wai">Accessibility Improvements</h3>
+
+<p>A project has started thanks to Francisco Donalisio from IBM to make
+ [%+ terms.Bugzilla %] compliant with the W3C Web Accessibility Initiative
+ standards. A lot more work still needs to be done, but we expect a much
+ better compatibility for the next major release.</p>
+
+<h3 id="v42_feat_other">Other Enhancements and Changes</h3>
+
+<h4>Enhancements for Users</h4>
<ul>
- <li><strong>[% terms.Bugs %]</strong>: You can now reassign
- [%+ terms.abug %] at the same time as you are changing its status.</li>
- <li><strong>[% terms.Bugs %]</strong>: When entering [% terms.abug %],
- you will now see the description of a component when you select it.</li>
- <li><strong>[% terms.Bugs %]</strong>: The [% terms.bug %] view now
- contains some <a href="http://microformats.org/about/">Microformats</a>,
- most notably for users' names and email addresses.</li>
- <li><strong>[% terms.Bugs %]</strong>: You can now remove a QA Contact
- from [% terms.abug %] simply by clearing the QA Contact field.</li>
- <li><strong>[% terms.Bugs %]</strong>: There is now a user preference
- that will allow you to exclude the quoted text when replying
- to comments.</li>
- <li><strong>[% terms.Bugs %]</strong>: You can now expand or collapse
- individual comments in the [% terms.bug %] view.</li>
-
- <li><strong>Attachments</strong>: There is now "mid-air collision"
- protection when editing attachments.</li>
- <li><strong>Attachments</strong>: Patches in the Diff Viewer now show
- line numbers (<a href="https://bugzilla.mozilla.org/attachment.cgi?id=327546">Example</a>).</li>
- <li><strong>Attachments</strong>: After creating or updating an attachment,
- you will be immediately shown the [% terms.bug %] that the attachment
- is on.</li>
-
- <li><strong>Search</strong>: You can now reverse the sort of
- [%+ terms.abug %] list by clicking on a column header again.</li>
- <li><strong>Search</strong>: Atom feeds of [% terms.bug %] lists now
- contain more fields.</li>
- <li><strong>Search</strong>: QuickSearch now supports searching flags
- and groups. It also now includes the OS field in the list of fields
- it searches by default.</li>
- <li><strong>Search</strong>: "Help" text can now appear on query.cgi
- for Internet Explorer and other non-Firefox browsers. (It always
- could appear for Firefox.)</li>
-
- <li>[% terms.Bugzilla %] now ships with an icon that will show
- up next to the URL in most browsers. If you want to replace it,
- it's in <kbd>images/favicon.ico</kbd>.</li>
-
- <li>You can now set the Deadline when using "Change Several
- [%+ terms.Bugs %] At Once"</li>
- <li><strong>Saved Searches</strong> now save their column list, so if
- you customize the list of columns and save your search, it will
- always contain those columns.</li>
- <li><strong>Saved Searches</strong>: When you share a search, you can
- now see how many users have subscribed to it, on
- <kbd>userprefs.cgi</kbd>.</li>
- <li><strong>Saved Searches</strong>: You can now see what group a
- shared search was shared to, on the list of available shared searches
- in <kbd>userprefs.cgi</kbd>.</li>
- <li><strong>Flags</strong>: If your installation uses drop-down user
- lists, the flag requestee box will now contain only users who are
- actually allowed to take requests.</li>
- <li><strong>Flags</strong>: If somebody makes a request to you, and you
- change the requestee to somebody else, the requester is no longer set
- to you. In other words, you can "redirect" requests and maintain the
- original requester.</li>
- <li><strong>Flags</strong>: Emails about flags now will thread properly
- in email clients to be a part of [% terms.abug %]'s thread.</li>
- <li>When using <kbd>email_in.pl</kbd>, you can now add users to the CC
- list by just using <kbd>@cc</kbd> as the field name.</li>
- <li>Many pages (particularly administrative pages) now contain links to
- the relevant section of the [% terms.Bugzilla %] Guide, so you can read
- the documentation for that page.</li>
- <li>Dependency Graphs should render more quickly, as they now (by default)
- only include the same [% terms.bugs %] that you'd see in the dependency
- tree.</li>
+ <li><strong>[% terms.Bugs %]:</strong> Users without editbugs privileges can
+ no longer remove other users from the CC list of [% terms.bugs %].</li>
+ <li><strong>[% terms.Bugs %]:</strong> Local [% terms.bug %] IDs are now valid
+ in the See Also field. Adding such an ID will also add a reciprocal link in
+ the other [% terms.bug %].</li>
+ <li><strong>[% terms.Bugs %]:</strong> After editing [% terms.abug %] or an
+ attachment, the URL is automatically changed to <kbd>show_bug.cgi</kbd>
+ instead of <kbd>post_bug.cgi</kbd>, <kbd>process_bug.cgi</kbd> or
+ <kbd>attachment.cgi</kbd> so that reloading the page (for instance when
+ restarting the web browser) displays the right page. This feature is supported
+ by Firefox, Chrome and Safari, but not by Internet Explorer 9.</li>
+ <li><strong>[% terms.Bugs %]:</strong> Inactive accounts are no longer
+ displayed in user fields when user-autocompletion is enabled.</li>
+ <li><strong>[% terms.Bugs %]:</strong> User-autocompletion is now much faster
+ on installations with many user accounts.</li>
+ <li><strong>[% terms.Bugs %]:</strong> The See Also field now accepts URLs
+ pointing to MantisBT, Trac, JIRA and the sourceforge.net b[%%]ug trackers.</li>
+ <li><strong>[% terms.Bugs %]:</strong> Displaying [% terms.abug %] with many
+ dependencies is now much faster.</li>
+ <li><strong>Attachments:</strong> The encoding of text files can be automatically
+ detected when uploading them as attachments.</li>
+ <li><strong>Attachments:</strong> Clickjacking could possibly occur in an attachment
+ Details page if a user attached a specially formatted HTML file. To fix this
+ potential problem, the Details page always displays the HTML source instead and
+ users can see rendered page by clicking on View.</li>
+ <li><strong>Flags:</strong> Changing the requestee of a flag no longer changes
+ the requester.</li>
+ <li><strong>Reports:</strong> If JavaScript is enabled in your web browser,
+ tabular reports are now sortable based on any displayed column.</li>
+ <li><strong>Dependency graphs:</strong> The <em>Show every [% terms.bug %] in
+ the system with dependencies</em> option has been removed.</li>
+ <li><strong>Searches:</strong> The columns displayed by default in
+ [%+ terms.bug %]lists have changed. These columns are now displayed by default
+ unless otherwise specified:<br>
+ <kbd>product | component | assignee | [% terms.bug %] status | resolution |
+ [%+ terms.bug %] summary | last change date</kbd><br>
+ This means that the priority, severity and operating system columns are no
+ longer displayed by default.</li>
+ <li><strong>Searches:</strong> [% terms.Bug %]lists will now only display the
+ first 500 [% terms.bugs %] by default. It is still possible to display the
+ whole list, though.</li>
+ <li><strong>Searches:</strong> When using relative dates and times, <kbd>-1w</kbd>
+ is now a synonym for <kbd>-7d</kbd> and means exactly 7 days. Previously,
+ <kbd>-1w</kbd> meant the beginning of the week, which was confusing some users.
+ The same confusion existed for <kbd>-1d</kbd> which was different from
+ <kbd>-24h</kbd>, and for <kbd>-1m</kbd> which was different from <kbd>-30d</kbd>.
+ Now if you really want the beginning of the day, week or month, you must use
+ <kbd>-1ds</kbd>, <kbd>-1ws</kbd>, and <kbd>-1ms</kbd> respectively, where
+ "s" means "start of". This change will affect existing saved searches using
+ relative dates.</li>
+ <li><strong>Searches:</strong> A new <em>Include fulltext when performing quick
+ searches</em> user preference has been added which permits users to include
+ or exclude comments when using quicksearches.</li>
+ <li><strong>Searches:</strong> It is now possible to query for [% terms.bugs %]
+ based on personal tags in the Custom Search section in the Advanced Search
+ page.</li>
+ <li><strong>Email notifications: </strong> The date and time of comments are no
+ longer displayed in the comment header in [% terms.bug%]mails. This information
+ is already available in the email header itself.</li>
</ul>
-<h4>Enhancements For Administrators</h4>
+<h4>Enhancements for Administrators and Developers</h4>
<ul>
- <li><strong>Admin UI</strong>: Instead of having the Administration
- Control Panel links in the footer, there is now just one link called
- "Administration" that takes you to a page that links to all the
- administrative controls for [% terms.Bugzilla %].</li>
- <li><strong>Admin UI</strong>: Administrative pages no longer display
- confirmation pages, instead they redirect you to some useful page
- and display a message about what changed.</li>
- <li><strong>Admin UI</strong>: The interface for editing group
- inheritance in <kbd>editgroups.cgi</kbd> is much clearer now.</li>
- <li><strong>Admin UI</strong>: When editing a user, you can now see
- all the components where that user is the Default Assignee or Default
- QA Contact.</li>
-
- <li><strong>Email</strong>: For installations that use SMTP to send
- mail (as opposed to Sendmail), [%+ terms.Bugzilla %] now supports
- SMTP Authentication, so that it can log in to your mail server
- before sending messages.</li>
- <li><strong>Email</strong>: Using the "Test" mail delivery method now
- creates a valid mbox file to make testing easier.</li>
-
- <li><strong>Authentication</strong>: [% terms.Bugzilla %] now correctly
- handles LDAP records which contain multiple email addresses. (The first
- email address in the list that is a valid [% terms.Bugzilla %] account
- will be used, or if this is a new user, the first email address in
- the list will be used.)</li>
- <li><strong>Authentication</strong>: [% terms.Bugzilla %] can now take
- a list of LDAP servers to try in order until it gets a successful
- connection.</li>
- <li><strong>Authentication</strong>: [% terms.Bugzilla %] now supports
- RADIUS authentication.</li>
-
- <li><strong>Security</strong>: The login cookie is now created as
- "HTTPOnly" so that it can't be read by possibly malicious scripts.
- Also, if SSL is enabled on your installation, the login cookie is
- now only sent over SSL connections.</li>
- <li><strong>Security</strong>: The <code>ssl</code> parameter now protects
- every page a logged-in user accesses, when set to "authenticated sessions."
- Also, SSL is now enforced appropriately in the WebServices interface when
- the parameter is set.</li>
-
- <li><strong>Database</strong>: [% terms.Bugzilla %] now uses transactions in
- the database instead of table locks. This should generally improve
- performance with many concurrent users. It also means if there is
- an unexpected error in the middle of a page, all database changes made
- during that page will be rolled back.</li>
- <li><strong>Database</strong>: You no longer have to set
- <code>max_packet_size</code> in MySQL to add large attachments. However,
- you may need to set it manually if you restore a mysqldump into your
- database.</li>
-
- <li>New WebService functions:
- <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html">B<!-- -->ug.add_comment</a>
- and <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bugzilla.html">Bugzilla.extensions</a>.</li>
-
- <li>You can now delete custom fields, but only if they have never been
- set on any [% terms.bug %].</li>
- <li>There is now a <kbd>--reset-password</kbd> argument to
- <kbd>checksetup.pl</kbd> that allows you to reset a user's password
- from the command line.</li>
- <li>There is now a script called <kbd>sanitycheck.pl</kbd> that you can
- run from the command line. It works just like <kbd>sanitycheck.cgi</kbd>.
- By default, it only outputs anything if there's an error, so it's
- ideal for administrators who want to run it nightly in a cron job.</li>
- <li>The <kbd>strict_isolation</kbd> parameter now prevents you from setting
- users who cannot see [% terms.abug %] as a CC, Assignee, or QA
- Contact. Previously it only prevented you from adding users who
- could not <em>edit</em> the [% terms.bug %].</li>
- <li>Extensions can now add their own headers to the HTML <head>
- for things like custom CSS and so on.</li>
- <li><kbd>sanitycheck.cgi</kbd> has been templatized, meaning that the
- entire [% terms.Bugzilla %] UI is now contained in templates.</li>
- <li>When setting the <kbd>sslbase</kbd> parameter, you can now specify
- a port number in the URL.</li>
- <li>When importing [% terms.bugs %] using <kbd>importxml.pl</kbd>,
- attachments will have their actual creator set as their creator,
- instead of the person who exported the [% terms.bug %] from the other
- system.</li>
- <li>The voting system is off by default in new installs. This is to
- prepare for the fact that it will be moved into an extension at
- some point in the future.</li>
- <li>The <code>shutdownhtml</code> parameter now works even when
- [% terms.Bugzilla %]'s database server is down.</li>
+ <li><strong>Installation:</strong> <kbd>checksetup.pl</kbd> is now much quieter
+ when creating a new database.</li>
+ <li><strong>Security:</strong> [% terms.Bugzilla %] 4.0 is using
+ <kbd>Math::Random::Secure</kbd> to generate cryptographically secure
+ pseudorandom numbers, but it appeared that installing this Perl module from
+ CPAN caused a lot of trouble for some people due to its numerous dependencies.
+ So the RNG code has been rewritten to only depend on <kbd>Math::Random::ISAAC</kbd>,
+ which was already in use in previous versions of [% terms.Bugzilla %].</li>
+ <li><strong>Security:</strong> <kbd>X-Frame-Options = SAMEORIGIN</kbd> is now
+ passed to all page headers (except when viewing attachments, as they can be
+ on a different host) to protect users from framing and subsequent possible
+ clickjacking problems.</li>
+ <li><strong>Configuration:</strong> A new parameter <em>password_complexity</em>
+ has been added (default: no_constraints) which allows admins to force users
+ to use passwords with a higher complexity, such as a combination of uppercase
+ and lowercase letters, numbers and special characters, or a subset of them.</li>
+ <li><strong>Configuration:</strong> A new parameter <em>search_allow_no_criteria</em>
+ has been added (default: on) which allows admins to forbid queries with no
+ criteria. This is particularly useful for large installations with several
+ tens of thousands [% terms.bugs %] where returning all [% terms.bugs %]
+ doesn't make sense and would have a performance impact on the database.</li>
+ <li><strong>Configuration:</strong> A new parameter <em>default_search_limit</em>
+ has been added (default: 500) which limits the number of [% terms.bugs %]
+ displayed by default in a [% terms.bug%]list. The user can ask to see a larger
+ list, though.</li>
+ <li><strong>Configuration:</strong> A new parameter <em>max_search_results</em>
+ has been added (default: 10000) which limits the number of [% terms.bugs %]
+ a user can request at once in a [% terms.bug%]list. This is a hard limit and
+ a user cannot bypass this value.</li>
+ <li><strong>Configuration:</strong> A new parameter <em>ajax_user_autocompletion</em>
+ has been added (default: on) to allow administrators to disable auto-completion
+ when typing characters in user fields. This parameter should only be disabled
+ if your installation is unable to support the load generated by this feature.</li>
+ <li><strong>Configuration:</strong> The <em>config_modify_panels</em> hook now
+ lets you add additional parameters to existing parameters panels.</li>
+ <li><strong>Flags:</strong> Users with local editcomponents privileges can now
+ edit flag types for products they can administer.</li>
+ <li><strong>Quips:</strong> A new system group <em>bz_quip_moderators</em> has
+ been created to moderate quips. Till now, you had to be in the <em>admin</em>
+ group to do that.</li>
+ <li><kbd>importxml.pl</kbd> now inserts each comment separately into the imported
+ [%+ terms.bug %] instead of concatenating them all into a single comment.</li>
+ <li><kbd>email_in.pl</kbd> now ignores auto-submitted incoming emails (for
+ instance, all these "out of office" emails).</li>
+ <li>New code hooks: email_in_before_parse, email_in_after_parse,
+ install_filesystem, install_update_db_fielddefs, job_map, object_end_of_create,
+ quicksearch_map, user_preferences.</li>
</ul>
-<h3>Enhancements for Localizers (or Localized Installations)</h3>
+<h4>WebService Changes</h4>
<ul>
- <li>The documentation can now be localized--in other words, you can have
- documentation installed for multiple languages at once and
- [%+ terms.Bugzilla %] will link to the correct language in its internal
- documentation links.</li>
- <li>[% terms.Bugzilla %] no longer uses the <kbd>languages</kbd> parameter.
- Instead it reads the <kbd>template/</kbd> directory to see which
- languages are available.</li>
- <li>Some of the messages printed by <kbd>checksetup.pl</kbd> can now
- be localized. See <kbd>template/en/default/setup/strings.txt.pl</kbd>.
+ <li>Two new methods have been added: <kbd>Product.create</kbd> and
+ <kbd>Group.create</kbd>.</li>
+ <li><kbd>B[%%]ug.update</kbd> no longer throws an error when passing an empty
+ string to <kbd>see_also</kbd>. It now simply ignores this empty value.</li>
+ <li><kbd>Product.get</kbd> now also returns data about the classification it
+ belongs to as well as its components, milestones and versions. It also
+ returns the <kbd>default_milestone</kbd> and <kbd>has_unconfirmed</kbd>
+ attributes.</li>
+ <li>In <kbd>B[%%]ug.fields</kbd>, the <kbd>sortkey</kbd> attribute used in
+ <kbd>values</kbd> has been renamed to <kbd>sort_key</kbd>.</li>
+ <li>In <kbd>B[%%]ug.attachments</kbd> and <kbd>B[%%]ug.add_attachment</kbd>,
+ the <kbd>is_url</kbd> attribute no longer exists.</li>
</ul>
-<h2><a name="v32_issues"></a>Outstanding Issues</h2>
+
+<h2 id="v42_issues">Outstanding Issues</h2>
+
+<ul>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
+ [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
+ the same time, there is no "mid-air collision" protection.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
+ [%- terms.Bug %] 276230</a>: The support for restricting access to
+ particular Categories of New Charts is not complete. You should treat the
+ <em>chartgroup</em> parameter as the only access mechanism available.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=584742">
+ [%- terms.Bug %] 584742</a>: When viewing [% terms.abug %], WebKit-based
+ browsers can automatically reset a field's selected value when the field
+ has disabled values.</li>
+</ul>
+
+
+<h2 id="v42_code_changes">Code Changes Which May Affect Customizations and Extensions</h2>
+
+<ul>
+ <li>The <kbd>email/newchangedmail.txt.tmpl</kbd> template is now fully templatized,
+ meaning that the diff table displaying changes in [% terms.bug %] fields is
+ now generated in the template itself. This means [% terms.bug %]mails are now
+ fully localizable.</li>
+ <li>The bugmail_recipients hook has been modified to pass <kbd>diffs</kbd> with
+ changes made to the [% terms.bug %] as well as <kbd>users</kbd> including
+ recipients of the email notification.</li>
+ <li>YUI has been upgraded to 2.9.0.</li>
+ <li>Due to the major code refactor of <kbd>B[%%]ugzilla/Search.pm</kbd>, any
+ customization made against this file will probably need to be rewritten.</li>
+ <li>The [% terms.Bugzilla %]-specific <kbd>url_quote</kbd> filter used in templates
+ has been removed and replaced by the <kbd>uri</kbd> filter from Template::Toolkit
+ as they are now similar.</li>
+ <li><kbd>long_list.cgi</kbd>, <kbd>showattachment.cgi</kbd> and <kbd>xml.cgi</kbd>
+ have been removed from the codebase. As <a href="#v40_code_changes">announced</a>
+ in the release notes of [% terms.Bugzilla %] 4.0, these scripts were deprecated
+ since [% terms.Bugzilla %] 2.19.</li>
+ <li><kbd>sidebar.cgi</kbd> has been removed, because Gecko-based browsers no
+ longer support remote XUL, and its popularity is very low.</li>
+ <li><kbd>contrib/yp_nomail.sh</kbd> has been removed. This script is no longer
+ useful since [% terms.Bugzilla %] 3.0.</li>
+ <li><kbd>contrib/bugzilla_ldapsync.rb</kbd> has been removed. This script didn't
+ work for a long time.</li>
+</ul>
+
+
+<h1 id="v42_previous">[% terms.Bugzilla %] 4.0 Release Notes</h1>
+
+<ul class="bz_toc">
+ <li><a href="#v40_introduction">Introduction</a></li>
+ <li><a href="#v40_point">Updates in this 4.0.x Release</a></li>
+ <li><a href="#v40_req">Minimum Requirements</a></li>
+ <li><a href="#v40_feat">New Features and Improvements</a></li>
+ <li><a href="#v40_issues">Outstanding Issues</a></li>
+ <li><a href="#v40_upgrading">Notes On Upgrading From a Previous Version</a></li>
+ <li><a href="#v40_code_changes">Code Changes Which May Affect
+ Customizations and Extensions</a></li>
+ <li><a href="#v40_previous">Release Notes for Previous Versions</a></li>
+</ul>
+
+<h2 id="v40_introduction">Introduction</h2>
+
+<p>This is [% terms.Bugzilla %] 4.0! Since 3.6 (our previous major
+ release) we've come a long way, and we've come even further compared to
+ 3.0 in 2007! Since [% terms.Bugzilla %] 3.0, almost every major user
+ interface in [% terms.Bugzilla %] has been redesigned, the WebServices have
+ evolved enormously, there's a great new Extensions system, and there
+ are hundreds of other new features. With the major redesigns that come
+ particularly in this release compared to 3.6, we felt that it was time to
+ call this release 4.0.</p>
+
+<p>It's not just major WebService and UI enhancements that are new in
+ [%+ terms.Bugzilla %] 4.0—there are many other exciting new features,
+ including automatic duplicate detection, enhanced custom field
+ functionality, autocomplete for users, search improvements, and much
+ more. Overall, 4.0 is far and away the best version of [% terms.Bugzilla %]
+ we've ever released.</p>
+
+<p>If you're upgrading, make sure to read <a href="#v40_upgrading">Notes
+ On Upgrading From a Previous Version</a>. If you are upgrading from a release
+ before 3.6, make sure to read the release notes for all the
+ <a href="#v40_previous">previous versions</a> in between your version
+ and this one, <strong>particularly the Upgrading section of each
+ version's release notes</strong>.</p>
+
+<p>We would like to thank
+ <a href="http://www.itasoftware.com/">ITA Software</a>,
+ the <a href="http://www.ibm.com/linux/ltc/">IBM Linux Technology Center</a>,
+ and <a href="http://www.redhat.com/">Red Hat</a> for funding the development
+ of certain features and improvements in this release of
+ [%+ terms.Bugzilla %].</p>
+
+<h2 id="v40_point">Updates in this 4.0.x Release</h2>
+
+<h3>4.0.2</h3>
+
+<p>This release fixes several security issues. See the
+ <a href="http://www.bugzilla.org/security/3.4.11/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in this
+ release:</p>
+
+<ul>
+ <li>The <kbd>B[% %]ug.create</kbd> WebService method now throws an error if you
+ pass a group name which doesn't exist. In [% terms.Bugzilla %] 4.0 and 4.0.1,
+ this group name was silently ignored, leaving your [% terms.bug %] unsecure
+ if no other group applied.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=653341">[% terms.Bug %] 653341</a>)</li>
+ <li>Moving several [% terms.bugs %] at once into another product displayed the
+ same confirmation page again and again, and changes were never committed
+ (regressed in 4.0).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=663208">[% terms.Bug %] 663208</a>)</li>
+ <li>Marking [% terms.abug %] as a duplicate now works in Internet Explorer 9.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=656769">[% terms.Bug %] 656769</a>)</li>
+ <li><kbd>importxml.pl</kbd> no longer crashes when importing keywords (regressed
+ in 4.0).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=657707">[% terms.Bug %] 657707</a>)</li>
+ <li>Data entered while reporting a new [% terms.bug %] could be lost if you had
+ to click the "Back" button of your web browser.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=652427">[% terms.Bug %] 652427</a>)</li>
+ <li>WebServices methods will return undefined [% terms.bug %] fields as undefined
+ instead of as an empty string. This change is consistent with how
+ [%+ terms.Bugzilla %] 4.2 behaves.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=657561">[% terms.Bug %] 657561</a>)</li>
+ <li>The XML-RPC interface now works with SOAP::Lite 0.711 and 0.712 under mod_perl.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=600810">[% terms.Bug %] 600810</a>)</li>
+ <li>LWP 6.00 and newer require Perl 5.8.8 and above. When installing this module
+ using <kbd>install-module.pl</kbd> on a Perl installation older than 5.8.8,
+ LWP 5.837 will be installed instead.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=655912">[% terms.Bug %] 655912</a>)</li>
+ <li>Viewing [% terms.abug %] report should be significantly faster when your
+ installation has many custom fields.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=634812">[% terms.Bug %] 634812</a>)</li>
+</ul>
+
+<h3>4.0.1</h3>
+
+<ul>
+ <li>During installation, the CPAN module Math::Random::Secure would
+ sometimes fail to install properly and give an error about
+ <kbd>Math::Random::Secure::irand</kbd>. Now, when using
+ <kbd>install-module.pl</kbd> to install Math::Random::Secure, this
+ will no longer happen. If you are currently experiencing this b[% %]ug
+ and it prevented you from installing 4.0, remove Math::Random::Secure
+ from your <kbd>lib/</kbd> directory, like:
+ <p><kbd>rm -rf lib/Math/Random/Secure*</kbd></p>
+ <p>(<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=646578">[% terms.Bug %] 646578</a>)</p></li>
+ <li>The "Remember values as bookmarkable template" button on the
+ [%+ terms.bug %] entry page will now work even when some required fields
+ are empty.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=640719">[% terms.Bug %] 640719</a>)</li>
+ <li>Email notifications about dependencies and flags had the wrong
+ timestamp.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=643910">[% terms.Bug %] 643910</a>
+ and (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=652165">[% terms.Bug %] 652165</a>)</li>
+ <li>You can now select "UTC" as a valid timezone in General Preferences.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=646209">[% terms.Bug %] 646209</a>)</li>
+ <li>Automatic duplicate detection now works on PostgreSQL (although
+ it is not as high-quality as on other DB platforms).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=634144">[% terms.Bug %] 634144</a>)</li>
+ <li>Autcomplete for users now works even if you are using the
+ "emailsuffix" option.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=641519">[% terms.Bug %] 641519</a>)</li>
+ <li>Javascript errors during series creation in New Charts have been
+ fixed.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=644285">[% terms.Bug %] 644285</a>)</li>
+ <li>The "Show Votes" page now works, for installations using the Voting
+ extension.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=652381">[% terms.Bug %] 652381</a>)</li>
+</ul>
+
+<h2 id="v40_req">Minimum Requirements</h2>
+
+<p>Any requirements that are new since 3.6.3 will look like
+ <span class="req_new">this</span>.</p>
+
+<ul>
+ <li><a href="#v40_req_perl">Perl</a></li>
+ <li><a href="#v40_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v40_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v40_req_oracle">For Oracle Users</a></li>
+ <li><a href="#v40_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v40_req_optional_mod">Optional Perl Modules</a></li>
+ <li><a href="#v40_req_apache">Optional Apache Modules</a></li>
+</ul>
+
+<h3 id="v40_req_perl">Perl</h3>
+
+<p>Perl v5.8.1</p>
+<h3 id="v40_req_mysql">For MySQL Users</h3>
+
+ <ul>
+ <li>MySQL v4.1.2</li>
+ <li><strong>perl module:</strong> DBD::mysql v4.00</li>
+ </ul>
+
+<h3 id="v40_req_pg">For PostgreSQL Users</h3>
+
+ <ul>
+ <li>PostgreSQL v8.00.0000</li>
+ <li><strong>perl module:</strong> DBD::Pg v1.45</li>
+ </ul>
+
+<h3 id="v40_req_oracle">For Oracle Users</h3>
+
+ <ul>
+ <li>Oracle v10.02.0</li>
+ <li><strong>perl module:</strong> DBD::Oracle v1.19</li>
+ </ul>
+
+<h3 id="v40_req_modules">Required Perl Modules</h3>
+
+ <table cellspacing="0" cellpadding="0" border="0" class="req_table">
+ <tbody>
+ <tr>
+ <th>Module</th><th>Version</th>
+ </tr>
+ <tr>
+ <td>CGI</td>
+ <td class="req_new">3.51</td>
+ </tr>
+ <tr>
+ <td>Digest::SHA</td>
+ <td>(Any)</td>
+ </tr>
+ <tr>
+ <td>Date::Format</td>
+ <td>2.21</td>
+ </tr>
+ <tr>
+ <td>DateTime</td>
+ <td>0.28</td>
+ </tr>
+ <tr>
+ <td>DateTime::TimeZone</td>
+ <td>0.71</td>
+ </tr>
+ <tr>
+ <td>DBI</td>
+ <td>1.41</td>
+ </tr>
+ <tr>
+ <td>Template</td>
+ <td>2.22</td>
+ </tr>
+ <tr>
+ <td>Email::Send</td>
+ <td>2.00</td>
+ </tr>
+
+ <tr>
+ <td>Email::MIME</td>
+ <td class="req_new">1.904</td>
+ </tr>
+ <tr>
+ <td>URI</td>
+ <td>(Any)</td>
+ </tr>
+ <tr>
+ <td class="req_new">List::MoreUtils</td>
+ <td class="req_new">0.22</td>
+ </tr>
+ </tbody>
+ </table>
+
+<h3 id="v40_req_optional_mod">Optional Perl Modules</h3>
+
+<p>The following perl modules, if installed, enable various
+ features of [% terms.Bugzilla %]:</p>
+
+ <table cellspacing="0" cellpadding="0" border="0" class="req_table">
+ <tbody>
+ <tr>
+ <th>Module</th><th>Version</th><th>Enables Feature</th>
+ </tr>
+ <tr>
+ <td>GD</td>
+ <td>1.20</td>
+ <td>Graphical Reports, New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td>Chart::Lines</td>
+ <td>2.1</td>
+ <td>New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td>Template::Plugin::GD::Image</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD::Text</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD::Graph</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>MIME::Parser</td>
+ <td>5.406</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td>LWP::UserAgent</td>
+ <td>(Any)</td>
+ <td>Automatic Update Notifications</td>
+ </tr>
+ <tr>
+ <td>XML::Twig</td>
+ <td>(Any)</td>
+ <td>Move [% terms.Bugs %] Between Installations, Automatic Update
+ Notifications</td>
+ </tr>
+ <tr>
+ <td>PatchReader</td>
+ <td>0.9.4</td>
+ <td>Patch Viewer</td>
+ </tr>
+ <tr>
+ <td>Net::LDAP</td>
+ <td>(Any)</td>
+ <td>LDAP Authentication</td>
+ </tr>
+ <tr>
+ <td>Authen::SASL</td>
+ <td>(Any)</td>
+ <td>SMTP Authentication</td>
+ </tr>
+ <tr>
+ <td>Authen::Radius</td>
+ <td>(Any)</td>
+ <td>RADIUS Authentication</td>
+ </tr>
+ <tr>
+ <td>SOAP::Lite</td>
+ <td class="req_new">0.712</td>
+ <td>XML-RPC Interface</td>
+ </tr>
+ <tr>
+ <td>JSON::RPC</td>
+ <td>(Any)</td>
+ <td>JSON-RPC Interface</td>
+ </tr>
+ <tr>
+ <td class="req_new">JSON::XS</td>
+ <td class="req_new">2.0</td>
+ <td>Make JSON-RPC Faster</td>
+ </tr>
+ <tr>
+ <td>Test::Taint</td>
+ <td>(Any)</td>
+ <td>JSON-RPC Interface, XML-RPC Interface</td>
+ </tr>
+ <tr>
+ <td>HTML::Parser</td>
+ <td>3.40</td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td>HTML::Scrubber</td>
+ <td>(Any)</td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td>Email::MIME::Attachment::Stripper</td>
+ <td>(Any)</td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td>Email::Reply</td>
+ <td>(Any)</td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td>TheSchwartz</td>
+ <td>(Any)</td>
+ <td>Mail Queueing</td>
+ </tr>
+ <tr>
+ <td>Daemon::Generic</td>
+ <td>(Any)</td>
+ <td>Mail Queueing</td>
+ </tr>
+ <tr>
+ <td>mod_perl2</td>
+ <td>1.999022</td>
+ <td>mod_perl</td>
+ </tr>
+ <tr>
+ <td>Apache2::SizeLimit</td>
+ <td class="req_new">0.93</td>
+ <td>mod_perl</td>
+ </tr>
+ <tr>
+ <td class="req_new">Math::Random::Secure</td>
+ <td class="req_new">0.05</td>
+ <td>Improve cookie and token security</td>
+ </tr>
+ </tbody>
+ </table>
+
+<h3 id="v40_req_apache">Optional Apache Modules</h3>
+
+<p>If you are using Apache as your webserver, [% terms.Bugzilla %] can
+ now take advantage of some Apache features if you have the below Apache
+ modules installed and enabled. Currently,
+ <a href="#v40_feat_js_css_update">certain [% terms.Bugzilla %] features</a>
+ are enabled only if you have all of the following modules installed
+ and enabled:</p>
+
+<ul>
+ <li>mod_headers</li>
+ <li>mod_expires</li>
+ <li>mod_env</li>
+</ul>
+
+<p>On most systems (but not on Windows), <kbd>checksetup.pl</kbd> is able to
+ tell whether or not you have these modules installed, and it will tell
+ you.</p>
+
+<h2 id="v40_feat">New Features and Improvements</h2>
+
+<ul>
+ <li><a href="#v40_feat_dup">Automatic Duplicate Detection When Filing
+ [%+ terms.Bugs %]</a></li>
+ <li><a href="#v40_feat_search_ui">New Advanced Search UI</a></li>
+ <li><a href="#v40_feat_attach_ui">New Attachment Details UI</a></li>
+ <li><a href="#v40_feat_autocomplete">Autocomplete for Users and
+ Keywords</a></li>
+ <li><a href="#v40_feat_ui">General Usability Improvements</a></li>
+ <li><a href="#v40_feat_workflow">New Default Status Workflow</a></li>
+ <li><a href="#v40_feat_lists">"Last Search" Now Remembers Multiple
+ Searches</a></li>
+ <li><a href="#v40_feat_jsonp">Cross-Domain WebServices with JSONP</a></li>
+ <li><a href="#v40_feat_ws">Major WebService Enhancements</a></li>
+ <li><a href="#v40_feat_mandatory">Mandatory Custom Fields</a></li>
+ <li><a href="#v40_feat_vot_ext">Voting Is Now An Extension</a></li>
+ <li><a href="#v40_feat_js_css_update">Users Get New CSS and Javascript
+ Automatically</a></li>
+ <li><a href="#v40_feat_hooks">Many New Hooks</a></li>
+ <li><a href="#v40_feat_apache_config">New Apache Configuration</a></li>
+ <li><a href="#v40_feat_other">Other Enhancements and Changes</a></li>
+</ul>
+
+<h3 id="v40_feat_dup">Automatic Duplicate Detection When Filing
+ [%+ terms.Bugs %]</h3>
+
+<p>When filing [% terms.abug %], as soon as you start typing in the summary
+ field, [% terms.Bugzilla %] will suggest possible duplicates of the
+ [%+ terms.bug %] you are filing.</p>
+
+<p>In order for this feature to work, all pre-requisites for JSON-RPC
+ support must be installed on your [% terms.Bugzilla %]. It will be
+ much faster on installations that run under mod_perl than it will
+ be on other installations.</p>
+
+<h3 id="v40_feat_search_ui">New Advanced Search UI</h3>
+
+<p>Thanks to the UI work of <a href="http://guy-pyrzak.blogspot.com/">Guy
+ Pyrzak</a>, the Advanced Search UI has been completely redesigned.
+ It is now much simpler, and far more approachable for new users, while
+ still retaining all of the features that power users are used to.</p>
+
+<h3 id="v40_feat_attach_ui">New Attachment Details UI</h3>
+
+<p>The UI used for editing attachment details has been completely
+ redesigned, allowing for a normally-size comment box to be used
+ when commenting on attachments, and allowing nearly the entire screen
+ width to be used when doing code reviews or editing an attachment as
+ a comment.</p>
+
+<p>Thanks to <a href="http://guy-pyrzak.blogspot.com/">Guy Pyrzak</a> for
+ his excellent work on this UI redesign.</p>
+
+<h3 id="v40_feat_autocomplete">Autocomplete for Users and Keywords</h3>
+
+<p>Once you type at least three characters in any field that can contain a user
+ (including the [% field_descs.cc FILTER html %],
+ [%+ field_descs.qa_contact FILTER html %], or
+ [%+ field_descs.assigned_to FILTER html %] fields), a list will appear
+ containing all of the users whose real names or usernames match what you are
+ typing. Your [% terms.Bugzilla %] must have all of the optional Perl
+ modules required for JSON-RPC support installed, though, in order for
+ this feature to work. Also, this feature will be <strong>much</strong>
+ faster on installations that run under mod_perl than it will be on
+ other installations.</p>
+
+<p>There is also a similar autocomplete for the Keywords field. The
+ Keywords autocomplete does not require JSON-RPC.</p>
+
+<h3 id="v40_feat_ui">General Usability Improvements</h3>
+
+<p>In addition to the enhancements listed above, there have been
+ <strong>many</strong> improvements made across the [% terms.Bugzilla %]
+ user interface. For a list of specific enhancements that were significant,
+ see the <a href="#v40_feat_other">Other Enhancements and Changes</a>
+ section.</p>
+
+<h3 id="v40_feat_workflow">New Default Status Workflow</h3>
+
+<p>For new installations of [% terms.Bugzilla %], the default set of
+ statuses will now be:</p>
+
+<ul>
+ <li>UNCONFIRMED</li>
+ <li>CONFIRMED</li>
+ <li>IN_PROGRESS</li>
+ <li>RESOLVED</li>
+ <li>VERIFIED</li>
+</ul>
+
+<p>And the UNCONFIRMED status will be enabled by default in all products.</p>
+
+<p>On upgrade, existing installations will not be affected--you will retain
+ your existing status workflow. However, we strongly recommend that you
+ update your existing workflow to the new one, using a special tool
+ we've included, <kbd>contrib/convert-workflow.pl</kbd>, which you
+ can run after you use <kbd>checksetup.pl</kbd> to upgrade. The
+ <kbd>whineatnews.pl</kbd> and <kbd>bugzilla-submit</kbd> scripts
+ will probably not work properly if you continue to use the old workflow
+ (though most other parts of [% terms.Bugzilla %] will still function
+ normally).</p>
+
+<p>For more information about the workflow and our rationale for changing
+ it, see the
+ <a href="http://bugzillaupdate.wordpress.com/2010/07/06/bugzilla-4-0-has-a-new-default-status-workflow/">blog
+ post about it</a> and the
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=486292">[% terms.bug %]
+ where the change was made</a>.</p>
+
+<h3 id="v40_feat_lists">"Last Search" Now Remembers Multiple Searches</h3>
+
+<p>At the top of every [% terms.bug %] in [% terms.Bugzilla %], there are
+ links that look like: "First", "Last", "Prev", "Next", and
+ "Show last search results". In earlier versions of [% terms.Bugzilla %],
+ if you did two separate searches in separate windows, these links would
+ only work for the <em>last</em> search you did. Now, [% terms.Bugzilla %]
+ will "remember" which search result you came from and give you the right
+ "last search results" or "next [% terms.bug %]" from <em>that</em> list,
+ instead of always using your most recent search.</p>
+
+<p>There are still some situations where [% terms.Bugzilla %] will have to
+ "guess" which search you are trying to navigate through, but it does its
+ best to get it right.</p>
+
+<h3 id="v40_feat_jsonp">Cross-Domain WebServices with JSONP</h3>
+
+<p>[% terms.Bugzilla %] now supports making WebService calls from
+ another domain, inside of a web browser, thanks to support for
+ <a href="http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/">JSONP</a>.
+ This will allow for web "mash-ups" to use [% terms.Bugzilla %] data.
+ When using JSONP, you may only call functions that <em>get</em> data,
+ you may not call functions that <em>change</em> data.</p>
+
+<p>For more details, see the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Server/JSONRPC.html#JSONP">JSONP
+ section</a> of the JSON-RPC WebService documentation.</p>
+
+<h3 id="v40_feat_ws">Major WebService Enhancements</h3>
+
+<p>The WebService has been expanded considerably. The WebService should now be
+ able to do everything with [% terms.bugs %] that you can do via the
+ web interface, including updating [% terms.bugs %], adding attachments,
+ and getting attachment data. For specifics, see the
+ <a href="#v40_feat_ws_changes">WebService Changes</a> section of these
+ release notes.</p>
+
+<h3 id="v40_feat_mandatory">Mandatory Custom Fields</h3>
+
+<p>You can now specify that certain custom fields are "mandatory",
+ meaning that they must have a value when [% terms.abug %] is filed,
+ and they can never be empty after that.</p>
+
+<h3 id="v40_feat_vot_ext">Voting Is Now An Extension</h3>
+
+<p>All of the code for voting in [% terms.Bugzilla %] has been moved
+ into an extension, called "Voting", in the <kbd>extensions/Voting/</kbd>
+ directory. To enable it, you must remove the <kbd>disabled</kbd> file
+ from that directory, and run <kbd>checksetup.pl</kbd>.</p>
+
+<p>In a future version of [% terms.Bugzilla %], the Voting extension will
+ be moved outside of the [% terms.Bugzilla %] core code, so we are looking
+ for somebody who has an interest in the Voting system and would like to
+ maintain it as a separate extension. There are many enhancement requests
+ that have been made against the Voting system, and the best way for those
+ to get addressed is for somebody to step up and offer to maintain the
+ system outside of [% terms.Bugzilla %]'s core code.</p>
+
+<h3 id="v40_feat_js_css_update">Users Get New CSS and Javascript
+ Automatically</h3>
+
+<p>In past versions of [% terms.Bugzilla %], if you changed
+ [%+ terms.Bugzilla %]'s CSS or Javascript files, then every user of
+ [%+ terms.Bugzilla %] would have to clear their cache in order to get
+ the updated files. Now, if you are using Apache as your webserver and
+ you have the <a href="#v40_req_apache">optional Apache modules</a>
+ installed and enabled, users will automatically get every new version of
+ [%+ terms.Bugzilla %]'s Javascript and CSS without having to clear
+ their caches.</p>
+
+<p>This feature also gives a slight performance speedup to
+ [%+ terms.Bugzilla %] in some cases, and so we recommend that all
+ administrators install and enable the optional Apache modules if possible.</p>
+
+<h3 id="v40_feat_hooks">Many New Hooks</h3>
+
+<p>Many new code hooks have been added for use by Extensions,
+ in [% terms.Bugzilla %] 4.0. Now Extensions can access and modify
+ nearly every part of [% terms.Bugzilla %].</p>
+
+<h3 id="v40_feat_apache_config">New Apache Configuration</h3>
+
+<p>If you run [% terms.Bugzilla %] under Apache (as most people do),
+ you most likely require a <strong>new Apache configuration</strong>
+ for this version of [% terms.Bugzilla %]. See the
+ <a href="#v40_upgrading">Notes On Upgrading From a Previous Version</a>
+ section for details.</p>
+
+<h3 id="v40_feat_other">Other Enhancements and Changes</h3>
+
+<h4>Enhancements for Users</h4>
+
+<ul>
+ <li>Now, everywhere in [% terms.Bugzilla %] where you can enter a date,
+ there is a Calendar widget where you can select the date on a
+ calendar.</li>
+ <li>The big icons on the front page have been replaced with much nicer
+ icons, thanks to Jon Pink of <a href="http://www.jpink.co.uk/">J. Pink Design</a>!</li>
+ <li><strong>[% terms.Bugs %]:</strong> When filing [% terms.bugs %],
+ you will now be warned if you forgot to fill in any mandatory fields,
+ <em>before</em> the page is submitted.</li>
+ <li><strong>[% terms.Bugs %]:</strong> When filing [% terms.abug %],
+ you can hover your mouse over any of the field labels on the page
+ to get a brief description of what that field is and what its purpose
+ is.</li>
+ <li><strong>[% terms.Bugs %]:</strong> When adding Hours Worked to [% terms.abug %],
+ you are no longer required to comment.</li>
+ <li><strong>[% terms.Bugs %]:</strong> There is now a user preference
+ for whether the comment box appears above or below the existing
+ comments.</li>
+ <li><strong>[% terms.Bugs %]:</strong> [% terms.Bugzilla %] will now
+ send an email for every comment that you mark or un-mark as being
+ private. (Previous versions of [% terms.Bugzilla %] did not send emails
+ to users about this change.) The state of comments being made private
+ is also now stored in [% terms.abug %]'s history.</li>
+ <li><strong>[% terms.Bugs %]:</strong> The box to "Add [% terms.Bug %] URLs"
+ in the See Also field is now hidden behind an "(add)" link that you
+ have to click to see the box.</li>
+
+ <li><strong>Searches:</strong> You can now properly search for field values
+ that have commas in their name, when using the Advanced Search form.</li>
+ <li><strong>Searches:</strong> The "URL" field can now be shown as a column
+ in search results.</li>
+ <li><strong>Searches:</strong> When viewing a search result, you can now
+ click on the Summary of the [% terms.bug %] in order to go to the
+ [%+ terms.bug %]-view page, in addition to being able to click on the
+ [%+ terms.bug %] ID.</li>
+ <li><strong>Searches:</strong> When doing a search using the "quicksearch"
+ box in the header or footer, the box will still contain what you searched
+ for when viewing the search results page.</li>
+ <li><strong>Searches:</strong> Multi-select custom fields can now be
+ shown as columns in the search results.</li>
+ <li><strong>Searches:</strong> When using the Boolean Charts (now called
+ "Custom Search"), if you specify both a criterion for an attachment
+ and a criteron for a flag, then only [% terms.bugs %] that have
+ attachments with that flag will be found.</li>
+ <li><strong>Searches:</strong> If you hover your mouse over the field labels
+ on the Advanced Search page, you will get a description of what that
+ field is.</li>
+ <li><strong>Searches:</strong> When searching via a saved search, if you
+ accidentally click on "Forget Search", there is a link to undo it.</li>
+ <li><strong>Searches:</strong> When using the Boolean Charts (now called
+ "Custom Search"), you can search for values "greater than or equal to"
+ or "less than or equal to" some value.</li>
+
+ <li><strong>Flags:</strong> If you hover your mouse over the name of
+ a flag setter when viewing [% terms.abug %], you can see that
+ flag setter's full name and complete username.</li>
+ <li><strong>Flags:</strong> When setting a flag on [% terms.abug %],
+ the box for entering a requestee does not appear until you set the flag
+ to "?", now.</li>
+ <li><strong>Flags:</strong> On the "My Requests" page, [% terms.bugs %]
+ that are restricted to certain groups now properly have the "padlock"
+ icon shown next to them to indicate that they may contain confidential
+ information.</li>
+
+ <li>When using the Reports interface, you can now choose many more fields
+ as the X, Y, or Z axis of a report, including custom fields.</li>
+ <li>[% terms.Bugzilla %] now prevents
+ Internet Explorer 8 and later from attempting to render
+ <kbd>text/plain</kbd> attachments as HTML.</li>
+ <li>If you receive a Whine mail that is empty, there will now be a brief
+ message explaining that your search found no results.</li>
+ <li>The <a href="page.cgi?id=fields.html">Field Help Page</a> now
+ contains a description of every single field that can be on
+ [%+ terms.abug %] in [% terms.Bugzilla %].</li>
+</ul>
+
+<h4>Enhancements for Administrators and Developers</h4>
+
+<ul>
+ <li>The system for moving [% terms.bugs %] between installations has been
+ moved into an extension called <kbd>OldBugMove</kbd>. This system was used
+ by very few [% terms.Bugzilla %] installations--if you aren't certain
+ whether or not you are using it, you're not using it. To enable the system,
+ you have to remove the file <kbd>extensions/OldBugMove/disabled</kbd>
+ and then run <kbd>checksetup.pl</kbd>. In a future version of [% terms.Bugzilla %],
+ this extension may be moved outside of the core [% terms.Bugzilla %] code,
+ so if you are interested in maintaining it, please let us know.</li>
+ <li><strong>Custom Fields: </strong> "[% terms.Bug %] ID" custom fields can
+ now represent relationships between [% terms.bugs %], similarly to how the
+ [%+ field_descs.blocked FILTER html %] and
+ [%+ field_descs.dependson FILTER html %] fields work now.</li>
+ <li><strong>Custom Fields:</strong> You can now restrict the visibility
+ of custom fields and their values to a specific Component or
+ Classification.</li>
+ <li>The "keyword cache" has been removed. When you edit keywords, you no
+ longer will have to "rebuild the keyword cache" after you are done.</li>
+ <li>Running <kbd>./collectstats.pl --regenerate</kbd> will now take
+ minutes or hours, instead of days.</li>
+ <li>When using <kbd>email_in.pl</kbd>, there are two new switches,
+ <kbd>--default</kbd> and <kbd>--override</kbd>, which allow you to
+ specify certain default values or override specified values for
+ <kbd>@field</kbd> values sent in emails. (This also allows you to specify
+ defaults for everything so that people do not have to specify any field
+ values when filing [% terms.abug %] via email.)</li>
+ <li><strong>Installation:</strong> If you are using a localized version of
+ [%+ terms.Bugzilla %] and your terminal does not understand Unicode,
+ <kbd>checksetup.pl</kbd> will now attempt to output its messages in your
+ terminal's character set.</li>
+ <li><strong>Installation:</strong> [% terms.Bugzilla %] no longer needs empty
+ "placeholder" CSS in the <kbd>skins/custom</kbd> directory and other
+ directories. When you update, <kbd>checksetup.pl</kbd> will remove these.
+ This also significantly reduces the number of HTTP requests required to
+ load a page for the first time in [% terms.Bugzilla %].</li>
+ <li><strong>Installation:</strong> For Windows users, [% terms.Bugzilla %]
+ now supports Strawberry Perl fully.</li>
+ <li><strong>Installation:</strong> Now, whenever <kbd>checksetup.pl</kbd>
+ throws an error, it will be printed in the color red, to make it
+ obvious that something is wrong.</li>
+ <li><strong>Installation:</strong> Some actions of <kbd>checksetup.pl</kbd> were
+ silent, in the past. Now, <kbd>checksetup.pl</kbd> will print a message for
+ almost anything it does.</li>
+ <li><strong>Installation:</strong> The process of adding foreign keys
+ to a table is now much faster. This will particularly improve the speed
+ of upgrading from [% terms.Bugzilla %] 3.4 or earlier.</li>
+ <li>If you are using <kbd>jobqueue.pl</kbd> and email gets heavily delayed
+ for some reason, those emails will now have a Date header reflecting the
+ time they were <em>supposed</em> to be sent, instead of when they actually
+ <em>were</em> sent.</li>
+ <li><kbd>./jobqueue.pl install</kbd> now works on SuSE Linux.</li>
+ <li>[% terms.Bugzilla %] now runs much better in Apache's suexec mode
+ than it used to. As part of this, <kbd>checksetup.pl</kbd> sets
+ much stricter permissions on all the files in [% terms.Bugzilla %]
+ than it used to. In particular, any files that [% terms.Bugzilla %]
+ does not know about will not be readable by the webserver.</li>
+ <li>The <kbd>sendmailnow</kbd> parameter has been removed, as it was
+ not necessary for any modern version of Sendmail or other Mail Transfer
+ Agent.</li>
+ <li>When editing a user via the Users administration panel, you can now
+ see if they are a Default CC on any component.</li>
+ <li>For new installations of [% terms.Bugzilla %], all users will be
+ able to see and use the Whining system by default.</li>
+ <li>When you are using SSL with [% terms.Bugzilla %], you can now
+ turn on the <kbd>strict_transport_security</kbd> parameter to
+ send the
+ <a href="https://developer.mozilla.org/en/Security/HTTP_Strict_Transport_Security">Strict-Transport-Security</a>
+ header with every HTTPS connection, for additional security.</li>
+ <li>New code hooks (see their documentation in
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Hook.html">Bugzilla::Hook</a>):
+ bug_check_can_change_field, search_operator_field_override,
+ bugmail_relationships, object_columns, object_update_columns,
+ and object_validators. The colchange_columns hook has been removed,
+ as it is no longer necessary (buglist_columns will be used for data
+ about which columns can be on the [% terms.bug %] list).</li>
+ <li>When [% terms.Bugzilla %] throws certain types of errors, it will
+ now include a "traceback" of where exactly the error occurred in the
+ code, to help administrators and developers debug problems.</li>
+ <li>There is now a test, <kbd>xt/search.t</kbd>, that assures that all
+ of the functionality of <kbd>Bugzilla::Search</kbd> is working properly.
+ If you customize the search functionality of [% terms.Bugzilla %],
+ you may wish to run this test to assure that your changes are correct.
+ You can see more information about running this test by doing
+ <kbd>perldoc xt/search.t</kbd> at the command line.</li>
+ <li>[% terms.Bugzilla %] now sends the
+ <a href="https://developer.mozilla.org/en/the_x-frame-options_response_header"><code>X-Frame-Options: SAMEORIGIN</code></a> header
+ with every page request in order to prevent "clickjacking" attacks. Note
+ that this prevents other domains from displaying [% terms.Bugzilla %]
+ in an HTML frame.</li>
+</ul>
+
+<h4 id="v40_feat_ws_changes">WebService Changes</h4>
+
+<ul>
+ <li>You can now call some JSON-RPC methods using HTTP GET, in addition to
+ using HTTP POST. See the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Server/JSONRPC.html#Connecting_via_GET">JSON-RPC
+ documentation</a> for details.</li>
+ <li>You can now update existing [% terms.bugs %] using the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#update">B[% %]ug.update</a>
+ function.</li>
+ <li>You can now add attachments to [% terms.bugs %] using the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#add_attachment">B[% %]ug.add_attachment</a>
+ function.</li>
+ <li>The <kbd>B[% %]ug.get</kbd> function now returns all of [% terms.abug %]'s
+ information other than comments and attachments.</li>
+ <li><kbd>B[% %]ug.get</kbd> no longer returns the <kbd>internals</kbd> hash.</li>
+ <li>The <kbd>B[% %]ug.attachments</kbd> function now also returns attachment
+ data.</li>
+ <li>The following functions now support the <kbd>include_fields</kbd>
+ and <kbd>exclude_fields</kbd> arguments: <kbd>B[% %]ug.get</kbd>,
+ <kbd>B[% %]ug.search</kbd>, and <kbd>B[% %]ug.attachments</kbd>. Also,
+ server-side performance of the WebService is actually increased when
+ using these arguments, now, as [% terms.Bugzilla %] will no longer
+ get data from the database for fields you haven't asked for.</li>
+ <li>You can now mark the initial description of [% terms.abug %] as
+ private when filing [% terms.abug %] via the <kbd>B[% %]ug.create</kbd>
+ function.</li>
+ <li>You can now specify groups to put [% terms.abug %] in, in the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#create">B[% %]ug.create</a>
+ function. (This also means that you can specify groups when filing
+ [%+ terms.abug %] via email_in.pl.)</li>
+ <li>The <kbd>User.get</kbd> function now accepts <kbd>groups</kbd>
+ and <kbd>group_ids</kbd> arguments, to limit the returned values to
+ only users in the specified groups.</li>
+ <li>There is a new, undocumented B[% %]ug.possible_duplicates
+ function that helps implement the automatic duplicate detection
+ system. Because this function is not documented, its API may change
+ between releases of [% terms.Bugzilla %].</li>
+ <li>You can no longer search using the <kbd>votes</kbd> argument in
+ <kbd>B[% %]ug.search</kbd>.</li>
+ <li><kbd>B[% %]ug.attachments</kbd> now returns the attachment's description
+ using the name "summary" instead of the name "description", to be
+ consistent with the fact that [% terms.bug %] summaries are called
+ "summary". The value is still <em>also</em> returned as "description",
+ for backwards compatibility, but this backwards compatibility will go
+ away in [% terms.Bugzilla %] 5.0.</li>
+ <li>In the return values of various <kbd>B[% %]ug</kbd> functions, the author
+ of comments, [% terms.bugs %], and attachments is now called "creator",
+ instead of sometimes being called "reporter", "author", or "attacher".
+ The old names are retained for backwards-compatibility, and will stay
+ around until [% terms.Bugzilla %] 5.0.</li>
+</ul>
+
+<h2 id="v40_issues">Outstanding Issues</h2>
<ul>
<li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=423439">
[%- terms.Bug %] 423439</a>: Tabs in comments will be converted
to four spaces, due to a b<!-- -->ug in Perl as of Perl 5.8.8.</li>
- <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69621">
- [%- terms.Bug %] 69621</a>: If you rename or remove a keyword that is
- in use on [% terms.bugs %], you will need to rebuild the "keyword cache"
- by running <a href="sanitycheck.cgi">sanitycheck.cgi</a> and choosing
- the option to rebuild the cache when it asks. Otherwise keywords may
- not show up properly in search results.</li>
<li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
[%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
the same time, there is no "mid-air collision" protection.</li>
@@ -500,1134 +1242,136 @@
There is currently no way to change this restriction, and the
groupings will not be updated if the group configuration
for the Product changes.</li>
- <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370370">
- [%- terms.Bug %] 370370</a>: mod_perl support is currently not
- working on Windows machines.</li>
</ul>
-<h2><a name="v32_upgrading"></a>How to Upgrade From An Older Version</h2>
+<h2 id="v40_upgrading">Notes On Upgrading From a Previous Version</h2>
-<h3><a name="v32_upgrading_notes"></a>Notes For Upgraders</h3>
+<h3>IMPORTANT: Apache Configuration Change</h3>
+
+<h4>mod_cgi</h4>
+
+<p>If you run [% terms.Bugzilla %] under mod_cgi (this is the most common
+ configuration, involving a <Directory> block in your Apache config
+ file), you will need to update the configuration of Apache for
+ [%+ terms.Bugzilla %]. In particular, this line in the [% terms.Bugzilla %]
+ <kbd><Directory></kbd> block:</p>
+
+<blockquote><code>AllowOverride Limit</code></blockquote>
+
+<p>needs to become:</p>
+
+<blockquote><code>AllowOverride Limit FileInfo Indexes</code></blockquote>
+
+<p>For full details on how to configure Apache for [% terms.Bugzilla %],
+ see the
+ <a href="[% docs_urlbase FILTER html %]configuration.html#http-apache">Configuration</a>
+ section of the [% terms.Bugzilla %] Guide.</p>
+
+<h4>mod_perl</h4>
+
+<p>If your [% terms.Bugzilla %] runs under mod_perl, the required Apache
+ configuration is now simpler. The line that used to look like:</p>
+
+<blockquote><code>PerlSwitches -w -T -I/var/www/html/bugzilla
+ -I/var/www/html/bugzilla/lib</code></blockquote>
+
+<p>Now should be only:</p>
+
+<blockquote><code>PerlSwitches -w -T</code></blockquote>
+
+<p>The <code>PerlConfigRequire</code> line should stay the same, however.</p>
+
+<h3>New .htaccess file</h3>
+
+<p>In previous versions of [% terms.Bugzilla %], there was a file
+ in [% terms.Bugzilla %]'s root directory called ".htaccess" that was
+ generated by <kbd>checksetup.pl</kbd>. This file is now shipped with
+ [%+ terms.Bugzilla %] instead of being generated during installation.</p>
+
+<p>If you update via CVS or bzr, you will get a message that your existing
+ .htaccess file conflicts with the new one. You must
+ <strong>remove your existing .htaccess file</strong> and use the new one
+ instead. Continuing to use your old .htaccess file will cause certain new
+ features of [% terms.Bugzilla %] to not work properly, and may also lead
+ to security issues for your system in the future.</p>
+
+<h2 id="v40_code_changes">Code Changes Which May Affect Customizations and
+ Extensions</h2>
<ul>
- <li>If you upgrade by CVS, the <kbd>extensions</kbd> and
- <kbd>skins/contrib</kbd> directories are now in CVS instead of
- being created by <kbd>checksetup.pl</kbd> If you do a <kbd>cvs update</kbd>
- from 3.0, you will be told that your directories are "in the way" and
- you should delete (or move) them and then do <kbd>cvs update</kbd>
- again. Also, the <kbd>docs</kbd> directory has been restructured
- and after you <kbd>cvs update</kbd> you can delete the <kbd>docs/html</kbd>,
- <kbd>docs/pdf</kbd>, <kbd>docs/txt</kbd>, and <kbd>docs/xml</kbd>
- directories.</li>
- <li>If you are using MySQL, you should know that [% terms.Bugzilla %]
- now uses InnoDB for all tables. <kbd>checksetup.pl</kbd> will convert
- your tables automatically, but if you have InnoDB disabled,
- the upgrade will not be able to complete (and <kbd>checksetup.pl</kbd>
- will tell you so).</li>
-
- <li><strong>You should also read the
- <a href="#v30_upgrading_notes">[% terms.Bugzilla %] 3.0 Notes For Upgraders
- section</a> of the
- <a href="#v32_previous">previous release notes</a> if you are upgrading
- from a version before 3.0.</strong></li>
+ <li>In Extensions, if you want to serve files to the user via the web,
+ they must now be in a <kbd>web/</kbd> subdirectory of your Extension.
+ (For example, <kbd>extensions/Foo/web/</kbd>). <kbd>checksetup.pl</kbd>
+ sets permissions on extensions much more strictly now, and files in
+ other locations (such as your base <kbd>extensions/Foo/</kbd> directory)
+ will no longer be available to [% terms.Bugzilla %] users via the web
+ under certain configurations.</li>
+ <li>Previous versions of [% terms.Bugzilla %] used to allow putting a
+ single file into the "skins" directory and having that be an entire
+ skin. That is no longer allowed, and on upgrade, <kbd>checksetup.pl</kbd>
+ will convert any such skins into a directory with a single
+ <kbd>global.css</kbd> file in them.</li>
+ <li>When updating [% terms.bugs %], you should now use
+ <code>$bug->set_all</code> instead of using the individual
+ <kbd>set_</kbd> methods. In particular, <kbd>set_all</kbd> is now the
+ <em>only</em> way to set the product of [% terms.abug %]. See
+ <kbd>process_bug.cgi</kbd> for an example of how <kbd>set_all</kbd>
+ should be used.</li>
+ <li>You should not insert <script> tags and <link> CSS tags
+ into HTML anymore, in Extensions or in your customizations. Instead,
+ you should push new values into the <kbd>style_urls</kbd> or
+ <kbd>javascript_urls</kbd> parameters. If you have to insert manual
+ tags for some reason, be sure to call "FILTER mtime" on the URL. (Search
+ for other uses of "FILTER mtime" in the templates to see how it is
+ used.)</li>
+ <li>When calling <kbd>Bugzilla::BugMail::Send</kbd>, the "changer"
+ argument must now be a <kbd>Bugzilla::User</kbd> object, not just
+ a login name. The "owner" and "qacontact" arguments are still
+ just login names.</li>
+ <li>When creating a new subclass of Bugzilla::Object, you should no
+ longer use <kbd>UPDATE_VALIDATORS</kbd>. Also, in most cases you will
+ no longer need to override <kbd>run_create_validators</kbd>. Instead,
+ there is a new constant called
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Object.html#VALIDATOR_DEPENDENCIES">VALIDATOR_DEPENDENCIES</a>,
+ that specifies that certain fields have to be validated before other fields.
+ Then, all validators receive each already-validated value in a hash
+ as their fourth argument, so each validator can know the other values
+ that were passed in, while an object is being created. For an example of
+ how to use <kbd>VALIDATOR_DEPENDENCIES</kbd>, see
+ <kbd>Bugzilla/Field.pm</kbd>.</li>
+ <li>In previous versions of [% terms.Bugzilla %], you had to call
+ <code>Bugzilla->template_inner("")</code> after any time
+ that you called <kbd>template_inner</kbd> for a specific language.
+ It is no longer necessary to do this second <kbd>template_inner</kbd>
+ call.</li>
+ <li><kbd>post_bug.cgi</kbd> and <kbd>Bugzilla::Bug->create</kbd> now take
+ the <em>names</em> of groups instead of group ids.</li>
+ <li>Bugzilla::Bugmail now uses Bugzilla::Bug objects internally instead of
+ a lot of direct SQL.</li>
+ <li>For sending changes about [% terms.bugs %], there is now a method
+ called <kbd>send_changes</kbd> that you can call on Bugzilla::Bug
+ objects. For an example of its use, see <kbd>process_bug.cgi</kbd>.</li>
+ <li>The <kbd>Bugzilla::Search</kbd> class has been refactored, and should
+ now be easier to customize.</li>
+ <li>The <kbd>Bugzilla::Util::lsearch</kbd> function is gone. Use
+ <kbd>firstidx</kbd> from <kbd>List::MoreUtils</kbd>, instead.</li>
+ <li>[% terms.Bugzilla %] now includes YUI 2.8.2.</li>
+ <li><kbd>long_list.cgi</kbd>, <kbd>showattachment.cgi</kbd> and
+ <kbd>xml.cgi</kbd> are deprecated scripts which are no longer actively
+ used since [% terms.Bugzilla %] 2.19. These scripts will be removed in
+ [%+ terms.Bugzilla %] 4.2.</li>
</ul>
-<h3>Steps For Upgrading</h3>
+<h2 id="v40_previous">Release Notes For Previous Versions</h2>
-<p>Once you have read the notes above, see the
- <a href="[% docs_urlbase FILTER html %]upgrade.html">Upgrading
- documentation</a> for instructions on how to upgrade.</p>
-
-<h2><a name="v32_code_changes"></a>Code Changes Which May Affect
- Customizations</h2>
-
-<ul>
- <li><a href="#v32_code_hooks">More Hooks!</a></li>
- <li><a href="#v32_code_search">Search.pm Rearchitecture</a></li>
- <li><a href="#v32_code_lib">lib Directory</a></li>
- <li><a href="#v32_code_other">Other Changes</a></li>
-</ul>
-
-<h3><a name="v32_code_hooks"></a>More Hooks!</h3>
-
-<p>There are more code hooks in 3.2 than there were in 3.0. See the
- documentation of <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Hook.html">Bugzilla::Hook</a>
- for more details.</p>
-
-<h3><a name="v32_code_search"></a>Search.pm Rearchitecture</h3>
-
-<p><kbd>Bugzilla/Search.pm</kbd> has been heavily modified, to be much
- easier to read and use. It contains mostly the same code as it did in
- 3.0, but it has been moved around and reorganized significantly.</p>
-
-<h3><a name="v32_code_lib"></a>lib Directory</h3>
-
-<p>As part of implementing <a href="#v32_feat_install">install-module.pl</a>,
- [%+ terms.Bugzilla %] was given a local <kbd>lib</kbd> directory which
- it searches for modules, in addition to the standard system path.</p>
-
-<p>This means that all [% terms.Bugzilla %] scripts now start with
- <code>use lib qw(. lib);</code> as one of the first lines.</p>
-
-<h3><a name="v32_code_other"></a>Other Changes</h3>
-
-<ul>
- <li>You should now be using <code>get_status('NEW')</code> instead of
- <code>status_descs.NEW</code> in templates.</li>
- <li>The <code>[%# version = 1.0 %]</code> comment at the top of every
- template file has been removed.</li>
-</ul>
-
-<h2><a name="v32_previous"></a>Release Notes For Previous Versions</h2>
-
-<h1>[% terms.Bugzilla %] 3.0.x Release Notes</h1>
-
-<h2>Table of Contents</h2>
-
-<ul class="bz_toc">
- <li><a href="#v30_introduction">Introduction</a></li>
- <li><a href="#v30_point">Updates In This 3.0.x Release</a></li>
- <li><a href="#v30_req">Minimum Requirements</a></li>
- <li><a href="#v30_feat">New Features and Improvements</a></li>
- <li><a href="#v30_issues">Outstanding Issues</a></li>
- <li><a href="#v30_security">Security Fixes In This Release</a></li>
- <li><a href="#v30_upgrading">How to Upgrade From An Older Version</a></li>
- <li><a href="#v30_code_changes">Code Changes Which May Affect
- Customizations</a></li>
- <li><a href="#v30_previous">Release Notes for Previous Versions</a></li>
-</ul>
-
-<h2><a name="v30_introduction"></a>Introduction</h2>
-
-<p>Welcome to [% terms.Bugzilla %] 3.0! It's been over eight years since
- we released [% terms.Bugzilla %] 2.0, and everything has changed since
- then. Even just since our previous release, [% terms.Bugzilla %] 2.22,
- we've added a <em>lot</em> of new features. So enjoy the release, we're
- happy to bring it to you.</p>
-
-<p>If you're upgrading, make sure to read <a href="#v30_upgrading">How to
- Upgrade From An Older Version</a>. If you are upgrading from a release
- before 2.22, make sure to read the release notes for all the
- <a href="#v30_previous">previous versions</a> in between your version
- and this one.</p>
-
-<h2><a name="v30_point">Updates in this 3.0.x Release</a></h2>
-
-<p>This section describes what's changed in the most recent b<!-- -->ug-fix
- releases of [% terms.Bugzilla %] after 3.0. We only list the
- most important fixes in each release. If you want a detailed list of
- <em>everything</em> that's changed in each version, you should use our
- <a href="http://www.bugzilla.org/status/changes.html">Change Log Page</a>.</p>
-
-<h3>3.0.6</h3>
-
-<ul>
- <li>Before 3.0.6, unexpected fatal WebService errors would result in
- a <code>faultCode</code> that was a string instead of a number.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=446327">[% terms.Bug %] 446327</a>)</li>
- <li>If you created a product or component with the same name as one you
- previously deleted, it would fail with an error about the series table.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=247936">[% terms.Bug %] 247936</a>)</li>
-</ul>
-
-<p>See also the <a href="#v30_security">Security Advisory</a> section for
- information about a security issue fixed in this release.</p>
-
-<h3>3.0.5</h3>
-
-<ul>
- <li>If you don't have permission to set a flag, it will now appear
- unchangeable in the UI.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=433851">[% terms.Bug %] 433851</a>)</li>
- <li>If you were running mod_perl, [% terms.Bugzilla %] was not correctly
- closing its connections to the database since 3.0.3, and so sometimes
- the DB would run out of connections.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=441592">[% terms.Bug %] 441592</a>)</li>
- <li>The installation script is now clear about exactly which
- <code>Email::</code> modules are required in Perl, thus avoiding the
- problem where emails show up with a body like
- <samp>SCALAR(0xBF126795)</samp>.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=441541">[% terms.Bug %] 441541</a>)</li>
- <li><a href="[% docs_urlbase FILTER html %]api/email_in.html">email_in.pl</a>
- is no longer case-sensitive for values of <kbd>@product</kbd>.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365697">[% terms.Bug %] 365697</a>)</li>
-</ul>
-
-<p>See also the <a href="#v30_security">Security Advisory</a> section for
- information about security issues fixed in this release.</p>
-
-<h3>3.0.4</h3>
-
-<ul>
- <li>[% terms.Bugzilla %] administrators were not being correctly notified
- about new releases.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=414726">[% terms.Bug %] 414726</a>)</li>
-
- <li>There could be extra whitespace in email subject lines.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=411544">[% terms.Bug %] 411544</a>)</li>
-
- <li>The priority, severity, OS, and platform fields were always required by
- the <kbd>B<!-- -->ug.create</kbd> WebService function, even if they had
- defaults specified.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=384009">[% terms.Bug %] 384009</a>)</li>
-
- <li>Better threading of [% terms.bug %]mail in some email clients.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=376453">[% terms.Bug %] 376453</a>)</li>
-
- <li>There were many fixes to the Inbound Email Interface
- (<kbd>email_in.pl</kbd>).
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=92274">[% terms.Bug %] 92274</a>,
- <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=377025">[% terms.Bug %] 377025</a>,
- <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=412943">[% terms.Bug %] 412943</a>,
- <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=413672">[% terms.Bug %] 413672</a>, and
- <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=431721">[% terms.Bug %] 431721</a>)</li>
-
- <li>checksetup.pl now handles UTF-8 conversion more reliably during upgrades.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=374951">[% terms.Bug %] 374951</a>)</li>
-
- <li>Comments written in CJK languages are now correctly word-wrapped.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=388723">[% terms.Bug %] 388723</a>)</li>
-
- <li>All emails will now be sent in the correct language, when the user
- has chosen a language for emails.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=405946">[% terms.Bug %] 405946</a>)
-
- <li>On Windows, temporary files created when uploading attachments are now
- correctly deleted when the upload is complete.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=414002">[% terms.Bug %] 414002</a>)</li>
-
- <li><kbd>checksetup.pl</kbd> now prints correct installation instructions
- for Windows users using Perl 5.10.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=414430">[% terms.Bug %] 414430</a>)
-</ul>
-
-<p>See also the <a href="#v30_security">Security Advisory</a> section for
- information about security issues fixed in this release.</p>
-
-<h3>3.0.3</h3>
-
-<ul>
- <li>mod_perl no longer compiles [% terms.Bugzilla %]'s code for each Apache
- process individually. It now compiles code only once and shares it among
- each Apache process. This greatly improves performance and highly
- decreases the memory footprint.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=398241">[% terms.Bug %] 398241</a>)</li>
-
- <li>You can now search for '---' (without quotes) in versions and milestones.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=362436">[% terms.Bug %] 362436</a>)</li>
-
- <li>[% terms.Bugzilla %] should no longer break lines unnecessarily in
- email subjects. This was causing trouble with some email clients.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=374424">[% terms.Bug %] 374424</a>)</li>
-
- <li>If you had selected "I'm added to or removed from this capacity" option
- for the "CC" role in your email preferences, you wouldn't get mail when
- more than one person was added to the CC list at once.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=394796">[% terms.Bug %] 394796</a>)</li>
-
- <li>Deleting a user account no longer deletes whines from another user who
- has the deleted account as addressee. The schedule is simply removed,
- but the whine itself is left intact.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=395924">[% terms.Bug %] 395924</a>)</li>
-
- <li><kbd>contrib/merge-users.pl</kbd> now correctly merges all required
- fields when merging two user accounts.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=400160">[% terms.Bug %] 400160</a>)</li>
-
- <li>[% terms.Bugzilla %] no longer requires Apache::DBI to run under
- mod_perl. It caused troubles such as lost connections with the DB and
- didn't give any important performance gain.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=408766">[% terms.Bug %] 408766</a>)</li>
-</ul>
-
-<h3>3.0.2</h3>
-
-<ul>
- <li>[% terms.Bugzilla %] should now work on Perl 5.9.5 (and thus the
- upcoming Perl 5.10.0).
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=390442">[% terms.Bug %] 390442</a>)</li>
-</ul>
-
-<p>See also the <a href="#v30_security">Security Advisory</a> section for
- information about an important security issue fixed in this release.</p>
-
-<h3>3.0.1</h3>
-
-<ul>
- <li>For users of Firefox 2, the <code>show_bug.cgi</code> user interface
- should no longer "collapse" after you modify [% terms.abug %].
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370739">[% terms.Bug %] 370739</a>)</li>
- <li>If you can bless a group, and you share a saved search with that
- group, it will no longer automatically appear in all of that group's
- footers unless you specifically request that it automatically appear
- in their footers.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365890">[% terms.Bug %] 365890</a>)</li>
- <li>There is now a parameter to allow users to perform searches without
- any search terms. (In other words, to search for just a Product
- and Status on the Simple Search page.) The parameter is called
- <code>specific_search_allow_empty_words</code>.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=385910">[% terms.Bug %] 385910</a>)</li>
- <li>If you attach a file that has a MIME-type of <code>text/x-patch</code>
- or <code>text/x-diff</code>, it will automatically be treated as a
- patch by [% terms.Bugzilla %].
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365756">[% terms.Bug %] 365756</a>)</li>
- <li>Dependency Graphs now work correctly on all mod_perl installations.
- There should now be no remaining signficant problems with running
- [%+ terms.Bugzilla %] under mod_perl.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370398">[% terms.Bug %] 370398</a>)</li>
- <li>If moving [% terms.abug %] between products would remove groups
- from the [% terms.bug %], you are now warned.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=303183">[% terms.Bug %] 303183</a>)</li>
- <li>On IIS, whenever [% terms.Bugzilla %] threw a warning, it would
- actually appear on the web page. Now warnings are suppressed,
- unless you have a file in the <code>data</code> directory called
- <code>errorlog</code>, in which case warnings will be printed there.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=390148">[% terms.Bug %] 390148</a>)</li>
- <li>If you used <kbd>email_in.pl</kbd> to edit [% terms.abug %] that was
- protected by groups, all of the groups would be cleared.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=385453">[% terms.Bug %] 385453</a>)</li>
- <li>PostgreSQL users: New Charts were failing to collect data over time.
- They will now start collecting data correctly.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=257351">[% terms.Bug %] 257351</a>)</li>
- <li>Some flag mails didn't specify who the requestee was.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=379787">[% terms.Bug %] 379787</a>)</li>
- <li>Instead of throwing real errors, <kbd>collectstats.pl</kbd> would
- just say that it couldn't find <code>ThrowUserError</code>.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=380709">[% terms.Bug %] 380709</a>)</li>
- <li>Logging into [% terms.Bugzilla %] from the home page works again
- with IIS5.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=364008">[% terms.Bug %] 364008</a>)</li>
- <li>If you were using SMTP for sending email, sometimes emails would
- be missing the <code>Date</code> header.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=304999">[% terms.Bug %] 304999</a>).</li>
- <li>In the XML-RPC WebService, <code>B<!-- -->ug.legal_values</code> now
- correctly returns values for custom fields if you request values
- for custom fields.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=381737">[% terms.Bug %] 381737</a>)</li>
- <li>The "[% terms.Bug %]-Writing Guidelines" page has been shortened
- and re-written.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=378590">[% terms.Bug %] 378590</a>)</li>
- <li>If your <code>urlbase</code> parameter included a port number,
- like <code>www.domain.com:8080</code>, SMTP might have failed.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=384501">[% terms.Bug %] 384501</a>)</li>
- <li>For SMTP users, there is a new parameter, <code>smtp_debug</code>.
- Turning on this parameter will log the full information about
- every SMTP session to your web server's error log, to help with
- debugging issues with SMTP.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=384497">[% terms.Bug %] 384497</a>)</li>
- <li>If you are a "global watcher" (you get all mails from every
- [% terms.bug %]), you can now see that in your Email Preferences.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365302">[% terms.Bug %] 365302</a>)</li>
- <li>The Status and Resolution of [% terms.bugs %] are now correctly
- localized in CSV search results.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=389517">[% terms.Bug %] 389517</a>)</li>
- <li>The "Subject" line of an email was being mangled if it contained
- non-Latin characters.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=387860">[% terms.Bug %] 387860</a>)</li>
- <li>Editing the "languages" parameter using <kbd>editparams.cgi</kbd> would
- sometimes fail, causing [% terms.Bugzilla %] to throw an error.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=335354">[% terms.Bug %] 335354</a>)</li>
-</ul>
-
-<h2><a name="v30_req"></a>Minimum Requirements</h2>
-
-<p>Any requirements that are new since 2.22 will look like
- <span class="req_new">this</span>.</p>
-
-<ul>
- <li><a href="#v30_req_perl">Perl</a></li>
- <li><a href="#v30_req_mysql">For MySQL Users</a></li>
- <li><a href="#v30_req_pg">For PostgreSQL Users</a></li>
- <li><a href="#v30_req_modules">Required Perl Modules</a></li>
- <li><a href="#v30_req_optional_mod">Optional Perl
- Modules</a></li>
-</ul>
-
-
-<h3><a name="v30_req_perl"></a>Perl</h3>
-
-<ul>
- <li>Perl <span class="req_new">v<strong>5.8.0</strong></span> (non-Windows
- platforms)</li>
- <li>Perl v<strong>5.8.1</strong> (Windows platforms)</li>
-</ul>
-
-<h3><a name="v30_req_mysql"></a>For MySQL Users</h3>
-
-<ul>
- <li>MySQL v4.1.2</li>
- <li><strong>perl module:</strong> DBD::mysql v2.9003</li>
-</ul>
-
-<h3><a name="v30_req_pg"></a>For PostgreSQL Users</h3>
-
-<ul>
- <li>PostgreSQL v8.00.0000</li>
- <li><strong>perl module:</strong> DBD::Pg v1.45</li>
-</ul>
-
-<h3><a name="v30_req_modules"></a>Required Perl Modules</h3>
-
-<table class="req_table" border="0" cellspacing="0" cellpadding="0">
- <tr>
- <th>Module</th> <th>Version</th>
- </tr>
- <tr><td>CGI</td> <td>2.93</td>
- </tr>
- <tr>
- <td>Date::Format</td> <td>2.21</td>
- </tr>
- <tr>
- <td>DBI</td>
- <td class="req_new">1.41</td>
- </tr>
- <tr>
- <td>File::Spec</td> <td>0.84</td>
- </tr>
- <tr>
- <td>Template</td> <td>2.12</td>
- </tr>
- <tr>
- <td class="req_new">Email::Send</td>
- <td class="req_new">2.00</td>
- </tr>
- <tr>
- <td>Email::MIME</td>
- <td>1.861</td>
- </tr>
- <tr>
- <td class="req_new">Email::MIME::Modifier</td>
- <td class="req_new">1.442</td>
- </tr>
-</table>
-
-<h3><a name="v30_req_optional_mod"></a>Optional Perl Modules</h3>
-
-<p>The following perl modules, if installed, enable various
- features of [% terms.Bugzilla %]:</p>
-
-<table class="req_table" border="0" cellspacing="0" cellpadding="0">
- <tr>
- <th>Module</th> <th>Version</th>
- <th>Enables Feature</th>
- </tr>
- <tr>
- <td class="req_new">LWP::UserAgent</td>
- <td class="req_new">(Any)</td>
- <td>Automatic Update Notifications</td>
- </tr>
- <tr>
- <td>Template::Plugin::GD::Image</td>
- <td>(Any)</td>
- <td>Graphical Reports</td>
- </tr>
- <tr>
- <td>GD::Graph</td>
- <td>(Any)</td>
- <td>Graphical Reports</td>
- </tr>
- <tr>
- <td>GD::Text</td>
- <td>(Any)</td>
- <td>Graphical Reports</td>
- </tr>
- <tr>
- <td>GD</td>
- <td>1.20</td>
- <td>Graphical Reports, New Charts, Old Charts</td>
- </tr>
- <tr>
- <td class="req_new">Email::MIME::Attachment::Stripper</td>
- <td class="req_new">(Any)</td>
- <td>Inbound Email</td>
- </tr>
- <tr>
- <td class="req_new">Email::Reply</td>
- <td class="req_new">(Any)</td>
- <td>Inbound Email</td>
- </tr>
- <tr>
- <td>Net::LDAP</td>
- <td>(Any)</td>
- <td>LDAP Authentication</td>
- </tr>
- <tr>
- <td>HTML::Parser</td>
- <td>3.40</td>
- <td>More HTML in Product/Group Descriptions</td>
- </tr>
- <tr>
- <td>HTML::Scrubber</td>
- <td>(Any)</td>
- <td>More HTML in Product/Group Descriptions</td>
- </tr>
- <tr>
- <td>XML::Twig</td>
- <td>(Any)</td>
- <td>Move [% terms.Bugs %] Between Installations</td>
- </tr>
- <tr>
- <td>MIME::Parser</td>
- <td>5.406</td>
- <td>Move [% terms.Bugs %] Between Installations</td>
- </tr>
- <tr>
- <td>Chart::Base</td>
- <td>1.0</td>
- <td>New Charts, Old Charts</td>
- </tr>
- <tr>
- <td>Image::Magick</td>
- <td>(Any)</td>
- <td>Optionally Convert BMP Attachments to PNGs</td>
- </tr>
- <tr>
- <td>PatchReader</td>
- <td>0.9.4</td>
- <td>Patch Viewer</td>
- </tr>
- <tr>
- <td class="req_new">SOAP::Lite</td>
- <td class="req_new">(Any)</td>
- <td>XML-RPC Interface</td>
- </tr>
- <tr>
- <td class="req_new">mod_perl2</td>
- <td class="req_new">1.999022</td>
- <td>mod_perl</td>
- </tr>
- <tr>
- <td> CGI</td>
- <td>3.11</td>
- <td>mod_perl</td>
- </tr>
-</table>
-
-<h2><a name="v30_feat"></a>New Features and Improvements</h2>
-
-<ul>
- <li><a href="#v30_feat_cf">Custom Fields</a></li>
- <li><a href="#v30_feat_mp">mod_perl Support</a></li>
- <li><a href="#v30_feat_sq">Shared Saved Searches</a></li>
- <li>
- <a href="#v30_feat_afn">Attachments and Flags on New [% terms.Bugs %]</a>
- </li>
- <li><a href="#v30_feat_cr">Custom Resolutions</a></li>
- <li><a href="#v30_feat_ppp">Per-Product Permissions</a></li>
- <li><a href="#v30_feat_ui">User Interface Improvements</a></li>
- <li><a href="#v30_feat_xml">XML-RPC Interface</a></li>
- <li><a href="#v30_feat_skin">Skins</a></li>
- <li><a href="#v30_feat_sbu">Unchangeable Fields Appear
- Unchangeable</a></li>
- <li><a href="#v30_feat_et">All Emails in Templates</a></li>
- <li><a href="#v30_feat_df">No More Double-Filed [% terms.Bugs %]</a></li>
- <li><a href="#v30_feat_cc">Default CC List for Components</a></li>
- <li><a href="#v30_feat_emi">File/Modify [% terms.Bugs %] By Email</a></li>
- <li><a href="#v30_feat_gw">Users Who Get All [% terms.Bug %]
- Notifications</a></li>
- <li><a href="#v30_feat_utf8">Improved UTF-8 Support</a></li>
- <li><a href="#v30_feat_upda">Automatic Update Notification</a></li>
- <li><a href="#v30_feat_welc">Welcome Page for New Installs</a></li>
- <li><a href="#v30_feat_other">Other Enhancements and Changes</a></li>
-</ul>
-
-<h3><a name="v30_feat_cf"></a>Custom Fields</h3>
-
-<p>[% terms.Bugzilla %] now includes very basic support for custom fields.</p>
-
-<p>Users in the <kbd>admin</kbd> group can add plain-text or drop-down
- custom fields. You can edit the values available for drop-down fields
- using the "Field Values" control panel.</p>
-
-<p>Don't add too many custom fields! It can make [% terms.Bugzilla %]
- very difficult to use. Try your best to get along with the default
- fields, and then if you find that you can't live without custom fields
- after a few weeks of using [% terms.Bugzilla %], only then should you
- start your custom fields.</p>
-
-<h3><a name="v30_feat_mp"></a>mod_perl Support</h3>
-
-<p>[% terms.Bugzilla %] 3.0 supports mod_perl, which allows for extremely
- enhanced page-load performance. mod_perl trades memory usage for performance,
- allowing near-instantaneous page loads, but using much more memory.</p>
-
-<p>If you want to enable mod_perl for your [% terms.Bugzilla %], we recommend
- a minimum of 1.5GB of RAM, and for a site with heavy traffic, 4GB to 8GB.</p>
-
-<p>If performance isn't that critical on your installation, you don't
- have the memory, or you are running some other web server than
- Apache, [% terms.Bugzilla %] still runs perfectly as a normal CGI
- application, as well.</p>
-
-<h3><a name="v30_feat_sq"></a>Shared Saved Searches</h3>
-
-<p>Users can now choose to "share" their saved searches
- with a certain group. That group will then be able to
- "subscribe" to those searches, and have them appear
- in their footer.</p>
-
-<p>If the sharer can "bless" the group he's sharing to,
- (that is, if he can add users to that group), it's considered
- that he's a manager of that group, and his queries show up
- automatically in that group's footer (although they can
- unsubscribe from any particular search, if they want.)</p>
-
-<p>In order to allow a user to share their queries, they also
- have to be a member of the group specified in the
- <code>querysharegroup</code> parameter.</p>
-
-<p>Users can control their shared and subscribed queries from
- the "Preferences" screen.</p>
-
-<h3><a name="v30_feat_afn"></a>Attachments and Flags on New
- [% terms.Bugs %]</h3>
-
-<p>You can now add an attachment while you are filing a new
- [% terms.bug %].</p>
-
-<p>You can also set flags on the [% terms.bug %] and on attachments, while
- filing a new [% terms.bug %].</p>
-
-<h3><a name="v30_feat_cr"></a>Custom Resolutions</h3>
-
-<p>You can now customize the list of resolutions available
- in [% terms.Bugzilla %], including renaming the default resolutions.</p>
-
-<p>The resolutions <code>FIXED</code>, <code>DUPLICATE</code>
- and <code>MOVED</code> have a special meaning to [% terms.Bugzilla %],
- though, and cannot be renamed or deleted.</p>
-
-<h3><a name="v30_feat_ppp"></a>Per-Product Permissions</h3>
-
-<p>You can now grant users <kbd>editbugs</kbd> and <kbd>canconfirm</kbd>
- for only certain products. You can also grant users <kbd>editcomponents</kbd>
- on a product, which means they will be able to edit that product
- including adding/removing components and other product-specific
- controls.</p>
-
-<h3><a name="v30_feat_ui"></a>User Interface Improvements</h3>
-
-<p>There has been some work on the user interface for [% terms.Bugzilla %] 3.0,
- including:</p>
-
-<ul>
- <li>There is now navigation and a search box a the <em>top</em> of
- each page, in addition to the bar at the bottom of the page.</li>
- <li>A re-designed "Format for Printing" page for
- [% terms.bugs %].</li>
- <li>The layout of <kbd>show_bug.cgi</kbd> (the [% terms.bug %] editing
- page) has been changed, and the attachment table has been redesigned.</li>
-</ul>
-
-<h3><a name="v30_feat_xml"></a>XML-RPC Interface</h3>
-
-<p>[% terms.Bugzilla %] now has a Web Services interface using the XML-RPC
- protocol. It can be accessed by external applications by going
- to the <kbd>xmlrpc.cgi</kbd> on your installation.</p>
-
-<p>Documentation can be found in the
- <a href="[% docs_urlbase FILTER html %]api/">[% terms.Bugzilla %]
- API Docs</a>, in the various <kbd>Bugzilla::WebService</kbd> modules.</p>
-
-<h3><a name="v30_feat_skin"></a>Skins</h3>
-
-<p>[% terms.Bugzilla %] can have multiple "skins" installed,
- and users can pick between them. To write a skin, you just have to
- write several CSS files. See the <a href="[% docs_urlbase FILTER html %]cust-skins.html">Custom
- Skins Documentation</a> for more details.</p>
-
-<p>We currently don't have any alternate skins shipping with
- [% terms.Bugzilla %]. If you write an alternate skin, please
- let us know!</p>
-
-<h3><a name="v30_feat_sbu"></a>Unchangeable Fields Appear
- Unchangeable</h3>
-
-<p>As long as you are logged in, when viewing [% terms.abug %], if you
- cannot change a field, it will not look like you can change it. That
- is, the value will just appear as plain text.</p>
-
-<h3><a name="v30_feat_et"></a>All Emails in Templates</h3>
-
-<p>All outbound emails are now controlled by the templating system.
- What used to be the <code>passwordmail</code>, <code>whinemail</code>,
- <code>newchangedmail</code> and <code>voteremovedmail</code>
- parameters are now all templates in the <kbd>template/</kbd> directory.</p>
-
-<p>This means that it's now much easier to customize your outbound
- emails, and it's also possible for localizers to have more
- localized emails as part of their language packs, if they want.</p>
-
-<p>We also added a <code>mailfrom</code> parameter to let you set
- who shows up in the <code>From</code> field on all emails that
- [%+ terms.Bugzilla %] sends.</p>
-
-<h3><a name="v30_feat_df"></a>No More Double-Filed [% terms.Bugs %]</h3>
-
-<p>Users of [% terms.Bugzilla %] will sometimes accidentally submit
- [% terms.abug %] twice, either by going back in their web browser,
- or just by refreshing a page. In the past, this could file the same
- [% terms.bug %] twice (or even three times) in a row, irritating
- developers and confusing users.</p>
-
-<p>Now, if you try to submit [% terms.abug %] twice from the same screen
- (by going back or by refreshing the page), [% terms.Bugzilla %] will warn
- you about what you're doing, before it actually submits the duplicate
- [%+ terms.bug %].</p>
-
-<h3><a name="v30_feat_cc"></a>Default CC List for Components</h3>
-
-<p>You can specify a list of users who will <em>always</em> be added to
- the CC list of new [% terms.bugs %] in a component.</p>
-
-<h3><a name="v30_feat_emi"></a>File/Modify [% terms.Bugs %] By Email</h3>
-
-<p>You can now file or modify [% terms.bugs %] via email. Previous versions
- of [% terms.Bugzilla %] included this feature only as an
- unsupported add-on, but it is now an official interface to
- [%+ terms.Bugzilla %].</p>
-
-<p>For more details see the <a href="[% docs_urlbase FILTER html %]api/email_in.html">documentation
- for email_in.pl</a>.</p>
-
-<h3><a name="v30_feat_gw"></a>Users Who Get All [% terms.Bug %]
- Notifications</h3>
-
-<p>There is now a parameter called <kbd>globalwatchers</kbd>. This
- is a comma-separated list of [% terms.Bugzilla %] users who will
- get all [% terms.bug %] notifications generated by [% terms.Bugzilla %].</p>
-
-<p>Group controls still apply, though, so users who can't see [% terms.abug %]
- still won't get notifications about that [% terms.bug %].</p>
-
-<h3><a name="v30_feat_utf8"></a>Improved UTF-8 Support</h3>
-
-<p>[% terms.Bugzilla %] users running MySQL should now have excellent
- UTF-8 support if they turn on the <kbd>utf8</kbd> parameter. (New
- installs have this parameter on by default.) [% terms.Bugzilla %]
- now correctly supports searching and sorting in non-English languages,
- including multi-bytes languages such as Chinese.</p>
-
-<h3><a name="v30_feat_upda"></a>Automatic Update Notification</h3>
-
-<p>If you belong to the <kbd>admin</kbd> group, you will be notified
- when you log in if there is a new release of [% terms.Bugzilla %]
- available to download.</p>
-
-<p>You can control these notifications by changing the
- <kbd>upgrade_notification</kbd> parameter.</p>
-
-<p>If your [% terms.Bugzilla %] installation is on a machine that needs to go
- through a proxy to access the web, you may also have to set the
- <kbd>proxy_url</kbd> parameter.</p>
-
-<h3><a name="v30_feat_welc"></a>Welcome Page for New Installs</h3>
-
-<p>When you log in for the first time on a brand-new [% terms.Bugzilla %]
- installation, you will be presented with a page that describes
- where you should go from here, and what parameters you should set.</p>
-
-<h3><a name="v30_feat_qs"></a>QuickSearch Plugin for IE7 and Firefox 2</h3>
-
-<p>Firefox 2 users and Internet Explorer 7 users will be presented
- with the option to add [% terms.Bugzilla %] to their search bar.
- This uses the
- <a href="page.cgi?id=quicksearch.html">QuickSearch syntax</a>.</p>
-
-<h3><a name="v30_feat_other"></a>Other Enhancements and Changes</h3>
-
-<p>These are either minor enhancements, or enhancements that have
- very short descriptions. Some of these are very useful, though!</p>
-
-<h4>Enhancements That Affect [% terms.Bugzilla %] Users</h4>
-
-<ul>
- <li>In comments, quoted text (lines that start with <kbd>></kbd>)
- will be a different color from normal text.</li>
- <li>There is now a user preference that will add you to the CC list
- of any [% terms.bug %] you modify. Note that it's <strong>on</strong>
- by default.</li>
- <li>[% terms.Bugs %] can now be filed with an initial state of
- <kbd>ASSIGNED</kbd>, if you are in the <kbd>editbugs</kbd> group.</li>
- <li>By default, comment fields will zoom large when you are typing in them,
- and become small when you move out of them. You can disable this
- in your user preferences.</li>
- <li>You can hide obsolete attachments on [% terms.abug %] by clicking
- "Hide Obsolete" at the bottom of the attachment table.</li>
- <li>If [% terms.abug %] has flags set, and you move it to a different
- product that has flags with the same name, the flags will be
- preserved.</li>
- <li>You now can't request a flag to be set by somebody who can't set it
- ([% terms.Bugzilla %] will throw an error if you try).</li>
- <li>Many new headers have been added to outbound [% terms.Bugzilla %]
- [%+ terms.bug %] emails: <code>X-Bugzilla-Status</code>,
- <code>X-Bugzilla-Priority</code>, <code>X-Bugzilla-Assigned-To</code>,
- <code>X-Bugzilla-Target-Milestone</code>, and
- <code>X-Bugzilla-Changed-Fields</code>, <code>X-Bugzilla-Who</code>.
- You can look at an email to get an idea of what they contain.</li>
- <li>In addition to the old <code>X-Bugzilla-Reason</code> email header
- which tells you why you got an email, if you got an email because
- you were watching somebody, there is now an
- <code>X-Bugzilla-Watch-Reason</code> header that tells you who you
- were watching and what role they had.</li>
- <li>If you hover your mouse over a full URL (like
- <code>http://bugs.mycompany.com/show_bug.cgi?id=1212</code>) that
- links to [% terms.abug %], you will see the title of the
- [%+ terms.bug %]. Of course, this only works for [% terms.bugs %] in your
- [%+ terms.Bugzilla %] installation.</li>
- <li>If your installation has user watching enabled, you will now see
- the users that you can remove from your watch-list as a multi-select
- box, much like the current CC list. (Previously it was just a text
- box.)</li>
- <li>When a user creates their own account in [% terms.Bugzilla %], the
- account is now not actually created until they verify their email
- address by clicking on a link that is emailed to them.</li>
- <li>You can change [% terms.abug %]'s resolution without reopening it.</li>
- <li>When you view the dependency tree on [% terms.abug %], resolved
- [%+ terms.bugs %] will be hidden by default. (In previous versions,
- resolved [% terms.bugs %] were shown by default.)</li>
- <li>When viewing [% terms.bug %] activity, fields that hold [% terms.bug %]
- numbers (such as "Blocks") will have the [% terms.bug %] numbers
- displayed as links to those [% terms.bugs %].</li>
- <li>When viewing the "Keywords" field in [% terms.abug %] list,
- it will be sorted alphabetically, so you can sanely sort a list on
- that field.</li>
- <li>In most places, the Version field is now sorted using a version-sort
- (so 1.10 is greater than 1.2) instead of an alphabetical sort.</li>
- <li>Options for flags will only appear if you can set them. So, for
- example, if you can't grant <kbd>+</kbd> on a flag, that option
- won't appear for you.</li>
- <li>You can limit the product-related output of <kbd>config.cgi</kbd>
- by specifying a <kbd>product=</kbd> URL argument, containing the name
- of a product. You can specify the argument more than once for multiple
- products.</li>
- <li>You can now search the boolean charts on whether or not a comment
- is private.</li>
-</ul>
-
-<h4>Enhancements For Administrators</h4>
-
-<ul>
- <li>Administrators can now delete attachments, making them disappear
- entirely from [% terms.Bugzilla %].</li>
- <li><kbd>sanitycheck.cgi</kbd> can now only be accessed by users
- in the <kbd>editcomponents</kbd> group.</li>
- <li>The "Field Values" control panel can now only be accessed
- by users in the <kbd>admin</kbd> group. (Previously it was accessible
- to anybody in the <kbd>editcomponents</kbd> group.)</li>
- <li>There is a new parameter <kbd>announcehtml</kbd>, that will allow
- you to enter some HTML that will be displayed at the top of every
- page, as an announcement.</li>
- <li>The <kbd>loginnetmask</kbd> parameter now defaults to 0 for new
- installations, meaning that as long as somebody has the right
- login cookie, they can log in from any IP address. This makes
- life a lot easier for dial-up users or other users whose IP
- changes a lot. This could be done because the login cookie is now
- very random, and thus secure.</li>
- <li>Classifications now have sortkeys, so they can be sorted in an
- order that isn't alphabetical.</li>
- <li>Authentication now supports LDAP over SSL (LDAPS) or TLS (using
- the STARTLS command) in addition to plain LDAP.</li>
- <li>LDAP users can have their LDAP username be their email address,
- instead of having the LDAP <kbd>mail</kbd> attribute be their
- email address. You may wish to set the <kbd>emailsuffix</kbd>
- parameter if you do this.</li>
- <li>Administrators can now see what has changed in a user account,
- when using the "Users" control panel.</li>
- <li><code>REMIND</code> and <code>LATER</code> are no longer part
- of the default list of resolutions. Upgrading installations will
- not be affected--they will still have these resolutions.</li>
- <li><kbd>editbugs</kbd> is now the default for the <kbd>timetrackinggroup</kbd>
- parameter, meaning that time-tracking will be on by default in a new
- installation.</li>
-</ul>
-
-<h2><a name="v30_issues"></a>Outstanding Issues</h2>
-
-<ul>
- <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69621">
- [%- terms.Bug %] 69621</a>: If you rename or remove a keyword that is
- in use on [% terms.bugs %], you will need to rebuild the "keyword cache"
- by running <a href="sanitycheck.cgi">sanitycheck.cgi</a> and choosing
- the option to rebuild the cache when it asks. Otherwise keywords may
- not show up properly in search results.</li>
- <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=99215">
- [%- terms.Bug %] 99215</a>: Flags are not protected by "mid-air
- collision" detection. Nor are any attachment changes.</li>
- <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
- [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
- the same time, there is no "mid-air collision" protection.</li>
- <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
- [%- terms.Bug %] 276230</a>: The support for restricting access to
- particular Categories of New Charts is not complete. You should treat
- the 'chartgroup' Param as the only access mechanism available.<br>
- However, charts migrated from Old Charts will be restricted to
- the groups that are marked MANDATORY for the corresponding Product.
- There is currently no way to change this restriction, and the
- groupings will not be updated if the group configuration
- for the Product changes.</li>
- <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370370">
- [%- terms.Bug %] 370370</a>: mod_perl support is currently not
- working on Windows machines.</li>
- <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=361149">
- [%- terms.Bug %] 361149</a>: If you are using Perl 5.8.0, you may
- get a lot of warnings in your Apache error_log about "deprecated
- pseudo-hashes." These are harmless--they are a b[%# fool test %]ug in
- Perl 5.8.0. Perl 5.8.1 and later do not have this problem.</li>
- <li>[% terms.Bugzilla %] 3.0rc1 allowed custom field column names in
- the database to be mixed-case. [% terms.Bugzilla %] 3.0 only allows
- lowercase column names. It will fix any column names that you have
- made mixed-case, but if you have custom fields that previously were
- mixed-case in any Saved Search, you will have to re-create that Saved
- Search yourself.</li>
-</ul>
-
-<h2><a name="v30_security"></a>Security Updates in This Release</h2>
-
-<h3>3.0.6</h3>
-
-<p>[% terms.Bugzilla %] contains a minor security fix. For details, see the
- <a href="http://www.bugzilla.org/security/2.20.6/">Security Advisory</a>.</p>
-
-<h3>3.0.5</h3>
-
-<p>[% terms.Bugzilla %] contains one security fix for
- <a href="[% docs_urlbase FILTER html %]api/importxml.html">importxml.pl</a>.
- For details, see the
- <a href="http://www.bugzilla.org/security/2.22.4/">Security Advisory</a>.</p>
-
-<h3>3.0.4</h3>
-
-<p>[% terms.Bugzilla %] 3.0.4 contains three security fixes.
- For details, see the
- <a href="http://www.bugzilla.org/security/2.20.5/">Security Advisory</a>.</p>
-</p>
-
-<h3>3.0.3</h3>
-
-<p>No security fixes in this release.</p>
-
-<h3>3.0.2</h3>
-
-<p>[% terms.Bugzilla %] 3.0.1 had an important security fix that is
- critical for public installations with "requirelogin" turned on.
- For details, see the
- <a href="http://www.bugzilla.org/security/3.0.1/">Security Advisory</a></p>
-
-<h3>3.0.1</h3>
-
-<p>[% terms.Bugzilla %] 3.0 had three security issues that have been
- fixed in this release: one minor information leak, one hole only
- exploitable by an admin or using <code>email_in.pl</code>, and one in an
- uncommonly-used template. For details, see the
- <a href="http://www.bugzilla.org/security/2.20.4/">Security Advisory</a>.</p>
-
-<h2><a name="v30_upgrading"></a>How to Upgrade From An Older Version</h2>
-
-<h3><a name="v30_upgrading_notes"></a>Notes For Upgraders</h3>
-
-<ul>
- <li>If you upgrade by CVS, there are several .cvsignore files
- that are now in CVS instead of being locally created by
- <kbd>checksetup.pl</kbd>. This means that you will have to
- delete those files when CVS tells you there's a conflict, and
- then run <kbd>cvs update</kbd> again.</li>
- <li>In this version of [% terms.Bugzilla %], the Summary field
- is now limited to 255 characters. When you upgrade, any Summary
- longer than that will be truncated, and the old summary will be
- preserved in a comment.</li>
- <li>If you have the <kbd>utf8</kbd> parameter turned on, at some
- point you will have to convert your database. <kbd>checksetup.pl</kbd>
- will tell you when this is, and it will give you certain instructions
- at that time, that you have to follow before you can complete
- the upgrade. Don't do the conversion yourself manually--follow
- the instructions of checksetup.pl.</li>
- <li>If you ever ran 2.23.3, 2.23.4, or 3.0rc1, you will have to run
- <kbd>./collectstats.pl --regenerate</kbd> at the command line, because
- the data for your Old Charts is corrupted. This can take several days,
- so you may only want to run it if you use Old Charts.</li>
- <li>You should also read the Outstanding Issues sections of
- <a href="#v30_previous">older release notes</a> if you are upgrading
- from a version lower than 2.22.</li>
-</ul>
-
-<h3>Steps For Upgrading</h3>
-
-<p>Once you have read the notes above, see the
- <a href="[% docs_urlbase FILTER html %]upgrade.html">Upgrading
- documentation</a> for instructions on how to upgrade.</p>
-
-<h2><a name="v30_code_changes"></a>Code Changes Which May Affect
- Customizations</h2>
-
-<ul>
- <li><a href="#v30_code_loc"><strong>Packagers:</strong> Location
- Variables Have Moved</a></li>
- <li><a href="#v30_code_hooks">Hooks!</a></li>
- <li><a href="#v30_code_api">API Documentation</a></li>
- <li><a href="#v30_code_globals">Elimination of globals.pl</a></li>
- <li><a href="#v30_code_scope">Cleaned Up Variable Scoping Issues</a></li>
- <li><a href="#v30_code_sql">No More SendSQL</a></li>
- <li><a href="#v30_code_auth">Auth Re-write</a></li>
- <li><a href="#v30_code_obj">Bugzilla::Object</a></li>
- <li><a href="#v30_code_req">Bugzilla->request_cache</a></li>
- <li><a href="#v30_code_other">Other Changes</a></li>
-</ul>
-
-<h3><a name="v30_code_loc"></a><strong>Packagers:</strong> Location
- Variables Have Moved</h3>
-
-<p>In previous versions of [% terms.Bugzilla %], <kbd>Bugzilla::Config</kbd>
- held all the paths for different things, such as the path to localconfig
- and the path to the <kbd>data/</kbd> directory.</p>
-
-<p>Now, all of this data is stored in a subroutine,
- <kbd>Bugzilla::Constants::bz_locations</kbd>.</p>
-
-<p>Also, note that for mod_perl, <kbd>bz_locations</kbd> must return
- <em>absolute</em> (not relative) paths. There is already code in that
- subroutine to help you with this.</p>
-
-<h3><a name="v30_code_hooks"></a>Hooks!</h3>
-
-<p>[% terms.Bugzilla %] now supports a code hook mechanism. See the
- documentation for
- <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Hook.html">Bugzilla::Hook</a>
- for more details.</p>
-
-<p>This gives [% terms.Bugzilla %] very advanced plugin support. You can
- hook templates, hook code, add new parameters, and use the XML-RPC
- interface. So we'd like to see some [% terms.Bugzilla %] plugins
- written! Let us know on the <a href="http://bugzilla.org/cgi-bin/mj_wwwusr?func=lists-long-full&extra=developers">developers@bugzilla.org</a>
- mailing list if you write a plugin.</p>
-
-<p>If you need more hooks, please
- <a href="http://www.bugzilla.org/developers/reporting_bugs.html">File a
- bug</a>!</p>
-
-<h3><a name="v30_code_api"></a>API Documentation</h3>
-
-<p>[% terms.Bugzilla %] now ships with all of its perldoc built
- as HTML. Go ahead and read the
- <a href="[% docs_urlbase FILTER html %]api/">API Documentation</a>
- for all of the [% terms.Bugzilla %] modules now! Even scripts like
- <kbd>checksetup.pl</kbd> have HTML documentation.</p>
-
-<h3><a name="v30_code_globals"></a>Elimination of globals.pl</h3>
-
-<p>The old file <kbd>globals.pl</kbd> has been eliminated.
- Its code is now in various modules. Each function went to the module
- that was appropriate for it.</p>
-
-<p>Usually we filed [% terms.abug %] in
- <a href="https://bugzilla.mozilla.org">bugzilla.mozilla.org</a> for
- each function we moved. You can search there for the old name of
- the function, and that should get you the information about what
- it's called now and where it lives.</p>
-
-<h3><a name="v30_code_scope"></a>Cleaned Up Variable Scoping Issues</h3>
-
-<p>In normal perl, you can have code like this:</p>
-<pre>my $var = 0;
-sub y { $var++ }</pre>
-
-<p>However, under mod_perl that doesn't work. So variables are no
- longer "shared" with subroutines--instead all variables
- that a subroutine needs must be declared inside the subroutine itself.</p>
-
-<h3><a name="v30_code_sql"></a>No More SendSQL</h3>
-
-<p>The old <kbd>SendSQL</kbd> function and all of its companions are
- <strong>gone</strong>. Instead, we now use DBI for all database
- interaction.</p>
-
-<p>For more information about how to use
- <a href="http://search.cpan.org/perldoc?DBI">DBI</a> with
- [% terms.Bugzilla %], see the
- <a href="http://www.bugzilla.org/docs/developer.html#sql-sendreceive">Developer's
- Guide Section About DBI</a></p>
-
-<h3><a name="v30_code_auth"></a>Auth Re-write</h3>
-
-<p>The <kbd>Bugzilla::Auth</kbd> family of modules have been completely
- re-written. For details on how the new structure of authentication,
- read the
- <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Auth.html">Bugzilla::Auth
- API docs</a>.</p>
-
-<p>It should be very easy to write new authentication plugins, now.</p>
-
-<h3><a name="v30_code_obj"></a>Bugzilla::Object</h3>
-
-<p>There is a new base class for most of our objects,
- <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Object.html">Bugzilla::Object</a>.
- It makes it really easy to create new objects based on things that are
- in the database.</p>
-
-<h3><a name="v30_code_req"></a>Bugzilla->request-cache</h3>
-
-<p><kbd>Bugzilla.pm</kbd> used to cache things like the database
- connection in package-global variables (like <kbd>$_dbh</kbd>).
- That doesn't work in mod_perl, so instead now there's a hash
- that can be accessed through <code>Bugzilla->request_cache</code>
- to store things for the rest of the current page request.</p>
-
-<p>You shouldn't access <code>Bugzilla->request_cache</code> directly,
- but you should use it inside of <kbd>Bugzilla.pm</kbd> if you modify
- that. The only time you should be accessing it directly is if you need
- to reset one of the caches. Hash keys are always named after the function
- that they cache, so to reset the template object, you'd do:
- <code>delete Bugzilla->request_cache->{template};</code>.</p>
-
-<h3><a name="v30_code_other"></a>Other Changes</h3>
-
-<ul>
- <li><code>checksetup.pl</code> has been completely re-written, and most
- of its code moved into modules in the <kbd>Bugzilla::Install</kbd>
- namespace. See the
- <a href="[% docs_urlbase FILTER html %]api/checksetup.html">checksetup
- documentation</a> and <a href="https://bugzilla.mozilla.org/showdependencytree.cgi?id=277502&hide_resolved=0">[% terms.Bugzilla %]
- [%+ terms.bug %] 277502</a> for details.</li>
- <li>Instead of <kbd>UserInGroup()</kbd>, all of [% terms.Bugzilla %] now
- uses <kbd>Bugzilla->user->in_group</kbd></li>
- <li>mod_perl doesn't like dependency loops in modules, so we now have
- a test for that detects dependency loops in modules when you run
- <kbd>runtests.pl</kbd>.</li>
- <li><kbd>globals.pl</kbd> used to modify the environment variables,
- like <kbd>PATH</kbd>. That now happens in <kbd>Bugzilla.pm</kbd>.</li>
- <li>Templates can now link to the documentation more easily.
- See the <kbd>global/code-error.html.tmpl</kbd> and
- <kbd>global/user-error.html.tmpl</kbd> templates for examples.
- (Search for "docslinks.")</li>
- <li>Parameters are accessed through <kbd>Bugzilla->params</kbd>
- instead of using the <kbd>Param()</kbd> function, now.</li>
- <li>The variables from the <kbd>localconfig</kbd> file are accessed
- through the <code>Bugzilla->localconfig</code> hash instead of through
- <kbd>Bugzilla::Config</kbd>.</li>
- <li><kbd>Bugzilla::BugMail::MessageToMTA()</kbd> has moved into its
- own module, along with other mail-handling code, called
- <kbd>Bugzilla::Mailer</kbd></li>
- <li>The <kbd>CheckCanChangeField()</kbd> subroutine in
- <kbd>process_bug.cgi</kbd> has been moved to <kbd>Bugzilla::Bug</kbd>,
- and is now a method of [% terms.abug %] object.</li>
- <li>The code that used to be in the <kbd>global/banner.html.tmpl</kbd>
- template is now in <kbd>global/header.html.tmpl</kbd>. The banner
- still exists, but the file is empty.</li>
-</ul>
-
-<h2><a name="v30_previous"></a>Release Notes For Previous Versions</h2>
-
-<p>Release notes for versions of [% terms.Bugzilla %] for versions
- prior to 3.0 are only available in text format:
- <a href="[% docs_urlbase FILTER remove('html/$') FILTER html %]rel_notes.txt">Release Notes for [% terms.Bugzilla %] 2.22
- and Earlier</a>.</p>
+<p><a href="page.cgi?id=release-notes3.html">Release Notes for
+ [%+ terms.Bugzilla %] 3.x and Earlier</a></p>
[% INCLUDE global/footer.html.tmpl %]
[% BLOCK db_req %]
[% SET m = DB_MODULE.$db %]
- <h3><a name="v32_req_[% db FILTER html %]"></a>For [% m.name FILTER html %]
- Users</h3>
+ <h3 id="v42_req_[% db FILTER html %]">For [% m.name FILTER html %] Users</h3>
<ul>
<li>[% m.name FILTER html %]
@@ -1652,10 +1396,10 @@
</tr>
[% FOREACH req = reqs %]
<tr>
- <td [% 'class="req_new"' IF new.contains(req.package) %]>
+ <td [% ' class="req_new"' IF new.contains(req.package) %]>
[%- req.module FILTER html %]</td>
- <td [% 'class="req_new"' IF updated.contains(req.package)
- OR new.contains(req.package) %]>
+ <td [% ' class="req_new"' IF updated.contains(req.package)
+ OR new.contains(req.package) %]>
[%- IF req.version == 0 %]
(Any)
[% ELSE %]
@@ -1663,7 +1407,7 @@
[% END %]
</td>
[% IF include_feature %]
- <td>[% req.feature FILTER html %]</td>
+ <td>[% req.feature.join(', ') FILTER html %]</td>
[% END %]
</tr>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/pages/release-notes3.html.tmpl b/Websites/bugs.webkit.org/template/en/default/pages/release-notes3.html.tmpl
new file mode 100644
index 0000000..79f0528
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/pages/release-notes3.html.tmpl
@@ -0,0 +1,3483 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Everything Solved.
+ # Portions created by Everything Solved are Copyright (C) 2006
+ # Everything Solved. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+[% SET title = "$terms.Bugzilla 3.6 Release Notes" %]
+[% INCLUDE global/header.html.tmpl
+ title = title
+ style_urls = ['skins/standard/page.css']
+%]
+
+<h3>Release Notes For Newer Versions</h3>
+
+<p>Release notes for versions of [% terms.Bugzilla %] of the 4.x series are
+ available <a href="page.cgi?id=release-notes.html">here</a>.</p>
+
+<h1>[% title FILTER html %]</h1>
+
+<ul class="bz_toc">
+ <li><a href="#v36_introduction">Introduction</a></li>
+ <li><a href="#v36_point">Updates in this 3.6.x Release</a></li>
+ <li><a href="#v36_req">Minimum Requirements</a></li>
+ <li><a href="#v36_feat">New Features and Improvements</a></li>
+ <li><a href="#v36_issues">Outstanding Issues</a></li>
+ <li><a href="#v36_upgrading">Notes On Upgrading From a Previous Version</a></li>
+ <li><a href="#v36_code_changes">Code Changes Which May Affect
+ Customizations</a></li>
+ <li><a href="#v36_previous">Release Notes for Previous Versions</a></li>
+</ul>
+
+<h2 id="v36_introduction">Introduction</h2>
+
+<p>Welcome to [% terms.Bugzilla %] 3.6! The focus of the 3.6 release is
+ on improving usability and "polishing up" all our features (by adding
+ some pieces that were "missing" or always wanted), although we
+ also have a few great new features for you, as well!</p>
+
+<p>If you're upgrading, make sure to read <a href="#v36_upgrading">Notes
+ On Upgrading From a Previous Version</a>. If you are upgrading from a release
+ before 3.4, make sure to read the release notes for all the
+ <a href="#v36_previous">previous versions</a> in between your version
+ and this one, <strong>particularly the Upgrading section of each
+ version's release notes</strong>.</p>
+
+<p>We would like to thank <a href="http://www.canonical.com/">Canonical
+ Ltd.</a>, <a href="http://www.itasoftware.com/">ITA Software</a>,
+ the <a href="http://www.ibm.com/linux/ltc/">IBM Linux Technology Center</a>,
+ <a href="http://www.redhat.com/">Red Hat</a>, and
+ <a href="http://www.novell.com/">Novell</a> for funding the development
+ of various features and improvements in this release of
+ [%+ terms.Bugzilla %].</p>
+
+<h2 id="v36_point">Updates in this 3.6.x Release</h2>
+
+<h3>3.6.2</h3>
+
+<p>This release fixes various security issues. See the
+ <a href="http://www.bugzilla.org/security/3.2.7/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in
+ this release:</p>
+
+<ul>
+ <li>[% terms.Bugzilla %] installations running on older versions of IIS
+ will no longer experience the "Undef to trick_taint" errors that would
+ sometimes occur.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=521416">[% terms.Bug %] 521416</a>)
+ </li>
+ <li>Email notifications were missing the dates that comments were made.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=578003">[% terms.Bug %] 578003</a>)
+ </li>
+ <li>Putting a phrase in quotes in the Quicksearch box now works properly,
+ again.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=578494">[% terms.Bug %] 578494</a>
+ and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=553884">[% terms.Bug %] 553884</a>)
+ </li>
+ <li>Quicksearch was usually (incorrectly) being limited to 200 results.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=581622">[% terms.Bug %] 581622</a>)
+ </li>
+ <li>On Windows, <kbd>install-module.pl</kbd> can now properly install
+ DateTime and certain other Perl modules that didn't install properly
+ before.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=576105">[% terms.Bug %] 576105</a>)
+ </li>
+ <li>Searching "keywords" for "contains none of the words" or "does not
+ match regular expression" now works properly.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=562014">[% terms.Bug %] 562014</a>)
+ </li>
+ <li>Doing <kbd>collectstats.pl --regenerate</kbd> now works on installations
+ using PostgreSQL.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=577058">[% terms.Bug %] 577058</a>)
+ </li>
+ <li>The "Field Values" administrative control panel was sometimes denying
+ admins the ability to delete field values when there was no reason
+ to deny the deletion.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=577054">[% terms.Bug %] 577054</a>)
+ </li>
+ <li>Eliminate the "uninitialized value" warnings that would happen when
+ editing a product's components.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=576911">[% terms.Bug %] 576911</a>)
+ </li>
+ <li>The updating of bugs_fulltext that happens during
+ <kbd>checksetup.pl</kbd> for upgrades to 3.6 should now be MUCH faster.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=577754">[% terms.Bug %] 577754</a>)
+ </li>
+ <li><kbd>email_in.pl</kbd> was not allowing the setting of time-tracking
+ fields via inbound emails.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=583622">[% terms.Bug %] 583622</a>)
+ </li>
+</ul>
+
+<h3>3.6.1</h3>
+
+<p>This release fixes two security issues. See the
+ <a href="http://www.bugzilla.org/security/3.2.6/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in
+ this release:</p>
+
+<ul>
+ <li>Using the "Change Columns" page would sometimes result in a
+ plain-text page instead of HTML.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=376044">[% terms.Bug %] 376044</a>)
+ </li>
+ <li>Extensions that have only templates and no code are now working.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=562551">[% terms.Bug %] 562551</a>)
+ </li>
+ <li><kbd>install-module.pl</kbd> has been fixed so that it installs
+ modules properly on both new and old versions of Perl.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=560318">[% terms.Bug %] 560318</a>
+ and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=560330">[% terms.Bug %] 560330</a>)
+ </li>
+ <li>It is now possible to upgrade from 3.4 to 3.6 when using Oracle.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=561379">[% terms.Bug %] 561379</a>)
+ </li>
+ <li>Editing a field value's name (using the Field Values admin control
+ panel) wasn't working if the value was set as the default for that
+ field.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=561296">[% terms.Bug %] 561296</a>)
+ </li>
+ <li>If you had the <kbd>noresolveonopenblockers</kbd> parameter set,
+ [%+ terms.bugs %] couldn't be edited at all if they were marked FIXED
+ and had any open blockers. (The parameter is only supposed to prevent
+ <em>changing</em> [% terms.bugs %] to FIXED, not modifying already-FIXED
+ [%+ terms.bugs %].)
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=565314">[% terms.Bug %] 565314</a>)
+ </li>
+ <li>Some minor issues with Perl 5.12 were fixed (mostly warnings that Perl
+ 5.12 was throwing). [% terms.Bugzilla %] now supports Perl 5.12.</li>
+</ul>
+
+<h2 id="v36_req">Minimum Requirements</h2>
+
+<p>Any requirements that are new since 3.4.5 will look like
+ <span class="req_new">this</span>.</p>
+
+<ul>
+ <li><a href="#v36_req_perl">Perl</a></li>
+ <li><a href="#v36_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v36_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v36_req_oracle">For Oracle Users</a></li>
+ <li><a href="#v36_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v36_req_optional_mod">Optional Perl Modules</a></li>
+</ul>
+
+<h3 id="v36_req_perl">Perl</h3>
+
+<p>Perl v5.8.1</p>
+
+<h3 id="v36_req_mysql">For MySQL Users</h3>
+
+ <ul>
+ <li>MySQL
+ v4.1.2
+ </li>
+ <li><strong>perl module:</strong>
+ DBD::mysql v4.00</li>
+ </ul>
+
+<h3 id="v36_req_pg">For PostgreSQL Users</h3>
+
+ <ul>
+ <li>PostgreSQL
+ v8.00.0000
+ </li>
+ <li><strong>perl module:</strong>
+ DBD::Pg v1.45</li>
+ </ul>
+<h3 id="v36_req_oracle">For Oracle Users</h3>
+
+ <ul>
+ <li>Oracle
+ v10.02.0
+ </li>
+ <li><strong>perl module:</strong>
+ DBD::Oracle v1.19</li>
+ </ul>
+
+<h3 id="v36_req_modules">Required Perl Modules</h3>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ </tr>
+ <tr>
+ <td >CGI</td>
+ <td >3.21
+ </td>
+ </tr>
+ <tr>
+ <td >Digest::SHA</td>
+ <td >
+ (Any)
+ </td>
+ </tr>
+ <tr>
+ <td >Date::Format</td>
+ <td >2.21
+ </td>
+ </tr>
+ <tr>
+ <td >DateTime</td>
+ <td >0.28
+ </td>
+ </tr>
+ <tr>
+ <td >DateTime::TimeZone</td>
+ <td >0.71
+ </td>
+ </tr>
+ <tr>
+ <td >DBI</td>
+ <td >1.41
+ </td>
+ </tr>
+ <tr>
+ <td >Template</td>
+ <td >2.22
+ </td>
+ </tr>
+ <tr>
+ <td >Email::Send</td>
+ <td >2.00
+ </td>
+ </tr>
+ <tr>
+ <td >Email::MIME</td>
+ <td >1.861
+ </td>
+ </tr>
+ <tr>
+ <td >Email::MIME::Encodings</td>
+ <td >1.313
+ </td>
+ </tr>
+ <tr>
+ <td >Email::MIME::Modifier</td>
+ <td >1.442
+ </td>
+ </tr>
+ <tr>
+ <td >URI</td>
+ <td >
+ (Any)
+ </td>
+ </tr>
+</table>
+
+<h3 id="v36_req_optional_mod">Optional Perl Modules</h3>
+
+<p>The following perl modules, if installed, enable various
+ features of [% terms.Bugzilla %]:</p>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ <th>Enables Feature</th>
+ </tr>
+ <tr>
+ <td >GD</td>
+ <td >1.20
+ </td>
+ <td>Graphical Reports, New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td >Chart::Lines</td>
+ <td class="req_new">2.1
+ </td>
+ <td>New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td >Template::Plugin::GD::Image</td>
+ <td >
+ (Any)
+ </td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td >GD::Text</td>
+ <td >
+ (Any)
+ </td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td >GD::Graph</td>
+ <td >
+ (Any)
+ </td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td >XML::Twig</td>
+ <td >
+ (Any)
+ </td>
+ <td>Move [% terms.Bugs %] Between Installations,
+ Automatic Update Notifications</td>
+ </tr>
+ <tr>
+ <td >MIME::Parser</td>
+ <td >5.406
+ </td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td >LWP::UserAgent</td>
+ <td >
+ (Any)
+ </td>
+ <td>Automatic Update Notifications</td>
+ </tr>
+ <tr>
+ <td >PatchReader</td>
+ <td >0.9.4
+ </td>
+ <td>Patch Viewer</td>
+ </tr>
+ <tr>
+ <td >Net::LDAP</td>
+ <td >
+ (Any)
+ </td>
+ <td>LDAP Authentication</td>
+ </tr>
+ <tr>
+ <td >Authen::SASL</td>
+ <td >
+ (Any)
+ </td>
+ <td>SMTP Authentication</td>
+ </tr>
+ <tr>
+ <td >Authen::Radius</td>
+ <td >
+ (Any)
+ </td>
+ <td>RADIUS Authentication</td>
+ </tr>
+ <tr>
+ <td >SOAP::Lite</td>
+ <td >0.710.06
+ </td>
+ <td>XML-RPC Interface</td>
+ </tr>
+ <tr>
+ <td class="req_new">JSON::RPC</td>
+ <td class="req_new">
+ (Any)
+ </td>
+ <td>JSON-RPC Interface</td>
+ </tr>
+ <tr>
+ <td class="req_new">Test::Taint</td>
+ <td class="req_new">
+ (Any)
+ </td>
+ <td>JSON-RPC Interface, XML-RPC Interface</td>
+ </tr>
+ <tr>
+ <td >HTML::Parser</td>
+ <td >3.40
+ </td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td >HTML::Scrubber</td>
+ <td >
+ (Any)
+ </td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td >Email::MIME::Attachment::Stripper</td>
+ <td >
+ (Any)
+ </td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td >Email::Reply</td>
+ <td >
+ (Any)
+ </td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td >TheSchwartz</td>
+ <td >
+ (Any)
+ </td>
+ <td>Mail Queueing</td>
+ </tr>
+ <tr>
+ <td >Daemon::Generic</td>
+ <td >
+ (Any)
+ </td>
+ <td>Mail Queueing</td>
+ </tr>
+ <tr>
+ <td >mod_perl2</td>
+ <td >1.999022
+ </td>
+ <td>mod_perl</td>
+ </tr>
+</table>
+
+<h2 id="v36_feat">New Features and Improvements</h2>
+
+<ul>
+ <li><a href="#v36_feat_usability">General Usability Improvements</a></li>
+ <li><a href="#v36_feat_extensions">New Extensions System</a></li>
+ <li><a href="#v36_feat_qs">Improved Quicksearch</a></li>
+ <li><a href="#v36_feat_browse">Simple "Browse" Interface</a></li>
+ <li><a href="#v36_feat_suexec">SUExec Support</a></li>
+ <li><a href="#v36_feat_mpwindows">Experimental mod_perl Support on Windows</a></li>
+ <li><a href="#v36_email_attachments">Send Attachments by Email</a></li>
+ <li><a href="#v36_feat_jsonrpc">JSON-RPC Interface</a></li>
+ <li><a href="#v36_feat_migrate">Migration From Other [% terms.Bug %]-Trackers</a></li>
+ <li><a href="#v36_feat_other">Other Enhancements and Changes</a></li>
+</ul>
+
+<h3 id="v36_feat_usability">General Usability Improvements</h3>
+
+<p>A <a href="https://wiki.mozilla.org/Bugzilla:CMU_HCI_Research_2008">scientific
+ usability study</a> was done on [% terms.Bugzilla %] by researchers
+ from Carnegie-Mellon University. As a result of this study,
+ <a href="https://bugzilla.mozilla.org/showdependencytree.cgi?id=490786&hide_resolved=0">several
+ usability issues</a> were prioritized to be fixed, based on specific data
+ from the study.</p>
+
+<p>As a result, you will see many small improvements in [% terms.Bugzilla %]'s
+ usability, such as using Javascript to validate certain forms before
+ they are submitted, standardizing the words that we use in the user interface,
+ being clearer about what [% terms.Bugzilla %] needs from the user,
+ and other changes, all of which are also listed individually in this New
+ Features section.</p>
+
+<p>Work continues on improving usability for the next release of
+ [%+ terms.Bugzilla %], but the results of the research have already
+ had an impact on this 3.6 release.</p>
+
+<h3 id="v36_feat_extensions">New Extensions System</h3>
+
+<p>[% terms.Bugzilla %] has a brand-new Extensions system. The system is
+ consistent, fast, and
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Extension.html">fully
+ documented</a>. It makes it possible to easily extend [% terms.Bugzilla %]'s
+ code and user interface to add new features or change existing features.
+ There's even
+ <a href="[% docs_urlbase FILTER html %]api/extensions/create.html">a
+ script</a> that will create the basic layout of an extension for you, to
+ help you get started. For more information about the new system, see the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Extension.html">Extensions
+ documentation</a>.</p>
+
+<p>If you had written any extensions using [% terms.Bugzilla %]'s previous
+ extensions system, there is
+ <a href="[% docs_urlbase FILTER html %]api/contrib/extension-convert.html">a
+ script to help convert old extensions into the new format</a>.</p>
+
+<h3 id="v36_feat_qs">Improved Quicksearch</h3>
+
+<p>The "quicksearch" box that appears on the front page of
+ [%+ terms.Bugzilla %] and in the header/footer of every page
+ is now simplified and made more powerful. There is a
+ <kbd>[?]</kbd> link next to the box that will take you to
+ the simplified <a href="page.cgi?id=quicksearch.html">Quicksearch Help</a>,
+ which describes every single feature of the system in a simple layout,
+ including new features such as the ability to use partial field names
+ when searching.</p>
+
+<p>Quicksearch should also be much faster than it was before, particularly
+ on large installations.</p>
+
+<p>Note that in order to implement the new quicksearch, certain old
+ and rarely-used features had to be removed:
+
+<ul>
+ <li><b>+</b> as a prefix to mean "search additional resolutions", and
+ <b>+</b> as a prefix to mean "search just the summary". You can
+ instead use <kbd>summary:</kbd> to explicitly search summaries.</li>
+ <li>Searching the Severity field if you type something that matches
+ the first few characters of a severity. You can explicitly search
+ the Severity field if you want to find [% terms.bugs %] by severity.</li>
+ <li>Searching the Priority field if you typed something that exactly
+ matched the name of a priority. You can explicitly search the
+ Priority field if you want to find [% terms.bugs %] by priority.</li>
+ <li>Searching the Platform and OS fields if you typed in one of a
+ certain hard-coded list of strings (like "pc", "windows", etc.).
+ You can explicitly search these fields, instead, if you want to
+ find [% terms.bugs %] with a specific Platform or OS set.</li>
+</ul>
+
+<h3 id="v36_feat_browse">Simple "Browse" Interface</h3>
+
+<p>There is now a "Browse" link in the header of each [% terms.Bugzilla %]
+ page that presents a very basic interface that allows users to simply
+ browse through all open [% terms.bugs %] in particular components.</p>
+
+<h3 id="v36_feat_suexec">SUExec Support</h3>
+
+<p>[% terms.Bugzilla %] can now be run in Apache's "SUExec" mode,
+ which is what control panel software like cPanel and Plesk use
+ (so [% terms.Bugzilla %] should now be much easier to install
+ on shared hosting). SUExec support shows up as an option
+ in the <kbd>localconfig</kbd> file during installation.</p>
+
+<h3 id="v36_feat_mpwindows">Experimental mod_perl Support on Windows</h3>
+
+<p>There is now experimental support for running [% terms.Bugzilla %]
+ under mod_perl on Windows, for a significant performance enhancement
+ (in exchange for using more memory).</p>
+
+<h3 id="v36_email_attachments">Send Attachments by Email</h3>
+
+<p>The <a href="[% docs_urlbase FILTER html %]api/email_in.html">email_in</a>
+ script now supports attaching multiple attachments to [% terms.abug %]
+ by email, both when filing and when updating [% terms.abug %].</p>
+
+<h3 id="v36_feat_jsonrpc">JSON-RPC Interface</h3>
+
+<p>[% terms.Bugzilla %] now has support for the
+ <a href="http://json-rpc.org/">JSON-RPC</a> WebServices protocol via
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Server/JSONRPC.html">jsonrpc.cgi</a>.
+ The JSON-RPC interface is experimental in this release--if you want any
+ fundamental changes in how it works,
+ <a href="http://www.bugzilla.org/developers/reporting_bugs.html">let us
+ know</a>, for the next release of [% terms.Bugzilla %].</p>
+
+<h3 id="v36_feat_migrate">Migration From Other [% terms.Bug %]-Trackers</h3>
+
+<p>[% terms.Bugzilla %] 3.6 comes with a new script,
+ <a href="[% docs_urlbase FILTER html %]api/migrate.html">migrate.pl</a>,
+ which allows migration from other [% terms.bug %]-tracking systems.
+ Among the various features of the migration system are:</p>
+
+<ul>
+ <li>It is non-destructive--you can migrate into an existing
+ [%+ terms.Bugzilla %] installation without destroying any data
+ in the installation.</li>
+ <li>It has a "dry-run" mode so you can test your migration
+ before actually running it.</li>
+ <li>It is relatively easy to write new migrators for new systems,
+ if you know Perl. The basic migration framework does most of the work
+ for you, you just have to provide it with the data from your
+ [%+ terms.bug %]-tracker. See the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Migrate.html">Bugzilla::Migrate</a>
+ documentation and see our current migrator,
+ <kbd>Bugzilla/Migrate/GNATS.pm</kbd> for information on how to make your
+ own migrator.</li>
+</ul>
+
+<p>The first migrator that has been implemented is for the GNATS
+ [%+ terms.bug %]-tracking system. We'd love to see migrators for
+ other systems! If you want to contribute a new migrator, see our
+ <a href="http://wiki.mozilla.org/Bugzilla:Developers">development
+ process</a> for details on how to get code into [% terms.Bugzilla %].</p>
+
+<p>Thanks to <a href="http://lambdares.com/">Lambda Research</a> for
+ funding the initial development of this feature.</p>
+
+<h3 id="v36_feat_other">Other Enhancements and Changes</h3>
+
+<h4>Enhancements for Users</h4>
+
+<ul>
+ <li><b>[% terms.Bug %] Filing:</b> When filing [% terms.abug %],
+ [%+ terms.Bugzilla %] now visually indicates which fields are
+ mandatory.</li>
+ <li><b>[% terms.Bug %] Filing:</b> "Bookmarkable templates" now
+ support the "alias" and "estimated hours" fields.</li>
+
+ <li><b>[% terms.Bug %] Editing:</b> In previous versions of
+ [%+ terms.Bugzilla %], if you added a private comment to [% terms.abug %],
+ then <em>none</em> of the changes that you made at that time were
+ sent to users who couldn't see the private comment. Now, for users
+ who can't see private comments, public changes are sent, but the private
+ comment is excluded from their email notification.</li>
+ <li><b>[% terms.Bug %] Editing:</b> The controls for groups now
+ appear to the right of the attachment and time-tracking tables,
+ when editing [% terms.abug %].</li>
+ <li><b>[% terms.Bug %] Editing:</b> The "Collapse All Comments"
+ and "Expand All Comments" links now appear to the right of the
+ comment list instead of above it.</li>
+ <li><b>[% terms.Bug %] Editing:</b> The See Also field now supports
+ URLs for Google Code Issues and the Debian B[% %]ug-Tracking System.</li>
+ <li><b>[% terms.Bug %] Editing:</b> There have been significant performance
+ improvements in <kbd>show_bug.cgi</kbd> (the script that displays the
+ [% terms.bug %]-editing form), particularly for [% terms.bugs %] that
+ have lots of comments or attachments.</li>
+
+ <li><b>Attachments:</b> The "Details" page of an attachment
+ now displays itself as uneditable if you can't edit the fields
+ there.</li>
+ <li><b>Attachments:</b> We now make sure that there is
+ a Description specified for an attachment, using JavaScript, before
+ the form is submitted.</li>
+ <li><b>Attachments:</b> There is now a link back to the [% terms.bug %]
+ at the bottom of the "Details" page for an attachment.</li>
+ <li><b>Attachments:</b> When you click on an "attachment 12345" link
+ in a comment, if the attachment is a patch, you will now see the
+ formatted "Diff" view instead of the raw patch.</li>
+ <li><b>Attachments</b>: For text attachments, we now let the browser
+ auto-detect the character encoding, instead of forcing the browser to
+ always assume the attachment is in UTF-8.</li>
+
+ <li><b>Search:</b> You can now display [% terms.bug %] flags as a column
+ in search results.</li>
+ <li><b>Search:</b> When viewing search results, you can see which columns are
+ being sorted on, and which direction the sort is on, as indicated
+ by arrows next to the column headers.</li>
+ <li><b>Search:</b> You can now search the Deadline field using relative
+ dates (like "1d", "2w", etc.).</li>
+ <li><b>Search:</b> The iCalendar format of search results now includes
+ a PRIORITY field.</li>
+ <li><b>Search:</b> It is no longer an error to enter an invalid search
+ order in a search URL--[% terms.Bugzilla %] will simply warn you that
+ some of your order options are invalid.</li>
+ <li><b>Search:</b> When there are no search results, some helpful
+ links are displayed, offering actions you might want to take.</li>
+ <li><b>Search:</b> For those who like to make their own
+ <kbd>buglist.cgi</kbd> URLs (and for people working on customizations),
+ <kbd>buglist.cgi</kbd> now accepts nearly every valid field in
+ [%+ terms.Bugzilla %] as a direct URL parameter, like
+ <kbd>&field=value</kbd>.</li>
+
+ <li><b>Requests:</b> When viewing the "My Requests" page, you can now
+ see the lists as a normal search result by clicking a link at the
+ bottom of each table.</li>
+ <li><b>Requests:</b> When viewing the "My Requests" page, if you are
+ using Classifications, the Product drop-down will be grouped by
+ Classification.</li>
+
+ <li><b>Inbound Email:</b> When filing [% terms.abug %] by email, if the
+ product that you are filing the [% terms.bug %] into has some groups
+ set as Default for you, the [% terms.bug %] will now be placed into those
+ groups automatically.</li>
+ <li><b>Inbound Email:</b> The field names that can be used when creating
+ [%+ terms.bugs %] by email now exactly matches the set of valid parameters
+ to the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#create">B[% %]ug.create
+ WebService function</a>. You can still use most of the old field names
+ that 3.4 and earlier used for inbound emails, though, for
+ backwards-compatibility.</li>
+
+ <li>If there are multiple languages available for your
+ [%+ terms.Bugzilla %], you can now select what language you want
+ [%+ terms.Bugzilla %] displayed in using links at the top of every
+ page.</li>
+ <li>When creating a new account, you will be automatically logged in
+ after setting your password.</li>
+ <li>There is no longer a maximum password length for accounts.</li>
+ <li>In the Dusk skin, it's now easier to see links.</li>
+ <li>In the Whining system, you can now choose to receive emails even
+ if there are no [% terms.bugs %] that match your searches.</li>
+ <li>The arrows in dependency graphs now point the other way, so that
+ [%+ terms.bugs %] point at their dependencies.</li>
+
+ <li><b>New Charts:</b> You can now convert an existing Saved Search
+ into a data series for New Charts.</li>
+ <li><b>New Charts:</b> There is now an interface that allows you to
+ delete data series.</li>
+ <li><b>New Charts:</b> When deleting a product, you now have the option
+ to delete the data series that are associated with that product.</li>
+</ul>
+
+<h4>Enhancements for Administrators and Developers</h4>
+
+<ul>
+ <li>Depending on how your workflow is set up, it is now possible to
+ have both UNCONFIRMED and REOPENED show up as status choices for
+ a closed [% terms.bug %]. If you only want one or the other to
+ show up, you should edit your status workflow appropriately
+ (possibly by removing or disabling the REOPENED status).</li>
+ <li>You can now "disable" field values so that they don't show
+ up as choices on [% terms.abug %] unless they are already set as
+ the value for that [% terms.bug %]. This doesn't work for the
+ per-product field values (component, target_milestone, and version)
+ yet, though.</li>
+ <li>Users are now locked out of their accounts for 30 minutes after
+ trying five bad passwords in a row during login. Every time a
+ user is locked out like this, the user in the "maintainer" parameter
+ will get an email.</li>
+ <li>The minimum length allowed for a password is now 6 characters.</li>
+ <li>The <kbd>UNCONFIRMED</kbd> status being enabled in a product
+ is now unrelated to the voting parameters. Instead, there is a checkbox
+ to enable the <kbd>UNCONFIRMED</kbd> status in a product.</li>
+ <li>Information about duplicates is now stored in the database instead
+ of being stored in the <kbd>data/</kbd> directory. On large installations
+ this could save several hundred megabytes of disk space.</li>
+
+ <li><b>Installation:</b> When installing [% terms.Bugzilla %], the
+ "maintainer" parameter will be automatically set to the administrator
+ that was created by <kbd>checksetup.pl</kbd>.</li>
+ <li><b>Installation:</b> <kbd>checksetup.pl</kbd> now prints out
+ certain errors in a special color so that you know that something
+ needs to be done.</li>
+ <li><b>Installation:</b> <kbd>checksetup.pl</kbd> is now <em>much</em>
+ faster at upgrading installations, particularly older installations.
+ Also, it's been made faster to run for the case where it's not
+ doing an upgrade.</li>
+ <li><b>Installation:</b> If you install [% terms.Bugzilla %] using the
+ tarball, the <kbd>CGI.pm</kbd> module from CPAN is now included in
+ the <kbd>lib/</kbd> dir. If you would rather use the CGI.pm from your
+ global Perl installation, you can delete <kbd>CGI.pm</kbd> and the
+ <kbd>CGI</kbd> directory from the <kbd>lib/</kbd> directory.</li>
+
+ <li>When editing a group, you can now specify that members of a group
+ are allowed to grant others membership in that group itself.</li>
+ <li>The ability to compress BMP attachments to PNGs is now an Extension.
+ To enable the feature, remove the file
+ <kbd>extensions/BmpConvert/disabled</kbd> and then run <kbd>checksetup.pl</kbd>.</li>
+ <li>The default list of values for the Priority field are now clear English
+ words instead of P1, P2, etc.</li>
+ <li>There is now a system in place so that all field values can be
+ localized. See the <kbd>value_descs</kbd> variable in
+ <kbd>template/en/default/global/field-descs.none.tmpl</kbd>.</li>
+ <li><kbd>config.cgi</kbd> now returns an ETag header and understands
+ the If-None-Match header in HTTP requests.</li>
+ <li>The XML format of <kbd>show_bug.cgi</kbd> now returns more information:
+ the numeric id of each comment, whether an attachment is a URL,
+ the modification time of an attachment, the numeric id of a flag,
+ and the numeric id of a flag's type.</li>
+
+ <li><b>Parameters:</b> Parameters that aren't actually required are no longer
+ in the "Required" section of the Parameters page. Instead, some are in the
+ new "General" section, and some are in the new "Advanced" section.</li>
+ <li><b>Parameters:</b> The old <kbd>ssl</kbd> parameter has been
+ changed to <kbd>ssl_redirect</kbd>, and can only be turned "on" or "off".
+ If "on", then all users will be forcibly redirected to SSL whenever
+ they access [% terms.Bugzilla %]. When the parameter is off,
+ no SSL-related redirects will occur (even if the user directly
+ accesses [% terms.Bugzilla %] via SSL, they will <em>not</em> be
+ redirected to a non-SSL page).</li>
+ <li><b>Parameters:</b> In the Advanced parameters, there is a new parameter,
+ <kbd>inbound_proxies</kbd>. If your [% terms.Bugzilla %] is behind a
+ proxy, you should set this parameter to the IP address of that proxy.
+ Then, [% terms.Bugzilla %] will "believe" any "X-Forwarded-For"
+ header sent from that proxy, and correctly use the X-Forwarded-For
+ as the end user's IP, instead of believing that all traffic is coming
+ from the proxy.</li>
+
+ <li><b>Removed Parameter:</b> The <kbd>loginnetmask</kbd> parameter has
+ been removed. Since [% terms.Bugzilla %] sends secure cookies, it's no
+ longer necessary to always restrict logins to a specific IP or block
+ of addresses.</li>
+ <li><b>Removed Parameter:</b> The <kbd>quicksearch_comment_cutoff</kbd>
+ parameter is gone. Quicksearch now always searches comments; however, it
+ uses a much faster algorithm to do it.</li>
+ <li><b>Removed Parameter:</b> The <kbd>usermatchmode</kbd> parameter has
+ been removed. User-matching is now <em>always</em> done.</li>
+ <li><b>Removed Parameter:</b> The <kbd>useentrygroupdefault</kbd> parameter
+ has been removed. [% terms.Bugzilla %] now always behaves as though
+ that parameter were off.</li>
+ <li>The <kbd>t/001compile.t</kbd> test should now always pass, no matter
+ what configuration of optional modules you do or don't have installed.</li>
+ <li>New script: <kbd>contrib/console.pl</kbd>, which allows you to have
+ a "command line" into [% terms.Bugzilla %] by inputting Perl code
+ or using a few custom commands.</li>
+</ul>
+
+<h4>WebService Changes</h4>
+
+<ul>
+ <li>The WebService now returns all dates and times in the UTC timezone.
+ <kbd>B[% %]ugzilla.time</kbd> now acts as though the [% terms.Bugzilla %]
+ server were in the UTC timezone, always. If you want to write clients
+ that are compatible across all [% terms.Bugzilla %] versions,
+ check the timezone from <kbd>B[% %]ugzilla.timezone</kbd> or
+ <kbd>B[% %]ugzilla.time</kbd>, and always input times in that timezone
+ and expect times to be returned in that format.</li>
+ <li>You can now log in by passing <kbd>Bugzilla_login</kbd> and
+ <kbd>Bugzilla_password</kbd> as arguments to any WebService function.
+ See the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService.html#LOGGING_IN">Bugzilla::WebService</a>
+ documentation for details.</li>
+ <li>New Method:
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#attachments">B[% %]ug.attachments</a>
+ which allows getting information about attachments.</li>
+ <li>New Method:
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#fields">B[% %]ug.fields</a>,
+ which gets information about all the fields that [% terms.abug %] can have
+ in [% terms.Bugzilla %], include custom fields and legal values for
+ all fields. The <kbd>B[% %]ug.legal_values</kbd> method is now deprecated.</li>
+ <li>In the <kbd>B[% %]ug.add_comment</kbd> method, the "private" parameter
+ has been renamed to "is_private" (for consistency with other methods).
+ You can still use "private", though, for backwards-compatibility.</li>
+ <li>The WebService now has Perl's "taint mode" turned on. This means that
+ it validates all data passed in before sending it to the database.
+ Also, all parameter names are validated, and if you pass in a parameter
+ whose name contains anything other than letters, numbers, or underscores,
+ that parameter will be ignored. Mostly this just affects
+ customizers--[% terms.Bugzilla %]'s WebService is not functionally
+ affected by these changes.</li>
+ <li>In previous versions of [% terms.Bugzilla %], error messages were
+ sent word-wrapped to the client, from the WebService. Error messages
+ are now sent as one unbroken line.</li>
+</ul>
+
+<h2 id="v36_issues">Outstanding Issues</h2>
+
+<ul>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=423439">
+ [%- terms.Bug %] 423439</a>: Tabs in comments will be converted
+ to four spaces, due to a b<!-- -->ug in Perl as of Perl 5.8.8.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69621">
+ [%- terms.Bug %] 69621</a>: If you rename or remove a keyword that is
+ in use on [% terms.bugs %], you will need to rebuild the "keyword cache"
+ by running <a href="sanitycheck.cgi">sanitycheck.cgi</a> and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may
+ not show up properly in search results.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
+ [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
+ the same time, there is no "mid-air collision" protection.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
+ [%- terms.Bug %] 276230</a>: The support for restricting access to
+ particular Categories of New Charts is not complete. You should treat
+ the 'chartgroup' Param as the only access mechanism available.<br>
+ However, charts migrated from Old Charts will be restricted to
+ the groups that are marked MANDATORY for the corresponding Product.
+ There is currently no way to change this restriction, and the
+ groupings will not be updated if the group configuration
+ for the Product changes.</li>
+</ul>
+
+<h2 id="v36_upgrading">Notes On Upgrading From a Previous Version</h2>
+
+<p>When upgrading to 3.6, <kbd>checksetup.pl</kbd> will create foreign keys
+ for many columns in the database. Before doing this, it will check the
+ database for consistency. If there are an unresolvable consistency
+ problems, it will tell you what table and column in the database contain
+ the bad values, and which values are bad. If you don't know what else to do,
+ you can always delete the database records which contain the bad values by
+ logging in to your database and running the following command:</p>
+
+<p><code>DELETE FROM <var>table</var> WHERE <var>column</var> IN
+ (<var>1, 2, 3, 4</var>)</code></p>
+
+<p>Just replace "table" and "column" with the name of the table
+ and column that <kbd>checksetup.pl</kbd> mentions, and "1, 2, 3, 4"
+ with the invalid values that <kbd>checksetup.pl</kbd> prints out.</p>
+
+<p>Remember that you should always back up your database before doing
+ an upgrade.</p>
+
+<h2 id="v36_code_changes">Code Changes Which May Affect Customizations</h2>
+
+<ul>
+ <li>There is no longer a SendBugMail method in the templates, and bugmail
+ is no longer sent by processing a template. Instead, it is sent
+ by using <kbd>Bugzilla::BugMail::Send</kbd>.</li>
+ <li>Comments are now represented as a
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Comment.html">Bugzilla::Comment</a>
+ object instead of just being hashes.</li>
+ <li>In previous versions of [% terms.Bugzilla %], the template for displaying
+ [%+ terms.abug %] required a lot of extra variables that are now global
+ template variables instead.</li>
+ <li>You can now check if optional modules are installed by using
+ <kbd>Bugzilla->feature</kbd> in Perl code or
+ <kbd>feature_enabled</kbd> in template code.</li>
+ <li>All of the various template header information required to display
+ the [% terms.bug %] form is now in one template,
+ <kbd>template/en/default/bug/show-header.html.tmpl</kbd>.</li>
+ <li>You should now use <kbd>display_value</kbd> instead of
+ <kbd>get_status</kbd> or <kbd>get_resolution</kbd> in templates.
+ <kbd>display_value</kbd> should be used anywhere that a
+ <select>-type field has its values displayed.</li>
+</ul>
+
+
+<h1 id="v36_previous">[% terms.Bugzilla %] 3.4 Release Notes</h1>
+
+<ul class="bz_toc">
+ <li><a href="#v34_introduction">Introduction</a></li>
+ <li><a href="#v34_point">Updates in this 3.4.x Release</a></li>
+ <li><a href="#v34_req">Minimum Requirements</a></li>
+ <li><a href="#v34_feat">New Features and Improvements</a></li>
+ <li><a href="#v34_issues">Outstanding Issues</a></li>
+ <li><a href="#v34_upgrading">Notes On Upgrading From a Previous Version</a></li>
+ <li><a href="#v34_code_changes">Code Changes Which May Affect
+ Customizations</a></li>
+ <li><a href="#v34_previous">Release Notes for Previous Versions</a></li>
+</ul>
+
+<h2 id="v34_introduction">Introduction</h2>
+
+<p>This is [% terms.Bugzilla %] 3.4! [% terms.Bugzilla %] 3.4 brings a lot
+ of great enhancements for [% terms.Bugzilla %] over previous versions,
+ with various improvements to the user interface, lots of interesting new
+ features, and many long-standing requests finally being addressed.</p>
+
+<p>If you're upgrading, make sure to read <a href="#v34_upgrading">Notes
+ On Upgrading From a Previous Version</a>. If you are upgrading from a release
+ before 3.2, make sure to read the release notes for all the
+ <a href="#v34_previous">previous versions</a> in between your version
+ and this one, <strong>particularly the Upgrading section of each
+ version's release notes</strong>.</p>
+
+<p>We would like to thank <a href="http://www.canonical.com/">Canonical
+ Ltd.</a> for funding development of one new feature, and NASA for funding
+ development of several new features through the
+ <a href="http://www.sjsufoundation.org/">San Jose State University
+ Foundation</a>.</p>
+
+<h2 id="v34_point">Updates In This 3.4.x Release</h2>
+
+<h3>3.4.6</h3>
+
+<ul>
+ <li>When doing a search that involves "not equals" or "does not contain the
+ string" or similar "negative" search types, the search description that
+ appears at the top of the resulting [% terms.bug %] list will indicate
+ that the search was of that type.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=474738">[% terms.Bug %] 474738</a>)
+ </li>
+ <li>In Internet Explorer, users couldn't easily mark a RESOLVED DUPLICATE
+ [%+ terms.bug %] as REOPENED, due to a JavaScript error.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=546719">[% terms.Bug %] 546719</a>)
+ </li>
+ <li>If you use a "bookmarkable template" to pre-fill forms on
+ the [% terms.bug %]-filing page, and you have custom fields
+ that are only supposed to appear (or only supposed to have certain
+ values) based on the values of other fields, those custom fields will
+ now work properly.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=538211">[% terms.Bug %] 538211</a>)
+ </li>
+ <li>If you have a custom field that's only supposed to appear when
+ a [% terms.bug %]'s resolution is FIXED, it will now behave properly
+ on the [% terms.bug %]-editing form when a user sets the [% terms.bug %]'s
+ status to RESOLVED.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=520993">[% terms.Bug %] 520993</a>)
+ </li>
+ <li>If you are logged-out and using <kbd>request.cgi</kbd>, the Requester
+ and Requestee fields no longer respect the <kbd>usermatching</kbd>
+ parameter--they always require full usernames.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=533018">[% terms.Bug %] 533018</a>)
+ </li>
+ <li>If you tried to do a search with too many terms (resulting in a URL
+ that was longer than about 7000 characters), Apache would return a
+ 500 error instead of your search results.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=513989">[% terms.Bug %] 513989</a>)
+ </li>
+ <li>[% terms.Bugzilla %] would sometimes lose fields from your sort order
+ when you added new fields to your sort order.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=470214">[% terms.Bug %] 470214</a>)
+ </li>
+ <li>The Atom format of search results would sometimes be missing the
+ Reporter or Assignee field for some [% terms.bugs %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=537834">[% terms.Bug %] 537834</a>)
+ </li>
+</ul>
+
+<h3>3.4.5</h3>
+
+<p>This release contains fixes for multiple security issues. See the
+ <a href="http://www.bugzilla.org/security/3.0.10/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in
+ this release:</p>
+
+<ul>
+ <li>Whining was failing if jobqueue.pl was enabled.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=530270">[% terms.Bug %] 530270</a>)
+ </li>
+ <li>The Assignee field was empty in Whine mails.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=511216">[% terms.Bug %] 511216</a>)
+ </li>
+ <li>Administrators can now successfully create user accounts using
+ editusers.cgi when using the "Env" authentication method.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=483987">[% terms.Bug %] 483987</a>)
+ </li>
+ <li>[% terms.Bug %]mail now uses the timezone of the recipient of the email,
+ when displaying the time a comment was made, instead of the timezone of the
+ person who made the change.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=534587">[% terms.Bug %] 534587</a>)
+ </li>
+ <li>"[% terms.bug %] 1234" in comments sometimes would not become a link if
+ word-wrapping happened between "[% terms.bug %]" and the number.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=514703">[% terms.Bug %] 514703</a>)
+ </li>
+ <li>Running <kbd>checksetup.pl</kbd> on Windows will no longer pop up an error box
+ about OCI.dll.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=480968">[% terms.Bug %] 480968</a>)
+ </li>
+</ul>
+
+<h3>3.4.4</h3>
+
+<p>This release contains a fix for a security issue. See the
+ <a href="http://www.bugzilla.org/security/3.4.3/">Security Advisory</a>
+ for details.</p>
+
+<p>Additionally, this release fixes a few minor [% terms.bugs %].</p>
+
+<h3>3.4.3</h3>
+
+<ul>
+ <li>[% terms.Bugzilla %] installations running under mod_perl were leaking
+ about 512K of RAM per page load.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=517793">[% terms.Bug %] 517793</a>)
+ </li>
+ <li>Attachments with Unicode characters in their names were being downloaded
+ with mangled names.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=328628">[% terms.Bug %] 328628</a>)
+ </li>
+ <li>Creating custom fields with Unicode in their database column name
+ is now no longer allowed, as it would break [% terms.Bugzilla %]. If you
+ created such a custom field, you should delete it by first marking it
+ obsolete and then clicking "Delete" in the custom field list, using
+ <a href="editfields.cgi">editfields.cgi</a>.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=525025">[% terms.Bug %] 525025</a>)
+ </li>
+ <li>Clicking "submit only my comment" on the "mid-air collisions" page
+ was leading to a "Suspicious Action" warning.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=514378">[% terms.Bug %] 514378</a>)
+ </li>
+ <li>The XML format of [% terms.abug %] accidentally contained the
+ word-wrapped content of comments instead of the unwrapped content.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=509152">[% terms.Bug %] 509152</a>)
+ </li>
+ <li>You can now do <kbd>./install-module.pl --shell</kbd> to get a CPAN
+ shell using the configuration of
+ <a href="[% docs_urlbase FILTER html %]api/install-module.html">install-module.pl</a>,
+ which allows you to do more advanced Perl module installation tasks.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=445875">[% terms.Bug %] 445875</a>)
+ </li>
+</ul>
+
+<h3>3.4.2</h3>
+
+<p>This release contains fixes for multiple security issues, one of which
+ is highly critical. See the
+ <a href="http://www.bugzilla.org/security/3.0.8/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in
+ this release:</p>
+
+<ul>
+ <li>Upgrades from older releases were sometimes failing during UTF-8
+ conversion with a foreign key error.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=508181">[% terms.Bug %] 508181</a>)
+ </li>
+ <li>Sorting [% terms.bug %] lists on certain fields would result in an error.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=510944">[% terms.Bug %] 510944</a>)
+ </li>
+ <li>[% terms.Bug %] update emails had two or three blank lines at the top
+ and between the various sections of the email. There is now only one
+ blank line in each of those places, making these emails more compact.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=73330">[% terms.Bug %] 73330</a>)
+ </li>
+ <li>[% terms.Bug %] email notifications for new [% terms.bugs %] incorrectly
+ had a line saying that the description was "Comment 0".
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=510798">[% terms.Bug %] 510798</a>)
+ </li>
+ <li>Running <kbd>./collectstats.pl --regenerate</kbd> is now much faster,
+ on the order of 20x or 100x faster.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=286625">[% terms.Bug %] 286625</a>)
+ </li>
+ <li>For users of RHEL, CentOS, Fedora, etc. jobqueue.pl can now automatically
+ be installed as a daemon by running <kbd>./jobqueue.pl install</kbd>
+ as root.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=475403">[% terms.Bug %] 475403</a>)
+ </li>
+ <li>XML-RPC interface responses had an incorrect Content-Length header
+ and would sometimes be truncated, if they contained certain UTF-8
+ characters.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=486306">[% terms.Bug %] 486306</a>)
+ </li>
+ <li>Users who didn't have access to the time-tracking fields would get an
+ empty [% terms.bug %] update email when the time-tracking fields were
+ changed.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=509035">[% terms.Bug %] 509035</a>)
+ </li>
+ <li>In the New Charts, non-public series now no longer show up as selectable
+ if you cannot access them.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=389396">[% terms.Bug %] 389396</a>)
+ </li>
+</ul>
+
+<h3>3.4.1</h3>
+
+<p>This release contains an important security fix. See the
+ <a href="http://www.bugzilla.org/security/3.4/">Security Advisory</a>
+ for details.</p>
+
+<h2 id="v34_req">Minimum Requirements</h2>
+
+<p>Any requirements that are new since 3.2.3 will look like
+ <span class="req_new">this</span>.</p>
+
+<ul>
+ <li><a href="#v34_req_perl">Perl</a></li>
+ <li><a href="#v34_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v34_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v34_req_oracle">For Oracle Users</a></li>
+ <li><a href="#v34_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v34_req_optional_mod">Optional Perl Modules</a></li>
+</ul>
+
+<h3 id="v34_req_perl">Perl</h3>
+
+<p>Perl v5.8.1</p>
+
+<h3 id="v34_req_mysql">For MySQL Users</h3>
+
+<ul>
+ <li>MySQL v4.1.2</li>
+ <li><strong>perl module:</strong> DBD::mysql v4.00</li>
+</ul>
+
+<h3 id="v34_req_pg">For PostgreSQL Users</h3>
+
+<ul>
+ <li>PostgreSQL v8.00.0000</li>
+ <li><strong>perl module:</strong> DBD::Pg v1.45</li>
+</ul>
+
+<h3 id="v34_req_oracle">For Oracle Users</h3>
+
+<ul>
+ <li>Oracle v10.02.0</li>
+ <li><strong>perl module:</strong> DBD::Oracle v1.19</li>
+</ul>
+
+<h3 id="v34_req_modules">Required Perl Modules</h3>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ </tr>
+ <tr>
+ <td>CGI</td>
+ <td>3.21</td>
+ </tr>
+ <tr>
+ <td class="req_new">Digest::SHA</td>
+ <td class="req_new"> (Any)</td>
+ </tr>
+ <tr>
+ <td>Date::Format</td>
+ <td>2.21</td>
+ </tr>
+ <tr>
+ <td class="req_new">DateTime</td>
+ <td class="req_new">0.28</td>
+ </tr>
+ <tr>
+ <td class="req_new">DateTime::TimeZone</td>
+ <td class="req_new">0.71</td>
+ </tr>
+ <tr>
+ <td>DBI</td>
+ <td>1.41</td>
+ </tr>
+ <tr>
+ <td>Template</td>
+ <td class="req_new">2.22</td>
+ </tr>
+ <tr>
+ <td>Email::Send</td>
+ <td>2.00</td>
+ </tr>
+ <tr>
+ <td>Email::MIME</td>
+ <td>1.861</td>
+ </tr>
+ <tr>
+ <td>Email::MIME::Encodings</td>
+ <td>1.313</td>
+ </tr>
+ <tr>
+ <td>Email::MIME::Modifier</td>
+ <td>1.442</td>
+ </tr>
+ <tr>
+ <td class="req_new">URI</td>
+ <td class="req_new">(Any)</td>
+ </tr>
+</table>
+
+<h3 id="v34_req_optional_mod">Optional Perl Modules</h3>
+
+<p>The following perl modules, if installed, enable various
+ features of [% terms.Bugzilla %]:</p>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th>
+ <th>Version</th>
+ <th>Enables Feature</th>
+ </tr>
+ <tr>
+ <td>LWP::UserAgent</td>
+ <td>(Any)</td>
+ <td>Automatic Update Notifications</td>
+ </tr>
+ <tr>
+ <td>Template::Plugin::GD::Image</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD::Text</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD::Graph</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD</td>
+ <td>1.20</td>
+ <td>Graphical Reports, New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td>Email::MIME::Attachment::Stripper</td>
+ <td>(Any)</td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td>Email::Reply</td>
+ <td>(Any)</td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td>Net::LDAP</td>
+ <td>(Any)</td>
+ <td>LDAP Authentication</td>
+ </tr>
+ <tr>
+ <td class="req_new">TheSchwartz</td>
+ <td class="req_new">(Any)</td>
+ <td>Mail Queueing</td>
+ </tr>
+ <tr>
+ <td class="req_new">Daemon::Generic</td>
+ <td class="req_new">(Any)</td>
+ <td>Mail Queueing</td>
+ </tr>
+ <tr>
+ <td>HTML::Parser</td>
+ <td>3.40</td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td>HTML::Scrubber</td>
+ <td>(Any)</td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td>XML::Twig</td>
+ <td>(Any)</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td>MIME::Parser</td>
+ <td>5.406</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td>Chart::Base</td>
+ <td>1.0</td>
+ <td>New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td>Image::Magick</td>
+ <td>(Any)</td>
+ <td>Optionally Convert BMP Attachments to PNGs</td>
+ </tr>
+ <tr>
+ <td>PatchReader</td>
+ <td>0.9.4</td>
+ <td>Patch Viewer</td>
+ </tr>
+ <tr>
+ <td>Authen::Radius</td>
+ <td>(Any)</td>
+ <td>RADIUS Authentication</td>
+ </tr>
+ <tr>
+ <td>Authen::SASL</td>
+ <td>(Any)</td>
+ <td>SMTP Authentication</td>
+ </tr>
+ <tr>
+ <td>SOAP::Lite</td>
+ <td>0.710.06</td>
+ <td>XML-RPC Interface</td>
+ </tr>
+ <tr>
+ <td>mod_perl2</td>
+ <td>1.999022</td>
+ <td>mod_perl</td>
+ </tr>
+</table>
+
+<h2 id="v34_feat">New Features and Improvements</h2>
+
+<ul>
+ <li><a href="#v34_feat_enter">Simple [% terms.Bug %] Filing</a></li>
+ <li><a href="#v34_feat_index">New Home Page</a></li>
+ <li><a href="#v34_feat_spam">Email Addresses Hidden From Logged-Out
+ Users</a></li>
+ <li><a href="#v34_feat_urls">Shorter Search URLs</a></li>
+ <li><a href="#v34_feat_async">Asynchronous Email Sending</a></li>
+ <li><a href="#v34_feat_tz">Dates and Times Displayed In User's Time
+ Zone</a></li>
+ <li><a href="#v34_feat_vis">Custom Fields That Only Appear When
+ Another Field Has a Particular Value</a></li>
+ <li><a href="#v34_feat_vals">Custom Fields Whose List of Values
+ Change Depending on the Value of Another Field</a></li>
+ <li><a href="#v34_feat_bugid">New Custom Field Type:
+ [%+ terms.Bug %] ID</a></li>
+ <li><a href="#v34_feat_see">"See Also" Field</a></li>
+ <li><a href="#v34_feat_cols">Re-order Columns in Search Results</a></li>
+ <li><a href="#v34_feat_desc">Search Descriptions</a></li>
+ <li><a href="#v34_feat_other">Other Enhancements and Changes</a></li>
+</ul>
+
+<h3 id="v34_feat_enter">Simple [% terms.Bug %] Filing</h3>
+
+<p>When entering a new [% terms.bug %], the vast majority of fields are
+ now hidden by default, which enormously simplifies the bug-filing form.
+ You can click "Show Advanced Fields" to show all the fields, if you want
+ them. [%+ terms.Bugzilla %] remembers whether you last used the "Advanced"
+ or "Simple" version of the [% terms.bug %]-entry form, and will display the
+ same version to you again next time you file [% terms.abug %].</p>
+
+<h3 id="v34_feat_index">New Home Page</h3>
+
+<p>[% terms.Bugzilla %]'s front page has been redesigned to be better at
+ guiding new users into the activities that they most commonly want to
+ do. Further enhancements to the home page are coming in future versions
+ of [% terms.Bugzilla %].</p>
+
+<h3 id="v34_feat_spam">Email Addresses Hidden From Logged-Out Users</h3>
+
+<p>To help prevent spam to [% terms.Bugzilla %] users, all email addresses
+ stored in [% terms.Bugzilla %] are now displayed only if you are logged in.
+ If you are logged out, only the part before the "@" of the email address is
+ displayed. This includes [% terms.bug %] lists, viewing [% terms.bugs %], the
+ XML format of [% terms.abug %], and any other place in the web interface that
+ an email address could appear.</p>
+
+<p>Email addresses are not filtered out of [% terms.bug %] comments.
+ The WebService still returns full email addresses, even if you are logged
+ out.</p>
+
+<h3 id="v34_feat_urls">Shorter Search URLs</h3>
+
+<p>When submitting a search, all the unused fields are now stripped from
+ the URL, so search URLs are much more meaningful, and much shorter.</p>
+
+<h3 id="v34_feat_async">Asynchronous Email Sending</h3>
+
+<p>The largest performance problem in former versions of [% terms.Bugzilla %]
+ was that when updating [% terms.bugs %], email would be sent immediately
+ to every user who needed to be notified, and <kbd>process_bug.cgi</kbd>
+ would wait for the emails to be sent before continuing.</p>
+
+<p>Now [% terms.Bugzilla %] is capable of queueing emails to be sent
+ while [% terms.abug %] is being updated, and sending them in the
+ background. This requires the administrator to run a daemon
+ that comes with [% terms.Bugzilla %], named
+ <a href="[% docs_urlbase FILTER html %]api/jobqueue.html">jobqueue.pl</a>,
+ and to enable the <a href="editparams.cgi?section=mta#use_mailer_queue">
+ use_mailer_queue</a> parameter.</p>
+
+<p>Using the background email-sending daemon instead of sending mail directly
+ should result in a very large speed-up for updating [% terms.bugs %],
+ particularly on larger installations.</p>
+
+<h3 id="v34_feat_tz">Dates and Times Displayed In User's Time Zone</h3>
+
+<p>Users can now select what time zone they are in and [% terms.Bugzilla %]
+ will adjust displayed times to be correct for their time zone. However,
+ times the user inputs are unfortunately still in [% terms.Bugzilla %]'s
+ time zone.</p>
+
+<h3 id="v34_feat_vis">Custom Fields That Only Appear When Another Field
+ Has a Particular Value</h3>
+
+<p>When creating a new custom field (or updating the definition of
+ an existing custom field), you can now say that "this field only
+ appears when field X has value Y". (In the future, you will be able
+ to select multiple values for "Y", so a field will appear when any
+ one of those values is selected.)</p>
+
+<p>This feature only hides fields--it doesn't make their values go away.
+ So [% terms.bugs %] will still show up in searches for that field's
+ value, but the field won't appear in the user interface.</p>
+
+<p>This is a good way of making Product-specific fields.</p>
+
+<h3 id="v34_feat_vals">Custom Fields Whose List of Values Change
+ Depending on the Value of Another Field</h3>
+
+<p>When creating a drop-down or multiple-selection custom field, you can
+ now specify that another field "controls the values" of this field.
+ Then, when adding values to this field, you can say that a particular
+ value only appears when the other field is set to a particular
+ value.</p>
+
+<p>Here's an example: Let's say that we create a field called "Colors",
+ and we make the Product field "control the values" for Colors. Then we
+ add Blue, Red, Black, and Yellow as legal values for the "Colors" field.
+ Now we can say that "Blue" and "Red" only appear as valid choices in
+ Product A, "Yellow" only appears in Product B, but "Black" <em>always</em>
+ appears.</p>
+
+<p>One thing to note is that this feature only controls what values appear in
+ the <em>user interface</em>. [% terms.Bugzilla %] itself will still accept
+ any combination of values as valid, in the backend.</p>
+
+<h3 id="v34_feat_bugid">New Custom Field Type: [% terms.Bug %] ID</h3>
+
+<p>You can now create a custom field that holds a reference to a single
+ valid [% terms.bug %] ID. In the future this will be enhanced to allow
+ [%+ terms.bugs %] to refer to each other via this field.</p>
+
+<h3 id="v34_feat_see">"See Also" Field</h3>
+
+<p>We have added a new standard field called "See Also" to
+ [%+ terms.Bugzilla %]. In this field, you can put URLs to multiple
+ [%+ terms.bugs %] in any [% terms.Bugzilla %] installation, to indicate
+ that those [% terms.bugs %] are related to this one. It also supports
+ adding URLs to [% terms.bugs %] in
+ <a href="http://launchpad.net/">Launchpad</a>.</p>
+
+<p>Right now, the field just validates the URLs and then displays them, but
+ in the future, it will grab information from the other installation about
+ the [% terms.bug %] and display it here, and possibly even update the
+ other installation.</p>
+
+<p>If your installation does not need this field, you can hide it by disabling
+ the <a href="editparams.cgi?section=bugfields#use_see_also">use_see_also
+ parameter</a>.</p>
+
+<h3 id="v34_feat_cols">Re-order Columns in Search Results</h3>
+
+<p>There is a new interface for choosing what columns appear in search
+ results, which allows you to change the order in which columns appear
+ from left to right when viewing the [% terms.bug %] list.</p>
+
+<h3 id="v34_feat_desc">Search Descriptions</h3>
+
+<p>When displaying search results, [% terms.Bugzilla %] will now show
+ a brief description of what you searched for, at the top of the
+ [%+ terms.bug %] list.</p>
+
+<h3 id="v34_feat_other">Other Enhancements and Changes</h3>
+
+<h4>Enhancements for Users</h4>
+
+<ul>
+ <li>You can now log in from every page, using the login form that appears
+ in the header or footer when you click "Log In".</li>
+ <li>When viewing [% terms.abug %], obsolete attachments are now
+ hidden from the attachment list by default. You can show them
+ by clicking "Show Obsolete" at the bottom of the attachment list.</li>
+ <li>In the Email Preferences, you can now choose to get email when
+ a new [% terms.bug %] report is filed and you have a particular
+ role on it.</li>
+ <li>When resolving a mid-air collision, you can now choose to submit
+ only your comment.</li>
+ <li>You can now set the Blocks and Depends On field on the "Change
+ Several [% terms.Bugs %] At Once" page.</li>
+ <li>If your installation uses the "insidergroup" feature, you can now add
+ private comments on the "Change Several [% terms.Bugs %] At Once"
+ page.</li>
+ <li>When viewing a search result, you can now hover over any abbreviated
+ field to see its full value.</li>
+ <li>When logging out, users are now redirected to the main page of
+ [%+ terms.Bugzilla %] instead of an empty page.</li>
+ <li>When editing [% terms.abug %], text fields (except the comment box) now
+ grow longer when you widen your browser window.</li>
+ <li>When viewing [% terms.abug %], the Depends On and Blocks list will
+ display [% terms.abug %]'s alias if it has one, instead of its id.
+ Also, closed [% terms.bugs %] will be sorted to the end of the list.</li>
+
+ <li>If you use the time-tracking features of [% terms.Bugzilla %], and
+ you enable the time-tracking related columns in a search result,
+ then you will see a summary of the time-tracking data at the
+ bottom of the search result.</li>
+ <li>For users of time-tracking, the <kbd>summarize_time.cgi</kbd> page
+ now contains more data.</li>
+
+ <li>When viewing an attachment's details page while you are logged-out,
+ flags are no longer shown as editable.</li>
+ <li>Cloning [% terms.abug %] will now retain the "Blocks" and "Depends On"
+ fields from the [% terms.bug %] being cloned.</li>
+ <li>[% terms.Bug %]mail for new [% terms.bugs %] will now indicate
+ what security groups the [% terms.bug %] has been restricted to.</li>
+ <li>You can now use any custom drop-down field as an axis for a tabular
+ or graphical report.</li>
+ <li>The <kbd>X-Bugzilla-Type</kbd> header in emails sent by
+ [%+ terms.Bugzilla %] is now "new" for [% terms.bug %]mail sent for
+ newly-filed [% terms.bugs %], and "changed" for emails having to do
+ with updated [% terms.bugs %].</li>
+ <li>Mails sent by the "Whining" system now contain the header
+ <kbd>X-Bugzilla-Type: whine</kbd>.</li>
+ <li>[% terms.bug %]mail now contains a X-Bugzilla-URL header to uniquely
+ identify which [% terms.Bugzilla %] installation the email came from.</li>
+ <li>If you input an invalid regular expression anywhere in
+ [%+ terms.Bugzilla %], it will now tell you explicitly instead of failing
+ cryptically.</li>
+ <li>The <kbd>duplicates.xul</kbd> page (which wasn't used by very many
+ people) is now gone.</li>
+</ul>
+
+<h4>Enhancements for Administrators and Developers</h4>
+
+<ul>
+ <li>[% terms.Bugzilla %] now uses the SHA-256 algorithm (a variant of
+ SHA-2) to encrypt passwords in the database, instead of using Unix's
+ "crypt" function. This allows passwords longer than eight characters
+ to actually be effective. Each user's password will be converted to
+ SHA-256 the first time they log in after you upgrade to
+ [%+ terms.Bugzilla %] 3.4 or later.</li>
+ <li>If you are using database replication with [% terms.Bugzilla %],
+ many more scripts now take advantage of the read-only slave (the
+ "shadowdb"). It may be safe to open up <kbd>show_bug.cgi</kbd>
+ to search-engine indexing by editing your <kbd>robots.txt</kbd> file,
+ now, if your [% terms.Bugzilla %] is on fast-enough hardware.</li>
+ <li>The database now uses foreign keys to enforce the validity of
+ relationships between tables. Not every single table has all its
+ foreign keys yet, but most do.</li>
+ <li>Various parameters have been removed, in an effort to de-clutter
+ the parameter interface and simplify [% terms.Bugzilla %]'s code.
+ The parameters that were removed were: timezone, supportwatchers,
+ maxpatchsize, commentonclearresolution, commentonreassignbycomponent,
+ showallproducts. They have all been replaced with sensible default
+ behaviors. (For example, user watching is now always enabled.)</li>
+ <li>When adding <code>&debug=1</code> to the end of a
+ <kbd>buglist.cgi</kbd> URL, [% terms.Bugzilla %] will now also do an
+ EXPLAIN on the query, to help debug performance issues.</li>
+ <li>When editing flag types in the administrative interface, you can now
+ see how many flags of each type have been set.</li>
+</ul>
+
+<h4>WebService Changes</h4>
+
+<ul>
+ <li>Various functions have been added to the WebService:
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#history">B[% %]ug.history</a>,
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#search">B[% %]ug.search</a>,
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#comments">B[% %]ug.comments</a>,
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html#update_see_also">B[% %]ug.update_see_also</a>,
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/User.html#get">User.get</a>,
+ and <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bugzilla.html#time">B[% %]ugzilla.time</a>
+ (<kbd>B[% %]ugzilla.timezone</kbd> is now deprecated).
+ </li>
+ <li>For network efficiency, you can now limit which fields are returned
+ from certain WebService functions, like <kbd>User.get</kbd>.</li>
+ <li>There is now a "permissive" argument for the <kbd>B[% %]ug.get</kbd>
+ WebService function, which causes it not to throw an error when you
+ ask for [% terms.bugs %] you can't see.</li>
+
+ <li>The <kbd>B[% %]ug.get</kbd> method now returns many more fields.</li>
+ <li>The <kbd>B[% %]ug.add_comment</kbd> method now returns the ID of the comment
+ that was just added.</li>
+ <li>The <kbd>B[% %]ug.add_comment</kbd> method will now throw an error if you
+ try to add a private comment but do not have the correct permissions.
+ (In previous versions, it would just silently ignore the <kbd>private</kbd>
+ argument if you didn't have the correct permissions.)</li>
+ <li>Many WebService function parameters now take individual values in
+ addition to arrays.</li>
+ <li>The WebService now validates input types--it makes sure that dates
+ are in the right format, that ints are actually ints, etc. It will throw
+ an error if you send it invalid data. It also accepts empty ints, doubles,
+ and dateTimes, and translates them to <kbd>undef</kbd>.</li>
+</ul>
+
+<h2 id="v34_issues">Outstanding Issues</h2>
+
+<ul>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=423439">
+ [%- terms.Bug %] 423439</a>: Tabs in comments will be converted
+ to four spaces, due to a b<!-- -->ug in Perl as of Perl 5.8.8.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69621">
+ [%- terms.Bug %] 69621</a>: If you rename or remove a keyword that is
+ in use on [% terms.bugs %], you will need to rebuild the "keyword cache"
+ by running <a href="sanitycheck.cgi">sanitycheck.cgi</a> and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may
+ not show up properly in search results.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
+ [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
+ the same time, there is no "mid-air collision" protection.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
+ [%- terms.Bug %] 276230</a>: The support for restricting access to
+ particular Categories of New Charts is not complete. You should treat
+ the 'chartgroup' Param as the only access mechanism available.<br>
+ However, charts migrated from Old Charts will be restricted to
+ the groups that are marked MANDATORY for the corresponding Product.
+ There is currently no way to change this restriction, and the
+ groupings will not be updated if the group configuration
+ for the Product changes.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370370">
+ [%- terms.Bug %] 370370</a>: mod_perl support is currently not
+ working on Windows machines.</li>
+</ul>
+
+<h2 id="v34_upgrading">Notes On Upgrading From a Previous Version</h2>
+
+<p>When upgrading to 3.4, <kbd>checksetup.pl</kbd> will create foreign keys
+ for many columns in the database. Before doing this, it will check the
+ database for consistency. If there are an unresolvable consistency
+ problems, it will tell you what table and column in the database contain
+ the bad values, and which values are bad. If you don't know what else to do,
+ you can always delete the database records which contain the bad values by
+ logging in to your database and running the following command:</p>
+
+<p><code>DELETE FROM <var>table</var> WHERE <var>column</var> IN
+ (<var>1, 2, 3, 4</var>)</code></p>
+
+<p>Just replace "table" and "column" with the name of the table
+ and column that <kbd>checksetup.pl</kbd> mentions, and "1, 2, 3, 4"
+ with the invalid values that <kbd>checksetup.pl</kbd> prints out.</p>
+
+<p>Remember that you should always back up your database before doing
+ an upgrade.</p>
+
+<h2 id="v34_code_changes">Code Changes Which May Affect Customizations</h2>
+
+<ul>
+ <li><kbd>checksetup.pl</kbd> now re-writes the <kbd>localconfig</kbd>
+ file every time it runs, keeping the current values set (if there
+ are any), but moving any unexpected variables into a file called
+ <kbd>localconfig.old</kbd>. If you want to continue having custom
+ varibles in <kbd>localconfig</kbd>, you will have to add them to
+ the <code>LOCALCONFIG_VARS</code> constant in
+ <kbd>Bugzilla::Install::Localconfig</kbd>.</li>
+ <li><kbd>Bugzilla::Object->update()</kbd> now returns something different
+ in list context than it does in scalar context.</li>
+ <li><kbd>Bugzilla::Object->check()</kbd> now can take object
+ ids in addition to names. Just pass in <code>{ id => $some_value
+ }</code>.</li>
+ <li>Instead of being defined in <kbd>buglist.cgi</kbd>, columns for
+ search results are now defined in a subroutine called <code>COLUMNS</code>
+ in <kbd>Bugzilla::Search</kbd>. The data now mostly comes from the
+ <kbd>fielddefs</kbd> table in the database. Search.pm now takes a list
+ of column names from fielddefs for its <kbd>fields</kbd> argument instead
+ of literal SQL columns.</li>
+ <li><kbd>Bugzilla::Field->legal_values</kbd> now returns an array of
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Field/Choice.html">Bugzilla::Field::Choice</a>
+ objects instead of an array of strings. Bugzilla::Field::Choice will be used
+ in more places, in the future.</li>
+ <li>We now use <kbd>Bugzilla::Bug->check()</kbd> instead of
+ <kbd>ValidateBugId</kbd>.</li>
+ <li>The <kbd>groups</kbd> and <kbd>bless_groups</kbd> methods in
+ <kbd>Bugzilla::User</kbd> now return an arrayref of
+ <kbd>Bugzilla::Group</kbd> objects instead of a hashref with
+ group ids and group names.</li>
+ <li>Standard [% terms.Bugzilla %] drop-down fields now have their type
+ set to <kbd>FIELD_TYPE_SINGLE_SELECT</kbd> in the fielddefs table.</li>
+ <li><kbd>Bugzilla->usage_mode</kbd> now defaults to
+ <kbd>USAGE_MODE_CMDLINE</kbd> if we are not running inside a web
+ server.</li>
+ <li>We no longer delete environment variables like <kbd>$ENV{PATH}</kbd>
+ automatically unless we're actually running in taint mode.</li>
+ <li>We are now using YUI 2.6.0.</li>
+ <li>In <a href="config.cgi?ctype=rdf">the RDF format of config.cgi</a>,
+ the "resource" attribute for flags now contains "flag.cgi" instead
+ of "flags.cgi".</li>
+</ul>
+
+
+
+
+
+
+
+<h1 id="v34_previous">[% terms.Bugzilla %] 3.2 Release Notes</h1>
+
+<h2>Table of Contents</h2>
+
+<ul class="bz_toc">
+ <li><a href="#v32_introduction">Introduction</a></li>
+ <li><a href="#v32_point">Updates In This 3.2.x Release</a></li>
+ <li><a href="#v32_security">Security Fixes In This 3.2.x Release</a></li>
+ <li><a href="#v32_req">Minimum Requirements</a></li>
+ <li><a href="#v32_feat">New Features and Improvements</a></li>
+ <li><a href="#v32_issues">Outstanding Issues</a></li>
+ <li><a href="#v32_upgrading">How to Upgrade From An Older Version</a></li>
+ <li><a href="#v32_code_changes">Code Changes Which May Affect
+ Customizations</a></li>
+ <li><a href="#v32_previous">Release Notes for Previous Versions</a></li>
+</ul>
+
+<h2 id="v32_introduction">Introduction</h2>
+
+<p>Welcome to [% terms.Bugzilla %] 3.2! This is our first major feature
+ release since [% terms.Bugzilla %] 3.0, and it brings a lot of great
+ improvements and polish to the [% terms.Bugzilla %] experience.</p>
+
+<p>If you're upgrading, make sure to read <a href="#v32_upgrading">How to
+ Upgrade From An Older Version</a>. If you are upgrading from a release
+ before 3.0, make sure to read the release notes for all the
+ <a href="#v32_previous">previous versions</a> in between your version
+ and this one, <strong>particularly the "Notes For Upgraders" section of each
+ version's release notes</strong>.</p>
+
+<h2 id="v32_point">Updates in this 3.2.x Release</h2>
+
+<p>This section describes what's changed in the most recent b<!-- -->ug-fix
+ releases of [% terms.Bugzilla %] after 3.2. We only list the
+ most important fixes in each release. If you want a detailed list of
+ <em>everything</em> that's changed in each version, you should use our
+ <a href="http://www.bugzilla.org/status/changes.html">Change Log
+ Page</a>.</p>
+
+<h3>3.2.3</h3>
+
+<ul>
+ <li>[% terms.Bugzilla %] is now compatible with MySQL 5.1.x versions 5.1.31
+ and greater.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=480001">[% terms.Bug %] 480001</a>)</li>
+ <li>On Windows, [% terms.Bugzilla %] sometimes would send mangled emails
+ (that would often fail to send).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=467920">[% terms.Bug %] 467920</a>)</li>
+ <li><code>recode.pl</code> would sometimes crash when trying to convert
+ databases from older versions of [% terms.Bugzilla %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=431201">[% terms.Bug %] 431201</a>)</li>
+ <li>Running a saved search with Unicode characters in its name would
+ cause [% terms.Bugzilla %] to crash.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=477513">[% terms.Bug %] 477513</a>)</li>
+ <li>[% terms.Bugzilla %] clients like Mylyn can now update [% terms.bugs %]
+ again (the [% terms.bug %] XML format now contains a "token" element that
+ can be used when updating [% terms.abug %]).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=476678">[% terms.Bug %] 476678</a>)</li>
+ <li>For installations using the <code>shadowdb</code> parameter,
+ [%+ terms.Bugzilla %] was accidentally writing to the "tokens" table
+ in the shadow database (instead of the master database) when using the
+ "Change Several [% terms.Bugs %] at Once" page.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=476943">[% terms.Bug %] 476943</a>)</li>
+</ul>
+
+<p>This release also contains a security fix. See the
+ <a href="#v32_security">Security Fixes Section</a> for details.</p>
+
+<h3>3.2.2</h3>
+
+<p>This release fixes one security issue that is critical for installations
+ running 3.2.1 under mod_perl. See the
+ <a href="http://www.bugzilla.org/security/3.0.7/">Security Advisory</a>
+ for details.</p>
+
+<h3>3.2.1</h3>
+
+<ul>
+ <li>Attachments, charts, and graphs would sometimes be garbled on Windows.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=464992">[% terms.Bug %] 464992</a>)</li>
+
+ <li>Saving changes to parameters would sometimes fail silently (particularly
+ on Windows when the web server didn't have the right permissions to
+ update the <code>params</code> file). [% terms.Bugzilla %] will now
+ throw an error in this case, telling you what is wrong.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=347707">[% terms.Bug %] 347707</a>)</li>
+
+ <li>If you were using the <code>usemenuforusers</code> parameter,
+ and [% terms.abug %] was assigned to (or had a QA Contact of) a disabled
+ user, that field would be reset to the first user in the list when
+ updating [% terms.abug %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=465589">[% terms.Bug %] 465589</a>)</li>
+
+ <li>If you were using the <code>PROJECT</code> environment variable
+ to have multiple [% terms.Bugzilla %] installations using one codebase,
+ project-specific templates were being ignored.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=467324">[% terms.Bug %] 467324</a>)</li>
+
+ <li>Some versions of the SOAP::Lite Perl module had a b[% %]ug that caused
+ [%+ terms.Bugzilla %]'s XML-RPC service to break.
+ <kbd>checksetup.pl</kbd> now checks for these bad versions and
+ will reject them.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=468009">[% terms.Bug %] 468009</a>)</li>
+
+ <li>The font sizes in various places were too small, when using the
+ Classic skin.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=469136">[% terms.Bug %] 469136</a>)</li>
+</ul>
+
+<h2 id="v32_security">Security Fixes In This 3.2.x Release</h2>
+
+<h3>3.2.3</h3>
+
+<p>This release fixes one security issue related to attachments. See the
+ <a href="http://www.bugzilla.org/security/3.2.2/">Security Advisory</a>
+ for details.</p>
+
+<h3>3.2.2</h3>
+
+<p>This release fixes one security issue that is critical for installations
+ running 3.2.1 under mod_perl. See the
+ <a href="http://www.bugzilla.org/security/3.0.7/">Security Advisory</a>
+ for details.</p>
+
+<h3>3.2.1</h3>
+
+<p>This release contains several security fixes. One fix may break any
+ automated scripts you have that are loading <kbd>process_bug.cgi</kbd>
+ directly. We recommend that you read the entire
+ <a href="http://www.bugzilla.org/security/2.22.6/">Security Advisory</a>
+ for this release.</p>
+
+<h2 id="v32_req">Minimum Requirements</h2>
+
+<p>Any requirements that are new since 3.0.5 will look like
+ <span class="req_new">this</span>.</p>
+
+<ul>
+ <li><a href="#v32_req_perl">Perl</a></li>
+ <li><a href="#v32_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v32_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v32_req_oracle">For Oracle Users</a></li>
+ <li><a href="#v32_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v32_req_optional_mod">Optional Perl
+ Modules</a></li>
+</ul>
+
+<h3 id="v32_req_perl">Perl</h3>
+
+<p>Perl <span class="req_new">v<strong>5.8.1</strong></span></p>
+
+<h3 id="v32_req_mysql">For MySQL Users</h3>
+
+<ul>
+ <li>MySQL v4.1.2</li>
+ <li><strong>perl module:</strong>
+ DBD::mysql <span class="req_new">v4.00</span></li>
+</ul>
+
+<h3 id="v32_req_pg">For PostgreSQL Users</h3>
+
+<ul>
+ <li>PostgreSQL v8.00.0000</li>
+ <li><strong>perl module:</strong> DBD::Pg v1.45</li>
+</ul>
+
+<h3 id="v32_req_oracle">Email Addresses Hidden From Logged-Out Users
+ For Oracle Users</h3>
+
+<ul>
+ <li>Oracle v10.02.0</li>
+ <li><strong>perl module:</strong> DBD::Oracle v1.19</li>
+</ul>
+
+<h3 id="v32_req_modules">Required Perl Modules</h3>
+
+<table class="req_table" border="0" cellpadding="0" cellspacing="0">
+<tr> <th>Module</th> <th>Version</th> </tr>
+<tr> <td>CGI</td> <td class="req_new">3.21 (on Perl 5.8.x)
+ or 3.33 (on Perl 5.10.x)</td> </tr>
+<tr> <td>Date::Format</td> <td>2.21</td> </tr>
+<tr> <td>File::Spec</td> <td>0.84</td> </tr>
+<tr> <td>DBI</td> <td>1.41</td> </tr>
+<tr> <td>Template</td> <td class="req_new">2.15</td> </tr>
+<tr> <td>Email::Send</td> <td>2.00</td> </tr>
+<tr> <td>Email::MIME</td> <td class="req_new">1.861</td> </tr>
+<tr>
+ <td class="req_new">Email::MIME::Encodings</td>
+ <td class="req_new">1.313</td>
+</tr>
+<tr>
+ <td>Email::MIME::Modifier</td>
+ <td class="req_new">1.442</td>
+</tr>
+</table>
+
+<h3 id="v32_req_optional_mod">Optional Perl Modules</h3>
+
+<p>The following perl modules, if installed, enable various
+ features of [% terms.Bugzilla %]:</p>
+
+<table class="req_table" border="0" cellpadding="0" cellspacing="0">
+<tr>
+ <th>Module</th>
+ <th>Version</th>
+ <th>Enables Feature</th>
+</tr>
+<tr>
+ <td>LWP::UserAgent</td>
+ <td>(Any)</td>
+ <td>Automatic Update Notifications</td>
+</tr>
+<tr>
+ <td>Template::Plugin::GD::Image</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+</tr>
+<tr>
+ <td>GD::Text</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+</tr>
+<tr>
+ <td>GD::Graph</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+</tr>
+<tr>
+ <td>GD</td>
+ <td>1.20</td>
+ <td>Graphical Reports, New Charts, Old Charts</td>
+</tr>
+<tr>
+ <td>Email::MIME::Attachment::Stripper</td>
+ <td>(Any)</td>
+ <td>Inbound Email</td>
+</tr>
+<tr>
+ <td>Email::Reply</td>
+ <td>(Any)</td>
+ <td>Inbound Email</td>
+</tr>
+<tr>
+ <td>Net::LDAP</td>
+ <td>(Any)</td>
+ <td>LDAP Authentication</td>
+</tr>
+<tr>
+ <td>HTML::Parser</td>
+ <td>3.40</td>
+ <td>More HTML in Product/Group Descriptions</td>
+</tr>
+<tr>
+ <td>HTML::Scrubber</td>
+ <td>(Any)</td>
+ <td>More HTML in Product/Group Descriptions</td>
+</tr>
+<tr>
+ <td>XML::Twig</td>
+ <td>(Any)</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+</tr>
+<tr>
+ <td>MIME::Parser</td>
+ <td>5.406</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+</tr>
+<tr>
+ <td>Chart::Base</td>
+ <td>1.0</td>
+ <td>New Charts, Old Charts</td>
+</tr>
+<tr>
+ <td>Image::Magick</td>
+ <td>(Any)</td>
+ <td>Optionally Convert BMP Attachments to PNGs</td>
+</tr>
+<tr>
+ <td>PatchReader</td>
+ <td>0.9.4</td>
+ <td>Patch Viewer</td>
+</tr>
+<tr>
+ <td class="req_new">Authen::Radius</td>
+ <td class="req_new">(Any)</td>
+ <td>RADIUS Authentication</td>
+</tr>
+<tr>
+ <td class="req_new">Authen::SASL</td>
+ <td class="req_new">(Any)</td>
+ <td>SMTP Authentication</td>
+</tr>
+<tr>
+ <td>SOAP::Lite</td>
+ <td>(Any)</td>
+ <td>XML-RPC Interface</td>
+</tr>
+<tr>
+ <td>mod_perl2</td>
+ <td>1.999022</td>
+ <td>mod_perl</td>
+</tr>
+</table>
+
+<h2 id="v32_feat">New Features and Improvements</h2>
+
+<ul>
+ <li><a href="#v32_feat_ui">Major UI Improvements</a></li>
+ <li><a href="#v32_feat_skin">New Default Skin: Dusk</a></li>
+ <li><a href="#v32_feat_status">Custom Status Workflow</a></li>
+ <li><a href="#v32_feat_fields">New Custom Field Types</a></li>
+ <li><a href="#v32_feat_install">Easier Installation</a></li>
+ <li><a href="#v32_feat_oracle">Experimental Oracle Support</a></li>
+ <li><a href="#v32_feat_utf8">Improved UTF-8 Support</a></li>
+ <li><a href="#v32_feat_grcons">Group Icons</a></li>
+ <li><a href="#v32_feat_other">Other Enhancements and Changes</a></li>
+</ul>
+
+<h3 id="v32_feat_ui">Major UI Improvements</h3>
+
+<p>[% terms.Bugzilla %] 3.2 has had some UI assistance from the NASA
+ Human-Computer Interaction department and the new
+ <a href="http://wiki.mozilla.org/Bugzilla:UE">[% terms.Bugzilla %]
+ User Interface Team</a>.</p>
+
+<p>In particular, you will notice a massively redesigned [% terms.bug %]
+ editing form, in addition to our <a href="#v32_feat_skin">new skin</a>.</p>
+
+<h3 id="v32_feat_skin">New Default Skin: Dusk</h3>
+
+<p>[% terms.Bugzilla %] 3.2 now ships with a skin called "Dusk" that is
+ a bit more colorful than old default "Classic" skin.</p>
+
+<p>Upgrading installations will still default to the "Classic"
+ skin--administrators can change the default in the Default Preferences
+ control panel. Users can also choose to use the old skin in their
+ Preferences (or using the View :: Page Style menu in Firefox).</p>
+
+<p>The changes that [% terms.Bugzilla %] required for Dusk made
+ [%+ terms.Bugzilla %] much easier to skin. See the
+ <a href="http://wiki.mozilla.org/Bugzilla:Addons#Skins">Addons page</a>
+ for additional skins, or try making your own!</p>
+
+<h3 id="v32_feat_status">Custom Status Workflow</h3>
+
+<p>You can now customize the list of statuses in [% terms.Bugzilla %],
+ and transitions between them.</p>
+
+<p>You can also specify that a comment must be made on certain transitions.</p>
+
+<h3 id="v32_feat_fields">New Custom Field Types</h3>
+
+<p>[% terms.Bugzilla %] 3.2 has support for three new types of
+ custom fields:</p>
+
+<ul>
+ <li>Large Text: Adds a multi-line textbox to your [% terms.bugs %].</li>
+ <li>Multiple Selection Box: Adds a box that allows you to choose
+ multiple items from a list.</li>
+ <li>Date/Time: Displays a date and time, along with a JavaScript
+ calendar popup to make picking a date easier.</li>
+</ul>
+
+<h3 id="v32_feat_install">Easier Installation</h3>
+
+<p>[% terms.Bugzilla %] now comes with a script called
+ <kbd>install-module.pl</kbd> that can automatically download
+ and install all of the required Perl modules for [% terms.Bugzilla %].
+ It stores them in a directory inside your [% terms.Bugzilla %]
+ installation, so you can use it even if you don't have administrator-level
+ access to your machine, and without modifying your main Perl install.</p>
+
+<p><kbd>checksetup.pl</kbd> will print out instructions for using
+ <kbd>install-module.pl</kbd>, or you can read its
+ <a href="[% docs_urlbase FILTER html %]api/install-module.html">documentation</a>.</p>
+
+<h3 id="v32_feat_oracle">Experimental Oracle Support</h3>
+
+<p>[% terms.Bugzilla %] 3.2 contains experimental support for using
+ Oracle as its database. Some features of [% terms.Bugzilla %] are known
+ to be broken on Oracle, but hopefully will be working by our next major
+ release.</p>
+
+<p>The [% terms.Bugzilla %] Project, as an open-source project, of course
+ does not recommend the use of proprietary database solutions. However,
+ if your organization requires that you use Oracle, this will allow
+ you to use [% terms.Bugzilla %]!</p>
+
+<p>The [% terms.Bugzilla %] Project thanks Oracle Corp. for their extensive
+ development contributions to [% terms.Bugzilla %] which allowed this to
+ happen!</p>
+
+<h3 id="v32_feat_utf8">Improved UTF-8 Support</h3>
+
+<p>[% terms.Bugzilla %] 3.2 now has advanced UTF-8 support in its code,
+ including correct handling for truncating and wrapping multi-byte
+ languages. Major issues with multi-byte or unusual languages
+ are now resolved, and [% terms.Bugzilla %] should now be usable
+ by users in every country with little (or at least much less)
+ customization.</p>
+
+<h3 id="v32_feat_grcons">Group Icons</h3>
+
+<p>Administrators can now specify that users who are in certain groups
+ should have an icon appear next to their name whenever they comment.
+ This is particularly useful for distinguishing developers from
+ [%+ terms.bug %] reporters.</p>
+
+<h3 id="v32_feat_other">Other Enhancements and Changes</h3>
+
+<p>These are either minor enhancements, or enhancements that have
+ very short descriptions. Some of these are very useful, though!</p>
+
+<h4>Enhancements For Users</h4>
+
+<ul>
+ <li><strong>[% terms.Bugs %]</strong>: You can now reassign
+ [%+ terms.abug %] at the same time as you are changing its status.</li>
+ <li><strong>[% terms.Bugs %]</strong>: When entering [% terms.abug %],
+ you will now see the description of a component when you select it.</li>
+ <li><strong>[% terms.Bugs %]</strong>: The [% terms.bug %] view now
+ contains some <a href="http://microformats.org/about/">Microformats</a>,
+ most notably for users' names and email addresses.</li>
+ <li><strong>[% terms.Bugs %]</strong>: You can now remove a QA Contact
+ from [% terms.abug %] simply by clearing the QA Contact field.</li>
+ <li><strong>[% terms.Bugs %]</strong>: There is now a user preference
+ that will allow you to exclude the quoted text when replying
+ to comments.</li>
+ <li><strong>[% terms.Bugs %]</strong>: You can now expand or collapse
+ individual comments in the [% terms.bug %] view.</li>
+
+ <li><strong>Attachments</strong>: There is now "mid-air collision"
+ protection when editing attachments.</li>
+ <li><strong>Attachments</strong>: Patches in the Diff Viewer now show
+ line numbers (<a href="https://bugzilla.mozilla.org/attachment.cgi?id=327546">Example</a>).</li>
+ <li><strong>Attachments</strong>: After creating or updating an attachment,
+ you will be immediately shown the [% terms.bug %] that the attachment
+ is on.</li>
+
+ <li><strong>Search</strong>: You can now reverse the sort of
+ [%+ terms.abug %] list by clicking on a column header again.</li>
+ <li><strong>Search</strong>: Atom feeds of [% terms.bug %] lists now
+ contain more fields.</li>
+ <li><strong>Search</strong>: QuickSearch now supports searching flags
+ and groups. It also now includes the OS field in the list of fields
+ it searches by default.</li>
+ <li><strong>Search</strong>: "Help" text can now appear on query.cgi
+ for Internet Explorer and other non-Firefox browsers. (It always
+ could appear for Firefox.)</li>
+
+ <li>[% terms.Bugzilla %] now ships with an icon that will show
+ up next to the URL in most browsers. If you want to replace it,
+ it's in <kbd>images/favicon.ico</kbd>.</li>
+
+ <li>You can now set the Deadline when using "Change Several
+ [%+ terms.Bugs %] At Once"</li>
+ <li><strong>Saved Searches</strong> now save their column list, so if
+ you customize the list of columns and save your search, it will
+ always contain those columns.</li>
+ <li><strong>Saved Searches</strong>: When you share a search, you can
+ now see how many users have subscribed to it, on
+ <kbd>userprefs.cgi</kbd>.</li>
+ <li><strong>Saved Searches</strong>: You can now see what group a
+ shared search was shared to, on the list of available shared searches
+ in <kbd>userprefs.cgi</kbd>.</li>
+ <li><strong>Flags</strong>: If your installation uses drop-down user
+ lists, the flag requestee box will now contain only users who are
+ actually allowed to take requests.</li>
+ <li><strong>Flags</strong>: If somebody makes a request to you, and you
+ change the requestee to somebody else, the requester is no longer set
+ to you. In other words, you can "redirect" requests and maintain the
+ original requester.</li>
+ <li><strong>Flags</strong>: Emails about flags now will thread properly
+ in email clients to be a part of [% terms.abug %]'s thread.</li>
+ <li>When using <kbd>email_in.pl</kbd>, you can now add users to the CC
+ list by just using <kbd>@cc</kbd> as the field name.</li>
+ <li>Many pages (particularly administrative pages) now contain links to
+ the relevant section of the [% terms.Bugzilla %] Guide, so you can read
+ the documentation for that page.</li>
+ <li>Dependency Graphs should render more quickly, as they now (by default)
+ only include the same [% terms.bugs %] that you'd see in the dependency
+ tree.</li>
+</ul>
+
+<h4>Enhancements For Administrators</h4>
+
+<ul>
+ <li><strong>Admin UI</strong>: Instead of having the Administration
+ Control Panel links in the footer, there is now just one link called
+ "Administration" that takes you to a page that links to all the
+ administrative controls for [% terms.Bugzilla %].</li>
+ <li><strong>Admin UI</strong>: Administrative pages no longer display
+ confirmation pages, instead they redirect you to some useful page
+ and display a message about what changed.</li>
+ <li><strong>Admin UI</strong>: The interface for editing group
+ inheritance in <kbd>editgroups.cgi</kbd> is much clearer now.</li>
+ <li><strong>Admin UI</strong>: When editing a user, you can now see
+ all the components where that user is the Default Assignee or Default
+ QA Contact.</li>
+
+ <li><strong>Email</strong>: For installations that use SMTP to send
+ mail (as opposed to Sendmail), [%+ terms.Bugzilla %] now supports
+ SMTP Authentication, so that it can log in to your mail server
+ before sending messages.</li>
+ <li><strong>Email</strong>: Using the "Test" mail delivery method now
+ creates a valid mbox file to make testing easier.</li>
+
+ <li><strong>Authentication</strong>: [% terms.Bugzilla %] now correctly
+ handles LDAP records which contain multiple email addresses. (The first
+ email address in the list that is a valid [% terms.Bugzilla %] account
+ will be used, or if this is a new user, the first email address in
+ the list will be used.)</li>
+ <li><strong>Authentication</strong>: [% terms.Bugzilla %] can now take
+ a list of LDAP servers to try in order until it gets a successful
+ connection.</li>
+ <li><strong>Authentication</strong>: [% terms.Bugzilla %] now supports
+ RADIUS authentication.</li>
+
+ <li><strong>Security</strong>: The login cookie is now created as
+ "HTTPOnly" so that it can't be read by possibly malicious scripts.
+ Also, if SSL is enabled on your installation, the login cookie is
+ now only sent over SSL connections.</li>
+ <li><strong>Security</strong>: The <code>ssl</code> parameter now protects
+ every page a logged-in user accesses, when set to "authenticated sessions."
+ Also, SSL is now enforced appropriately in the WebServices interface when
+ the parameter is set.</li>
+
+ <li><strong>Database</strong>: [% terms.Bugzilla %] now uses transactions in
+ the database instead of table locks. This should generally improve
+ performance with many concurrent users. It also means if there is
+ an unexpected error in the middle of a page, all database changes made
+ during that page will be rolled back.</li>
+ <li><strong>Database</strong>: You no longer have to set
+ <code>max_packet_size</code> in MySQL to add large attachments. However,
+ you may need to set it manually if you restore a mysqldump into your
+ database.</li>
+
+ <li>New WebService functions:
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html">B<!-- -->ug.add_comment</a>
+ and <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bugzilla.html">Bugzilla.extensions</a>.</li>
+
+ <li>You can now delete custom fields, but only if they have never been
+ set on any [% terms.bug %].</li>
+ <li>There is now a <kbd>--reset-password</kbd> argument to
+ <kbd>checksetup.pl</kbd> that allows you to reset a user's password
+ from the command line.</li>
+ <li>There is now a script called <kbd>sanitycheck.pl</kbd> that you can
+ run from the command line. It works just like <kbd>sanitycheck.cgi</kbd>.
+ By default, it only outputs anything if there's an error, so it's
+ ideal for administrators who want to run it nightly in a cron job.</li>
+ <li>The <kbd>strict_isolation</kbd> parameter now prevents you from setting
+ users who cannot see [% terms.abug %] as a CC, Assignee, or QA
+ Contact. Previously it only prevented you from adding users who
+ could not <em>edit</em> the [% terms.bug %].</li>
+ <li>Extensions can now add their own headers to the HTML <head>
+ for things like custom CSS and so on.</li>
+ <li><kbd>sanitycheck.cgi</kbd> has been templatized, meaning that the
+ entire [% terms.Bugzilla %] UI is now contained in templates.</li>
+ <li>When setting the <kbd>sslbase</kbd> parameter, you can now specify
+ a port number in the URL.</li>
+ <li>When importing [% terms.bugs %] using <kbd>importxml.pl</kbd>,
+ attachments will have their actual creator set as their creator,
+ instead of the person who exported the [% terms.bug %] from the other
+ system.</li>
+ <li>The voting system is off by default in new installs. This is to
+ prepare for the fact that it will be moved into an extension at
+ some point in the future.</li>
+ <li>The <code>shutdownhtml</code> parameter now works even when
+ [%+ terms.Bugzilla %]'s database server is down.</li>
+</ul>
+
+<h3>Enhancements for Localizers (or Localized Installations)</h3>
+
+<ul>
+ <li>The documentation can now be localized--in other words, you can have
+ documentation installed for multiple languages at once and
+ [%+ terms.Bugzilla %] will link to the correct language in its internal
+ documentation links.</li>
+ <li>[% terms.Bugzilla %] no longer uses the <kbd>languages</kbd> parameter.
+ Instead it reads the <kbd>template/</kbd> directory to see which
+ languages are available.</li>
+ <li>Some of the messages printed by <kbd>checksetup.pl</kbd> can now
+ be localized. See <kbd>template/en/default/setup/strings.txt.pl</kbd>.
+</ul>
+
+<h2 id="v32_issues">Outstanding Issues</h2>
+
+<ul>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=423439">
+ [%- terms.Bug %] 423439</a>: Tabs in comments will be converted
+ to four spaces, due to a b<!-- -->ug in Perl as of Perl 5.8.8.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69621">
+ [%- terms.Bug %] 69621</a>: If you rename or remove a keyword that is
+ in use on [% terms.bugs %], you will need to rebuild the "keyword cache"
+ by running <a href="sanitycheck.cgi">sanitycheck.cgi</a> and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may
+ not show up properly in search results.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
+ [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
+ the same time, there is no "mid-air collision" protection.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
+ [%- terms.Bug %] 276230</a>: The support for restricting access to
+ particular Categories of New Charts is not complete. You should treat
+ the 'chartgroup' Param as the only access mechanism available.<br>
+ However, charts migrated from Old Charts will be restricted to
+ the groups that are marked MANDATORY for the corresponding Product.
+ There is currently no way to change this restriction, and the
+ groupings will not be updated if the group configuration
+ for the Product changes.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370370">
+ [%- terms.Bug %] 370370</a>: mod_perl support is currently not
+ working on Windows machines.</li>
+</ul>
+
+<h2 id="v32_upgrading">How to Upgrade From An Older Version</h2>
+
+<h3 id="v32_upgrading_notes">Notes For Upgraders</h3>
+
+<ul>
+ <li>If you upgrade by CVS, the <kbd>extensions</kbd> and
+ <kbd>skins/contrib</kbd> directories are now in CVS instead of
+ being created by <kbd>checksetup.pl</kbd> If you do a <kbd>cvs update</kbd>
+ from 3.0, you will be told that your directories are "in the way" and
+ you should delete (or move) them and then do <kbd>cvs update</kbd>
+ again. Also, the <kbd>docs</kbd> directory has been restructured
+ and after you <kbd>cvs update</kbd> you can delete the <kbd>docs/html</kbd>,
+ <kbd>docs/pdf</kbd>, <kbd>docs/txt</kbd>, and <kbd>docs/xml</kbd>
+ directories.</li>
+ <li>If you are using MySQL, you should know that [% terms.Bugzilla %]
+ now uses InnoDB for all tables. <kbd>checksetup.pl</kbd> will convert
+ your tables automatically, but if you have InnoDB disabled,
+ the upgrade will not be able to complete (and <kbd>checksetup.pl</kbd>
+ will tell you so).</li>
+
+ <li><strong>You should also read the
+ <a href="#v30_upgrading_notes">[% terms.Bugzilla %] 3.0 Notes For Upgraders
+ section</a> of the
+ <a href="#v32_previous">previous release notes</a> if you are upgrading
+ from a version before 3.0.</strong></li>
+</ul>
+
+<h3>Steps For Upgrading</h3>
+
+<p>Once you have read the notes above, see the
+ <a href="[% docs_urlbase FILTER html %]upgrade.html">Upgrading
+ documentation</a> for instructions on how to upgrade.</p>
+
+<h2 id="v32_code_changes">Code Changes Which May Affect Customizations</h2>
+
+<ul>
+ <li><a href="#v32_code_hooks">More Hooks!</a></li>
+ <li><a href="#v32_code_search">Search.pm Rearchitecture</a></li>
+ <li><a href="#v32_code_lib">lib Directory</a></li>
+ <li><a href="#v32_code_other">Other Changes</a></li>
+</ul>
+
+<h3 id="v32_code_hooks">More Hooks!</h3>
+
+<p>There are more code hooks in 3.2 than there were in 3.0. See the
+ documentation of <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Hook.html">Bugzilla::Hook</a>
+ for more details.</p>
+
+<h3 id="v32_code_search">Search.pm Rearchitecture</h3>
+
+<p><kbd>Bugzilla/Search.pm</kbd> has been heavily modified, to be much
+ easier to read and use. It contains mostly the same code as it did in
+ 3.0, but it has been moved around and reorganized significantly.</p>
+
+<h3 id="v32_code_lib">lib Directory</h3>
+
+<p>As part of implementing <a href="#v32_feat_install">install-module.pl</a>,
+ [%+ terms.Bugzilla %] was given a local <kbd>lib</kbd> directory which
+ it searches for modules, in addition to the standard system path.</p>
+
+<p>This means that all [% terms.Bugzilla %] scripts now start with
+ <code>use lib qw(. lib);</code> as one of the first lines.</p>
+
+<h3 id="v32_code_other">Other Changes</h3>
+
+<ul>
+ <li>You should now be using <code>get_status('NEW')</code> instead of
+ <code>status_descs.NEW</code> in templates.</li>
+ <li>The <code>[%# version = 1.0 %]</code> comment at the top of every
+ template file has been removed.</li>
+</ul>
+
+<h1 id="v32_previous">[% terms.Bugzilla %] 3.0.x Release Notes</h1>
+
+<h2>Table of Contents</h2>
+
+<ul class="bz_toc">
+ <li><a href="#v30_introduction">Introduction</a></li>
+ <li><a href="#v30_point">Updates In This 3.0.x Release</a></li>
+ <li><a href="#v30_req">Minimum Requirements</a></li>
+ <li><a href="#v30_feat">New Features and Improvements</a></li>
+ <li><a href="#v30_issues">Outstanding Issues</a></li>
+ <li><a href="#v30_security">Security Fixes In This Release</a></li>
+ <li><a href="#v30_upgrading">How to Upgrade From An Older Version</a></li>
+ <li><a href="#v30_code_changes">Code Changes Which May Affect
+ Customizations</a></li>
+ <li><a href="#v30_previous">Release Notes for Previous Versions</a></li>
+</ul>
+
+<h2 id="v30_introduction">Introduction</h2>
+
+<p>Welcome to [% terms.Bugzilla %] 3.0! It's been over eight years since
+ we released [% terms.Bugzilla %] 2.0, and everything has changed since
+ then. Even just since our previous release, [% terms.Bugzilla %] 2.22,
+ we've added a <em>lot</em> of new features. So enjoy the release, we're
+ happy to bring it to you.</p>
+
+<p>If you're upgrading, make sure to read <a href="#v30_upgrading">How to
+ Upgrade From An Older Version</a>. If you are upgrading from a release
+ before 2.22, make sure to read the release notes for all the
+ <a href="#v30_previous">previous versions</a> in between your version
+ and this one.</p>
+
+<h2 id="v30_point">Updates in this 3.0.x Release</h2>
+
+<p>This section describes what's changed in the most recent b<!-- -->ug-fix
+ releases of [% terms.Bugzilla %] after 3.0. We only list the
+ most important fixes in each release. If you want a detailed list of
+ <em>everything</em> that's changed in each version, you should use our
+ <a href="http://www.bugzilla.org/status/changes.html">Change Log Page</a>.</p>
+
+<h3>3.0.6</h3>
+
+<ul>
+ <li>Before 3.0.6, unexpected fatal WebService errors would result in
+ a <code>faultCode</code> that was a string instead of a number.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=446327">[% terms.Bug %] 446327</a>)</li>
+ <li>If you created a product or component with the same name as one you
+ previously deleted, it would fail with an error about the series table.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=247936">[% terms.Bug %] 247936</a>)</li>
+</ul>
+
+<p>See also the <a href="#v30_security">Security Advisory</a> section for
+ information about a security issue fixed in this release.</p>
+
+<h3>3.0.5</h3>
+
+<ul>
+ <li>If you don't have permission to set a flag, it will now appear
+ unchangeable in the UI.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=433851">[% terms.Bug %] 433851</a>)</li>
+ <li>If you were running mod_perl, [% terms.Bugzilla %] was not correctly
+ closing its connections to the database since 3.0.3, and so sometimes
+ the DB would run out of connections.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=441592">[% terms.Bug %] 441592</a>)</li>
+ <li>The installation script is now clear about exactly which
+ <code>Email::</code> modules are required in Perl, thus avoiding the
+ problem where emails show up with a body like
+ <samp>SCALAR(0xBF126795)</samp>.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=441541">[% terms.Bug %] 441541</a>)</li>
+ <li><a href="[% docs_urlbase FILTER html %]api/email_in.html">email_in.pl</a>
+ is no longer case-sensitive for values of <kbd>@product</kbd>.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365697">[% terms.Bug %] 365697</a>)</li>
+</ul>
+
+<p>See also the <a href="#v30_security">Security Advisory</a> section for
+ information about security issues fixed in this release.</p>
+
+<h3>3.0.4</h3>
+
+<ul>
+ <li>[% terms.Bugzilla %] administrators were not being correctly notified
+ about new releases.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=414726">[% terms.Bug %] 414726</a>)</li>
+
+ <li>There could be extra whitespace in email subject lines.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=411544">[% terms.Bug %] 411544</a>)</li>
+
+ <li>The priority, severity, OS, and platform fields were always required by
+ the <kbd>B<!-- -->ug.create</kbd> WebService function, even if they had
+ defaults specified.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=384009">[% terms.Bug %] 384009</a>)</li>
+
+ <li>Better threading of [% terms.bug %]mail in some email clients.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=376453">[% terms.Bug %] 376453</a>)</li>
+
+ <li>There were many fixes to the Inbound Email Interface
+ (<kbd>email_in.pl</kbd>).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=92274">[% terms.Bug %] 92274</a>,
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=377025">[% terms.Bug %] 377025</a>,
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=412943">[% terms.Bug %] 412943</a>,
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=413672">[% terms.Bug %] 413672</a>, and
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=431721">[% terms.Bug %] 431721</a>)</li>
+
+ <li><kbd>checksetup.pl</kbd> now handles UTF-8 conversion more reliably during upgrades.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=374951">[% terms.Bug %] 374951</a>)</li>
+
+ <li>Comments written in CJK languages are now correctly word-wrapped.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=388723">[% terms.Bug %] 388723</a>)</li>
+
+ <li>All emails will now be sent in the correct language, when the user
+ has chosen a language for emails.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=405946">[% terms.Bug %] 405946</a>)
+
+ <li>On Windows, temporary files created when uploading attachments are now
+ correctly deleted when the upload is complete.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=414002">[% terms.Bug %] 414002</a>)</li>
+
+ <li><kbd>checksetup.pl</kbd> now prints correct installation instructions
+ for Windows users using Perl 5.10.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=414430">[% terms.Bug %] 414430</a>)
+</ul>
+
+<p>See also the <a href="#v30_security">Security Advisory</a> section for
+ information about security issues fixed in this release.</p>
+
+<h3>3.0.3</h3>
+
+<ul>
+ <li>mod_perl no longer compiles [% terms.Bugzilla %]'s code for each Apache
+ process individually. It now compiles code only once and shares it among
+ each Apache process. This greatly improves performance and highly
+ decreases the memory footprint.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=398241">[% terms.Bug %] 398241</a>)</li>
+
+ <li>You can now search for '---' (without quotes) in versions and milestones.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=362436">[% terms.Bug %] 362436</a>)</li>
+
+ <li>[% terms.Bugzilla %] should no longer break lines unnecessarily in
+ email subjects. This was causing trouble with some email clients.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=374424">[% terms.Bug %] 374424</a>)</li>
+
+ <li>If you had selected "I'm added to or removed from this capacity" option
+ for the "CC" role in your email preferences, you wouldn't get mail when
+ more than one person was added to the CC list at once.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=394796">[% terms.Bug %] 394796</a>)</li>
+
+ <li>Deleting a user account no longer deletes whines from another user who
+ has the deleted account as addressee. The schedule is simply removed,
+ but the whine itself is left intact.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=395924">[% terms.Bug %] 395924</a>)</li>
+
+ <li><kbd>contrib/merge-users.pl</kbd> now correctly merges all required
+ fields when merging two user accounts.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=400160">[% terms.Bug %] 400160</a>)</li>
+
+ <li>[% terms.Bugzilla %] no longer requires Apache::DBI to run under
+ mod_perl. It caused troubles such as lost connections with the DB and
+ didn't give any important performance gain.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=408766">[% terms.Bug %] 408766</a>)</li>
+</ul>
+
+<h3>3.0.2</h3>
+
+<ul>
+ <li>[% terms.Bugzilla %] should now work on Perl 5.9.5 (and thus the
+ upcoming Perl 5.10.0).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=390442">[% terms.Bug %] 390442</a>)</li>
+</ul>
+
+<p>See also the <a href="#v30_security">Security Advisory</a> section for
+ information about an important security issue fixed in this release.</p>
+
+<h3>3.0.1</h3>
+
+<ul>
+ <li>For users of Firefox 2, the <code>show_bug.cgi</code> user interface
+ should no longer "collapse" after you modify [% terms.abug %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370739">[% terms.Bug %] 370739</a>)</li>
+ <li>If you can bless a group, and you share a saved search with that
+ group, it will no longer automatically appear in all of that group's
+ footers unless you specifically request that it automatically appear
+ in their footers.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365890">[% terms.Bug %] 365890</a>)</li>
+ <li>There is now a parameter to allow users to perform searches without
+ any search terms. (In other words, to search for just a Product
+ and Status on the Simple Search page.) The parameter is called
+ <code>specific_search_allow_empty_words</code>.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=385910">[% terms.Bug %] 385910</a>)</li>
+ <li>If you attach a file that has a MIME-type of <code>text/x-patch</code>
+ or <code>text/x-diff</code>, it will automatically be treated as a
+ patch by [% terms.Bugzilla %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365756">[% terms.Bug %] 365756</a>)</li>
+ <li>Dependency Graphs now work correctly on all mod_perl installations.
+ There should now be no remaining signficant problems with running
+ [%+ terms.Bugzilla %] under mod_perl.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370398">[% terms.Bug %] 370398</a>)</li>
+ <li>If moving [% terms.abug %] between products would remove groups
+ from the [% terms.bug %], you are now warned.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=303183">[% terms.Bug %] 303183</a>)</li>
+ <li>On IIS, whenever [% terms.Bugzilla %] threw a warning, it would
+ actually appear on the web page. Now warnings are suppressed,
+ unless you have a file in the <code>data</code> directory called
+ <code>errorlog</code>, in which case warnings will be printed there.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=390148">[% terms.Bug %] 390148</a>)</li>
+ <li>If you used <kbd>email_in.pl</kbd> to edit [% terms.abug %] that was
+ protected by groups, all of the groups would be cleared.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=385453">[% terms.Bug %] 385453</a>)</li>
+ <li>PostgreSQL users: New Charts were failing to collect data over time.
+ They will now start collecting data correctly.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=257351">[% terms.Bug %] 257351</a>)</li>
+ <li>Some flag mails didn't specify who the requestee was.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=379787">[% terms.Bug %] 379787</a>)</li>
+ <li>Instead of throwing real errors, <kbd>collectstats.pl</kbd> would
+ just say that it couldn't find <code>ThrowUserError</code>.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=380709">[% terms.Bug %] 380709</a>)</li>
+ <li>Logging into [% terms.Bugzilla %] from the home page works again
+ with IIS5.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=364008">[% terms.Bug %] 364008</a>)</li>
+ <li>If you were using SMTP for sending email, sometimes emails would
+ be missing the <code>Date</code> header.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=304999">[% terms.Bug %] 304999</a>).</li>
+ <li>In the XML-RPC WebService, <code>B<!-- -->ug.legal_values</code> now
+ correctly returns values for custom fields if you request values
+ for custom fields.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=381737">[% terms.Bug %] 381737</a>)</li>
+ <li>The "[% terms.Bug %]-Writing Guidelines" page has been shortened
+ and re-written.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=378590">[% terms.Bug %] 378590</a>)</li>
+ <li>If your <code>urlbase</code> parameter included a port number,
+ like <code>www.domain.com:8080</code>, SMTP might have failed.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=384501">[% terms.Bug %] 384501</a>)</li>
+ <li>For SMTP users, there is a new parameter, <code>smtp_debug</code>.
+ Turning on this parameter will log the full information about
+ every SMTP session to your web server's error log, to help with
+ debugging issues with SMTP.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=384497">[% terms.Bug %] 384497</a>)</li>
+ <li>If you are a "global watcher" (you get all mails from every
+ [%+ terms.bug %]), you can now see that in your Email Preferences.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=365302">[% terms.Bug %] 365302</a>)</li>
+ <li>The Status and Resolution of [% terms.bugs %] are now correctly
+ localized in CSV search results.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=389517">[% terms.Bug %] 389517</a>)</li>
+ <li>The "Subject" line of an email was being mangled if it contained
+ non-Latin characters.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=387860">[% terms.Bug %] 387860</a>)</li>
+ <li>Editing the "languages" parameter using <kbd>editparams.cgi</kbd> would
+ sometimes fail, causing [% terms.Bugzilla %] to throw an error.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=335354">[% terms.Bug %] 335354</a>)</li>
+</ul>
+
+<h2 id="v30_req">Minimum Requirements</h2>
+
+<p>Any requirements that are new since 2.22 will look like
+ <span class="req_new">this</span>.</p>
+
+<ul>
+ <li><a href="#v30_req_perl">Perl</a></li>
+ <li><a href="#v30_req_mysql">For MySQL Users</a></li>
+ <li><a href="#v30_req_pg">For PostgreSQL Users</a></li>
+ <li><a href="#v30_req_modules">Required Perl Modules</a></li>
+ <li><a href="#v30_req_optional_mod">Optional Perl
+ Modules</a></li>
+</ul>
+
+
+<h3 id="v30_req_perl">Perl</h3>
+
+<ul>
+ <li>Perl <span class="req_new">v<strong>5.8.0</strong></span> (non-Windows
+ platforms)</li>
+ <li>Perl v<strong>5.8.1</strong> (Windows platforms)</li>
+</ul>
+
+<h3 id="v30_req_mysql">For MySQL Users</h3>
+
+<ul>
+ <li>MySQL <span class="req_new">v4.1.2</span></li>
+ <li><strong>perl module:</strong> DBD::mysql v2.9003</li>
+</ul>
+
+<h3 id="v30_req_pg">For PostgreSQL Users</h3>
+
+<ul>
+ <li>PostgreSQL v8.00.0000</li>
+ <li><strong>perl module:</strong> DBD::Pg v1.45</li>
+</ul>
+
+<h3 id="v30_req_modules">Required Perl Modules</h3>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ </tr>
+ <tr><td>CGI</td> <td>2.93</td>
+ </tr>
+ <tr>
+ <td>Date::Format</td> <td>2.21</td>
+ </tr>
+ <tr>
+ <td>DBI</td>
+ <td class="req_new">1.41</td>
+ </tr>
+ <tr>
+ <td>File::Spec</td> <td>0.84</td>
+ </tr>
+ <tr>
+ <td>Template</td> <td>2.12</td>
+ </tr>
+ <tr>
+ <td class="req_new">Email::Send</td>
+ <td class="req_new">2.00</td>
+ </tr>
+ <tr>
+ <td>Email::MIME</td>
+ <td>1.861</td>
+ </tr>
+ <tr>
+ <td class="req_new">Email::MIME::Modifier</td>
+ <td class="req_new">1.442</td>
+ </tr>
+</table>
+
+<h3 id="v30_req_optional_mod">Optional Perl Modules</h3>
+
+<p>The following perl modules, if installed, enable various
+ features of [% terms.Bugzilla %]:</p>
+
+<table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ <th>Enables Feature</th>
+ </tr>
+ <tr>
+ <td class="req_new">LWP::UserAgent</td>
+ <td class="req_new">(Any)</td>
+ <td>Automatic Update Notifications</td>
+ </tr>
+ <tr>
+ <td>Template::Plugin::GD::Image</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD::Graph</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD::Text</td>
+ <td>(Any)</td>
+ <td>Graphical Reports</td>
+ </tr>
+ <tr>
+ <td>GD</td>
+ <td>1.20</td>
+ <td>Graphical Reports, New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td class="req_new">Email::MIME::Attachment::Stripper</td>
+ <td class="req_new">(Any)</td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td class="req_new">Email::Reply</td>
+ <td class="req_new">(Any)</td>
+ <td>Inbound Email</td>
+ </tr>
+ <tr>
+ <td>Net::LDAP</td>
+ <td>(Any)</td>
+ <td>LDAP Authentication</td>
+ </tr>
+ <tr>
+ <td>HTML::Parser</td>
+ <td>3.40</td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td>HTML::Scrubber</td>
+ <td>(Any)</td>
+ <td>More HTML in Product/Group Descriptions</td>
+ </tr>
+ <tr>
+ <td>XML::Twig</td>
+ <td>(Any)</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td>MIME::Parser</td>
+ <td>5.406</td>
+ <td>Move [% terms.Bugs %] Between Installations</td>
+ </tr>
+ <tr>
+ <td>Chart::Base</td>
+ <td>1.0</td>
+ <td>New Charts, Old Charts</td>
+ </tr>
+ <tr>
+ <td>Image::Magick</td>
+ <td>(Any)</td>
+ <td>Optionally Convert BMP Attachments to PNGs</td>
+ </tr>
+ <tr>
+ <td>PatchReader</td>
+ <td>0.9.4</td>
+ <td>Patch Viewer</td>
+ </tr>
+ <tr>
+ <td class="req_new">SOAP::Lite</td>
+ <td class="req_new">(Any)</td>
+ <td>XML-RPC Interface</td>
+ </tr>
+ <tr>
+ <td class="req_new">mod_perl2</td>
+ <td class="req_new">1.999022</td>
+ <td>mod_perl</td>
+ </tr>
+ <tr>
+ <td> CGI</td>
+ <td>3.11</td>
+ <td>mod_perl</td>
+ </tr>
+</table>
+
+<h2 id="v30_feat">New Features and Improvements</h2>
+
+<ul>
+ <li><a href="#v30_feat_cf">Custom Fields</a></li>
+ <li><a href="#v30_feat_mp">mod_perl Support</a></li>
+ <li><a href="#v30_feat_sq">Shared Saved Searches</a></li>
+ <li>
+ <a href="#v30_feat_afn">Attachments and Flags on New [% terms.Bugs %]</a>
+ </li>
+ <li><a href="#v30_feat_cr">Custom Resolutions</a></li>
+ <li><a href="#v30_feat_ppp">Per-Product Permissions</a></li>
+ <li><a href="#v30_feat_ui">User Interface Improvements</a></li>
+ <li><a href="#v30_feat_xml">XML-RPC Interface</a></li>
+ <li><a href="#v30_feat_skin">Skins</a></li>
+ <li><a href="#v30_feat_sbu">Unchangeable Fields Appear
+ Unchangeable</a></li>
+ <li><a href="#v30_feat_et">All Emails in Templates</a></li>
+ <li><a href="#v30_feat_df">No More Double-Filed [% terms.Bugs %]</a></li>
+ <li><a href="#v30_feat_cc">Default CC List for Components</a></li>
+ <li><a href="#v30_feat_emi">File/Modify [% terms.Bugs %] By Email</a></li>
+ <li><a href="#v30_feat_gw">Users Who Get All [% terms.Bug %]
+ Notifications</a></li>
+ <li><a href="#v30_feat_utf8">Improved UTF-8 Support</a></li>
+ <li><a href="#v30_feat_upda">Automatic Update Notification</a></li>
+ <li><a href="#v30_feat_welc">Welcome Page for New Installs</a></li>
+ <li><a href="#v30_feat_other">Other Enhancements and Changes</a></li>
+</ul>
+
+<h3 id="v30_feat_cf">Custom Fields</h3>
+
+<p>[% terms.Bugzilla %] now includes very basic support for custom fields.</p>
+
+<p>Users in the <kbd>admin</kbd> group can add plain-text or drop-down
+ custom fields. You can edit the values available for drop-down fields
+ using the "Field Values" control panel.</p>
+
+<p>Don't add too many custom fields! It can make [% terms.Bugzilla %]
+ very difficult to use. Try your best to get along with the default
+ fields, and then if you find that you can't live without custom fields
+ after a few weeks of using [% terms.Bugzilla %], only then should you
+ start your custom fields.</p>
+
+<h3 id="v30_feat_mp">mod_perl Support</h3>
+
+<p>[% terms.Bugzilla %] 3.0 supports mod_perl, which allows for extremely
+ enhanced page-load performance. mod_perl trades memory usage for performance,
+ allowing near-instantaneous page loads, but using much more memory.</p>
+
+<p>If you want to enable mod_perl for your [% terms.Bugzilla %], we recommend
+ a minimum of 1.5GB of RAM, and for a site with heavy traffic, 4GB to 8GB.</p>
+
+<p>If performance isn't that critical on your installation, you don't
+ have the memory, or you are running some other web server than
+ Apache, [% terms.Bugzilla %] still runs perfectly as a normal CGI
+ application, as well.</p>
+
+<h3 id="v30_feat_sq">Shared Saved Searches</h3>
+
+<p>Users can now choose to "share" their saved searches
+ with a certain group. That group will then be able to
+ "subscribe" to those searches, and have them appear
+ in their footer.</p>
+
+<p>If the sharer can "bless" the group he's sharing to,
+ (that is, if he can add users to that group), it's considered
+ that he's a manager of that group, and his queries show up
+ automatically in that group's footer (although they can
+ unsubscribe from any particular search, if they want.)</p>
+
+<p>In order to allow a user to share their queries, they also
+ have to be a member of the group specified in the
+ <code>querysharegroup</code> parameter.</p>
+
+<p>Users can control their shared and subscribed queries from
+ the "Preferences" screen.</p>
+
+<h3 id="v30_feat_afn">Attachments and Flags on New [% terms.Bugs %]</h3>
+
+<p>You can now add an attachment while you are filing a new
+ [%+ terms.bug %].</p>
+
+<p>You can also set flags on the [% terms.bug %] and on attachments, while
+ filing a new [% terms.bug %].</p>
+
+<h3 id="v30_feat_cr">Custom Resolutions</h3>
+
+<p>You can now customize the list of resolutions available
+ in [% terms.Bugzilla %], including renaming the default resolutions.</p>
+
+<p>The resolutions <code>FIXED</code>, <code>DUPLICATE</code>
+ and <code>MOVED</code> have a special meaning to [% terms.Bugzilla %],
+ though, and cannot be renamed or deleted.</p>
+
+<h3 id="v30_feat_ppp">Per-Product Permissions</h3>
+
+<p>You can now grant users <kbd>editbugs</kbd> and <kbd>canconfirm</kbd>
+ for only certain products. You can also grant users <kbd>editcomponents</kbd>
+ on a product, which means they will be able to edit that product
+ including adding/removing components and other product-specific
+ controls.</p>
+
+<h3 id="v30_feat_ui">User Interface Improvements</h3>
+
+<p>There has been some work on the user interface for [% terms.Bugzilla %] 3.0,
+ including:</p>
+
+<ul>
+ <li>There is now navigation and a search box a the <em>top</em> of
+ each page, in addition to the bar at the bottom of the page.</li>
+ <li>A re-designed "Format for Printing" page for
+ [%+ terms.bugs %].</li>
+ <li>The layout of <kbd>show_bug.cgi</kbd> (the [% terms.bug %] editing
+ page) has been changed, and the attachment table has been redesigned.</li>
+</ul>
+
+<h3 id="v30_feat_xml">XML-RPC Interface</h3>
+
+<p>[% terms.Bugzilla %] now has a Web Services interface using the XML-RPC
+ protocol. It can be accessed by external applications by going
+ to the <kbd>xmlrpc.cgi</kbd> on your installation.</p>
+
+<p>Documentation can be found in the
+ <a href="[% docs_urlbase FILTER html %]api/">[% terms.Bugzilla %]
+ API Docs</a>, in the various <kbd>Bugzilla::WebService</kbd> modules.</p>
+
+<h3 id="v30_feat_skin">Skins</h3>
+
+<p>[% terms.Bugzilla %] can have multiple "skins" installed,
+ and users can pick between them. To write a skin, you just have to
+ write several CSS files. See the <a href="[% docs_urlbase FILTER html %]cust-skins.html">Custom
+ Skins Documentation</a> for more details.</p>
+
+<p>We currently don't have any alternate skins shipping with
+ [%+ terms.Bugzilla %]. If you write an alternate skin, please
+ let us know!</p>
+
+<h3 id="v30_feat_sbu">Unchangeable Fields Appear Unchangeable</h3>
+
+<p>As long as you are logged in, when viewing [% terms.abug %], if you
+ cannot change a field, it will not look like you can change it. That
+ is, the value will just appear as plain text.</p>
+
+<h3 id="v30_feat_et">All Emails in Templates</h3>
+
+<p>All outbound emails are now controlled by the templating system.
+ What used to be the <code>passwordmail</code>, <code>whinemail</code>,
+ <code>newchangedmail</code> and <code>voteremovedmail</code>
+ parameters are now all templates in the <kbd>template/</kbd> directory.</p>
+
+<p>This means that it's now much easier to customize your outbound
+ emails, and it's also possible for localizers to have more
+ localized emails as part of their language packs, if they want.</p>
+
+<p>We also added a <code>mailfrom</code> parameter to let you set
+ who shows up in the <code>From</code> field on all emails that
+ [%+ terms.Bugzilla %] sends.</p>
+
+<h3 id="v30_feat_df">No More Double-Filed [% terms.Bugs %]</h3>
+
+<p>Users of [% terms.Bugzilla %] will sometimes accidentally submit
+ [%+ terms.abug %] twice, either by going back in their web browser,
+ or just by refreshing a page. In the past, this could file the same
+ [%+ terms.bug %] twice (or even three times) in a row, irritating
+ developers and confusing users.</p>
+
+<p>Now, if you try to submit [% terms.abug %] twice from the same screen
+ (by going back or by refreshing the page), [% terms.Bugzilla %] will warn
+ you about what you're doing, before it actually submits the duplicate
+ [%+ terms.bug %].</p>
+
+<h3 id="v30_feat_cc">Default CC List for Components</h3>
+
+<p>You can specify a list of users who will <em>always</em> be added to
+ the CC list of new [% terms.bugs %] in a component.</p>
+
+<h3 id="v30_feat_emi">File/Modify [% terms.Bugs %] By Email</h3>
+
+<p>You can now file or modify [% terms.bugs %] via email. Previous versions
+ of [% terms.Bugzilla %] included this feature only as an
+ unsupported add-on, but it is now an official interface to
+ [%+ terms.Bugzilla %].</p>
+
+<p>For more details see the <a href="[% docs_urlbase FILTER html %]api/email_in.html">documentation
+ for email_in.pl</a>.</p>
+
+<h3 id="v30_feat_gw">Users Who Get All [% terms.Bug %] Notifications</h3>
+
+<p>There is now a parameter called <kbd>globalwatchers</kbd>. This
+ is a comma-separated list of [% terms.Bugzilla %] users who will
+ get all [% terms.bug %] notifications generated by [% terms.Bugzilla %].</p>
+
+<p>Group controls still apply, though, so users who can't see [% terms.abug %]
+ still won't get notifications about that [% terms.bug %].</p>
+
+<h3 id="v30_feat_utf8">Improved UTF-8 Support</h3>
+
+<p>[% terms.Bugzilla %] users running MySQL should now have excellent
+ UTF-8 support if they turn on the <kbd>utf8</kbd> parameter. (New
+ installs have this parameter on by default.) [% terms.Bugzilla %]
+ now correctly supports searching and sorting in non-English languages,
+ including multi-bytes languages such as Chinese.</p>
+
+<h3 id="v30_feat_upda">Automatic Update Notification</h3>
+
+<p>If you belong to the <kbd>admin</kbd> group, you will be notified
+ when you log in if there is a new release of [% terms.Bugzilla %]
+ available to download.</p>
+
+<p>You can control these notifications by changing the
+ <kbd>upgrade_notification</kbd> parameter.</p>
+
+<p>If your [% terms.Bugzilla %] installation is on a machine that needs to go
+ through a proxy to access the web, you may also have to set the
+ <kbd>proxy_url</kbd> parameter.</p>
+
+<h3 id="v30_feat_welc">Welcome Page for New Installs</h3>
+
+<p>When you log in for the first time on a brand-new [% terms.Bugzilla %]
+ installation, you will be presented with a page that describes
+ where you should go from here, and what parameters you should set.</p>
+
+<h3 id="v30_feat_qs">QuickSearch Plugin for IE7 and Firefox 2</h3>
+
+<p>Firefox 2 users and Internet Explorer 7 users will be presented
+ with the option to add [% terms.Bugzilla %] to their search bar.
+ This uses the
+ <a href="page.cgi?id=quicksearch.html">QuickSearch syntax</a>.</p>
+
+<h3 id="v30_feat_other">Other Enhancements and Changes</h3>
+
+<p>These are either minor enhancements, or enhancements that have
+ very short descriptions. Some of these are very useful, though!</p>
+
+<h4>Enhancements That Affect [% terms.Bugzilla %] Users</h4>
+
+<ul>
+ <li>In comments, quoted text (lines that start with <kbd>></kbd>)
+ will be a different color from normal text.</li>
+ <li>There is now a user preference that will add you to the CC list
+ of any [% terms.bug %] you modify. Note that it's <strong>on</strong>
+ by default.</li>
+ <li>[% terms.Bugs %] can now be filed with an initial state of
+ <kbd>ASSIGNED</kbd>, if you are in the <kbd>editbugs</kbd> group.</li>
+ <li>By default, comment fields will zoom large when you are typing in them,
+ and become small when you move out of them. You can disable this
+ in your user preferences.</li>
+ <li>You can hide obsolete attachments on [% terms.abug %] by clicking
+ "Hide Obsolete" at the bottom of the attachment table.</li>
+ <li>If [% terms.abug %] has flags set, and you move it to a different
+ product that has flags with the same name, the flags will be
+ preserved.</li>
+ <li>You now can't request a flag to be set by somebody who can't set it
+ ([% terms.Bugzilla %] will throw an error if you try).</li>
+ <li>Many new headers have been added to outbound [% terms.Bugzilla %]
+ [%+ terms.bug %] emails: <code>X-Bugzilla-Status</code>,
+ <code>X-Bugzilla-Priority</code>, <code>X-Bugzilla-Assigned-To</code>,
+ <code>X-Bugzilla-Target-Milestone</code>, and
+ <code>X-Bugzilla-Changed-Fields</code>, <code>X-Bugzilla-Who</code>.
+ You can look at an email to get an idea of what they contain.</li>
+ <li>In addition to the old <code>X-Bugzilla-Reason</code> email header
+ which tells you why you got an email, if you got an email because
+ you were watching somebody, there is now an
+ <code>X-Bugzilla-Watch-Reason</code> header that tells you who you
+ were watching and what role they had.</li>
+ <li>If you hover your mouse over a full URL (like
+ <code>http://bugs.mycompany.com/show_bug.cgi?id=1212</code>) that
+ links to [% terms.abug %], you will see the title of the
+ [%+ terms.bug %]. Of course, this only works for [% terms.bugs %] in your
+ [%+ terms.Bugzilla %] installation.</li>
+ <li>If your installation has user watching enabled, you will now see
+ the users that you can remove from your watch-list as a multi-select
+ box, much like the current CC list. (Previously it was just a text
+ box.)</li>
+ <li>When a user creates their own account in [% terms.Bugzilla %], the
+ account is now not actually created until they verify their email
+ address by clicking on a link that is emailed to them.</li>
+ <li>You can change [% terms.abug %]'s resolution without reopening it.</li>
+ <li>When you view the dependency tree on [% terms.abug %], resolved
+ [%+ terms.bugs %] will be hidden by default. (In previous versions,
+ resolved [% terms.bugs %] were shown by default.)</li>
+ <li>When viewing [% terms.bug %] activity, fields that hold [% terms.bug %]
+ numbers (such as "Blocks") will have the [% terms.bug %] numbers
+ displayed as links to those [% terms.bugs %].</li>
+ <li>When viewing the "Keywords" field in [% terms.abug %] list,
+ it will be sorted alphabetically, so you can sanely sort a list on
+ that field.</li>
+ <li>In most places, the Version field is now sorted using a version-sort
+ (so 1.10 is greater than 1.2) instead of an alphabetical sort.</li>
+ <li>Options for flags will only appear if you can set them. So, for
+ example, if you can't grant <kbd>+</kbd> on a flag, that option
+ won't appear for you.</li>
+ <li>You can limit the product-related output of <kbd>config.cgi</kbd>
+ by specifying a <kbd>product=</kbd> URL argument, containing the name
+ of a product. You can specify the argument more than once for multiple
+ products.</li>
+ <li>You can now search the boolean charts on whether or not a comment
+ is private.</li>
+</ul>
+
+<h4>Enhancements For Administrators</h4>
+
+<ul>
+ <li>Administrators can now delete attachments, making them disappear
+ entirely from [% terms.Bugzilla %].</li>
+ <li><kbd>sanitycheck.cgi</kbd> can now only be accessed by users
+ in the <kbd>editcomponents</kbd> group.</li>
+ <li>The "Field Values" control panel can now only be accessed
+ by users in the <kbd>admin</kbd> group. (Previously it was accessible
+ to anybody in the <kbd>editcomponents</kbd> group.)</li>
+ <li>There is a new parameter <kbd>announcehtml</kbd>, that will allow
+ you to enter some HTML that will be displayed at the top of every
+ page, as an announcement.</li>
+ <li>The <kbd>loginnetmask</kbd> parameter now defaults to 0 for new
+ installations, meaning that as long as somebody has the right
+ login cookie, they can log in from any IP address. This makes
+ life a lot easier for dial-up users or other users whose IP
+ changes a lot. This could be done because the login cookie is now
+ very random, and thus secure.</li>
+ <li>Classifications now have sortkeys, so they can be sorted in an
+ order that isn't alphabetical.</li>
+ <li>Authentication now supports LDAP over SSL (LDAPS) or TLS (using
+ the STARTLS command) in addition to plain LDAP.</li>
+ <li>LDAP users can have their LDAP username be their email address,
+ instead of having the LDAP <kbd>mail</kbd> attribute be their
+ email address. You may wish to set the <kbd>emailsuffix</kbd>
+ parameter if you do this.</li>
+ <li>Administrators can now see what has changed in a user account,
+ when using the "Users" control panel.</li>
+ <li><code>REMIND</code> and <code>LATER</code> are no longer part
+ of the default list of resolutions. Upgrading installations will
+ not be affected--they will still have these resolutions.</li>
+ <li><kbd>editbugs</kbd> is now the default for the <kbd>timetrackinggroup</kbd>
+ parameter, meaning that time-tracking will be on by default in a new
+ installation.</li>
+</ul>
+
+<h2 id="v30_issues">Outstanding Issues</h2>
+
+<ul>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=69621">
+ [%- terms.Bug %] 69621</a>: If you rename or remove a keyword that is
+ in use on [% terms.bugs %], you will need to rebuild the "keyword cache"
+ by running <a href="sanitycheck.cgi">sanitycheck.cgi</a> and choosing
+ the option to rebuild the cache when it asks. Otherwise keywords may
+ not show up properly in search results.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=99215">
+ [%- terms.Bug %] 99215</a>: Flags are not protected by "mid-air
+ collision" detection. Nor are any attachment changes.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=89822">
+ [%- terms.Bug %] 89822</a>: When changing multiple [% terms.bugs %] at
+ the same time, there is no "mid-air collision" protection.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276230">
+ [%- terms.Bug %] 276230</a>: The support for restricting access to
+ particular Categories of New Charts is not complete. You should treat
+ the 'chartgroup' Param as the only access mechanism available.<br>
+ However, charts migrated from Old Charts will be restricted to
+ the groups that are marked MANDATORY for the corresponding Product.
+ There is currently no way to change this restriction, and the
+ groupings will not be updated if the group configuration
+ for the Product changes.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=370370">
+ [%- terms.Bug %] 370370</a>: mod_perl support is currently not
+ working on Windows machines.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=361149">
+ [%- terms.Bug %] 361149</a>: If you are using Perl 5.8.0, you may
+ get a lot of warnings in your Apache error_log about "deprecated
+ pseudo-hashes." These are harmless--they are a b[%# fool test %]ug in
+ Perl 5.8.0. Perl 5.8.1 and later do not have this problem.</li>
+ <li>[% terms.Bugzilla %] 3.0rc1 allowed custom field column names in
+ the database to be mixed-case. [% terms.Bugzilla %] 3.0 only allows
+ lowercase column names. It will fix any column names that you have
+ made mixed-case, but if you have custom fields that previously were
+ mixed-case in any Saved Search, you will have to re-create that Saved
+ Search yourself.</li>
+</ul>
+
+<h2 id="v30_security">Security Updates in This Release</h2>
+
+<h3>3.0.6</h3>
+
+<p>[% terms.Bugzilla %] contains a minor security fix. For details, see the
+ <a href="http://www.bugzilla.org/security/2.20.6/">Security Advisory</a>.</p>
+
+<h3>3.0.5</h3>
+
+<p>[% terms.Bugzilla %] contains one security fix for
+ <a href="[% docs_urlbase FILTER html %]api/importxml.html">importxml.pl</a>.
+ For details, see the
+ <a href="http://www.bugzilla.org/security/2.22.4/">Security Advisory</a>.</p>
+
+<h3>3.0.4</h3>
+
+<p>[% terms.Bugzilla %] 3.0.4 contains three security fixes.
+ For details, see the
+ <a href="http://www.bugzilla.org/security/2.20.5/">Security Advisory</a>.</p>
+
+<h3>3.0.3</h3>
+
+<p>No security fixes in this release.</p>
+
+<h3>3.0.2</h3>
+
+<p>[% terms.Bugzilla %] 3.0.1 had an important security fix that is
+ critical for public installations with "requirelogin" turned on.
+ For details, see the
+ <a href="http://www.bugzilla.org/security/3.0.1/">Security Advisory</a></p>
+
+<h3>3.0.1</h3>
+
+<p>[% terms.Bugzilla %] 3.0 had three security issues that have been
+ fixed in this release: one minor information leak, one hole only
+ exploitable by an admin or using <code>email_in.pl</code>, and one in an
+ uncommonly-used template. For details, see the
+ <a href="http://www.bugzilla.org/security/2.20.4/">Security Advisory</a>.</p>
+
+<h2 id="v30_upgrading">How to Upgrade From An Older Version</h2>
+
+<h3 id="v30_upgrading_notes">Notes For Upgraders</h3>
+
+<ul>
+ <li>If you upgrade by CVS, there are several .cvsignore files
+ that are now in CVS instead of being locally created by
+ <kbd>checksetup.pl</kbd>. This means that you will have to
+ delete those files when CVS tells you there's a conflict, and
+ then run <kbd>cvs update</kbd> again.</li>
+ <li>In this version of [% terms.Bugzilla %], the Summary field
+ is now limited to 255 characters. When you upgrade, any Summary
+ longer than that will be truncated, and the old summary will be
+ preserved in a comment.</li>
+ <li>If you have the <kbd>utf8</kbd> parameter turned on, at some
+ point you will have to convert your database. <kbd>checksetup.pl</kbd>
+ will tell you when this is, and it will give you certain instructions
+ at that time, that you have to follow before you can complete
+ the upgrade. Don't do the conversion yourself manually--follow
+ the instructions of <kbd>checksetup.pl</kbd>.</li>
+ <li>If you ever ran 2.23.3, 2.23.4, or 3.0rc1, you will have to run
+ <kbd>./collectstats.pl --regenerate</kbd> at the command line, because
+ the data for your Old Charts is corrupted. This can take several days,
+ so you may only want to run it if you use Old Charts.</li>
+ <li>You should also read the Outstanding Issues sections of
+ <a href="#v30_previous">older release notes</a> if you are upgrading
+ from a version lower than 2.22.</li>
+</ul>
+
+<h3>Steps For Upgrading</h3>
+
+<p>Once you have read the notes above, see the
+ <a href="[% docs_urlbase FILTER html %]upgrade.html">Upgrading
+ documentation</a> for instructions on how to upgrade.</p>
+
+<h2 id="v30_code_changes">Code Changes Which May Affect Customizations</h2>
+
+<ul>
+ <li><a href="#v30_code_loc"><strong>Packagers:</strong> Location
+ Variables Have Moved</a></li>
+ <li><a href="#v30_code_hooks">Hooks!</a></li>
+ <li><a href="#v30_code_api">API Documentation</a></li>
+ <li><a href="#v30_code_globals">Elimination of globals.pl</a></li>
+ <li><a href="#v30_code_scope">Cleaned Up Variable Scoping Issues</a></li>
+ <li><a href="#v30_code_sql">No More SendSQL</a></li>
+ <li><a href="#v30_code_auth">Auth Re-write</a></li>
+ <li><a href="#v30_code_obj">Bugzilla::Object</a></li>
+ <li><a href="#v30_code_req">Bugzilla->request_cache</a></li>
+ <li><a href="#v30_code_other">Other Changes</a></li>
+</ul>
+
+<h3 id="v30_code_loc"><strong>Packagers:</strong> Location Variables
+ Have Moved</h3>
+
+<p>In previous versions of [% terms.Bugzilla %], <kbd>Bugzilla::Config</kbd>
+ held all the paths for different things, such as the path to localconfig
+ and the path to the <kbd>data/</kbd> directory.</p>
+
+<p>Now, all of this data is stored in a subroutine,
+ <kbd>Bugzilla::Constants::bz_locations</kbd>.</p>
+
+<p>Also, note that for mod_perl, <kbd>bz_locations</kbd> must return
+ <em>absolute</em> (not relative) paths. There is already code in that
+ subroutine to help you with this.</p>
+
+<h3 id="v30_code_hooks">Hooks!</h3>
+
+<p>[% terms.Bugzilla %] now supports a code hook mechanism. See the
+ documentation for
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Hook.html">Bugzilla::Hook</a>
+ for more details.</p>
+
+<p>This gives [% terms.Bugzilla %] very advanced plugin support. You can
+ hook templates, hook code, add new parameters, and use the XML-RPC
+ interface. So we'd like to see some [% terms.Bugzilla %] plugins
+ written! Let us know on the <a href="http://bugzilla.org/cgi-bin/mj_wwwusr?func=lists-long-full&extra=developers">developers@bugzilla.org</a>
+ mailing list if you write a plugin.</p>
+
+<p>If you need more hooks, please
+ <a href="http://www.bugzilla.org/developers/reporting_bugs.html">File a b<!-- -->ug</a>!</p>
+
+<h3 id="v30_code_api">API Documentation</h3>
+
+<p>[% terms.Bugzilla %] now ships with all of its perldoc built
+ as HTML. Go ahead and read the
+ <a href="[% docs_urlbase FILTER html %]api/">API Documentation</a>
+ for all of the [% terms.Bugzilla %] modules now! Even scripts like
+ <kbd>checksetup.pl</kbd> have HTML documentation.</p>
+
+<h3 id="v30_code_globals">Elimination of globals.pl</h3>
+
+<p>The old file <kbd>globals.pl</kbd> has been eliminated.
+ Its code is now in various modules. Each function went to the module
+ that was appropriate for it.</p>
+
+<p>Usually we filed [% terms.abug %] in
+ <a href="https://bugzilla.mozilla.org">bugzilla.mozilla.org</a> for
+ each function we moved. You can search there for the old name of
+ the function, and that should get you the information about what
+ it's called now and where it lives.</p>
+
+<h3 id="v30_code_scope">Cleaned Up Variable Scoping Issues</h3>
+
+<p>In normal perl, you can have code like this:</p>
+<pre>my $var = 0;
+sub y { $var++ }</pre>
+
+<p>However, under mod_perl that doesn't work. So variables are no
+ longer "shared" with subroutines--instead all variables
+ that a subroutine needs must be declared inside the subroutine itself.</p>
+
+<h3 id="v30_code_sql">No More SendSQL</h3>
+
+<p>The old <kbd>SendSQL</kbd> function and all of its companions are
+ <strong>gone</strong>. Instead, we now use DBI for all database
+ interaction.</p>
+
+<p>For more information about how to use
+ <a href="http://search.cpan.org/perldoc?DBI">DBI</a> with
+ [%+ terms.Bugzilla %], see the
+ <a href="http://www.bugzilla.org/docs/developer.html#sql-sendreceive">Developer's
+ Guide Section About DBI</a></p>
+
+<h3 id="v30_code_auth">Auth Re-write</h3>
+
+<p>The <kbd>Bugzilla::Auth</kbd> family of modules have been completely
+ re-written. For details on how the new structure of authentication,
+ read the
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Auth.html">Bugzilla::Auth
+ API docs</a>.</p>
+
+<p>It should be very easy to write new authentication plugins, now.</p>
+
+<h3 id="v30_code_obj">Bugzilla::Object</h3>
+
+<p>There is a new base class for most of our objects,
+ <a href="[% docs_urlbase FILTER html %]api/Bugzilla/Object.html">Bugzilla::Object</a>.
+ It makes it really easy to create new objects based on things that are
+ in the database.</p>
+
+<h3 id="v30_code_req">Bugzilla->request-cache</h3>
+
+<p><kbd>Bugzilla.pm</kbd> used to cache things like the database
+ connection in package-global variables (like <kbd>$_dbh</kbd>).
+ That doesn't work in mod_perl, so instead now there's a hash
+ that can be accessed through <code>Bugzilla->request_cache</code>
+ to store things for the rest of the current page request.</p>
+
+<p>You shouldn't access <code>Bugzilla->request_cache</code> directly,
+ but you should use it inside of <kbd>Bugzilla.pm</kbd> if you modify
+ that. The only time you should be accessing it directly is if you need
+ to reset one of the caches. Hash keys are always named after the function
+ that they cache, so to reset the template object, you'd do:
+ <code>delete Bugzilla->request_cache->{template};</code>.</p>
+
+<h3 id="v30_code_other">Other Changes</h3>
+
+<ul>
+ <li><kbd>checksetup.pl</kbd> has been completely re-written, and most
+ of its code moved into modules in the <kbd>Bugzilla::Install</kbd>
+ namespace. See the
+ <a href="[% docs_urlbase FILTER html %]api/checksetup.html">checksetup
+ documentation</a> and <a href="https://bugzilla.mozilla.org/showdependencytree.cgi?id=277502&hide_resolved=0">[% terms.Bugzilla %]
+ [%+ terms.bug %] 277502</a> for details.</li>
+ <li>Instead of <kbd>UserInGroup()</kbd>, all of [% terms.Bugzilla %] now
+ uses <kbd>Bugzilla->user->in_group</kbd></li>
+ <li>mod_perl doesn't like dependency loops in modules, so we now have
+ a test for that detects dependency loops in modules when you run
+ <kbd>runtests.pl</kbd>.</li>
+ <li><kbd>globals.pl</kbd> used to modify the environment variables,
+ like <kbd>PATH</kbd>. That now happens in <kbd>Bugzilla.pm</kbd>.</li>
+ <li>Templates can now link to the documentation more easily.
+ See the <kbd>global/code-error.html.tmpl</kbd> and
+ <kbd>global/user-error.html.tmpl</kbd> templates for examples.
+ (Search for "docslinks.")</li>
+ <li>Parameters are accessed through <kbd>Bugzilla->params</kbd>
+ instead of using the <kbd>Param()</kbd> function, now.</li>
+ <li>The variables from the <kbd>localconfig</kbd> file are accessed
+ through the <code>Bugzilla->localconfig</code> hash instead of through
+ <kbd>Bugzilla::Config</kbd>.</li>
+ <li><kbd>Bugzilla::BugMail::MessageToMTA()</kbd> has moved into its
+ own module, along with other mail-handling code, called
+ <kbd>Bugzilla::Mailer</kbd></li>
+ <li>The <kbd>CheckCanChangeField()</kbd> subroutine in
+ <kbd>process_bug.cgi</kbd> has been moved to <kbd>Bugzilla::Bug</kbd>,
+ and is now a method of [% terms.abug %] object.</li>
+ <li>The code that used to be in the <kbd>global/banner.html.tmpl</kbd>
+ template is now in <kbd>global/header.html.tmpl</kbd>. The banner
+ still exists, but the file is empty.</li>
+</ul>
+
+<h2 id="v30_previous">Release Notes For Previous Versions</h2>
+
+<p>Release notes for versions of [% terms.Bugzilla %] for versions
+ prior to 3.0 are only available in text format:
+ <a href="[% docs_urlbase FILTER remove('html/$') FILTER html %]rel_notes.txt">Release Notes for [% terms.Bugzilla %] 2.22
+ and Earlier</a>.</p>
+
+[% INCLUDE global/footer.html.tmpl %]
+
+[% BLOCK db_req %]
+ [% SET m = DB_MODULE.$db %]
+ <h3 id="v40_req_[% db FILTER html %]">For [% m.name FILTER html %] Users</h3>
+
+ <ul>
+ <li>[% m.name FILTER html %]
+ [%+ '<span class="req_new">' IF db_new %]v[% m.db_version FILTER html %]
+ [% '</span>' IF db_new %]
+ </li>
+ <li><strong>perl module:</strong>
+ [%+ m.dbd.module FILTER html %]
+ [% '<span class="req_new">' IF dbd_new %]v[% m.dbd.version FILTER html %]
+ [% '</span>' IF dbd_new %]</li>
+ </ul>
+[% END %]
+
+
+[% BLOCK req_table %]
+ <table class="req_table" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <th>Module</th> <th>Version</th>
+ [% IF include_feature %]
+ <th>Enables Feature</th>
+ [% END %]
+ </tr>
+ [% FOREACH req = reqs %]
+ <tr>
+ <td [% ' class="req_new"' IF new.contains(req.package) %]>
+ [%- req.module FILTER html %]</td>
+ <td [% ' class="req_new"' IF updated.contains(req.package)
+ OR new.contains(req.package) %]>
+ [%- IF req.version == 0 %]
+ (Any)
+ [% ELSE %]
+ [%- req.version FILTER html %]
+ [% END %]
+ </td>
+ [% IF include_feature %]
+ <td>[% req.feature.join(', ') FILTER html %]</td>
+ [% END %]
+ </tr>
+ [% END %]
+</table>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/pages/sudo.html.tmpl b/Websites/bugs.webkit.org/template/en/default/pages/sudo.html.tmpl
index dff2d7d..c790ff1 100644
--- a/Websites/bugs.webkit.org/template/en/default/pages/sudo.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/pages/sudo.html.tmpl
@@ -51,14 +51,14 @@
</p>
<p id="message">
- [% IF user.groups.bz_sudoers %]
+ [% IF user.in_group('bz_sudoers') %]
You are a member of the <b>bz_sudoers</b> group. You may use this
feature to impersonate others.
[% ELSE %]
You are not a member of an appropriate group. You may not use this
feature.
[% END %]
- [% IF user.groups.bz_sudo_protect %]
+ [% IF user.in_group('bz_sudo_protect') %]
<br>
You are a member of the <b>bz_sudo_protect</b> group. Other people will
not be able to use this feature to impersonate you.
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/chart.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/chart.html.tmpl
index 06a8d79..e14744d 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/chart.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/chart.html.tmpl
@@ -25,9 +25,11 @@
height = 350
%]
+[% time = time FILTER time('%Y-%m-%d %H:%M:%S') FILTER html %]
+
[% PROCESS global/header.html.tmpl
title = "Chart"
- header_addl_info = time2str("%Y-%m-%d %H:%M:%S", time)
+ header_addl_info = time
%]
<div align="center">
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/components.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/components.html.tmpl
index 351c7d0..ef7d5ae 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/components.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/components.html.tmpl
@@ -16,17 +16,22 @@
# Rights Reserved.
#
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
#%]
[%# INTERFACE:
- # product: object. The product for which we want to display component descriptions.
+ # product: object. The product for which we want to display component
+ # descriptions.
#%]
[% title = BLOCK %]
Components for [% product.name FILTER html %]
[% END %]
-[% PROCESS global/header.html.tmpl title = title %]
+[% PROCESS global/header.html.tmpl
+ style_urls = [ "skins/standard/reports.css" ]
+ title = title
+%]
[% IF Param("useqacontact") %]
[% numcols = 3 %]
@@ -34,27 +39,39 @@
[% numcols = 2 %]
[% END %]
-<p>
- [% product.description FILTER html_light %]
-</p>
-
-<table>
+<table cellpadding="0" cellspacing="0" id="components_header_table">
<tr>
- <th align="left">Component</th>
- <th align="left">Default Assignee</th>
+ <td class="instructions">
+ Select a component to see open [% terms.bugs %] in that component:
+ </td>
+ <td class="product_container">
+ <span class="product_name">[% product.name FILTER html %]</span>
+ <div class="product_desc">
+ [% product.description FILTER html_light %]
+ </div>
+ </td>
+ </tr>
+</table>
+
+<span class="components_header">Components</span>
+
+<table summary="Components table"
+ class="component_table" cellspacing="0" cellpadding="0">
+ <thead>
+ <tr>
+ <th> </th>
+ <th>Default Assignee</th>
[% IF Param("useqacontact") %]
- <th align="left">Default QA Contact</th>
+ <th>Default QA Contact</th>
[% END %]
</tr>
+ </thead>
+ <tbody>
[% FOREACH comp = product.components %]
[% INCLUDE describe_comp %]
[% END %]
- <tr>
- <td colspan="[% numcols %]">
- <hr>
- </td>
- </tr>
+ </tbody>
</table>
[% PROCESS global/footer.html.tmpl %]
@@ -64,28 +81,24 @@
[%############################################################################%]
[% BLOCK describe_comp %]
- <tr>
- <td colspan="[% numcols %]">
- <hr>
+ <tr id="[% comp.name FILTER html %]">
+ <td rowspan="2" class="component_name">
+ <a href="buglist.cgi?product=
+ [%- product.name FILTER uri %]&component=
+ [%- comp.name FILTER uri %]&resolution=---">
+ [% comp.name FILTER html %]</a>
</td>
- </tr>
- <tr>
- <td rowspan="2">
- <a name="[% comp.name FILTER html %]">[% comp.name FILTER html %]</a>
- </td>
- <td>
- <a href="mailto:[% comp.default_assignee.email FILTER html %]">
- [% comp.default_assignee.login FILTER html %]</a>
+ <td class="component_assignee">
+ [% INCLUDE global/user.html.tmpl who = comp.default_assignee %]
</td>
[% IF Param("useqacontact") %]
- <td>
- <a href="mailto:[% comp.default_qa_contact.email FILTER html %]">
- [% comp.default_qa_contact.login FILTER html %]</a>
+ <td class="component_qa_contact">
+ [% INCLUDE global/user.html.tmpl who = comp.default_qa_contact %]
</td>
[% END %]
</tr>
<tr>
- <td colspan="[% numcols - 1 %]">
+ <td colspan="[% numcols - 1 %]" class="component_description">
[% comp.description FILTER html_light %]
</td>
</tr>
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/create-chart.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/create-chart.html.tmpl
index d911d03..b7068ac 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/create-chart.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/create-chart.html.tmpl
@@ -50,6 +50,7 @@
i++;
}
+ namewidget.disabled = false;
namewidget.options[0].selected = true;
checkNewState();
@@ -98,23 +99,16 @@
<td>
<noscript>
<input type="submit" name="action-assemble" value="Update -->"
- id="action-assemble">
+ id="action-assemble2">
</noscript>
</td>
- <td align="left">
- <select name="name" id="name" style="width: 15em"
- size="5" multiple="multiple"
- [%+ "disabled=\"disabled\"" UNLESS name.keys.size %]>
- [% FOREACH x = name.keys.sort %]
- <option value="[% name.$x FILTER html %]">
- [% x FILTER html %]</option>
- [% END %]
- [% UNLESS name.keys.size %]
- <option value="" disabled="disabled"></option>
- [% END %]
- </select>
- </td>
+ [% PROCESS series_select sel = { name => 'name',
+ size => 5,
+ multiple => 1,
+ # We want to use the series ID as value,
+ # not its name.
+ value_in_hash => 1 } %]
<td align="center" valign="middle">
<input type="submit" name="action-add" value="Add To List"
@@ -124,17 +118,9 @@
[% END %]
</table>
- <script type="text/javascript">
- document.chartform.category[0].selected = true;
- document.chartform.subcategory.disabled = '';
- document.chartform.name.disabled = '';
- catSelected();
- subcatSelected();
- </script>
-
<h3>List Of Data Sets To Plot</h3>
- [% IF chart.lines.size > 0 %]
+ [% IF chart.lines.size %]
<table cellspacing="2" cellpadding="2">
<tr>
<th style="width: 5em;">Select</th>
@@ -187,14 +173,16 @@
</td>
<td align="center">
- [% IF user.id == series.creator OR user.in_group("admin") %]
+ [% IF user.id == series.creator_id OR user.in_group("admin") %]
<a href="chart.cgi?action=edit&series_id=
[% series.series_id %]">Edit</a> |
+ <a href="chart.cgi?action=confirm-delete&series_id=
+ [%- series.series_id %]">Delete</a> |
[% END %]
<a href="buglist.cgi?cmdtype=dorem&namedcmd=
- [% series.category FILTER url_quote %]%20/%20
- [% series.subcategory FILTER url_quote %]%20/%20
- [% series.name FILTER url_quote -%]&series_id=
+ [% series.category FILTER uri %]%20/%20
+ [% series.subcategory FILTER uri %]%20/%20
+ [% series.name FILTER uri -%]&series_id=
[% series.series_id %]&remaction=runseries">Run Search</a>
</td>
</tr>
@@ -260,7 +248,23 @@
</form>
[% IF user.in_group('editbugs') %]
- <h3><a href="query.cgi?format=create-series">Create New Data Set</a></h3>
+ <h3>Create New Data Set</h3>
+ <p>
+ You can either create a new data set based on one of your saved searches
+ or start with a clean slate.
+ </p>
+
+ <form action="chart.cgi" id="create_series" name="create_series" method="GET">
+ <input type="hidden" name="action" value="convert_search">
+ <label for="series_from_search">Based on:</label>
+ <select id="series_from_search" name="series_from_search">
+ <option value="">(Clean slate)</option>
+ [% FOREACH q = user.queries %]
+ <option value="[% q.name FILTER html %]">[% q.name FILTER html %]</option>
+ [% END %]
+ </select>
+ <input id="submit_create" type="submit" value="Create a new data set">
+ </form>
[% END %]
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/delete-series.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/delete-series.html.tmpl
new file mode 100644
index 0000000..a25cd1e
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/reports/delete-series.html.tmpl
@@ -0,0 +1,59 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Frédéric Buclin.
+ # Portions created by the Initial Developer are Copyright (C) 2009
+ # the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Frédéric Buclin <LpSolit@gmail.com>
+ #%]
+
+[% series_name = BLOCK %]
+ [% series.category FILTER html %] /
+ [%+ series.subcategory FILTER html %] /
+ [%+ series.name FILTER html %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl title = "Delete Series"
+ style_urls = ['skins/standard/admin.css'] %]
+
+<p>
+ You are going to completely remove the <b>[% series_name FILTER none %]</b> series
+ from the database. All data related to this series will be permanently deleted.
+</p>
+<p>
+ [% IF series.creator %]
+ This series has been created by <a href="mailto:[% series.creator.email FILTER html %]">
+ [% series.creator.email FILTER html %]</a>
+ [% ELSE %]
+ This series has been automatically created by [% terms.Bugzilla %]
+ [% END %]
+
+ [% IF series.public %]
+ and is public.
+ [% ELSIF series.creator %]
+ and is only visible by this user.
+ [% ELSE %]
+ and cannot be displayed by anybody.
+ [% END %]
+</p>
+
+<p class="areyoureallyreallysure">Are you sure you want to delete this series?</p>
+
+<p>
+ <a href="chart.cgi?action=delete&series_id=[% series.series_id FILTER html %]&token=
+ [%- issue_hash_token([series.id, series.name]) FILTER uri %]">Yes, delete</a> |
+ <a href="chart.cgi">No, go back to the charts page</a>
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/duplicates-simple.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/duplicates-simple.html.tmpl
index 61d0c6f..a085227 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/duplicates-simple.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/duplicates-simple.html.tmpl
@@ -15,7 +15,9 @@
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
- # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Contributor(s):
+ # Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
#%]
[%# INTERFACE:
@@ -24,20 +26,26 @@
[% PROCESS global/variables.none.tmpl %]
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
<html>
-
- [% IF product %]
- [% title = "Most Frequently Reported $terms.Bugs for $product" %]
+ [% IF product.size %]
+ [% title = BLOCK %]
+ Most Frequently Reported [% terms.Bugs %] for [% product.join(', ') FILTER html %]
+ [% END %]
[% ELSE %]
[% title = "Most Frequently Reported $terms.Bugs" %]
[% END%]
<head>
<title>[% title FILTER html %]</title>
+ <link href="[% 'skins/standard/global.css' FILTER mtime %]"
+ rel="stylesheet" type="text/css">
+ <link href="[% 'skins/standard/duplicates.css' FILTER mtime %]"
+ rel="stylesheet" type="text/css">
</head>
<body>
[% PROCESS "reports/duplicates-table.html.tmpl" %]
</body>
-
</html>
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/duplicates-table.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/duplicates-table.html.tmpl
index 5dbef21..a7abf07 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/duplicates-table.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/duplicates-table.html.tmpl
@@ -15,21 +15,16 @@
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
- # Contributor(s): Gervase Markham <gerv@gerv.net>
+ # Contributor(s):
+ # Gervase Markham <gerv@gerv.net>
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
#%]
[%# INTERFACE:
- # bugs: list of hashes. May be empty. Each hash has nine members:
- # id: integer. The bug number
+ # bugs: list of hashes. May be empty. Each hash has three members:
+ # bug: A Bugzilla::Bug object
# count: integer. The number of dupes
# delta: integer. The change in count in the last $changedsince days
- # component: string. The bug's component
- # bug_severity: string. The bug's severity.
- # op_sys: string. The bug's reported OS.
- # target_milestone: string. The bug's TM.
- # short_desc: string. The bug's summary.
- # bug_status: string. The bug's status.
- # resolution: string. The bug's resolution, if any.
#
# bug_ids: list of integers. May be empty. The IDs of the bugs in $bugs.
#
@@ -38,104 +33,89 @@
# maxrows: integer. Max number of rows to display.
# changedsince: integer. The number of days ago for the changedsince column.
# openonly: boolean. True if we are only showing open bugs.
- # query_products: list of strings. Restrict to these products only.
+ # product: array of strings. Restrict to these products only.
#%]
-[% PROCESS global/variables.none.tmpl %]
+[% PROCESS "global/field-descs.none.tmpl" %]
[%# *** Column Headers *** %]
-[% IF bug_ids.size > 0 %]
- <table border>
+[% SET columns = [
+ { name => "id", description => "$terms.Bug #" },
+ { name => "count", description => "Dupe<br>Count" },
+ { name => "delta",
+ description => "Change in last<br>$changedsince day(s)" },
+ { name => "component", description => field_descs.component },
+ { name => "bug_severity", description => field_descs.bug_severity },
+ { name => "op_sys", description => field_descs.op_sys },
+ { name => "target_milestone", description => field_descs.target_milestone },
+ { name => "short_desc", description => field_descs.short_desc },
+] %]
+
+[% SET base_args = [] %]
+[% FOREACH param = ['maxrows', 'openonly', 'format', 'sortvisible',
+ 'changedsince', 'product']
+%]
+ [% NEXT IF NOT ${param}.defined %]
+ [% FOREACH value = ${param} %]
+ [% filtered_value = value FILTER uri %]
+ [% base_args.push("$param=$filtered_value") %]
+ [% END %]
+[% END %]
+[% IF sortvisible %]
+ [% bug_ids_string = bug_ids.nsort.join(',') FILTER uri %]
+ [% base_args.push("bug_id=$bug_ids_string") %]
+[% END %]
+[% base_args_string = base_args.join('&') %]
+
+[% IF bugs.size %]
+ <table id="duplicates_table" cellpadding="0" cellspacing="0">
<thead>
- <tr bgcolor="#CCCCCC">
- [% FOREACH column = [ { name => "id", description => "$terms.Bug #" },
- { name => "count", description => "Dupe<br>Count" },
- { name => "delta",
- description => "Change in last<br>$changedsince day(s)" },
- { name => "component", description => "Component" },
- { name => "bug_severity", description => "Severity" },
- { name => "op_sys", description => "Op Sys" },
- { name => "target_milestone",
- description => "Target<br>Milestone" },
- { name => "short_desc", description => "Summary" } ]
- %]
-
- [%# Small hack to keep delta column out if we don't need it %]
- [% NEXT IF column.name == "delta" AND NOT dobefore %]
-
- <th>
- [% bug_ids_string = bug_ids.join(',') %]
- <a href="duplicates.cgi?sortby=[% column.name %]
- [% IF sortby == column.name %]
- [% "&reverse=1" IF NOT reverse %]
- [% ELSE %]
- [%-# Some columns start off reversed %]
- [% "&reverse=1" IF column.name.match('delta|count') %]
- [% END %]
- [% IF maxrows %]&maxrows=[% maxrows FILTER html %][% END %]
- [% IF changedsince %]&changedsince=[% changedsince FILTER html %][% END %]
- [% "&openonly=1" IF openonly %]
- [% FOREACH p = query_products %]&product=[% p FILTER html %][% END %]
- [% IF format %]&format=[% format FILTER html %][% END %]
- [% IF sortvisible %]&bug_id=[% bug_ids_string FILTER html %]&sortvisible=1[% END %]">
- [% column.description %]</a>
+ <tr>
+ [% FOREACH column = columns %]
+ [% IF column.name == sortby %]
+ [%# We add this to the column object so it doesn't affect future
+ # iterations of the loop.
+ #%]
+ [% column.reverse_sort = reverse ? 0 : 1 %]
+ [% END %]
+ <th class="[% column.name FILTER html %]">
+ <a href="duplicates.cgi?sortby=[% column.name FILTER uri %]
+ [% IF column.reverse_sort.defined %]
+ [%- %]&reverse=[% column.reverse_sort FILTER uri %]
+ [% END %]
+ [% IF base_args_string %]
+ [% "&$base_args_string" FILTER none %]
+ [% END %]"
+ >[% column.description FILTER none %]</a>
</th>
[% END %]
</tr>
</thead>
- [% IF NOT sortby %]
- [% sortby = "count"; reverse = "1" %]
- [% END %]
-
- [% IF sortby == "id" OR sortby == "count" OR sortby == "delta" %]
- [%# Numeric sort %]
- [% sortedbugs = bugs.nsort(sortby) %]
- [% ELSE %]
- [% sortedbugs = bugs.sort(sortby) %]
- [% END %]
-
- [% IF reverse %]
- [% bugs = sortedbugs.reverse %]
- [% ELSE %]
- [% bugs = sortedbugs %]
- [% END %]
-
[%# *** Buglist *** %]
+
<tbody>
-
- [%# We need to keep track of the bug IDs we are actually displaying, because
- # if the user decides to sort the visible list, we need to know what that
- # list actually is. %]
- [% vis_bug_ids = [] %]
-
- [% FOREACH bug = bugs %]
- [% LAST IF loop.index() >= maxrows %]
- [% vis_bug_ids.push(bug.id) %]
-
- <tr [% "class='resolved'" IF bug.resolution != "" %]>
- <td>
- <center>
- [% bug.id FILTER bug_link(bug.id) FILTER none %]
- </center>
+ [% FOREACH item = bugs %]
+ [% SET bug = item.bug %]
+ <tr [% " class='resolved'" IF NOT bug.isopened %]>
+ <td class="id">
+ [% bug.id FILTER bug_link(bug) FILTER none %]
</td>
-
- <td>
- <center>
- [% bug.count %]
- </center>
+ <td class="count">[% item.count FILTER html %]</td>
+ <td class="delta">[% item.delta FILTER html %]</td>
+ <td class="component">[% bug.component FILTER html %]</td>
+ <td class="bug_severity">
+ [%- display_value('bug_severity', bug.bug_severity) FILTER html %]
</td>
-
- [% IF dobefore %]
- <td><center>[% bug.delta %]</center></td>
- [% END %]
-
- <td>[% bug.component FILTER html %]</td>
- <td><center>[% bug.bug_severity FILTER html %]</center></td>
- <td><center>[% bug.op_sys FILTER html %]</center></td>
- <td><center>[% bug.target_milestone FILTER html %]</center></td>
- <td>[% bug.short_desc FILTER html %]</td>
+ <td class="op_sys">
+ [%- display_value('op_sys', bug.op_sys) FILTER html %]
+ </td>
+ <td class="target_milestone">
+ [% display_value('target_milestone',
+ bug.target_milestone) FILTER html %]
+ </td>
+ <td class="short_desc">[% bug.short_desc FILTER html %]</td>
</tr>
[% END %]
</tbody>
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/duplicates.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/duplicates.html.tmpl
index e4ea738..ff1c271 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/duplicates.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/duplicates.html.tmpl
@@ -19,14 +19,12 @@
#%]
[%# INTERFACE:
- # products: an array of product objects this user can see.
- #
# sortby: string. the column on which we are sorting the buglist.
# reverse: boolean. True if we are reversing the current sort.
# maxrows: integer. Max number of rows to display.
# changedsince: integer. The number of days ago for the changedsince column.
# openonly: boolean. True if we are only showing open bugs.
- # query_products: list of strings. The set of products we check for dups.
+ # product: array of strings. The set of products we check for dups.
#
# Additionally, you need to fulfill the interface to
# duplicates-table.html.tmpl.
@@ -34,9 +32,10 @@
[% PROCESS global/variables.none.tmpl %]
-[% IF query_products.size %]
+[% IF product.size %]
[% title = BLOCK %]
- Most Frequently Reported [% terms.Bugs %] for [% query_products.join(', ') FILTER html %]
+ Most Frequently Reported [% terms.Bugs %] for
+ [%+ product.join(', ') FILTER html %]
[% END %]
[% ELSE %]
[% title = "Most Frequently Reported $terms.Bugs" %]
@@ -44,7 +43,7 @@
[% PROCESS global/header.html.tmpl
title = title
- style = ".resolved { background-color: #d9d9d9; color: #000000; }"
+ style_urls = ['skins/standard/duplicates.css']
%]
<p>
@@ -57,27 +56,26 @@
[%# *** Parameters *** %]
-[% bug_ids_string = vis_bug_ids.join(',') %]
+[% bug_ids_string = bug_ids.join(',') %]
-<h3><a name="params">Change Parameters</a></h3>
+<h3 id="params">Change Parameters</h3>
<form method="get" action="duplicates.cgi">
<input type="hidden" name="sortby" value="[% sortby FILTER html %]">
- <input type="hidden" name="reverse" value="[% reverse %]">
- <input type="hidden" name="bug_id" value="[% bug_ids_string %]">
+ <input type="hidden" name="reverse" value="[% reverse FILTER html %]">
+ <input type="hidden" name="bug_id" value="[% bug_ids_string FILTER html %]">
<table>
<tr>
- <td>When sorting or restricting,
- work with:</td>
+ <td>When sorting or restricting, work with:</td>
<td>
<input type="radio" name="sortvisible" id="entirelist" value="0"
- [%+ "checked" IF NOT sortvisible %]>
+ [% ' checked="checked"' IF NOT sortvisible %]>
<label for="entirelist">
entire list
</label>
<br>
<input type="radio" name="sortvisible" id="visiblelist" value="1"
- [%+ "checked" IF sortvisible %]>
+ [% ' checked="checked"' IF sortvisible %]>
<label for="visiblelist">
currently visible list
</label>
@@ -85,9 +83,9 @@
<td rowspan="4" valign="top">Restrict to products:</td>
<td rowspan="4" valign="top">
<select name="product" size="5" multiple="multiple">
- [% FOREACH p = products %]
+ [% FOREACH p = user.get_selectable_products %]
<option name="[% p.name FILTER html %]"
- [% " selected" IF lsearch(query_products, p.name) != -1 %]
+ [% ' selected="selected"' IF product.contains(p.name) %]
>[% p.name FILTER html %]</option>
[% END %]
</select>
@@ -95,16 +93,20 @@
</tr>
<tr>
- <td>Max rows:</td>
+ <td><label for="maxrows">Max rows:</label></td>
<td>
- <input size="4" name="maxrows" value="[% maxrows %]">
+ <input size="4" name="maxrows" id="maxrows"
+ value="[% maxrows FILTER html %]">
</td>
</tr>
<tr>
- <td>Change column is change in the last:</td>
<td>
- <input size="4" name="changedsince" value="[% changedsince %]"> days
+ <label for="changedsince">Change column is change in the last:</label>
+ </td>
+ <td>
+ <input size="4" name="changedsince" id="changedsince"
+ value="[% changedsince FILTER html %]"> days
</td>
</tr>
@@ -116,7 +118,7 @@
</td>
<td>
<input type="checkbox" name="openonly" id="openonly" value="1"
- [%+ "checked" IF openonly %]>
+ [% ' checked="checked"' IF openonly %]>
</td>
</tr>
@@ -126,28 +128,27 @@
</form>
<form method="post" action="buglist.cgi">
- <input type="hidden" name="bug_id" value="[% bug_ids_string %]">
- <input type="hidden" name="order" value="Reuse same sort as last time">
+ <input type="hidden" name="bug_id" value="[% bug_ids_string FILTER html %]">
Or just give this to me as a <input type="submit" id="list"
- value="[% terms.bug %] list">.
+ value="[% terms.bug %] list">.
(Note: the order may not be the same.)
</form>
<hr>
-<b>
- <a name="explanation">What are "Most Frequently Reported [% terms.Bugs %]"?</a>
-</b>
+<h3 id="explanation">
+ What are "Most Frequently Reported [% terms.Bugs %]"?
+</h3>
-<blockquote>
- The Most Frequent [% terms.Bugs %] page lists the known open [% terms.bugs %] which
- are reported most frequently. It is
- automatically generated from the [% terms.Bugzilla %] database every 24 hours, by
+<p>
+ The Most Frequent [% terms.Bugs %] page lists the known open
+ [%+ terms.bugs %] which are reported most frequently,
counting the number of direct and indirect duplicates of [% terms.bugs %].
This information is provided in order to assist in minimizing
- the amount of duplicate [% terms.bugs %] entered into [% terms.Bugzilla %], which
- saves time for Quality Assurance engineers who have to triage the [% terms.bugs %].
-</blockquote>
+ the amount of duplicate [% terms.bugs %] entered into [% terms.Bugzilla %],
+ which saves time for Quality Assurance engineers who have to triage
+ the [% terms.bugs %].
+</p>
<b>How do I use this list?</b>
@@ -166,11 +167,12 @@
<ul>
<li><a href="query.cgi">Try and locate a similar [% terms.bug %]</a>
- that has already been filed.</li>
+ that has already been filed.</li>
<li>If you find your [% terms.bug %] in [% terms.Bugzilla %],
- feel free to comment with any new or additional data you may have.</li>
- <li>If you cannot find your problem already documented in [% terms.Bugzilla %],
- <a href="enter_bug.cgi">file a new [% terms.bug %]</a>.</li>
+ feel free to comment with any new or additional data you may have.</li>
+ <li>If you cannot find your problem already documented in
+ [%+ terms.Bugzilla %],
+ <a href="enter_bug.cgi">file a new [% terms.bug %]</a>.</li>
</ul>
</ul>
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/edit-series.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/edit-series.html.tmpl
index 7fbdcbd..da7d15e 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/edit-series.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/edit-series.html.tmpl
@@ -40,6 +40,8 @@
[% PROCESS reports/series.html.tmpl
button_name = "Change Data Set" %]
<input type="hidden" name="action" value="alter">
+ <input type="hidden" name="token"
+ value="[% issue_hash_token([default.id, default.name]) FILTER html %]">
[% IF default.series_id %]
<input type="hidden" name="series_id" value="[% default.series_id %]">
@@ -48,9 +50,9 @@
<p>
<b>Creator</b>:
- [% IF creator.email %]
- <a href="mailto:[% creator.email FILTER html %]">
- [% creator.email FILTER html %]</a>
+ [% IF default.creator %]
+ <a href="mailto:[% default.creator.email FILTER html %]">
+ [% default.creator.email FILTER html %]</a>
[% ELSE %]
(automatically created by [% terms.Bugzilla %])
[% END %]
@@ -64,9 +66,9 @@
<a href="query.cgi?[% default.query FILTER html %]">View
series search parameters</a> |
<a href="buglist.cgi?cmdtype=dorem&namedcmd=
- [% default.category FILTER url_quote %]-
- [% default.subcategory FILTER url_quote %]-
- [% default.name FILTER url_quote %]&remaction=runseries&series_id=
+ [% default.category FILTER uri %]-
+ [% default.subcategory FILTER uri %]-
+ [% default.name FILTER uri %]&remaction=runseries&series_id=
[% default.series_id %]">Run series search</a>
</p>
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/keywords.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/keywords.html.tmpl
index 10e6573..045c6eb 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/keywords.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/keywords.html.tmpl
@@ -49,15 +49,14 @@
</tr>
[% END %]
- <tr>
+ <tr id="[% keyword.name FILTER html %]">
<th>
- <a name="[% keyword.name FILTER html %]">
- [% keyword.name FILTER html %]</a>
+ [% keyword.name FILTER html %]
</th>
<td>[% keyword.description FILTER html_light %]</td>
<td align="center">
[% IF keyword.bug_count > 0 %]
- <a href="buglist.cgi?keywords=[% keyword.name FILTER url_quote %]&resolution=---">
+ <a href="buglist.cgi?keywords=[% keyword.name FILTER uri %]&resolution=---">
Search</a>
[% ELSE %]
none
@@ -65,7 +64,7 @@
</td>
<td align="right">
[% IF keyword.bug_count > 0 %]
- <a href="buglist.cgi?keywords=[% keyword.name FILTER url_quote %]">
+ <a href="buglist.cgi?keywords=[% keyword.name FILTER uri %]">
[% keyword.bug_count %]</a>
[% ELSE %]
none
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/menu.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/menu.html.tmpl
index db5b192..5e19b12 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/menu.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/menu.html.tmpl
@@ -28,6 +28,7 @@
[% PROCESS global/header.html.tmpl
title = "Reporting and Charting Kitchen"
doc_section = "reporting.html"
+ style_urls = ['skins/standard/reports.css']
%]
<p>
@@ -38,38 +39,51 @@
<h2>Current State</h2>
<ul>
- <li>
+ <li id="report_search">
<strong><a href="query.cgi">Search</a></strong> -
list sets of [% terms.bugs %].
</li>
- <li>
+ <li id="report_tabular">
<strong>
<a href="query.cgi?format=report-table">Tabular reports</a>
</strong> -
tables of [% terms.bug %] counts in 1, 2 or 3 dimensions, as HTML or CSV.
</li>
- <li>
- <strong>
- <a href="query.cgi?format=report-graph">Graphical reports</a>
- </strong> -
- line graphs, bar and pie charts.
- </li>
-</ul>
-
-<h2>Change Over Time</h2>
-
-<ul>
- <li>
- <strong><a href="reports.cgi">Old Charts</a></strong> -
- plot the status and/or resolution of [% terms.bugs %] against
- time, for each product in your database.
- </li>
- [% IF user.in_group(Param("chartgroup")) %]
- <li>
- <strong><a href="chart.cgi">New Charts</a></strong> -
- plot any arbitrary search against time. Far more powerful.
+ [% IF feature_enabled('graphical_reports') %]
+ <li id="report_graphical">
+ <strong>
+ <a href="query.cgi?format=report-graph">Graphical reports</a>
+ </strong> -
+ line graphs, bar and pie charts.
</li>
[% END %]
+ <li id="report_duplicates">
+ <strong><a href="duplicates.cgi">Duplicates</a></strong> -
+ list of most frequently reported [% terms.bugs %].
+ </li>
+ [% Hook.process('current_state') %]
</ul>
+[% IF feature_enabled('new_charts') OR feature_enabled('old_charts') %]
+ <h2>Change Over Time</h2>
+
+ <ul>
+ [% IF feature_enabled('old_charts') %]
+ <li id="old_charts">
+ <strong><a href="reports.cgi">Old Charts</a></strong> -
+ plot the status and/or resolution of [% terms.bugs %] against
+ time, for each product in your database.
+ </li>
+ [% END %]
+ [% IF feature_enabled('new_charts') AND user.in_group(Param("chartgroup")) %]
+ <li id="new_charts">
+ <strong><a href="chart.cgi">New Charts</a></strong> -
+ plot any arbitrary search against time. Far more powerful.
+ </li>
+ [% END %]
+ </ul>
+[% END %]
+
+[% Hook.process('end') %]
+
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/old-charts.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/old-charts.html.tmpl
index ca3ba6c..4bdc0cf 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/old-charts.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/old-charts.html.tmpl
@@ -51,7 +51,7 @@
[%# We cannot use translated statuses and resolutions from field-descs.none.html
# because old charts do not distinguish statuses from resolutions. %]
[% FOREACH dataset = datasets %]
- <option value="[% dataset.value FILTER html %]:"
+ <option value="[% dataset.value FILTER html %]"
[% " selected=\"selected\"" IF dataset.selected %]>
[% dataset.value FILTER html %]</option>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/report-bar.png.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/report-bar.png.tmpl
index 74e2ca3..649fba4 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/report-bar.png.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/report-bar.png.tmpl
@@ -26,28 +26,12 @@
[% col_field_disp = field_descs.$col_field || col_field %]
-[% IF col_field == 'bug_status' %]
- [% FOR i IN [ 0 .. data.0.0.max ] %]
- [% data.0.0.$i = get_status(data.0.0.$i) %]
- [% END %]
+[% FOR i IN [ 0 .. data.0.0.max ] %]
+ [% data.0.0.$i = display_value(col_field, data.0.0.$i) %]
[% END %]
-[% IF col_field == 'resolution' %]
- [% FOR i IN [ 0 .. data.0.0.max ] %]
- [% data.0.0.$i = get_resolution(data.0.0.$i) %]
- [% END %]
-[% END %]
-
-[% IF row_field == 'bug_status' %]
- [% FOR i IN [ 0 .. row_names.max ] %]
- [% row_names.$i = get_status(row_names.$i) %]
- [% END %]
-[% END %]
-
-[% IF row_field == 'resolution' %]
- [% FOR i IN [ 0 .. row_names.max ] %]
- [% row_names.$i = get_resolution(row_names.$i) %]
- [% END %]
+[% FOR i IN [ 0 .. row_names.max ] %]
+ [% row_names.$i = display_value(row_field, row_names.$i) %]
[% END %]
[% FILTER null;
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/report-line.png.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/report-line.png.tmpl
index d4982bc..0edc0fe 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/report-line.png.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/report-line.png.tmpl
@@ -26,28 +26,12 @@
[% col_field_disp = field_descs.$col_field || col_field %]
-[% IF col_field == 'bug_status' %]
- [% FOR i IN [ 0 .. data.0.0.max ] %]
- [% data.0.0.$i = get_status(data.0.0.$i) %]
- [% END %]
+[% FOR i IN [ 0 .. data.0.0.max ] %]
+ [% data.0.0.$i = display_value(col_field, data.0.0.$i) %]
[% END %]
-[% IF col_field == 'resolution' %]
- [% FOR i IN [ 0 .. data.0.0.max ] %]
- [% data.0.0.$i = get_resolution(data.0.0.$i) %]
- [% END %]
-[% END %]
-
-[% IF row_field == 'bug_status' %]
- [% FOR i IN [ 0 .. row_names.max ] %]
- [% row_names.$i = get_status(row_names.$i) %]
- [% END %]
-[% END %]
-
-[% IF row_field == 'resolution' %]
- [% FOR i IN [ 0 .. row_names.max ] %]
- [% row_names.$i = get_resolution(row_names.$i) %]
- [% END %]
+[% FOR i IN [ 0 .. row_names.max ] %]
+ [% row_names.$i = display_value(row_field, row_names.$i) %]
[% END %]
[% IF cumulate %]
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/report-pie.png.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/report-pie.png.tmpl
index 342d9b7..27f5525 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/report-pie.png.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/report-pie.png.tmpl
@@ -22,16 +22,8 @@
[% col_field_disp = field_descs.$col_field || col_field %]
-[% IF col_field == 'bug_status' %]
- [% FOR i IN [ 0 .. data.0.0.max ] %]
- [% data.0.0.$i = get_status(data.0.0.$i) %]
- [% END %]
-[% END %]
-
-[% IF col_field == 'resolution' %]
- [% FOR i IN [ 0 .. data.0.0.max ] %]
- [% data.0.0.$i = get_resolution(data.0.0.$i) %]
- [% END %]
+[% FOR i IN [ 0 .. data.0.0.max ] %]
+ [% data.0.0.$i = display_value(col_field, data.0.0.$i) %]
[% END %]
[% FILTER null;
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/report-table.csv.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/report-table.csv.tmpl
index cf37749..4d8b50a 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/report-table.csv.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/report-table.csv.tmpl
@@ -30,7 +30,14 @@
[% row_field_disp = field_descs.$row_field || row_field %]
[% IF tbl_field %]
- [% tbl_field_disp FILTER csv %]: [% tbl FILTER csv %]
+ [% IF tbl_field == 'assigned_to' OR tbl_field == 'reporter'
+ OR tbl_field == 'qa_contact'
+ %]
+ [% tbl_disp = tbl FILTER email %]
+ [% ELSE %]
+ [% tbl_disp = tbl %]
+ [% END %]
+ [% tbl_field_disp FILTER csv %]: [% tbl_disp FILTER csv %]
[% END %]
[% IF row_field %]
[% row_field_disp FILTER csv %]
@@ -40,26 +47,14 @@
[% IF col_field -%]
[% FOREACH col = col_names -%]
[% colsepchar %]
- [% IF col_field == 'bug_status' %]
- [% get_status(col) FILTER csv -%]
- [% ELSIF col_field == 'resolution' %]
- [% get_resolution(col) FILTER csv -%]
- [% ELSE %]
- [% col FILTER csv -%]
- [% END %]
+ [% PROCESS value_display value = col field = col_field %]
[% END -%]
[% ELSE -%]
[% colsepchar %][% num_bugs FILTER csv %]
[% END %]
[% FOREACH row = row_names %]
- [% IF row_field == 'bug_status' %]
- [% get_status(row) FILTER csv -%]
- [% ELSIF row_field == 'resolution' %]
- [% get_resolution(row) FILTER csv -%]
- [% ELSE %]
- [% row FILTER csv -%]
- [% END %]
+ [% PROCESS value_display value = row field = row_field %]
[% FOREACH col = col_names %]
[% colsepchar %]
[% IF data.$tbl AND data.$tbl.$col AND data.$tbl.$col.$row %]
@@ -70,3 +65,13 @@
[% END %]
[% END %]
+
+[% BLOCK value_display %]
+ [% SET disp_value = display_value(field, value) %]
+ [% IF field == 'assigned_to' OR field == 'reporter'
+ OR field == 'qa_contact'
+ %]
+ [% disp_value = value FILTER email %]
+ [% END %]
+ [% disp_value FILTER csv %]
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/report-table.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/report-table.html.tmpl
index 0ebe631..8a3ab95 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/report-table.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/report-table.html.tmpl
@@ -17,6 +17,8 @@
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
# <rdean@cambianetworks.com>
+ # Frédéric Buclin <LpSolit@gmail.com>
+ # Guy Pyrzak <guy.parzak@gmail.com>
#%]
[%# INTERFACE:
@@ -34,17 +36,112 @@
[% col_field_disp = field_descs.$col_field || col_field %]
[% row_field_disp = field_descs.$row_field || row_field %]
-
+
+[% urlbase = BLOCK %]buglist.cgi?[% buglistbase FILTER html %][% END %]
[% IF tbl == "-total-" %]
- [% urlbase = BLOCK %]buglist.cgi?[% buglistbase FILTER html %]
- [% "&$tbl_vals" IF tbl_vals %][% END %]
-[% ELSE %]
- [% urlbase = BLOCK %]buglist.cgi?[% buglistbase FILTER html %]&
- [% tbl_field FILTER url_quote %]=[% tbl FILTER url_quote %][% END %]
+ [% IF tbl_vals %]
+ [% urlbase = urlbase _ "&" _ tbl_vals %]
+ [% END %]
+[% ELSIF tbl_field %]
+ [% urlbase = BLOCK %][% urlbase %]&[% tbl_field FILTER uri %]=[% tbl FILTER uri %][% END %]
[% END %]
+<script type="text/javascript">
+YAHOO.util.Event.addListener(window, "load", function() {
+ this.Linkify = function(elLiner, oRecord, oColumn, oData) {
+ if (oData == 0)
+ elLiner.innerHTML = ".";
+ else if (oRecord.getData("row_title") == "Total")
+ elLiner.innerHTML = "<a href='[% urlbase %]&[% col_field FILTER js %]="
+ + oColumn.field + "[% '&' _ row_vals IF row_vals %]'>"
+ + oData + "</a>";
+ else
+ elLiner.innerHTML = "<a href='[% urlbase %]&[% row_field FILTER js %]="
+ + oRecord.getData("row_title").replace(/\s+$/,"")
+ + "&[% col_field FILTER js %]=" + oColumn.field
+ + "'>" + oData + "</a>";
+ };
+
+ this.LinkifyTotal = function(elLiner, oRecord, oColumn, oData) {
+ if (oData == 0)
+ elLiner.innerHTML = ".";
+ else if (oRecord.getData("row_title") == "Total")
+ elLiner.innerHTML = "<a href='[% urlbase %][% '&' _ row_vals IF row_vals %]
+ [%~ '&' _ col_vals IF col_vals %]'>"
+ + oData + "</a>";
+ else
+ elLiner.innerHTML = "<a href='[% urlbase %]&[% row_field FILTER js %]="
+ + oRecord.getData("row_title").replace(/\s+$/,"")
+ + "[% '&' _ col_vals IF col_vals %]'>" + oData + "</a>";
+
+ YAHOO.util.Dom.addClass(elLiner.parentNode, "ttotal");
+ };
+
+ var totalRowFormatter = function( elTr, oRecord ) {
+ if ( oRecord.getData('row_title') == "Total" ) {
+ YAHOO.util.Dom.addClass( elTr, 'ttotal' );
+ }
+ return true;
+ };
+
+ var totalNumberSorter = function( a, b, desc, field ){
+ var a_value = a.getData(field);
+ var b_value = b.getData(field);
+ var a_total_test = a.getData("row_title");
+ var b_total_test = b.getData("row_title");
+ var comp_result = YAHOO.util.Sort.compare(a_value, b_value, desc);
+ if( a_total_test == "Total" ){
+ comp_result = 1;
+ }else if( b_total_test == "Total" ){
+ comp_result = -1;
+ }
+ return comp_result;
+ };
+
+
+ var myColumnDefs = [
+ {key:"row_title", label:"", sortable:true, sortOptions: { sortFunction:totalNumberSorter }},
+ [% FOREACH col = col_names %]
+ {key:"[% col FILTER js %]", label:"[% display_value(col_field, col) FILTER js %]", sortable:true,
+ formatter:this.Linkify, sortOptions: { defaultDir: YAHOO.widget.DataTable.CLASS_DESC, sortFunction:totalNumberSorter }},
+ [% END %]
+ {key:"total", label:"Total", sortable:true, formatter:this.LinkifyTotal,
+ sortOptions: { defaultDir: YAHOO.widget.DataTable.CLASS_DESC, sortFunction:totalNumberSorter }}
+ ];
+ this.parseString = function(str) {
+ return YAHOO.lang.trim(str);
+ };
+
+ this.parseNumber = function(str) {
+ if (str.match(/^\s*\.\s*$/m))
+ return 0;
+
+ // Do not use <\/a>$. For some reason, IE6 doesn't understand it.
+ // We use [^\d]+$ instead.
+ var res = str.match(/>(\d+)[^\d]+$/m);
+ if (res && res[1])
+ return parseFloat(res[1]);
+ };
+
+ this.myDataSource = new YAHOO.util.DataSource(YAHOO.util.Dom.get("tabular_report"));
+ this.myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
+ this.myDataSource.responseSchema = {
+ fields: [
+ {key:"row_title", parser:this.parseString},
+ [% FOREACH col = col_names %]
+ {key:"[% col FILTER js %]", parser:this.parseNumber},
+ [% END %]
+ {key:"total", parser:this.parseNumber}
+ ]
+ };
+ this.myDataTable = new YAHOO.widget.DataTable("tabular_report_container_
+ [% tbl FILTER js %]", myColumnDefs, this.myDataSource,
+ {formatRow: totalRowFormatter});
+});
+</script>
+
[% IF tbl_field %]
- <h2>[% tbl_disp FILTER html %]</h2>
+ <h2>[% tbl_disp FILTER email FILTER html %]</h2>
[% END %]
<table>
@@ -67,58 +164,48 @@
[% col_idx = 0 %]
[% row_idx = 0 %]
[% grand_total = 0 %]
-
-<table border="1">
+<div id="tabular_report_container_[% tbl FILTER js %]">
+<table id="tabular_report" border="1">
[% IF col_field %]
+ <thead>
<tr>
- <td class="[% classes.$row_idx.$col_idx %]">
- </td>
+ <th class="[% classes.$row_idx.$col_idx %]">
+ </th>
[% FOREACH col = col_names %]
[% col_totals.$col = 0 %]
[% NEXT IF col == "" %]
[% col_idx = 1 - col_idx %]
- <td class="[% classes.$row_idx.$col_idx %]">
- [% IF col_field == 'bug_status' %]
- [% get_status(col) FILTER html FILTER replace('^ $',' ') %]
- [% ELSIF col_field == 'resolution' %]
- [% get_resolution(col) FILTER html FILTER replace('^ $',' ') %]
- [% ELSE %]
- [% col FILTER html FILTER replace('^ $',' ') %]
- [% END %]
- </td>
+ <th class="[% classes.$row_idx.$col_idx %]">
+ [% PROCESS value_display value = col field = col_field %]
+ </th>
[% END %]
- <td class="ttotal">
+ <th class="ttotal">
Total
- </td>
+ </th>
</tr>
+ </thead>
[% END %]
-
+ <tbody>
[% FOREACH row = row_names %]
[% row_total = 0 %]
[% row_idx = 1 - row_idx %]
<tr>
<td class="[% classes.$row_idx.$col_idx %]" align="right">
- [% IF row_field == 'bug_status' %]
- [% get_status(row) FILTER html FILTER replace('^ $',' ') %]
- [% ELSIF row_field == 'resolution' %]
- [% get_resolution(row) FILTER html FILTER replace('^ $',' ') %]
- [% ELSE %]
- [% row FILTER html FILTER replace('^ $',' ') %]
- [% END %]
+ [% PROCESS value_display value = row field = row_field %]
</td>
[% FOREACH col = col_names %]
[% row_total = row_total + data.$tbl.$col.$row %]
[% NEXT IF col == "" %]
- [% col_totals.$col = col_totals.$col + data.$tbl.$col.$row %]
+ [% col_totals.$col = (col_totals.$col || 0) + data.$tbl.$col.$row %]
[% col_idx = 1 - col_idx %]
<td class="[% classes.$row_idx.$col_idx %]" align="center">
[% IF data.$tbl.$col.$row AND data.$tbl.$col.$row > 0 %]
<a href="[% urlbase %]&
- [% row_field FILTER url_quote %]=[% row FILTER url_quote %]&
- [% col_field FILTER url_quote %]=[% col FILTER url_quote %]">
+ [% row_field FILTER uri %]=[% row FILTER uri %]&
+ [% col_field FILTER uri %]=[% col FILTER uri %]">
[% data.$tbl.$col.$row %]</a>
[% ELSE %]
.
@@ -127,40 +214,50 @@
[% END %]
<td class="ttotal" align="right">
<a href="[% urlbase %]&
- [% row_field FILTER url_quote %]=[% row FILTER url_quote %]
+ [% row_field FILTER uri %]=[% row FILTER uri %]
[% "&$col_vals" IF col_vals %]">
[% row_total %]</a>
[% grand_total = grand_total + row_total %]
</td>
</tr>
[% END %]
-
- <tr>
- [% row_idx = 1 - row_idx %]
- <td class="ttotal">
- Total
- </td>
- [% FOREACH col = col_names %]
- [% NEXT IF col == "" %]
+ <tr>
+ [% row_idx = 1 - row_idx %]
+ <td class="ttotal">
+ Total
+ </td>
+ [% FOREACH col = col_names %]
+ [% NEXT IF col == "" %]
- <td class="ttotal" align="center">
- <a href="[% urlbase %]&
- [% col_field FILTER url_quote %]=[% col FILTER url_quote %]
- [% "&$row_vals" IF row_vals %]">
- [% col_totals.$col %]</a>
- </td>
- [% END %]
- <td class="ttotal" align="right">
- <strong>
- <a href="[% urlbase %]
- [% "&$row_vals" IF row_vals %]
- [% "&$col_vals" IF col_vals %]">[% grand_total %]</a>
- </strong>
+ <td class="ttotal" align="center">
+ <a href="[% urlbase %]&
+ [% col_field FILTER uri %]=[% col FILTER uri %]
+ [% "&$row_vals" IF row_vals %]">
+ [% col_totals.$col %]</a>
+ </td>
+ [% END %]
+ <td class="ttotal" align="right">
+ <strong>
+ <a href="[% urlbase %]
+ [% "&$row_vals" IF row_vals %]
+ [% "&$col_vals" IF col_vals %]">[% grand_total %]</a>
+ </strong>
+ </td>
+ </tr>
+ </tbody>
+</table>
+</div>
+
</td>
</tr>
</table>
-
- </td>
- </tr>
-</table>
+[% BLOCK value_display %]
+ [% SET disp_value = display_value(field, value) %]
+ [% IF field == 'assigned_to' OR field == 'reporter'
+ OR field == 'qa_contact'
+ %]
+ [% disp_value = value FILTER email %]
+ [% END %]
+ [% disp_value FILTER html FILTER replace('^ $',' ') %]
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/report.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/report.html.tmpl
index b8e5221..d4c9d40 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/report.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/report.html.tmpl
@@ -66,15 +66,18 @@
[% col_field_disp FILTER html %]
[% END %]
+[% time = time FILTER time('%Y-%m-%d %H:%M:%S') FILTER html %]
+
[% PROCESS global/header.html.tmpl
style = "
.t1 { background-color: #ffffff } /* white */
.t2 { background-color: #dfefff } /* light blue */
.t3 { background-color: #dddddd } /* grey */
.t4 { background-color: #c3d3ed } /* darker blue */
- .ttotal { background-color: #cfffdf } /* light green */
+ .ttotal, .ttotal td { background-color: #cfffdf } /* light green */
"
- header_addl_info = time2str("%Y-%m-%d %H:%M:%S", time)
+ header_addl_info = time
+ yui = ['datatable']
%]
[% IF debug %]
@@ -94,18 +97,18 @@
[% PROCESS "reports/report-table.html.tmpl" %]
[% ELSE %]
[% IF tbl %]
- <h2>[% tbl_disp FILTER html %]</h2>
+ <h2>[% tbl_disp FILTER email FILTER html %]</h2>
[% END %]
[% imageurl = BLOCK %]report.cgi?[% imagebase FILTER html %]&format=
- [% format FILTER url_quote %]&ctype=png&action=plot&
+ [% format FILTER uri %]&ctype=png&action=plot&
[% IF tbl_field %]
[% IF tbl != "-total-" %]
- [% tbl_field FILTER url_quote %]=[% tbl FILTER url_quote %]&
+ [% tbl_field FILTER uri %]=[% tbl FILTER uri %]&
[% ELSE %]
[% FOREACH tblname = tbl_names %]
[% IF tblname != "-total-" %]
- [% tbl_field FILTER url_quote %]=[% tblname FILTER url_quote %]&
+ [% tbl_field FILTER uri %]=[% tblname FILTER uri %]&
[% END %]
[% END %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/reports/series-common.html.tmpl b/Websites/bugs.webkit.org/template/en/default/reports/series-common.html.tmpl
index 35586cb..cecf288 100644
--- a/Websites/bugs.webkit.org/template/en/default/reports/series-common.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/reports/series-common.html.tmpl
@@ -48,7 +48,6 @@
[% END %]
};
-[%# Should attempt to preserve selection across invocations @@@ %]
[%# This function takes necessary action on selection of a category %]
function catSelected() {
var cat = document.chartform.category.value;
@@ -67,7 +66,8 @@
[% IF newtext %]
subcatwidget.options[i] = new Option("[% newtext FILTER js %]", "");
[% END %]
-
+
+ subcatwidget.disabled = false;
subcatwidget.options[0].selected = true;
if (document.chartform.action[1]) {
@@ -100,11 +100,13 @@
<td align="left">
<select name="[% sel.name %]" id="[% sel.name %]"
size="[% sel.size %]" style="width: 15em"
+ [%+ 'multiple="multiple"' IF sel.multiple %]
[%+ "disabled=\"disabled\"" UNLESS ${sel.name}.keys.size || newtext %]
[%+ "onchange=\"$sel.onchange\"" IF sel.onchange %]>
[% FOREACH x = ${sel.name}.keys.sort %]
- <option value="[% x FILTER html %]"
- [% " selected" IF default.${sel.name} == x %]>
+ [% value = sel.value_in_hash ? ${sel.name}.$x : x %]
+ <option value="[% value FILTER html %]"
+ [% " selected" IF default.${sel.name} == value %]>
[% x FILTER html %]</option>
[% END %]
[% IF newtext %]
diff --git a/Websites/bugs.webkit.org/template/en/default/request/email.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/request/email.txt.tmpl
index 81948c4..fb95748 100644
--- a/Websites/bugs.webkit.org/template/en/default/request/email.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/request/email.txt.tmpl
@@ -24,30 +24,34 @@
[% bugidsummary = bug.bug_id _ ': ' _ bug.short_desc %]
[% attidsummary = attachment.id _ ': ' _ attachment.description %]
+[% flagtype_name = flag ? flag.type.name : old_flag.type.name %]
[% statuses = { '+' => "granted" , '-' => 'denied' , 'X' => "canceled" ,
'?' => "asked" } %]
[% to_identity = "" %]
[% on_behalf_of = 0 %]
-[% IF flag.status == '?' %]
+[% action = flag.status || 'X' %]
+
+[% IF flag && flag.status == '?' %]
[% subject_status = "requested" %]
- [% IF flag.setter.id == user.id %]
+ [% IF flag.setter_id == user.id %]
[% to_identity = flag.requestee.identity _ " for" %]
[% ELSE %]
[% on_behalf_of = 1 %]
[% IF flag.requestee %][% to_identity = " to " _ flag.requestee.identity %][% END %]
[% END %]
[% ELSE %]
- [% IF flag.requester %]
- [% to_identity = flag.requester.identity _ "'s request for" %]
+ [% IF old_flag && old_flag.status == '?' %]
+ [% to_identity = old_flag.setter.identity _ "'s request for" %]
[% END %]
- [% subject_status = statuses.${flag.status} %]
+ [% subject_status = statuses.$action %]
[% END %]
From: [% Param('mailfrom') %]
To: [% to %]
-Subject: [% flag.type.name %] [%+ subject_status %]: [[% terms.Bug %] [%+ bug.bug_id %]] [% bug.short_desc %]
+Subject: [% flagtype_name %] [%+ subject_status %]: [[% terms.Bug %] [%+ bug.bug_id %]] [% bug.short_desc %]
[%- IF attachment %] :
- [Attachment [% attachment.id %]] [% attachment.description %][% END %]
+ [Attachment [% attachment.id %]] [% attachment.description FILTER clean_text %][% END %]
+Date: [% date %]
X-Bugzilla-Type: request
[%+ threadingmarker %]
@@ -55,10 +59,10 @@
[%- FILTER bullet = wrap(80) -%]
[% IF on_behalf_of %]
-[% user.identity %] has reassigned [% flag.setter.identity %]'s request for [% flag.type.name %]
+[% user.identity %] has reassigned [% flag.setter.identity %]'s request for [% flagtype_name %]
[% to_identity %]:
[% ELSE %]
-[% user.identity %] has [% statuses.${flag.status} %] [%+ to_identity %] [%+ flag.type.name %]:
+[% user.identity %] has [% statuses.$action %] [%+ to_identity %] [%+ flagtype_name %]:
[% END %]
[% terms.Bug %] [%+ bugidsummary %]
@@ -71,10 +75,14 @@
[%- END %]
[%+ urlbase %]attachment.cgi?id=[% attachment.id %]&action=edit
[%- END %]
+
+[%- Hook.process('after_summary') -%]
+
[%- FILTER bullet = wrap(80) %]
[% USE Bugzilla %]
-[% IF Bugzilla.cgi.param("comment") && Bugzilla.cgi.param("comment").length > 0 %]
+[%-# .defined is necessary to avoid a taint issue in Perl < 5.10.1, see bug 509794. %]
+[% IF Bugzilla.cgi.param("comment").defined && Bugzilla.cgi.param("comment").length > 0 %]
------- Additional Comments from [% user.identity %]
[%+ Bugzilla.cgi.param("comment") %]
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/request/queue.html.tmpl b/Websites/bugs.webkit.org/template/en/default/request/queue.html.tmpl
index af911b2..57650de 100644
--- a/Websites/bugs.webkit.org/template/en/default/request/queue.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/request/queue.html.tmpl
@@ -23,18 +23,43 @@
[% USE Bugzilla %]
[% cgi = Bugzilla.cgi %]
-[% PROCESS "global/js-products.html.tmpl" %]
-
[% PROCESS global/header.html.tmpl
title="Request Queue"
style = "
table.requests th { text-align: left; }
table#filtering th { text-align: right; }
"
- onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, 'Any');"
- javascript_urls=["js/productform.js"]
+ onload="var f = document.request_form; selectProduct(f.product, f.component, null, null, 'Any');"
+ javascript_urls=["js/productform.js", "js/field.js"]
+ style_urls = ['skins/standard/buglist.css']
+ yui = ['autocomplete']
%]
+<script type="text/javascript">
+ var useclassification = false; // No classification level in use
+ var first_load = true; // Is this the first time we load the page?
+ var last_sel = []; // Caches last selection
+ var cpts = new Array();
+ [% n = 1 %]
+ [% IF Param('useclassification') %]
+ [% FOREACH clas = user.get_selectable_classifications %]
+ [% FOREACH prod = user.get_selectable_products(clas.id) %]
+ [%+ PROCESS js_comp %]
+ [% END %]
+ [% END %]
+ [% ELSE %]
+ [% FOREACH prod = user.get_selectable_products %]
+ [%+ PROCESS js_comp %]
+ [% END %]
+ [% END %]
+</script>
+
+[% BLOCK js_comp %]
+ cpts['[% n %]'] = [
+ [%- FOREACH comp = prod.components %]'[% comp.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%]];
+ [% n = n+1 %]
+[% END %]
+
<p>
When you are logged in, only requests made by you or addressed to you
are shown by default. You can change the criteria using the form below.
@@ -42,22 +67,44 @@
to some group are shown by default.
</p>
-<form action="request.cgi" method="get">
+<form id="request_form" name="request_form" action="request.cgi" method="get">
<input type="hidden" name="action" value="queue">
<table id="filtering">
<tr>
<th>Requester:</th>
- <td><input type="text" name="requester" value="[% cgi.param('requester') FILTER html %]" size="20"
- title="Requester's email address"></td>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ id => "requester"
+ name => "requester"
+ value => cgi.param('requester')
+ size => 20
+ emptyok => 1
+ field_title => "Requester's email address"
+ %]
+ </td>
<th>Product:</th>
<td>
<select name="product" onchange="selectProduct(this, this.form.component, null, null, 'Any');">
<option value="">Any</option>
- [% FOREACH prod = products %]
- <option value="[% prod.name FILTER html %]"
- [% "selected" IF cgi.param('product') == prod.name %]>
- [% prod.name FILTER html %]</option>
+ [% IF Param('useclassification') %]
+ [% FOREACH c = user.get_selectable_classifications %]
+ <optgroup label="[% c.name FILTER html %]">
+ [% FOREACH p = user.get_selectable_products(c.id) %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF cgi.param('product') == p.name %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
+ </optgroup>
+ [% END %]
+ [% ELSE %]
+ [% FOREACH p = user.get_selectable_products %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF cgi.param('product') == p.name %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
[% END %]
</select>
</td>
@@ -83,8 +130,17 @@
</tr>
<tr>
<th>Requestee:</th>
- <td><input type="text" name="requestee" value="[% cgi.param('requestee') FILTER html %]" size="20"
- title="Requestee's email address or "-" (hyphen) for requests with no requestee"></td>
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ id => "requestee"
+ name => "requestee"
+ value => cgi.param('requestee')
+ size => 20
+ emptyok => 1
+ hyphenok => 1
+ field_title => "Requestee's email address or \"-\" (hyphen) for requests with no requestee"
+ %]
+ </td>
<th>Component:</th>
<td>
<select name="component">
@@ -136,28 +192,32 @@
</p>
[% ELSE %]
[% FOREACH request = requests %]
- [% IF loop.first %] [% PROCESS start_new_table %] [% END %]
- [% IF request.$group_field != group_value %]
+ [% IF request.$group_field != group_value || loop.first %]
[% group_value = request.$group_field %]
- [% UNLESS loop.first %]
- </table>
- [% PROCESS start_new_table %]
- [% END %]
+ [% PROCESS display_buglist UNLESS loop.first %]
+ [% PROCESS start_new_table %]
[% END %]
+ [% buglist.${request.bug_id} = 1 %]
<tr>
[% FOREACH column = display_columns %]
[% NEXT IF column == group_field || excluded_columns.contains(column) %]
- <td>[% PROCESS "display_$column" %]</td>
+ <td>
+ [% PROCESS "display_$column" %]
+ [% Hook.process('after_column') %]
+ </td>
[% END %]
</tr>
[% END %]
- </table>
+ [% PROCESS display_buglist %]
[% END %]
[% PROCESS global/footer.html.tmpl %]
[% BLOCK start_new_table %]
- <h3>[% column_headers.$group_field %]: [% (request.$group_field || "None") FILTER html %]</h3>
+ [% buglist = {} %]
+
+ <h3>[% column_headers.$group_field %]:
+ [%+ (request.$group_field || "None") FILTER email FILTER html %]</h3>
<table class="requests" cellspacing="0" cellpadding="4" border="1">
<tr>
[% FOREACH column = display_columns %]
@@ -176,7 +236,8 @@
[% END %]
[% BLOCK display_bug %]
- <a href="show_bug.cgi?id=[% request.bug_id %]">
+ <a href="show_bug.cgi?id=[% request.bug_id %]"
+ [%- ' class="bz_secure"' IF request.restricted %]>
[% request.bug_id %]: [%+ request.bug_summary FILTER html %]</a>
[% END %]
@@ -190,14 +251,21 @@
[% END %]
[% BLOCK display_requestee %]
- [% request.requestee FILTER html %]
+ [% request.requestee FILTER email FILTER html %]
[% END %]
[% BLOCK display_requester %]
- [% request.requester FILTER html %]
+ [% request.requester FILTER email FILTER html %]
[% END %]
[% BLOCK display_created %]
[% request.created FILTER time %]
[% END %]
+[% BLOCK display_buglist %]
+ </table>
+ [% NEXT UNLESS buglist.keys.size %]
+ <a href="buglist.cgi?bug_id=
+ [%- buglist.keys.nsort.join(",") FILTER html %]">(view as
+ [%+ terms.bug %] list)</a>
+[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/search/boolean-charts.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/boolean-charts.html.tmpl
index 97a10d4..878589c 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/boolean-charts.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/boolean-charts.html.tmpl
@@ -17,119 +17,176 @@
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
#%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
[% types = [
- { name => "noop", description => "---" },
- { name => "equals", description => "is equal to" },
- { name => "notequals", description => "is not equal to" },
- { name => "anyexact", description => "is equal to any of the strings" },
- { name => "substring", description => "contains the string" },
- { name => "casesubstring", description => "contains the string (exact case)" },
- { name => "notsubstring", description => "does not contain the string" },
- { name => "anywordssubstr", description => "contains any of the strings" },
- { name => "allwordssubstr", description => "contains all of the strings" },
- { name => "nowordssubstr", description => "contains none of the strings" },
- { name => "regexp", description => "contains regexp" },
- { name => "notregexp", description => "does not contain regexp" },
- { name => "lessthan", description => "is less than" },
- { name => "greaterthan", description => "is greater than" },
- { name => "anywords", description => "contains any of the words" },
- { name => "allwords", description => "contains all of the words" },
- { name => "nowords", description => "contains none of the words" },
- { name => "changedbefore", description => "changed before" },
- { name => "changedafter", description => "changed after" },
- { name => "changedfrom", description => "changed from" },
- { name => "changedto", description => "changed to" },
- { name => "changedby", description => "changed by" },
- { name => "matches", description => "matches" } ] %]
+ "noop",
+ "equals",
+ "notequals",
+ "anyexact",
+ "substring",
+ "casesubstring",
+ "notsubstring",
+ "anywordssubstr",
+ "allwordssubstr",
+ "nowordssubstr",
+ "regexp",
+ "notregexp",
+ "lessthan",
+ "lessthaneq",
+ "greaterthan",
+ "greaterthaneq",
+ "anywords",
+ "allwords",
+ "nowords",
+ "changedbefore",
+ "changedafter",
+ "changedfrom",
+ "changedto",
+ "changedby",
+ "matches",
+ "notmatches",
+] %]
- <p>
- <strong>
- <a name="chart">Advanced Searching Using Boolean Charts</a>:
- </strong>
- </p>
-
-[%# Whoever wrote the original version of boolean charts had a seriously twisted mind %]
-
-[% jsmagic = "onclick=\"this.form.action='query.cgi#chart'; this.form.method='POST'; return 1;\"" %]
-
-[% FOREACH chart = default.charts %]
- [% chartnum = loop.count - 1 %]
- <table>
- <tr>
- <td>
- <input type="checkbox" id="negate[% chartnum FILTER html %]"
- name="negate[% chartnum FILTER html %]" value="1"
- [%+ "checked" IF chart.negate %]>
- <label for="negate[% chartnum FILTER html %]">
- Not (negate this whole chart)
- </label>
- </td>
- </tr>
- [% FOREACH row = chart.rows %]
- [% rownum = loop.count - 1 %]
- <tr>
- [% FOREACH col = row %]
- [% colnum = loop.count - 1 %]
- <td>
- <select name="[% "field${chartnum}-${rownum}-${colnum}" %]">
- [% FOREACH field = fields %]
- <option value="[% field.name %]" [% "selected" IF field.name == col.field %]>
- [% field_descs.${field.name} || field.description FILTER html %]
- </option>
- [% END %]
- </select>
-
- <select name="[% "type${chartnum}-${rownum}-${colnum}" %]">
- [% FOREACH type = types %]
- <option value="[% type.name %]"
- [%- " selected" IF type.name == col.type %]>[% type.description %]</option>
- [% END %]
- </select>
-
- <input name="[% "value${chartnum}-${rownum}-${colnum}" %]"
- value="[% col.value FILTER html %]">
- </td>
-
- [% UNLESS loop.last %]
- <td align="center">
- Or
- </td>
- </tr>
- <tr>
- [% ELSE %]
- <td>
- [% newor = colnum + 1 %]
- <input type="submit" value="Or" [% jsmagic %]
- name="cmd-add[% "${chartnum}-${rownum}-${newor}" %]"
- id="cmd-add[% "${chartnum}-${rownum}-${newor}" %]">
- </td>
- [% END %]
-
- [% END %]
- </tr>
-
- [% UNLESS loop.last %]
- <tr>
- <td>And</td>
- </tr>
- [% ELSE %]
- <tr>
- <td>
- [% newand = rownum + 1; newchart = chartnum + 1 %]
- <input type="submit" value="And" [% jsmagic %]
- name="cmd-add[% "${chartnum}-${newand}-0" %]"
- id="cmd-add[% "${chartnum}-${newand}-0" %]">
-
- <input type="submit" value="Add another boolean chart" [% jsmagic %]
- name="cmd-add[% newchart %]-0-0"
- id="cmd-add[% newchart %]-0-0">
-
- </td>
- </tr>
- [% END %]
-
+<div class="bz_section_title" id="custom_search_filter">
+ <div id="custom_search_query_controller" class="arrow">▼</div>
+ <a id="chart" href="javascript:TUI_toggle_class('custom_search_query')" >
+ Custom Search</a> <span class="section_help">Didn't find what
+ you're looking for above? This area allows for ANDs, ORs,
+ and other more complex searches.</span>
+</div>
+<div id="custom_search_filter_section"
+ class="bz_search_section custom_search_query">
+ [% SET indent_level = 0 %]
+ [% SET cond_num = 0 %]
+ [% FOREACH condition = default.custom_search %]
+ [% SET cond_num = loop.count - 1 %]
+ [% PROCESS one_condition with_buttons = 0 %]
[% END %]
- </table>
- <hr>
+ [% PROCESS one_condition
+ with_buttons = 1
+ condition = { f => 'noop' }
+ cond_num = cond_num + 1 %]
+ <script type="text/javascript">
+ TUI_alternates['custom_search_query'] = '►';
+ TUI_hide_default('custom_search_query');
+ TUI_alternates['custom_search_advanced'] = "Show Advanced Features";
+ TUI_hide_default('custom_search_advanced');
+ </script>
+ <script type="text/javascript" src="[% 'js/custom-search.js' FILTER mtime %]"></script>
+ <script type="text/javascript" src="[% 'js/history.js/native.history.js' FILTER mtime %]"></script>
+ <script type="text/javascript">
+ redirect_html4_browsers();
+ </script>
+</div>
+
+
+[% BLOCK one_condition %]
+ [%# Skip any conditions that don't have a field defined. %]
+ [% RETURN IF !condition.f %]
+
+ [% IF !top_level_any_shown %]
+ [% INCLUDE any_all_select
+ name = "j_top" selected = default.j_top.0
+ with_advanced_link = 1 %]
+ [% top_level_any_shown = 1 %]
+ [% END %]
+
+ [% IF condition.f == "CP" %]
+ [% indent_level = indent_level - 1 %]
+ [% END %]
+
+ <div class="custom_search_condition"
+ [% ' style="margin-left: ' _ (indent_level * 2) _ 'em"' IF indent_level %]
+ [% ' id="custom_search_last_row"' IF with_buttons %]>
+
+ [% IF previous_condition.f == "OP" %]
+ [% INCLUDE any_all_select
+ name = "j" _ (cond_num - 1)
+ selected = previous_condition.j %]
+ [% END %]
+
+ [% IF with_buttons %]
+ <button id="op_button" type="button" class="custom_search_advanced"
+ title="Start a new group of criteria, including this row"
+ onclick="custom_search_open_paren()">(</button>
+ [% END %]
+
+ [% UNLESS condition.f == "CP" %]
+ [%# This only gets hidden via custom_search_advanced if it isn't set. %]
+ <span id="custom_search_not_container_[% cond_num FILTER html %]"
+ class="custom_search_not_container
+ [%- ' custom_search_advanced' UNLESS condition.n %]"
+ title="Search for the opposite of the criteria here">
+ <input type="checkbox" id="n[% cond_num FILTER html %]"
+ class="custom_search_form_field"
+ name="n[% cond_num FILTER html %]" value="1"
+ onclick="custom_search_not_changed([% cond_num FILTER js %])"
+ [% ' checked="checked"' IF condition.n %]>
+ <label for="n[% cond_num FILTER html %]">Not</label>
+ </span>
+ [% END %]
+
+ [% IF condition.f == "OP" %]
+ <input type="hidden" name="f[% cond_num FILTER html %]"
+ id="f[% cond_num FILTER html %]" value="OP">
+ (
+ [% indent_level = indent_level + 1 %]
+ [% ELSIF condition.f == "CP" %]
+ <input type="hidden" name="f[% cond_num FILTER html %]" value="CP">
+ )
+ [% ELSE %]
+ <select name="f[% cond_num FILTER html %]" title="Field"
+ id="f[% cond_num FILTER html %]"
+ onchange="fix_query_string(this)"
+ class="custom_search_form_field">
+ [% FOREACH field = fields %]
+ <option value="[% field.name FILTER html %]"
+ [%~ ' selected="selected"' IF field.name == condition.f %]>
+ [% field_descs.${field.name} || field.description FILTER html %]
+ </option>
+ [% END %]
+ </select>
+
+ [% INCLUDE "search/type-select.html.tmpl"
+ name = "o${cond_num}", class = "custom_search_form_field"
+ types = types, selected = condition.o %]
+
+ <input name="v[% cond_num FILTER html %]" title="Value"
+ class="custom_search_form_field"
+ onchange="fix_query_string(this)"
+ value="[% condition.v FILTER html %]">
+ [% END %]
+
+ [% IF with_buttons %]
+ <button class="custom_search_add_button" type="button"
+ id="add_button" title="Add a new row"
+ onclick="custom_search_new_row()">+</button>
+ <span id="cp_container" [% ' class="bz_default_hidden"' IF !indent_level %]>
+ <button id="cp_button" type="button"
+ title="End this group of criteria"
+ onclick="custom_search_close_paren()">)</button>
+ </span>
+ [% END %]
+ </div>
+
+ [% previous_condition = condition %]
+[% END %]
+
+[% BLOCK any_all_select %]
+ <div class="any_all_select">
+ <select name="[% name FILTER html %]" id="[% name FILTER html %]"
+ onchange="fix_query_string(this)">
+ <option value="AND">Match ALL of the following:</option>
+ <option value="OR" [% ' selected="selected"' IF selected == "OR" %]>
+ Match ANY of the following:</option>
+ </select>
+ [% IF with_advanced_link %]
+ <a id="custom_search_advanced_controller"
+ href="javascript:TUI_toggle_class('custom_search_advanced')">
+ Hide Advanced Features
+ </a>
+ [% END %]
+ </div>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/search/field.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/field.html.tmpl
new file mode 100644
index 0000000..defc94c
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/search/field.html.tmpl
@@ -0,0 +1,142 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Guy Pyrzak
+ # Portions created by the Initial Developer are Copyright (C) 2010 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Guy Pyrzak <guy.pyrzak@gmail.com>
+ # Reed Loden <reed@reedloden.com>
+ #
+ #%]
+[%# INTERFACE:
+ # field: a Bugzilla::Field object
+ # value: the value or values that should be used to prepopulate the field
+ # accesskey: the access key used to access the field more quickly
+ # onchange: js to run when the change event fires on the field
+ # type_selected: used by the free text to indicate which type of text
+ # search was selected for a particular field
+ #%]
+
+[% SWITCH field.type %]
+ [% CASE [ constants.FIELD_TYPE_FREETEXT,
+ constants.FIELD_TYPE_TEXTAREA,
+ constants.FIELD_TYPE_UNKNOWN ] %]
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = field
+ tag_name = "span"
+ editable = 1
+ %]
+ [% INCLUDE "search/type-select.html.tmpl"
+ name = field.name _ "_type",
+ types = types,
+ selected = type_selected
+ %]
+ <input name="[% field.name FILTER html %]"
+ id="[% field.name FILTER html %]" size="40"
+ [% IF onchange %] onchange="[% onchange FILTER html %]"[% END %]
+ value="[% value FILTER html %]">
+ [% CASE constants.FIELD_TYPE_KEYWORDS %]
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = field
+ tag_name = "span"
+ editable = 1
+ %]
+ [% INCLUDE "search/type-select.html.tmpl"
+ name = field.name _ "_type",
+ types = types,
+ selected = type_selected
+ %]
+ <div id="keyword_container">
+ <input name="[% field.name FILTER html %]"
+ id="[% field.name FILTER html %]" size="40"
+ [% IF onchange %] onchange="[% onchange FILTER html %]"[% END %]
+ value="[% value FILTER html %]">
+ <div id="keyword_autocomplete"></div>
+ </div>
+ <script type="text/javascript" defer="defer">
+ YAHOO.bugzilla.keyword_array = [
+ [%- FOREACH keyword = all_keywords %]
+ [%-# %]"[% keyword.name FILTER js %]"
+ [%- "," IF NOT loop.last %][% END %]];
+ YAHOO.bugzilla.keywordAutocomplete.init('[% field.name FILTER js %]',
+ 'keyword_autocomplete');
+ </script>
+ [% CASE constants.FIELD_TYPE_DATETIME %]
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = field
+ tag_name = "span"
+ editable = 1
+ %]
+ from <input name="[% field.name FILTER html %]from"
+ id="[% field.name FILTER html %]"
+ size="10" maxlength="10"
+ value="[% value.0 FILTER html %]"
+ onchange="updateCalendarFromField(this);[% onchange FILTER html %]">
+ <button type="button" class="calendar_button"
+ id="button_calendar_[% field.name FILTER html %]"
+ onclick="showCalendar('[% field.name FILTER js %]')">
+ <span>Calendar</span>
+ </button>
+ <span id="con_calendar_[% field.name FILTER html %]"></span>
+ to <input name="[% field.name FILTER html %]to"
+ id="[% field.name FILTER html %]to" size="10" maxlength="10"
+ value="[% value.1 FILTER html %]"
+ onchange="updateCalendarFromField(this);[% onchange FILTER html %]">
+ <button type="button" class="calendar_button"
+ id="button_calendar_[% field.name FILTER html %]to"
+ onclick="showCalendar('[% field.name FILTER js %]to')">
+ <span>Calendar</span>
+ </button>
+ <small>(YYYY-MM-DD or relative dates)</small>
+
+ <span id="con_calendar_[% field.name FILTER html %]to"></span>
+ <script type="text/javascript">
+ createCalendar('[% field.name FILTER js %]');
+ createCalendar('[% field.name FILTER js %]to');
+ </script>
+ [% CASE [ constants.FIELD_TYPE_SINGLE_SELECT,
+ constants.FIELD_TYPE_MULTI_SELECT ] %]
+ <div id="container_[% field.name FILTER html %]" class="search_field_grid">
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = field
+ editable = 1
+ tag_name = "span"
+ %]
+ <select name="[% field.name FILTER html%]"
+ id="[% field.name FILTER html %]"
+ [% IF onchange %] onchange="[% onchange FILTER html %]"[% END %]
+ multiple="multiple" size="7">
+ [% legal_values = ${field.name} %]
+ [% IF field.name == "component" %]
+ [% legal_values = ${"component_"} %]
+ [% END %]
+ [% FOREACH current_value = legal_values %]
+ [% IF current_value.id %]
+ [%# current_value is a hash instead of a value which
+ only applies for Resolution really, everywhere else current_value
+ is just the value %]
+ [% v = current_value.name OR '---' -%]
+ <option value="[% v FILTER html %]"
+ [% ' selected="selected"' IF value.contains( v ) %]>
+ [% display_value(field.name, current_value.name) FILTER html %]
+ </option>
+ [% ELSE %]
+ <option value="[% current_value OR '---' FILTER html %]"
+ [% ' selected="selected"' IF value.contains( current_value ) %]>
+ [% display_value(field.name, current_value) FILTER html %]
+ </option>
+ [% END %]
+ [% END %]
+ </select>
+ </div>
+ [% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/search/form.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/form.html.tmpl
index 05b52dc..41e1165 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/form.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/form.html.tmpl
@@ -18,8 +18,11 @@
# Contributor(s): Chris Lahey <clahey@ximian.com> [javascript fixes]
# Christian Reis <kiko@async.com.br> [javascript rewrite]
# Gervase Markham <gerv@gerv.net>
+ # Guy Pyrzak <guy.pyrzak@gmail.com>
#%]
+[% PROCESS "global/field-descs.none.tmpl" %]
+
<script type="text/javascript">
var first_load = true; [%# is this the first time we load the page? %]
@@ -101,22 +104,29 @@
}
}
+// Hide the Advanced Fields by default, unless the user has a cookie
+// that specifies otherwise.
+// ▸ and ▾ are both utf8 escaped characters for right
+// and down facing arrows respectivly.
+TUI_alternates['history_query'] = '►';
+TUI_alternates['people_query'] = '►';
+TUI_alternates['information_query'] = '►';
+
+TUI_hide_default('history_query');
+TUI_hide_default('people_query');
+TUI_hide_default('information_query');
</script>
-
-[% PROCESS global/variables.none.tmpl %]
-
-[% query_variants = [
- { value => "allwordssubstr", description => "contains all of the words/strings" },
- { value => "anywordssubstr", description => "contains any of the words/strings" },
- { value => "substring", description => "contains the string" },
- { value => "casesubstring", description => "contains the string (exact case)" },
- { value => "allwords", description => "contains all of the words" },
- { value => "anywords", description => "contains any of the words" },
- { value => "regexp", description => "matches the regexp" },
- { value => "notregexp", description => "doesn't match the regexp" } ] %]
-
-[% PROCESS "global/field-descs.none.tmpl" %]
+[% query_types = [
+ "allwordssubstr",
+ "anywordssubstr",
+ "substring",
+ "casesubstring",
+ "allwords",
+ "anywords",
+ "regexp",
+ "notregexp",
+] %]
[%# If we resubmit to ourselves, we need to know if we are using a format. %]
[% thisformat = query_format != '' ? query_format : format %]
@@ -124,391 +134,202 @@
[%# *** Summary *** %]
-<table>
- <tr>
- <th align="right">
- <label for="short_desc" accesskey="s"><u>S</u>ummary</label>:
- </th>
- <td>
- <select name="short_desc_type">
- [% FOREACH qv = query_variants %]
- <option value="[% qv.value %]"
- [% " selected" IF default.short_desc_type.0 == qv.value %]>[% qv.description %]</option>
- [% END %]
- </select>
- </td>
- <td>
- <input name="short_desc" id="short_desc" size="40"
- value="[% default.short_desc.0 FILTER html %]">
+ <div class="search_field_row" id="summary_field">
+ [% INCLUDE "search/field.html.tmpl"
+ field = bug_fields.short_desc
+ types = query_types
+ value = default.short_desc.0
+ type_selected = default.short_desc_type.0
+ accesskey = "s"
+ %]
<script type="text/javascript"> <!--
document.forms[queryform].short_desc.focus();
// -->
</script>
- </td>
- <td>
+
[% IF button_name %]
<input type="submit" id="[% button_name FILTER css_class_quote %]_top"
value="[% button_name FILTER html %]">
[% END %]
- </td>
- </tr>
+ </div>
-[%# *** Classification Product Component Version Target *** %]
- <tr>
- <td colspan="4">
- <table>
- <tr>
- [% IF Param('useclassification') %]
- <td valign="top">
- <table>
- <tr valign="bottom">
- <th align="left">
- <label for="classification">Classification</label>:
- </th>
- </tr>
- <tr valign="top">
- <td align="left">
- <select name="classification" multiple="multiple" size="5" id="classification"
- onchange="doOnSelectProduct(1);">
- [% FOREACH cat = classification %]
- <option value="[% cat.name FILTER html %]"
- [% " selected" IF lsearch(default.classification, cat.name) != -1 %]>
- [% cat.name FILTER html %]
- </option>
- [% END %]
- </select>
- </td>
- </tr>
- </table>
- </td>
- [% END %]
- <td valign="top">
- <table>
- <tr valign="bottom">
- <th align="left">
- <label for="product" accesskey="p"><u>P</u>roduct</label>:
- </th>
- </tr>
- <tr valign="top">
- [%# Can't use the select block here because of the onChange %]
- <td align="left">
- <select name="product" multiple="multiple" size="5" id="product"
- onchange="doOnSelectProduct(2);">
- [% FOREACH p = product %]
- [% IF p.components.size %]
- <option value="[% p.name FILTER html %]"
- [% " selected" IF lsearch(default.product, p.name) != -1 %]>
- [% p.name FILTER html %]</option>
- [% END %]
- [% END %]
- </select>
- </td>
- </tr>
- </table>
- </td>
- <td valign="top">
- <table>
- <tr valign="bottom">
- <th align="left">
- <label for="component" accesskey="m"><a href="describecomponents.cgi">Co<u>m</u>ponent</a></label>:
- </th>
- </tr>
- <tr valign="top">
- [%# Can't use the select block here because 'component' is a toolkit
- reserved word - we use 'component_' instead. %]
- <td align="left">
- <select name="component" id="component"
- multiple="multiple" size="5">
- [% FOREACH c = component_ %]
- <option value="[% c FILTER html %]"
- [% " selected" IF lsearch(default.component, c) != -1 %]>
- [% c FILTER html %]</option>
- [% END %]
- </select>
- </td>
- </tr>
- </table>
- </td>
- <td valign="top">
- <table>
- <tr valign="bottom">
- <th align="left">
- <label for="version">Version</label>:
- </th>
- </tr>
- <tr valign="top">
- [% PROCESS select sel = { name => 'version',
- size => 5 } %]
- </tr>
- </table>
- </td>
- [% IF Param('usetargetmilestone') %]
- <td valign="top">
- <table>
- <tr valign="bottom">
- <th align="left">
- <label for="target_milestone">Target</label>:
- </th>
- </tr>
- <tr valign="top">
- [% PROCESS select sel = { name => 'target_milestone',
- size => 5 } %]
- </tr>
- </table>
- </td>
- [% END %]
- </tr>
- </table>
- </td>
- </tr>
+[%# *** Classification Product Component *** %]
+
+[% Hook.process('before_selects_top') %]
+[% IF Param('useclassification') %]
+ [% fake_classfication = { name => bug_fields.classification.name,
+ type => constants.FIELD_TYPE_SINGLE_SELECT } %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => fake_classfication
+ accesskey => "c"
+ onchange => "doOnSelectProduct(1);"
+ value => default.classification
+ %]
+[% END %]
+[% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.product
+ accesskey => "p"
+ onchange => "doOnSelectProduct(2);"
+ value => default.product
+%]
+[% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.component
+ accesskey => "m"
+ value => default.component
+%]
+[% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.bug_status
+ accesskey => "a"
+ value => default.bug_status
+%]
+[% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.resolution
+ accesskey => "r"
+ value => default.resolution
+%]
+
+[% Hook.process('after_selects_top') %]
+
+<div id="detailed_information" class="bz_section_title">
+ <div id="information_query_controller" class="arrow">▼</div>
+ <a href="javascript:TUI_toggle_class('information_query')">
+ Detailed [% terms.Bug %] Information
+ </a>
+ <span class="section_help">Narrow results by the following fields:
+ [%+ field_descs.longdesc FILTER html %]s, [%+ field_descs.bug_file_loc FILTER html %],
+ [% IF Param('usestatuswhiteboard') %] [%+ field_descs.status_whiteboard FILTER html %], [%+ END %]
+ [% IF use_keywords %] [%+ field_descs.keywords FILTER html %], [%+ END %]
+ [% IF user.is_timetracker %] [%+ field_descs.deadline FILTER html %], [%+ END %]
+ [% terms.Bug %] Numbers, [%+ field_descs.version FILTER html %],
+ [% IF Param('usetargetmilestone') %] [%+ field_descs.target_milestone FILTER html %], [%+ END %]
+ [% field_descs.bug_severity FILTER html %], [%+ field_descs.priority FILTER html %], [%+ field_descs.rep_platform FILTER html %],
+ [%+ field_descs.op_sys FILTER html %]
+ </span>
+</div>
[%# *** Comment URL Whiteboard Keywords *** %]
-
- [% FOREACH field = [
- { name => "long_desc", description => "A <u>C</u>omment",
- accesskey => 'c' },
- { name => "bug_file_loc", description => "The <u>U</u>RL",
- accesskey => 'u' },
- { name => "status_whiteboard", description => "<u>W</u>hiteboard",
- accesskey => 'w' } ] %]
-
- [% UNLESS field.name == 'status_whiteboard' AND NOT Param('usestatuswhiteboard') %]
- <tr>
- <th align="right">
- <label for="[% field.name %]" accesskey="[% field.accesskey %]">[% field.description %]</label>:
- </th>
- <td>
- <select name="[% field.name %]_type">
- [% FOREACH qv = query_variants %]
- [% type = "${field.name}_type" %]
- <option value="[% qv.value %]"
- [% " selected" IF default.$type.0 == qv.value %]>[% qv.description %]</option>
- [% END %]
- </select>
- </td>
- <td><input name="[% field.name %]" id="[% field.name %]" size="40"
- value="[% default.${field.name}.0 FILTER html %]">
- </td>
- <td></td>
- </tr>
- [% END %]
- [% END %]
-
- [% IF have_keywords %]
- <tr>
- <th align="right">
- <label for="keywords" accesskey="k"><a href="describekeywords.cgi"><u>K</u>eywords</a></label>:
- </th>
- <td>
- <select name="keywords_type">
- [% FOREACH qv = [
- { name => "allwords", description => "contains all of the keywords" },
- { name => "anywords", description => "contains any of the keywords" },
- { name => "nowords", description => "contains none of the keywords" } ] %]
-
- <option value="[% qv.name %]"
- [% " selected" IF default.keywords_type.0 == qv.name %]>
- [% qv.description %]</option>
- [% END %]
- </select>
- </td>
- <td>
- <input name="keywords" id="keywords" size="40"
- value="[% default.keywords.0 FILTER html %]">
- </td>
- </tr>
+<div id="detailed_information_section" class="bz_search_section information_query">
+ [% SET freetext_fields = [
+ { field => bug_fields.longdesc, accesskey => 'c' },
+ { field => bug_fields.bug_file_loc, accesskey => 'u' },
+ { field => bug_fields.status_whiteboard, accesskey => 'w' },
+ { field => bug_fields.keywords, accesskey => 'k',
+ qtypes => ['allwords', 'anywords', 'nowords', 'regexp', 'notregexp'] }
+ ] %]
+ [% Hook.process('before_freetext_fields') %]
+
+ [%# loop through a bunch of free text fields and print out their text stuff %]
+ [% FOREACH field_container = freetext_fields %]
+ [% NEXT IF field_container.field.name == 'status_whiteboard'
+ AND NOT Param('usestatuswhiteboard')
+ %]
+ [% NEXT IF field_container.field.name == 'keywords'
+ AND NOT use_keywords
+ %]
+ <div class="search_field_row">
+ [% type = field_container.field.name _ "_type" %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => field_container.field
+ types => field_container.qtypes || query_types
+ accesskey => field_container.accesskey
+ value => default.${field_container.field.name}.0
+ type_selected => default.$type.0
+ %]
+ </div>
[% END %]
[%# Deadline %]
- [% IF user.in_group(Param("timetrackinggroup")) %]
- <tr>
- <th align="right">
- <label for="deadlinefrom" accesskey="l">Dead<u>l</u>ine</label>:
- </th>
- <td>
- from <input name="deadlinefrom" id="deadlinefrom" size="10" maxlength="10"
- value="[% default.deadlinefrom.0 FILTER html %]">
- to <input name="deadlineto" size="10" maxlength="10"
- value="[% default.deadlineto.0 FILTER html %]">
- </td>
- <td>
- <small>(YYYY-MM-DD)</small>
- </td>
- </tr>
+ [% IF user.is_timetracker %]
+ <div class="search_field_row">
+ [% INCLUDE "search/field.html.tmpl"
+ field = bug_fields.deadline
+ accesskey = "l"
+ value = [ default.deadlinefrom.0, default.deadlineto.0 ]
+ %]
+ </div>
[% END %]
+
+ <div class="search_field_row">
+ <span class="field_label"><label for="bug_id">[% terms.Bugs %] numbered</label></span>
+ <div id="bug_id_container" >
+ <input type="text" name="bug_id" id="bug_id"
+ value="[% default.bug_id.0 FILTER html %]" size="20">
+ <div class="field_help">(comma-separated list)</div>
+ </div>
+ should be
+ <select name="bug_id_type" id="bug_id_type">
+ <option value="anyexact"[% " selected" IF default.bug_id_type.0 == "anyexact" %]>only included in</option>
+ <option value="nowords"[% " selected" IF default.bug_id_type.0 == "nowords" %]>excluded from</option>
+ </select> the results
+ </div>
+
+ [% Hook.process('after_freetext_fields') %]
-</table>
-
-<hr>
-
-[%# *** Status Resolution Severity Priority Hardware OS *** %]
-
-<table>
- <tr>
- <td>
- <table>
- <tr>
- <th align="left">
- <label for="bug_status" accesskey="a">St<u>a</u>tus</label>:
- </th>
- </tr>
- <tr valign="top">
- [% PROCESS select sel = { name => 'bug_status',
- size => 7 } %]
- </tr>
- </table>
- </td>
- <td>
- <table>
- <tr>
- <th align="left">
- <label for="resolution" accesskey="r"><u>R</u>esolution</label>:
- </th>
- </tr>
- <tr valign="top">
- [% PROCESS select sel = { name => 'resolution',
- size => 7 } %]
- </tr>
- </table>
- </td>
- <td>
- <table>
- <tr>
- <th align="left">
- <label for="bug_severity">Severity</label>:
- </th>
- </tr>
- <tr valign="top">
- [% PROCESS select sel = { name => 'bug_severity',
- size => 7 }%]
- </tr>
- </table>
- </td>
- <td>
- <table>
- <tr>
- <th align="left">
- <label for="priority" accesskey="i">Pr<u>i</u>ority</label>:
- </th>
- </tr>
- <tr valign="top">
- [% PROCESS select sel = { name => 'priority',
- size => 7 } %]
- </tr>
- </table>
- </td>
- <td>
- <table>
- <tr>
- <th align="left">
- <label for="rep_platform" accesskey="h"><u>H</u>ardware</label>:
- </th>
- </tr>
- <tr valign="top">
- [% PROCESS select sel = { name => 'rep_platform',
- size => 7 } %]
- </tr>
- </table>
- </td>
- <td>
- <table>
- <tr>
- <th align="left">
- <label for="op_sys" accesskey="o"><u>O</u>S</label>:
- </th>
- </tr>
- <tr valign="top">
- [% PROCESS select sel = { name => 'op_sys',
- size => 7 } %]
- </tr>
- </table>
- </td>
- </tr>
-</table>
-
-[%# *** Email Numbering Votes *** %]
-
-<table>
- <tr>
- <td>
- <fieldset>
- <legend>
- <strong>
- [% IF Param('usevotes') %]
- Email Addresses, [% terms.Bug %] Numbers, and Votes
- [% ELSE %]
- Email Addresses and [% terms.Bug %] Numbers
- [% END %]
- </strong>
- </legend>
-
-
-<table>
- <tr>
- [% FOREACH n = [1, 2] %]
- <td>
-
-
-<table cellspacing="0" cellpadding="0">
- <tr>
- <td>
+ [%# *** Status Resolution Severity Priority Hardware OS *** %]
+ <div>
+ [% Hook.process('before_selects_bottom') %]
+ [% fake_version_field = { name => bug_fields.version.name,
+ type => constants.FIELD_TYPE_SINGLE_SELECT }%]
+ [% INCLUDE "search/field.html.tmpl"
+ field => fake_version_field
+ value => default.version
+ %]
+ [% IF Param('usetargetmilestone') %]
+ [% fake_target_milestone_field = { name => bug_fields.target_milestone.name ,
+ type => constants.FIELD_TYPE_SINGLE_SELECT } %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => fake_target_milestone_field
+ value => default.target_milestone
+ %]
+ [% END %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.bug_severity
+ accesskey=> "v"
+ value => default.bug_severity
+ %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.priority
+ accesskey => "i"
+ value => default.priority
+ %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.rep_platform
+ accesskey =>"h"
+ value => default.rep_platform
+ %]
+ [% INCLUDE "search/field.html.tmpl"
+ field => bug_fields.op_sys
+ accesskey =>"o"
+ value => default.op_sys
+ %]
+ [% Hook.process('after_selects_bottom') %]
+ </div>
+</div>
+[%# *** Email Numbering *** %]
+ <div class="bz_section_title" id="people_filter">
+ <div id="people_query_controller" class="arrow">▼</div>
+ <a href="javascript:TUI_toggle_class('people_query')">Search By People</a>
+ <span>Narrow results to a role (i.e. [% field_descs.assigned_to FILTER html %],
+ [%+ field_descs.reporter FILTER html %], [% field_descs.commenter FILTER html %],
+ etc.) a person has on [% terms.abug %]
+ </span>
+ </div>
+ <div id="people_filter_section" class="bz_search_section people_query">
+ [% FOREACH n = [1, 2, 3] %]
+ <div class="search_email_fields">
Any of:
- </td>
- </tr>
- <tr>
- <td>
- <input type="checkbox" name="emailassigned_to[% n %]"
- id="emailassigned_to[% n %]" value="1"
- [% " checked" IF default.emailassigned_to.$n %]>
- <label for="emailassigned_to[% n %]">
- the [% terms.bug %] assignee
- </label>
- </td>
- </tr>
- <tr>
- <td>
- <input type="checkbox" name="emailreporter[% n %]"
- id="emailreporter[% n %]" value="1"
- [% " checked" IF default.emailreporter.$n %]>
- <label for="emailreporter[% n %]">
- the reporter
- </label>
- </td>
- </tr>
- [% IF Param('useqacontact') %]
- <tr>
- <td>
- <input type="checkbox" name="emailqa_contact[% n %]"
- id="emailqa_contact[% n %]" value="1"
- [% " checked" IF default.emailqa_contact.$n %]>
- <label for="emailqa_contact[% n %]">
- the QA contact
- </label>
- </td>
- </tr>
- [% END %]
- <tr>
- <td>
- <input type="checkbox" name="emailcc[% n %]"
- id="emailcc[% n %]" value="1"
- [% " checked" IF default.emailcc.$n %]>
- <label for="emailcc[% n %]">
- a CC list member
- </label>
- </td>
- </tr>
- <tr>
- <td>
- <input type="checkbox" name="emaillongdesc[% n %]"
- id="emaillongdesc[% n %]" value="1"
- [% " checked" IF default.emaillongdesc.$n %]>
- <label for="emaillongdesc[% n %]">
- a commenter
- </label>
- </td>
- </tr>
- <tr>
- <td>
+ [% PROCESS role_types field = { count => n, name => "emailassigned_to",
+ label=> "the ${terms.Bug} ${field_descs.assigned_to}" } %]
+ [% PROCESS role_types field = { count => n, name => "emailreporter",
+ label=> "the ${field_descs.reporter}" } %]
+ [% IF Param('useqacontact') %]
+ [% PROCESS role_types field = { count => n, name => "emailqa_contact",
+ label=> "the ${field_descs.qa_contact}" } %]
+ [% END %]
+ [% PROCESS role_types field = { count => n, name => "emailcc",
+ label=> "a ${field_descs.cc} list member" } %]
+ [% PROCESS role_types field = { count => n, name => "emaillongdesc",
+ label=> " a ${field_descs.commenter}" } %]
<select name="emailtype[% n %]">
[% FOREACH qv = [
{ name => "substring", description => "contains" },
@@ -516,129 +337,88 @@
{ name => "notequals", description => "is not" },
{ name => "regexp", description => "matches regexp" },
{ name => "notregexp", description => "doesn't match regexp" } ] %]
-
<option value="[% qv.name %]"
[% " selected" IF default.emailtype.$n == qv.name %]>[% qv.description %]</option>
[% END %]
</select>
- </td>
- </tr>
- <tr>
- <td>
- <input name="email[% n %]" size="25" value="[% default.email.$n FILTER html %]">
- </td>
- </tr>
-</table>
-
-
- </td>
+ [% IF feature_enabled('jsonrpc') %]
+ <div id="email[% n %]_autocomplete">
+ [% END %]
+ <input name="email[% n %]" class="email" id="email[% n %]"
+ value="[% default.email.$n FILTER html %]">
+ [% IF feature_enabled('jsonrpc') %]
+ <div id="email[% n %]_autocomplete_container"></div>
+ </div>
+ <script type="text/javascript">
+ YAHOO.bugzilla.userAutocomplete.init( "email[% n %]",
+ "email[% n %]_autocomplete_container");
+ </script>
+ [% END %]
+ </div>
[% END %]
- </tr>
-</table>
-<hr>
-<table>
- <tr>
- <td>
- <select name="bugidtype">
- <option value="include"[% " selected" IF default.bugidtype.0 == "include" %]>Only include</option>
- <option value="exclude"[% " selected" IF default.bugidtype.0 == "exclude" %]>Exclude</option>
- </select>
- <label for="bug_id">[% terms.bugs %] numbered</label>:
- </td>
- <td>
- <input type="text" name="bug_id" id="bug_id"
- value="[% default.bug_id.0 FILTER html %]" size="20">
- </td>
- </tr>
- <tr>
- <td></td>
- <td>(comma-separated list)</td>
- </tr>
- [% IF Param('usevotes') %]
- <tr>
- <td align="right">
- <label for="votes">Only [% terms.bugs %] with at least</label>:
- </td>
- <td>
- <input name="votes" id="votes" size="3"
- value="[% default.votes.0 FILTER html %]">
- votes
- </td>
- </tr>
- [% END %]
-</table>
-
-
- </fieldset>
- </td>
-
+ [% Hook.process('email_numbering_end') %]
+ </div>
[%# *** Bug Changes *** %]
-
- <td valign="top">
- <fieldset>
- <legend><strong>[% terms.Bug %] Changes</strong></legend>
-
-
-<dl class="bug_changes">
- <dt>
- <label for="chfieldfrom">Only [% terms.bugs %] changed between</label>:
- </dt>
- <dd>
- <input name="chfieldfrom" id="chfieldfrom"
- size="10" value="[% default.chfieldfrom.0 FILTER html %]">
- and <input name="chfieldto" size="10" value="[% default.chfieldto.0 FILTER html %]">
- <br>(YYYY-MM-DD or relative dates)
- </dd>
- <dt>
- <label for="chfield">where one or more of the following changed</label>:
- </dt>
- <dd>
+<div class="bz_section_title" id="history_filter">
+ <div id="history_query_controller" class="arrow">▼</div>
+ <a href="javascript:TUI_toggle_class('history_query')" >Search By Change History</a>
+ <span>Narrow results to how fields have changed during a specific time period</span>
+</div>
+<ul class="bug_changes bz_search_section history_query" id="history_filter_section" >
+ <li>
+ <label for="chfield">where ANY of the fields:</label>
[%# Create array, so we can sort it by description #%]
[% chfields = [] %]
[% FOREACH field = chfield %]
[% chfields.push({value => field, desc => (field_descs.$field || field) }) %]
[% END %]
-
<select name="chfield" id="chfield" multiple="multiple" size="4">
[% FOREACH field = chfields.sort('desc') %]
<option value="[% field.value FILTER html %]"
- [% " selected" IF lsearch(default.chfield, field.value) != -1 %]>
+ [% " selected" IF default.chfield.contains(field.value) %]>
[% field.desc FILTER html %]</option>
[% END %]
</select>
- </dd>
- <dt>and <label for="chfieldvalue">the new value was</label>:</dt>
- <dd>
+ </li>
+ <li>
+ <label for="chfieldvalue">[% search_descs.changedto FILTER html %]:</label>
<input name="chfieldvalue" id="chfieldvalue"
size="20" value="[% default.chfieldvalue.0 FILTER html %]">
- </dd>
-</dl>
-
- </fieldset>
- </td>
- </tr>
-</table>
+ </li>
+ <li>
+ <label for="chfieldfrom">between:</label>
+ <input name="chfieldfrom" id="chfieldfrom" size="10"
+ value="[% default.chfieldfrom.0 FILTER html %]" onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_chfieldfrom"
+ onclick="showCalendar('chfieldfrom')"><span>Calendar</span></button>
+ and
+ <div id="con_calendar_chfieldfrom"></div>
+ <input name="chfieldto" size="10" id="chfieldto"
+ value="[% default.chfieldto.0 || "Now" FILTER html %]"
+ onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_chfieldto"
+ onclick="showCalendar('chfieldto')"><span>Calendar</span></button>
+ <div id="con_calendar_chfieldto"></div>
+ (YYYY-MM-DD or relative dates)
+ <script type="text/javascript">
+ createCalendar('chfieldfrom');
+ createCalendar('chfieldto');
+ </script>
+ </li>
+</ul>
[%############################################################################%]
-[%# Block for SELECT fields #%]
+[%# Block for email role type use to select which email to search through #%]
[%############################################################################%]
-
-[% BLOCK select %]
- <td align="left">
- <select name="[% sel.name %]" id="[% sel.name %]"
- multiple="multiple" size="[% sel.size %]">
- [% FOREACH name = ${sel.name} %]
- <option value="[% name FILTER html %]"
- [% " selected" IF lsearch(default.${sel.name}, name) != -1 %]>
- [% IF sel.name == "bug_status" %]
- [% get_status(name) FILTER html %]
- [% ELSIF sel.name == "resolution" %]
- [% get_resolution(name) FILTER html %]
- [% ELSE %]
- [% name FILTER html %]
- [% END %]
- </option>
- [% END %]
- </select>
- </td>
+[% BLOCK role_types %]
+ <div class="role_type">
+ <input type="checkbox" name="[% field.name _ field.count FILTER html %]"
+ id="[% field.name _ field.count FILTER html %]" value="1"
+ [% " checked" IF default.${field.name}.${field.count} %]>
+ <label for="[% field.name _ field.count FILTER html%]">
+ [% field.label FILTER html%]
+ </label>
+ </div>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/search/knob.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/knob.html.tmpl
index d0381e1..17ff63a 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/knob.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/knob.html.tmpl
@@ -62,6 +62,10 @@
[%# The name of the existing query will be passed to buglist.cgi. %]
<input type="hidden" name="query_based_on" value="[% known_name FILTER html %]">
[% END %]
+ [%# Preserve any custom column list that might be set. %]
+ [% IF columnlist %]
+ <input type="hidden" name="columnlist" value="[% columnlist FILTER html %]">
+ [% END %]
</p>
<p>
diff --git a/Websites/bugs.webkit.org/template/en/default/search/search-advanced.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/search-advanced.html.tmpl
index 1f1fd50..ef7fa76 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/search-advanced.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/search-advanced.html.tmpl
@@ -36,10 +36,11 @@
[% PROCESS global/header.html.tmpl
title = "Search for $terms.bugs"
- onload = "doOnSelectProduct(0); enableHelp();"
+ onload = "doOnSelectProduct(0);"
javascript = js_data
- javascript_urls = [ "js/productform.js" "js/util.js" "js/help.js" ]
- style_urls = [ "skins/standard/help.css" ]
+ yui = [ 'autocomplete', 'calendar' ]
+ javascript_urls = [ "js/productform.js", "js/util.js", "js/TUI.js", "js/field.js"]
+ style_urls = [ "skins/standard/search_form.css" ]
doc_section = "query.html"
style = "dl.bug_changes dt {
margin-top: 15px;
@@ -50,30 +51,16 @@
[% button_name = "Search" %]
-[%# The decent help requires Javascript %]
-<script type="text/javascript"> <!--
-[% IF NOT cgi.param("help") %]
- document.write("<p><a href='query.cgi?help=1&format=advanced'>Give me some help<\/a> (reloads page).<\/p>");
-[% ELSE %]
- [% PROCESS "search/search-help.html.tmpl" %]
- if (generateHelp())
- document.write("<p>For help, mouse over the page elements.<\/p>");
- else
- document.write("<p>Help initialization failed, no help available.<\/p>");
-[% END %]
-// -->
-</script>
+<p id="search_help">Hover your mouse over each field label to get help for that field.</p>
-<form method="get" action="buglist.cgi" name="queryform">
+<form method="post" action="buglist.cgi" name="queryform" id="queryform">
[% PROCESS search/form.html.tmpl %]
-[% PROCESS search/knob.html.tmpl %]
-
-<hr>
-
[% PROCESS "search/boolean-charts.html.tmpl" %]
+[% PROCESS search/knob.html.tmpl %]
+
</form>
diff --git a/Websites/bugs.webkit.org/template/en/default/search/search-create-series.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/search-create-series.html.tmpl
index da1011e..3ca68ac 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/search-create-series.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/search-create-series.html.tmpl
@@ -33,8 +33,10 @@
[% PROCESS global/header.html.tmpl
title = "Create New Data Set"
onload = "doOnSelectProduct(0);"
+ yui = [ 'autocomplete', 'calendar' ]
javascript = js_data
- javascript_urls = [ "js/productform.js" ]
+ javascript_urls = [ "js/util.js", "js/productform.js", "js/TUI.js", "js/field.js" ]
+ style_urls = [ "skins/standard/search_form.css" ]
doc_section = "reporting.html#charts-new-series"
%]
@@ -43,7 +45,7 @@
[% PROCESS search/form.html.tmpl %]
<p>
- <input type="submit" name="action-search" value="Run Search">
+ <input type="submit" id="action-search" name="action-search" value="Run Search">
to see which [% terms.bugs %] would be included in this data set.
</p>
@@ -52,6 +54,7 @@
[% PROCESS reports/series.html.tmpl
button_name = "Create Data Set" %]
<input type="hidden" name="action" value="create">
+ <input type="hidden" name="token" value="[% issue_hash_token(['create-series']) FILTER html %]">
<script type="text/javascript">
document.chartform.category[0].selected = true;
diff --git a/Websites/bugs.webkit.org/template/en/default/search/search-help.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/search-help.html.tmpl
deleted file mode 100644
index a087927..0000000
--- a/Websites/bugs.webkit.org/template/en/default/search/search-help.html.tmpl
+++ /dev/null
@@ -1,107 +0,0 @@
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Gervase Markham <gerv@gerv.net>
- #%]
-
-[% help_html = [
-{ id => "short_desc_type",
- html => "The type of summary search you would like" },
-{ id => "short_desc",
- html => "The $terms.bug summary is a short sentence which succinctly
- describes <br> what the $terms.bug is about." },
-{ id => "classification",
- html => "$terms.Bugs are categorised into Classifications, Products and Components. classifications is the<br>
- top-level categorisation." },
-{ id => "product",
- html => Param('useclassification') ?
- "$terms.Bugs are categorised into Products and Components. Select a Classification to narrow down this list" :
- "$terms.Bugs are categorised into Products and Components. Product is
- the<br>top-level categorisation." },
-{ id => "component",
- html => "Components are second-level categories; each belongs to a<br>
- particular Product. Select a Product to narrow down this list." },
-{ id => "version",
- html => "The version field defines the version of the software the
- $terms.bug<br>was found in." },
-{ id => "target_milestone",
- html => "The target_milestone field is used to define when the engineer<br>
- the $terms.bug is assigned to expects to fix it." },
-{ id => "long_desc",
- html => "$terms.Bugs have comments added to them by $terms.Bugzilla users.
- You can<br>search for some text in those comments." },
-{ id => "long_desc_type",
- html => "The type of comment search you would like" },
-{ id => "bug_file_loc",
- html => "$terms.Bugs can have a URL associated with them - for example, a
- pointer<br>to a web site where the problem is seen." },
-{ id => "bug_file_loc_type",
- html => "The type of URL search you would like" },
-{ id => "status_whiteboard",
- html => "Each $terms.bug has a free-form single line text entry box for
- adding<br>tags and status information." },
-{ id => "status_whiteboard_type",
- html => "The type of whiteboard search you would like" },
-{ id => "keywords",
- html => "You can add keywords from a defined list to $terms.bugs, in order
- to<br>tag and group them." },
-{ id => "keywords_type",
- html => "The type of keyword search you would like" },
-{ id => "bug_status",
- html => "$terms.Abug may be in any of a number of states." },
-{ id => "resolution",
- html => "If $terms.abug is in a resolved state, then one of these reasons
- will<br>be given for its resolution." },
-{ id => "bug_severity",
- html => "How severe the $terms.bug is, or whether it's an enhancement." },
-{ id => "priority",
- html => "Engineers prioritize their $terms.bugs using this field." },
-{ id => "rep_platform",
- html => "The hardware platform the $terms.bug was observed on." },
-{ id => "op_sys",
- html => "The operating system the $terms.bug was observed on." },
-{ id => "email1",
- html => "Every $terms.bug has people associated with it in different
- roles.<br>Here, you can search on what people are in what role." },
-{ id => "email2",
- html => "Every $terms.bug has people associated with it in different
- roles.<br>Here, you can search on what people are in what role." },
-{ id => "bug_id",
- html => "You can limit your search to a specific set of $terms.bugs ." },
-{ id => "votes",
- html => "Some $terms.bugs can be voted for, and you can limit your search to
- $terms.bugs<br>with more than a certain number of votes." },
-{ id => "chfield",
- html => "You can search for specific types of change - this field define <br>
- which field you are interested in changes for." },
-{ id => "chfieldfrom",
- html => "Specify the start and end dates either in YYYY-MM-DD format<br>
- (optionally followed by HH:mm, in 24 hour clock), or in relative<br>
- dates such as 1h, 2d, 3w, 4m, 5y, which respectively mean one hour,<br>
- two days, three weeks, four months, or five years ago. 0d is last<br>
- midnight, and 0h, 0w, 0m, 0y is the beginning of this hour, week,<br>
- month, or year." },
-{ id => "chfieldto",
- html => "Specify the start and end dates either in YYYY-MM-DD format<br>
- (optionally followed by HH:mm, in 24 hour clock), or in relative<br>
- dates such as 1h, 2d, 3w, 4m, 5y, which respectively mean one hour,<br>
- two days, three weeks, four months, or five years ago. 0d is last<br>
- midnight, and 0h, 0w, 0m, 0y is the beginning of this hour, week,<br>
- month, or year." },
-{ id => "chfieldvalue",
- html => "The value the field defined above changed to during that time." },
-] %]
diff --git a/Websites/bugs.webkit.org/template/en/default/search/search-plugin.xml.tmpl b/Websites/bugs.webkit.org/template/en/default/search/search-plugin.xml.tmpl
index fe870a8..8564dca 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/search-plugin.xml.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/search-plugin.xml.tmpl
@@ -19,6 +19,10 @@
<ShortName>[% terms.Bugzilla %]</ShortName>
<Description>[% terms.Bugzilla %] Quick Search</Description>
<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABGdBTUEAAK%2FINwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALBSURBVHjaYnxckcEAA3%2F%2B%2FT%2F17LUcH%2Fevf%2F8U%2BHmYGBkZMABAALEgc%2B68%2F3T227cf2tJKKhJLt59n%2FfmbnYnZV1KEhYkJrgYggBghNrz78fPIi3d8uvKBIdb%2FOaWPnzitLc97%2Bc5rFXnhnVO3%2BslLwjUABBDIhnsfPl%2Fj53VO91FX4Gfgkjxw%2Fd%2F6Q49%2FWStqyAj%2B%2B88gZqn%2B9u5rYU52iAaAAGL69%2F%2F%2F2d9%2FYiMclGT4fv76%2BZ9DbO%2FeA39%2BfJHVcvj5l%2Bnh03e%2FWThOvnwLtwEgAAAxAM7%2FBPj8%2FRYkHQYHAf3%2F%2Fv%2F%2B%2Fv8BAVNTUPX18yorLNHE2S8mB%2FT2%2Bq7a4dvu8iUSDgAAAAKICRgUv3%2F8ZGKGeIvpz6eXBvq61lZWLMwMv%2F5zMP7%2FqSAjVFyZ%2FNvZftuT10DnAAQAMQDO%2FwQIBAPz5Or6%2Ff0CBQEAAgT99ubq38z2%2BwT18%2FAM%2F%2BkNDAv6%2FQMCAA1GVVrhMze5h4kCCORpkd9%2F3n74KiHO%2B%2BffX8b%2Ff7m%2BXWP985%2Bf5R%2BPLNdfoK%2F%2F%2Ffv39%2BePj2%2FkZYR0fe0BAgikQZGX%2B9b9FzLS%2FH%2F%2B%2FGVgYGRlZWNlA7nv7z9QuDP8%2B8nw%2FRXjn68Mv4Gu%2FAwQQCCni3FxPLn7nIGZGegfNhYmNjYWZnBMASOakZER6Eumf9%2FYGT4y%2FHx%2F%2BfBFgAAC2cDGzPT99WeGvwzvv%2Fx89vrr%2F39%2FJER4pcT5Gf4z%2FP37D2jtj9%2B%2FL918fmzrKSsWNoAAgiaN%2Fz9%2Fff%2F6S4CP8%2BWbz9vWHfv54aukpAAz0Og%2Ff%2F7%2F%2Bs36668cO3ugED9QJUAAQTUArf7%2F8x87D9vRjcejhPiZhAUYcACAAGI5%2FOHH9ddvXzAxmjz%2B8P8lw4fXn5l4eRlwA4AAYmaTkBFg%2FKvJwfbkwZuXN57y%2Fv%2F34stXGR4uRmxpGwgAAgwA4%2FkfrfCWvLQAAAAASUVORK5CYII%3D</Image>
+[% IF favicon %]
+ <Image width="16" height="16">data:image/x-icon;base64,[% favicon FILTER base64 %]</Image>
+[% ELSE %]
+ <Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABGdBTUEAAK%2FINwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALBSURBVHjaYnxckcEAA3%2F%2B%2FT%2F17LUcH%2Fevf%2F8U%2BHmYGBkZMABAALEgc%2B68%2F3T227cf2tJKKhJLt59n%2FfmbnYnZV1KEhYkJrgYggBghNrz78fPIi3d8uvKBIdb%2FOaWPnzitLc97%2Bc5rFXnhnVO3%2BslLwjUABBDIhnsfPl%2Fj53VO91FX4Gfgkjxw%2Fd%2F6Q49%2FWStqyAj%2B%2B88gZqn%2B9u5rYU52iAaAAGL69%2F%2F%2F2d9%2FYiMclGT4fv76%2BZ9DbO%2FeA39%2BfJHVcvj5l%2Bnh03e%2FWThOvnwLtwEgAAAxAM7%2FBPj8%2FRYkHQYHAf3%2F%2Fv%2F%2B%2Fv8BAVNTUPX18yorLNHE2S8mB%2FT2%2Bq7a4dvu8iUSDgAAAAKICRgUv3%2F8ZGKGeIvpz6eXBvq61lZWLMwMv%2F5zMP7%2FqSAjVFyZ%2FNvZftuT10DnAAQAMQDO%2FwQIBAPz5Or6%2Ff0CBQEAAgT99ubq38z2%2BwT18%2FAM%2F%2BkNDAv6%2FQMCAA1GVVrhMze5h4kCCORpkd9%2F3n74KiHO%2B%2BffX8b%2Ff7m%2BXWP985%2Bf5R%2BPLNdfoK%2F%2F%2Ffv39%2BePj2%2FkZYR0fe0BAgikQZGX%2B9b9FzLS%2FH%2F%2B%2FGVgYGRlZWNlA7nv7z9QuDP8%2B8nw%2FRXjn68Mv4Gu%2FAwQQCCni3FxPLn7nIGZGegfNhYmNjYWZnBMASOakZER6Eumf9%2FYGT4y%2FHx%2F%2BfBFgAAC2cDGzPT99WeGvwzvv%2Fx89vrr%2F39%2FJER4pcT5Gf4z%2FP37D2jtj9%2B%2FL918fmzrKSsWNoAAgiaN%2Fz9%2Fff%2F6S4CP8%2BWbz9vWHfv54aukpAAz0Og%2Ff%2F7%2F%2Bs36668cO3ugED9QJUAAQTUArf7%2F8x87D9vRjcejhPiZhAUYcACAAGI5%2FOHH9ddvXzAxmjz%2B8P8lw4fXn5l4eRlwA4AAYmaTkBFg%2FKvJwfbkwZuXN57y%2Fv%2F34stXGR4uRmxpGwgAAgwA4%2FkfrfCWvLQAAAAASUVORK5CYII%3D</Image>
+[% END %]
<Url type="text/html" method="GET" template="[% urlbase FILTER xml %]buglist.cgi?quicksearch={searchTerms}"/>
</OpenSearchDescription>
diff --git a/Websites/bugs.webkit.org/template/en/default/search/search-report-graph.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/search-report-graph.html.tmpl
index 61dd3b5..3c894cf 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/search-report-graph.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/search-report-graph.html.tmpl
@@ -32,8 +32,10 @@
[% PROCESS global/header.html.tmpl
title = "Generate Graphical Report"
onload = "doOnSelectProduct(0); chartTypeChanged()"
+ yui = [ 'autocomplete', 'calendar' ]
javascript = js_data
- javascript_urls = [ "js/productform.js" ]
+ javascript_urls = [ "js/util.js", "js/productform.js", "js/TUI.js", "js/field.js" ]
+ style_urls = [ "skins/standard/search_form.css" ]
doc_section = "reporting.html#reports"
%]
@@ -63,7 +65,7 @@
[% button_name = "Generate Report" %]
-<form method="get" action="report.cgi" name="reportform">
+<form method="get" action="report.cgi" name="reportform" id="reportform">
<table align="center">
<tr>
@@ -128,14 +130,13 @@
[% PROCESS search/form.html.tmpl %]
-<br>
-<input type="submit" id="[% button_name FILTER css_class_quote %]"
- value="[% button_name FILTER html %]">
-<input type="hidden" name="action" value="wrap">
-<hr>
-
[% PROCESS "search/boolean-charts.html.tmpl" %]
+ <div id="knob">
+ <input type="submit" id="[% button_name FILTER css_class_quote %]"
+ value="[% button_name FILTER html %]">
+ <input type="hidden" name="action" value="wrap">
+ </div>
</form>
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/search/search-report-select.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/search-report-select.html.tmpl
index e893bf6..5e5db06 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/search-report-select.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/search-report-select.html.tmpl
@@ -26,22 +26,25 @@
[% PROCESS "global/field-descs.none.tmpl" %]
[% BLOCK select %]
- [% rep_fields = ["classification", "product", "component", "version", "rep_platform",
- "op_sys", "bug_status", "resolution", "bug_severity",
- "priority", "target_milestone", "assigned_to",
- "reporter", "qa_contact", "votes" ] %]
+ [% Hook.process('rep_fields', 'search/search-report-select.html.tmpl') %]
<select name="[% name FILTER html %]">
<option value=""><none></option>
- [% FOREACH field = rep_fields %]
+ [% FOREACH field = report_columns.keys.sort %]
[% NEXT IF field == "classification" AND !Param('useclassification') %]
[% NEXT IF field == "target_milestone" AND !Param('usetargetmilestone') %]
[% NEXT IF field == "qa_contact" AND !Param('useqacontact') %]
- [% NEXT IF field == "votes" AND !Param('usevotes') %]
<option value="[% field FILTER html %]"
[% " selected" IF default.$name.0 == field %]>
[% field_descs.$field || field FILTER html %]</option>
[% END %]
+
+ [%# Single-select fields are also valid column names. %]
+ [% FOREACH field = custom_fields %]
+ <option value="[% field.name FILTER html %]"
+ [% " selected" IF default.$name.0 == field.name %]>
+ [% field.description FILTER html %]</option>
+ [% END %]
</select>
[% END %]
diff --git a/Websites/bugs.webkit.org/template/en/default/search/search-report-table.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/search-report-table.html.tmpl
index 55d62a1..7e087e7 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/search-report-table.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/search-report-table.html.tmpl
@@ -32,8 +32,10 @@
[% PROCESS global/header.html.tmpl
title = "Generate Tabular Report"
onload = "doOnSelectProduct(0)"
+ yui = [ 'autocomplete', 'calendar' ]
javascript = js_data
- javascript_urls = [ "js/productform.js" ]
+ javascript_urls = [ "js/util.js", "js/productform.js", "js/TUI.js", "js/field.js" ]
+ style_urls = [ "skins/standard/search_form.css" ]
doc_section = "reporting.html#reports"
%]
@@ -46,7 +48,7 @@
[% button_name = "Generate Report" %]
-<form method="get" action="report.cgi" name="reportform">
+<form method="get" action="report.cgi" name="reportform" id="reportform">
<table align="center">
<tr>
@@ -78,17 +80,16 @@
<hr>
-[% PROCESS search/form.html.tmpl %]
+ [% PROCESS search/form.html.tmpl %]
-<br>
-<input type="submit" id="[% button_name FILTER css_class_quote %]"
- value="[% button_name FILTER html %]">
-<input type="hidden" name="format" value="table">
-<input type="hidden" name="action" value="wrap">
-<hr>
+ [% PROCESS "search/boolean-charts.html.tmpl" %]
-[% PROCESS "search/boolean-charts.html.tmpl" %]
-
+ <div id="knob">
+ <input type="submit" id="[% button_name FILTER css_class_quote %]"
+ value="[% button_name FILTER html %]">
+ <input type="hidden" name="format" value="table">
+ <input type="hidden" name="action" value="wrap">
+ </div>
</form>
[% PROCESS global/footer.html.tmpl %]
diff --git a/Websites/bugs.webkit.org/template/en/default/search/search-specific.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/search-specific.html.tmpl
index d35d600..9ef2994 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/search-specific.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/search-specific.html.tmpl
@@ -21,7 +21,8 @@
[% PROCESS global/variables.none.tmpl %]
[% PROCESS global/header.html.tmpl
- title = "Find a Specific " _ terms.Bug
+ title = "Simple Search"
+ style_urls = [ "skins/standard/search_form.css" ]
%]
[% WRAPPER search/tabs.html.tmpl %]
@@ -42,11 +43,11 @@
<input type="hidden" name="query_format" value="specific">
<input type="hidden" name="order" value="relevance desc">
-<table>
+<table summary="Search fields" class="bz_simple_search_form">
<tr>
- <td align="right" valign="baseline">
- <b><label for="bug_status">Status:</label></b>
- </td>
+ <th>
+ <label for="bug_status">[% field_descs.bug_status FILTER html %]:</label>
+ </th>
<td>
<select name="bug_status" id="bug_status">
[% statuses = [ { name = 'open', label = "Open" },
@@ -62,9 +63,9 @@
</td>
</tr>
<tr>
- <td align="right" valign="baseline">
- <b><label for="product">Product:</label></b>
- </td>
+ <th>
+ <label for="product">[% field_descs.product FILTER html %]:</label>
+ </th>
<td>
<select name="product" id="product">
<option value="">All</option>
@@ -74,7 +75,7 @@
[% FOREACH p = user.get_selectable_products(c.id) %]
[% IF p.components.size %]
<option value="[% p.name FILTER html %]"
- [% " selected" IF lsearch(default.product, p.name) != -1 %]>
+ [% " selected" IF default.product.contains(p.name) %]>
[% p.name FILTER html %]
</option>
[% END %]
@@ -84,7 +85,7 @@
[% ELSE %]
[% FOREACH p = product %]
<option value="[% p.name FILTER html %]"
- [% " selected" IF lsearch(default.product, p.name) != -1 %]>
+ [% " selected" IF default.product.contains(p.name) %]>
[% p.name FILTER html %]
</option>
[% END %]
@@ -93,9 +94,9 @@
</td>
</tr>
<tr>
- <td align="right" valign="baseline">
- <b><label for="content">Words:</label></b>
- </td>
+ <th>
+ <label for="content">Words:</label>
+ </th>
<td>
<input name="content" size="40" id="content"
value="[% default.content.0 FILTER html %]">
@@ -109,7 +110,7 @@
<td></td>
<td>
- [% IF Param('specific_search_allow_empty_words') %]
+ [% IF Param('search_allow_no_criteria') %]
<input type="submit" id="search" value="Search">
[% ELSE %]
<input type="submit" id="search" value="Search"
diff --git a/Websites/bugs.webkit.org/template/en/default/search/tabs.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/tabs.html.tmpl
index 6676f05..119b30f 100644
--- a/Websites/bugs.webkit.org/template/en/default/search/tabs.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/search/tabs.html.tmpl
@@ -24,7 +24,7 @@
#%]
[% WRAPPER global/tabs.html.tmpl
- tabs = [ { name => 'specific', label => "Find a Specific $terms.Bug",
+ tabs = [ { name => 'specific', label => "Simple Search",
link => "query.cgi?format=specific" },
{ name => 'advanced', label => "Advanced Search",
link => "query.cgi?format=advanced" } ]
diff --git a/Websites/bugs.webkit.org/template/en/default/search/type-select.html.tmpl b/Websites/bugs.webkit.org/template/en/default/search/type-select.html.tmpl
new file mode 100644
index 0000000..6da8820
--- /dev/null
+++ b/Websites/bugs.webkit.org/template/en/default/search/type-select.html.tmpl
@@ -0,0 +1,30 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is the San Jose State
+ # University Foundation. Portions created by the Initial Developer are
+ # Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+<select name="[% name FILTER html %]" title="Search type"
+ class="[% class FILTER css_class_quote %]">
+ [% FOREACH type = types %]
+ <option value="[% type FILTER html %]"
+ [%- ' selected="selected"' IF type == selected %]>
+ [%- search_descs.$type FILTER html %]</option>
+ [% END %]
+</select>
diff --git a/Websites/bugs.webkit.org/template/en/default/setup/strings.txt.pl b/Websites/bugs.webkit.org/template/en/default/setup/strings.txt.pl
index f1b5008..36c3fe2 100644
--- a/Websites/bugs.webkit.org/template/en/default/setup/strings.txt.pl
+++ b/Websites/bugs.webkit.org/template/en/default/setup/strings.txt.pl
@@ -28,12 +28,90 @@
%strings = (
any => 'any',
+ apachectl_failed => <<END,
+WARNING: We could not check the configuration of Apache. This sometimes
+happens when you are not running checksetup.pl as ##root##. To see the
+problem we ran into, run: ##command##
+END
+ bad_executable => 'not a valid executable: ##bin##',
blacklisted => '(blacklisted)',
+ bz_schema_exists_before_220 => <<'END',
+You are upgrading from a version before 2.20, but the bz_schema table
+already exists. This means that you restored a mysqldump into the Bugzilla
+database without first dropping the already-existing Bugzilla database,
+at some point. Whenever you restore a Bugzilla database backup, you must
+always drop the entire database first.
+
+Please drop your Bugzilla database and restore it from a backup that does
+not contain the bz_schema table. If for some reason you cannot do this, you
+can connect to your MySQL database and drop the bz_schema table, as a last
+resort.
+END
checking_for => 'Checking for',
checking_dbd => 'Checking available perl DBD modules...',
checking_optional => 'The following Perl modules are optional:',
checking_modules => 'Checking perl modules...',
+ chmod_failed => '##path##: Failed to change permissions: ##error##',
+ chown_failed => '##path##: Failed to change ownership: ##error##',
+ commands_dbd => <<EOT,
+YOU MUST RUN ONE OF THE FOLLOWING COMMANDS (depending on which database
+you use):
+EOT
+ commands_optional => 'COMMANDS TO INSTALL OPTIONAL MODULES:',
+ commands_required => <<EOT,
+COMMANDS TO INSTALL REQUIRED MODULES (You *must* run all these commands
+and then re-run checksetup.pl):
+EOT
+ continue_without_answers => <<'END',
+Re-run checksetup.pl in interactive mode (without an 'answers' file)
+to continue.
+END
+ cpan_bugzilla_home =>
+ "WARNING: Using the Bugzilla directory as the CPAN home.",
+ db_enum_setup => "Setting up choices for standard drop-down fields:",
+ db_schema_init => "Initializing bz_schema...",
+ db_table_new => "Adding new table ##table##...",
+ db_table_setup => "Creating tables...",
done => 'done.',
+ enter_or_ctrl_c => "Press Enter to continue or Ctrl-C to exit...",
+ error_localconfig_read => <<'END',
+An error has occurred while reading the ##localconfig## file. The text of
+the error message is:
+
+##error##
+
+Please fix the error in the localconfig file. Alternately, rename your
+localconfig file and re-run checksetup.pl to have it create a new
+localconfig file:
+
+ $ mv -f localconfig localconfig.old
+ $ ./checksetup.pl
+END
+ extension_must_return_name => <<END,
+##file## returned ##returned##, which is not a valid name for an extension.
+Extensions must return their name, not <code>1</code> or a number. See
+the documentation of Bugzilla::Extension for details.
+END
+ feature_auth_ldap => 'LDAP Authentication',
+ feature_auth_radius => 'RADIUS Authentication',
+ feature_graphical_reports => 'Graphical Reports',
+ feature_html_desc => 'More HTML in Product/Group Descriptions',
+ feature_inbound_email => 'Inbound Email',
+ feature_jobqueue => 'Mail Queueing',
+ feature_jsonrpc => 'JSON-RPC Interface',
+ feature_jsonrpc_faster => 'Make JSON-RPC Faster',
+ feature_new_charts => 'New Charts',
+ feature_old_charts => 'Old Charts',
+ feature_mod_perl => 'mod_perl',
+ feature_moving => 'Move Bugs Between Installations',
+ feature_patch_viewer => 'Patch Viewer',
+ feature_smtp_auth => 'SMTP Authentication',
+ feature_updates => 'Automatic Update Notifications',
+ feature_xmlrpc => 'XML-RPC Interface',
+ feature_detect_charset => 'Automatic charset detection for text attachments',
+
+ file_remove => 'Removing ##name##...',
+ file_rename => 'Renaming ##from## to ##to##...',
header => "* This is Bugzilla ##bz_ver## on perl ##perl_ver##\n"
. "* Running on ##os_name## ##os_ver##",
install_all => <<EOT,
@@ -52,18 +130,315 @@
EOT
install_module => 'Installing ##module## version ##version##...',
+ installation_failed => '*** Installation aborted. Read the messages above. ***',
+ install_no_compiler => <<END,
+ERROR: Using install-module.pl requires that you install a compiler, such as
+gcc.
+END
+ install_no_make => <<END,
+ERROR: Using install-module.pl requires that you install "make".
+END
+ lc_new_vars => <<'END',
+This version of Bugzilla contains some variables that you may want to
+change and adapt to your local settings. The following variables are
+new to ##localconfig## since you last ran checksetup.pl:
+
+##new_vars##
+
+Please edit the file ##localconfig## and then re-run checksetup.pl
+to complete your installation.
+END
+ lc_old_vars => <<'END',
+The following variables are no longer used in ##localconfig##, and
+have been moved to ##old_file##: ##vars##
+END
+ localconfig_create_htaccess => <<'END',
+If you are using Apache as your web server, Bugzilla can create .htaccess
+files for you, which will keep this file (localconfig) and other
+confidential files from being read over the web.
+
+If this is set to 1, checksetup.pl will create .htaccess files if
+they don't exist.
+
+If this is set to 0, checksetup.pl will not create .htaccess files.
+END
+ localconfig_cvsbin => <<'END',
+If you want to use the CVS integration of the Patch Viewer, please specify
+the full path to the "cvs" executable here.
+END
+ localconfig_db_check => <<'END',
+Should checksetup.pl try to verify that your database setup is correct?
+With some combinations of database servers/Perl modules/moonphase this
+doesn't work, and so you can try setting this to 0 to make checksetup.pl
+run.
+END
+ localconfig_db_driver => <<'END',
+What SQL database to use. Default is mysql. List of supported databases
+can be obtained by listing Bugzilla/DB directory - every module corresponds
+to one supported database and the name of the module (before ".pm")
+corresponds to a valid value for this variable.
+END
+ localconfig_db_host => <<'END',
+The DNS name or IP address of the host that the database server runs on.
+END
+ localconfig_db_name => <<'END',
+The name of the database. For Oracle, this is the database's SID. For
+SQLite, this is a name (or path) for the DB file.
+END
+ localconfig_db_pass => <<'END',
+Enter your database password here. It's normally advisable to specify
+a password for your bugzilla database user.
+If you use apostrophe (') or a backslash (\) in your password, you'll
+need to escape it by preceding it with a '\' character. (\') or (\)
+(It is far simpler to just not use those characters.)
+END
+ localconfig_db_port => <<'END',
+Sometimes the database server is running on a non-standard port. If that's
+the case for your database server, set this to the port number that your
+database server is running on. Setting this to 0 means "use the default
+port for my database server."
+END
+ localconfig_db_sock => <<'END',
+MySQL Only: Enter a path to the unix socket for MySQL. If this is
+blank, then MySQL's compiled-in default will be used. You probably
+want that.
+END
+ localconfig_db_user => "Who we connect to the database as.",
+ localconfig_diffpath => <<'END',
+For the "Difference Between Two Patches" feature to work, we need to know
+what directory the "diff" bin is in. (You only need to set this if you
+are using that feature of the Patch Viewer.)
+END
+ localconfig_index_html => <<'END',
+Most web servers will allow you to use index.cgi as a directory
+index, and many come preconfigured that way, but if yours doesn't
+then you'll need an index.html file that provides redirection
+to index.cgi. Setting $index_html to 1 below will allow
+checksetup.pl to create an index.html for you if it doesn't exist.
+NOTE: checksetup.pl will not replace an existing file, so if you
+ wish to have checksetup.pl create one for you, you must
+ make sure that index.html doesn't already exist.
+END
+ localconfig_interdiffbin => <<'END',
+If you want to use the "Difference Between Two Patches" feature of the
+Patch Viewer, please specify the full path to the "interdiff" executable
+here.
+END
+ localconfig_site_wide_secret => <<'END',
+This secret key is used by your installation for the creation and
+validation of encrypted tokens. These tokens are used to implement
+security features in Bugzilla, to protect against certain types of attacks.
+A random string is generated by default. It's very important that this key
+is kept secret. It also must be very long.
+END
+ localconfig_use_suexec => <<'END',
+Set this to 1 if Bugzilla runs in an Apache SuexecUserGroup environment.
+
+If your web server runs control panel software (cPanel, Plesk or similar),
+or if your Bugzilla is to run in a shared hosting environment, then you are
+almost certainly in an Apache SuexecUserGroup environment.
+
+If this is a Windows box, ignore this setting, as it does nothing.
+
+If set to 0, checksetup.pl will set file permissions appropriately for
+a normal webserver environment.
+
+If set to 1, checksetup.pl will set file permissions so that Bugzilla
+works in a SuexecUserGroup environment.
+END
+ localconfig_webservergroup => <<'END',
+The name of the group that your web server runs as. On Red Hat
+distributions, this is usually "apache". On Debian/Ubuntu, it is
+usually "www-data".
+
+If you have use_suexec turned on below, then this is instead the name
+of the group that your web server switches to to run cgi files.
+
+If this is a Windows machine, ignore this setting, as it does nothing.
+
+If you do not have access to the group your scripts will run under,
+set this to "". If you do set this to "", then your Bugzilla installation
+will be _VERY_ insecure, because some files will be world readable/writable,
+and so anyone who can get local access to your machine can do whatever they
+want. You should only have this set to "" if this is a testing installation
+and you cannot set this up any other way. YOU HAVE BEEN WARNED!
+
+If you set this to anything other than "", you will need to run checksetup.pl
+as ##root## or as a user who is a member of the specified group.
+END
max_allowed_packet => <<EOT,
WARNING: You need to set the max_allowed_packet parameter in your MySQL
configuration to at least ##needed##. Currently it is set to ##current##.
You can set this parameter in the [mysqld] section of your MySQL
configuration file.
EOT
+ min_version_required => "Minimum version required: ",
+
+# Note: When translating these "modules" messages, don't change the formatting
+# if possible, because there is hardcoded formatting in
+# Bugzilla::Install::Requirements to match the box formatting.
+ modules_message_apache => <<END,
+***********************************************************************
+* APACHE MODULES *
+***********************************************************************
+* Normally, when Bugzilla is upgraded, all Bugzilla users have to *
+* clear their browser cache or Bugzilla will break. If you enable *
+* certain modules in your Apache configuration (usually called *
+* httpd.conf or apache2.conf) then your users will not have to clear *
+* their caches when you upgrade Bugzilla. The modules you need to *
+* enable are: *
+* *
+END
+ modules_message_db => <<EOT,
+***********************************************************************
+* DATABASE ACCESS *
+***********************************************************************
+* In order to access your database, Bugzilla requires that the *
+* correct "DBD" module be installed for the database that you are *
+* running. See below for the correct command to run to install the *
+* appropriate module for your database. *
+EOT
+ modules_message_optional => <<EOT,
+***********************************************************************
+* OPTIONAL MODULES *
+***********************************************************************
+* Certain Perl modules are not required by Bugzilla, but by *
+* installing the latest version you gain access to additional *
+* features. *
+* *
+* The optional modules you do not have installed are listed below, *
+* with the name of the feature they enable. Below that table are the *
+* commands to install each module. *
+EOT
+ modules_message_required => <<EOT,
+***********************************************************************
+* REQUIRED MODULES *
+***********************************************************************
+* Bugzilla requires you to install some Perl modules which are either *
+* missing from your system, or the version on your system is too old. *
+* See below for commands to install these modules. *
+EOT
+
module_found => "found v##ver##",
module_not_found => "not found",
module_ok => 'ok',
module_unknown_version => "found unknown version",
+ no_such_module => "There is no Perl module on CPAN named ##module##.",
+ mysql_innodb_disabled => <<'END',
+InnoDB is disabled in your MySQL installation.
+Bugzilla requires InnoDB to be enabled.
+Please enable it and then re-run checksetup.pl.
+END
+ mysql_index_renaming => <<'END',
+We are about to rename old indexes. The estimated time to complete
+renaming is ##minutes## minutes. You cannot interrupt this action once
+it has begun. If you would like to cancel, press Ctrl-C now...
+(Waiting 45 seconds...)
+END
+ mysql_utf8_conversion => <<'END',
+WARNING: We are about to convert your table storage format to UTF-8. This
+ allows Bugzilla to correctly store and sort international characters.
+ However, if you have any non-UTF-8 data in your database,
+ it ***WILL BE DELETED*** by this process. So, before
+ you continue with checksetup.pl, if you have any non-UTF-8
+ data (or even if you're not sure) you should press Ctrl-C now
+ to interrupt checksetup.pl, and run contrib/recode.pl to make all
+ the data in your database into UTF-8. You should also back up your
+ database before continuing. This will affect every single table
+ in the database, even non-Bugzilla tables.
+
+ If you ever used a version of Bugzilla before 2.22, we STRONGLY
+ recommend that you stop checksetup.pl NOW and run contrib/recode.pl.
+END
+ no_checksetup_from_cgi => <<END,
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+ <head>
+ <title>checksetup.pl cannot run from a web browser</title>
+ </head>
+
+ <body>
+ <h1>checksetup.pl cannot run from a web browser</h1>
+ <p>
+ You <b>must not</b> execute this script from your web browser.
+ To install or upgrade Bugzilla, run this script from
+ the command-line (e.g. <tt>bash</tt> or <tt>ssh</tt> on Linux
+ or <tt>cmd.exe</tt> on Windows), and follow instructions given there.
+ </p>
+
+ <p>
+ For more information on how to install Bugzilla, please
+ <a href="http://www.bugzilla.org/docs/">read the documentation</a>
+ available on the official Bugzilla website.
+ </p>
+ </body>
+</html>
+END
+ patchutils_missing => <<'END',
+OPTIONAL NOTE: If you want to be able to use the 'difference between two
+patches' feature of Bugzilla (which requires the PatchReader Perl module
+as well), you should install patchutils from:
+
+ http://cyberelk.net/tim/patchutils/
+END
+ ppm_repo_add => <<EOT,
+***********************************************************************
+* Note For Windows Users *
+***********************************************************************
+* In order to install the modules listed below, you first have to run *
+* the following command as an Administrator: *
+* *
+* ppm repo add theory58S ##theory_url##
+EOT
+ ppm_repo_up => <<EOT,
+* *
+* Then you have to do (also as an Administrator): *
+* *
+* ppm repo up theory58S *
+* *
+* Do that last command over and over until you see "theory58S" at the *
+* top of the displayed list. *
+EOT
template_precompile => "Precompiling templates...",
+ template_removal_failed => <<END,
+WARNING: The directory '##template_cache##' could not be removed.
+ It has been moved into '##deleteme##', which should be
+ deleted manually to conserve disk space.
+END
template_removing_dir => "Removing existing compiled templates...",
+ update_cf_invalid_name =>
+ "Removing custom field '##field##', because it has an invalid name...",
+ update_flags_bad_name => <<'END',
+"##flag##" is not a valid name for a flag. Rename it to not have any spaces
+or commas.
+END
+ update_nomail_bad => <<'END',
+WARNING: The following users were listed in ##data##/nomail, but do
+not have an account here. The unmatched entries have been moved to
+##data##/nomail.bad:
+END
+ update_summary_truncate_comment =>
+ "The original value of the Summary field was longer than 255"
+ . " characters, and so it was truncated during an upgrade."
+ . " The original summary was:\n\n##summary##",
+ update_summary_truncated => <<'END',
+WARNING: Some of your bugs had summaries longer than 255 characters.
+They have had their original summary copied into a comment, and then
+the summary was truncated to 255 characters. The affected bug numbers were:
+END
+ update_quips => <<'END',
+Quips are now stored in the database, rather than in an external file.
+The quips previously stored in ##data##/comments have been copied into
+the database, and that file has been renamed to ##data##/comments.bak
+You may delete the renamed file once you have confirmed that all your
+quips were moved successfully.
+END
+ update_queries_to_tags => "Populating the new 'tag' table:",
+ webdot_bad_htaccess => <<END,
+WARNING: Dependency graph images are not accessible.
+Delete ##dir##/.htaccess and re-run checksetup.pl.
+END
);
1;
diff --git a/Websites/bugs.webkit.org/template/en/default/sidebar.xul.tmpl b/Websites/bugs.webkit.org/template/en/default/sidebar.xul.tmpl
deleted file mode 100644
index 31c1472..0000000
--- a/Websites/bugs.webkit.org/template/en/default/sidebar.xul.tmpl
+++ /dev/null
@@ -1,130 +0,0 @@
-[%# -*- mode: sgml -*- %]
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
- # Scott Collins <scc@mozilla.org>
- # Christopher A. Aillon <christopher@aillon.com>
- #%]
-
-[% PROCESS global/variables.none.tmpl %]
-
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
-<?xml-stylesheet href="[% urlbase FILTER xml %]skins/standard/panel.css" type="text/css"?>
-<window
- xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- xmlns:html="http://www.w3.org/1999/xhtml"
- orient="vertical"
- onload="document.getElementById('query-field').addEventListener('keypress', initial_keypress_handler, true)">
-
-<script type="application/x-javascript"><![CDATA[
-
-function load_absolute_url( aAbsoluteURL ) {
- content.location = aAbsoluteURL;
-}
-
-function load_relative_url( aRelativeURL ) {
- aRelativeURL = '[% urlbase FILTER xml %]' + aRelativeURL;
- _content.location = aRelativeURL;
-}
-
-function initial_keypress_handler( aEvent ) {
- this.removeAttribute("class");
- this.addEventListener("keypress", normal_keypress_handler, true);
- this.removeEventListener("keypress", initial_keypress_handler, true);
-}
-
-function normal_keypress_handler( aEvent ) {
- if ( aEvent.keyCode == 13 )
- load_relative_url('buglist.cgi?quicksearch=' + this.value);
-}
-
-]]></script>
-
- <textbox id="query-field" class="descriptive-content" value="enter search" onfocus="this.setSelectionRange(0,this.value.length)"/>
-
- <separator class="groove"/>
-
- <box autostretch="never" valign="top">
- <box orient="vertical" flex="1">
- <text class="text-link" onclick="load_relative_url('query.cgi')" value="new search"/>
- <text class="text-link" onclick="load_relative_url('report.cgi')" value="reports"/>
- <text class="text-link" onclick="load_relative_url('enter_bug.cgi')" value="new [% terms.bug %]"/>
- <separator class="thin"/>
-
-[% IF user.id %]
- <text class="text-link" onclick="load_relative_url('userprefs.cgi')" value="edit prefs"/>
- [%- IF user.groups.tweakparams %]
- <text class="text-link" onclick="load_relative_url('editparams.cgi')" value="edit params"/>
- <text class="text-link" onclick="load_relative_url('editsettings.cgi')" value="edit default preferences"/>
- [%- END %]
- [%- IF user.groups.editusers || user.can_bless %]
- <text class="text-link" onclick="load_relative_url('editusers.cgi')" value="edit users"/>
- [%- END %]
- [%- IF Param('useclassification') && user.groups.editclassifications %]
- <text class="text-link" onclick="load_relative_url('editclassifications.cgi')" value="edit classifications"/>
- [%- END %]
- [%- IF user.groups.editcomponents %]
- <text class="text-link" onclick="load_relative_url('editcomponents.cgi')" value="edit components"/>
- <text class="text-link" onclick="load_relative_url('editflagtypes.cgi')" value="edit flags"/>
- <text class="text-link" onclick="load_relative_url('editvalues.cgi')" value="edit field values"/>
- [%- END %]
- [%- IF user.groups.creategroups %]
- <text class="text-link" onclick="load_relative_url('editgroups.cgi')" value="edit groups"/>
- [%- END %]
- [%- IF user.groups.editkeywords %]
- <text class="text-link" onclick="load_relative_url('editkeywords.cgi')" value="edit keywords"/>
- [%- END %]
- [%- IF user.groups.bz_canusewhines %]
- <text class="text-link" onclick="load_relative_url('editwhines.cgi')" value="edit whining"/>
- [%- END %]
- [%- IF user.groups.editcomponents %]
- <text class="text-link" onclick="load_relative_url('sanitycheck.cgi')" value="sanity check"/>
- [%- END %]
- [%- IF user.authorizer.can_logout %]
- <text class="text-link" onclick="load_relative_url('relogin.cgi')" value="log out [% user.login FILTER html %]"/>
- [%- END %]
- <separator class="thin"/>
- [%- IF user.showmybugslink %]
- [% filtered_username = user.login FILTER url_quote %]
- <text class="text-link" onclick="load_relative_url('[% Param('mybugstemplate').replace('%userid%', filtered_username) FILTER js FILTER html %]')" value="my [% terms.bugs %]"/>
- [%- END %]
- [%- IF Param('usevotes') %]
- <text class="text-link" onclick="load_relative_url('votes.cgi?action=show_user')" value="my votes"/>
- [%- END %]
-
- [%- FOREACH q = user.queries %]
- <text class="text-link" onclick="load_relative_url('buglist.cgi?cmdtype=runnamed&namedcmd=[% q.name FILTER url_quote %]')" value="[% q.name FILTER html %]"/>
- [% END %]
-
-[% ELSE %]
- <text class="text-link" onclick="load_relative_url('createaccount.cgi')" value="new user"/>
- <text class="text-link" onclick="load_relative_url('index.cgi?GoAheadAndLogIn=1')" value="log in"/>
-[% END %]
-
- </box>
- </box>
-
- <spring flex="1"/>
- <box orient="horizontal">
- <spring flex="1"/>
- <html align="right">
- <html:a class="text-link" href="[% urlbase FILTER xml %]sidebar.cgi">reload</html:a>
- </html>
- </box>
-</window>
diff --git a/Websites/bugs.webkit.org/template/en/default/welcome-admin.html.tmpl b/Websites/bugs.webkit.org/template/en/default/welcome-admin.html.tmpl
index 6e5e36b..289a6bd 100644
--- a/Websites/bugs.webkit.org/template/en/default/welcome-admin.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/welcome-admin.html.tmpl
@@ -41,31 +41,28 @@
parameters for this installation; among others:</p>
<ul>
- <li><a href="editparams.cgi?section=core#maintainer">maintainer</a>, the person
- responsible for this installation if something is running wrong.</li>
-
- <li><a href="editparams.cgi?section=core#urlbase">urlbase</a>, which is the URL
+ <li><a href="editparams.cgi?section=core#urlbase_desc">urlbase</a>, which is the URL
pointing to this installation and which will be used in emails (which is also the
reason you see this page: as long as this parameter is not set, you will see this
page again and again).</li>
- <li><a href="editparams.cgi?section=core#cookiepath">cookiepath</a> is important
+ <li><a href="editparams.cgi?section=core#cookiepath_desc">cookiepath</a> is important
for your browser to manage your cookies correctly.</li>
- <li><a href="editparams.cgi?section=core#utf8">utf8</a> will let you encode all
- texts into UTF-8, if desired (it is strongly recommended to keep this parameter
- turned on).</li>
+ <li><a href="editparams.cgi?section=general#maintainer_desc">maintainer</a>,
+ the person responsible for this installation if something is
+ running wrongly.</li>
</ul>
<p>Also important are the following parameters:</p>
<ul>
- <li><a href="editparams.cgi?section=auth#requirelogin">requirelogin</a>, if turned
+ <li><a href="editparams.cgi?section=auth#requirelogin_desc">requirelogin</a>, if turned
on, will protect your installation from users having no account on this installation.
In other words, users who are not explicitly authenticated with a valid account
cannot see any data. This is what you want if you want to keep your data private.</li>
- <li><a href="editparams.cgi?section=auth#createemailregexp">createemailregexp</a>
+ <li><a href="editparams.cgi?section=auth#createemailregexp_desc">createemailregexp</a>
defines which users are allowed to create an account on this installation. If set
to ".*" (the default), everybody is free to create his own account. If set to
"@mycompany.com$", only users having an account @mycompany.com will be allowed to
@@ -74,7 +71,7 @@
installation, you must absolutely set this parameter to something different from
the default.</li>
- <li><a href="editparams.cgi?section=mta#mail_delivery_method">mail_delivery_method</a>
+ <li><a href="editparams.cgi?section=mta#mail_delivery_method_desc">mail_delivery_method</a>
defines the method used to send emails, such as sendmail or SMTP. You have to set
it correctly to send emails.</li>
</ul>
diff --git a/Websites/bugs.webkit.org/template/en/default/whine/mail.html.tmpl b/Websites/bugs.webkit.org/template/en/default/whine/mail.html.tmpl
index e1df9db..a7bff50 100644
--- a/Websites/bugs.webkit.org/template/en/default/whine/mail.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/whine/mail.html.tmpl
@@ -31,10 +31,6 @@
[% PROCESS global/variables.none.tmpl %]
[% PROCESS 'global/field-descs.none.tmpl' %]
-[%# assignee_login_string is a literal string used for getting the
- # assignee's name out of the bug data %]
-[% SET assignee_login_string="map_assigned_to.login_name" %]
-
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
@@ -44,9 +40,9 @@
</head>
<body bgcolor="#FFFFFF">
- <p align="left">
+ <pre>
[% body FILTER html %]
- </p>
+ </pre>
<p align="left">
[% IF author.login == recipient.login %]
@@ -57,39 +53,42 @@
[% END %]
</p>
+[% IF queries.size %]
+ [% FOREACH query=queries %]
-[% FOREACH query=queries %]
+ <h2>[%+ query.title FILTER html %]</h2>
- <h2>[%+ query.title FILTER html %]</h2>
-
- <table width="100%">
- <tr>
- <th align="left">ID</th>
- <th align="left">Sev</th>
- <th align="left">Pri</th>
- <th align="left">Plt</th>
- <th align="left">Assignee</th>
- <th align="left">Status</th>
- <th align="left">Resolution</th>
- <th align="left">Summary</th>
- </tr>
-
- [% FOREACH bug=query.bugs %]
+ <table width="100%">
<tr>
- <td align="left"><a href="[%+ urlbase FILTER html %]show_bug.cgi?id=
- [%- bug.bug_id %]">[% bug.bug_id %]</a></td>
- <td align="left">[% bug.bug_severity FILTER html %]</td>
- <td align="left">[% bug.priority FILTER html %]</td>
- <td align="left">[% bug.rep_platform FILTER html %]</td>
- <td align="left">[% bug.$assignee_login_string FILTER html %]</td>
- <td align="left">[% get_status(bug.bug_status) FILTER html %]</td>
- <td align="left">[% get_resolution(bug.resolution) FILTER html %]</td>
- <td align="left">[% bug.short_desc FILTER html %]</td>
+ <th align="left">ID</th>
+ <th align="left">Sev</th>
+ <th align="left">Pri</th>
+ <th align="left">Plt</th>
+ <th align="left">Assignee</th>
+ <th align="left">Status</th>
+ <th align="left">Resolution</th>
+ <th align="left">Summary</th>
</tr>
- [% END %]
- </table>
-[% END %]
+ [% FOREACH bug=query.bugs %]
+ <tr>
+ <td align="left"><a href="[%+ urlbase FILTER html %]show_bug.cgi?id=
+ [%- bug.bug_id %]">[% bug.bug_id %]</a></td>
+ <td align="left">[% display_value("bug_severity", bug.bug_severity) FILTER html %]</td>
+ <td align="left">[% display_value("priority", bug.priority) FILTER html %]</td>
+ <td align="left">[% display_value("rep_platform", bug.rep_platform) FILTER html %]</td>
+ <td align="left">[% bug.assigned_to FILTER html %]</td>
+ <td align="left">[% display_value("bug_status", bug.bug_status) FILTER html %]</td>
+ <td align="left">[% display_value("resolution", bug.resolution) FILTER html %]</td>
+ <td align="left">[% bug.short_desc FILTER html %]</td>
+ </tr>
+ [% END %]
+ </table>
+ [% END %]
+[% ELSE %]
+
+ <h3>No [% terms.bugs %] were found that matched the search criteria.</h3>
+[% END %]
</body>
</html>
diff --git a/Websites/bugs.webkit.org/template/en/default/whine/mail.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/whine/mail.txt.tmpl
index 4375ee1..13216d8 100644
--- a/Websites/bugs.webkit.org/template/en/default/whine/mail.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/whine/mail.txt.tmpl
@@ -31,10 +31,6 @@
[% PROCESS global/variables.none.tmpl %]
[% PROCESS 'global/field-descs.none.tmpl' %]
-[%# assignee_login_string is a literal string used for getting the
- # assignee's name out of the bug data %]
-[% SET assignee_login_string="map_assigned_to.login_name" %]
-
[% body %]
[% IF author.login == recipient.login %]
@@ -44,26 +40,29 @@
This search was scheduled by [% author.login %].
[% END %]
-
-[% FOREACH query=queries %]
+[% IF queries.size %]
+ [% FOREACH query=queries %]
[%+ query.title +%]
[%+ "-" FILTER repeat(query.title.length) %]
- [% FOREACH bug=query.bugs %]
+ [% FOREACH bug=query.bugs %]
[% terms.Bug +%] [%+ bug.bug_id %]:
[%+ urlbase %]show_bug.cgi?id=[% bug.bug_id +%]
- Priority: [%+ bug.priority -%]
- Severity: [%+ bug.bug_severity -%]
- Platform: [%+ bug.rep_platform %]
- Assignee: [%+ bug.$assignee_login_string %]
- Status: [%+ get_status(bug.bug_status) %]
- [%- IF bug.resolution -%] Resolution: [% get_resolution(bug.resolution) -%]
- [%- END %]
+ Priority: [%+ display_value("priority", bug.priority) -%]
+ Severity: [%+ display_value("bug_severity", bug.bug_severity) -%]
+ Platform: [%+ display_value("rep_platform", bug.rep_platform) %]
+ Assignee: [%+ bug.assigned_to %]
+ Status: [%+ display_value("bug_status", bug.bug_status) %]
+ [%- IF bug.resolution -%] Resolution: [% display_value("resolution", bug.resolution) -%]
+ [%- END %]
Summary: [% bug.short_desc %]
- [% END %]
+ [% END %]
+ [% END %]
+[% ELSE %]
+
+ No [% terms.bugs %] were found that matched the search criteria.
[% END %]
-
diff --git a/Websites/bugs.webkit.org/template/en/default/whine/multipart-mime.txt.tmpl b/Websites/bugs.webkit.org/template/en/default/whine/multipart-mime.txt.tmpl
index 0c22575..7fdfdb0 100644
--- a/Websites/bugs.webkit.org/template/en/default/whine/multipart-mime.txt.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/whine/multipart-mime.txt.tmpl
@@ -35,6 +35,7 @@
Subject: [[% terms.Bugzilla %]] [% subject %]
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="[% boundary %]"
+X-Bugzilla-Type: whine
This is a MIME multipart message. It is possible that your mail program
diff --git a/Websites/bugs.webkit.org/template/en/default/whine/schedule.html.tmpl b/Websites/bugs.webkit.org/template/en/default/whine/schedule.html.tmpl
index 0917f47..245a3e4 100644
--- a/Websites/bugs.webkit.org/template/en/default/whine/schedule.html.tmpl
+++ b/Websites/bugs.webkit.org/template/en/default/whine/schedule.html.tmpl
@@ -72,11 +72,7 @@
</p>
<p>
- [% IF Param("timezone") %]
- All times are server local time ([% Param("timezone") FILTER upper %]).
- [% ELSE %]
- All times are server local time.
- [% END %]
+ All times are server local time ([% local_timezone FILTER html %]).
</p>
<form method="post" action="editwhines.cgi">
@@ -128,6 +124,16 @@
</td>
</tr>
+ <tr>
+ <td valign="top" align="right">
+ Send a message even if there are no [% terms.bugs %] in the search result:
+ </td>
+ <td colspan="2">
+ <input type="checkbox" name="event_[% event.key %]_mailifnobugs"
+ [%- IF event.value.mailifnobugs == 1 %] checked [% END %]>
+ </td>
+ </tr>
+
[% IF event.value.schedule.size == 0 %]
<tr>
diff --git a/Websites/bugs.webkit.org/template/en/extension/filterexceptions.pl b/Websites/bugs.webkit.org/template/en/extension/filterexceptions.pl
deleted file mode 100644
index 29e2a1e..0000000
--- a/Websites/bugs.webkit.org/template/en/extension/filterexceptions.pl
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code are the Bugzilla tests.
-#
-# The Initial Developer of the Original Code is Jacob Steenhagen.
-# Portions created by Jacob Steenhagen are
-# Copyright (C) 2001 Jacob Steenhagen. All
-# Rights Reserved.
-#
-# Contributor(s): Gervase Markham <gerv@gerv.net>
-# Joel Peshkin <bugreport@peshkin.net>
-
-# Important! The following classes of directives are excluded in the test,
-# and so do not need to be added here. Doing so will cause warnings.
-# See 008filter.t for more details.
-#
-# Comments - [%#...
-# Directives - [% IF|ELSE|UNLESS|FOREACH...
-# Assignments - [% foo = ...
-# Simple literals - [% " selected" ...
-# Values always used for numbers - [% (i|j|k|n|count) %]
-# Params - [% Param(...
-# Safe functions - [% (time2str)...
-# Safe vmethods - [% foo.size %] [% foo.length %]
-# [% foo.push() %]
-# TT loop variables - [% loop.count %]
-# Already-filtered stuff - [% wibble FILTER html %]
-# where the filter is one of html|csv|js|url_quote|quoteUrls|time|uri|xml|none
-
-%::safe = (
-);
-
diff --git a/Websites/bugs.webkit.org/testserver.pl b/Websites/bugs.webkit.org/testserver.pl
index 3a1ca8a..03e0527 100755
--- a/Websites/bugs.webkit.org/testserver.pl
+++ b/Websites/bugs.webkit.org/testserver.pl
@@ -21,13 +21,7 @@
use strict;
use lib qw(. lib);
-BEGIN {
- my $envpath = $ENV{'PATH'};
- require Bugzilla;
- # $ENV{'PATH'} is required by the 'ps' command to run correctly.
- $ENV{'PATH'} = $envpath;
-}
-
+use Bugzilla;
use Bugzilla::Constants;
use Socket;
@@ -48,7 +42,7 @@
# Try to determine the GID used by the web server.
my @pscmds = ('ps -eo comm,gid', 'ps -acxo command,gid', 'ps -acxo command,rgid');
my $sgid = 0;
-if ($^O !~ /MSWin32/i) {
+if (!ON_WINDOWS) {
foreach my $pscmd (@pscmds) {
open PH, "$pscmd 2>/dev/null |";
while (my $line = <PH>) {
@@ -65,7 +59,8 @@
my $webservergroup = Bugzilla->localconfig->{webservergroup};
if ($webservergroup =~ /^(\d+)$/) {
$webgroupnum = $1;
-} else {
+}
+else {
eval { $webgroupnum = (getgrnam $webservergroup) || 0; };
}
@@ -76,30 +71,33 @@
"WARNING \$webservergroup is set to an empty string.
That is a very insecure practice. Please refer to the
Bugzilla documentation.\n";
- } elsif ($webgroupnum == $sgid) {
+ }
+ elsif ($webgroupnum == $sgid || Bugzilla->localconfig->{use_suexec}) {
print "TEST-OK Webserver is running under group id in \$webservergroup.\n";
- } else {
+ }
+ else {
print
"TEST-WARNING Webserver is running under group id not matching \$webservergroup.
This if the tests below fail, this is probably the problem.
Please refer to the web server configuration section of the Bugzilla guide.
If you are using virtual hosts or suexec, this warning may not apply.\n";
}
-} elsif ($^O !~ /MSWin32/i) {
+}
+elsif (!ON_WINDOWS) {
print
"TEST-WARNING Failed to find the GID for the 'httpd' process, unable
to validate webservergroup.\n";
}
-# Try to fetch a static file (front.png)
+# Try to fetch a static file (padlock.png)
$ARGV[0] =~ s/\/$//;
-my $url = $ARGV[0] . "/skins/standard/index/front.png";
+my $url = $ARGV[0] . "/images/padlock.png";
if (fetch($url)) {
- print "TEST-OK Got front picture.\n";
+ print "TEST-OK Got padlock picture.\n";
} else {
print
-"TEST-FAILED Fetch of skins/standard/index/front.png failed
+"TEST-FAILED Fetch of images/padlock.png failed
Your web server could not fetch $url.
Check your web server configuration and try again.\n";
exit(1);
@@ -140,7 +138,7 @@
# Ensure major versions of GD and libgd match
# Windows's GD module include libgd.dll, guaranteed to match
- if ($^O !~ /MSWin32/i) {
+ if (!ON_WINDOWS) {
my $gdlib = `gdlib-config --version 2>&1` || "";
$gdlib =~ s/\n$//;
if (!$gdlib) {
diff --git a/Websites/bugs.webkit.org/token.cgi b/Websites/bugs.webkit.org/token.cgi
index 39ffc70..b10830b 100755
--- a/Websites/bugs.webkit.org/token.cgi
+++ b/Websites/bugs.webkit.org/token.cgi
@@ -37,6 +37,7 @@
use Bugzilla::Token;
use Bugzilla::User;
+use Date::Format;
use Date::Parse;
my $dbh = Bugzilla->dbh;
@@ -44,6 +45,9 @@
local our $template = Bugzilla->template;
local our $vars = {};
+my $action = $cgi->param('a');
+my $token = $cgi->param('t');
+
Bugzilla->login(LOGIN_OPTIONAL);
################################################################################
@@ -52,47 +56,40 @@
# Throw an error if the form does not contain an "action" field specifying
# what the user wants to do.
-$cgi->param('a') || ThrowCodeError("unknown_action");
-
-# Assign the action to a global variable.
-$::action = $cgi->param('a');
+$action || ThrowUserError('unknown_action');
# If a token was submitted, make sure it is a valid token that exists in the
# database and is the correct type for the action being taken.
-if ($cgi->param('t')) {
- # Assign the token and its SQL quoted equivalent to global variables.
- $::token = $cgi->param('t');
-
- # Make sure the token contains only valid characters in the right amount.
- # validate_password will throw an error if token is invalid
- validate_password($::token);
-
+if ($token) {
Bugzilla::Token::CleanTokenTable();
+ # It's safe to detaint the token as it's used in a placeholder.
+ trick_taint($token);
+
# Make sure the token exists in the database.
my ($tokentype) = $dbh->selectrow_array('SELECT tokentype FROM tokens
- WHERE token = ?', undef, $::token);
+ WHERE token = ?', undef, $token);
$tokentype || ThrowUserError("token_does_not_exist");
# Make sure the token is the correct type for the action being taken.
- if ( grep($::action eq $_ , qw(cfmpw cxlpw chgpw)) && $tokentype ne 'password' ) {
- Bugzilla::Token::Cancel($::token, "wrong_token_for_changing_passwd");
+ if ( grep($action eq $_ , qw(cfmpw cxlpw chgpw)) && $tokentype ne 'password' ) {
+ Bugzilla::Token::Cancel($token, "wrong_token_for_changing_passwd");
ThrowUserError("wrong_token_for_changing_passwd");
}
- if ( ($::action eq 'cxlem')
+ if ( ($action eq 'cxlem')
&& (($tokentype ne 'emailold') && ($tokentype ne 'emailnew')) ) {
- Bugzilla::Token::Cancel($::token, "wrong_token_for_cancelling_email_change");
+ Bugzilla::Token::Cancel($token, "wrong_token_for_cancelling_email_change");
ThrowUserError("wrong_token_for_cancelling_email_change");
}
- if ( grep($::action eq $_ , qw(cfmem chgem))
+ if ( grep($action eq $_ , qw(cfmem chgem))
&& ($tokentype ne 'emailnew') ) {
- Bugzilla::Token::Cancel($::token, "wrong_token_for_confirming_email_change");
+ Bugzilla::Token::Cancel($token, "wrong_token_for_confirming_email_change");
ThrowUserError("wrong_token_for_confirming_email_change");
}
- if (($::action =~ /^(request|confirm|cancel)_new_account$/)
+ if (($action =~ /^(request|confirm|cancel)_new_account$/)
&& ($tokentype ne 'account'))
{
- Bugzilla::Token::Cancel($::token, 'wrong_token_for_creating_account');
+ Bugzilla::Token::Cancel($token, 'wrong_token_for_creating_account');
ThrowUserError('wrong_token_for_creating_account');
}
}
@@ -102,7 +99,7 @@
# their login name and it exists in the database, and that the DB module is in
# the list of allowed verification methods.
my $user_account;
-if ( $::action eq 'reqpw' ) {
+if ( $action eq 'reqpw' ) {
my $login_name = $cgi->param('loginname')
|| ThrowUserError("login_needed_for_password_change");
@@ -115,18 +112,26 @@
|| ThrowUserError('illegal_email_address', {addr => $login_name});
$user_account = Bugzilla::User->check($login_name);
+
+ # Make sure the user account is active.
+ if (!$user_account->is_enabled) {
+ ThrowUserError('account_disabled',
+ {disabled_reason => get_text('account_disabled', {account => $login_name})});
+ }
}
# If the user is changing their password, make sure they submitted a new
# password and that the new password is valid.
my $password;
-if ( $::action eq 'chgpw' ) {
+if ( $action eq 'chgpw' ) {
$password = $cgi->param('password');
defined $password
&& defined $cgi->param('matchpassword')
|| ThrowUserError("require_new_password");
validate_password($password, $cgi->param('matchpassword'));
+ # Make sure that these never show up in the UI under any circumstances.
+ $cgi->delete('password', 'matchpassword');
}
################################################################################
@@ -137,31 +142,28 @@
# determines what the user wants to do. The code below checks the value of
# that variable and runs the appropriate code.
-if ($::action eq 'reqpw') {
+if ($action eq 'reqpw') {
requestChangePassword($user_account);
-} elsif ($::action eq 'cfmpw') {
- confirmChangePassword();
-} elsif ($::action eq 'cxlpw') {
- cancelChangePassword();
-} elsif ($::action eq 'chgpw') {
- changePassword($password);
-} elsif ($::action eq 'cfmem') {
- confirmChangeEmail();
-} elsif ($::action eq 'cxlem') {
- cancelChangeEmail();
-} elsif ($::action eq 'chgem') {
- changeEmail();
-} elsif ($::action eq 'request_new_account') {
- request_create_account();
-} elsif ($::action eq 'confirm_new_account') {
- confirm_create_account();
-} elsif ($::action eq 'cancel_new_account') {
- cancel_create_account();
+} elsif ($action eq 'cfmpw') {
+ confirmChangePassword($token);
+} elsif ($action eq 'cxlpw') {
+ cancelChangePassword($token);
+} elsif ($action eq 'chgpw') {
+ changePassword($token, $password);
+} elsif ($action eq 'cfmem') {
+ confirmChangeEmail($token);
+} elsif ($action eq 'cxlem') {
+ cancelChangeEmail($token);
+} elsif ($action eq 'chgem') {
+ changeEmail($token);
+} elsif ($action eq 'request_new_account') {
+ request_create_account($token);
+} elsif ($action eq 'confirm_new_account') {
+ confirm_create_account($token);
+} elsif ($action eq 'cancel_new_account') {
+ cancel_create_account($token);
} else {
- # If the action that the user wants to take (specified in the "a" form field)
- # is none of the above listed actions, display an error telling the user
- # that we do not understand what they would like to do.
- ThrowCodeError("unknown_action", { action => $::action });
+ ThrowUserError('unknown_action', {action => $action});
}
exit;
@@ -182,16 +184,18 @@
}
sub confirmChangePassword {
- $vars->{'token'} = $::token;
-
+ my $token = shift;
+ $vars->{'token'} = $token;
+
print $cgi->header();
$template->process("account/password/set-forgotten-password.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
-sub cancelChangePassword {
+sub cancelChangePassword {
+ my $token = shift;
$vars->{'message'} = "password_change_canceled";
- Bugzilla::Token::Cancel($::token, $vars->{'message'});
+ Bugzilla::Token::Cancel($token, $vars->{'message'});
print $cgi->header();
$template->process("global/message.html.tmpl", $vars)
@@ -199,7 +203,7 @@
}
sub changePassword {
- my ($password) = @_;
+ my ($token, $password) = @_;
my $dbh = Bugzilla->dbh;
# Create a crypted version of the new password
@@ -207,7 +211,7 @@
# Get the user's ID from the tokens table.
my ($userid) = $dbh->selectrow_array('SELECT userid FROM tokens
- WHERE token = ?', undef, $::token);
+ WHERE token = ?', undef, $token);
# Update the user's password in the profiles table and delete the token
# from the tokens table.
@@ -216,7 +220,7 @@
SET cryptpassword = ?
WHERE userid = ?},
undef, ($cryptedpassword, $userid) );
- $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $::token);
+ $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $token);
$dbh->bz_commit_transaction();
Bugzilla->logout_user_by_id($userid);
@@ -229,22 +233,22 @@
}
sub confirmChangeEmail {
- # Return HTTP response headers.
+ my $token = shift;
+ $vars->{'token'} = $token;
+
print $cgi->header();
-
- $vars->{'token'} = $::token;
-
$template->process("account/email/confirm.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
sub changeEmail {
+ my $token = shift;
my $dbh = Bugzilla->dbh;
# Get the user's ID from the tokens table.
my ($userid, $eventdata) = $dbh->selectrow_array(
q{SELECT userid, eventdata FROM tokens
- WHERE token = ?}, undef, $::token);
+ WHERE token = ?}, undef, $token);
my ($old_email, $new_email) = split(/:/,$eventdata);
# Check the user entered the correct old email address
@@ -255,7 +259,7 @@
# confirmed initially so cancel token if it is not still available
if (! is_available_username($new_email,$old_email)) {
$vars->{'email'} = $new_email; # Needed for Bugzilla::Token::Cancel's mail
- Bugzilla::Token::Cancel($::token, "account_exists", $vars);
+ Bugzilla::Token::Cancel($token, "account_exists", $vars);
ThrowUserError("account_exists", { email => $new_email } );
}
@@ -266,15 +270,16 @@
SET login_name = ?
WHERE userid = ?},
undef, ($new_email, $userid));
- $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $::token);
+ $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $token);
$dbh->do(q{DELETE FROM tokens WHERE userid = ?
AND tokentype = 'emailnew'}, undef, $userid);
- $dbh->bz_commit_transaction();
# The email address has been changed, so we need to rederive the groups
my $user = new Bugzilla::User($userid);
$user->derive_regexp_groups;
+ $dbh->bz_commit_transaction();
+
# Return HTTP response headers.
print $cgi->header();
@@ -287,12 +292,15 @@
}
sub cancelChangeEmail {
+ my $token = shift;
my $dbh = Bugzilla->dbh;
+ $dbh->bz_start_transaction();
+
# Get the user's ID from the tokens table.
my ($userid, $tokentype, $eventdata) = $dbh->selectrow_array(
q{SELECT userid, tokentype, eventdata FROM tokens
- WHERE token = ?}, undef, $::token);
+ WHERE token = ?}, undef, $token);
my ($old_email, $new_email) = split(/:/,$eventdata);
if($tokentype eq "emailold") {
@@ -304,16 +312,15 @@
# check to see if it has been altered
if($actualemail ne $old_email) {
+ # XXX - This is NOT safe - if A has change to B, another profile
+ # could have grabbed A's username in the meantime.
+ # The DB constraint will catch this, though
$dbh->do(q{UPDATE profiles
SET login_name = ?
WHERE userid = ?},
undef, ($old_email, $userid));
# email has changed, so rederive groups
- # Note that this is done _after_ the tables are unlocked
- # This is sort of a race condition (given the lack of transactions)
- # but the user had access to it just now, so it's not a security
- # issue
my $user = new Bugzilla::User($userid);
$user->derive_regexp_groups;
@@ -327,12 +334,14 @@
$vars->{'old_email'} = $old_email;
$vars->{'new_email'} = $new_email;
- Bugzilla::Token::Cancel($::token, $vars->{'message'}, $vars);
+ Bugzilla::Token::Cancel($token, $vars->{'message'}, $vars);
$dbh->do(q{DELETE FROM tokens WHERE userid = ?
AND tokentype = 'emailold' OR tokentype = 'emailnew'},
undef, $userid);
+ $dbh->bz_commit_transaction();
+
# Return HTTP response headers.
print $cgi->header();
@@ -341,29 +350,29 @@
}
sub request_create_account {
- my (undef, $date, $login_name) = Bugzilla::Token::GetTokenData($::token);
- $vars->{'token'} = $::token;
+ my $token = shift;
+
+ Bugzilla->user->check_account_creation_enabled;
+ my (undef, $date, $login_name) = Bugzilla::Token::GetTokenData($token);
+ $vars->{'token'} = $token;
$vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
- $vars->{'date'} = str2time($date);
+ $vars->{'expiration_ts'} = ctime(str2time($date) + MAX_TOKEN_AGE * 86400);
- # When 'ssl' equals 'always' or 'authenticated sessions',
- # we want this form to always be over SSL.
- if ($cgi->protocol ne 'https' && Bugzilla->params->{'sslbase'} ne ''
- && Bugzilla->params->{'ssl'} ne 'never')
- {
- $cgi->require_https(Bugzilla->params->{'sslbase'});
- }
print $cgi->header();
-
$template->process('account/email/confirm-new.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
}
sub confirm_create_account {
- my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($::token);
+ my $token = shift;
+
+ Bugzilla->user->check_account_creation_enabled;
+ my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($token);
my $password = $cgi->param('passwd1') || '';
validate_password($password, $cgi->param('passwd2') || '');
+ # Make sure that these never show up anywhere in the UI.
+ $cgi->delete('passwd1', 'passwd2');
my $otheruser = Bugzilla::User->create({
login_name => $login_name,
@@ -371,25 +380,31 @@
cryptpassword => $password});
# Now delete this token.
- delete_token($::token);
+ delete_token($token);
# Let the user know that his user account has been successfully created.
$vars->{'message'} = 'account_created';
$vars->{'otheruser'} = $otheruser;
- $vars->{'login_info'} = 1;
+
+ # Log in the new user using credentials he just gave.
+ $cgi->param('Bugzilla_login', $otheruser->login);
+ $cgi->param('Bugzilla_password', $password);
+ Bugzilla->login(LOGIN_OPTIONAL);
print $cgi->header();
- $template->process('global/message.html.tmpl', $vars)
+ $template->process('index.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
}
sub cancel_create_account {
- my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($::token);
+ my $token = shift;
+
+ my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($token);
$vars->{'message'} = 'account_creation_canceled';
$vars->{'account'} = $login_name;
- Bugzilla::Token::Cancel($::token, $vars->{'message'});
+ Bugzilla::Token::Cancel($token, $vars->{'message'});
print $cgi->header();
$template->process('global/message.html.tmpl', $vars)
diff --git a/Websites/bugs.webkit.org/userprefs.cgi b/Websites/bugs.webkit.org/userprefs.cgi
index 80fbf02..de48451 100755
--- a/Websites/bugs.webkit.org/userprefs.cgi
+++ b/Websites/bugs.webkit.org/userprefs.cgi
@@ -27,6 +27,7 @@
use lib qw(. lib);
use Bugzilla;
+use Bugzilla::BugMail;
use Bugzilla::Constants;
use Bugzilla::Search;
use Bugzilla::Util;
@@ -56,8 +57,9 @@
Bugzilla::Token::CleanTokenTable();
my @token = $dbh->selectrow_array(
- "SELECT tokentype, issuedate + " .
- $dbh->sql_interval(MAX_TOKEN_AGE, 'DAY') . ", eventdata
+ "SELECT tokentype, " .
+ $dbh->sql_date_math('issuedate', '+', MAX_TOKEN_AGE, 'DAY')
+ . ", eventdata
FROM tokens
WHERE userid = ?
AND tokentype LIKE 'email%'
@@ -79,36 +81,26 @@
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
+ my $oldpassword = $cgi->param('old_password');
my $pwd1 = $cgi->param('new_password1');
my $pwd2 = $cgi->param('new_password2');
+ my $new_login_name = trim($cgi->param('new_login_name'));
if ($user->authorizer->can_change_password
- && ($cgi->param('Bugzilla_password') ne "" || $pwd1 ne "" || $pwd2 ne ""))
+ && ($oldpassword ne "" || $pwd1 ne "" || $pwd2 ne ""))
{
- my ($oldcryptedpwd) = $dbh->selectrow_array(
- q{SELECT cryptpassword FROM profiles WHERE userid = ?},
- undef, $user->id);
+ my $oldcryptedpwd = $user->cryptpassword;
$oldcryptedpwd || ThrowCodeError("unable_to_retrieve_password");
- my $oldpassword = $cgi->param('Bugzilla_password');
-
- # Wide characters cause crypt to die
- if (Bugzilla->params->{'utf8'}) {
- utf8::encode($oldpassword) if utf8::is_utf8($oldpassword);
- }
-
- if (crypt($oldpassword, $oldcryptedpwd) ne $oldcryptedpwd)
- {
+ if (bz_crypt($oldpassword, $oldcryptedpwd) ne $oldcryptedpwd) {
ThrowUserError("old_password_incorrect");
}
- if ($pwd1 ne "" || $pwd2 ne "")
- {
- $cgi->param('new_password1')
- || ThrowUserError("new_password_missing");
+ if ($pwd1 ne "" || $pwd2 ne "") {
+ $pwd1 || ThrowUserError("new_password_missing");
validate_password($pwd1, $pwd2);
- if ($cgi->param('Bugzilla_password') ne $pwd1) {
+ if ($oldpassword ne $pwd1) {
my $cryptedpassword = bz_crypt($pwd1);
$dbh->do(q{UPDATE profiles
SET cryptpassword = ?
@@ -123,16 +115,11 @@
if ($user->authorizer->can_change_email
&& Bugzilla->params->{"allowemailchange"}
- && $cgi->param('new_login_name'))
+ && $new_login_name)
{
- my $old_login_name = $cgi->param('Bugzilla_login');
- my $new_login_name = trim($cgi->param('new_login_name'));
+ if ($user->login ne $new_login_name) {
+ $oldpassword || ThrowUserError("old_password_required");
- if($old_login_name ne $new_login_name) {
- $cgi->param('Bugzilla_password')
- || ThrowUserError("old_password_required");
-
- use Bugzilla::Token;
# Block multiple email changes for the same user.
if (Bugzilla::Token::HasEmailChangeToken($user->id)) {
ThrowUserError("email_change_in_progress");
@@ -144,8 +131,7 @@
is_available_username($new_login_name)
|| ThrowUserError("account_exists", {email => $new_login_name});
- Bugzilla::Token::IssueEmailChangeToken($user, $old_login_name,
- $new_login_name);
+ Bugzilla::Token::IssueEmailChangeToken($user, $new_login_name);
$vars->{'email_changes_saved'} = 1;
}
@@ -188,6 +174,7 @@
foreach my $name (@setting_list) {
next if ! ($settings->{$name}->{'is_enabled'});
my $value = $cgi->param($name);
+ next unless defined $value;
my $setting = new Bugzilla::User::Setting($name);
if ($value eq "${name}-isdefault" ) {
@@ -210,43 +197,26 @@
###########################################################################
# User watching
###########################################################################
- if (Bugzilla->params->{"supportwatchers"}) {
- my $watched_ref = $dbh->selectcol_arrayref(
- "SELECT profiles.login_name FROM watch INNER JOIN profiles" .
- " ON watch.watched = profiles.userid" .
- " WHERE watcher = ?" .
- " ORDER BY profiles.login_name",
- undef, $user->id);
- $vars->{'watchedusers'} = $watched_ref;
+ my $watched_ref = $dbh->selectcol_arrayref(
+ "SELECT profiles.login_name FROM watch INNER JOIN profiles" .
+ " ON watch.watched = profiles.userid" .
+ " WHERE watcher = ?" .
+ " ORDER BY profiles.login_name",
+ undef, $user->id);
+ $vars->{'watchedusers'} = $watched_ref;
- my $watcher_ids = $dbh->selectcol_arrayref(
- "SELECT watcher FROM watch WHERE watched = ?",
- undef, $user->id);
+ my $watcher_ids = $dbh->selectcol_arrayref(
+ "SELECT watcher FROM watch WHERE watched = ?",
+ undef, $user->id);
- my @watchers;
- foreach my $watcher_id (@$watcher_ids) {
- my $watcher = new Bugzilla::User($watcher_id);
- push (@watchers, Bugzilla::User::identity($watcher));
- }
-
- @watchers = sort { lc($a) cmp lc($b) } @watchers;
- $vars->{'watchers'} = \@watchers;
+ my @watchers;
+ foreach my $watcher_id (@$watcher_ids) {
+ my $watcher = new Bugzilla::User($watcher_id);
+ push(@watchers, Bugzilla::User::identity($watcher));
}
- ###########################################################################
- # Role-based preferences
- ###########################################################################
- my $sth = $dbh->prepare("SELECT relationship, event " .
- "FROM email_setting " .
- "WHERE user_id = ?");
- $sth->execute($user->id);
-
- my %mail;
- while (my ($relationship, $event) = $sth->fetchrow_array()) {
- $mail{$relationship}{$event} = 1;
- }
-
- $vars->{'mail'} = \%mail;
+ @watchers = sort { lc($a) cmp lc($b) } @watchers;
+ $vars->{'watchers'} = \@watchers;
}
sub SaveEmail {
@@ -254,68 +224,76 @@
my $cgi = Bugzilla->cgi;
my $user = Bugzilla->user;
- if (Bugzilla->params->{"supportwatchers"}) {
- Bugzilla::User::match_field($cgi, { 'new_watchedusers' => {'type' => 'multi'} });
- }
+ Bugzilla::User::match_field({ 'new_watchedusers' => {'type' => 'multi'} });
###########################################################################
# Role-based preferences
###########################################################################
$dbh->bz_start_transaction();
- # Delete all the user's current preferences
- $dbh->do("DELETE FROM email_setting WHERE user_id = ?", undef, $user->id);
+ my $sth_insert = $dbh->prepare('INSERT INTO email_setting
+ (user_id, relationship, event) VALUES (?, ?, ?)');
- # Repopulate the table - first, with normal events in the
+ my $sth_delete = $dbh->prepare('DELETE FROM email_setting
+ WHERE user_id = ? AND relationship = ? AND event = ?');
+ # Load current email preferences into memory before updating them.
+ my $settings = $user->mail_settings;
+
+ # Update the table - first, with normal events in the
# relationship/event matrix.
- # Note: the database holds only "off" email preferences, as can be implied
- # from the name of the table - profiles_nomail.
- foreach my $rel (RELATIONSHIPS) {
+ my %relationships = Bugzilla::BugMail::relationships();
+ foreach my $rel (keys %relationships) {
+ next if ($rel == REL_QA && !Bugzilla->params->{'useqacontact'});
# Positive events: a ticked box means "send me mail."
foreach my $event (POS_EVENTS) {
- if (defined($cgi->param("email-$rel-$event"))
- && $cgi->param("email-$rel-$event") == 1)
- {
- $dbh->do("INSERT INTO email_setting " .
- "(user_id, relationship, event) " .
- "VALUES (?, ?, ?)",
- undef, ($user->id, $rel, $event));
+ my $is_set = $cgi->param("email-$rel-$event");
+ if ($is_set xor $settings->{$rel}{$event}) {
+ if ($is_set) {
+ $sth_insert->execute($user->id, $rel, $event);
+ }
+ else {
+ $sth_delete->execute($user->id, $rel, $event);
+ }
}
}
# Negative events: a ticked box means "don't send me mail."
foreach my $event (NEG_EVENTS) {
- if (!defined($cgi->param("neg-email-$rel-$event")) ||
- $cgi->param("neg-email-$rel-$event") != 1)
- {
- $dbh->do("INSERT INTO email_setting " .
- "(user_id, relationship, event) " .
- "VALUES (?, ?, ?)",
- undef, ($user->id, $rel, $event));
+ my $is_set = $cgi->param("neg-email-$rel-$event");
+ if (!$is_set xor $settings->{$rel}{$event}) {
+ if (!$is_set) {
+ $sth_insert->execute($user->id, $rel, $event);
+ }
+ else {
+ $sth_delete->execute($user->id, $rel, $event);
+ }
}
}
}
# Global positive events: a ticked box means "send me mail."
foreach my $event (GLOBAL_EVENTS) {
- if (defined($cgi->param("email-" . REL_ANY . "-$event"))
- && $cgi->param("email-" . REL_ANY . "-$event") == 1)
- {
- $dbh->do("INSERT INTO email_setting " .
- "(user_id, relationship, event) " .
- "VALUES (?, ?, ?)",
- undef, ($user->id, REL_ANY, $event));
+ my $is_set = $cgi->param("email-" . REL_ANY . "-$event");
+ if ($is_set xor $settings->{+REL_ANY}{$event}) {
+ if ($is_set) {
+ $sth_insert->execute($user->id, REL_ANY, $event);
+ }
+ else {
+ $sth_delete->execute($user->id, REL_ANY, $event);
+ }
}
}
$dbh->bz_commit_transaction();
+ # We have to clear the cache about email preferences.
+ delete $user->{'mail_settings'};
+
###########################################################################
# User watching
###########################################################################
- if (Bugzilla->params->{"supportwatchers"}
- && (defined $cgi->param('new_watchedusers')
- || defined $cgi->param('remove_watched_users')))
+ if (defined $cgi->param('new_watchedusers')
+ || defined $cgi->param('remove_watched_users'))
{
$dbh->bz_start_transaction();
@@ -406,7 +384,7 @@
$vars->{'queryshare_groups'} =
Bugzilla::Group->new_from_list($user->queryshare_groups);
}
- $vars->{'bless_group_ids'} = [map {$_->{'id'}} @{$user->bless_groups}];
+ $vars->{'bless_group_ids'} = [map { $_->id } @{$user->bless_groups}];
}
sub SaveSavedSearches {
@@ -507,18 +485,22 @@
my $cgi = Bugzilla->cgi;
-# This script needs direct access to the username and password CGI variables,
-# so we save them before their removal in Bugzilla->login, and delete them
-# before login in case we might be in a sudo session.
-my $bugzilla_login = $cgi->param('Bugzilla_login');
-my $bugzilla_password = $cgi->param('Bugzilla_password');
+# Delete credentials before logging in in case we are in a sudo session.
$cgi->delete('Bugzilla_login', 'Bugzilla_password') if ($cgi->cookie('sudo'));
+$cgi->delete('GoAheadAndLogIn');
+# First try to get credentials from cookies.
+Bugzilla->login(LOGIN_OPTIONAL);
+
+if (!Bugzilla->user->id) {
+ # Use credentials given in the form if login cookies are not available.
+ $cgi->param('Bugzilla_login', $cgi->param('old_login'));
+ $cgi->param('Bugzilla_password', $cgi->param('old_password'));
+}
Bugzilla->login(LOGIN_REQUIRED);
-$cgi->param('Bugzilla_login', $bugzilla_login);
-$cgi->param('Bugzilla_password', $bugzilla_password);
-$vars->{'changes_saved'} = $cgi->param('dosave');
+my $save_changes = $cgi->param('dosave');
+$vars->{'changes_saved'} = $save_changes;
my $current_tab_name = $cgi->param('tab') || "settings";
@@ -528,22 +510,32 @@
$vars->{'current_tab_name'} = $current_tab_name;
my $token = $cgi->param('token');
-check_token_data($token, 'edit_user_prefs') if $cgi->param('dosave');
+check_token_data($token, 'edit_user_prefs') if $save_changes;
# Do any saving, and then display the current tab.
SWITCH: for ($current_tab_name) {
+
+ # Extensions must set it to 1 to confirm the tab is valid.
+ my $handled = 0;
+ Bugzilla::Hook::process('user_preferences',
+ { 'vars' => $vars,
+ save_changes => $save_changes,
+ current_tab => $current_tab_name,
+ handled => \$handled });
+ last SWITCH if $handled;
+
/^account$/ && do {
- SaveAccount() if $cgi->param('dosave');
+ SaveAccount() if $save_changes;
DoAccount();
last SWITCH;
};
/^settings$/ && do {
- SaveSettings() if $cgi->param('dosave');
+ SaveSettings() if $save_changes;
DoSettings();
last SWITCH;
};
/^email$/ && do {
- SaveEmail() if $cgi->param('dosave');
+ SaveEmail() if $save_changes;
DoEmail();
last SWITCH;
};
@@ -552,15 +544,16 @@
last SWITCH;
};
/^saved-searches$/ && do {
- SaveSavedSearches() if $cgi->param('dosave');
+ SaveSavedSearches() if $save_changes;
DoSavedSearches();
last SWITCH;
};
+
ThrowUserError("unknown_tab",
{ current_tab_name => $current_tab_name });
}
-delete_token($token) if $cgi->param('dosave');
+delete_token($token) if $save_changes;
if ($current_tab_name ne 'permissions') {
$vars->{'token'} = issue_session_token('edit_user_prefs');
}
diff --git a/Websites/bugs.webkit.org/votes.cgi b/Websites/bugs.webkit.org/votes.cgi
index d1a9de0..9446946 100755
--- a/Websites/bugs.webkit.org/votes.cgi
+++ b/Websites/bugs.webkit.org/votes.cgi
@@ -13,344 +13,38 @@
#
# The Original Code is the Bugzilla Bug Tracking System.
#
-# The Initial Developer of the Original Code is Netscape Communications
-# Corporation. Portions created by Netscape are
-# Copyright (C) 1998 Netscape Communications Corporation. All
-# Rights Reserved.
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
#
-# Contributor(s): Terry Weissman <terry@mozilla.org>
-# Stephan Niemz <st.n@gmx.net>
-# Christopher Aillon <christopher@aillon.com>
-# Gervase Markham <gerv@gerv.net>
-# Frédéric Buclin <LpSolit@gmail.com>
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This script remains as a backwards-compatibility URL for before
+# the time that Voting was an extension.
use strict;
use lib qw(. lib);
-
use Bugzilla;
-use Bugzilla::Constants;
-use Bugzilla::Util;
use Bugzilla::Error;
-use Bugzilla::Bug;
-use Bugzilla::User;
-use Bugzilla::Product;
-use List::Util qw(min);
+my $is_enabled = grep { $_->NAME eq 'Voting' } @{ Bugzilla->extensions };
+$is_enabled || ThrowCodeError('extension_disabled', { name => 'Voting' });
my $cgi = Bugzilla->cgi;
-local our $vars = {};
-
-# If the action is show_bug, you need a bug_id.
-# If the action is show_user, you can supply a userid to show the votes for
-# another user, otherwise you see your own.
-# If the action is vote, your votes are set to those encoded in the URL as
-# <bug_id>=<votes>.
-#
-# If no action is defined, we default to show_bug if a bug_id is given,
-# otherwise to show_user.
-my $bug_id = $cgi->param('bug_id');
-my $action = $cgi->param('action') || ($bug_id ? "show_bug" : "show_user");
-
-if ($action eq "show_bug" ||
- ($action eq "show_user" && defined $cgi->param('user')))
-{
- Bugzilla->login();
-}
-else {
- Bugzilla->login(LOGIN_REQUIRED);
-}
-
-################################################################################
-# Begin Data/Security Validation
-################################################################################
-
-# Make sure the bug ID is a positive integer representing an existing
-# bug that the user is authorized to access.
-
-ValidateBugID($bug_id) if defined $bug_id;
-
-################################################################################
-# End Data/Security Validation
-################################################################################
+my $action = $cgi->param('action') || 'show_user';
if ($action eq "show_bug") {
- show_bug($bug_id);
+ $cgi->delete('action');
+ $cgi->param('id', 'voting/bug.html');
}
-elsif ($action eq "show_user") {
- show_user($bug_id);
-}
-elsif ($action eq "vote") {
- record_votes() if Bugzilla->params->{'usevotes'};
- show_user($bug_id);
+elsif ($action eq "show_user" or $action eq 'vote') {
+ $cgi->delete('action') unless $action eq 'vote';
+ $cgi->param('id', 'voting/user.html');
}
else {
- ThrowCodeError("unknown_action", {action => $action});
+ ThrowUserError('unknown_action', {action => $action});
}
+print $cgi->redirect('page.cgi?' . $cgi->query_string);
exit;
-
-# Display the names of all the people voting for this one bug.
-sub show_bug {
- my ($bug_id) = @_;
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
- my $template = Bugzilla->template;
-
- ThrowCodeError("missing_bug_id") unless defined $bug_id;
-
- $vars->{'bug_id'} = $bug_id;
- $vars->{'users'} =
- $dbh->selectall_arrayref('SELECT profiles.login_name, votes.vote_count
- FROM votes
- INNER JOIN profiles
- ON profiles.userid = votes.who
- WHERE votes.bug_id = ?',
- {'Slice' => {}}, $bug_id);
-
- print $cgi->header();
- $template->process("bug/votes/list-for-bug.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
-}
-
-# Display all the votes for a particular user. If it's the user
-# doing the viewing, give them the option to edit them too.
-sub show_user {
- my ($bug_id) = @_;
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- my $template = Bugzilla->template;
-
- # If a bug_id is given, and we're editing, we'll add it to the votes list.
- $bug_id ||= "";
-
- my $name = $cgi->param('user') || $user->login;
- my $who = login_to_id($name, THROW_ERROR);
- my $userid = $user->id;
-
- my $canedit = (Bugzilla->params->{'usevotes'} && $userid == $who) ? 1 : 0;
-
- $dbh->bz_start_transaction();
-
- if ($canedit && $bug_id) {
- # Make sure there is an entry for this bug
- # in the vote table, just so that things display right.
- my $has_votes = $dbh->selectrow_array('SELECT vote_count FROM votes
- WHERE bug_id = ? AND who = ?',
- undef, ($bug_id, $who));
- if (!$has_votes) {
- $dbh->do('INSERT INTO votes (who, bug_id, vote_count)
- VALUES (?, ?, 0)', undef, ($who, $bug_id));
- }
- }
-
- my @all_bug_ids;
- my @products;
- my $products = $user->get_selectable_products;
- # Read the votes data for this user for each product.
- foreach my $product (@$products) {
- next unless ($product->votes_per_user > 0);
-
- my @bugs;
- my @bug_ids;
- my $total = 0;
- my $onevoteonly = 0;
-
- my $vote_list =
- $dbh->selectall_arrayref('SELECT votes.bug_id, votes.vote_count,
- bugs.short_desc
- FROM votes
- INNER JOIN bugs
- ON votes.bug_id = bugs.bug_id
- WHERE votes.who = ?
- AND bugs.product_id = ?
- ORDER BY votes.bug_id',
- undef, ($who, $product->id));
-
- foreach (@$vote_list) {
- my ($id, $count, $summary) = @$_;
- $total += $count;
-
- # Next if user can't see this bug. So, the totals will be correct
- # and they can see there are votes 'missing', but not on what bug
- # they are. This seems a reasonable compromise; the alternative is
- # to lie in the totals.
- next if !$user->can_see_bug($id);
-
- push (@bugs, { id => $id,
- summary => $summary,
- count => $count });
- push (@bug_ids, $id);
- push (@all_bug_ids, $id);
- }
-
- $onevoteonly = 1 if (min($product->votes_per_user,
- $product->max_votes_per_bug) == 1);
-
- # Only add the product for display if there are any bugs in it.
- if ($#bugs > -1) {
- push (@products, { name => $product->name,
- bugs => \@bugs,
- bug_ids => \@bug_ids,
- onevoteonly => $onevoteonly,
- total => $total,
- maxvotes => $product->votes_per_user,
- maxperbug => $product->max_votes_per_bug });
- }
- }
-
- $dbh->do('DELETE FROM votes WHERE vote_count <= 0');
- $dbh->bz_commit_transaction();
-
- $vars->{'canedit'} = $canedit;
- $vars->{'voting_user'} = { "login" => $name };
- $vars->{'products'} = \@products;
- $vars->{'bug_id'} = $bug_id;
- $vars->{'all_bug_ids'} = \@all_bug_ids;
-
- print $cgi->header();
- $template->process("bug/votes/list-for-user.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
-}
-
-# Update the user's votes in the database.
-sub record_votes {
- ############################################################################
- # Begin Data/Security Validation
- ############################################################################
-
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
- my $template = Bugzilla->template;
-
- # Build a list of bug IDs for which votes have been submitted. Votes
- # are submitted in form fields in which the field names are the bug
- # IDs and the field values are the number of votes.
-
- my @buglist = grep {/^[1-9][0-9]*$/} $cgi->param();
-
- # If no bugs are in the buglist, let's make sure the user gets notified
- # that their votes will get nuked if they continue.
- if (scalar(@buglist) == 0) {
- if (!defined $cgi->param('delete_all_votes')) {
- print $cgi->header();
- $template->process("bug/votes/delete-all.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit();
- }
- elsif ($cgi->param('delete_all_votes') == 0) {
- print $cgi->redirect("votes.cgi");
- exit();
- }
- }
-
- # Call ValidateBugID on each bug ID to make sure it is a positive
- # integer representing an existing bug that the user is authorized
- # to access, and make sure the number of votes submitted is also
- # a non-negative integer (a series of digits not preceded by a
- # minus sign).
- my %votes;
- foreach my $id (@buglist) {
- ValidateBugID($id);
- $votes{$id} = $cgi->param($id);
- detaint_natural($votes{$id})
- || ThrowUserError("votes_must_be_nonnegative");
- }
-
- ############################################################################
- # End Data/Security Validation
- ############################################################################
- my $who = Bugzilla->user->id;
-
- # If the user is voting for bugs, make sure they aren't overstuffing
- # the ballot box.
- if (scalar(@buglist)) {
- my %prodcount;
- my %products;
- # XXX - We really need a $bug->product() method.
- foreach my $bug_id (@buglist) {
- my $bug = new Bugzilla::Bug($bug_id);
- my $prod = $bug->product;
- $products{$prod} ||= new Bugzilla::Product({name => $prod});
- $prodcount{$prod} ||= 0;
- $prodcount{$prod} += $votes{$bug_id};
-
- # Make sure we haven't broken the votes-per-bug limit
- ($votes{$bug_id} <= $products{$prod}->max_votes_per_bug)
- || ThrowUserError("too_many_votes_for_bug",
- {max => $products{$prod}->max_votes_per_bug,
- product => $prod,
- votes => $votes{$bug_id}});
- }
-
- # Make sure we haven't broken the votes-per-product limit
- foreach my $prod (keys(%prodcount)) {
- ($prodcount{$prod} <= $products{$prod}->votes_per_user)
- || ThrowUserError("too_many_votes_for_product",
- {max => $products{$prod}->votes_per_user,
- product => $prod,
- votes => $prodcount{$prod}});
- }
- }
-
- # Update the user's votes in the database. If the user did not submit
- # any votes, they may be using a form with checkboxes to remove all their
- # votes (checkboxes are not submitted along with other form data when
- # they are not checked, and Bugzilla uses them to represent single votes
- # for products that only allow one vote per bug). In that case, we still
- # need to clear the user's votes from the database.
- my %affected;
- $dbh->bz_start_transaction();
-
- # Take note of, and delete the user's old votes from the database.
- my $bug_list = $dbh->selectcol_arrayref('SELECT bug_id FROM votes
- WHERE who = ?', undef, $who);
-
- foreach my $id (@$bug_list) {
- $affected{$id} = 1;
- }
- $dbh->do('DELETE FROM votes WHERE who = ?', undef, $who);
-
- my $sth_insertVotes = $dbh->prepare('INSERT INTO votes (who, bug_id, vote_count)
- VALUES (?, ?, ?)');
- # Insert the new values in their place
- foreach my $id (@buglist) {
- if ($votes{$id} > 0) {
- $sth_insertVotes->execute($who, $id, $votes{$id});
- }
- $affected{$id} = 1;
- }
-
- # Update the cached values in the bugs table
- print $cgi->header();
- my @updated_bugs = ();
-
- my $sth_getVotes = $dbh->prepare("SELECT SUM(vote_count) FROM votes
- WHERE bug_id = ?");
-
- my $sth_updateVotes = $dbh->prepare("UPDATE bugs SET votes = ?
- WHERE bug_id = ?");
-
- foreach my $id (keys %affected) {
- $sth_getVotes->execute($id);
- my $v = $sth_getVotes->fetchrow_array || 0;
- $sth_updateVotes->execute($v, $id);
-
- my $confirmed = CheckIfVotedConfirmed($id, $who);
- push (@updated_bugs, $id) if $confirmed;
- }
- $dbh->bz_commit_transaction();
-
- $vars->{'type'} = "votes";
- $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
- $vars->{'title_tag'} = 'change_votes';
-
- foreach my $bug_id (@updated_bugs) {
- $vars->{'id'} = $bug_id;
- $template->process("bug/process/results.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- # Set header_done to 1 only after the first bug.
- $vars->{'header_done'} = 1;
- }
- $vars->{'votes_recorded'} = 1;
-}
diff --git a/Websites/bugs.webkit.org/whine.pl b/Websites/bugs.webkit.org/whine.pl
index 7d3ff19..424a5a4 100755
--- a/Websites/bugs.webkit.org/whine.pl
+++ b/Websites/bugs.webkit.org/whine.pl
@@ -34,6 +34,7 @@
use Bugzilla::User;
use Bugzilla::Mailer;
use Bugzilla::Util;
+use Bugzilla::Group;
# create some handles that we'll need
my $template = Bugzilla->template;
@@ -64,7 +65,8 @@
" whine_schedules.eventid, " .
" whine_events.owner_userid, " .
" whine_events.subject, " .
- " whine_events.body " .
+ " whine_events.body, " .
+ " whine_events.mailifnobugs " .
"FROM whine_schedules " .
"LEFT JOIN whine_events " .
" ON whine_events.id = whine_schedules.eventid " .
@@ -148,20 +150,22 @@
# A time greater than now means it still has to run today
elsif ($time >= $now_hour) {
# set it to today + number of hours
- $sth = $dbh->prepare("UPDATE whine_schedules " .
- "SET run_next = CURRENT_DATE + " .
- $dbh->sql_interval('?', 'HOUR') .
- " WHERE id = ?");
+ $sth = $dbh->prepare(
+ "UPDATE whine_schedules " .
+ "SET run_next = " .
+ $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'HOUR') .
+ " WHERE id = ?");
$sth->execute($time, $schedule_id);
}
# the target time is less than the current time
else { # set it for the next applicable day
$day = &get_next_date($day);
+ my $run_next = $dbh->sql_date_math('('
+ . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY')
+ . ')', '+', '?', 'HOUR');
$sth = $dbh->prepare("UPDATE whine_schedules " .
- "SET run_next = (CURRENT_DATE + " .
- $dbh->sql_interval('?', 'DAY') . ") + " .
- $dbh->sql_interval('?', 'HOUR') .
- " WHERE id = ?");
+ "SET run_next = $run_next
+ WHERE id = ?");
$sth->execute($day, $time, $schedule_id);
}
@@ -174,11 +178,12 @@
# midnight
my $target_time = ($time =~ /^\d+$/) ? $time : 0;
+ my $run_next = $dbh->sql_date_math('('
+ . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY')
+ . ')', '+', '?', 'HOUR');
$sth = $dbh->prepare("UPDATE whine_schedules " .
- "SET run_next = (CURRENT_DATE + " .
- $dbh->sql_interval('?', 'DAY') . ") + " .
- $dbh->sql_interval('?', 'HOUR') .
- " WHERE id = ?");
+ "SET run_next = $run_next
+ WHERE id = ?");
$sth->execute($target_date, $target_time, $schedule_id);
}
}
@@ -199,6 +204,7 @@
# users - array of user objects for recipients
# subject - Subject line for the email
# body - the text inserted above the bug lists
+# mailifnobugs - send message even if there are no query or query results
sub get_next_event {
my $event = {};
@@ -213,7 +219,7 @@
my $fetched = $sth_next_scheduled_event->fetch;
$sth_next_scheduled_event->finish;
return undef unless $fetched;
- my ($eventid, $owner_id, $subject, $body) = @{$fetched};
+ my ($eventid, $owner_id, $subject, $body, $mailifnobugs) = @{$fetched};
my $owner = Bugzilla::User->new($owner_id);
@@ -250,7 +256,7 @@
$groupname, $owner);
if ($group_id) {
my $glist = join(',',
- @{Bugzilla::User->flatten_group_membership(
+ @{Bugzilla::Group->flatten_group_membership(
$group_id)});
$sth = $dbh->prepare("SELECT user_id FROM " .
"user_group_map " .
@@ -281,6 +287,7 @@
'mailto' => \@users,
'subject' => $subject,
'body' => $body,
+ 'mailifnobugs' => $mailifnobugs,
};
}
}
@@ -295,6 +302,7 @@
# mailto (array of user objects for mail targets)
# subject (subject line for message)
# body (text blurb at top of message)
+# mailifnobugs (send message even if there are no query or query results)
while (my $event = get_next_event) {
my $eventid = $event->{'eventid'};
@@ -315,12 +323,14 @@
# run the queries for this schedule
my $queries = run_queries($args);
- # check to make sure there is something to output
- my $there_are_bugs = 0;
- for my $query (@{$queries}) {
- $there_are_bugs = 1 if scalar @{$query->{'bugs'}};
+ # If mailifnobugs is false, make sure there is something to output
+ if (!$event->{'mailifnobugs'}) {
+ my $there_are_bugs = 0;
+ for my $query (@{$queries}) {
+ $there_are_bugs = 1 if scalar @{$query->{'bugs'}};
+ }
+ next unless $there_are_bugs;
}
- next unless $there_are_bugs;
$args->{'queries'} = $queries;
@@ -357,7 +367,7 @@
# Don't send mail to someone whose bugmail notification is disabled.
return if $addressee->email_disabled;
- my $template = Bugzilla->template_inner($addressee->settings->{'lang'}->{'value'});
+ my $template = Bugzilla->template_inner($addressee->setting('lang'));
my $msg = ''; # it's a temporary variable to hold the template output
$args->{'alternatives'} ||= [];
@@ -388,7 +398,6 @@
$template->process("whine/multipart-mime.txt.tmpl", $args, \$msg)
or die($template->error());
- Bugzilla->template_inner("");
MessageToMTA($msg);
delete $args->{'boundary'};
@@ -424,16 +433,15 @@
next unless $savedquery; # silently ignore missing queries
# Execute the saved query
- my @searchfields = (
- 'bugs.bug_id',
- 'bugs.bug_severity',
- 'bugs.priority',
- 'bugs.rep_platform',
- 'bugs.assigned_to',
- 'bugs.bug_status',
- 'bugs.resolution',
- 'bugs.short_desc',
- 'map_assigned_to.login_name',
+ my @searchfields = qw(
+ bug_id
+ bug_severity
+ priority
+ rep_platform
+ assigned_to
+ bug_status
+ resolution
+ short_desc
);
# A new Bugzilla::CGI object needs to be created to allow
# Bugzilla::Search to execute a saved query. It's exceedingly weird,
@@ -441,10 +449,18 @@
my $searchparams = new Bugzilla::CGI($savedquery);
my $search = new Bugzilla::Search(
'fields' => \@searchfields,
- 'params' => $searchparams,
+ 'params' => scalar $searchparams->Vars,
'user' => $args->{'recipient'}, # the search runs as the recipient
);
- my $sqlquery = $search->getSQL();
+ # If a query fails for whatever reason, it shouldn't kill the script.
+ my $sqlquery = eval { $search->sql };
+ if ($@) {
+ print STDERR get_text('whine_query_failed', { query_name => $thisquery->{'name'},
+ author => $args->{'author'},
+ reason => $@ }) . "\n";
+ next;
+ }
+
$sth = $dbh->prepare($sqlquery);
$sth->execute;
@@ -579,21 +595,22 @@
my $target_time = ($run_time =~ /^\d+$/) ? $run_time : 0;
my $nextdate = &get_next_date($run_day);
-
+ my $run_next = $dbh->sql_date_math('('
+ . $dbh->sql_date_math('CURRENT_DATE', '+', '?', 'DAY')
+ . ')', '+', '?', 'HOUR');
$sth = $dbh->prepare("UPDATE whine_schedules " .
- "SET run_next = (CURRENT_DATE + " .
- $dbh->sql_interval('?', 'DAY') . ") + " .
- $dbh->sql_interval('?', 'HOUR') .
- " WHERE id = ?");
+ "SET run_next = $run_next
+ WHERE id = ?");
$sth->execute($nextdate, $target_time, $schedule_id);
return;
}
if ($minute_offset > 0) {
# Scheduling is done in terms of whole minutes.
- my $next_run = $dbh->selectrow_array('SELECT NOW() + ' .
- $dbh->sql_interval('?', 'MINUTE'),
- undef, $minute_offset);
+
+ my $next_run = $dbh->selectrow_array(
+ 'SELECT ' . $dbh->sql_date_math('NOW()', '+', '?', 'MINUTE'),
+ undef, $minute_offset);
$next_run = format_time($next_run, "%Y-%m-%d %R");
$sth = $dbh->prepare("UPDATE whine_schedules " .
diff --git a/Websites/bugs.webkit.org/whineatnews.pl b/Websites/bugs.webkit.org/whineatnews.pl
index 39361fa..4454a6d 100755
--- a/Websites/bugs.webkit.org/whineatnews.pl
+++ b/Websites/bugs.webkit.org/whineatnews.pl
@@ -25,8 +25,10 @@
# This is a script suitable for running once a day from a cron job. It
# looks at all the bugs, and sends whiny mail to anyone who has a bug
-# assigned to them that has status NEW or REOPENED that has not been
-# touched for more than the number of days specified in the whinedays param.
+# assigned to them that has status CONFIRMED, NEW, or REOPENED that has not
+# been touched for more than the number of days specified in the whinedays
+# param. (We have NEW and REOPENED in there to keep compatibility with old
+# Bugzillas.)
use strict;
use lib qw(. lib);
@@ -44,7 +46,7 @@
FROM bugs
INNER JOIN profiles
ON userid = assigned_to
- WHERE (bug_status = ? OR bug_status = ?)
+ WHERE bug_status IN (?,?,?)
AND disable_mail = 0
AND } . $dbh->sql_to_days('NOW()') . " - " .
$dbh->sql_to_days('delta_ts') . " > " .
@@ -54,7 +56,8 @@
my %bugs;
my %desc;
-my $slt_bugs = $dbh->selectall_arrayref($query, undef, 'NEW', 'REOPENED');
+my $slt_bugs = $dbh->selectall_arrayref($query, undef, 'CONFIRMED', 'NEW',
+ 'REOPENED');
foreach my $bug (@$slt_bugs) {
my ($id, $desc, $email) = @$bug;
@@ -85,11 +88,10 @@
$vars->{'bugs'} = \@bugs;
my $msg;
- my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+ my $template = Bugzilla->template_inner($user->setting('lang'));
$template->process("email/whine.txt.tmpl", $vars, \$msg)
or die($template->error());
- Bugzilla->template_inner("");
MessageToMTA($msg);
print "$email " . join(" ", @{$bugs{$email}}) . "\n";
diff --git a/Websites/bugs.webkit.org/xml.cgi b/Websites/bugs.webkit.org/xml.cgi
deleted file mode 100755
index 90db8ad..0000000
--- a/Websites/bugs.webkit.org/xml.cgi
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env perl -wT
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Bug Tracking System.
-#
-# The Initial Developer of the Original Code is Netscape Communications
-# Corporation. Portions created by Netscape are
-# Copyright (C) 1998 Netscape Communications Corporation. All
-# Rights Reserved.
-#
-# Contributor(s): Dawn Endico <endico@mozilla.org>
-# Terry Weissman <terry@mozilla.org>
-# Gervase Markham <gerv@gerv.net>
-
-use strict;
-
-use lib qw(. lib);
-use Bugzilla;
-
-my $cgi = Bugzilla->cgi;
-
-# Convert comma/space separated elements into separate params
-my @ids = ();
-
-if (defined $cgi->param('id')) {
- @ids = split (/[, ]+/, $cgi->param('id'));
-}
-
-my $ids = join('', map { $_ = "&id=" . $_ } @ids);
-
-print $cgi->redirect("show_bug.cgi?ctype=xml$ids");
diff --git a/Websites/bugs.webkit.org/xmlrpc.cgi b/Websites/bugs.webkit.org/xmlrpc.cgi
index f24f91b..b42be94 100755
--- a/Websites/bugs.webkit.org/xmlrpc.cgi
+++ b/Websites/bugs.webkit.org/xmlrpc.cgi
@@ -21,16 +21,17 @@
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
-use Bugzilla::Hook;
use Bugzilla::WebService::Constants;
+BEGIN {
+ if (!Bugzilla->feature('xmlrpc')) {
+ ThrowCodeError('feature_disabled', { feature => 'xmlrpc' });
+ }
+}
+use Bugzilla::WebService::Server::XMLRPC;
-# Use an eval here so that runtests.pl accepts this script even if SOAP-Lite
-# is not installed.
-eval 'use XMLRPC::Transport::HTTP;
- use Bugzilla::WebService;';
-$@ && ThrowCodeError('soap_not_installed');
+Bugzilla->usage_mode(USAGE_MODE_XMLRPC);
-Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_WEBSERVICE);
+# Fix the error code that SOAP::Lite uses for Perl errors.
local $SOAP::Constants::FAULT_SERVER;
$SOAP::Constants::FAULT_SERVER = ERROR_UNKNOWN_FATAL;
# The line above is used, this one is ignored, but SOAP::Lite
@@ -38,27 +39,10 @@
local $XMLRPC::Constants::FAULT_SERVER;
$XMLRPC::Constants::FAULT_SERVER = ERROR_UNKNOWN_FATAL;
-my %hook_dispatch;
-Bugzilla::Hook::process('webservice', { dispatch => \%hook_dispatch });
local @INC = (bz_locations()->{extensionsdir}, @INC);
-
-my $dispatch = {
- 'Bugzilla' => 'Bugzilla::WebService::Bugzilla',
- 'Bug' => 'Bugzilla::WebService::Bug',
- 'User' => 'Bugzilla::WebService::User',
- 'Product' => 'Bugzilla::WebService::Product',
- %hook_dispatch
-};
-
-# The on_action sub needs to be wrapped so that we can work out which
-# class to use; when the XMLRPC module calls it theres no indication
-# of which dispatch class will be handling it.
-# Note that using this to get code thats called before the actual routine
-# is a bit of a hack; its meant to be for modifying the SOAPAction
-# headers, which XMLRPC doesn't use; it relies on the XMLRPC modules
-# using SOAP::Lite internally....
-
-my $response = Bugzilla::WebService::XMLRPC::Transport::HTTP::CGI
- ->dispatch_with($dispatch)
- ->on_action(sub { Bugzilla::WebService::handle_login($dispatch, @_) } )
- ->handle;
+my $server = new Bugzilla::WebService::Server::XMLRPC;
+# We use a sub for on_action because that gets us the info about what
+# class is being called. Note that this is a hack--this is technically
+# for setting SOAPAction, which isn't used by XML-RPC.
+$server->on_action(sub { $server->handle_login(WS_DISPATCH, @_) })
+ ->handle();
diff --git a/Websites/bugs.webkit.org/xt/README b/Websites/bugs.webkit.org/xt/README
new file mode 100644
index 0000000..22f9f17
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/README
@@ -0,0 +1,18 @@
+The tests in this directory require a working database, as opposed
+to the tests in t/, which simply test the code without a working
+installation.
+
+Some of the tests may modify your current working installation, even
+if only temporarily. To run the tests that modify your database,
+set the environment variable BZ_WRITE_TESTS to 1.
+
+Some tests also take additional, optional arguments. You can pass arguments
+to tests like:
+
+ prove xt/search.t :: --long --operators=equals,notequals
+
+Note the "::"--that is necessary to note that the arguments are going to
+the test, not to "prove".
+
+See the perldoc of the individual tests to see what options they support,
+or do "perl xt/search.t --help".
diff --git a/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search.pm b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search.pm
new file mode 100644
index 0000000..f18d3e4
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search.pm
@@ -0,0 +1,1002 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This module tests Bugzilla/Search.pm. It uses various constants
+# that are in Bugzilla::Test::Search::Constants, in xt/lib/.
+#
+# It does this by:
+# 1) Creating a bunch of field values. Each field value is
+# randomly named and fully unique.
+# 2) Creating a bunch of bugs that use those unique field
+# values. Each bug has different characteristics--see
+# the comment above the NUM_BUGS constant for a description
+# of each bug.
+# 3) Running searches using the combination of every search operator against
+# every field. The tests that we run are described by the TESTS constant.
+# Some of the operator/field combinations are known to be broken--
+# these are listed in the KNOWN_BROKEN constant.
+# 4) For each search, we make sure that certain bugs are contained in
+# the search, and certain other bugs are not contained in the search.
+# The code for the operator/field tests is mostly in
+# Bugzilla::Test::Search::FieldTest.
+# 5) After testing each operator/field combination's functionality, we
+# do additional tests to make sure that there are no SQL injections
+# possible via any operator/field combination. The code for the
+# SQL Injection tests is in Bugzilla::Test::Search::InjectionTest.
+#
+# Generally, the only way that you should modify the behavior of this
+# script is by modifying the constants.
+
+package Bugzilla::Test::Search;
+
+use strict;
+use warnings;
+use Bugzilla::Attachment;
+use Bugzilla::Bug ();
+use Bugzilla::Constants;
+use Bugzilla::Field;
+use Bugzilla::Field::Choice;
+use Bugzilla::FlagType;
+use Bugzilla::Group;
+use Bugzilla::Install ();
+use Bugzilla::Test::Search::Constants;
+use Bugzilla::Test::Search::CustomTest;
+use Bugzilla::Test::Search::FieldTestNormal;
+use Bugzilla::Test::Search::OperatorTest;
+use Bugzilla::User ();
+use Bugzilla::Util qw(generate_random_password);
+
+use Carp;
+use DateTime;
+use Scalar::Util qw(blessed);
+
+###############
+# Constructor #
+###############
+
+sub new {
+ my ($class, $options) = @_;
+ return bless { options => $options }, $class;
+}
+
+#############
+# Accessors #
+#############
+
+sub options { return $_[0]->{options} }
+sub option { return $_[0]->{options}->{$_[1]} }
+
+sub num_tests {
+ my ($self) = @_;
+ my @top_operators = $self->top_level_operators;
+ my @all_operators = $self->all_operators;
+ my $top_operator_tests = $self->_total_operator_tests(\@top_operators);
+ my $all_operator_tests = $self->_total_operator_tests(\@all_operators);
+
+ my @fields = $self->all_fields;
+
+ # Basically, we run TESTS_PER_RUN tests for each field/operator combination.
+ my $top_combinations = $top_operator_tests * scalar(@fields);
+ my $all_combinations = $all_operator_tests * scalar(@fields);
+ # But we also have ORs, for which we run combinations^2 tests.
+ my $join_tests = $self->option('long')
+ ? ($top_combinations * $all_combinations) : 0;
+ # And AND tests, which means we run 2x $join_tests;
+ $join_tests = $join_tests * 2;
+ # Also, because of NOT tests and Normal tests, we run 3x $top_combinations.
+ my $basic_tests = $top_combinations * 3;
+ my $operator_field_tests = ($basic_tests + $join_tests) * TESTS_PER_RUN;
+
+ # Then we test each field/operator combination for SQL injection.
+ my @injection_values = INJECTION_TESTS;
+ my $sql_injection_tests = scalar(@fields) * scalar(@top_operators)
+ * scalar(@injection_values) * NUM_SEARCH_TESTS;
+
+ # This @{ [] } thing is the only reasonable way to get a count out of a
+ # constant array.
+ my $special_tests = scalar(@{ [SPECIAL_PARAM_TESTS, CUSTOM_SEARCH_TESTS] })
+ * TESTS_PER_RUN;
+
+ return $operator_field_tests + $sql_injection_tests + $special_tests;
+}
+
+sub _total_operator_tests {
+ my ($self, $operators) = @_;
+
+ # Some operators have more than one test. Find those ones and add
+ # them to the total operator tests
+ my $extra_operator_tests;
+ foreach my $operator (@$operators) {
+ my $tests = TESTS->{$operator};
+ next if !$tests;
+ my $extra_num = scalar(@$tests) - 1;
+ $extra_operator_tests += $extra_num;
+ }
+ return scalar(@$operators) + $extra_operator_tests;
+
+}
+
+sub all_operators {
+ my ($self) = @_;
+ if (not $self->{all_operators}) {
+
+ my @operators;
+ if (my $limit_operators = $self->option('operators')) {
+ @operators = split(',', $limit_operators);
+ }
+ else {
+ @operators = sort (keys %{ Bugzilla::Search::OPERATORS() });
+ }
+ # "substr" is just a backwards-compatibility operator, same as "substring".
+ @operators = grep { $_ ne 'substr' } @operators;
+ $self->{all_operators} = \@operators;
+ }
+ return @{ $self->{all_operators} };
+}
+
+sub all_fields {
+ my $self = shift;
+ if (not $self->{all_fields}) {
+ $self->_create_custom_fields();
+ my @fields = @{ Bugzilla->fields };
+ @fields = sort { $a->name cmp $b->name } @fields;
+ $self->{all_fields} = \@fields;
+ }
+ return @{ $self->{all_fields} };
+}
+
+sub top_level_operators {
+ my ($self) = @_;
+ if (!$self->{top_level_operators}) {
+ my @operators;
+ my $limit_top = $self->option('top-operators');
+ if ($limit_top) {
+ @operators = split(',', $limit_top);
+ }
+ else {
+ @operators = $self->all_operators;
+ }
+ $self->{top_level_operators} = \@operators;
+ }
+ return @{ $self->{top_level_operators} };
+}
+
+sub text_fields {
+ my ($self) = @_;
+ my @text_fields = grep { $_->type == FIELD_TYPE_TEXTAREA
+ or $_->type == FIELD_TYPE_FREETEXT } $self->all_fields;
+ @text_fields = map { $_->name } @text_fields;
+ push(@text_fields, qw(short_desc status_whiteboard bug_file_loc see_also));
+ return @text_fields;
+}
+
+sub bugs {
+ my $self = shift;
+ $self->{bugs} ||= [map { $self->_create_one_bug($_) } (1..NUM_BUGS)];
+ return @{ $self->{bugs} };
+}
+
+# Get a numbered bug.
+sub bug {
+ my ($self, $number) = @_;
+ return ($self->bugs)[$number - 1];
+}
+
+sub admin {
+ my $self = shift;
+ if (!$self->{admin_user}) {
+ my $admin = create_user("admin");
+ Bugzilla::Install::make_admin($admin);
+ $self->{admin_user} = $admin;
+ }
+ # We send back a fresh object every time, to make sure that group
+ # memberships are always up-to-date.
+ return new Bugzilla::User($self->{admin_user}->id);
+}
+
+sub nobody {
+ my $self = shift;
+ $self->{nobody} ||= Bugzilla::Group->create({ name => "nobody-" . random(),
+ description => "Nobody", isbuggroup => 1 });
+ return $self->{nobody};
+}
+sub everybody {
+ my ($self) = @_;
+ $self->{everybody} ||= create_group('To The Limit');
+ return $self->{everybody};
+}
+
+sub bug_create_value {
+ my ($self, $number, $field) = @_;
+ $field = $field->name if blessed($field);
+ if ($number == 6 and $field ne 'alias') {
+ $number = 1;
+ }
+ my $extra_values = $self->_extra_bug_create_values->{$number};
+ if (exists $extra_values->{$field}) {
+ return $extra_values->{$field};
+ }
+ return $self->_bug_create_values->{$number}->{$field};
+}
+sub bug_update_value {
+ my ($self, $number, $field) = @_;
+ $field = $field->name if blessed($field);
+ if ($number == 6 and $field ne 'alias') {
+ $number = 1;
+ }
+ return $self->_bug_update_values->{$number}->{$field};
+}
+
+# Values used to create the bugs.
+sub _bug_create_values {
+ my $self = shift;
+ return $self->{bug_create_values} if $self->{bug_create_values};
+ my %values;
+ foreach my $number (1..NUM_BUGS) {
+ $values{$number} = $self->_create_field_values($number, 'for create');
+ }
+ $self->{bug_create_values} = \%values;
+ return $self->{bug_create_values};
+}
+# Values as they existed on the bug, at creation time. Used by the
+# changedfrom tests.
+sub _extra_bug_create_values {
+ my $self = shift;
+ $self->{extra_bug_create_values} ||= { map { $_ => {} } (1..NUM_BUGS) };
+ return $self->{extra_bug_create_values};
+}
+
+# Values used to update the bugs after they are created.
+sub _bug_update_values {
+ my $self = shift;
+ return $self->{bug_update_values} if $self->{bug_update_values};
+ my %values;
+ foreach my $number (1..NUM_BUGS) {
+ $values{$number} = $self->_create_field_values($number);
+ }
+ $self->{bug_update_values} = \%values;
+ return $self->{bug_update_values};
+}
+
+##############################
+# General Helper Subroutines #
+##############################
+
+sub random {
+ $_[0] ||= FIELD_SIZE;
+ generate_random_password(@_);
+}
+
+# We need to use a custom timestamp for each create() and update(),
+# because the database returns the same value for LOCALTIMESTAMP(0)
+# for the entire transaction, and we need each created bug to have
+# its own creation_ts and delta_ts.
+sub timestamp {
+ my ($day, $second) = @_;
+ return DateTime->new(
+ year => 2037,
+ month => 1,
+ day => $day,
+ hour => 12,
+ minute => $second,
+ second => 0,
+ # We make it floating because the timezone doesn't matter for our uses,
+ # and we want totally consistent behavior across all possible machines.
+ time_zone => 'floating',
+ );
+}
+
+sub create_keyword {
+ my ($number) = @_;
+ return Bugzilla::Keyword->create({
+ name => "$number-keyword-" . random(),
+ description => "Keyword $number" });
+}
+
+sub create_user {
+ my ($prefix) = @_;
+ my $user_name = $prefix . '-' . random(15) . "@" . random(12)
+ . "." . random(3);
+ my $user_realname = $prefix . '-' . random();
+ my $user = Bugzilla::User->create({
+ login_name => $user_name,
+ realname => $user_realname,
+ cryptpassword => '*',
+ });
+ return $user;
+}
+
+sub create_group {
+ my ($prefix) = @_;
+ return Bugzilla::Group->create({
+ name => "$prefix-group-" . random(), description => "Everybody $prefix",
+ userregexp => '.*', isbuggroup => 1 });
+}
+
+sub create_legal_value {
+ my ($field, $number) = @_;
+ my $type = Bugzilla::Field::Choice->type($field);
+ my $field_name = $field->name;
+ return $type->create({ value => "$number-$field_name-" . random(),
+ is_open => 0 });
+}
+
+#########################
+# Custom Field Creation #
+#########################
+
+sub _create_custom_fields {
+ my ($self) = @_;
+ return if !$self->option('add-custom-fields');
+
+ while (my ($type, $name) = each %{ CUSTOM_FIELDS() }) {
+ my $exists = new Bugzilla::Field({ name => $name });
+ next if $exists;
+ Bugzilla::Field->create({
+ name => $name,
+ type => $type,
+ description => "Search Test Field $name",
+ enter_bug => 1,
+ custom => 1,
+ buglist => 1,
+ is_mandatory => 0,
+ });
+ }
+}
+
+########################
+# Field Value Creation #
+########################
+
+sub _create_field_values {
+ my ($self, $number, $for_create) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->set_user($self->admin);
+
+ my @selects = grep { $_->is_select } $self->all_fields;
+ my %values;
+ foreach my $field (@selects) {
+ next if $field->is_abnormal;
+ $values{$field->name} = create_legal_value($field, $number)->name;
+ }
+
+ my $group = create_group($number);
+ $values{groups} = [$group->name];
+
+ $values{'keywords'} = create_keyword($number)->name;
+
+ foreach my $field (qw(assigned_to qa_contact reporter cc)) {
+ $values{$field} = create_user("$number-$field")->login;
+ }
+
+ my $classification = Bugzilla::Classification->create(
+ { name => "$number-classification-" . random() });
+ $classification = $classification->name;
+
+ my $version = "$number-version-" . random();
+ my $milestone = "$number-tm-" . random(15);
+ my $product = Bugzilla::Product->create({
+ name => "$number-product-" . random(),
+ description => 'Created by t/search.t',
+ defaultmilestone => $milestone,
+ classification => $classification,
+ version => $version,
+ allows_unconfirmed => 1,
+ });
+ foreach my $item ($group, $self->nobody) {
+ $product->set_group_controls($item,
+ { membercontrol => CONTROLMAPSHOWN,
+ othercontrol => CONTROLMAPNA });
+ }
+ # $product->update() is called lower down.
+ my $component = Bugzilla::Component->create({
+ product => $product, name => "$number-component-" . random(),
+ initialowner => create_user("$number-defaultowner")->login,
+ initialqacontact => create_user("$number-defaultqa")->login,
+ initial_cc => [create_user("$number-initcc")->login],
+ description => "Component $number" });
+
+ $values{'product'} = $product->name;
+ $values{'component'} = $component->name;
+ $values{'target_milestone'} = $milestone;
+ $values{'version'} = $version;
+
+ foreach my $field ($self->text_fields) {
+ # We don't add a - after $field for the text fields, because
+ # if we do, fulltext searching for short_desc pulls out
+ # "short_desc" as a word and matches it in every bug.
+ my $value = "$number-$field" . random();
+ if ($field eq 'bug_file_loc' or $field eq 'see_also') {
+ $value = "http://$value-" . random(3)
+ . "/show_bug.cgi?id=$number";
+ }
+ $values{$field} = $value;
+ }
+ $values{'tag'} = ["$number-tag-" . random()];
+
+ my @date_fields = grep { $_->type == FIELD_TYPE_DATETIME } $self->all_fields;
+ foreach my $field (@date_fields) {
+ # We use 03 as the month because that differs from our creation_ts,
+ # delta_ts, and deadline. (It's nice to have recognizable values
+ # for each field when debugging.)
+ my $second = $for_create ? $number : $number + 1;
+ $values{$field->name} = "2037-03-0$number 12:34:0$second";
+ }
+
+ $values{alias} = "$number-alias-" . random(12);
+
+ # Prefixing the original comment with "description" makes the
+ # lesserthan and greaterthan tests behave predictably.
+ my $comm_prefix = $for_create ? "description-" : '';
+ $values{comment} = "$comm_prefix$number-comment-" . random()
+ . ' ' . random();
+
+ my @flags;
+ my $setter = create_user("$number-setters.login_name");
+ my $requestee = create_user("$number-requestees.login_name");
+ $values{set_flags} = _create_flags($number, $setter, $requestee);
+
+ my $month = $for_create ? "12" : "02";
+ $values{'deadline'} = "2037-$month-0$number";
+ my $estimate_times = $for_create ? 10 : 1;
+ $values{estimated_time} = $estimate_times * $number;
+
+ $values{attachment} = _get_attach_values($number, $for_create);
+
+ # Some things only happen on the first bug.
+ if ($number == 1) {
+ # We use 6 as the prefix for the extra values, because bug 6's values
+ # don't otherwise get used (since bug 6 is created as a clone of
+ # bug 1). This also makes sure that our greaterthan/lessthan
+ # tests work properly.
+ my $extra_group = create_group(6);
+ $product->set_group_controls($extra_group,
+ { membercontrol => CONTROLMAPSHOWN,
+ othercontrol => CONTROLMAPNA });
+ $values{groups} = [$values{groups}->[0], $extra_group->name];
+ my $extra_keyword = create_keyword(6);
+ $values{keywords} = [$values{keywords}, $extra_keyword->name];
+ my $extra_cc = create_user("6-cc");
+ $values{cc} = [$values{cc}, $extra_cc->login];
+ my @multi_selects = grep { $_->type == FIELD_TYPE_MULTI_SELECT }
+ $self->all_fields;
+ foreach my $field (@multi_selects) {
+ my $new_value = create_legal_value($field, 6);
+ my $name = $field->name;
+ $values{$name} = [$values{$name}, $new_value->name];
+ }
+ push(@{ $values{'tag'} }, "6-tag-" . random());
+ }
+
+ # On bug 5, any field that *can* be left empty, *is* left empty.
+ if ($number == 5) {
+ my @set_fields = grep { $_->type == FIELD_TYPE_SINGLE_SELECT }
+ $self->all_fields;
+ @set_fields = map { $_->name } @set_fields;
+ push(@set_fields, qw(short_desc version reporter));
+ foreach my $key (keys %values) {
+ delete $values{$key} unless grep { $_ eq $key } @set_fields;
+ }
+ }
+
+ $product->update();
+
+ return \%values;
+}
+
+# Flags
+sub _create_flags {
+ my ($number, $setter, $requestee) = @_;
+
+ my $flagtypes = _create_flagtypes($number);
+
+ my %flags;
+ foreach my $type (qw(a b)) {
+ $flags{$type} = _get_flag_values(@_, $flagtypes->{$type});
+ }
+ return \%flags;
+}
+
+sub _create_flagtypes {
+ my ($number) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $name = "$number-flag-" . random();
+ my $desc = "FlagType $number";
+
+ my %flagtypes;
+ foreach my $target (qw(a b)) {
+ $dbh->do("INSERT INTO flagtypes
+ (name, description, target_type, is_requestable,
+ is_requesteeble, is_multiplicable, cc_list)
+ VALUES (?,?,?,1,1,1,'')",
+ undef, $name, $desc, $target);
+ my $id = $dbh->bz_last_key('flagtypes', 'id');
+ $dbh->do('INSERT INTO flaginclusions (type_id) VALUES (?)',
+ undef, $id);
+ my $flagtype = new Bugzilla::FlagType($id);
+ $flagtypes{$target} = $flagtype;
+ }
+ return \%flagtypes;
+}
+
+sub _get_flag_values {
+ my ($number, $setter, $requestee, $flagtype) = @_;
+
+ my @set_flags;
+ if ($number <= 2) {
+ foreach my $value (qw(? - + ?)) {
+ my $flag = { type_id => $flagtype->id, status => $value,
+ setter => $setter, flagtype => $flagtype };
+ push(@set_flags, $flag);
+ }
+ $set_flags[0]->{requestee} = $requestee->login;
+ }
+ else {
+ @set_flags = ({ type_id => $flagtype->id, status => '+',
+ setter => $setter, flagtype => $flagtype });
+ }
+ return \@set_flags;
+}
+
+# Attachments
+sub _get_attach_values {
+ my ($number, $for_create) = @_;
+
+ my $boolean = $number == 1 ? 1 : 0;
+ if ($for_create) {
+ $boolean = !$boolean ? 1 : 0;
+ }
+ my $ispatch = $for_create ? 'ispatch' : 'is_patch';
+ my $isobsolete = $for_create ? 'isobsolete' : 'is_obsolete';
+ my $isprivate = $for_create ? 'isprivate' : 'is_private';
+ my $mimetype = $for_create ? 'mimetype' : 'content_type';
+
+ my %values = (
+ description => "$number-attach_desc-" . random(),
+ filename => "$number-filename-" . random(),
+ $ispatch => $boolean,
+ $isobsolete => $boolean,
+ $isprivate => $boolean,
+ $mimetype => "text/x-$number-" . random(),
+ );
+ if ($for_create) {
+ $values{data} = "$number-data-" . random() . random();
+ }
+ return \%values;
+}
+
+################
+# Bug Creation #
+################
+
+sub _create_one_bug {
+ my ($self, $number) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # We need bug 6 to have a unique alias that is not a clone of bug 1's,
+ # so we get the alias separately from the other parameters.
+ my $alias = $self->bug_create_value($number, 'alias');
+ my $update_alias = $self->bug_update_value($number, 'alias');
+
+ # Otherwise, make bug 6 a clone of bug 1.
+ my $real_number = $number;
+ $number = 1 if $number == 6;
+
+ my $reporter = $self->bug_create_value($number, 'reporter');
+ Bugzilla->set_user(Bugzilla::User->check($reporter));
+
+ # We create the bug with one set of values, and then we change it
+ # to have different values.
+ my %params = %{ $self->_bug_create_values->{$number} };
+ $params{alias} = $alias;
+
+ # There are some things in bug_create_values that shouldn't go into
+ # create().
+ delete @params{qw(attachment set_flags tag)};
+
+ my ($status, $resolution, $see_also) =
+ delete @params{qw(bug_status resolution see_also)};
+ # All the bugs are created with everconfirmed = 0.
+ $params{bug_status} = 'UNCONFIRMED';
+ my $bug = Bugzilla::Bug->create(\%params);
+
+ # These are necessary for the changedfrom tests.
+ my $extra_values = $self->_extra_bug_create_values->{$number};
+ foreach my $field (qw(comments remaining_time percentage_complete
+ keyword_objects everconfirmed dependson blocked
+ groups_in classification actual_time))
+ {
+ $extra_values->{$field} = $bug->$field;
+ }
+ $extra_values->{reporter_accessible} = $number == 1 ? 0 : 1;
+ $extra_values->{cclist_accessible} = $number == 1 ? 0 : 1;
+
+ if ($number == 5) {
+ # Bypass Bugzilla::Bug--we don't want any changes in bugs_activity
+ # for bug 5.
+ $dbh->do('UPDATE bugs SET qa_contact = NULL, reporter_accessible = 0,
+ cclist_accessible = 0 WHERE bug_id = ?',
+ undef, $bug->id);
+ $dbh->do('DELETE FROM cc WHERE bug_id = ?', undef, $bug->id);
+ my $ts = '1970-01-01 00:00:00';
+ $dbh->do('UPDATE bugs SET creation_ts = ?, delta_ts = ?
+ WHERE bug_id = ?', undef, $ts, $ts, $bug->id);
+ $dbh->do('UPDATE longdescs SET bug_when = ? WHERE bug_id = ?',
+ undef, $ts, $bug->id);
+ $bug->{creation_ts} = $ts;
+ $extra_values->{see_also} = [];
+ }
+ else {
+ # Manually set the creation_ts so that each bug has a different one.
+ #
+ # Also, manually update the resolution and bug_status, because
+ # we want to see both of them change in bugs_activity, so we
+ # have to start with values for both (and as of the time when I'm
+ # writing this test, Bug->create doesn't support setting resolution).
+ #
+ # Same for see_also.
+ my $timestamp = timestamp($number, $number - 1);
+ my $creation_ts = $timestamp->ymd . ' ' . $timestamp->hms;
+ $bug->{creation_ts} = $creation_ts;
+ $dbh->do('UPDATE longdescs SET bug_when = ? WHERE bug_id = ?',
+ undef, $creation_ts, $bug->id);
+ $dbh->do('UPDATE bugs SET creation_ts = ?, bug_status = ?,
+ resolution = ? WHERE bug_id = ?',
+ undef, $creation_ts, $status, $resolution, $bug->id);
+ $dbh->do('INSERT INTO bug_see_also (bug_id, value, class) VALUES (?,?,?)',
+ undef, $bug->id, $see_also, 'Bugzilla::BugUrl::Bugzilla');
+ $extra_values->{see_also} = $bug->see_also;
+
+ # All the tags must be created as the admin user, so that the
+ # admin user can find them, later.
+ my $original_user = Bugzilla->user;
+ Bugzilla->set_user($self->admin);
+ my $tags = $self->bug_create_value($number, 'tag');
+ $bug->add_tag($_) foreach @$tags;
+ $extra_values->{tags} = $tags;
+ Bugzilla->set_user($original_user);
+
+ if ($number == 1) {
+ # Bug 1 needs to start off with reporter_accessible and
+ # cclist_accessible being 0, so that when we change them to 1,
+ # that change shows up in bugs_activity.
+ $dbh->do('UPDATE bugs SET reporter_accessible = 0,
+ cclist_accessible = 0 WHERE bug_id = ?',
+ undef, $bug->id);
+ # Bug 1 gets three comments, so that longdescs.count matches it
+ # uniquely. The third comment is added in the middle, so that the
+ # last comment contains all of the important data, like work_time.
+ $bug->add_comment("1-comment-" . random(100));
+ }
+
+ my %update_params = %{ $self->_bug_update_values->{$number} };
+ my %reverse_map = reverse %{ Bugzilla::Bug->FIELD_MAP };
+ foreach my $db_name (keys %reverse_map) {
+ next if $db_name eq 'comment';
+ next if $db_name eq 'status_whiteboard';
+ if (exists $update_params{$db_name}) {
+ my $update_name = $reverse_map{$db_name};
+ $update_params{$update_name} = delete $update_params{$db_name};
+ }
+ }
+
+ my ($new_status, $new_res) =
+ delete @update_params{qw(status resolution)};
+ # Bypass the status workflow.
+ $bug->{bug_status} = $new_status;
+ $bug->{resolution} = $new_res;
+ $bug->{everconfirmed} = 1 if $number == 1;
+
+ # add/remove/set fields.
+ $update_params{keywords} = { set => $update_params{keywords} };
+ $update_params{groups} = { add => $update_params{groups},
+ remove => $bug->groups_in };
+ my @cc_remove = map { $_->login } @{ $bug->cc_users };
+ my $cc_new = $update_params{cc};
+ my @cc_add = ref($cc_new) ? @$cc_new : ($cc_new);
+ # We make the admin an explicit CC on bug 1 (but not on bug 6), so
+ # that we can test the %user% pronoun properly.
+ if ($real_number == 1) {
+ push(@cc_add, $self->admin->login);
+ }
+ $update_params{cc} = { add => \@cc_add, remove => \@cc_remove };
+ my $see_also_remove = $bug->see_also;
+ my $see_also_add = [$update_params{see_also}];
+ $update_params{see_also} = { add => $see_also_add,
+ remove => $see_also_remove };
+ $update_params{comment} = { body => $update_params{comment} };
+ $update_params{work_time} = $number;
+ # Setting work_time kills the remaining_time, so we need to
+ # preserve that. We add 8 because that produces an integer
+ # percentage_complete for bug 1, which is necessary for
+ # accurate "equals"-type searching.
+ $update_params{remaining_time} = $number + 8;
+ $update_params{reporter_accessible} = $number == 1 ? 1 : 0;
+ $update_params{cclist_accessible} = $number == 1 ? 1 : 0;
+ $update_params{alias} = $update_alias;
+
+ $bug->set_all(\%update_params);
+ my $flags = $self->bug_create_value($number, 'set_flags')->{b};
+ $bug->set_flags([], $flags);
+ $timestamp->set(second => $number);
+ $bug->update($timestamp->ymd . ' ' . $timestamp->hms);
+ $extra_values->{flags} = $bug->flags;
+
+ # It's not generally safe to do update() multiple times on
+ # the same Bug object.
+ $bug = new Bugzilla::Bug($bug->id);
+ my $update_flags = $self->bug_update_value($number, 'set_flags')->{b};
+ $_->{status} = 'X' foreach @{ $bug->flags };
+ $bug->set_flags($bug->flags, $update_flags);
+ if ($number == 1) {
+ my $comment_id = $bug->comments->[-1]->id;
+ $bug->set_comment_is_private({ $comment_id => 1 });
+ }
+ $bug->update($bug->delta_ts);
+
+ my $attach_create = $self->bug_create_value($number, 'attachment');
+ my $attachment = Bugzilla::Attachment->create({
+ bug => $bug,
+ creation_ts => $creation_ts,
+ %$attach_create });
+ # Store for the changedfrom tests.
+ $extra_values->{attachments} =
+ [new Bugzilla::Attachment($attachment->id)];
+
+ my $attach_update = $self->bug_update_value($number, 'attachment');
+ $attachment->set_all($attach_update);
+ # In order to keep the mimetype on the ispatch attachment,
+ # we need to bypass the validator.
+ $attachment->{mimetype} = $attach_update->{content_type};
+ my $attach_flags = $self->bug_update_value($number, 'set_flags')->{a};
+ $attachment->set_flags([], $attach_flags);
+ $attachment->update($bug->delta_ts);
+ }
+
+ # Values for changedfrom.
+ $extra_values->{creation_ts} = $bug->creation_ts;
+ $extra_values->{delta_ts} = $bug->creation_ts;
+
+ return new Bugzilla::Bug($bug->id);
+}
+
+###################################
+# Test::Builder Memory Efficiency #
+###################################
+
+# Test::Builder stores information for each test run, but Test::Harness
+# and TAP::Harness don't actually need this information. When we run 60
+# million tests, the history eats up all our memory. (After about
+# 1 million tests, memory usage is around 1 GB.)
+#
+# The only part of the history that Test::More actually *uses* is the "ok"
+# field, which we store more efficiently, in an array, and then we re-populate
+# the Test_Results in Test::Builder at the end of the test.
+sub clean_test_history {
+ my ($self) = @_;
+ return if !$self->option('long');
+ my $builder = Test::More->builder;
+ my $current_test = $builder->current_test;
+
+ # I don't use details() because I don't want to copy the array.
+ my $results = $builder->{Test_Results};
+ my $check_test = $current_test - 1;
+ while (my $result = $results->[$check_test]) {
+ last if !$result;
+ $self->test_success($check_test, $result->{ok});
+ $check_test--;
+ }
+
+ # Truncate the test history array, but retain the current test number.
+ $builder->{Test_Results} = [];
+ $builder->{Curr_Test} = $current_test;
+}
+
+sub test_success {
+ my ($self, $index, $status) = @_;
+ $self->{test_success}->[$index] = $status;
+ return $self->{test_success};
+}
+
+sub repopulate_test_results {
+ my ($self) = @_;
+ return if !$self->option('long');
+ $self->clean_test_history();
+ # We create only two hashes, for memory efficiency.
+ my %ok = ( ok => 1 );
+ my %not_ok = ( ok => 0 );
+ my @results;
+ foreach my $success (@{ $self->{test_success} }) {
+ push(@results, $success ? \%ok : \%not_ok);
+ }
+ my $builder = Test::More->builder;
+ $builder->{Test_Results} = \@results;
+}
+
+##########
+# Caches #
+##########
+
+# When doing AND and OR tests, we essentially test the same field/operator
+# combinations over and over. So, if we're going to be running those tests,
+# we cache the translated_value of the FieldTests globally so that we don't
+# have to re-run the value-translation code every time (which can be pretty
+# slow).
+sub value_translation_cache {
+ my ($self, $field_test, $value) = @_;
+ return if !$self->option('long');
+ my $test_name = $field_test->name;
+ if (@_ == 3) {
+ $self->{value_translation_cache}->{$test_name} = $value;
+ }
+ return $self->{value_translation_cache}->{$test_name};
+}
+
+# When doing AND/OR tests, the value for transformed_value_was_equal
+# (see Bugzilla::Test::Search::FieldTest) won't be recalculated
+# if we pull our values from the value_translation_cache. So we need
+# to also cache the values for transformed_value_was_equal.
+sub was_equal_cache {
+ my ($self, $field_test, $number, $value) = @_;
+ return if !$self->option('long');
+ my $test_name = $field_test->name;
+ if (@_ == 4) {
+ $self->{tvwe_cache}->{$test_name}->{$number} = $value;
+ }
+ return $self->{tvwe_cache}->{$test_name}->{$number};
+}
+
+#############
+# Main Test #
+#############
+
+sub run {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # We want backtraces on any "die" message or any warning.
+ # Otherwise it's hard to trace errors inside of Bugzilla::Search from
+ # reading automated test run results.
+ local $SIG{__WARN__} = \&Carp::cluck;
+ local $SIG{__DIE__} = \&Carp::confess;
+
+ $dbh->bz_start_transaction();
+
+ # Some parameters need to be set in order for the tests to function
+ # properly.
+ my $everybody = $self->everybody;
+ my $params = Bugzilla->params;
+ local $params->{'useclassification'} = 1;
+ local $params->{'useqacontact'} = 1;
+ local $params->{'usebugaliases'} = 1;
+ local $params->{'usetargetmilestone'} = 1;
+ local $params->{'mail_delivery_method'} = 'None';
+ local $params->{'timetrackinggroup'} = $everybody->name;
+ local $params->{'insidergroup'} = $everybody->name;
+
+ $self->_setup_bugs();
+
+ # Even though _setup_bugs set us as an admin, we want to be sure at
+ # this point that we have an admin with refreshed group memberships.
+ Bugzilla->set_user($self->admin);
+ foreach my $test (CUSTOM_SEARCH_TESTS) {
+ my $custom_test = new Bugzilla::Test::Search::CustomTest($test, $self);
+ $custom_test->run();
+ }
+ foreach my $test (SPECIAL_PARAM_TESTS) {
+ my $operator_test =
+ new Bugzilla::Test::Search::OperatorTest($test->{operator}, $self);
+ my $field = Bugzilla::Field->check($test->{field});
+ my $special_test = new Bugzilla::Test::Search::FieldTestNormal(
+ $operator_test, $field, $test);
+ $special_test->run();
+ }
+ foreach my $operator ($self->top_level_operators) {
+ my $operator_test =
+ new Bugzilla::Test::Search::OperatorTest($operator, $self);
+ $operator_test->run();
+ }
+
+ # Rollbacks won't get rid of bugs_fulltext entries, so we do that ourselves.
+ my @bug_ids = map { $_->id } $self->bugs;
+ my $bug_id_string = join(',', @bug_ids);
+ $dbh->do("DELETE FROM bugs_fulltext WHERE bug_id IN ($bug_id_string)");
+ $dbh->bz_rollback_transaction();
+ $self->repopulate_test_results();
+}
+
+# This makes a few changes to the bugs after they're created--changes
+# that can only be done after all the bugs have been created.
+sub _setup_bugs {
+ my ($self) = @_;
+ $self->_setup_dependencies();
+ $self->_set_bug_id_fields();
+ $self->_protect_bug_6();
+}
+sub _setup_dependencies {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # Set up depedency relationships between the bugs.
+ # Bug 1 + 6 depend on bug 2 and block bug 3.
+ my $bug2 = $self->bug(2);
+ my $bug3 = $self->bug(3);
+ foreach my $number (1,6) {
+ my $bug = $self->bug($number);
+ my @original_delta = ($bug2->delta_ts, $bug3->delta_ts);
+ Bugzilla->set_user($bug->reporter);
+ $bug->set_dependencies([$bug2->id], [$bug3->id]);
+ $bug->update($bug->delta_ts);
+ # Setting dependencies changed the delta_ts on bug2 and bug3, so
+ # re-set them back to what they were before. However, we leave
+ # the correct update times in bugs_activity, so that the changed*
+ # searches still work right.
+ my $set_delta = $dbh->prepare(
+ 'UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
+ foreach my $row ([$original_delta[0], $bug2->id],
+ [$original_delta[1], $bug3->id])
+ {
+ $set_delta->execute(@$row);
+ }
+ }
+}
+
+sub _set_bug_id_fields {
+ my ($self) = @_;
+ # BUG_ID fields couldn't be set before, because before we create bug 1,
+ # we don't necessarily have any valid bug ids.)
+ my @bug_id_fields = grep { $_->type == FIELD_TYPE_BUG_ID }
+ $self->all_fields;
+ foreach my $number (1..NUM_BUGS) {
+ my $bug = $self->bug($number);
+ $number = 1 if $number == 6;
+ next if $number == 5;
+ my $other_bug = $self->bug($number + 1);
+ Bugzilla->set_user($bug->reporter);
+ foreach my $field (@bug_id_fields) {
+ $bug->set_custom_field($field, $other_bug->id);
+ $bug->update($bug->delta_ts);
+ }
+ }
+}
+
+sub _protect_bug_6 {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ Bugzilla->set_user($self->admin);
+
+ # Put bug6 in the nobody group.
+ my $nobody = $self->nobody;
+ # We pull it newly from the DB to be sure it's safe to call update()
+ # on.
+ my $bug6 = new Bugzilla::Bug($self->bug(6)->id);
+ $bug6->add_group($nobody);
+ $bug6->update($bug6->delta_ts);
+
+ # Remove the admin (and everybody else) from the $nobody group.
+ $dbh->do('DELETE FROM group_group_map
+ WHERE grantor_id = ? OR member_id = ?', undef,
+ $nobody->id, $nobody->id);
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/AndTest.pm b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/AndTest.pm
new file mode 100644
index 0000000..b7f8b3c
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/AndTest.pm
@@ -0,0 +1,66 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This test combines two field/operator combinations using AND in
+# a single boolean chart.
+package Bugzilla::Test::Search::AndTest;
+use base qw(Bugzilla::Test::Search::OrTest);
+
+use Bugzilla::Test::Search::Constants;
+use List::MoreUtils qw(all);
+
+use constant type => 'AND';
+
+#############
+# Accessors #
+#############
+
+# In an AND test, bugs ARE supposed to be contained only if they are contained
+# by ALL tests.
+sub bug_is_contained {
+ my ($self, $number) = @_;
+ return all { $_->bug_is_contained($number) } $self->field_tests;
+}
+
+sub _bug_will_actually_be_contained {
+ my ($self, $number) = @_;
+ return all { $_->will_actually_contain_bug($number) } $self->field_tests;
+}
+
+##############################
+# Bugzilla::Search arguments #
+##############################
+
+sub search_params {
+ my ($self) = @_;
+ my @all_params = map { $_->search_params } $self->field_tests;
+ my %params;
+ my $chart = 0;
+ foreach my $item (@all_params) {
+ $params{"field0-$chart-0"} = $item->{'field0-0-0'};
+ $params{"type0-$chart-0"} = $item->{'type0-0-0'};
+ $params{"value0-$chart-0"} = $item->{'value0-0-0'};
+ $chart++;
+ }
+ return \%params;
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/Constants.pm b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/Constants.pm
new file mode 100644
index 0000000..512d180
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/Constants.pm
@@ -0,0 +1,1130 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+
+# These are constants used by Bugzilla::Test::Search.
+# See the comment at the top of that package for a general overview
+# of how the search test works, and how the constants are used.
+# More detailed information on each constant is available in the comments
+# in this file.
+package Bugzilla::Test::Search::Constants;
+use base qw(Exporter);
+use Bugzilla::Constants;
+use Bugzilla::Util qw(generate_random_password);
+
+our @EXPORT = qw(
+ ATTACHMENT_FIELDS
+ BROKEN_NOT
+ COLUMN_TRANSLATION
+ COMMENT_FIELDS
+ CUSTOM_FIELDS
+ CUSTOM_SEARCH_TESTS
+ FIELD_SIZE
+ FIELD_SUBSTR_SIZE
+ FLAG_FIELDS
+ INJECTION_BROKEN_FIELD
+ INJECTION_BROKEN_OPERATOR
+ INJECTION_TESTS
+ KNOWN_BROKEN
+ NUM_BUGS
+ NUM_SEARCH_TESTS
+ SKIP_FIELDS
+ SPECIAL_PARAM_TESTS
+ SUBSTR_NO_FIELD_ADD
+ SUBSTR_SIZE
+ TESTS
+ TESTS_PER_RUN
+ USER_FIELDS
+);
+
+# Bug 1 is designed to be found by all the "equals" tests. It has
+# multiple values for several fields where other fields only have
+# one value.
+#
+# Bug 2 and 3 have a dependency relationship with Bug 1,
+# but show up in "not equals" tests. We do use bug 2 in multiple-value
+# tests.
+#
+# Bug 4 should never show up in any equals test, and has no relationship
+# with any other bug. However, it does have all its fields set.
+#
+# Bug 5 only has values set for mandatory fields, to expose problems
+# that happen with "not equals" tests failing to catch bugs that don't
+# have a value set at all.
+#
+# Bug 6 is a clone of Bug 1, but is in a group that the searcher isn't
+# in.
+use constant NUM_BUGS => 6;
+
+# How many tests there are for each operator/field combination other
+# than the "contains" tests.
+use constant NUM_SEARCH_TESTS => 3;
+# This is how many tests get run for each field/operator.
+use constant TESTS_PER_RUN => NUM_SEARCH_TESTS + NUM_BUGS;
+
+# This is how many random characters we generate for most fields' names.
+# (Some fields can't be this long, though, so they have custom lengths
+# in Bugzilla::Test::Search).
+use constant FIELD_SIZE => 30;
+
+# These are the custom fields that are created if the BZ_MODIFY_DATABASE_TESTS
+# environment variable is set.
+use constant CUSTOM_FIELDS => {
+ FIELD_TYPE_FREETEXT, 'cf_freetext',
+ FIELD_TYPE_SINGLE_SELECT, 'cf_single_select',
+ FIELD_TYPE_MULTI_SELECT, 'cf_multi_select',
+ FIELD_TYPE_TEXTAREA, 'cf_textarea',
+ FIELD_TYPE_DATETIME, 'cf_datetime',
+ FIELD_TYPE_BUG_ID, 'cf_bugid',
+};
+
+# This translates fielddefs names into Search column names.
+use constant COLUMN_TRANSLATION => {
+ creation_ts => 'opendate',
+ delta_ts => 'changeddate',
+ work_time => 'actual_time',
+};
+
+# Make comment field names to their Bugzilla::Comment accessor.
+use constant COMMENT_FIELDS => {
+ longdesc => 'body',
+ commenter => 'author',
+ 'longdescs.isprivate' => 'is_private',
+};
+
+# Same as above, for Bugzilla::Attachment.
+use constant ATTACHMENT_FIELDS => {
+ mimetype => 'contenttype',
+ submitter => 'attacher',
+ thedata => 'data',
+};
+
+# Same, for Bugzilla::Flag.
+use constant FLAG_FIELDS => {
+ 'flagtypes.name' => 'name',
+ 'setters.login_name' => 'setter',
+ 'requestees.login_name' => 'requestee',
+};
+
+# These are fields that we don't test. Test::More will mark these
+# "TODO & SKIP", and not run tests for them at all.
+#
+# We don't support days_elapsed or owner_idle_time yet.
+use constant SKIP_FIELDS => qw(
+ owner_idle_time
+ days_elapsed
+);
+
+# All the fields that represent users.
+use constant USER_FIELDS => qw(
+ assigned_to
+ cc
+ reporter
+ qa_contact
+ commenter
+ attachments.submitter
+ setters.login_name
+ requestees.login_name
+);
+
+# For the "substr"-type searches, how short of a substring should
+# we use? The goal is to be shorter than the full string, but
+# long enough to still be globally unique.
+use constant SUBSTR_SIZE => 20;
+# However, for some fields, we use a different size.
+use constant FIELD_SUBSTR_SIZE => {
+ alias => 11,
+ # Just the month and day.
+ deadline => -5,
+ creation_ts => -8,
+ delta_ts => -8,
+ percentage_complete => 1,
+ work_time => 3,
+ remaining_time => 3,
+ target_milestone => 15,
+ longdesc => 25,
+ # Just the hour and minute.
+ FIELD_TYPE_DATETIME, -5,
+};
+
+# For most fields, we add the length of the name of the field plus
+# the SUBSTR_SIZE specified above to determine how large of a substring
+# we're going to use. However, for some fields, it doesn't make sense to
+# add in their field name this way.
+use constant SUBSTR_NO_FIELD_ADD => FIELD_TYPE_DATETIME, qw(
+ target_milestone remaining_time percentage_complete work_time
+ attachments.mimetype attachments.submitter attachments.filename
+ attachments.description flagtypes.name
+);
+
+################
+# Known Broken #
+################
+
+# See the KNOWN_BROKEN constant for a general description of these
+# "_BROKEN" constants.
+
+# Shared between greaterthan and greaterthaneq.
+#
+# As with other fields, longdescs greaterthan matches if any comment
+# matches (which might be OK).
+#
+# Same for keywords, and cc. Logically, all of these might
+# be OK, but it makes the operation not the logical reverse of
+# lessthaneq. What we're really saying here by marking these broken
+# is that there ought to be some way of searching "all ccs" vs "any cc"
+# (and same for the other fields).
+use constant GREATERTHAN_BROKEN => (
+ cc => { contains => [1] },
+);
+
+# allwords and allwordssubstr have these broken tests in common.
+#
+# allwordssubstr on cc fields matches against a single cc,
+# instead of matching against all ccs on a bug.
+use constant ALLWORDS_BROKEN => (
+ cc => { contains => [1] },
+);
+
+# Fields that don't generally work at all with changed* searches, but
+# probably should.
+use constant CHANGED_BROKEN => (
+ classification => { contains => [1] },
+ commenter => { contains => [1] },
+ percentage_complete => { contains => [1] },
+ 'requestees.login_name' => { contains => [1] },
+ 'setters.login_name' => { contains => [1] },
+ delta_ts => { contains => [1] },
+);
+
+# These are additional broken tests that changedfrom and changedto
+# have in common.
+use constant CHANGED_VALUE_BROKEN => (
+ bug_group => { contains => [1] },
+ cc => { contains => [1] },
+ estimated_time => { contains => [1] },
+ 'flagtypes.name' => { contains => [1] },
+ keywords => { contains => [1] },
+ 'longdescs.count' => { search => 1 },
+ FIELD_TYPE_MULTI_SELECT, { contains => [1] },
+);
+
+
+# Any test listed in KNOWN_BROKEN gets marked TODO by Test::More
+# (using some complex code in Bugzilla::Test::Seach::FieldTest).
+# This means that if you run the test under "prove -v", these tests will
+# still show up as "not ok", but the test suite results won't show them
+# as a failure.
+#
+# This constant contains operators as keys, which point to hashes. The hashes
+# have field names as keys. Each field name points to a hash describing
+# how that field/operator combination is broken. The "contains"
+# array specifies that that particular "contains" test is expected
+# to fail. If "search" is set to 1, then we expect the creation of the
+# Bugzilla::Search object to fail.
+#
+# To allow handling custom fields, you can also use the field type as a key
+# instead of the field name. Specifying explicit field names always overrides
+# specifying a field type.
+#
+# Sometimes the operators have multiple tests, and one of them works
+# while the other fails. In this case, we have a special override for
+# "operator-value", which uniquely identifies tests.
+use constant KNOWN_BROKEN => {
+ "equals-%group.<1-bug_group>%" => {
+ commenter => { contains => [1,2,3,4,5] },
+ },
+
+ greaterthan => { GREATERTHAN_BROKEN },
+ greaterthaneq => { GREATERTHAN_BROKEN },
+
+ 'allwordssubstr-<1>' => { ALLWORDS_BROKEN },
+ 'allwords-<1>' => {
+ ALLWORDS_BROKEN,
+ },
+
+ # setters.login_name and requestees.login name aren't tracked individually
+ # in bugs_activity, so can't be searched using this method.
+ #
+ # percentage_complete isn't tracked in bugs_activity (and it would be
+ # really hard to track). However, it adds a 0=0 term instead of using
+ # the changed* charts or simply denying them.
+ #
+ # delta_ts changedbefore/after should probably search for bugs based
+ # on their delta_ts.
+ #
+ # creation_ts changedbefore/after should search for bug creation dates.
+ #
+ # The commenter field changedbefore/after should search for comment
+ # creation dates.
+ #
+ # classification isn't being tracked properly in bugs_activity, I think.
+ #
+ # attach_data.thedata should search when attachments were created and
+ # who they were created by.
+ 'changedbefore' => {
+ CHANGED_BROKEN,
+ 'attach_data.thedata' => { contains => [1] },
+ },
+ 'changedafter' => {
+ 'attach_data.thedata' => { contains => [2,3,4] },
+ classification => { contains => [2,3,4] },
+ commenter => { contains => [2,3,4] },
+ delta_ts => { contains => [2,3,4] },
+ percentage_complete => { contains => [2,3,4] },
+ 'requestees.login_name' => { contains => [2,3,4] },
+ 'setters.login_name' => { contains => [2,3,4] },
+ },
+ changedfrom => {
+ CHANGED_BROKEN,
+ CHANGED_VALUE_BROKEN,
+ # All fields should have a way to search for "changing
+ # from a blank value" probably.
+ blocked => { contains => [3,4,5], no_criteria => 1 },
+ dependson => { contains => [2,4,5], no_criteria => 1 },
+ work_time => { contains => [1] },
+ FIELD_TYPE_BUG_ID, { contains => [5], no_criteria => 1 },
+ },
+ # changeto doesn't find remaining_time changes (possibly due to us not
+ # tracking that data properly).
+ #
+ # multi-valued fields are stored as comma-separated strings, so you
+ # can't do changedfrom/to on them.
+ #
+ # Perhaps commenter can either tell you who the last commenter was,
+ # or if somebody commented at a given time (combined with other
+ # charts).
+ #
+ # longdesc changedto/from doesn't do anything; maybe it should.
+ # Same for attach_data.thedata.
+ changedto => {
+ CHANGED_BROKEN,
+ CHANGED_VALUE_BROKEN,
+ 'attach_data.thedata' => { contains => [1] },
+ longdesc => { contains => [1] },
+ remaining_time => { contains => [1] },
+ },
+ changedby => {
+ CHANGED_BROKEN,
+ # This should probably search the attacher or anybody who changed
+ # anything about an attachment at all.
+ 'attach_data.thedata' => { contains => [1] },
+ # This should probably search the reporter.
+ creation_ts => { contains => [1] },
+ },
+};
+
+###################
+# Broken NotTests #
+###################
+
+# Common BROKEN_NOT values for the changed* fields.
+use constant CHANGED_BROKEN_NOT => (
+ "attach_data.thedata" => { contains => [1] },
+ "classification" => { contains => [1] },
+ "commenter" => { contains => [1] },
+ "delta_ts" => { contains => [1] },
+ percentage_complete => { contains => [1] },
+ "requestees.login_name" => { contains => [1] },
+ "setters.login_name" => { contains => [1] },
+);
+
+# For changedfrom and changedto.
+use constant CHANGED_FROM_TO_BROKEN_NOT => (
+ 'longdescs.count' => { search => 1 },
+ "bug_group" => { contains => [1] },
+ "cc" => { contains => [1] },
+ "estimated_time" => { contains => [1] },
+ "flagtypes.name" => { contains => [1] },
+ "keywords" => { contains => [1] },
+ FIELD_TYPE_MULTI_SELECT, { contains => [1] },
+);
+
+# These are field/operator combinations that are broken when run under NOT().
+use constant BROKEN_NOT => {
+ allwords => {
+ cc => { contains => [1] },
+ },
+ 'allwords-<1> <2>' => {
+ cc => { },
+ },
+ allwordssubstr => {
+ cc => { contains => [1] },
+ },
+ 'allwordssubstr-<1>,<2>' => {
+ cc => { },
+ },
+ changedafter => {
+ "attach_data.thedata" => { contains => [2, 3, 4] },
+ "classification" => { contains => [2, 3, 4] },
+ "commenter" => { contains => [2, 3, 4] },
+ percentage_complete => { contains => [2, 3, 4] },
+ "delta_ts" => { contains => [2, 3, 4] },
+ "requestees.login_name" => { contains => [2, 3, 4] },
+ "setters.login_name" => { contains => [2, 3, 4] },
+ },
+ changedbefore => {
+ CHANGED_BROKEN_NOT,
+ },
+ changedby => {
+ CHANGED_BROKEN_NOT,
+ creation_ts => { contains => [1] },
+ work_time => { contains => [1] },
+ },
+ changedfrom => {
+ CHANGED_BROKEN_NOT,
+ CHANGED_FROM_TO_BROKEN_NOT,
+ 'attach_data.thedata' => { },
+ blocked => { contains => [1, 2] },
+ dependson => { contains => [1, 3] },
+ work_time => { contains => [1] },
+ FIELD_TYPE_BUG_ID, { contains => [1 .. 4] },
+
+ },
+ changedto => {
+ CHANGED_BROKEN_NOT,
+ CHANGED_FROM_TO_BROKEN_NOT,
+ longdesc => { contains => [1] },
+ "remaining_time" => { contains => [1] },
+ },
+ greaterthan => {
+ cc => { contains => [1] },
+ },
+ greaterthaneq => {
+ cc => { contains => [1] },
+ },
+};
+
+#############
+# Overrides #
+#############
+
+# These overrides are used in the TESTS constant, below.
+
+# Regex tests need unique test values for certain fields.
+use constant REGEX_OVERRIDE => {
+ 'attachments.mimetype' => { value => '^text/x-1-' },
+ bug_file_loc => { value => '^http://1-' },
+ see_also => { value => '^http://1-' },
+ blocked => { value => '^<1>$' },
+ dependson => { value => '^<1>$' },
+ bug_id => { value => '^<1>$' },
+ 'attachments.isobsolete' => { value => '^1'},
+ 'attachments.ispatch' => { value => '^1'},
+ 'attachments.isprivate' => { value => '^1' },
+ cclist_accessible => { value => '^1' },
+ reporter_accessible => { value => '^1' },
+ everconfirmed => { value => '^1' },
+ 'longdescs.count' => { value => '^3' },
+ 'longdescs.isprivate' => { value => '^1' },
+ creation_ts => { value => '^2037-01-01' },
+ delta_ts => { value => '^2037-01-01' },
+ deadline => { value => '^2037-02-01' },
+ estimated_time => { value => '^1.0' },
+ remaining_time => { value => '^9.0' },
+ work_time => { value => '^1.0' },
+ longdesc => { value => '^1-' },
+ percentage_complete => { value => '^10' },
+ FIELD_TYPE_BUG_ID, { value => '^<1>$' },
+ FIELD_TYPE_DATETIME, { value => '^2037-03-01' }
+};
+
+# Common overrides between lessthan and lessthaneq.
+use constant LESSTHAN_OVERRIDE => (
+ alias => { contains => [1,5] },
+ estimated_time => { contains => [1,5] },
+ qa_contact => { contains => [1,5] },
+ resolution => { contains => [1,5] },
+ status_whiteboard => { contains => [1,5] },
+ FIELD_TYPE_TEXTAREA, { contains => [1,5] },
+ FIELD_TYPE_FREETEXT, { contains => [1,5] },
+);
+
+# The mandatorily-set fields have values higher than <1>,
+# so bug 5 shows up.
+use constant GREATERTHAN_OVERRIDE => (
+ classification => { contains => [2,3,4,5] },
+ assigned_to => { contains => [2,3,4,5] },
+ bug_id => { contains => [2,3,4,5] },
+ bug_group => { contains => [1,2,3,4] },
+ bug_severity => { contains => [2,3,4,5] },
+ bug_status => { contains => [2,3,4,5] },
+ component => { contains => [2,3,4,5] },
+ commenter => { contains => [2,3,4,5] },
+ # keywords matches if *any* keyword matches
+ keywords => { contains => [1,2,3,4] },
+ longdesc => { contains => [1,2,3,4] },
+ op_sys => { contains => [2,3,4,5] },
+ priority => { contains => [2,3,4,5] },
+ product => { contains => [2,3,4,5] },
+ reporter => { contains => [2,3,4,5] },
+ rep_platform => { contains => [2,3,4,5] },
+ short_desc => { contains => [2,3,4,5] },
+ version => { contains => [2,3,4,5] },
+ tag => { contains => [1,2,3,4] },
+ target_milestone => { contains => [2,3,4,5] },
+ # Bug 2 is the only bug besides 1 that has a Requestee set.
+ 'requestees.login_name' => { contains => [2] },
+ FIELD_TYPE_SINGLE_SELECT, { contains => [2,3,4,5] },
+ # Override SINGLE_SELECT for resolution.
+ resolution => { contains => [2,3,4] },
+ # MULTI_SELECTs match if *any* value matches
+ FIELD_TYPE_MULTI_SELECT, { contains => [1,2,3,4] },
+);
+
+# For all positive multi-value types.
+use constant MULTI_BOOLEAN_OVERRIDE => (
+ 'attachments.ispatch' => { value => '1,1', contains => [1] },
+ 'attachments.isobsolete' => { value => '1,1', contains => [1] },
+ 'attachments.isprivate' => { value => '1,1', contains => [1] },
+ cclist_accessible => { value => '1,1', contains => [1] },
+ reporter_accessible => { value => '1,1', contains => [1] },
+ 'longdescs.isprivate' => { value => '1,1', contains => [1] },
+ everconfirmed => { value => '1,1', contains => [1] },
+);
+
+# Same as above, for negative multi-value types.
+use constant NEGATIVE_MULTI_BOOLEAN_OVERRIDE => (
+ 'attachments.ispatch' => { value => '1,1', contains => [2,3,4,5] },
+ 'attachments.isobsolete' => { value => '1,1', contains => [2,3,4,5] },
+ 'attachments.isprivate' => { value => '1,1', contains => [2,3,4,5] },
+ cclist_accessible => { value => '1,1', contains => [2,3,4,5] },
+ reporter_accessible => { value => '1,1', contains => [2,3,4,5] },
+ 'longdescs.isprivate' => { value => '1,1', contains => [2,3,4,5] },
+ everconfirmed => { value => '1,1', contains => [2,3,4,5] },
+);
+
+# For anyexact and anywordssubstr
+use constant ANY_OVERRIDE => (
+ 'longdescs.count' => { contains => [1,2,3,4] },
+ 'work_time' => { value => '1.0,2.0' },
+ dependson => { value => '<1>,<3>', contains => [1,3] },
+ MULTI_BOOLEAN_OVERRIDE,
+);
+
+# For all the changed* searches. The ones that have empty contains
+# are fields that never change in value, or will never be rationally
+# tracked in bugs_activity.
+use constant CHANGED_OVERRIDE => (
+ 'attachments.submitter' => { contains => [] },
+ bug_id => { contains => [] },
+ reporter => { contains => [] },
+ tag => { contains => [] },
+);
+
+#########
+# Tests #
+#########
+
+# The basic format of this is a hashref, where the keys are operators,
+# and each operator has an arrayref of tests that it runs. The tests
+# are hashrefs, with the following possible keys:
+#
+# contains: This is a list of bug numbers that the search is expected
+# to contain. (This is bug numbers, like 1,2,3, not the bug
+# ids. For a description of each bug number, see NUM_BUGS.)
+# Any bug not listed in "contains" must *not* show up in the
+# search result.
+# value: The value that you're searching for. There are certain special
+# codes that will be replaced with bug values when the tests are
+# run. In these examples below, "#" indicates a bug number:
+#
+# <#> - The field value for this bug.
+#
+# For any operator that has the string "word" in it, this is
+# *all* the values for the current field from the numbered bug,
+# joined by a space.
+#
+# If the operator has the string "substr" in it, then we
+# take a substring of the value (for single-value searches)
+# or we take a substring of each value and join them (for
+# multi-value "word" searches). The length of the substring
+# is determined by the SUBSTR_SIZE constants above.)
+#
+# For other operators, this just becomes the first value from
+# the field for the numbered bug.
+#
+# So, if we were running the "equals" test and checking the
+# cc field, <1> would become the login name of the first cc on
+# Bug 1. If we did an "anywords" search test, it would become
+# a space-separated string of the login names of all the ccs
+# on Bug 1. If we did an "anywordssubstr" search test, it would
+# become a space-separated string of the first few characters
+# of each CC's login name on Bug 1.
+#
+# <#-id> - The bug id of the numbered bug.
+# <#-reporter> - The login name of the numbered bug's reporter.
+# <#-delta> - The delta_ts of the numbered bug.
+#
+# escape: If true, we will call quotemeta() on the value immediately
+# before passing it to Search.pm.
+#
+# transform: A function to call on any field value before inserting
+# it for a <#> replacement. The transformation function
+# gets all of the bug's values for the field as its arguments.
+# if_equal: This allows you to override "contains" for the case where
+# the transformed value (from calling the "transform" function)
+# is equal to the original value.
+#
+# override: This allows you to override "contains" and "values" for
+# certain fields.
+use constant TESTS => {
+ equals => [
+ { contains => [1], value => '<1>' },
+ ],
+ notequals => [
+ { contains => [2,3,4,5], value => '<1>' },
+ ],
+ substring => [
+ { contains => [1], value => '<1>',
+ override => {
+ percentage_complete => { contains => [1,2,3] },
+ }
+ },
+ ],
+ casesubstring => [
+ { contains => [1], value => '<1>',
+ override => {
+ percentage_complete => { contains => [1,2,3] },
+ }
+ },
+ { contains => [], value => '<1>', transform => sub { lc($_[0]) },
+ extra_name => 'lc', if_equal => { contains => [1] },
+ override => {
+ percentage_complete => { contains => [1,2,3] },
+ }
+ },
+ ],
+ notsubstring => [
+ { contains => [2,3,4,5], value => '<1>',
+ override => {
+ percentage_complete => { contains => [4,5] },
+ },
+ }
+ ],
+ regexp => [
+ { contains => [1], value => '<1>', escape => 1,
+ override => {
+ percentage_complete => { value => '^10' },
+ }
+ },
+ { contains => [1], value => '^1-', override => REGEX_OVERRIDE },
+ ],
+ notregexp => [
+ { contains => [2,3,4,5], value => '<1>', escape => 1,
+ override => {
+ percentage_complete => { value => '^10' },
+ }
+ },
+ { contains => [2,3,4,5], value => '^1-', override => REGEX_OVERRIDE },
+ ],
+ lessthan => [
+ { contains => [1], value => 2,
+ override => {
+ # A lot of these contain bug 5 because an empty value is validly
+ # less than the specified value.
+ bug_file_loc => { value => 'http://2-', contains => [1,5] },
+ see_also => { value => 'http://2-' },
+ 'attachments.mimetype' => { value => 'text/x-2-' },
+ blocked => { value => '<4-id>', contains => [1,2] },
+ dependson => { value => '<3-id>', contains => [1,3] },
+ bug_id => { value => '<2-id>' },
+ 'attachments.isprivate' => { value => 1, contains => [2,3,4] },
+ 'attachments.isobsolete' => { value => 1, contains => [2,3,4] },
+ 'attachments.ispatch' => { value => 1, contains => [2,3,4] },
+ cclist_accessible => { value => 1, contains => [2,3,4,5] },
+ reporter_accessible => { value => 1, contains => [2,3,4,5] },
+ 'longdescs.count' => { value => 3, contains => [2,3,4,5] },
+ 'longdescs.isprivate' => { value => 1, contains => [1,2,3,4,5] },
+ everconfirmed => { value => 1, contains => [2,3,4,5] },
+ creation_ts => { value => '2037-01-02', contains => [1,5] },
+ delta_ts => { value => '2037-01-02', contains => [1,5] },
+ deadline => { value => '2037-02-02', contains => [1,5] },
+ remaining_time => { value => 10, contains => [1,5] },
+ percentage_complete => { value => 11, contains => [1,5] },
+ longdesc => { value => '2-', contains => [1,5] },
+ work_time => { value => 1, contains => [5] },
+ FIELD_TYPE_BUG_ID, { value => '<2>', contains => [1,5] },
+ FIELD_TYPE_DATETIME, { value => '2037-03-02', contains => [1,5] },
+ LESSTHAN_OVERRIDE,
+ }
+ },
+ ],
+ lessthaneq => [
+ { contains => [1], value => '<1>',
+ override => {
+ 'attachments.isobsolete' => { value => 0, contains => [2,3,4] },
+ 'attachments.ispatch' => { value => 0, contains => [2,3,4] },
+ 'attachments.isprivate' => { value => 0, contains => [2,3,4] },
+ cclist_accessible => { value => 0, contains => [2,3,4,5] },
+ reporter_accessible => { value => 0, contains => [2,3,4,5] },
+ 'longdescs.count' => { value => 2, contains => [2,3,4,5] },
+ 'longdescs.isprivate' => { value => -1, contains => [] },
+ everconfirmed => { value => 0, contains => [2,3,4,5] },
+ bug_file_loc => { contains => [1,5] },
+ blocked => { contains => [1,2] },
+ deadline => { contains => [1,5] },
+ dependson => { contains => [1,3] },
+ creation_ts => { contains => [1,5] },
+ delta_ts => { contains => [1,5] },
+ remaining_time => { contains => [1,5] },
+ longdesc => { contains => [1,5] },
+ percentage_complete => { contains => [1,5] },
+ work_time => { value => 1, contains => [1,5] },
+ FIELD_TYPE_BUG_ID, { contains => [1,5] },
+ FIELD_TYPE_DATETIME, { contains => [1,5] },
+ LESSTHAN_OVERRIDE,
+ },
+ },
+ ],
+ greaterthan => [
+ { contains => [2,3,4], value => '<1>',
+ override => {
+ dependson => { contains => [3] },
+ blocked => { contains => [2] },
+ 'attachments.ispatch' => { value => 0, contains => [1] },
+ 'attachments.isobsolete' => { value => 0, contains => [1] },
+ 'attachments.isprivate' => { value => 0, contains => [1] },
+ cclist_accessible => { value => 0, contains => [1] },
+ reporter_accessible => { value => 0, contains => [1] },
+ 'longdescs.count' => { value => 2, contains => [1] },
+ 'longdescs.isprivate' => { value => 0, contains => [1] },
+ everconfirmed => { value => 0, contains => [1] },
+ 'flagtypes.name' => { value => 2, contains => [2,3,4] },
+ GREATERTHAN_OVERRIDE,
+ },
+ },
+ ],
+ greaterthaneq => [
+ { contains => [2,3,4], value => '<2>',
+ override => {
+ 'attachments.ispatch' => { value => 1, contains => [1] },
+ 'attachments.isobsolete' => { value => 1, contains => [1] },
+ 'attachments.isprivate' => { value => 1, contains => [1] },
+ cclist_accessible => { value => 1, contains => [1] },
+ reporter_accessible => { value => 1, contains => [1] },
+ 'longdescs.count' => { value => 3, contains => [1] },
+ 'longdescs.isprivate' => { value => 1, contains => [1] },
+ everconfirmed => { value => 1, contains => [1] },
+ dependson => { value => '<3>', contains => [1,3] },
+ blocked => { contains => [1,2] },
+ GREATERTHAN_OVERRIDE,
+ }
+ },
+ ],
+ matches => [
+ { contains => [1], value => '<1>' },
+ ],
+ notmatches => [
+ { contains => [2,3,4,5], value => '<1>' },
+ ],
+ anyexact => [
+ { contains => [1,2], value => '<1>, <2>',
+ override => { ANY_OVERRIDE } },
+ ],
+ anywordssubstr => [
+ { contains => [1,2], value => '<1> <2>',
+ override => {
+ ANY_OVERRIDE,
+ percentage_complete => { contains => [1,2,3] },
+ }
+ },
+ ],
+ allwordssubstr => [
+ { contains => [1], value => '<1>',
+ override => {
+ MULTI_BOOLEAN_OVERRIDE,
+ # We search just the number "1" for percentage_complete,
+ # which matches a lot of bugs.
+ percentage_complete => { contains => [1,2,3] },
+ },
+ },
+ { contains => [], value => '<1>,<2>',
+ override => {
+ dependson => { value => '<1-id> <3-id>', contains => [] },
+ # bug 3 has the value "21" here, so matches "2,1"
+ percentage_complete => { value => '<2>,<3>', contains => [3] },
+ # 1 0 matches bug 1, which has both public and private comments.
+ 'longdescs.isprivate' => { contains => [1] },
+ }
+ },
+ ],
+ nowordssubstr => [
+ { contains => [2,3,4,5], value => '<1>',
+ override => {
+ # longdescs.isprivate translates to "1 0", so no bugs should
+ # show up.
+ 'longdescs.isprivate' => { contains => [] },
+ percentage_complete => { contains => [4,5] },
+ work_time => { contains => [2,3,4,5] },
+ }
+ },
+ ],
+ anywords => [
+ { contains => [1], value => '<1>',
+ override => {
+ MULTI_BOOLEAN_OVERRIDE,
+ }
+ },
+ { contains => [1,2], value => '<1> <2>',
+ override => {
+ MULTI_BOOLEAN_OVERRIDE,
+ dependson => { value => '<1> <3>', contains => [1,3] },
+ 'longdescs.count' => { contains => [1,2,3,4] },
+ },
+ },
+ ],
+ allwords => [
+ { contains => [1], value => '<1>',
+ override => { MULTI_BOOLEAN_OVERRIDE } },
+ { contains => [], value => '<1> <2>',
+ override => {
+ dependson => { contains => [], value => '<2-id> <3-id>' },
+ # 1 0 matches bug 1, which has both public and private comments.
+ 'longdescs.isprivate' => { contains => [1] },
+ }
+ },
+ ],
+ nowords => [
+ { contains => [2,3,4,5], value => '<1>',
+ override => {
+ # longdescs.isprivate translates to "1 0", so no bugs should
+ # show up.
+ 'longdescs.isprivate' => { contains => [] },
+ work_time => { contains => [2,3,4,5] },
+ }
+ },
+ ],
+
+ changedbefore => [
+ { contains => [1], value => '<1-delta>',
+ override => {
+ CHANGED_OVERRIDE,
+ creation_ts => { contains => [1,5] },
+ blocked => { contains => [1,2] },
+ dependson => { contains => [1,3] },
+ longdesc => { contains => [1,5] },
+ 'longdescs.count' => { contains => [1,5] },
+ }
+ },
+ ],
+ changedafter => [
+ { contains => [2,3,4], value => '<2-delta>',
+ override => {
+ CHANGED_OVERRIDE,
+ creation_ts => { contains => [3,4] },
+ # We only change this for one bug, and it doesn't match.
+ 'longdescs.isprivate' => { contains => [] },
+ # Same for everconfirmed.
+ 'everconfirmed' => { contains => [] },
+ # For blocked and dependson, they have the delta_ts of bug1
+ # in the bugs_activity table, so they won't ever match.
+ blocked => { contains => [] },
+ dependson => { contains => [] },
+ }
+ },
+ ],
+ changedfrom => [
+ { contains => [1], value => '<1>',
+ override => {
+ CHANGED_OVERRIDE,
+ # The test never changes an already-set dependency field, but
+ # we *can* attempt to test searching against an empty value,
+ # which should get us some bugs.
+ blocked => { value => '', contains => [1,2] },
+ dependson => { value => '', contains => [1,3] },
+ FIELD_TYPE_BUG_ID, { value => '', contains => [1,2,3,4] },
+ # longdesc changedfrom doesn't make any sense.
+ longdesc => { contains => [] },
+ # Nor does creation_ts changedfrom.
+ creation_ts => { contains => [] },
+ 'attach_data.thedata' => { contains => [] },
+ bug_id => { value => '<1-id>', contains => [] },
+ },
+ },
+ ],
+ changedto => [
+ { contains => [1], value => '<1>',
+ override => {
+ CHANGED_OVERRIDE,
+ # I can't imagine any use for creation_ts changedto.
+ creation_ts => { contains => [] },
+ }
+ },
+ ],
+ changedby => [
+ { contains => [1], value => '<1-reporter>',
+ override => {
+ CHANGED_OVERRIDE,
+ blocked => { contains => [1,2] },
+ dependson => { contains => [1,3] },
+ },
+ },
+ ],
+};
+
+# Fields that do not behave as we expect, for InjectionTest.
+# search => 1 means the Bugzilla::Search creation fails.
+# sql_error is a regex that specifies a SQL error that's OK for us to throw.
+# operator_ok overrides the "brokenness" of certain operators, so that they
+# are always OK for that field/operator combination.
+use constant INJECTION_BROKEN_FIELD => {
+ # Pg can't run injection tests against integer or date fields. See bug 577557.
+ 'attachments.isobsolete' => { db_skip => ['Pg'] },
+ 'attachments.ispatch' => { db_skip => ['Pg'] },
+ 'attachments.isprivate' => { db_skip => ['Pg'] },
+ blocked => { db_skip => ['Pg'] },
+ bug_id => { db_skip => ['Pg'] },
+ cclist_accessible => { db_skip => ['Pg'] },
+ creation_ts => { db_skip => ['Pg'] },
+ days_elapsed => { db_skip => ['Pg'] },
+ dependson => { db_skip => ['Pg'] },
+ deadline => { db_skip => ['Pg'] },
+ delta_ts => { db_skip => ['Pg'] },
+ estimated_time => { db_skip => ['Pg'] },
+ everconfirmed => { db_skip => ['Pg'] },
+ 'longdescs.isprivate' => { db_skip => ['Pg'] },
+ percentage_complete => { db_skip => ['Pg'] },
+ remaining_time => { db_skip => ['Pg'] },
+ reporter_accessible => { db_skip => ['Pg'] },
+ work_time => { db_skip => ['Pg'] },
+ FIELD_TYPE_BUG_ID, { db_skip => ['Pg'] },
+ FIELD_TYPE_DATETIME, { db_skip => ['Pg'] },
+ owner_idle_time => { search => 1 },
+ 'longdescs.count' => {
+ search => 1,
+ db_skip => ['Pg'],
+ operator_ok => [qw(allwords allwordssubstr anywordssubstr casesubstring
+ changedbefore changedafter greaterthan greaterthaneq
+ lessthan lessthaneq notregexp notsubstring
+ nowordssubstr regexp substring anywords
+ notequals nowords equals anyexact)],
+ },
+};
+
+# Operators that do not behave as we expect, for InjectionTest.
+# search => 1 means the Bugzilla::Search creation fails, but
+# field_ok contains fields that it does actually succeed for.
+use constant INJECTION_BROKEN_OPERATOR => {
+ changedafter => { search => 1, field_ok => ['creation_ts'] },
+ changedbefore => { search => 1, field_ok => ['creation_ts'] },
+ changedby => { search => 1 },
+};
+
+# Tests run by Bugzilla::Test::Search::InjectionTest.
+# We have to make sure the values are all one word or they'll be split
+# up by the multi-word tests.
+use constant INJECTION_TESTS => (
+ { value => ';SEMICOLON_TEST' },
+ { value => '--COMMENT_TEST' },
+ { value => "'QUOTE_TEST" },
+ { value => "';QUOTE_SEMICOLON_TEST" },
+ { value => '/*STAR_COMMENT_TEST' }
+);
+
+#################
+# Special Tests #
+#################
+
+use constant SPECIAL_PARAM_TESTS => (
+ { field => 'bug_status', operator => 'anyexact', value => '__open__',
+ contains => [5] },
+ { field => 'bug_status', operator => 'anyexact', value => '__closed__',
+ contains => [1,2,3,4] },
+ { field => 'bug_status', operator => 'anyexact', value => '__all__',
+ contains => [1,2,3,4,5] },
+
+ { field => 'resolution', operator => 'anyexact', value => '---',
+ contains => [5] },
+
+ # email* query parameters.
+ { field => 'assigned_to', operator => 'anyexact',
+ value => '<1>, <2-reporter>', contains => [1,2],
+ extra_params => { emailreporter1 => 1 } },
+ { field => 'assigned_to', operator => 'equals',
+ value => '<1>', extra_name => 'email2', contains => [],
+ extra_params => {
+ email2 => generate_random_password(100), emaillongdesc2 => 1,
+ },
+ },
+
+ # standard pronouns
+ { field => 'assigned_to', operator => 'equals', value => '%assignee%',
+ contains => [1,2,3,4,5] },
+ { field => 'reporter', operator => 'equals', value => '%reporter%',
+ contains => [1,2,3,4,5] },
+ { field => 'qa_contact', operator => 'equals', value => '%qacontact%',
+ contains => [1,2,3,4,5] },
+ { field => 'cc', operator => 'equals', value => '%user%',
+ contains => [1] },
+ # group pronouns
+ { field => 'reporter', operator => 'equals',
+ value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] },
+ { field => 'assigned_to', operator => 'equals',
+ value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] },
+ { field => 'qa_contact', operator => 'equals',
+ value => '%group.<1-bug_group>%', contains => [1,2,3,4] },
+ { field => 'cc', operator => 'equals',
+ value => '%group.<1-bug_group>%', contains => [1,2,3,4] },
+ { field => 'commenter', operator => 'equals',
+ value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] },
+);
+
+use constant CUSTOM_SEARCH_TESTS => (
+ { name => 'OP without CP', contains => [1],
+ params => [
+ { f => 'OP' },
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ ]
+ },
+
+ { name => 'Empty OP/CP pair before criteria', contains => [1],
+ params => [
+ { f => 'OP' }, { f => 'CP' },
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ ]
+ },
+
+ { name => 'Empty OP/CP pair after criteria', contains => [1],
+ params => [
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ { f => 'OP' }, { f => 'CP' },
+ ]
+ },
+
+ { name => 'empty OP/CP mid criteria', contains => [1],
+ columns => ['assigned_to'],
+ params => [
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ { f => 'OP' }, { f => 'CP' },
+ { f => 'assigned_to', o => 'substr', v => '@' },
+ ]
+ },
+
+ { name => 'bug_id = 1 AND assigned_to contains @', contains => [1],
+ columns => ['assigned_to'],
+ params => [
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ { f => 'assigned_to', o => 'substr', v => '@' },
+ ]
+ },
+
+ { name => 'NOT(bug_id = 1) AND NOT(assigned_to = 2)',
+ contains => [3,4,5],
+ columns => ['assigned_to'],
+ params => [
+ { n => 1, f => 'bug_id', o => 'equals', v => '<1>' },
+ { n => 1, f => 'assigned_to', o => 'equals', v => '<2>' },
+ ]
+ },
+
+ { name => 'bug_id = 1 OR assigned_to = 2', contains => [1,2],
+ columns => ['assigned_to'], top_params => { j_top => 'OR' },
+ params => [
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ { f => 'assigned_to', o => 'equals', v => '<2>' },
+ ]
+ },
+
+ { name => 'NOT(bug_id = 1 AND assigned_to = 1)', contains => [2,3,4,5],
+ columns => ['assigned_to'],
+ params => [
+ { f => 'OP', n => 1 },
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ { f => 'assigned_to', o => 'equals', v => '<1>' },
+ { f => 'CP' },
+ ]
+ },
+
+
+ { name => '(bug_id = 1 AND assigned_to contains @) '
+ . ' OR (bug_id = 2 AND assigned_to contains @)',
+ contains => [1,2], columns => ['assigned_to'],
+ top_params => { j_top => 'OR' },
+ params => [
+ { f => 'OP' },
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ { f => 'assigned_to', o => 'substr', v => '@' },
+ { f => 'CP' },
+ { f => 'OP' },
+ { f => 'bug_id', o => 'equals', v => '<2>' },
+ { f => 'assigned_to', o => 'substr', v => '@' },
+ { f => 'CP' },
+ ]
+ },
+
+ { name => '(bug_id = 1 OR assigned_to = 2) '
+ . ' AND (bug_id = 2 OR assigned_to = 1)',
+ contains => [1,2], columns => ['assigned_to'],
+ params => [
+ { f => 'OP', j => 'OR' },
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ { f => 'assigned_to', o => 'equals', v => '<2>' },
+ { f => 'CP' },
+ { f => 'OP', j => 'OR' },
+ { f => 'bug_id', o => 'equals', v => '<2>' },
+ { f => 'assigned_to', o => 'equals', v => '<1>' },
+ { f => 'CP' },
+ ]
+ },
+
+ { name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) '
+ . ' AND (bug_id = 2 OR assigned_to = 1) )',
+ contains => [1,2,3], columns => ['assigned_to'],
+ top_params => { j_top => 'OR' },
+ params => [
+ { f => 'bug_id', o => 'equals', v => '<3>' },
+ { f => 'OP' },
+ { f => 'OP', j => 'OR' },
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ { f => 'assigned_to', o => 'equals', v => '<2>' },
+ { f => 'CP' },
+ { f => 'OP', j => 'OR' },
+ { f => 'bug_id', o => 'equals', v => '<2>' },
+ { f => 'assigned_to', o => 'equals', v => '<1>' },
+ { f => 'CP' },
+ { f => 'CP' },
+ ]
+ },
+
+ { name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) '
+ . ' AND (bug_id = 2 OR assigned_to = 1) ) OR bug_id = 4',
+ contains => [1,2,3,4], columns => ['assigned_to'],
+ top_params => { j_top => 'OR' },
+ params => [
+ { f => 'bug_id', o => 'equals', v => '<3>' },
+ { f => 'OP' },
+ { f => 'OP', j => 'OR' },
+ { f => 'bug_id', o => 'equals', v => '<1>' },
+ { f => 'assigned_to', o => 'equals', v => '<2>' },
+ { f => 'CP' },
+ { f => 'OP', j => 'OR' },
+ { f => 'bug_id', o => 'equals', v => '<2>' },
+ { f => 'assigned_to', o => 'equals', v => '<1>' },
+ { f => 'CP' },
+ { f => 'CP' },
+ { f => 'bug_id', o => 'equals', v => '<4>' },
+ ]
+ },
+
+);
+
+1;
diff --git a/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/CustomTest.pm b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/CustomTest.pm
new file mode 100644
index 0000000..62a6225
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/CustomTest.pm
@@ -0,0 +1,115 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Google, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2011 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This module represents a test with custom URL parameters.
+# Tests like this are specified in CUSTOM_SEARCH_TESTS in
+# Bugzilla::Test::Search::Constants.
+package Bugzilla::Test::Search::CustomTest;
+use base qw(Bugzilla::Test::Search::FieldTest);
+use strict;
+use warnings;
+
+use Bugzilla::Test::Search::FieldTest;
+use Bugzilla::Test::Search::OperatorTest;
+
+use Storable qw(dclone);
+
+###############
+# Constructor #
+###############
+
+sub new {
+ my ($class, $test, $search_test) = @_;
+ bless { raw_test => dclone($test), search_test => $search_test }, $class;
+}
+
+#############
+# Accessors #
+#############
+
+sub search_test { return $_[0]->{search_test} }
+sub name { return 'Custom: ' . $_[0]->test->{name} }
+sub test { return $_[0]->{raw_test} }
+
+sub operator_test { die "unimplemented" }
+sub field_object { die "unimplemented" }
+sub main_value { die "unimplenmented" }
+sub test_value { die "unimplemented" }
+# Custom tests don't use transforms.
+sub transformed_value_was_equal { 0 }
+sub debug_value {
+ my ($self) = @_;
+ my $string = '';
+ my $params = $self->search_params;
+ foreach my $param (keys %$params) {
+ $string .= $param . "=" . $params->{$param} . '&';
+ }
+ chop($string);
+ return $string;
+}
+
+# The tests we know are broken for this operator/field combination.
+sub _known_broken { return {} }
+sub contains_known_broken { return undef }
+sub search_known_broken { return undef }
+sub field_not_yet_implemented { return undef }
+sub invalid_field_operator_combination { return undef }
+
+#########################################
+# Accessors: Bugzilla::Search Arguments #
+#########################################
+
+# Converts the f, o, v rows into f0, o0, v0, etc. and translates
+# the values appropriately.
+sub search_params {
+ my ($self) = @_;
+
+ my %params = %{ $self->test->{top_params} || {} };
+ my $counter = 0;
+ foreach my $row (@{ $self->test->{params} }) {
+ $row->{v} = $self->translate_value($row) if exists $row->{v};
+ foreach my $key (keys %$row) {
+ $params{"${key}$counter"} = $row->{$key};
+ }
+ $counter++;
+ }
+
+ return \%params;
+}
+
+sub translate_value {
+ my ($self, $row) = @_;
+ my $as_test = { field => $row->{f}, operator => $row->{o},
+ value => $row->{v} };
+ my $operator_test = new Bugzilla::Test::Search::OperatorTest($row->{o},
+ $self->search_test);
+ my $field = Bugzilla::Field->check($row->{f});
+ my $field_test = new Bugzilla::Test::Search::FieldTest($operator_test,
+ $field, $as_test);
+ return $field_test->translated_value;
+}
+
+sub search_columns {
+ my ($self) = @_;
+ return ['bug_id', @{ $self->test->{columns} || [] }];
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/FieldTest.pm b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/FieldTest.pm
new file mode 100644
index 0000000..283a90d
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/FieldTest.pm
@@ -0,0 +1,614 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This module represents the tests that get run on a single
+# operator/field combination for Bugzilla::Test::Search.
+# This is where all the actual testing happens.
+package Bugzilla::Test::Search::FieldTest;
+
+use strict;
+use warnings;
+use Bugzilla::Search;
+use Bugzilla::Test::Search::Constants;
+
+use Data::Dumper;
+use Scalar::Util qw(blessed);
+use Test::More;
+use Test::Exception;
+
+###############
+# Constructor #
+###############
+
+sub new {
+ my ($class, $operator_test, $field, $test) = @_;
+ return bless { operator_test => $operator_test,
+ field_object => $field,
+ raw_test => $test }, $class;
+}
+
+#############
+# Accessors #
+#############
+
+sub num_tests { return TESTS_PER_RUN }
+
+# The Bugzilla::Test::Search::OperatorTest that this is a child of.
+sub operator_test { return $_[0]->{operator_test} }
+# The Bugzilla::Field being tested.
+sub field_object { return $_[0]->{field_object} }
+# The name of the field being tested, which we need much more often
+# than we need the object.
+sub field {
+ my ($self) = @_;
+ $self->{field_name} ||= $self->field_object->name;
+ return $self->{field_name};
+}
+# The Bugzilla::Test::Search object that this is a child of.
+sub search_test { return $_[0]->operator_test->search_test }
+# The operator being tested
+sub operator { return $_[0]->operator_test->operator }
+# The bugs currently being tested by Bugzilla::Test::Search.
+sub bugs { return $_[0]->search_test->bugs }
+sub bug {
+ my $self = shift;
+ return $self->search_test->bug(@_);
+}
+
+# The name displayed for this test by Test::More. Used in test descriptions.
+sub name {
+ my ($self) = @_;
+ my $field = $self->field;
+ my $operator = $self->operator;
+ my $value = $self->main_value;
+
+ my $name = "$field-$operator-$value";
+ if (my $extra_name = $self->test->{extra_name}) {
+ $name .= "-$extra_name";
+ }
+ return $name;
+}
+
+# The appropriate value from the TESTS constant for this test, taking
+# into account overrides.
+sub test {
+ my $self = shift;
+ return $self->{test} if $self->{test};
+
+ my %test = %{ $self->{raw_test} };
+
+ # We have field name overrides...
+ my $override = $test{override}->{$self->field};
+ # And also field type overrides.
+ if (!$override) {
+ $override = $test{override}->{$self->field_object->type} || {};
+ }
+
+ foreach my $key (%$override) {
+ $test{$key} = $override->{$key};
+ }
+
+ $self->{test} = \%test;
+ return $self->{test};
+}
+
+# All the values for all the bugs for this field.
+sub _field_values {
+ my ($self) = @_;
+ return $self->{field_values} if $self->{field_values};
+
+ my %field_values;
+ foreach my $number (1..NUM_BUGS) {
+ $field_values{$number} = $self->_field_values_for_bug($number);
+ }
+ $self->{field_values} = \%field_values;
+ return $self->{field_values};
+}
+# The values for this field for the numbered bug.
+sub bug_values {
+ my ($self, $number) = @_;
+ return @{ $self->_field_values->{$number} };
+}
+
+# The untranslated, non-overriden value--used in the name of the test
+# and other places.
+sub main_value { return $_[0]->{raw_test}->{value} }
+# The untranslated test value, taking into account overrides.
+sub test_value { return $_[0]->test->{value} };
+# The value translated appropriately for passing to Bugzilla::Search.
+sub translated_value {
+ my $self = shift;
+ if (!exists $self->{translated_value}) {
+ my $value = $self->search_test->value_translation_cache($self);
+ if (!defined $value) {
+ $value = $self->_translate_value();
+ $self->search_test->value_translation_cache($self, $value);
+ }
+ $self->{translated_value} = $value;
+ }
+ return $self->{translated_value};
+}
+# Used in failure diagnostic messages.
+sub debug_value {
+ my ($self) = @_;
+ return "Value: '" . $self->translated_value . "'";
+}
+
+# True for a bug if we ran the "transform" function on it and the
+# result was equal to its first value.
+sub transformed_value_was_equal {
+ my ($self, $number, $value) = @_;
+ if (@_ > 2) {
+ $self->{transformed_value_was_equal}->{$number} = $value;
+ $self->search_test->was_equal_cache($self, $number, $value);
+ }
+ my $cached = $self->search_test->was_equal_cache($self, $number);
+ return $cached if defined $cached;
+ return $self->{transformed_value_was_equal}->{$number};
+}
+
+# True if this test is supposed to contain the numbered bug.
+sub bug_is_contained {
+ my ($self, $number) = @_;
+ my $contains = $self->test->{contains};
+ if ($self->transformed_value_was_equal($number)
+ and !$self->test->{override}->{$self->field}->{contains})
+ {
+ $contains = $self->test->{if_equal}->{contains};
+ }
+ return grep($_ == $number, @$contains) ? 1 : 0;
+}
+
+###################################################
+# Accessors: Ways of doing SKIP and TODO on tests #
+###################################################
+
+# The tests we know are broken for this operator/field combination.
+sub _known_broken {
+ my ($self, $constant, $skip_pg_check) = @_;
+ $constant ||= KNOWN_BROKEN;
+ my $field = $self->field;
+ my $type = $self->field_object->type;
+ my $operator = $self->operator;
+ my $value = $self->main_value;
+ my $value_name = "$operator-$value";
+ if (my $extra_name = $self->test->{extra_name}) {
+ $value_name .= "-$extra_name";
+ }
+
+ my $value_broken = $constant->{$value_name}->{$field};
+ $value_broken ||= $constant->{$value_name}->{$type};
+ return $value_broken if $value_broken;
+ my $operator_broken = $constant->{$operator}->{$field};
+ $operator_broken ||= $constant->{$operator}->{$type};
+ return $operator_broken if $operator_broken;
+ return {};
+}
+
+# True if the "contains" search for the numbered bug is broken.
+# That is, either the result is supposed to contain it and doesn't,
+# or the result is not supposed to contain it and does.
+sub contains_known_broken {
+ my ($self, $number) = @_;
+ my $field = $self->field;
+ my $operator = $self->operator;
+
+ my $contains_broken = $self->_known_broken->{contains} || [];
+ if (grep($_ == $number, @$contains_broken)) {
+ return "$field $operator contains $number is known to be broken";
+ }
+ return undef;
+}
+
+# Used by subclasses. Checks both bug_is_contained and contains_known_broken
+# to tell you whether or not the bug will *actually* be found by the test.
+sub will_actually_contain_bug {
+ my ($self, $number) = @_;
+ my $is_contained = $self->bug_is_contained($number) ? 1 : 0;
+ my $is_broken = $self->contains_known_broken($number) ? 1 : 0;
+
+ # If the test is supposed to contain the bug and *isn't* broken,
+ # then the test will contain the bug.
+ return 1 if ($is_contained and !$is_broken);
+ # If this test is *not* supposed to contain the bug, but that test is
+ # broken, then this test *will* contain the bug.
+ return 1 if (!$is_contained and $is_broken);
+
+ return 0;
+}
+
+# Returns a string if creating a Bugzilla::Search object throws an error,
+# with this field/operator/value combination.
+sub search_known_broken {
+ my ($self) = @_;
+ my $field = $self->field;
+ my $operator = $self->operator;
+ if ($self->_known_broken->{search}) {
+ return "Bugzilla::Search for $field $operator is known to be broken";
+ }
+ return undef;
+}
+
+# Returns a string if we haven't yet implemented the tests for this field,
+# but we plan to in the future.
+sub field_not_yet_implemented {
+ my ($self) = @_;
+ my $skip_this_field = grep { $_ eq $self->field } SKIP_FIELDS;
+ if ($skip_this_field) {
+ my $field = $self->field;
+ return "$field testing not yet implemented";
+ }
+ return undef;
+}
+
+# Returns a message if this field/operator combination can't ever be run.
+# At no time in the future will this field/operator combination ever work.
+sub invalid_field_operator_combination {
+ my ($self) = @_;
+ my $field = $self->field;
+ my $operator = $self->operator;
+
+ if ($field eq 'content' && $operator !~ /matches/) {
+ return "content field does not support $operator";
+ }
+ elsif ($operator =~ /matches/ && $field ne 'content') {
+ return "matches operator does not support fields other than content";
+ }
+ return undef;
+}
+
+# True if this field is broken in an OR combination.
+sub join_broken {
+ my ($self, $or_broken_map) = @_;
+ my $or_broken = $or_broken_map->{$self->field . '-' . $self->operator};
+ if (!$or_broken) {
+ # See if this is a comment field, and in that case, if there's
+ # a generic entry for all comment fields.
+ my $is_comment_field = COMMENT_FIELDS->{$self->field};
+ if ($is_comment_field) {
+ $or_broken = $or_broken_map->{'longdescs.-' . $self->operator};
+ }
+ }
+ return $or_broken;
+}
+
+#########################################
+# Accessors: Bugzilla::Search Arguments #
+#########################################
+
+# The data that will get passed to Bugzilla::Search as its arguments.
+sub search_params {
+ my ($self) = @_;
+ return $self->{search_params} if $self->{search_params};
+
+ my %params = (
+ "field0-0-0" => $self->field,
+ "type0-0-0" => $self->operator,
+ "value0-0-0" => $self->translated_value,
+ );
+
+ $self->{search_params} = \%params;
+ return $self->{search_params};
+}
+
+sub search_columns {
+ my ($self) = @_;
+ my $field = $self->field;
+ my @search_fields = qw(bug_id);
+ if ($self->field_object->buglist) {
+ my $col_name = COLUMN_TRANSLATION->{$field} || $field;
+ push(@search_fields, $col_name);
+ }
+ return \@search_fields;
+}
+
+
+################
+# Field Values #
+################
+
+sub _field_values_for_bug {
+ my ($self, $number) = @_;
+ my $field = $self->field;
+
+ my @values;
+
+ if ($field =~ /^attach.+\.(.+)$/ ) {
+ my $attach_field = $1;
+ $attach_field = ATTACHMENT_FIELDS->{$attach_field} || $attach_field;
+ @values = $self->_values_for($number, 'attachments', $attach_field);
+ }
+ elsif (my $flag_field = FLAG_FIELDS->{$field}) {
+ @values = $self->_values_for($number, 'flags', $flag_field);
+ }
+ elsif (my $translation = COMMENT_FIELDS->{$field}) {
+ @values = $self->_values_for($number, 'comments', $translation);
+ # We want the last value to come first, so that single-value
+ # searches use the last comment.
+ @values = reverse @values;
+ }
+ elsif ($field eq 'longdescs.count') {
+ @values = scalar(@{ $self->bug($number)->comments });
+ }
+ elsif ($field eq 'work_time') {
+ @values = $self->_values_for($number, 'actual_time');
+ }
+ elsif ($field eq 'bug_group') {
+ @values = $self->_values_for($number, 'groups_in', 'name');
+ }
+ elsif ($field eq 'keywords') {
+ @values = $self->_values_for($number, 'keyword_objects', 'name');
+ }
+ elsif ($field eq 'content') {
+ @values = $self->_values_for($number, 'short_desc');
+ }
+ elsif ($field eq 'see_also') {
+ @values = $self->_values_for($number, 'see_also', 'name');
+ }
+ elsif ($field eq 'tag') {
+ @values = $self->_values_for($number, 'tags');
+ }
+ # Bugzilla::Bug truncates creation_ts, but we need the full value
+ # from the database. This has no special value for changedfrom,
+ # because it never changes.
+ elsif ($field eq 'creation_ts') {
+ my $bug = $self->bug($number);
+ my $creation_ts = Bugzilla->dbh->selectrow_array(
+ 'SELECT creation_ts FROM bugs WHERE bug_id = ?',
+ undef, $bug->id);
+ @values = ($creation_ts);
+ }
+ else {
+ @values = $self->_values_for($number, $field);
+ }
+
+ # We convert user objects to their login name, here, all in one
+ # block for simplicity.
+ if (grep { $_ eq $field } USER_FIELDS) {
+ # requestees.login_name is empty for most bugs (but checking
+ # blessed(undef) handles that.
+ # Values that come from %original_values aren't User objects.
+ @values = map { blessed($_) ? $_->login : $_ } @values;
+ @values = grep { defined $_ } @values;
+ }
+
+ return \@values;
+}
+
+sub _values_for {
+ my ($self, $number, $bug_field, $item_field) = @_;
+
+ my $item;
+ if ($self->operator eq 'changedfrom') {
+ $item = $self->search_test->bug_create_value($number, $bug_field);
+ }
+ else {
+ my $bug = $self->bug($number);
+ $item = $bug->$bug_field;
+ }
+
+ if ($item_field) {
+ if ($bug_field eq 'flags' and $item_field eq 'name') {
+ return (map { $_->name . $_->status } @$item);
+ }
+ return (map { $self->_get_item($_, $item_field) } @$item);
+ }
+
+ return @$item if ref($item) eq 'ARRAY';
+ return $item if defined $item;
+ return ();
+}
+
+sub _get_item {
+ my ($self, $from, $field) = @_;
+ if (blessed($from)) {
+ return $from->$field;
+ }
+ return $from->{$field};
+}
+
+#####################
+# Value Translation #
+#####################
+
+# This function translates the "value" specified in TESTS into an actual
+# search value to pass to Search.pm. This means that we get the value
+# from the current bug (or, in the case of changedfrom, from %original_values)
+# and then we insert it as required into the "value" from TESTS. (For example,
+# <1> becomes the value for the field from bug 1.)
+sub _translate_value {
+ my $self = shift;
+ my $value = $self->test_value;
+ foreach my $number (1..NUM_BUGS) {
+ $value = $self->_translate_value_for_bug($number, $value);
+ }
+ # Sanity check to make sure that none of the <> stuff was left in.
+ if ($value =~ /<\d/) {
+ die $self->name . ": value untranslated: $value\n";
+ }
+ return $value;
+}
+
+sub _translate_value_for_bug {
+ my ($self, $number, $value) = @_;
+
+ my $bug = $self->bug($number);
+
+ my $bug_id = $bug->id;
+ $value =~ s/<$number-id>/$bug_id/g;
+ my $bug_delta = $bug->delta_ts;
+ $value =~ s/<$number-delta>/$bug_delta/g;
+ my $reporter = $bug->reporter->login;
+ $value =~ s/<$number-reporter>/$reporter/g;
+ if ($value =~ /<$number-bug_group>/) {
+ my @bug_groups = map { $_->name } @{ $bug->groups_in };
+ @bug_groups = grep { $_ =~ /^\d+-group-/ } @bug_groups;
+ my $group = $bug_groups[0];
+ $value =~ s/<$number-bug_group>/$group/g;
+ }
+
+ my @bug_values = $self->bug_values($number);
+ return $value if !@bug_values;
+
+ if ($self->operator =~ /substr/) {
+ @bug_values = map { $self->_substr_value($_) } @bug_values;
+ }
+
+ my $string_value = $bug_values[0];
+ if ($self->operator =~ /word/) {
+ $string_value = join(' ', @bug_values);
+ }
+ if (my $func = $self->test->{transform}) {
+ my $transformed = $func->(@bug_values);
+ my $is_equal = $transformed eq $bug_values[0] ? 1 : 0;
+ $self->transformed_value_was_equal($number, $is_equal);
+ $string_value = $transformed;
+ }
+
+ if ($self->test->{escape}) {
+ $string_value = quotemeta($string_value);
+ }
+ $value =~ s/<$number>/$string_value/g;
+
+ return $value;
+}
+
+sub _substr_value {
+ my ($self, $value) = @_;
+ my $field = $self->field;
+ my $type = $self->field_object->type;
+ my $substr_size = SUBSTR_SIZE;
+ if (exists FIELD_SUBSTR_SIZE->{$field}) {
+ $substr_size = FIELD_SUBSTR_SIZE->{$field};
+ }
+ elsif (exists FIELD_SUBSTR_SIZE->{$type}) {
+ $substr_size = FIELD_SUBSTR_SIZE->{$type};
+ }
+ if ($substr_size > 0) {
+ # The field name is included in every field value, and if it's
+ # long, it might take up the whole substring, and we don't want that.
+ if (!grep { $_ eq $field or $_ eq $type } SUBSTR_NO_FIELD_ADD) {
+ $substr_size += length($field);
+ }
+ my $string = substr($value, 0, $substr_size);
+ return $string;
+ }
+ return substr($value, $substr_size);
+}
+
+#####################
+# Main Test Methods #
+#####################
+
+sub run {
+ my ($self) = @_;
+
+ my $invalid_combination = $self->invalid_field_operator_combination;
+ my $field_not_implemented = $self->field_not_yet_implemented;
+
+ SKIP: {
+ skip($invalid_combination, $self->num_tests) if $invalid_combination;
+ TODO: {
+ todo_skip ($field_not_implemented, $self->num_tests) if $field_not_implemented;
+ $self->do_tests();
+ }
+ }
+}
+
+sub do_tests {
+ my ($self) = @_;
+ my $name = $self->name;
+
+ my $search_broken = $self->search_known_broken;
+
+ my $search = $self->_test_search_object_creation();
+
+ my $sql;
+ TODO: {
+ local $TODO = $search_broken if $search_broken;
+ lives_ok { $sql = $search->sql } "$name: generate SQL";
+ }
+
+ my $results;
+ SKIP: {
+ skip "Can't run SQL without any SQL", 1 if !defined $sql;
+ $results = $self->_test_sql($sql);
+ }
+
+ $self->_test_content($results, $sql);
+}
+
+sub _test_search_object_creation {
+ my ($self) = @_;
+ my $name = $self->name;
+ my @args = (fields => $self->search_columns, params => $self->search_params);
+ my $search;
+ lives_ok { $search = new Bugzilla::Search(@args) }
+ "$name: create search object";
+ return $search;
+}
+
+sub _test_sql {
+ my ($self, $sql) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $name = $self->name;
+ my $results;
+ lives_ok { $results = $dbh->selectall_arrayref($sql) } "$name: Run SQL Query"
+ or diag($sql);
+ return $results;
+}
+
+sub _test_content {
+ my ($self, $results, $sql) = @_;
+
+ SKIP: {
+ skip "Without results we can't test them", NUM_BUGS if !$results;
+ foreach my $number (1..NUM_BUGS) {
+ $self->_test_content_for_bug($number, $results, $sql);
+ }
+ }
+}
+
+sub _test_content_for_bug {
+ my ($self, $number, $results, $sql) = @_;
+ my $name = $self->name;
+
+ my $contains_known_broken = $self->contains_known_broken($number);
+
+ my %result_ids = map { $_->[0] => 1 } @$results;
+ my $bug_id = $self->bug($number)->id;
+
+ TODO: {
+ local $TODO = $contains_known_broken if $contains_known_broken;
+ if ($self->bug_is_contained($number)) {
+ ok($result_ids{$bug_id},
+ "$name: contains bug $number ($bug_id)")
+ or diag Dumper($results) . $self->debug_value . "\n\nSQL: $sql";
+ }
+ else {
+ ok(!$result_ids{$bug_id},
+ "$name: does not contain bug $number ($bug_id)")
+ or diag Dumper($results) . $self->debug_value . "\n\nSQL: $sql";
+ }
+ }
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm
new file mode 100644
index 0000000..1e0a75f
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm
@@ -0,0 +1,118 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This is the same as a FieldTest, except that it uses normal URL
+# parameters instead of Boolean Charts.
+package Bugzilla::Test::Search::FieldTestNormal;
+use strict;
+use warnings;
+use base qw(Bugzilla::Test::Search::FieldTest);
+
+use Scalar::Util qw(blessed);
+
+use constant CH_OPERATOR => {
+ changedafter => 'chfieldfrom',
+ changedbefore => 'chfieldto',
+ changedto => 'chfieldvalue',
+};
+
+use constant EMAIL_FIELDS => qw(assigned_to qa_contact cc reporter commenter);
+
+# Normally, we just clone a FieldTest because that's the best for performance,
+# overall--that way we don't have to translate the value again. However,
+# sometimes (like in Bugzilla::Test::Search's direct code) we just want
+# to create a FieldTestNormal.
+sub new {
+ my $class = shift;
+ my ($first_arg) = @_;
+ if (blessed $first_arg
+ and $first_arg->isa('Bugzilla::Test::Search::FieldTest'))
+ {
+ my $self = { %$first_arg };
+ return bless $self, $class;
+ }
+ return $class->SUPER::new(@_);
+}
+
+sub name {
+ my $self = shift;
+ my $name = $self->SUPER::name(@_);
+ return "$name (Normal Params)";
+}
+
+sub search_columns {
+ my $self = shift;
+ my $field = $self->field;
+ # For the assigned_to, qa_contact, and reporter fields, have the
+ # "Normal Params" test check that the _realname columns work
+ # all by themselves.
+ if (grep($_ eq $field, EMAIL_FIELDS) && $self->field_object->buglist) {
+ return ['bug_id', "${field}_realname"]
+ }
+ return $self->SUPER::search_columns(@_);
+}
+
+sub search_params {
+ my ($self) = @_;
+ my $field = $self->field;
+ my $operator = $self->operator;
+ my $value = $self->translated_value;
+ if ($operator eq 'anyexact') {
+ $value = [split ',', $value];
+ }
+
+ if (my $ch_param = CH_OPERATOR->{$operator}) {
+ if ($field eq 'creation_ts') {
+ $field = '[Bug creation]';
+ }
+ return { chfield => $field, $ch_param => $value };
+ }
+
+ if ($field eq 'delta_ts' and $operator eq 'greaterthaneq') {
+ return { chfieldfrom => $value };
+ }
+ if ($field eq 'delta_ts' and $operator eq 'lessthaneq') {
+ return { chfieldto => $value };
+ }
+
+ if ($field eq 'deadline' and $operator eq 'greaterthaneq') {
+ return { deadlinefrom => $value };
+ }
+ if ($field eq 'deadline' and $operator eq 'lessthaneq') {
+ return { deadlineto => $value };
+ }
+
+ if (grep { $_ eq $field } EMAIL_FIELDS) {
+ $field = 'longdesc' if $field eq 'commenter';
+ return {
+ email1 => $value,
+ "email${field}1" => 1,
+ emailtype1 => $operator,
+ # Used to do extra tests on special sorts of email* combinations.
+ %{ $self->test->{extra_params} || {} },
+ };
+ }
+
+ $field =~ s/\./_/g;
+ return { $field => $value, "${field}_type" => $operator };
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/InjectionTest.pm b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/InjectionTest.pm
new file mode 100644
index 0000000..41f5fcd
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/InjectionTest.pm
@@ -0,0 +1,91 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This module represents the SQL Injection tests that get run on a single
+# operator/field combination for Bugzilla::Test::Search.
+package Bugzilla::Test::Search::InjectionTest;
+use base qw(Bugzilla::Test::Search::FieldTest);
+
+use strict;
+use warnings;
+use Bugzilla::Test::Search::Constants;
+use Test::Exception;
+
+sub num_tests { return NUM_SEARCH_TESTS }
+
+sub _known_broken {
+ my ($self) = @_;
+ my $operator_broken = INJECTION_BROKEN_OPERATOR->{$self->operator};
+ # We don't want to auto-vivify $operator_broken and thus make it true.
+ my @field_ok = $operator_broken ? @{ $operator_broken->{field_ok} || [] }
+ : ();
+ $operator_broken = undef if grep { $_ eq $self->field } @field_ok;
+
+ my $field_broken = INJECTION_BROKEN_FIELD->{$self->field}
+ || INJECTION_BROKEN_FIELD->{$self->field_object->type};
+ # We don't want to auto-vivify $field_broken and thus make it true.
+ my @operator_ok = $field_broken ? @{ $field_broken->{operator_ok} || [] }
+ : ();
+ $field_broken = undef if grep { $_ eq $self->operator } @operator_ok;
+
+ return $operator_broken || $field_broken || {};
+}
+
+sub sql_error_ok { return $_[0]->_known_broken->{sql_error} }
+
+# Injection tests only skip fields on certain dbs.
+sub field_not_yet_implemented {
+ my ($self) = @_;
+ # We use the constant directly because we don't want operator_ok
+ # or field_ok to stop us.
+ my $broken = INJECTION_BROKEN_FIELD->{$self->field}
+ || INJECTION_BROKEN_FIELD->{$self->field_object->type};
+ my $skip_for_dbs = $broken->{db_skip};
+ return undef if !$skip_for_dbs;
+ my $dbh = Bugzilla->dbh;
+ if (my ($skip) = grep { $dbh->isa("Bugzilla::DB::$_") } @$skip_for_dbs) {
+ my $field = $self->field;
+ return "$field injection testing is not supported with $skip";
+ }
+ return undef;
+}
+# Injection tests don't do translation.
+sub translated_value { $_[0]->test_value }
+
+sub name { return "injection-" . $_[0]->SUPER::name; }
+
+# Injection tests don't check content.
+sub _test_content {}
+
+sub _test_sql {
+ my $self = shift;
+ my ($sql) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $name = $self->name;
+ if (my $error_ok = $self->sql_error_ok) {
+ throws_ok { $dbh->selectall_arrayref($sql) } $error_ok,
+ "$name: SQL query dies, as we expect";
+ return;
+ }
+ return $self->SUPER::_test_sql(@_);
+}
+
+1;
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/NotTest.pm b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/NotTest.pm
new file mode 100644
index 0000000..86da4c6
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/NotTest.pm
@@ -0,0 +1,75 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This module runs tests just like a normal FieldTest, AndTest,
+# or OrTest, but in a NOT chart instead of a normal chart.
+#
+# Logically this should be a mixin of some sort so that we can apply
+# it to OrTest and AndTest, but without Moose there isn't much of an
+# easy way to do that.
+package Bugzilla::Test::Search::NotTest;
+use base qw(Bugzilla::Test::Search::FieldTest);
+use strict;
+use warnings;
+use Bugzilla::Test::Search::Constants;
+
+# We just clone a FieldTest because that's the best for performance,
+# overall--that way we don't have to translate the value again.
+sub new {
+ my ($class, $field_test) = @_;
+ my $self = { %$field_test };
+ return bless $self, $class;
+}
+
+#############
+# Accessors #
+#############
+
+sub name {
+ my ($self) = @_;
+ return "NOT(" . $self->SUPER::name . ")";
+}
+
+# True if this test is supposed to contain the numbered bug. Reversed for
+# NOT tests.
+sub bug_is_contained {
+ my $self = shift;
+ my ($number) = @_;
+ # No search ever returns bug 6, because it's protected by security groups
+ # that the searcher isn't a member of.
+ return 0 if $number == 6;
+ return $self->SUPER::bug_is_contained(@_) ? 0 : 1;
+}
+
+# NOT tests have their own constant for tracking broken-ness.
+sub _known_broken {
+ my ($self) = @_;
+ return $self->SUPER::_known_broken(BROKEN_NOT, 'skip pg check');
+}
+
+sub search_params {
+ my ($self) = @_;
+ my %params = %{ $self->SUPER::search_params() };
+ $params{negate0} = 1;
+ return \%params;
+}
+
+1;
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/OperatorTest.pm b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/OperatorTest.pm
new file mode 100644
index 0000000..f088004
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/OperatorTest.pm
@@ -0,0 +1,117 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This module represents the tests that get run on a single operator
+# from the TESTS constant in Bugzilla::Search::Test::Constants.
+package Bugzilla::Test::Search::OperatorTest;
+
+use strict;
+use warnings;
+use Bugzilla::Test::Search::Constants;
+use Bugzilla::Test::Search::FieldTest;
+use Bugzilla::Test::Search::FieldTestNormal;
+use Bugzilla::Test::Search::InjectionTest;
+use Bugzilla::Test::Search::OrTest;
+use Bugzilla::Test::Search::AndTest;
+use Bugzilla::Test::Search::NotTest;
+
+###############
+# Constructor #
+###############
+
+sub new {
+ my ($invocant, $operator, $search_test) = @_;
+ $search_test ||= $invocant->search_test;
+ my $class = ref($invocant) || $invocant;
+ return bless { search_test => $search_test, operator => $operator }, $class;
+}
+
+#############
+# Accessors #
+#############
+
+# The Bugzilla::Test::Search object that this is a child of.
+sub search_test { return $_[0]->{search_test} }
+# The operator being tested
+sub operator { return $_[0]->{operator} }
+# The tests that we're going to run on this operator.
+sub tests { return @{ TESTS->{$_[0]->operator } } }
+# The fields we're going to test for this operator.
+sub test_fields { return $_[0]->search_test->all_fields }
+
+sub run {
+ my ($self) = @_;
+
+ foreach my $field ($self->test_fields) {
+ foreach my $test ($self->tests) {
+ my $field_test =
+ new Bugzilla::Test::Search::FieldTest($self, $field, $test);
+ $field_test->run();
+ my $normal_test =
+ new Bugzilla::Test::Search::FieldTestNormal($field_test);
+ $normal_test->run();
+ my $not_test = new Bugzilla::Test::Search::NotTest($field_test);
+ $not_test->run();
+
+ next if !$self->search_test->option('long');
+
+ # Run the OR tests. This tests every other operator (including
+ # this operator itself) in combination with every other field,
+ # in an OR with this operator and field.
+ foreach my $other_operator ($self->search_test->all_operators) {
+ $self->run_join_tests($field_test, $other_operator);
+ }
+ }
+ foreach my $test (INJECTION_TESTS) {
+ my $injection_test =
+ new Bugzilla::Test::Search::InjectionTest($self, $field, $test);
+ $injection_test->run();
+ }
+ }
+}
+
+sub run_join_tests {
+ my ($self, $field_test, $other_operator) = @_;
+
+ my $other_operator_test = $self->new($other_operator);
+ foreach my $other_test ($other_operator_test->tests) {
+ foreach my $other_field ($self->test_fields) {
+ $self->_run_one_join_test($field_test, $other_operator_test,
+ $other_field, $other_test);
+ $self->search_test->clean_test_history();
+ }
+ }
+}
+
+sub _run_one_join_test {
+ my ($self, $field_test, $other_operator_test, $other_field, $other_test) = @_;
+ my $other_field_test =
+ new Bugzilla::Test::Search::FieldTest($other_operator_test,
+ $other_field, $other_test);
+ my $or_test = new Bugzilla::Test::Search::OrTest($field_test,
+ $other_field_test);
+ $or_test->run();
+ my $and_test = new Bugzilla::Test::Search::AndTest($field_test,
+ $other_field_test);
+ $and_test->run();
+}
+
+1;
\ No newline at end of file
diff --git a/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/OrTest.pm b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/OrTest.pm
new file mode 100644
index 0000000..b1dbf64
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/OrTest.pm
@@ -0,0 +1,155 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This test combines two field/operator combinations using OR in
+# a single boolean chart.
+package Bugzilla::Test::Search::OrTest;
+use base qw(Bugzilla::Test::Search::FieldTest);
+
+use Bugzilla::Test::Search::Constants;
+use List::MoreUtils qw(all any uniq);
+
+use constant type => 'OR';
+
+###############
+# Constructor #
+###############
+
+sub new {
+ my $class = shift;
+ my $self = { field_tests => [@_] };
+ return bless $self, $class;
+}
+
+#############
+# Accessors #
+#############
+
+sub field_tests { return @{ $_[0]->{field_tests} } }
+sub search_test { ($_[0]->field_tests)[0]->search_test }
+
+sub name {
+ my ($self) = @_;
+ my @names = map { $_->name } $self->field_tests;
+ return join('-' . $self->type . '-', @names);
+}
+
+# In an OR test, bugs ARE supposed to be contained if they are contained
+# by ANY test.
+sub bug_is_contained {
+ my ($self, $number) = @_;
+ return any { $_->bug_is_contained($number) } $self->field_tests;
+}
+
+# Needed only for failure messages
+sub debug_value {
+ my ($self) = @_;
+ my @values = map { $_->field . ' ' . $_->debug_value } $self->field_tests;
+ return join(' ' . $self->type . ' ', @values);
+}
+
+########################
+# SKIP & TODO Messages #
+########################
+
+sub field_not_yet_implemented {
+ my ($self) = @_;
+ return $self->_join_messages('field_not_yet_implemented');
+}
+sub invalid_field_operator_combination {
+ my ($self) = @_;
+ return $self->_join_messages('invalid_field_operator_combination');
+}
+sub search_known_broken {
+ my ($self) = @_;
+ return $self->_join_messages('search_known_broken');
+}
+
+sub _join_messages {
+ my ($self, $message_method) = @_;
+ my @messages = map { $_->$message_method } $self->field_tests;
+ @messages = grep { $_ } @messages;
+ return join(' AND ', @messages);
+}
+
+sub _bug_will_actually_be_contained {
+ my ($self, $number) = @_;
+
+ foreach my $test ($self->field_tests) {
+ # Some tests are broken in such a way that they actually
+ # generate no criteria in the SQL. In this case, the only way
+ # the test contains the bug is if *another* test contains it.
+ next if $test->_known_broken->{no_criteria};
+ return 1 if $test->will_actually_contain_bug($number);
+ }
+ return 0;
+}
+
+sub contains_known_broken {
+ my ($self, $number) = @_;
+
+ if ( ( $self->bug_is_contained($number)
+ and !$self->_bug_will_actually_be_contained($number) )
+ or ( !$self->bug_is_contained($number)
+ and $self->_bug_will_actually_be_contained($number) ) )
+ {
+ my @messages = map { $_->contains_known_broken($number) }
+ $self->field_tests;
+ @messages = grep { $_ } @messages;
+ # Sometimes, with things that break because of no_criteria, there won't
+ # be anything in @messages even though we need to print out a message.
+ if (!@messages) {
+ my @no_criteria = grep { $_->_known_broken->{no_criteria} }
+ $self->field_tests;
+ @messages = map { "No criteria generated by " . $_->name }
+ @no_criteria;
+ }
+ die "broken test with no message" if !@messages;
+ return join(' AND ', @messages);
+ }
+ return undef;
+}
+
+##############################
+# Bugzilla::Search arguments #
+##############################
+
+sub search_columns {
+ my ($self) = @_;
+ my @columns = map { @{ $_->search_columns } } $self->field_tests;
+ return [uniq @columns];
+}
+
+sub search_params {
+ my ($self) = @_;
+ my @all_params = map { $_->search_params } $self->field_tests;
+ my %params;
+ my $chart = 0;
+ foreach my $item (@all_params) {
+ $params{"field0-0-$chart"} = $item->{'field0-0-0'};
+ $params{"type0-0-$chart"} = $item->{'type0-0-0'};
+ $params{"value0-0-$chart"} = $item->{'value0-0-0'};
+ $chart++;
+ }
+ return \%params;
+}
+
+1;
diff --git a/Websites/bugs.webkit.org/xt/search.t b/Websites/bugs.webkit.org/xt/search.t
new file mode 100644
index 0000000..be423b0
--- /dev/null
+++ b/Websites/bugs.webkit.org/xt/search.t
@@ -0,0 +1,96 @@
+#!/usr/bin/env perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# For a description of this test, see Bugzilla::Test::Search
+# in xt/lib/.
+
+use strict;
+use warnings;
+use lib qw(. xt/lib lib);
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Test::Search;
+use Getopt::Long;
+use Pod::Usage;
+
+use Test::More;
+
+my %switches;
+GetOptions(\%switches, 'operators=s', 'top-operators=s', 'long',
+ 'add-custom-fields', 'help|h') || die $@;
+
+pod2usage(verbose => 1) if $switches{'help'};
+
+plan skip_all => "BZ_WRITE_TESTS environment variable not set"
+ if !$ENV{BZ_WRITE_TESTS};
+
+Bugzilla->usage_mode(USAGE_MODE_TEST);
+
+my $test = new Bugzilla::Test::Search(\%switches);
+plan tests => $test->num_tests;
+$test->run();
+
+__END__
+
+=head1 NAME
+
+search.t - Test L<Bugzilla::Search>
+
+=head1 DESCRIPTION
+
+This test tests L<Bugzilla::Search>.
+
+Note that users may be prevented from writing new bugs, products, components,
+etc. to your database while this test is running.
+
+=head1 OPTIONS
+
+=over
+
+=item --long
+
+Run AND and OR tests in addition to normal tests. Specifying
+--long without also specifying L</--top-operators> is likely to
+run your system out of memory.
+
+=item --add-custom-fields
+
+This adds every type of custom field to the database, so that they can
+all be tested. Note that this B<CANNOT BE REVERSED>, so do not use this
+switch on a production installation.
+
+=item --operators=a,b,c
+
+Limit the test to testing only the listed operators.
+
+=item --top-operators=a,b,c
+
+Limit the top-level tested operators to the following list. This
+means that for normal tests, only the listed operators will be tested.
+However, for OR and AND tests, all other operators will be tested
+along with the operators you listed.
+
+=item --help
+
+Display this help.
+
+=back