Merged BugsSite to Bugzilla-3.2.3

        Updated to the latest-and-greatest stable version.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@45520 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/BugsSite/.cvsignore b/BugsSite/.cvsignore
index 1d378db..cba381b 100644
--- a/BugsSite/.cvsignore
+++ b/BugsSite/.cvsignore
@@ -4,4 +4,3 @@
 localconfig
 index.html
 old-params.txt
-extensions
diff --git a/BugsSite/Bugzilla.pm b/BugsSite/Bugzilla.pm
index 7be74b1..d54f974 100644
--- a/BugsSite/Bugzilla.pm
+++ b/BugsSite/Bugzilla.pm
@@ -26,6 +26,15 @@
 
 use strict;
 
+# We want any compile errors to get to the browser, if possible.
+BEGIN {
+    # This makes sure we're in a CGI.
+    if ($ENV{SERVER_SOFTWARE} && !$ENV{MOD_PERL}) {
+        require CGI::Carp;
+        CGI::Carp->import('fatalsToBrowser');
+    }
+}
+
 use Bugzilla::Config;
 use Bugzilla::Constants;
 use Bugzilla::Auth;
@@ -38,8 +47,10 @@
 use Bugzilla::Error;
 use Bugzilla::Util;
 use Bugzilla::Field;
+use Bugzilla::Flag;
 
 use File::Basename;
+use File::Spec::Functions;
 use Safe;
 
 # This creates the request cache for non-mod_perl installations.
@@ -65,20 +76,11 @@
 # Global Code
 #####################################################################
 
-# The following subroutine is for debugging purposes only.
-# Uncommenting this sub and the $::SIG{__DIE__} trap underneath it will
-# cause any fatal errors to result in a call stack trace to help track
-# down weird errors.
-#
-#sub die_with_dignity {
-#    use Carp ();
-#    my ($err_msg) = @_;
-#    print $err_msg;
-#    Carp::confess($err_msg);
-#}
-#$::SIG{__DIE__} = \&Bugzilla::die_with_dignity;
+# $::SIG{__DIE__} = i_am_cgi() ? \&CGI::Carp::confess : \&Carp::confess;
 
+# Note that this is a raw subroutine, not a method, so $class isn't available.
 sub init_page {
+    (binmode STDOUT, ':utf8') if Bugzilla->params->{'utf8'};
 
     # Some environment variables are not taint safe
     delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
@@ -125,7 +127,13 @@
 
         # For security reasons, log out users when Bugzilla is down.
         # Bugzilla->login() is required to catch the logincookie, if any.
-        my $user = Bugzilla->login(LOGIN_OPTIONAL);
+        my $user;
+        eval { $user = Bugzilla->login(LOGIN_OPTIONAL); };
+        if ($@) {
+            # The DB is not accessible. Use the default user object.
+            $user = Bugzilla->user;
+            $user->{settings} = {};
+        }
         my $userid = $user->id;
         Bugzilla->logout();
 
@@ -160,40 +168,42 @@
 
 sub template {
     my $class = shift;
-    request_cache()->{language} = "";
-    request_cache()->{template} ||= Bugzilla::Template->create();
-    return request_cache()->{template};
+    $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 : (request_cache()->{language} || "");
-    request_cache()->{language} = $lang;
-    request_cache()->{"template_inner_$lang"} ||= Bugzilla::Template->create();
-    return request_cache()->{"template_inner_$lang"};
+    $lang = defined($lang) ? $lang : ($class->request_cache->{language} || "");
+    $class->request_cache->{language} = $lang;
+    $class->request_cache->{"template_inner_$lang"}
+        ||= Bugzilla::Template->create();
+    return $class->request_cache->{"template_inner_$lang"};
 }
 
 sub cgi {
     my $class = shift;
-    request_cache()->{cgi} ||= new Bugzilla::CGI();
-    return request_cache()->{cgi};
+    $class->request_cache->{cgi} ||= new Bugzilla::CGI();
+    return $class->request_cache->{cgi};
 }
 
 sub localconfig {
-    request_cache()->{localconfig} ||= read_localconfig();
-    return request_cache()->{localconfig};
+    my $class = shift;
+    $class->request_cache->{localconfig} ||= read_localconfig();
+    return $class->request_cache->{localconfig};
 }
 
 sub params {
     my $class = shift;
-    request_cache()->{params} ||= Bugzilla::Config::read_param_file();
-    return request_cache()->{params};
+    $class->request_cache->{params} ||= Bugzilla::Config::read_param_file();
+    return $class->request_cache->{params};
 }
 
 sub user {
     my $class = shift;
-    request_cache()->{user} ||= new Bugzilla::User;
-    return request_cache()->{user};
+    $class->request_cache->{user} ||= new Bugzilla::User;
+    return $class->request_cache->{user};
 }
 
 sub set_user {
@@ -203,31 +213,25 @@
 
 sub sudoer {
     my $class = shift;    
-    return request_cache()->{sudoer};
+    return $class->request_cache->{sudoer};
 }
 
 sub sudo_request {
-    my $class = shift;
-    my $new_user = shift;
-    my $new_sudoer = shift;
-
-    request_cache()->{user}   = $new_user;
-    request_cache()->{sudoer} = $new_sudoer;
-
+    my ($class, $new_user, $new_sudoer) = @_;
+    $class->request_cache->{user}   = $new_user;
+    $class->request_cache->{sudoer} = $new_sudoer;
     # NOTE: If you want to log the start of an sudo session, do it here.
-
-    return;
 }
 
 sub login {
     my ($class, $type) = @_;
 
-    return Bugzilla->user if Bugzilla->user->id;
+    return $class->user if $class->user->id;
 
     my $authorizer = new Bugzilla::Auth();
-    $type = LOGIN_REQUIRED if Bugzilla->cgi->param('GoAheadAndLogIn');
+    $type = LOGIN_REQUIRED if $class->cgi->param('GoAheadAndLogIn');
     if (!defined $type || $type == LOGIN_NORMAL) {
-        $type = Bugzilla->params->{'requirelogin'} ? LOGIN_REQUIRED : LOGIN_NORMAL;
+        $type = $class->params->{'requirelogin'} ? LOGIN_REQUIRED : LOGIN_NORMAL;
     }
     my $authenticated_user = $authorizer->login($type);
     
@@ -250,8 +254,8 @@
         !($sudo_target->in_group('bz_sudo_protect'))
        )
     {
-        Bugzilla->set_user($sudo_target);
-        request_cache()->{sudoer} = $authenticated_user;
+        $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);
@@ -259,21 +263,29 @@
         # NOTE: If you want to do any special logging, do it here.
     }
     else {
-        Bugzilla->set_user($authenticated_user);
+        $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 Bugzilla->user;
+    return $class->user;
 }
 
 sub logout {
     my ($class, $option) = @_;
 
     # If we're not logged in, go away
-    return unless user->id;
+    return unless $class->user->id;
 
     $option = LOGOUT_CURRENT unless defined $option;
     Bugzilla::Auth::Persist::Cookie->logout({type => $option});
-    Bugzilla->logout_request() unless $option eq LOGOUT_KEEP_CURRENT;
+    $class->logout_request() unless $option eq LOGOUT_KEEP_CURRENT;
 }
 
 sub logout_user {
@@ -292,35 +304,53 @@
 
 # hack that invalidates credentials for a single request
 sub logout_request {
-    delete request_cache()->{user};
-    delete request_cache()->{sudoer};
+    my $class = shift;
+    delete $class->request_cache->{user};
+    delete $class->request_cache->{sudoer};
     # We can't delete from $cgi->cookie, so logincookie data will remain
     # there. Don't rely on it: use Bugzilla->user->login instead!
 }
 
 sub dbh {
     my $class = shift;
-
     # If we're not connected, then we must want the main db
-    request_cache()->{dbh} ||= request_cache()->{dbh_main} 
+    $class->request_cache->{dbh} ||= $class->request_cache->{dbh_main} 
         = Bugzilla::DB::connect_main();
 
-    return request_cache()->{dbh};
+    return $class->request_cache->{dbh};
+}
+
+sub languages {
+    my $class = shift;
+    return $class->request_cache->{languages}
+        if $class->request_cache->{languages};
+
+    my @files = glob(catdir(bz_locations->{'templatedir'}, '*'));
+    my @languages;
+    foreach my $dir_entry (@files) {
+        # It's a language directory only if it contains "default" or
+        # "custom". This auto-excludes CVS directories as well.
+        next unless (-d catdir($dir_entry, 'default')
+                  || -d catdir($dir_entry, 'custom'));
+        $dir_entry = basename($dir_entry);
+        # Check for language tag format conforming to RFC 1766.
+        next unless $dir_entry =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/;
+        push(@languages, $dir_entry);
+    }
+    return $class->request_cache->{languages} = \@languages;
 }
 
 sub error_mode {
-    my $class = shift;
-    my $newval = shift;
+    my ($class, $newval) = @_;
     if (defined $newval) {
-        request_cache()->{error_mode} = $newval;
+        $class->request_cache->{error_mode} = $newval;
     }
-    return request_cache()->{error_mode}
+    return $class->request_cache->{error_mode}
         || Bugzilla::Constants::ERROR_MODE_WEBPAGE;
 }
 
 sub usage_mode {
-    my $class = shift;
-    my $newval = shift;
+    my ($class, $newval) = @_;
     if (defined $newval) {
         if ($newval == USAGE_MODE_BROWSER) {
             $class->error_mode(ERROR_MODE_WEBPAGE);
@@ -338,9 +368,9 @@
             ThrowCodeError('usage_mode_invalid',
                            {'invalid_usage_mode', $newval});
         }
-        request_cache()->{usage_mode} = $newval;
+        $class->request_cache->{usage_mode} = $newval;
     }
-    return request_cache()->{usage_mode}
+    return $class->request_cache->{usage_mode}
         || Bugzilla::Constants::USAGE_MODE_BROWSER;
 }
 
@@ -369,15 +399,15 @@
 sub switch_to_shadow_db {
     my $class = shift;
 
-    if (!request_cache()->{dbh_shadow}) {
-        if (Bugzilla->params->{'shadowdb'}) {
-            request_cache()->{dbh_shadow} = Bugzilla::DB::connect_shadow();
+    if (!$class->request_cache->{dbh_shadow}) {
+        if ($class->params->{'shadowdb'}) {
+            $class->request_cache->{dbh_shadow} = Bugzilla::DB::connect_shadow();
         } else {
-            request_cache()->{dbh_shadow} = request_cache()->{dbh_main};
+            $class->request_cache->{dbh_shadow} = request_cache()->{dbh_main};
         }
     }
 
-    request_cache()->{dbh} = request_cache()->{dbh_shadow};
+    $class->request_cache->{dbh} = $class->request_cache->{dbh_shadow};
     # we have to return $class->dbh instead of {dbh} as
     # {dbh_shadow} may be undefined if no shadow DB is used
     # and no connection to the main DB has been established yet.
@@ -387,7 +417,7 @@
 sub switch_to_main_db {
     my $class = shift;
 
-    request_cache()->{dbh} = request_cache()->{dbh_main};
+    $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.
@@ -404,10 +434,22 @@
     return @$fields;
 }
 
-sub custom_field_names {
-    # Get a list of custom fields and convert it into a list of their names.
-    return map($_->{name}, 
-               @{Bugzilla::Field->match({ custom=>1, obsolete=>0 })});
+sub active_custom_fields {
+    my $class = shift;
+    if (!exists $class->request_cache->{active_custom_fields}) {
+        $class->request_cache->{active_custom_fields} =
+          Bugzilla::Field->match({ custom => 1, obsolete => 0 });
+    }
+    return @{$class->request_cache->{active_custom_fields}};
+}
+
+sub has_flags {
+    my $class = shift;
+
+    if (!defined $class->request_cache->{has_flags}) {
+        $class->request_cache->{has_flags} = Bugzilla::Flag::has_flags();
+    }
+    return $class->request_cache->{has_flags};
 }
 
 sub hook_args {
@@ -426,14 +468,16 @@
 
 # Private methods
 
-# Per process cleanup
+# Per-process cleanup. Note that this is a plain subroutine, not a method,
+# so we don't have $class available.
 sub _cleanup {
-
-    # When we support transactions, need to ->rollback here
-    my $main   = request_cache()->{dbh_main};
-    my $shadow = request_cache()->{dbh_shadow};
-    $main->disconnect if $main;
-    $shadow->disconnect if $shadow && Bugzilla->params->{"shadowdb"};
+    my $main   = Bugzilla->request_cache->{dbh_main};
+    my $shadow = Bugzilla->request_cache->{dbh_shadow};
+    foreach my $dbh ($main, $shadow) {
+        next if !$dbh;
+        $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction;
+        $dbh->disconnect;
+    }
     undef $_request_cache;
 }
 
@@ -626,6 +670,11 @@
 
 The current database handle. See L<DBI>.
 
+=item C<languages>
+
+Currently installed languages.
+Returns a reference to a list of RFC 1766 language tags of installed languages.
+
 =item C<switch_to_shadow_db>
 
 Switch from using the main database to using the shadow database.
diff --git a/BugsSite/Bugzilla/Attachment.pm b/BugsSite/Bugzilla/Attachment.pm
index a08c0d5..314227c 100644
--- a/BugsSite/Bugzilla/Attachment.pm
+++ b/BugsSite/Bugzilla/Attachment.pm
@@ -88,9 +88,10 @@
         'attachments.bug_id AS bug_id',
         'attachments.description AS description',
         'attachments.mimetype AS contenttype',
-        'attachments.submitter_id AS _attacher_id',
+        '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',
@@ -98,13 +99,14 @@
         'attachments.isprivate AS isprivate'
     );
     my $columns = join(", ", @columns);
-
-    my $records = Bugzilla->dbh->selectall_arrayref("SELECT $columns
-                                                     FROM attachments
-                                                     WHERE attach_id IN (" .
-                                                     join(",", @$ids) . ")
-                                                     ORDER BY attach_id",
-                                                    { Slice => {} });
+    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;
 }
 
@@ -187,7 +189,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->{attacher_id});
     return $self->{attacher};
 }
 
@@ -208,6 +210,21 @@
 
 =over
 
+=item C<modification_time>
+
+the date and time on which the attachment was last modified.
+
+=back
+
+=cut
+
+sub modification_time {
+    my $self = shift;
+    return $self->{modification_time};
+}
+
+=over
+
 =item C<filename>
 
 the name of the file the attacher attached
@@ -283,6 +300,40 @@
 
 =over
 
+=item C<is_viewable>
+
+Returns 1 if the attachment has a content-type viewable in this browser.
+Note that we don't use $cgi->Accept()'s ability to check if a content-type
+matches, because this will return a value even if it's matched by the generic
+*/* which most browsers add to the end of their Accept: headers.
+
+=back
+
+=cut
+
+sub is_viewable {
+    my $self = shift;
+    my $contenttype = $self->contenttype;
+    my $cgi = Bugzilla->cgi;
+
+    # We assume we can view all text and image types.
+    return 1 if ($contenttype =~ /^(text|image)\//);
+
+    # Mozilla can view XUL. Note the trailing slash on the Gecko detection to
+    # avoid sending XUL to Safari.
+    return 1 if (($contenttype =~ /^application\/vnd\.mozilla\./)
+                 && ($cgi->user_agent() =~ /Gecko\//));
+
+    # If it's not one of the above types, we check the Accept: header for any
+    # types mentioned explicitly.
+    my $accept = join(",", $cgi->Accept());
+    return 1 if ($accept =~ /^(.*,)?\Q$contenttype\E(,.*)?$/);
+
+    return 0;
+}
+
+=over
+
 =item C<data>
 
 the content of the attachment
@@ -375,7 +426,7 @@
     my $self = shift;
     return $self->{flags} if exists $self->{flags};
 
-    $self->{flags} = Bugzilla::Flag::match({ 'attach_id' => $self->id });
+    $self->{flags} = Bugzilla::Flag->match({ 'attach_id' => $self->id });
     return $self->{flags};
 }
 
@@ -450,7 +501,7 @@
         my $imgdata = $img->ImageToBlob();
         $data = $imgdata;
         $cgi->param('contenttype', 'image/png');
-        $$hr_vars->{'convertedbmp'} = 1;
+        $hr_vars->{'convertedbmp'} = 1;
     }
 
     # Make sure the attachment does not exceed the maximum permitted size
@@ -625,19 +676,12 @@
 
 sub validate_can_edit {
     my ($attachment, $product_id) = @_;
-    my $dbh = Bugzilla->dbh;
     my $user = Bugzilla->user;
 
-    # Bug 97729 - the submitter can edit their attachments.
-    return if ($attachment->attacher->id == $user->id);
-
-    # Only users in the insider group can view private attachments.
-    if ($attachment->isprivate && !$user->is_insider) {
-        ThrowUserError('illegal_attachment_edit', {attach_id => $attachment->id});
-    }
-
-    # Users with editbugs privs can edit all attachments.
-    return if $user->in_group('editbugs', $product_id);
+    # 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 });
@@ -714,8 +758,6 @@
 
 Returns:    the ID of the new attachment.
 
-=back
-
 =cut
 
 sub insert_attachment_for_bug {
@@ -728,8 +770,8 @@
     my $filename;
     my $contenttype;
     my $isurl;
-    $class->validate_is_patch($throw_error) || return 0;
-    $class->validate_description($throw_error) || return 0;
+    $class->validate_is_patch($throw_error) || return;
+    $class->validate_description($throw_error) || return;
 
     if (Bugzilla->params->{'allow_attach_url'}
         && ($attachurl =~ /^(http|https|ftp):\/\/\S+/)
@@ -743,11 +785,11 @@
         $cgi->delete('bigfile');
     }
     else {
-        $filename = _validate_filename($throw_error) || return 0;
+        $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 0;
+            $class->validate_content_type($throw_error) || return;
 
             # Set the ispatch flag to 1 if we're set to autodetect
             # and the content type is text/x-diff or text/x-patch
@@ -761,7 +803,7 @@
         $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 0;
+        ($data ne '0') || return;
         $contenttype = $cgi->param('contenttype');
 
         # These are inserted using placeholders so no need to panic
@@ -783,12 +825,12 @@
         '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
     }, MATCH_SKIP_CONFIRM);
 
-    $$hr_vars->{'match_field'} = 'requestee';
+    $hr_vars->{'match_field'} = 'requestee';
     if ($match_status == USER_MATCH_FAILED) {
-        $$hr_vars->{'message'} = 'user_match_failed';
+        $hr_vars->{'message'} = 'user_match_failed';
     }
     elsif ($match_status == USER_MATCH_MULTIPLE) {
-        $$hr_vars->{'message'} = 'user_match_multiple';
+        $hr_vars->{'message'} = 'user_match_multiple';
     }
 
     # Escape characters in strings that will be used in SQL statements.
@@ -799,10 +841,10 @@
     # Insert the attachment into the database.
     my $sth = $dbh->do(
         "INSERT INTO attachments
-            (bug_id, creation_ts, filename, description,
+            (bug_id, creation_ts, modification_time, filename, description,
              mimetype, ispatch, isurl, isprivate, submitter_id)
-         VALUES (?,?,?,?,?,?,?,?,?)", undef, ($bug->bug_id, $timestamp, $filename,
-              $description, $contenttype, $cgi->param('ispatch'),
+         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');
@@ -815,7 +857,7 @@
     $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
     $sth->execute();
 
-    # If the file is to be stored locally, stream the file from the webserver
+    # If the file is to be stored locally, stream the file from the web server
     # to the local file without reading it into a local variable.
     if ($cgi->param('bigfile')) {
         my $attachdir = bz_locations()->{'attachdir'};
@@ -835,7 +877,7 @@
                 close AH;
                 close $fh;
                 unlink "$attachdir/$hash/attachment.$attachid";
-                $throw_error ? ThrowUserError("local_file_too_large") : return 0;
+                $throw_error ? ThrowUserError("local_file_too_large") : return;
             }
         }
         close AH;
@@ -848,10 +890,11 @@
     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);
+        Bugzilla::Flag->CancelRequests($bug, $obsolete_attachment, $timestamp);
 
-        $dbh->do('UPDATE attachments SET isobsolete = 1 WHERE attach_id = ?',
-                 undef, $obsolete_attachment->id);
+        $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)
@@ -874,17 +917,43 @@
     my $error_mode_cache = Bugzilla->error_mode;
     Bugzilla->error_mode(ERROR_MODE_DIE);
     eval {
-        Bugzilla::Flag::validate($cgi, $bug->bug_id, -1, SKIP_REQUESTEE_ON_ERROR);
-        Bugzilla::Flag::process($bug, $attachment, $timestamp, $cgi);
+        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'} = $@;
+        $hr_vars->{'message'} = 'flag_creation_failed';
+        $hr_vars->{'flag_creation_error'} = $@;
     }
 
-    # Return the ID of the new attachment.
-    return $attachid;
+    # Return the new attachment object.
+    return $attachment;
+}
+
+=pod
+
+=item C<remove_from_db()>
+
+Description: removes an attachment from the DB.
+
+Params:     none
+
+Returns:    nothing
+
+=back
+
+=cut
+
+sub remove_from_db {
+    my $self = shift;
+    my $dbh = Bugzilla->dbh;
+
+    $dbh->bz_start_transaction();
+    $dbh->do('DELETE FROM flags WHERE attach_id = ?', undef, $self->id);
+    $dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
+    $dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isurl = ?, isobsolete = ?
+              WHERE attach_id = ?', undef, ('text/plain', 0, 0, 1, $self->id));
+    $dbh->bz_commit_transaction();
 }
 
 1;
diff --git a/BugsSite/Bugzilla/Attachment/PatchReader.pm b/BugsSite/Bugzilla/Attachment/PatchReader.pm
index 5dbffb5..cfc7610 100644
--- a/BugsSite/Bugzilla/Attachment/PatchReader.pm
+++ b/BugsSite/Bugzilla/Attachment/PatchReader.pm
@@ -21,7 +21,7 @@
 
 use Bugzilla::Error;
 use Bugzilla::Attachment;
-
+use Bugzilla::Util;
 
 sub process_diff {
     my ($attachment, $format, $context) = @_;
@@ -38,7 +38,7 @@
         # Actually print out the patch.
         print $cgi->header(-type => 'text/plain',
                            -expires => '+3M');
-
+        disable_utf8();
         $reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
     }
     else {
@@ -74,7 +74,12 @@
         $vars->{'other_patches'} = \@other_patches;
 
         setup_template_patch_reader($last_reader, $format, $context, $vars);
-        # Actually print out the patch.
+        # The patch is going to be displayed in a HTML page and if the utf8
+        # param is enabled, we have to encode attachment data as utf8.
+        if (Bugzilla->params->{'utf8'}) {
+            $attachment->data; # Populate ->{data}
+            utf8::decode($attachment->{data});
+        }
         $reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
     }
 }
@@ -85,10 +90,19 @@
     my $lc  = Bugzilla->localconfig;
     my $vars = {};
 
+    # Encode attachment data as utf8 if it's going to be displayed in a HTML
+    # page using the UTF-8 encoding.
+    if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
+        $old_attachment->data; # Populate ->{data}
+        utf8::decode($old_attachment->{data});
+        $new_attachment->data; # Populate ->{data}
+        utf8::decode($new_attachment->{data});
+    }
+
     # Get old patch data.
-    my ($old_filename, $old_file_list) = get_unified_diff($old_attachment);
+    my ($old_filename, $old_file_list) = get_unified_diff($old_attachment, $format);
     # Get new patch data.
-    my ($new_filename, $new_file_list) = get_unified_diff($new_attachment);
+    my ($new_filename, $new_file_list) = get_unified_diff($new_attachment, $format);
 
     my $warning = warn_if_interdiff_might_fail($old_file_list, $new_file_list);
 
@@ -105,8 +119,12 @@
         # Actually print out the patch.
         print $cgi->header(-type => 'text/plain',
                            -expires => '+3M');
+        disable_utf8();
     }
     else {
+        # In case the HTML page is displayed with the UTF-8 encoding.
+        binmode $interdiff_fh, ':utf8' if Bugzilla->params->{'utf8'};
+
         $vars->{'warning'} = $warning if $warning;
         $vars->{'bugid'} = $new_attachment->bug_id;
         $vars->{'oldid'} = $old_attachment->id;
@@ -131,7 +149,7 @@
 ######################
 
 sub get_unified_diff {
-    my $attachment = shift;
+    my ($attachment, $format) = @_;
 
     # Bring in the modules we need.
     require PatchReader::Raw;
@@ -162,6 +180,10 @@
 
     # Prints out to temporary file.
     my ($fh, $filename) = File::Temp::tempfile();
+    if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
+        # The HTML page will be displayed with the UTF-8 encoding.
+        binmode $fh, ':utf8';
+    }
     my $raw_printer = new PatchReader::DiffPrinter::raw($fh);
     $last_reader->sends_data_to($raw_printer);
     $last_reader = $raw_printer;
@@ -245,7 +267,7 @@
         $vars->{'headers'} = $cgi->param('headers');
     }
     else {
-        $vars->{'headers'} = 1 if !defined $cgi->param('headers');
+        $vars->{'headers'} = 1;
     }
 
     $vars->{'collapsed'} = $cgi->param('collapsed');
diff --git a/BugsSite/Bugzilla/Auth.pm b/BugsSite/Bugzilla/Auth.pm
index 74678af..8e18f86 100644
--- a/BugsSite/Bugzilla/Auth.pm
+++ b/BugsSite/Bugzilla/Auth.pm
@@ -151,23 +151,17 @@
         ThrowCodeError($result->{error}, $result->{details});
     }
     elsif ($fail_code == AUTH_NODATA) {
-        if ($login_type == LOGIN_REQUIRED) {
-            # This seems like as good as time as any to get rid of
-            # old crufty junk in the logincookies table.  Get rid
-            # of any entry that hasn't been used in a month.
-            $dbh->do("DELETE FROM logincookies WHERE " .
-                     $dbh->sql_to_days('NOW()') . " - " .
-                     $dbh->sql_to_days('lastused') . " > 30");
-            $self->{_info_getter}->fail_nodata($self);
-        }
-        # Otherwise, we just return the "default" user.
+        $self->{_info_getter}->fail_nodata($self) 
+            if $login_type == LOGIN_REQUIRED;
+
+        # If we're not LOGIN_REQUIRED, we just return the default user.
         $user = Bugzilla->user;
     }
     # The username/password may be wrong
     # Don't let the user know whether the username exists or whether
     # the password was just wrong. (This makes it harder for a cracker
     # to find account names by brute force)
-    elsif (($fail_code == AUTH_LOGINFAILED) || ($fail_code == AUTH_NO_SUCH_USER)) {
+    elsif ($fail_code == AUTH_LOGINFAILED or $fail_code == AUTH_NO_SUCH_USER) {
         ThrowUserError("invalid_username_or_password");
     }
     # The account may be disabled
diff --git a/BugsSite/Bugzilla/Auth/Login/CGI.pm b/BugsSite/Bugzilla/Auth/Login/CGI.pm
index 2a61a54..5be98aa 100644
--- a/BugsSite/Bugzilla/Auth/Login/CGI.pm
+++ b/BugsSite/Bugzilla/Auth/Login/CGI.pm
@@ -65,12 +65,17 @@
             ->faultstring('Login Required');
     }
 
-    # Redirect to SSL if required
-    if (Bugzilla->params->{'sslbase'} ne '' 
-        and Bugzilla->params->{'ssl'} ne 'never') 
+    # If system is not configured to never require SSL connections
+    # we want to always redirect to SSL since passing usernames and
+    # passwords over an unprotected connection is a bad idea. If we 
+    # get here then a login form will be provided to the user so we
+    # want this to be protected if possible.
+    if ($cgi->protocol ne 'https' && Bugzilla->params->{'sslbase'} ne ''
+        && Bugzilla->params->{'ssl'} ne 'never')
     {
         $cgi->require_https(Bugzilla->params->{'sslbase'});
     }
+
     print $cgi->header();
     $template->process("account/auth/login.html.tmpl",
                        { 'target' => $cgi->url(-relative=>1) }) 
diff --git a/BugsSite/Bugzilla/Auth/Persist/Cookie.pm b/BugsSite/Bugzilla/Auth/Persist/Cookie.pm
index 3faa892..420bad1 100644
--- a/BugsSite/Bugzilla/Auth/Persist/Cookie.pm
+++ b/BugsSite/Bugzilla/Auth/Persist/Cookie.pm
@@ -60,6 +60,8 @@
     # subsequent login
     trick_taint($ip_addr);
 
+    $dbh->bz_start_transaction();
+
     my $login_cookie = 
         Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
 
@@ -67,6 +69,16 @@
               VALUES (?, ?, ?, NOW())",
               undef, $login_cookie, $user->id, $ip_addr);
 
+    # Issuing a new cookie is a good time to clean up the old
+    # cookies.
+    $dbh->do("DELETE FROM logincookies WHERE lastused < LOCALTIMESTAMP(0) - "
+             . $dbh->sql_interval(MAX_LOGINCOOKIE_AGE, 'DAY'));
+
+    $dbh->bz_commit_transaction();
+
+    # Prevent JavaScript from accessing login cookies.
+    my %cookieargs = ('-httponly' => 1);
+
     # Remember cookie only if admin has told so
     # or admin didn't forbid it and user told to remember.
     if ( Bugzilla->params->{'rememberlogin'} eq 'on' ||
@@ -74,20 +86,23 @@
           $cgi->param('Bugzilla_remember') &&
           $cgi->param('Bugzilla_remember') eq 'on') ) 
     {
-        $cgi->send_cookie(-name => 'Bugzilla_login',
-                          -value => $user->id,
-                          -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
-        $cgi->send_cookie(-name => 'Bugzilla_logincookie',
-                          -value => $login_cookie,
-                          -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
+        # Not a session cookie, so set an infinite expiry
+        $cookieargs{'-expires'} = 'Fri, 01-Jan-2038 00:00:00 GMT';
+    }
+    if (Bugzilla->params->{'ssl'} ne 'never'
+        && Bugzilla->params->{'sslbase'} ne '')
+    {
+        # Bugzilla->login will automatically redirect to https://,
+        # so it's safe to turn on the 'secure' bit.
+        $cookieargs{'-secure'} = 1;
+    }
 
-    }
-    else {
-        $cgi->send_cookie(-name => 'Bugzilla_login',
-                          -value => $user->id);
-        $cgi->send_cookie(-name => 'Bugzilla_logincookie',
-                          -value => $login_cookie);
-    }
+    $cgi->send_cookie(-name => 'Bugzilla_login',
+                      -value => $user->id,
+                      %cookieargs);
+    $cgi->send_cookie(-name => 'Bugzilla_logincookie',
+                      -value => $login_cookie,
+                      %cookieargs);
 }
 
 sub logout {
diff --git a/BugsSite/Bugzilla/Auth/Verify.pm b/BugsSite/Bugzilla/Auth/Verify.pm
index deb5f4e..b293e25 100644
--- a/BugsSite/Bugzilla/Auth/Verify.pm
+++ b/BugsSite/Bugzilla/Auth/Verify.pm
@@ -116,16 +116,15 @@
         validate_email_syntax($username)
           || return { failure => AUTH_ERROR, error => 'auth_invalid_email',
                       details => {addr => $username} };
-        $dbh->do('UPDATE profiles SET login_name = ? WHERE userid = ?',
-                 undef, $username, $user->id);
+        $user->set_login($username);
     }
     if ($real_name && $user->name ne $real_name) {
         # $real_name is more than likely tainted, but we only use it
         # in a placeholder and we never use it after this.
         trick_taint($real_name);
-        $dbh->do('UPDATE profiles SET realname = ? WHERE userid = ?',
-                 undef, $real_name, $user->id);
+        $user->set_name($real_name);
     }
+    $user->update();
 
     return { user => $user };
 }
diff --git a/BugsSite/Bugzilla/Auth/Verify/DB.pm b/BugsSite/Bugzilla/Auth/Verify/DB.pm
index 88ad78d..f2c008d 100644
--- a/BugsSite/Bugzilla/Auth/Verify/DB.pm
+++ b/BugsSite/Bugzilla/Auth/Verify/DB.pm
@@ -53,6 +53,11 @@
         "SELECT cryptpassword FROM profiles WHERE userid = ?",
         undef, $user_id);
 
+    # Wide characters cause crypt to die
+    if (Bugzilla->params->{'utf8'}) {
+        utf8::encode($password) if utf8::is_utf8($password);
+    }
+
     # Using the internal crypted password as the salt,
     # crypt the password the user entered.
     my $entered_password_crypted = crypt($password, $real_password_crypted);
diff --git a/BugsSite/Bugzilla/Auth/Verify/LDAP.pm b/BugsSite/Bugzilla/Auth/Verify/LDAP.pm
index 343f795..b590430 100644
--- a/BugsSite/Bugzilla/Auth/Verify/LDAP.pm
+++ b/BugsSite/Bugzilla/Auth/Verify/LDAP.pm
@@ -37,6 +37,8 @@
 
 use Bugzilla::Constants;
 use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Util;
 
 use Net::LDAP;
 
@@ -89,7 +91,22 @@
                      details => {attr => $mail_attr} };
         }
 
-        $params->{bz_username} = $user_entry->get_value($mail_attr);
+        my @emails = $user_entry->get_value($mail_attr);
+
+        # Default to the first email address returned.
+        $params->{bz_username} = $emails[0];
+
+        if (@emails > 1) {
+            # Cycle through the adresses and check if they're Bugzilla logins.
+            # Use the first one that returns a valid id. 
+            foreach my $email (@emails) {
+                if ( login_to_id($email) ) {
+                    $params->{bz_username} = $email;
+                    last;
+                }
+            }
+        }
+
     } else {
         $params->{bz_username} = $username;
     }
@@ -97,6 +114,8 @@
     $params->{realname}  ||= $user_entry->get_value("displayName");
     $params->{realname}  ||= $user_entry->get_value("cn");
 
+    $params->{extern_id} = $username;
+
     return $params;
 }
 
@@ -134,11 +153,15 @@
     my ($self) = @_;
     return $self->{ldap} if $self->{ldap};
 
-    my $server = Bugzilla->params->{"LDAPserver"};
-    ThrowCodeError("ldap_server_not_defined") unless $server;
+    my @servers = split(/[\s,]+/, Bugzilla->params->{"LDAPserver"});
+    ThrowCodeError("ldap_server_not_defined") unless @servers;
 
-    $self->{ldap} = new Net::LDAP($server)
-        || ThrowCodeError("ldap_connect_failed", { server => $server });
+    foreach (@servers) {
+        $self->{ldap} = new Net::LDAP(trim($_));
+        last if $self->{ldap};
+    }
+    ThrowCodeError("ldap_connect_failed", { server => join(", ", @servers) }) 
+        unless $self->{ldap};
 
     # try to start TLS if needed
     if (Bugzilla->params->{"LDAPstarttls"}) {
diff --git a/BugsSite/Bugzilla/Auth/Verify/RADIUS.pm b/BugsSite/Bugzilla/Auth/Verify/RADIUS.pm
new file mode 100644
index 0000000..da36c3b
--- /dev/null
+++ b/BugsSite/Bugzilla/Auth/Verify/RADIUS.pm
@@ -0,0 +1,64 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Marc Schumann.
+# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
+# All rights reserved.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+
+package Bugzilla::Auth::Verify::RADIUS;
+use strict;
+use base qw(Bugzilla::Auth::Verify);
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+use Authen::Radius;
+
+use constant admin_can_create_account => 0;
+use constant user_can_create_account  => 0;
+
+sub check_credentials {
+    my ($self, $params) = @_;
+    my $dbh = Bugzilla->dbh;
+    my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'};
+    my $username = $params->{username};
+
+    # If we're using RADIUS_email_suffix, we may need to cut it off from
+    # the login name.
+    if ($address_suffix) {
+        $username =~ s/\Q$address_suffix\E$//i;
+    }
+
+    # Create RADIUS object.
+    my $radius =
+        new Authen::Radius(Host   => Bugzilla->params->{'RADIUS_server'},
+                           Secret => Bugzilla->params->{'RADIUS_secret'})
+        || return { failure => AUTH_ERROR, error => 'radius_preparation_error',
+                    details => {errstr => Authen::Radius::strerror() } };
+
+    # Check the password.
+    $radius->check_pwd($username, $params->{password},
+                       Bugzilla->params->{'RADIUS_NAS_IP'} || undef)
+        || return { failure => AUTH_LOGINFAILED };
+
+    # Build the user account's e-mail address.
+    $params->{bz_username} = $username . $address_suffix;
+
+    return $params;
+}
+
+1;
diff --git a/BugsSite/Bugzilla/Bug.pm b/BugsSite/Bugzilla/Bug.pm
index 92e7f5b..7f2b8d8 100644
--- a/BugsSite/Bugzilla/Bug.pm
+++ b/BugsSite/Bugzilla/Bug.pm
@@ -35,6 +35,7 @@
 use Bugzilla::Field;
 use Bugzilla::Flag;
 use Bugzilla::FlagType;
+use Bugzilla::Hook;
 use Bugzilla::Keyword;
 use Bugzilla::User;
 use Bugzilla::Util;
@@ -42,17 +43,18 @@
 use Bugzilla::Product;
 use Bugzilla::Component;
 use Bugzilla::Group;
+use Bugzilla::Status;
 
 use List::Util qw(min);
+use Storable qw(dclone);
 
 use base qw(Bugzilla::Object Exporter);
 @Bugzilla::Bug::EXPORT = qw(
-    AppendComment ValidateComment
-    bug_alias_to_id ValidateBugAlias ValidateBugID
+    bug_alias_to_id ValidateBugID
     RemoveVotes CheckIfVotedConfirmed
     LogActivityEntry
-    is_open_state
     editable_bug_fields
+    SPECIAL_STATUS_WORKFLOW_ACTIONS
 );
 
 #####################################################################
@@ -67,8 +69,12 @@
 # This is a sub because it needs to call other subroutines.
 sub DB_COLUMNS {
     my $dbh = Bugzilla->dbh;
+    my @custom = grep {$_->type != FIELD_TYPE_MULTI_SELECT}
+                      Bugzilla->active_custom_fields;
+    my @custom_names = map {$_->name} @custom;
     return qw(
         alias
+        assigned_to
         bug_file_loc
         bug_id
         bug_severity
@@ -81,6 +87,7 @@
         op_sys
         priority
         product_id
+        qa_contact
         remaining_time
         rep_platform
         reporter_accessible
@@ -90,22 +97,15 @@
         target_milestone
         version
     ),
-    'assigned_to AS assigned_to_id',
     'reporter    AS reporter_id',
-    'qa_contact  AS qa_contact_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',
-    Bugzilla->custom_field_names;
+    @custom_names;
 }
 
 use constant REQUIRED_CREATE_FIELDS => qw(
-    bug_severity
-    comment
     component
-    op_sys
-    priority
     product
-    rep_platform
     short_desc
     version
 );
@@ -130,28 +130,104 @@
         status_whiteboard => \&_check_status_whiteboard,
     };
 
-    my @select_fields = Bugzilla->get_fields({custom => 1, obsolete => 0,
-                                              type => FIELD_TYPE_SINGLE_SELECT});
-
-    foreach my $field (@select_fields) {
-        $validators->{$field->name} = \&_check_select_field;
+    # Set up validators for custom fields.    
+    foreach my $field (Bugzilla->active_custom_fields) {
+        my $validator;
+        if ($field->type == FIELD_TYPE_SINGLE_SELECT) {
+            $validator = \&_check_select_field;
+        }
+        elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
+            $validator = \&_check_multi_select_field;
+        }
+        elsif ($field->type == FIELD_TYPE_DATETIME) {
+            $validator = \&_check_datetime_field;
+        }
+        elsif ($field->type == FIELD_TYPE_FREETEXT) {
+            $validator = \&_check_freetext_field;
+        }
+        else {
+            $validator = \&_check_default_field;
+        }
+        $validators->{$field->name} = $validator;
     }
+
     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 UPDATE_COLUMNS {
+    my @custom = grep {$_->type != FIELD_TYPE_MULTI_SELECT}
+                      Bugzilla->active_custom_fields;
+    my @custom_names = map {$_->name} @custom;
+    my @columns = qw(
+        alias
+        assigned_to
+        bug_file_loc
+        bug_severity
+        bug_status
+        cclist_accessible
+        component_id
+        deadline
+        estimated_time
+        everconfirmed
+        op_sys
+        priority
+        product_id
+        qa_contact
+        remaining_time
+        rep_platform
+        reporter_accessible
+        resolution
+        short_desc
+        status_whiteboard
+        target_milestone
+        version
+    );
+    push(@columns, @custom_names);
+    return @columns;
+};
+
+use constant NUMERIC_COLUMNS => qw(
+    estimated_time
+    remaining_time
+);
+
+sub DATE_COLUMNS {
+    my @fields = Bugzilla->get_fields(
+        { custom => 1, 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;
 
-# Used in ValidateComment(). Gives the max length allowed for a comment.
-use constant MAX_COMMENT_LENGTH => 65535;
-
-# The statuses that are valid on enter_bug.cgi and post_bug.cgi.
-# The order is important--see _check_bug_status
-use constant VALID_ENTRY_STATUS => qw(
-    UNCONFIRMED
-    NEW
-    ASSIGNED
+use constant SPECIAL_STATUS_WORKFLOW_ACTIONS => qw(
+    none
+    duplicate
+    change_resolution
+    clearresolution
 );
 
 #####################################################################
@@ -191,12 +267,6 @@
         return $error_self;
     }
 
-    # XXX At some point these should be moved into accessors.
-    # They only are here because this is how Bugzilla::Bug
-    # originally did things, before it was a Bugzilla::Object.
-    $self->{'isunconfirmed'} = ($self->{bug_status} eq 'UNCONFIRMED');
-    $self->{'isopened'}      = is_open_state($self->{bug_status});
-
     return $self;
 }
 
@@ -239,22 +309,32 @@
 # C<deadline>       - For time-tracking. Will be ignored for the same
 #                     reasons as C<estimated_time>.
 sub create {
-    my $class  = shift;
+    my ($class, $params) = @_;
     my $dbh = Bugzilla->dbh;
 
-    $class->check_required_create_fields(@_);
-    my $params = $class->run_create_validators(@_);
+    $dbh->bz_start_transaction();
+
+    # These fields have default values which we can use if they are undefined.
+    $params->{bug_severity} = Bugzilla->params->{defaultseverity}
+      unless defined $params->{bug_severity};
+    $params->{priority} = Bugzilla->params->{defaultpriority}
+      unless defined $params->{priority};
+    $params->{op_sys} = Bugzilla->params->{defaultopsys}
+      unless defined $params->{op_sys};
+    $params->{rep_platform} = Bugzilla->params->{defaultplatform}
+      unless defined $params->{rep_platform};
+    # Make sure a comment is always defined.
+    $params->{comment} = '' unless defined $params->{comment};
+
+    $class->check_required_create_fields($params);
+    $params = $class->run_create_validators($params);
 
     # These are not a fields in the bugs table, so we don't pass them to
     # insert_create_data.
-    my $cc_ids = $params->{cc};
-    delete $params->{cc};
-    my $groups = $params->{groups};
-    delete $params->{groups};
-    my $depends_on = $params->{dependson};
-    delete $params->{dependson};
-    my $blocked = $params->{blocked};
-    delete $params->{blocked};
+    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};
@@ -266,13 +346,9 @@
 
     # We don't want the bug to appear in the system until it's correctly
     # protected by groups.
-    my $timestamp = $params->{creation_ts}; 
-    delete $params->{creation_ts};
+    my $timestamp = delete $params->{creation_ts}; 
 
-    $dbh->bz_lock_tables('bugs WRITE', 'bug_group_map WRITE', 
-        'longdescs WRITE', 'cc WRITE', 'keywords WRITE', 'dependencies WRITE',
-        'bugs_activity WRITE', 'fielddefs READ');
-
+    my $ms_values = $class->_extract_multi_selects($params);
     my $bug = $class->insert_create_data($params);
 
     # Add the group restrictions
@@ -320,6 +396,16 @@
         $sth_bug_time->execute($timestamp, $blocked_id);
     }
 
+    # Insert the values into the multiselect value tables
+    foreach my $field (keys %$ms_values) {
+        $dbh->do("DELETE FROM bug_$field where bug_id = ?",
+                undef, $bug->bug_id);
+        foreach my $value ( @{$ms_values->{$field}} ) {
+            $dbh->do("INSERT INTO bug_$field (bug_id, value) VALUES (?,?)",
+                    undef, $bug->bug_id, $value);
+        }
+    }
+
     # And insert the comment. We always insert a comment on bug creation,
     # but sometimes it's blank.
     my @columns = qw(bug_id who bug_when thetext);
@@ -335,7 +421,12 @@
     $dbh->do('INSERT INTO longdescs (' . join(',', @columns)  . ")
                    VALUES ($qmarks)", undef, @values);
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
+
+    # Because MySQL doesn't support transactions on the fulltext table,
+    # we do this after we've committed the transaction. That way we're
+    # sure we're inserting a good Bug ID.
+    $bug->_sync_fulltext('new bug');
 
     return $bug;
 }
@@ -350,30 +441,31 @@
     delete $params->{product};
 
     ($params->{bug_status}, $params->{everconfirmed})
-        = $class->_check_bug_status($product, $params->{bug_status});
+        = $class->_check_bug_status($params->{bug_status}, $product,
+                                    $params->{comment});
 
-    $params->{target_milestone} = $class->_check_target_milestone($product,
-        $params->{target_milestone});
+    $params->{target_milestone} = $class->_check_target_milestone(
+        $params->{target_milestone}, $product);
 
-    $params->{version} = $class->_check_version($product, $params->{version});
+    $params->{version} = $class->_check_version($params->{version}, $product);
 
-    $params->{keywords} = $class->_check_keywords($product, $params->{keywords});
+    $params->{keywords} = $class->_check_keywords($params->{keywords}, $product);
 
     $params->{groups} = $class->_check_groups($product,
         $params->{groups});
 
-    my $component = $class->_check_component($product, $params->{component});
+    my $component = $class->_check_component($params->{component}, $product);
     $params->{component_id} = $component->id;
     delete $params->{component};
 
     $params->{assigned_to} = 
-        $class->_check_assigned_to($component, $params->{assigned_to});
+        $class->_check_assigned_to($params->{assigned_to}, $component);
     $params->{qa_contact} =
-        $class->_check_qa_contact($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.
-    $params->{reporter} = Bugzilla->user->id;
+    $params->{reporter} = $class->_check_reporter();
 
     $params->{creation_ts} ||= Bugzilla->dbh->selectrow_array('SELECT NOW()');
     $params->{delta_ts} = $params->{creation_ts};
@@ -382,11 +474,12 @@
         $params->{remaining_time} = $params->{estimated_time};
     }
 
-    $class->_check_strict_isolation($product, $params->{cc},
-                                    $params->{assigned_to}, $params->{qa_contact});
+    $class->_check_strict_isolation($params->{cc}, $params->{assigned_to},
+                                    $params->{qa_contact}, $product);
 
     ($params->{dependson}, $params->{blocked}) = 
-        $class->_check_dependencies($product, $params->{dependson}, $params->{blocked});
+        $class->_check_dependencies($params->{dependson}, $params->{blocked},
+                                    $product);
 
     # You can't set these fields on bug creation (or sometimes ever).
     delete $params->{resolution};
@@ -397,6 +490,275 @@
     return $params;
 }
 
+sub update {
+    my $self = shift;
+
+    my $dbh = Bugzilla->dbh;
+    # XXX This is just a temporary hack until all updating happens
+    # inside this function.
+    my $delta_ts = shift || $dbh->selectrow_array("SELECT NOW()");
+
+    my $old_bug = $self->new($self->id);
+    my $changes = $self->SUPER::update(@_);
+
+    # Certain items in $changes have to be fixed so that they hold
+    # a name instead of an ID.
+    foreach my $field (qw(product_id component_id)) {
+        my $change = delete $changes->{$field};
+        if ($change) {
+            my $new_field = $field;
+            $new_field =~ s/_id$//;
+            $changes->{$new_field} = 
+                [$self->{"_old_${new_field}_name"}, $self->$new_field];
+        }
+    }
+    foreach my $field (qw(qa_contact assigned_to)) {
+        if ($changes->{$field}) {
+            my ($from, $to) = @{ $changes->{$field} };
+            $from = $old_bug->$field->login if $from;
+            $to   = $self->$field->login    if $to;
+            $changes->{$field} = [$from, $to];
+        }
+    }
+
+    # CC
+    my @old_cc = map {$_->id} @{$old_bug->cc_users};
+    my @new_cc = map {$_->id} @{$self->cc_users};
+    my ($removed_cc, $added_cc) = diff_arrays(\@old_cc, \@new_cc);
+    
+    if (scalar @$removed_cc) {
+        $dbh->do('DELETE FROM cc WHERE bug_id = ? AND ' 
+                 . $dbh->sql_in('who', $removed_cc), undef, $self->id);
+    }
+    foreach my $user_id (@$added_cc) {
+        $dbh->do('INSERT INTO cc (bug_id, who) VALUES (?,?)',
+                 undef, $self->id, $user_id);
+    }
+    # If any changes were found, record it in the activity log
+    if (scalar @$removed_cc || scalar @$added_cc) {
+        my $removed_users = Bugzilla::User->new_from_list($removed_cc);
+        my $added_users   = Bugzilla::User->new_from_list($added_cc);
+        my $removed_names = join(', ', (map {$_->login} @$removed_users));
+        my $added_names   = join(', ', (map {$_->login} @$added_users));
+        $changes->{cc} = [$removed_names, $added_names];
+    }
+    
+    # Keywords
+    my @old_kw_ids = map { $_->id } @{$old_bug->keyword_objects};
+    my @new_kw_ids = map { $_->id } @{$self->keyword_objects};
+
+    my ($removed_kw, $added_kw) = diff_arrays(\@old_kw_ids, \@new_kw_ids);
+
+    if (scalar @$removed_kw) {
+        $dbh->do('DELETE FROM keywords WHERE bug_id = ? AND ' 
+                 . $dbh->sql_in('keywordid', $removed_kw), undef, $self->id);
+    }
+    foreach my $keyword_id (@$added_kw) {
+        $dbh->do('INSERT INTO keywords (bug_id, keywordid) VALUES (?,?)',
+                 undef, $self->id, $keyword_id);
+    }
+    $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);
+        my $added_keywords   = Bugzilla::Keyword->new_from_list($added_kw);
+        my $removed_names = join(', ', (map {$_->name} @$removed_keywords));
+        my $added_names   = join(', ', (map {$_->name} @$added_keywords));
+        $changes->{keywords} = [$removed_names, $added_names];
+    }
+
+    # Dependencies
+    foreach my $pair ([qw(dependson blocked)], [qw(blocked dependson)]) {
+        my ($type, $other) = @$pair;
+        my $old = $old_bug->$type;
+        my $new = $self->$type;
+        
+        my ($removed, $added) = diff_arrays($old, $new);
+        foreach my $removed_id (@$removed) {
+            $dbh->do("DELETE FROM dependencies WHERE $type = ? AND $other = ?",
+                     undef, $removed_id, $self->id);
+            
+            # Add an activity entry for the other bug.
+            LogActivityEntry($removed_id, $other, $self->id, '',
+                             Bugzilla->user->id, $delta_ts);
+            # Update delta_ts on the other bug so that we trigger mid-airs.
+            $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+                     undef, $delta_ts, $removed_id);
+        }
+        foreach my $added_id (@$added) {
+            $dbh->do("INSERT INTO dependencies ($type, $other) VALUES (?,?)",
+                     undef, $added_id, $self->id);
+            
+            # Add an activity entry for the other bug.
+            LogActivityEntry($added_id, $other, '', $self->id,
+                             Bugzilla->user->id, $delta_ts);
+            # Update delta_ts on the other bug so that we trigger mid-airs.
+            $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+                     undef, $delta_ts, $added_id);
+        }
+        
+        if (scalar(@$removed) || scalar(@$added)) {
+            $changes->{$type} = [join(', ', @$removed), join(', ', @$added)];
+        }
+    }
+
+    # Groups
+    my %old_groups = map {$_->id => $_} @{$old_bug->groups_in};
+    my %new_groups = map {$_->id => $_} @{$self->groups_in};
+    my ($removed_gr, $added_gr) = diff_arrays([keys %old_groups],
+                                              [keys %new_groups]);
+    if (scalar @$removed_gr || scalar @$added_gr) {
+        if (@$removed_gr) {
+            my $qmarks = join(',', ('?') x @$removed_gr);
+            $dbh->do("DELETE FROM bug_group_map
+                       WHERE bug_id = ? AND group_id IN ($qmarks)", undef,
+                     $self->id, @$removed_gr);
+        }
+        my $sth_insert = $dbh->prepare(
+            'INSERT INTO bug_group_map (bug_id, group_id) VALUES (?,?)');
+        foreach my $gid (@$added_gr) {
+            $sth_insert->execute($self->id, $gid);
+        }
+        my @removed_names = map { $old_groups{$_}->name } @$removed_gr;
+        my @added_names   = map { $new_groups{$_}->name } @$added_gr;
+        $changes->{'bug_group'} = [join(', ', @removed_names),
+                                   join(', ', @added_names)];
+    }
+    
+    # Comments
+    foreach my $comment (@{$self->{added_comments} || []}) {
+        my $columns = join(',', keys %$comment);
+        my @values  = values %$comment;
+        my $qmarks  = join(',', ('?') x @values);
+        $dbh->do("INSERT INTO longdescs (bug_id, who, bug_when, $columns)
+                       VALUES (?,?,?,$qmarks)", undef,
+                 $self->bug_id, Bugzilla->user->id, $delta_ts, @values);
+        if ($comment->{work_time}) {
+            LogActivityEntry($self->id, "work_time", "", $comment->{work_time},
+                Bugzilla->user->id, $delta_ts);
+        }
+    }
+    
+    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.
+    }
+
+    # Insert the values into the multiselect value tables
+    my @multi_selects = grep {$_->type == FIELD_TYPE_MULTI_SELECT}
+                             Bugzilla->active_custom_fields;
+    foreach my $field (@multi_selects) {
+        my $name = $field->name;
+        my ($removed, $added) = diff_arrays($old_bug->$name, $self->$name);
+        if (scalar @$removed || scalar @$added) {
+            $changes->{$name} = [join(', ', @$removed), join(', ', @$added)];
+
+            $dbh->do("DELETE FROM bug_$name where bug_id = ?",
+                     undef, $self->id);
+            foreach my $value (@{$self->$name}) {
+                $dbh->do("INSERT INTO bug_$name (bug_id, value) VALUES (?,?)",
+                         undef, $self->id, $value);
+            }
+        }
+    }
+
+    # Log bugs_activity items
+    # XXX Eventually, when bugs_activity is able to track the dupe_id,
+    # this code should go below the duplicates-table-updating code below.
+    foreach my $field (keys %$changes) {
+        my $change = $changes->{$field};
+        my $from = defined $change->[0] ? $change->[0] : '';
+        my $to   = defined $change->[1] ? $change->[1] : '';
+        LogActivityEntry($self->id, $field, $from, $to, Bugzilla->user->id,
+                         $delta_ts);
+    }
+
+    # Check if we have to update the duplicates table and the other bug.
+    my ($old_dup, $cur_dup) = ($old_bug->dup_id || 0, $self->dup_id || 0);
+    if ($old_dup != $cur_dup) {
+        $dbh->do("DELETE FROM duplicates WHERE dupe = ?", undef, $self->id);
+        if ($cur_dup) {
+            $dbh->do('INSERT INTO duplicates (dupe, dupe_of) VALUES (?,?)',
+                     undef, $self->id, $cur_dup);
+            if (my $update_dup = delete $self->{_dup_for_update}) {
+                $update_dup->update();
+            }
+        }
+        
+        $changes->{'dup_id'} = [$old_dup || undef, $cur_dup || undef];
+    }
+
+    Bugzilla::Hook::process('bug-end_of_update', { bug       => $self,
+                                                   timestamp => $delta_ts,
+                                                   changes   => $changes,
+                                                 });
+
+    # If any change occurred, refresh the timestamp of the bug.
+    if (scalar(keys %$changes) || $self->{added_comments}) {
+        $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+                 undef, ($delta_ts, $self->id));
+        $self->{delta_ts} = $delta_ts;
+    }
+
+    # 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};
+
+    # Remove obsolete internal variables.
+    delete $self->{'_old_assigned_to'};
+    delete $self->{'_old_qa_contact'};
+
+    return $changes;
+}
+
+# Used by create().
+# We need to handle multi-select fields differently than normal fields,
+# because they're arrays and don't go into the bugs table.
+sub _extract_multi_selects {
+    my ($invocant, $params) = @_;
+
+    my @multi_selects = grep {$_->type == FIELD_TYPE_MULTI_SELECT}
+                             Bugzilla->active_custom_fields;
+    my %ms_values;
+    foreach my $field (@multi_selects) {
+        my $name = $field->name;
+        if (exists $params->{$name}) {
+            my $array = delete($params->{$name}) || [];
+            $ms_values{$name} = $array;
+        }
+    }
+    return \%ms_values;
+}
+
+# Should be called any time you update short_desc or change a comment.
+sub _sync_fulltext {
+    my ($self, $new_bug) = @_;
+    my $dbh = Bugzilla->dbh;
+    if ($new_bug) {
+        $dbh->do('INSERT INTO bugs_fulltext (bug_id, short_desc)
+                  SELECT bug_id, short_desc FROM bugs WHERE bug_id = ?',
+                 undef, $self->id);
+    }
+    else {
+        $dbh->do('UPDATE bugs_fulltext SET short_desc = ? WHERE bug_id = ?',
+                 undef, $self->short_desc, $self->id);
+    }
+    my $comments = $dbh->selectall_arrayref(
+        'SELECT thetext, isprivate FROM longdescs WHERE bug_id = ?',
+        undef, $self->id);
+    my $all = join("\n", map { $_->[0] } @$comments);
+    my @no_private = grep { !$_->[1] } @$comments;
+    my $nopriv_string = join("\n", map { $_->[0] } @no_private);
+    $dbh->do('UPDATE bugs_fulltext SET comments = ?, comments_noprivate = ?
+               WHERE bug_id = ?', undef, $all, $nopriv_string, $self->id);
+}
+
+
 # This is the correct way to delete bugs from the DB.
 # No bug should be deleted from anywhere else except from here.
 #
@@ -415,6 +777,7 @@
     # - bug_group_map
     # - bugs
     # - bugs_activity
+    # - bugs_fulltext
     # - cc
     # - dependencies
     # - duplicates
@@ -422,16 +785,12 @@
     # - 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.
 
-    $dbh->bz_lock_tables('attachments WRITE', 'bug_group_map WRITE',
-                         'bugs WRITE', 'bugs_activity WRITE', 'cc WRITE',
-                         'dependencies WRITE', 'duplicates WRITE',
-                         'flags WRITE', 'keywords WRITE',
-                         'longdescs WRITE', 'votes WRITE',
-                         'attach_data WRITE');
+    $dbh->bz_start_transaction();
 
     $dbh->do("DELETE FROM bug_group_map WHERE bug_id = ?", undef, $bug_id);
     $dbh->do("DELETE FROM bugs_activity WHERE bug_id = ?", undef, $bug_id);
@@ -442,7 +801,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 longdescs 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.
@@ -451,15 +809,26 @@
                                   WHERE bug_id = ?", undef, $bug_id);
 
     if (scalar(@$attach_ids)) {
-        $dbh->do("DELETE FROM attach_data WHERE id IN (" .
-                 join(",", @$attach_ids) . ")");
+        $dbh->do("DELETE FROM attach_data WHERE " 
+                 . $dbh->sql_in('id', $attach_ids));
     }
 
     # Several of the previous tables also depend on attach_id.
     $dbh->do("DELETE FROM attachments WHERE bug_id = ?", undef, $bug_id);
     $dbh->do("DELETE FROM bugs WHERE bug_id = ?", undef, $bug_id);
+    $dbh->do("DELETE FROM longdescs WHERE bug_id = ?", undef, $bug_id);
 
-    $dbh->bz_unlock_tables();
+    # 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;
@@ -474,29 +843,67 @@
    my ($invocant, $alias) = @_;
    $alias = trim($alias);
    return undef if (!Bugzilla->params->{'usebugaliases'} || !$alias);
-   ValidateBugAlias($alias);
+
+    # Make sure the alias isn't too long.
+    if (length($alias) > 20) {
+        ThrowUserError("alias_too_long");
+    }
+    # Make sure the alias isn't just a number.
+    if ($alias =~ /^\d+$/) {
+        ThrowUserError("alias_is_numeric", { alias => $alias });
+    }
+    # Make sure the alias has no commas or spaces.
+    if ($alias =~ /[, ]/) {
+        ThrowUserError("alias_has_comma_or_space", { alias => $alias });
+    }
+    # Make sure the alias is unique, or that it's already our alias.
+    my $other_bug = new Bugzilla::Bug($alias);
+    if (!$other_bug->{error}
+        && (!ref $invocant || $other_bug->id != $invocant->id))
+    {
+        ThrowUserError("alias_in_use", { alias => $alias,
+                                         bug_id => $other_bug->id });
+    }
+
    return $alias;
 }
 
 sub _check_assigned_to {
-    my ($invocant, $component, $name) = @_;
+    my ($invocant, $assignee, $component) = @_;
     my $user = Bugzilla->user;
 
-    $name = trim($name);
     # Default assignee is the component owner.
     my $id;
-    if (!$user->in_group('editbugs', $component->product_id) || !$name) {
+    # If this is a new bug, you can only set the assignee if you have editbugs.
+    # If you didn't specify the assignee, we use the default assignee.
+    if (!ref $invocant
+        && (!$user->in_group('editbugs', $component->product_id) || !$assignee))
+    {
         $id = $component->default_assignee->id;
     } else {
-        $id = login_to_id($name, THROW_ERROR);
+        if (!ref $assignee) {
+            $assignee = trim($assignee);
+            # When updating a bug, assigned_to can't be empty.
+            ThrowUserError("reassign_to_empty") if ref $invocant && !$assignee;
+            $assignee = Bugzilla::User->check($assignee);
+        }
+        $id = $assignee->id;
+        # create() checks this another way, so we don't have to run this
+        # check during create().
+        $invocant->_check_strict_isolation_for_user($assignee) if ref $invocant;
     }
     return $id;
 }
 
 sub _check_bug_file_loc {
     my ($invocant, $url) = @_;
-    # If bug_file_loc is "http://", the default, use an empty value instead.
-    $url = '' if (!defined($url) || $url eq 'http://');
+    $url = '' if !defined($url);
+    # On bug entry, if bug_file_loc is "http://", the default, use an 
+    # empty value instead. However, on bug editing people can set that
+    # back if they *really* want to.
+    if (!ref $invocant && $url eq 'http://') {
+        $url = '';
+    }
     return $url;
 }
 
@@ -508,31 +915,84 @@
 }
 
 sub _check_bug_status {
-    my ($invocant, $product, $status) = @_;
+    my ($invocant, $new_status, $product, $comment) = @_;
     my $user = Bugzilla->user;
+    my @valid_statuses;
+    my $old_status; # Note that this is undef for new bugs.
 
-    my @valid_statuses = VALID_ENTRY_STATUS;
-
-    if ($user->in_group('editbugs', $product->id)
-        || $user->in_group('canconfirm', $product->id)) {
-       # Default to NEW if the user with privs hasn't selected another status.
-       $status ||= 'NEW';
-    }
-    elsif (!$product->votes_to_confirm) {
-        # Without privs, products that don't support UNCONFIRMED default to
-        # NEW.
-        $status = 'NEW';
+    if (ref $invocant) {
+        @valid_statuses = @{$invocant->status->can_change_to};
+        $product = $invocant->product_obj;
+        $old_status = $invocant->status;
+        my $comments = $invocant->{added_comments} || [];
+        $comment = $comments->[-1];
     }
     else {
-        $status = 'UNCONFIRMED';
+        @valid_statuses = @{Bugzilla::Status->can_change_to()};
     }
 
-    # UNCONFIRMED becomes an invalid status if votes_to_confirm is 0,
-    # even if you are in editbugs.
-    shift @valid_statuses if !$product->votes_to_confirm;
+    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;
+    }
 
-    check_field('bug_status', $status, \@valid_statuses);
-    return ($status, $status eq 'UNCONFIRMED' ? 0 : 1);
+    # Check permissions for users filing new bugs.
+    if (!ref $invocant) {
+        if ($user->in_group('editbugs', $product->id)
+            || $user->in_group('canconfirm', $product->id)) {
+            # If the user with privs hasn't selected another status,
+            # select the first one of the list.
+            unless ($new_status) {
+                if (scalar(@valid_statuses) == 1) {
+                    $new_status = $valid_statuses[0];
+                }
+                else {
+                    $new_status = ($valid_statuses[0]->name ne 'UNCONFIRMED') ?
+                                  $valid_statuses[0] : $valid_statuses[1];
+                }
+            }
+        }
+        else {
+            # A user with no privs cannot choose the initial status.
+            # If UNCONFIRMED is valid for this product, use it; else
+            # use the first bug status available.
+            if (grep {$_->name eq 'UNCONFIRMED'} @valid_statuses) {
+                $new_status = 'UNCONFIRMED';
+            }
+            else {
+                $new_status = $valid_statuses[0];
+            }
+        }
+    }
+    # Time to validate the bug status.
+    $new_status = Bugzilla::Status->check($new_status) unless ref($new_status);
+    if (!grep {$_->name eq $new_status->name} @valid_statuses) {
+        ThrowUserError('illegal_bug_status_transition',
+                       { old => $old_status, new => $new_status });
+    }
+
+    # Check if a comment is required for this change.
+    if ($new_status->comment_required_on_change_from($old_status) && !$comment)
+    {
+        ThrowUserError('comment_required', { old => $old_status,
+                                             new => $new_status });
+        
+    }
+    
+    if (ref $invocant && $new_status->name eq 'ASSIGNED'
+        && Bugzilla->params->{"usetargetmilestone"}
+        && Bugzilla->params->{"musthavemilestoneonaccept"}
+        # musthavemilestoneonaccept applies only if at least two
+        # target milestones are defined for the product.
+        && scalar(@{ $product->milestones }) > 1
+        && $invocant->target_milestone eq $product->default_milestone)
+    {
+        ThrowUserError("milestone_required", { bug => $invocant });
+    }
+
+    return $new_status->name if ref $invocant;
+    return ($new_status->name, $new_status->name eq 'UNCONFIRMED' ? 0 : 1);
 }
 
 sub _check_cc {
@@ -562,16 +1022,7 @@
     $comment =~ s/\s*$//s;
     $comment =~ s/\r\n?/\n/g; # Get rid of \r.
 
-    ValidateComment($comment);
-
-    if (Bugzilla->params->{"commentoncreate"} && !$comment) {
-        ThrowUserError("description_required");
-    }
-
-    # On creation only, there must be a single-space comment, or
-    # email will be supressed.
-    $comment = ' ' if $comment eq '' && !ref($invocant);
-
+    ThrowUserError('comment_too_long') if length($comment) > MAX_COMMENT_LENGTH;
     return $comment;
 }
 
@@ -582,20 +1033,35 @@
             && $comment_privacy) ? 1 : 0;
 }
 
+sub _check_comment_type {
+    my ($invocant, $type) = @_;
+    detaint_natural($type)
+      || ThrowCodeError('bad_arg', { argument => 'type', 
+                                     function => caller });
+    return $type;
+}
+
 sub _check_component {
-    my ($invocant, $product, $name) = @_;
+    my ($invocant, $name, $product) = @_;
     $name = trim($name);
     $name || ThrowUserError("require_component");
-    my $obj = Bugzilla::Component::check_component($product, $name);
+    ($product = $invocant->product_obj) if ref $invocant;
+    my $obj = Bugzilla::Component->check({ product => $product, name => $name });
     return $obj;
 }
 
 sub _check_deadline {
     my ($invocant, $date) = @_;
-    $date = trim($date);
+    
+    # Check time-tracking permissions.
     my $tt_group = Bugzilla->params->{"timetrackinggroup"};
-    return undef unless $date && $tt_group 
-                        && Bugzilla->user->in_group($tt_group);
+    # 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);
+    
+    # Validate entered deadline
+    $date = trim($date);
+    return undef if !$date;
     validate_date($date)
         || ThrowUserError('illegal_date', { date   => $date,
                                             format => 'YYYY-MM-DD' });
@@ -605,32 +1071,152 @@
 # Takes two comma/space-separated strings and returns arrayrefs
 # of valid bug IDs.
 sub _check_dependencies {
-    my ($invocant, $product, $depends_on, $blocks) = @_;
+    my ($invocant, $depends_on, $blocks, $product) = @_;
 
-    # Only editbugs users can set dependencies on bug entry.
-    return ([], []) unless Bugzilla->user->in_group('editbugs', $product->id);
-
-    $depends_on ||= '';
-    $blocks     ||= '';
-
-    # Make sure all the bug_ids are valid.
-    my @results;
-    foreach my $string ($depends_on, $blocks) {
-        my @array = split(/[\s,]+/, $string);
-        # Eliminate nulls
-        @array = grep($_, @array);
-        # $field is not passed to ValidateBugID to prevent adding new
-        # dependencies on inaccessible bugs.
-        ValidateBugID($_) foreach (@array);
-        push(@results, \@array);
+    if (!ref $invocant) {
+        # Only editbugs users can set dependencies on bug entry.
+        return ([], []) unless Bugzilla->user->in_group('editbugs',
+                                                        $product->id);
     }
 
-    #                               dependson    blocks
-    my %deps = ValidateDependencies($results[0], $results[1]);
+    my %deps_in = (dependson => $depends_on || '', blocked => $blocks || '');
+
+    foreach my $type qw(dependson blocked) {
+        my @bug_ids = 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;
+       
+        my @check_access = @bug_ids;
+        # When we're updating a bug, only added or removed bug_ids are 
+        # checked for whether or not we can see/edit those bugs.
+        if (ref $invocant) {
+            my $old = $invocant->$type;
+            my ($removed, $added) = diff_arrays($old, \@bug_ids);
+            @check_access = (@$added, @$removed);
+            
+            # Check field permissions if we've changed anything.
+            if (@check_access) {
+                my $privs;
+                if (!$invocant->check_can_change_field($type, 0, 1, \$privs)) {
+                    ThrowUserError('illegal_change', { field => $type,
+                                                       privs => $privs });
+                }
+            }
+        }
+
+        my $user = Bugzilla->user;
+        foreach my $modified_id (@check_access) {
+            ValidateBugID($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});
+                }
+            }
+        }
+        
+        $deps_in{$type} = \@bug_ids;
+    }
+
+    # And finally, check for dependency loops.
+    my $bug_id = ref($invocant) ? $invocant->id : 0;
+    my %deps = ValidateDependencies($deps_in{dependson}, $deps_in{blocked}, $bug_id);
 
     return ($deps{'dependson'}, $deps{'blocked'});
 }
 
+sub _check_dup_id {
+    my ($self, $dupe_of) = @_;
+    my $dbh = Bugzilla->dbh;
+    
+    $dupe_of = trim($dupe_of);
+    $dupe_of || ThrowCodeError('undefined_field', { field => 'dup_id' });
+    # Validate the bug ID. The second argument will force ValidateBugID() 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');
+
+    # 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);
+
+    # 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);
+    }
+
+    my $cur_dup = $self->dup_id || 0;
+    if ($cur_dup != $dupe_of && Bugzilla->params->{'commentonduplicate'}
+        && !$self->{added_comments})
+    {
+        ThrowUserError('comment_required');
+    }
+
+    # Should we add the reporter to the CC list of the new bug?
+    # If he can see the bug...
+    if ($self->reporter->can_see_bug($dupe_of)) {
+        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;
+    }
+    # What if the reporter currently can't see the new bug? In the browser 
+    # interface, we prompt the user. In other interfaces, we default to 
+    # not adding the user, as the safest option.
+    elsif (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
+        # If we've already confirmed whether the user should be added...
+        my $cgi = Bugzilla->cgi;
+        my $add_confirmed = $cgi->param('confirm_add_duplicate');
+        if (defined $add_confirmed) {
+            $self->{_add_dup_cc} = $add_confirmed;
+        }
+        else {
+            # Note that here we don't check if he user is already the reporter
+            # of the dupe_of bug, since we already checked if he can *see*
+            # the bug, above. People might have reporter_accessible turned
+            # off, but cclist_accessible turned on, so they might want to
+            # add the reporter even though he's already the reporter of the
+            # dup_of bug.
+            my $vars = {};
+            my $template = Bugzilla->template;
+            # Ask the user what they want to do about the reporter.
+            $vars->{'cclist_accessible'} = $dbh->selectrow_array(
+                q{SELECT cclist_accessible FROM bugs WHERE bug_id = ?},
+                undef, $dupe_of);
+            $vars->{'original_bug_id'} = $dupe_of;
+            $vars->{'duplicate_bug_id'} = $self->id;
+            print $cgi->header();
+            $template->process("bug/process/confirm-duplicate.html.tmpl", $vars)
+              || ThrowTemplateError($template->error());
+            exit;
+        }
+    }
+
+    return $dupe_of;
+}
+
 sub _check_estimated_time {
     return $_[0]->_check_time($_[1], 'estimated_time');
 }
@@ -682,11 +1268,15 @@
 }
 
 sub _check_keywords {
-    my ($invocant, $product, $keyword_string) = @_;
+    my ($invocant, $keyword_string, $product) = @_;
     $keyword_string = trim($keyword_string);
-    return [] if (!$keyword_string
-                  || !Bugzilla->user->in_group('editbugs', $product->id));
-
+    return [] if !$keyword_string;
+    
+    # On creation, only editbugs users can set keywords.
+    if (!ref $invocant) {
+        return [] if !Bugzilla->user->in_group('editbugs', $product->id);
+    }
+    
     my %keywords;
     foreach my $keyword (split(/[\s,]+/, $keyword_string)) {
         next unless $keyword;
@@ -699,13 +1289,18 @@
 
 sub _check_product {
     my ($invocant, $name) = @_;
+    $name = trim($name);
+    # If we're updating the bug and they haven't changed the product,
+    # always allow it.
+    if (ref $invocant && lc($invocant->product_obj->name) eq lc($name)) {
+        return $invocant->product_obj;
+    }
     # Check that the product exists and that the user
     # is allowed to enter bugs into this product.
     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.
-    my $obj = new Bugzilla::Product({ name => $name });
-    return $obj;
+    return new Bugzilla::Product({ name => $name });
 }
 
 sub _check_op_sys {
@@ -717,7 +1312,7 @@
 
 sub _check_priority {
     my ($invocant, $priority) = @_;
-    if (!Bugzilla->params->{'letsubmitterchoosepriority'}) {
+    if (!ref $invocant && !Bugzilla->params->{'letsubmitterchoosepriority'}) {
         $priority = Bugzilla->params->{'defaultpriority'};
     }
     $priority = trim($priority);
@@ -726,6 +1321,39 @@
     return $priority;
 }
 
+sub _check_qa_contact {
+    my ($invocant, $qa_contact, $component) = @_;
+    $qa_contact = trim($qa_contact) if !ref $qa_contact;
+    
+    my $id;
+    if (!ref $invocant) {
+        # Bugs get no QA Contact on creation if useqacontact is off.
+        return undef if !Bugzilla->params->{useqacontact};
+        # Set the default QA Contact if one isn't specified or if the
+        # user doesn't have editbugs.
+        if (!Bugzilla->user->in_group('editbugs', $component->product_id)
+            || !$qa_contact)
+        {
+            $id = $component->default_qa_contact->id;
+        }
+    }
+    
+    # If a QA Contact was specified or if we're updating, check
+    # the QA Contact for validity.
+    if (!defined $id && $qa_contact) {
+        $qa_contact = Bugzilla::User->check($qa_contact) if !ref $qa_contact;
+        $id = $qa_contact->id;
+        # create() checks this another way, so we don't have to run this
+        # check during create().
+        # If there is no QA contact, this check is not required.
+        $invocant->_check_strict_isolation_for_user($qa_contact)
+            if (ref $invocant && $id);
+    }
+
+    # "0" always means "undef", for QA Contact.
+    return $id || undef;
+}
+
 sub _check_remaining_time {
     return $_[0]->_check_time($_[1], 'remaining_time');
 }
@@ -737,6 +1365,60 @@
     return $platform;
 }
 
+sub _check_reporter {
+    my $invocant = shift;
+    my $reporter;
+    if (ref $invocant) {
+        # You cannot change the reporter of a bug.
+        $reporter = $invocant->reporter->id;
+    }
+    else {
+        # On bug creation, the reporter is the logged in user
+        # (meaning that he must be logged in first!).
+        $reporter = Bugzilla->user->id;
+        $reporter || ThrowCodeError('invalid_user');
+    }
+    return $reporter;
+}
+
+sub _check_resolution {
+    my ($self, $resolution) = @_;
+    $resolution = trim($resolution);
+    
+    # Throw a special error for resolving bugs without a resolution
+    # (or trying to change the resolution to '' on a closed bug without
+    # using clear_resolution).
+    ThrowUserError('missing_resolution', { status => $self->status->name })
+        if !$resolution && !$self->status->is_open;
+    
+    # Make sure this is a valid resolution.
+    check_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')
+    {
+        my @dependencies = CountOpenDependencies($self->id);
+        if (@dependencies) {
+            ThrowUserError("still_unresolved_bugs",
+                           { dependencies     => \@dependencies,
+                             dependency_count => scalar @dependencies });
+        }
+    }
+
+    # Check if they're changing the resolution and need to comment.
+    if (Bugzilla->params->{'commentonchange_resolution'} 
+        && $self->resolution && $resolution ne $self->resolution 
+        && !$self->{added_comments})
+    {
+        ThrowUserError('comment_required');
+    }
+    
+    return $resolution;
+}
+
 sub _check_short_desc {
     my ($invocant, $short_desc) = @_;
     # Set the parameter to itself, but cleaned up
@@ -752,37 +1434,81 @@
 
 # Unlike other checkers, this one doesn't return anything.
 sub _check_strict_isolation {
-    my ($invocant, $product, $cc_ids, $assignee_id, $qa_contact_id) = @_;
-
+    my ($invocant, $ccs, $assignee, $qa_contact, $product) = @_;
     return unless Bugzilla->params->{'strict_isolation'};
 
-    my @related_users = @$cc_ids;
-    push(@related_users, $assignee_id);
+    if (ref $invocant) {
+        my $original = $invocant->new($invocant->id);
 
-    if (Bugzilla->params->{'useqacontact'} && $qa_contact_id) {
-        push(@related_users, $qa_contact_id);
+        # We only check people if they've been added. This way, if
+        # strict_isolation is turned on when there are invalid users
+        # on bugs, people can still add comments and so on.
+        my @old_cc = map { $_->id } @{$original->cc_users};
+        my @new_cc = map { $_->id } @{$invocant->cc_users};
+        my ($removed, $added) = diff_arrays(\@old_cc, \@new_cc);
+        $ccs = Bugzilla::User->new_from_list($added);
+
+        $assignee = $invocant->assigned_to
+            if $invocant->assigned_to->id != $original->assigned_to->id;
+        if ($invocant->qa_contact
+            && (!$original->qa_contact
+                || $invocant->qa_contact->id != $original->qa_contact->id))
+        {
+            $qa_contact = $invocant->qa_contact;
+        }
+        $product = $invocant->product_obj;
     }
 
+    my @related_users = @$ccs;
+    push(@related_users, $assignee) if $assignee;
+
+    if (Bugzilla->params->{'useqacontact'} && $qa_contact) {
+        push(@related_users, $qa_contact);
+    }
+
+    @related_users = @{Bugzilla::User->new_from_list(\@related_users)}
+        if !ref $invocant;
+
     # For each unique user in @related_users...(assignee and qa_contact
     # could be duplicates of users in the CC list)
-    my %unique_users = map {$_ => 1} @related_users;
+    my %unique_users = map {$_->id => $_} @related_users;
     my @blocked_users;
-    foreach my $pid (keys %unique_users) {
-        my $related_user = Bugzilla::User->new($pid);
-        if (!$related_user->can_edit_product($product->id)) {
+    foreach my $id (keys %unique_users) {
+        my $related_user = $unique_users{$id};
+        if (!$related_user->can_edit_product($product->id) ||
+            !$related_user->can_see_product($product->name)) {
             push (@blocked_users, $related_user->login);
         }
     }
     if (scalar(@blocked_users)) {
-        ThrowUserError("invalid_user_group",
-            {'users' => \@blocked_users,
-             'new' => 1,
-             'product' => $product->name});
+        my %vars = ( users   => \@blocked_users,
+                     product => $product->name );
+        if (ref $invocant) {
+            $vars{'bug_id'} = $invocant->id;
+        }
+        else {
+            $vars{'new'} = 1;
+        }
+        ThrowUserError("invalid_user_group", \%vars);
+    }
+}
+
+# This is used by various set_ checkers, to make their code simpler.
+sub _check_strict_isolation_for_user {
+    my ($self, $user) = @_;
+    return unless Bugzilla->params->{"strict_isolation"};
+    if (!$user->can_edit_product($self->{product_id})) {
+        ThrowUserError('invalid_user_group',
+                       { users   => $user->login,
+                         product => $self->product,
+                         bug_id  => $self->id });
     }
 }
 
 sub _check_target_milestone {
-    my ($invocant, $product, $target) = @_;
+    my ($invocant, $target, $product) = @_;
+    $product = $invocant->product_obj if ref $invocant;
+
     $target = trim($target);
     $target = $product->default_milestone if !defined $target;
     check_field('target_milestone', $target,
@@ -792,39 +1518,78 @@
 
 sub _check_time {
     my ($invocant, $time, $field) = @_;
+
+    my $current = 0;
+    if (ref $invocant && $field ne 'work_time') {
+        $current = $invocant->$field;
+    }
     my $tt_group = Bugzilla->params->{"timetrackinggroup"};
-    return 0 unless $tt_group && Bugzilla->user->in_group($tt_group);
+    return $current unless $tt_group && Bugzilla->user->in_group($tt_group);
+    
     $time = trim($time) || 0;
     ValidateTime($time, $field);
     return $time;
 }
 
-sub _check_qa_contact {
-    my ($invocant, $component, $name) = @_;
-    my $user = Bugzilla->user;
-
-    return undef unless Bugzilla->params->{'useqacontact'};
-
-    $name = trim($name);
-
-    my $id;
-    if (!$user->in_group('editbugs', $component->product_id) || !$name) {
-        # We want to insert NULL into the database if we get a 0.
-        $id = $component->default_qa_contact->id || undef;
-    } else {
-        $id = login_to_id($name, THROW_ERROR);
-    }
-
-    return $id;
-}
-
 sub _check_version {
-    my ($invocant, $product, $version) = @_;
+    my ($invocant, $version, $product) = @_;
     $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');
+}
+
+# Custom Field Validators
+
+sub _check_datetime_field {
+    my ($invocant, $date_time) = @_;
+
+    # Empty datetimes are empty strings or strings only containing
+    # 0's, whitespace, and punctuation.
+    if ($date_time =~ /^[\s0[:punct:]]*$/) {
+        return undef;
+    }
+
+    $date_time = trim($date_time);
+    my ($date, $time) = split(' ', $date_time);
+    if ($date && !validate_date($date)) {
+        ThrowUserError('illegal_date', { date   => $date,
+                                         format => 'YYYY-MM-DD' });
+    }
+    if ($time && !validate_time($time)) {
+        ThrowUserError('illegal_time', { 'time' => $time,
+                                         format => 'HH:MM:SS' });
+    }
+    return $date_time
+}
+
+sub _check_default_field { return defined $_[1] ? trim($_[1]) : ''; }
+
+sub _check_freetext_field {
+    my ($invocant, $text) = @_;
+
+    $text = (defined $text) ? trim($text) : '';
+    if (length($text) > MAX_FREETEXT_LENGTH) {
+        ThrowUserError('freetext_too_long', { 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);
+    }
+    return $values;
+}
+
 sub _check_select_field {
     my ($invocant, $value, $field) = @_;
     $value = trim($value);
@@ -850,18 +1615,590 @@
            bug_file_loc status_whiteboard keywords
            priority bug_severity target_milestone
            dependson blocked votes everconfirmed
-           reporter assigned_to cc),
-    
+           reporter assigned_to cc estimated_time
+           remaining_time actual_time deadline),
+
         # Conditional Fields
         Bugzilla->params->{'useqacontact'} ? "qa_contact" : (),
-        Bugzilla->params->{'timetrackinggroup'} ? 
-            qw(estimated_time remaining_time actual_time deadline) : (),
-    
         # Custom Fields
-        Bugzilla->custom_field_names
+        map { $_->name } Bugzilla->active_custom_fields
     );
 }
 
+#####################################################################
+# Mutators 
+#####################################################################
+
+# To run check_can_change_field.
+sub _set_global_validator {
+    my ($self, $value, $field) = @_;
+    my $current = $self->$field;
+    my $privs;
+
+    if (ref $current && ref($current) ne 'ARRAY'
+        && $current->isa('Bugzilla::Object')) {
+        $current = $current->id ;
+    }
+    if (ref $value && ref($value) ne 'ARRAY'
+        && $value->isa('Bugzilla::Object')) {
+        $value = $value->id ;
+    }
+    my $can = $self->check_can_change_field($field, $current, $value, \$privs);
+    if (!$can) {
+        if ($field eq 'assigned_to' || $field eq 'qa_contact') {
+            $value   = user_id_to_login($value);
+            $current = user_id_to_login($current);
+        }
+        ThrowUserError('illegal_change', { field    => $field,
+                                           oldvalue => $current,
+                                           newvalue => $value,
+                                           privs    => $privs });
+    }
+}
+
+
+#################
+# "Set" Methods #
+#################
+
+sub set_alias { $_[0]->set('alias', $_[1]); }
+sub set_assigned_to {
+    my ($self, $value) = @_;
+    $self->set('assigned_to', $value);
+    # Store the old assignee. check_can_change_field() needs it.
+    $self->{'_old_assigned_to'} = $self->{'assigned_to_obj'}->id;
+    delete $self->{'assigned_to_obj'};
+}
+sub reset_assigned_to {
+    my $self = shift;
+    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});
+    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;
+    }
+}
+sub set_component  {
+    my ($self, $name) = @_;
+    my $old_comp  = $self->component_obj;
+    my $component = $self->_check_component($name);
+    if ($old_comp->id != $component->id) {
+        $self->{component_id}  = $component->id;
+        $self->{component}     = $component->name;
+        $self->{component_obj} = $component;
+        # For update()
+        $self->{_old_component_name} = $old_comp->name;
+        # Add in the Default CC of the new Component;
+        foreach my $cc (@{$component->initial_cc}) {
+            $self->add_cc($cc);
+        }
+    }
+}
+sub set_custom_field {
+    my ($self, $field, $value) = @_;
+    if (ref $value eq 'ARRAY' && $field->type != FIELD_TYPE_MULTI_SELECT) {
+        $value = $value->[0];
+    }
+    ThrowCodeError('field_not_custom', { field => $field }) if !$field->custom;
+    $self->set($field->name, $value);
+}
+sub set_deadline { $_[0]->set('deadline', $_[1]); }
+sub set_dependencies {
+    my ($self, $dependson, $blocked) = @_;
+    ($dependson, $blocked) = $self->_check_dependencies($dependson, $blocked);
+    # These may already be detainted, but all setters are supposed to
+    # detaint their input if they've run a validator (just as though
+    # we had used Bugzilla::Object::set), so we do that here.
+    detaint_natural($_) foreach (@$dependson, @$blocked);
+    $self->{'dependson'} = $dependson;
+    $self->{'blocked'}   = $blocked;
+}
+sub _clear_dup_id { $_[0]->{dup_id} = undef; }
+sub set_dup_id {
+    my ($self, $dup_id) = @_;
+    my $old = $self->dup_id || 0;
+    $self->set('dup_id', $dup_id);
+    my $new = $self->dup_id;
+    return if $old == $new;
+    
+    # Update the other bug.
+    my $dupe_of = new Bugzilla::Bug($self->dup_id);
+    if (delete $self->{_add_dup_cc}) {
+        $dupe_of->add_cc($self->reporter);
+    }
+    $dupe_of->add_comment("", { type       => CMT_HAS_DUPE,
+                                extra_data => $self->id });
+    $self->{_dup_for_update} = $dupe_of;
+    
+    # Now make sure that we add a duplicate comment on *this* bug.
+    # (Change an existing comment into a dup comment, if there is one,
+    # or add an empty dup comment.)
+    if ($self->{added_comments}) {
+        my @normal = grep { !defined $_->{type} || $_->{type} == CMT_NORMAL }
+                          @{ $self->{added_comments} };
+        # Turn the last one into a dup comment.
+        $normal[-1]->{type} = CMT_DUPE_OF;
+        $normal[-1]->{extra_data} = $self->dup_id;
+    }
+    else {
+        $self->add_comment('', { type       => CMT_DUPE_OF,
+                                 extra_data => $self->dup_id });
+    }
+}
+sub set_estimated_time { $_[0]->set('estimated_time', $_[1]); }
+sub _set_everconfirmed { $_[0]->set('everconfirmed', $_[1]); }
+sub set_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 {
+    my ($self, $name, $params) = @_;
+    my $old_product = $self->product_obj;
+    my $product = $self->_check_product($name);
+    
+    my $product_changed = 0;
+    if ($old_product->id != $product->id) {
+        $self->{product_id}  = $product->id;
+        $self->{product}     = $product->name;
+        $self->{product_obj} = $product;
+        # For update()
+        $self->{_old_product_name} = $old_product->name;
+        # Delete fields that depend upon the old Product value.
+        delete $self->{choices};
+        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};
+    # This way, if usetargetmilestone is off and we've changed products,
+    # set_target_milestone will reset our target_milestone to
+    # $product->default_milestone. But if we haven't changed products,
+    # we don't reset anything.
+    if (!defined $tm_name
+        && (Bugzilla->params->{'usetargetmilestone'} || !$product_changed))
+    {
+        $tm_name = $self->target_milestone;
+    }
+
+    if ($product_changed && Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
+        # Try to set each value with the new product.
+        # Have to set error_mode because Throw*Error calls exit() otherwise.
+        my $old_error_mode = Bugzilla->error_mode;
+        Bugzilla->error_mode(ERROR_MODE_DIE);
+        my $component_ok = eval { $self->set_component($comp_name);      1; };
+        my $version_ok   = eval { $self->set_version($vers_name);        1; };
+        my $milestone_ok = 1;
+        # Reporters can move bugs between products but not set the TM.
+        if ($self->check_can_change_field('target_milestone', 0, 1)) {
+            $milestone_ok = eval { $self->set_target_milestone($tm_name); 1; };
+        }
+        else {
+            # Have to set this directly to bypass the validators.
+            $self->{target_milestone} = $product->default_milestone;
+        }
+        # If there were any errors thrown, make sure we don't mess up any
+        # other part of Bugzilla that checks $@.
+        undef $@;
+        Bugzilla->error_mode($old_error_mode);
+        
+        my $verified = $params->{change_confirmed};
+        my %vars;
+        if (!$verified || !$component_ok || !$version_ok || !$milestone_ok) {
+            $vars{defaults} = {
+                # Note that because of the eval { set } above, these are
+                # already set correctly if they're valid, otherwise they're
+                # set to some invalid value which the template will ignore.
+                component => $self->component,
+                version   => $self->version,
+                milestone => $milestone_ok ? $self->target_milestone
+                                           : $product->default_milestone
+            };
+            $vars{components} = [map { $_->name } @{$product->components}];
+            $vars{milestones} = [map { $_->name } @{$product->milestones}];
+            $vars{versions}   = [map { $_->name } @{$product->versions}];
+        }
+
+        if (!$verified) {
+            $vars{verify_bug_groups} = 1;
+            my $dbh = Bugzilla->dbh;
+            my @idlist = ($self->id);
+            push(@idlist, map {$_->id} @{ $params->{other_bugs} })
+                if $params->{other_bugs};
+            # Get the ID of groups which are no longer valid in the new product.
+            my $gids = $dbh->selectcol_arrayref(
+                'SELECT bgm.group_id
+                   FROM bug_group_map AS bgm
+                  WHERE bgm.bug_id IN (' . join(',', ('?') x @idlist) . ')
+                    AND bgm.group_id NOT IN
+                        (SELECT gcm.group_id
+                           FROM group_control_map AS gcm
+                           WHERE gcm.product_id = ?
+                                 AND ( (gcm.membercontrol != ?
+                                        AND gcm.group_id IN ('
+                                        . Bugzilla->user->groups_as_string . '))
+                                       OR gcm.othercontrol != ?) )',
+                undef, (@idlist, $product->id, CONTROLMAPNA, CONTROLMAPNA));
+            $vars{'old_groups'} = Bugzilla::Group->new_from_list($gids);            
+        }
+        
+        if (%vars) {
+            $vars{product} = $product;
+            $vars{bug} = $self;
+            my $template = Bugzilla->template;
+            $template->process("bug/process/verify-new-product.html.tmpl",
+                \%vars) || ThrowTemplateError($template->error());
+            exit;
+        }
+    }
+    else {
+        # When we're not in the browser (or we didn't change the product), we
+        # just die if any of these are invalid.
+        $self->set_component($comp_name);
+        $self->set_version($vers_name);
+        if ($product_changed && !$self->check_can_change_field('target_milestone', 0, 1)) {
+            # Have to set this directly to bypass the validators.
+            $self->{target_milestone} = $product->default_milestone;
+        }
+        else {
+            $self->set_target_milestone($tm_name);
+        }
+    }
+    
+    if ($product_changed) {
+        # Remove groups that 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.
+        #
+        # 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})) {
+                $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)}) {
+            $self->add_group($group);
+        }
+    }
+    
+    # XXX This is temporary until all of process_bug uses update();
+    return $product_changed;
+}
+
+sub set_qa_contact {
+    my ($self, $value) = @_;
+    $self->set('qa_contact', $value);
+    # Store the old QA contact. check_can_change_field() needs it.
+    if ($self->{'qa_contact_obj'}) {
+        $self->{'_old_qa_contact'} = $self->{'qa_contact_obj'}->id;
+    }
+    delete $self->{'qa_contact_obj'};
+}
+sub reset_qa_contact {
+    my $self = shift;
+    if (Bugzilla->params->{'commentonreassignbycomponent'}
+        && !$self->{added_comments})
+    {
+        ThrowUserError('comment_required');
+    }
+    my $comp = $self->component_obj;
+    $self->set_qa_contact($comp->default_qa_contact);
+}
+sub set_remaining_time { $_[0]->set('remaining_time', $_[1]); }
+# Used only when closing a bug or moving between closed states.
+sub _zero_remaining_time { $_[0]->{'remaining_time'} = 0; }
+sub set_reporter_accessible { $_[0]->set('reporter_accessible', $_[1]); }
+sub set_resolution {
+    my ($self, $value, $params) = @_;
+    
+    my $old_res = $self->resolution;
+    $self->set('resolution', $value);
+    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();
+        }
+        # Duplicates should have no remaining time left.
+        elsif ($new_res eq 'DUPLICATE' && $self->remaining_time != 0) {
+            $self->_zero_remaining_time();
+        }
+    }
+    
+    # We don't check if we're entering or leaving the dup resolution here,
+    # because we could be moving from being a dup of one bug to being a dup
+    # of another, theoretically. Note that this code block will also run
+    # when going between different closed states.
+    if ($self->resolution eq 'DUPLICATE') {
+        if ($params->{dupe_of}) {
+            $self->set_dup_id($params->{dupe_of});
+        }
+        elsif (!$self->dup_id) {
+            ThrowUserError('dupe_id_required');
+        }
+    }
+}
+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 {
+    my ($self, $status, $params) = @_;
+    my $old_status = $self->status;
+    $self->set('bug_status', $status);
+    delete $self->{'status'};
+    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->clear_resolution();
+    }
+    else {
+        # We do this here so that we can make sure closed statuses have
+        # resolutions.
+        my $resolution = delete $params->{resolution} || $self->resolution;
+        $self->set_resolution($resolution, $params);
+
+        # Changing between closed statuses zeros the remaining time.
+        if ($new_status->id != $old_status->id && $self->remaining_time != 0) {
+            $self->_zero_remaining_time();
+        }
+    }
+}
+sub set_status_whiteboard { $_[0]->set('status_whiteboard', $_[1]); }
+sub set_summary           { $_[0]->set('short_desc',        $_[1]); }
+sub set_target_milestone  { $_[0]->set('target_milestone',  $_[1]); }
+sub set_url               { $_[0]->set('bug_file_loc',      $_[1]); }
+sub set_version           { $_[0]->set('version',           $_[1]); }
+
+########################
+# "Add/Remove" Methods #
+########################
+
+# These are in alphabetical order by field name.
+
+# Accepts a User object or a username. Adds the user only if they
+# don't already exist as a CC on the bug.
+sub add_cc {
+    my ($self, $user_or_name) = @_;
+    return if !$user_or_name;
+    my $user = ref $user_or_name ? $user_or_name
+                                 : Bugzilla::User->check($user_or_name);
+    $self->_check_strict_isolation_for_user($user);
+    my $cc_users = $self->cc_users;
+    push(@$cc_users, $user) if !grep($_->id == $user->id, @$cc_users);
+}
+
+# Accepts a User object or a username. Removes the User if they exist
+# in the list, but doesn't throw an error if they don't exist.
+sub remove_cc {
+    my ($self, $user_or_name) = @_;
+    my $user = ref $user_or_name ? $user_or_name
+                                 : Bugzilla::User->check($user_or_name);
+    my $cc_users = $self->cc_users;
+    @$cc_users = grep { $_->id != $user->id } @$cc_users;
+}
+
+# $bug->add_comment("comment", {isprivate => 1, work_time => 10.5,
+#                               type => CMT_NORMAL, extra_data => $data});
+sub add_comment {
+    my ($self, $comment, $params) = @_;
+
+    $comment = $self->_check_comment($comment);
+
+    $params ||= {};
+    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})) {
+        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};
+    }
+
+    push(@{$self->{added_comments}}, $add_comment);
+}
+
+# There was a lot of duplicate code when I wrote this as three separate
+# functions, so I just combined them all into one. This is also easier for
+# process_bug to use.
+sub modify_keywords {
+    my ($self, $keywords, $action) = @_;
+    
+    $action ||= "makeexact";
+    if (!grep($action eq $_, qw(add delete makeexact))) {
+        $action = "makeexact";
+    }
+    
+    $keywords = $self->_check_keywords($keywords);
+
+    my (@result, $any_changes);
+    if ($action eq 'makeexact') {
+        @result = @$keywords;
+        # Check if anything was added or removed.
+        my @old_ids = map { $_->id } @{$self->keyword_objects};
+        my @new_ids = map { $_->id } @result;
+        my ($removed, $added) = diff_arrays(\@old_ids, \@new_ids);
+        $any_changes = scalar @$removed || scalar @$added;
+    }
+    else {
+        # We're adding or deleting specific keywords.
+        my %keys = map {$_->id => $_} @{$self->keyword_objects};
+        if ($action eq 'add') {
+            $keys{$_->id} = $_ foreach @$keywords;
+        }
+        else {
+            delete $keys{$_->id} foreach @$keywords;
+        }
+        @result = values %keys;
+        $any_changes = scalar @$keywords;
+    }
+    # Make sure we retain the sort order.
+    @result = sort {lc($a->name) cmp lc($b->name)} @result;
+    
+    if ($any_changes) {
+        my $privs;
+        my $new = join(', ', (map {$_->name} @result));
+        my $check = $self->check_can_change_field('keywords', 0, 1, \$privs)
+            || ThrowUserError('illegal_change', { field    => 'keywords',
+                                                  oldvalue => $self->keywords,
+                                                  newvalue => $new,
+                                                  privs    => $privs });
+    }
+
+    $self->{'keyword_objects'} = \@result;
+    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;
+
+    # 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 });
+
+    # OtherControl people can add groups only during a product change,
+    # and only when the group is not NA for them.
+    if (!Bugzilla->user->in_group($group->name)) {
+        my $controls = $self->product_obj->group_controls->{$group->id};
+        if (!$self->{_old_product_name}
+            || $controls->{othercontrol} == CONTROLMAPNA)
+        {
+            ThrowUserError('group_change_denied',
+                           { bug => $self, group_id => $group->id });
+        }
+    }
+
+    my $current_groups = $self->groups_in;
+    if (!grep($group->id == $_->id, @$current_groups)) {
+        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})) {   
+        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 });
+        }
+
+        # OtherControl people can remove groups only during a product change,
+        # and only when they are non-Mandatory and non-NA.
+        if (!Bugzilla->user->in_group($group->name)) {
+            if (!$self->{_old_product_name}
+                || $controls->{othercontrol} == CONTROLMAPMANDATORY
+                || $controls->{othercontrol} == CONTROLMAPNA)
+            {
+                ThrowUserError('group_change_denied',
+                               { bug => $self, group_id => $group->id });
+            }
+        }
+    }
+    
+    my $current_groups = $self->groups_in;
+    @$current_groups = grep { $_->id != $group->id } @$current_groups;
+}
 
 #####################################################################
 # Instance Accessors
@@ -930,17 +2267,31 @@
     return $self->{'attachments'} if exists $self->{'attachments'};
     return [] if $self->{'error'};
 
-    $self->{'attachments'} =
-        Bugzilla::Attachment->get_attachments_by_bug($self->bug_id);
+    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];
     return $self->{'attachments'};
 }
 
 sub assigned_to {
     my ($self) = @_;
-    return $self->{'assigned_to'} if exists $self->{'assigned_to'};
-    $self->{'assigned_to_id'} = 0 if $self->{'error'};
-    $self->{'assigned_to'} = new Bugzilla::User($self->{'assigned_to_id'});
-    return $self->{'assigned_to'};
+    return $self->{'assigned_to_obj'} if exists $self->{'assigned_to_obj'};
+    $self->{'assigned_to'} = 0 if $self->{'error'};
+    $self->{'assigned_to_obj'} ||= new Bugzilla::User($self->{'assigned_to'});
+    return $self->{'assigned_to_obj'};
 }
 
 sub blocked {
@@ -972,6 +2323,19 @@
     return $self->{'cc'};
 }
 
+# XXX Eventually this will become the standard "cc" method used everywhere.
+sub cc_users {
+    my $self = shift;
+    return $self->{'cc_users'} if exists $self->{'cc_users'};
+    return [] if $self->{'error'};
+    
+    my $dbh = Bugzilla->dbh;
+    my $cc_ids = $dbh->selectcol_arrayref(
+        'SELECT who FROM cc WHERE bug_id = ?', undef, $self->id);
+    $self->{'cc_users'} = Bugzilla::User->new_from_list($cc_ids);
+    return $self->{'cc_users'};
+}
+
 sub component {
     my ($self) = @_;
     return $self->{component} if exists $self->{component};
@@ -982,6 +2346,15 @@
     return $self->{component};
 }
 
+# XXX Eventually this will replace component()
+sub component_obj {
+    my ($self) = @_;
+    return $self->{component_obj} if defined $self->{component_obj};
+    return {} if $self->{error};
+    $self->{component_obj} = new Bugzilla::Component($self->{component_id});
+    return $self->{component_obj};
+}
+
 sub classification_id {
     my ($self) = @_;
     return $self->{classification_id} if exists $self->{classification_id};
@@ -1023,34 +2396,50 @@
          'product_id'   => $self->{'product_id'}, 
          'component_id' => $self->{'component_id'} });
 
-    foreach my $flag_type (@$flag_types) {
-        $flag_type->{'flags'} = Bugzilla::Flag::match(
-            { 'bug_id'      => $self->bug_id,
-              'type_id'     => $flag_type->{'id'},
-              'target_type' => 'bug' });
-    }
+    $_->{'flags'} = [] foreach @$flag_types;
+    my %flagtypes = map { $_->id => $_ } @$flag_types;
 
-    $self->{'flag_types'} = $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];
 
     return $self->{'flag_types'};
 }
 
+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 $self->{'keywords'} if exists $self->{'keywords'};
-    return () if $self->{'error'};
+    return join(', ', (map { $_->name } @{$self->keyword_objects}));
+}
+
+# XXX At some point, this should probably replace the normal "keywords" sub.
+sub keyword_objects {
+    my $self = shift;
+    return $self->{'keyword_objects'} if defined $self->{'keyword_objects'};
+    return [] if $self->{'error'};
 
     my $dbh = Bugzilla->dbh;
-    my $list_ref = $dbh->selectcol_arrayref(
-         "SELECT keyworddefs.name
-            FROM keyworddefs, keywords
-           WHERE keywords.bug_id = ?
-             AND keyworddefs.id = keywords.keywordid
-        ORDER BY keyworddefs.name",
-        undef, ($self->bug_id));
-
-    $self->{'keywords'} = join(', ', @$list_ref);
-    return $self->{'keywords'};
+    my $ids = $dbh->selectcol_arrayref(
+         "SELECT keywordid FROM keywords WHERE bug_id = ?", undef, $self->id);
+    $self->{'keyword_objects'} = Bugzilla::Keyword->new_from_list($ids);
+    return $self->{'keyword_objects'};
 }
 
 sub longdescs {
@@ -1066,8 +2455,7 @@
     return $self->{'milestoneurl'} if exists $self->{'milestoneurl'};
     return '' if $self->{'error'};
 
-    $self->{'prod_obj'} ||= new Bugzilla::Product({name => $self->product});
-    $self->{'milestoneurl'} = $self->{'prod_obj'}->milestone_url;
+    $self->{'milestoneurl'} = $self->product_obj->milestone_url;
     return $self->{'milestoneurl'};
 }
 
@@ -1081,20 +2469,28 @@
     return $self->{product};
 }
 
+# XXX This should eventually replace the "product" subroutine.
+sub product_obj {
+    my $self = shift;
+    return {} if $self->{error};
+    $self->{product_obj} ||= new Bugzilla::Product($self->{product_id});
+    return $self->{product_obj};
+}
+
 sub qa_contact {
     my ($self) = @_;
-    return $self->{'qa_contact'} if exists $self->{'qa_contact'};
+    return $self->{'qa_contact_obj'} if exists $self->{'qa_contact_obj'};
     return undef if $self->{'error'};
 
-    if (Bugzilla->params->{'useqacontact'} && $self->{'qa_contact_id'}) {
-        $self->{'qa_contact'} = new Bugzilla::User($self->{'qa_contact_id'});
+    if (Bugzilla->params->{'useqacontact'} && $self->{'qa_contact'}) {
+        $self->{'qa_contact_obj'} = new Bugzilla::User($self->{'qa_contact'});
     } else {
         # XXX - This is somewhat inconsistent with the assignee/reporter 
         # methods, which will return an empty User if they get a 0. 
         # However, we're keeping it this way now, for backwards-compatibility.
-        $self->{'qa_contact'} = undef;
+        $self->{'qa_contact_obj'} = undef;
     }
-    return $self->{'qa_contact'};
+    return $self->{'qa_contact_obj'};
 }
 
 sub reporter {
@@ -1105,6 +2501,13 @@
     return $self->{'reporter'};
 }
 
+sub status {
+    my $self = shift;
+    return undef if $self->{'error'};
+
+    $self->{'status'} ||= new Bugzilla::Status({name => $self->{'bug_status'}});
+    return $self->{'status'};
+}
 
 sub show_attachment_flags {
     my ($self) = @_;
@@ -1120,7 +2523,7 @@
         { 'target_type'  => 'attachment',
           'product_id'   => $self->{'product_id'},
           'component_id' => $self->{'component_id'} });
-    my $num_attachment_flags = Bugzilla::Flag::count(
+    my $num_attachment_flags = Bugzilla::Flag->count(
         { 'target_type'  => 'attachment',
           'bug_id'       => $self->bug_id });
 
@@ -1134,10 +2537,8 @@
     my ($self) = @_;
     return 0 if $self->{'error'};
 
-    $self->{'prod_obj'} ||= new Bugzilla::Product({name => $self->product});
-
     return Bugzilla->params->{'usevotes'} 
-           && $self->{'prod_obj'}->votes_per_user > 0;
+           && $self->product_obj->votes_per_user > 0;
 }
 
 sub groups {
@@ -1206,6 +2607,17 @@
     return $self->{'groups'};
 }
 
+sub groups_in {
+    my $self = shift;
+    return $self->{'groups_in'} if exists $self->{'groups_in'};
+    return [] if $self->{'error'};
+    my $group_ids = Bugzilla->dbh->selectcol_arrayref(
+        'SELECT group_id FROM bug_group_map WHERE bug_id = ?',
+        undef, $self->id);
+    $self->{'groups_in'} = Bugzilla::Group->new_from_list($group_ids);
+    return $self->{'groups_in'};
+}
+
 sub user {
     my $self = shift;
     return $self->{'user'} if exists $self->{'user'};
@@ -1218,10 +2630,10 @@
 
     my $unknown_privileges = $user->in_group('editbugs', $prod_id);
     my $canedit = $unknown_privileges
-                  || $user->id == $self->{assigned_to_id}
+                  || $user->id == $self->{'assigned_to'}
                   || (Bugzilla->params->{'useqacontact'}
-                      && $self->{'qa_contact_id'}
-                      && $user->id == $self->{qa_contact_id});
+                      && $self->{'qa_contact'}
+                      && $user->id == $self->{'qa_contact'});
     my $canconfirm = $unknown_privileges
                      || $user->in_group('canconfirm', $prod_id);
     my $isreporter = $user->id
@@ -1240,7 +2652,6 @@
     return {} if $self->{'error'};
 
     $self->{'choices'} = {};
-    $self->{prod_obj} ||= new Bugzilla::Product({name => $self->product});
 
     my @prodlist = map {$_->name} @{Bugzilla->user->get_enterable_products};
     # The current product is part of the popup, even if new bugs are no longer
@@ -1251,7 +2662,7 @@
     }
 
     # Hack - this array contains "". See bug 106589.
-    my @res = grep ($_, @{settable_resolutions()});
+    my @res = grep ($_, @{get_legal_field_values('resolution')});
 
     $self->{'choices'} =
       {
@@ -1262,30 +2673,14 @@
        'op_sys'       => get_legal_field_values('op_sys'),
        'bug_status'   => get_legal_field_values('bug_status'),
        'resolution'   => \@res,
-       'component'    => [map($_->name, @{$self->{prod_obj}->components})],
-       'version'      => [map($_->name, @{$self->{prod_obj}->versions})],
-       'target_milestone' => [map($_->name, @{$self->{prod_obj}->milestones})],
+       'component'    => [map($_->name, @{$self->product_obj->components})],
+       'version'      => [map($_->name, @{$self->product_obj->versions})],
+       'target_milestone' => [map($_->name, @{$self->product_obj->milestones})],
       };
 
     return $self->{'choices'};
 }
 
-# List of resolutions that may be set directly by hand in the bug form.
-# 'MOVED' and 'DUPLICATE' are excluded from the list because setting
-# bugs to those resolutions requires a special process.
-sub settable_resolutions {
-    my $resolutions = get_legal_field_values('resolution');
-    my $pos = lsearch($resolutions, 'DUPLICATE');
-    if ($pos >= 0) {
-        splice(@$resolutions, $pos, 1);
-    }
-    $pos = lsearch($resolutions, 'MOVED');
-    if ($pos >= 0) {
-        splice(@$resolutions, $pos, 1);
-    }
-    return $resolutions;
-}
-
 sub votes {
     my ($self) = @_;
     return 0 if $self->{error};
@@ -1318,43 +2713,6 @@
 # Subroutines
 #####################################################################
 
-sub AppendComment {
-    my ($bugid, $whoid, $comment, $isprivate, $timestamp, $work_time,
-        $type, $extra_data) = @_;
-    $work_time ||= 0;
-    $type ||= CMT_NORMAL;
-    my $dbh = Bugzilla->dbh;
-
-    ValidateTime($work_time, "work_time") if $work_time;
-    trick_taint($work_time);
-    detaint_natural($type)
-      || ThrowCodeError('bad_arg', {argument => 'type', function => 'AppendComment'});
-
-    # 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()');
-
-    $comment =~ s/\r\n/\n/g;     # Handle Windows-style line endings.
-    $comment =~ s/\r/\n/g;       # Handle Mac-style line endings.
-
-    if ($comment =~ /^\s*$/ && !$type) {  # Nothin' but whitespace
-        return;
-    }
-
-    # Comments are always safe, because we always display their raw contents,
-    # and we use them in a placeholder below.
-    trick_taint($comment); 
-    my $privacyval = $isprivate ? 1 : 0 ;
-    $dbh->do(q{INSERT INTO longdescs
-                      (bug_id, who, bug_when, thetext, isprivate, work_time,
-                       type, extra_data)
-               VALUES (?, ?, ?, ?, ?, ?, ?, ?)}, undef,
-             ($bugid, $whoid, $timestamp, $comment, $privacyval, $work_time,
-              $type, $extra_data));
-    $dbh->do("UPDATE bugs SET delta_ts = ? WHERE bug_id = ?",
-             undef, $timestamp, $bugid);
-}
-
 sub update_comment {
     my ($self, $comment_id, $new_comment) = @_;
 
@@ -1377,9 +2735,10 @@
     $new_comment =~ s/\r\n?/\n/g; # Handle Windows and Mac-style line endings.
     trick_taint($new_comment);
 
-    # We assume ValidateComment() has already been called earlier.
+    # 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;
@@ -1393,7 +2752,8 @@
     @obsolete_fields = map { $_->name } @obsolete_fields;
     foreach my $remove ("bug_id", "reporter", "creation_ts", "delta_ts", "lastdiffed", @obsolete_fields) {
         my $location = lsearch(\@fields, $remove);
-        splice(@fields, $location, 1);
+        # Custom multi-select fields are not stored in the bugs table.
+        splice(@fields, $location, 1) if ($location > -1);
     }
     # Sorted because the old @::log_columns variable, which this replaces,
     # was sorted.
@@ -1412,12 +2772,6 @@
     return $list_ref;
 }
 
-# Tells you whether or not the argument is a valid "open" state.
-sub is_open_state {
-    my ($state) = @_;
-    return (grep($_ eq $state, BUG_STATE_OPEN) ? 1 : 0);
-}
-
 sub ValidateTime {
     my ($time, $field) = @_;
 
@@ -1453,8 +2807,7 @@
     my @comments;
     my @args = ($id);
 
-    my $query = 'SELECT longdescs.comment_id AS id, profiles.realname AS name,
-                        profiles.login_name AS email, ' .
+    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
@@ -1473,9 +2826,7 @@
 
     while (my $comment_ref = $sth->fetchrow_hashref()) {
         my %comment = %$comment_ref;
-
-        $comment{'email'} .= Bugzilla->params->{'emailsuffix'};
-        $comment{'name'} = $comment{'name'} || $comment{'email'};
+        $comment{'author'} = new Bugzilla::User($comment{'userid'});
 
         # If raw data is requested, do not format 'special' comments.
         $comment{'body'} = format_comment(\%comment) unless $raw;
@@ -1519,11 +2870,11 @@
 # Get the activity of a bug, starting from $starttime (if given).
 # This routine assumes ValidateBugID has been previously called.
 sub GetBugActivity {
-    my ($id, $starttime) = @_;
+    my ($bug_id, $attach_id, $starttime) = @_;
     my $dbh = Bugzilla->dbh;
 
     # Arguments passed to the SQL query.
-    my @args = ($id);
+    my @args = ($bug_id);
 
     # Only consider changes since $starttime, if given.
     my $datepart = "";
@@ -1533,6 +2884,12 @@
         $datepart = "AND bugs_activity.bug_when > ?";
     }
 
+    my $attachpart = "";
+    if ($attach_id) {
+        push(@args, $attach_id);
+        $attachpart = "AND bugs_activity.attach_id = ?";
+    }
+
     # Only includes attachments the user is allowed to see.
     my $suppjoins = "";
     my $suppwhere = "";
@@ -1562,6 +2919,7 @@
             ON profiles.userid = bugs_activity.who
          WHERE bugs_activity.bug_id = ?
                $datepart
+               $attachpart
                $suppwhere
       ORDER BY bugs_activity.bug_when";
 
@@ -1591,7 +2949,7 @@
 
         if ($activity_visible) {
             # This gets replaced with a hyperlink in the template.
-            $field =~ s/^Attachment// if $attachid;
+            $field =~ s/^Attachment\s*// if $attachid;
 
             # Check for the results of an old Bugzilla data corruption bug
             $incomplete_data = 1 if ($added =~ /^\?/ || $removed =~ /^\?/);
@@ -1680,9 +3038,9 @@
     my $sth = $dbh->prepare(
           "SELECT blocked, COUNT(bug_status) " .
             "FROM bugs, dependencies " .
-           "WHERE blocked IN (" . (join "," , @bug_list) . ") " .
+           "WHERE " . $dbh->sql_in('blocked', \@bug_list) .
              "AND bug_id = dependson " .
-             "AND bug_status IN ('" . (join "','", BUG_STATE_OPEN)  . "') " .
+             "AND bug_status IN (" . join(', ', map {$dbh->quote($_)} BUG_STATE_OPEN)  . ") " .
           $dbh->sql_group_by('blocked'));
     $sth->execute();
 
@@ -1694,14 +3052,6 @@
     return @dependencies;
 }
 
-sub ValidateComment {
-    my ($comment) = @_;
-
-    if (defined($comment) && length($comment) > MAX_COMMENT_LENGTH) {
-        ThrowUserError("comment_too_long");
-    }
-}
-
 # 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 {
@@ -1731,7 +3081,6 @@
     if (scalar(@list)) {
         foreach my $ref (@list) {
             my ($name, $userid, $oldvotes, $votesperuser, $maxvotesperbug) = (@$ref);
-            my $s;
 
             $maxvotesperbug = min($votesperuser, $maxvotesperbug);
 
@@ -1745,23 +3094,13 @@
 
             my $removedvotes = $oldvotes - $newvotes;
 
-            $s = ($oldvotes == 1) ? "" : "s";
-            my $oldvotestext = "You had $oldvotes vote$s on this bug.";
-
-            $s = ($removedvotes == 1) ? "" : "s";
-            my $removedvotestext = "You had $removedvotes vote$s removed from this bug.";
-
-            my $newvotestext;
             if ($newvotes) {
                 $dbh->do("UPDATE votes SET vote_count = ? " .
                          "WHERE bug_id = ? AND who = ?",
                          undef, ($newvotes, $id, $userid));
-                $s = $newvotes == 1 ? "" : "s";
-                $newvotestext = "You still have $newvotes vote$s on this bug."
             } else {
                 $dbh->do("DELETE FROM votes WHERE bug_id = ? AND who = ?",
                          undef, ($id, $userid));
-                $newvotestext = "You have no more votes remaining on this bug.";
             }
 
             # Notice that we did not make sure that the user fit within the $votesperuser
@@ -1772,7 +3111,6 @@
             # 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,
@@ -1780,19 +3118,17 @@
                 'votesremoved' => $removedvotes,
                 'votesold' => $oldvotes,
                 'votesnew' => $newvotes,
-
-                'votesremovedtext' => $removedvotestext,
-                'votesoldtext' => $oldvotestext,
-                'votesnewtext' => $newvotestext,
-
-                'count' => $removedvotes . "\n    " . $newvotestext
             };
 
+            my $voter = new Bugzilla::User($userid);
+            my $template = Bugzilla->template_inner($voter->settings->{'lang'}->{'value'});
+
             my $msg;
-            my $template = Bugzilla->template;
             $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;
@@ -1809,6 +3145,9 @@
     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() " .
@@ -1819,6 +3158,9 @@
 
     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, " .
@@ -1840,8 +3182,6 @@
                  "VALUES (?, ?, ?, ?, ?, ?)",
                  undef, ($id, $who, $timestamp, $fieldid, '0', '1'));
 
-        AppendComment($id, $who, "", 0, $timestamp, 0, CMT_POPULAR_VOTES);
-
         $ret = 1;
     }
     return $ret;
@@ -1862,11 +3202,10 @@
 # $oldvalue - what they are changing it from
 # $newvalue - what they are changing it to
 # $PrivilegesRequired - return the reason of the failure, if any
-# $data     - hash containing relevant parameters, e.g. from the CGI object
 ################################################################################
 sub check_can_change_field {
     my $self = shift;
-    my ($field, $oldvalue, $newvalue, $PrivilegesRequired, $data) = (@_);
+    my ($field, $oldvalue, $newvalue, $PrivilegesRequired) = (@_);
     my $user = Bugzilla->user;
 
     $oldvalue = defined($oldvalue) ? $oldvalue : '';
@@ -1875,11 +3214,13 @@
     # Return true if they haven't changed this field at all.
     if ($oldvalue eq $newvalue) {
         return 1;
+    } elsif (ref($newvalue) eq 'ARRAY' && ref($oldvalue) eq 'ARRAY') {
+        my ($removed, $added) = diff_arrays($oldvalue, $newvalue);
+        return 1 if !scalar(@$removed) && !scalar(@$added);
     } elsif (trim($oldvalue) eq trim($newvalue)) {
         return 1;
     # numeric fields need to be compared using ==
     } elsif (($field eq 'estimated_time' || $field eq 'remaining_time')
-             && $newvalue ne $data->{'dontchange'}
              && $oldvalue == $newvalue)
     {
         return 1;
@@ -1890,14 +3231,6 @@
         return 1;
     }
 
-    # Ignore the assigned_to field if the bug is not being reassigned
-    if ($field eq 'assigned_to'
-        && $data->{'knob'} ne 'reassignbycomponent'
-        && $data->{'knob'} ne 'reassign')
-    {
-        return 1;
-    }
-
     # If the user isn't allowed to change a field, we must tell him who can.
     # We store the required permission set into the $PrivilegesRequired
     # variable which gets passed to the error template.
@@ -1906,6 +3239,15 @@
     # $PrivilegesRequired = 1 : the reporter, assignee or an empowered user;
     # $PrivilegesRequired = 2 : the assignee or an empowered user;
     # $PrivilegesRequired = 3 : 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;
+            return 0;
+        }
+    }
 
     # Allow anyone with (product-specific) "editbugs" privs to change anything.
     if ($user->in_group('editbugs', $self->{'product_id'})) {
@@ -1925,14 +3267,16 @@
     # Make sure that a valid bug ID has been given.
     if (!$self->{'error'}) {
         # Allow the assignee to change anything else.
-        if ($self->{'assigned_to_id'} == $user->id) {
+        if ($self->{'assigned_to'} == $user->id
+            || $self->{'_old_assigned_to'} && $self->{'_old_assigned_to'} == $user->id)
+        {
             return 1;
         }
 
         # Allow the QA contact to change anything else.
         if (Bugzilla->params->{'useqacontact'}
-            && $self->{'qa_contact_id'}
-            && ($self->{'qa_contact_id'} == $user->id))
+            && (($self->{'qa_contact'} && $self->{'qa_contact'} == $user->id)
+                || ($self->{'_old_qa_contact'} && $self->{'_old_qa_contact'} == $user->id)))
         {
             return 1;
         }
@@ -1992,6 +3336,8 @@
     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
@@ -2001,7 +3347,7 @@
     my $alias = $id;
     if (!detaint_natural($id)) {
         $id = bug_alias_to_id($alias);
-        $id || ThrowUserError("invalid_bug_id_or_alias",
+        $id || ThrowUserError("improper_bug_id_field_value",
                               {'bug_id' => $alias,
                                'field'  => $field });
     }
@@ -2012,14 +3358,19 @@
     
     # First check that the bug exists
     $dbh->selectrow_array("SELECT bug_id FROM bugs WHERE bug_id = ?", undef, $id)
-      || ThrowUserError("invalid_bug_id_non_existent", {'bug_id' => $id});
+      || ThrowUserError("bug_id_does_not_exist", {'bug_id' => $id});
 
-    return if (defined $field && ($field eq "dependson" || $field eq "blocked"));
-    
+    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 user did not pass any of the authorization tests, which means they
-    # are not authorized to see the bug.  Display an error and stop execution.
     # 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) {
@@ -2029,53 +3380,6 @@
     }
 }
 
-# ValidateBugAlias:
-#   Check that the bug alias is valid and not used by another bug.  If 
-#   curr_id is specified, verify the alias is not used for any other
-#   bug id.  
-sub ValidateBugAlias {
-    my ($alias, $curr_id) = @_;
-    my $dbh = Bugzilla->dbh;
-
-    $alias = trim($alias || "");
-    trick_taint($alias);
-
-    if ($alias eq "") {
-        ThrowUserError("alias_not_defined");
-    }
-
-    # Make sure the alias isn't too long.
-    if (length($alias) > 20) {
-        ThrowUserError("alias_too_long");
-    }
-
-    # Make sure the alias is unique.
-    my $query = "SELECT bug_id FROM bugs WHERE alias = ?";
-    if ($curr_id && detaint_natural($curr_id)) {
-        $query .= " AND bug_id != $curr_id";
-    }
-    my $id = $dbh->selectrow_array($query, undef, $alias); 
-
-    my $vars = {};
-    $vars->{'alias'} = $alias;
-    if ($id) {
-        $vars->{'bug_id'} = $id;
-        ThrowUserError("alias_in_use", $vars);
-    }
-
-    # Make sure the alias isn't just a number.
-    if ($alias =~ /^\d+$/) {
-        ThrowUserError("alias_is_numeric", $vars);
-    }
-
-    # Make sure the alias has no commas or spaces.
-    if ($alias =~ /[, ]/) {
-        ThrowUserError("alias_has_comma_or_space", $vars);
-    }
-
-    $_[0] = $alias;
-}
-
 # Validate and return a hash of dependencies
 sub ValidateDependencies {
     my $fields = {};
@@ -2192,11 +3496,19 @@
   no strict 'refs';
   *$AUTOLOAD = sub {
       my $self = shift;
-      if (defined $self->{$attr}) {
+
+      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};
-      } else {
-          return '';
       }
+
+      return '';
   };
 
   goto &$AUTOLOAD;
diff --git a/BugsSite/Bugzilla/BugMail.pm b/BugsSite/Bugzilla/BugMail.pm
index 9a68b1a..46f5597 100644
--- a/BugsSite/Bugzilla/BugMail.pm
+++ b/BugsSite/Bugzilla/BugMail.pm
@@ -37,13 +37,20 @@
 use Bugzilla::Constants;
 use Bugzilla::Util;
 use Bugzilla::Bug;
+use Bugzilla::Classification;
 use Bugzilla::Product;
 use Bugzilla::Component;
+use Bugzilla::Status;
 use Bugzilla::Mailer;
 
 use Date::Parse;
 use Date::Format;
 
+use constant FORMAT_TRIPLE => "%19s|%-28s|%-28s";
+use constant FORMAT_3_SIZE => [19,28,28];
+use constant FORMAT_DOUBLE => "%19s %-55s";
+use constant FORMAT_2_SIZE => [19,55];
+
 use constant BIT_DIRECT    => 1;
 use constant BIT_WATCHING  => 2;
 
@@ -58,25 +65,38 @@
     REL_GLOBAL_WATCHER, "GlobalWatcher"
 };
 
-sub FormatTriple {
-    my ($a, $b, $c) = (@_);
-    $^A = "";
-    my $temp = formline << 'END', $a, $b, $c;
-^>>>>>>>>>>>>>>>>>>|^<<<<<<<<<<<<<<<<<<<<<<<<<<<|^<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
-END
-    ; # This semicolon appeases my emacs editor macros. :-)
-    return $^A;
+# 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 FormatDouble {
-    my ($a, $b) = (@_);
-    $a .= ":";
-    $^A = "";
-    my $temp = formline << 'END', $a, $b;
-^>>>>>>>>>>>>>>>>>> ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
-END
-    ; # This semicolon appeases my emacs editor macros. :-)
-    return $^A;
+
+sub three_columns {
+    return multiline_sprintf(FORMAT_TRIPLE, \@_, FORMAT_3_SIZE);
 }
 
 # This is a bit of a hack, basically keeping the old system()
@@ -111,16 +131,19 @@
 
     my %values = %{$dbh->selectrow_hashref(
         'SELECT ' . join(',', editable_bug_fields()) . ', reporter,
-                lastdiffed AS start, LOCALTIMESTAMP(0) AS end
+                lastdiffed AS start_time, LOCALTIMESTAMP(0) AS end_time
            FROM bugs WHERE bug_id = ?',
         undef, $id)};
 
     my $product = new Bugzilla::Product($values{product_id});
     $values{product} = $product->name;
+    if (Bugzilla->params->{'useclassification'}) {
+        $values{classification} = Bugzilla::Classification->new($product->classification_id)->name;
+    }
     my $component = new Bugzilla::Component($values{component_id});
     $values{component} = $component->name;
 
-    my ($start, $end) = ($values{start}, $values{end});
+    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.
@@ -227,10 +250,9 @@
         my $diffpart = {};
         if ($who ne $lastwho) {
             $lastwho = $who;
-            $fullwho = $whoname ? "$whoname <$who" . Bugzilla->params->{'emailsuffix'} . ">" :
-                                  "$who" . Bugzilla->params->{'emailsuffix'};
+            $fullwho = $whoname ? "$whoname <$who>" : $who;
             $diffheader = "\n$fullwho changed:\n\n";
-            $diffheader .= FormatTriple("What    ", "Removed", "Added");
+            $diffheader .= three_columns("What    ", "Removed", "Added");
             $diffheader .= ('-' x 76) . "\n";
         }
         $what =~ s/^(Attachment )?/Attachment #$attachid / if $attachid;
@@ -247,7 +269,7 @@
                 'SELECT isprivate FROM attachments WHERE attach_id = ?',
                 undef, ($attachid));
         }
-        $difftext = FormatTriple($what, $old, $new);
+        $difftext = three_columns($what, $old, $new);
         $diffpart->{'header'} = $diffheader;
         $diffpart->{'fieldname'} = $fieldname;
         $diffpart->{'text'} = $difftext;
@@ -301,13 +323,13 @@
                   "\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 .= FormatTriple("What    ", "Old Value", "New Value");
+                $thisdiff .= three_columns("What    ", "Old Value", "New Value");
                 $thisdiff .= ('-' x 76) . "\n";
                 $interestingchange = 0;
             }
-            $thisdiff .= FormatTriple($fielddescription{$what}, $old, $new);
+            $thisdiff .= three_columns($fielddescription{$what}, $old, $new);
             if ($what eq 'bug_status'
-                && Bugzilla::Bug::is_open_state($old) ne Bugzilla::Bug::is_open_state($new))
+                && is_open_state($old) ne is_open_state($new))
             {
                 $interestingchange = 1;
             }
@@ -499,7 +521,7 @@
                                       \@diffparts,
                                       $comments{$lang},
                                       $anyprivate, 
-                                      $start, 
+                                      ! $start, 
                                       $id,
                                       exists $watching{$user_id} ?
                                              $watching{$user_id} : undef);
@@ -521,8 +543,8 @@
 }
 
 sub sendMail {
-    my ($user, $hlRef, $relRef, $valueRef, $dmhRef, $fdRef,  
-        $diffRef, $newcomments, $anyprivate, $start, 
+    my ($user, $hlRef, $relRef, $valueRef, $dmhRef, $fdRef,
+        $diffRef, $newcomments, $anyprivate, $isnew,
         $id, $watchingRef) = @_;
 
     my %values = %$valueRef;
@@ -530,25 +552,7 @@
     my %mailhead = %$dmhRef;
     my %fielddescription = %$fdRef;
     my @diffparts = @$diffRef;    
-    my $head = "";
     
-    foreach my $f (@headerlist) {
-      if ($mailhead{$f}) {
-        my $value = $values{$f};
-        # If there isn't anything to show, don't include this header
-        if (! $value) {
-          next;
-        }
-        # Only send estimated_time if it is enabled and the user is in the group
-        if (($f ne 'estimated_time' && $f ne 'deadline') ||
-             $user->groups->{Bugzilla->params->{'timetrackinggroup'}}) {
-
-            my $desc = $fielddescription{$f};
-            $head .= FormatDouble($desc, $value);
-        }
-      }
-    }
-
     # Build difftext (the actions) by verifying the user should see them
     my $difftext = "";
     my $diffheader = "";
@@ -584,13 +588,11 @@
         }
     }
  
-    if ($difftext eq "" && $newcomments eq "") {
+    if ($difftext eq "" && $newcomments eq "" && !$isnew) {
       # Whoops, no differences!
       return 0;
     }
     
-    my $isnew = !$start;
-    
     # If an attachment was created, then add an URL. (Note: the 'g'lobal
     # replace should work with comments with multiple attachments.)
 
@@ -604,6 +606,21 @@
 
     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;
     }
 
@@ -619,24 +636,14 @@
     push(@watchingrel, 'None') unless @watchingrel;
     push @watchingrel, map { user_id_to_login($_) } @$watchingRef;
 
-    my $sitespec = '@' . Bugzilla->params->{'urlbase'};
-    $sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain
-    $sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate
-    if ($2) {
-        $sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
-    }
-    my $threadingmarker;
-    if ($isnew) {
-        $threadingmarker = "Message-ID: <bug-$id-" . $user->id . "$sitespec>";
-    } else {
-        $threadingmarker = "In-Reply-To: <bug-$id-" . $user->id . "$sitespec>";
-    }
-    
+    my $threadingmarker = build_thread_marker($id, $user->id, $isnew);
 
     my $vars = {
-        neworchanged => $isnew ? 'New: ' : '',
+        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'},
@@ -644,6 +651,7 @@
         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'},
@@ -653,6 +661,8 @@
         reasonswatchheader => join(" ", @watchingrel),
         changer => $values{'changer'},
         changername => $values{'changername'},
+        reporter => $values{'reporter'},
+        reportername => Bugzilla::User->new({name => $values{'reporter'}})->name,
         diffs => $diffs,
         threadingmarker => $threadingmarker
     };
@@ -703,13 +713,8 @@
     my $result = "";
     foreach my $comment (@$raw_comments) {
         if ($count) {
-            $result .= "\n\n--- Comment #$count from ";
-            if ($comment->{'name'} eq $comment->{'email'}) {
-                $result .= $comment->{'email'};
-            } else {
-                $result .= $comment->{'name'} . " <" . $comment->{'email'} . ">";
-            }
-            $result .= "  " . format_time($comment->{'time'}) . " ---\n";
+            $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()
diff --git a/BugsSite/Bugzilla/CGI.pm b/BugsSite/Bugzilla/CGI.pm
index 7b76f6a..1799786 100644
--- a/BugsSite/Bugzilla/CGI.pm
+++ b/BugsSite/Bugzilla/CGI.pm
@@ -45,7 +45,7 @@
 $| = 1;
 
 # Ignore SIGTERM and SIGPIPE - this prevents DB corruption. If the user closes
-# their browser window while a script is running, the webserver sends these
+# 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';
@@ -54,7 +54,10 @@
 # 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 {};
+sub DESTROY {
+    my $self = shift;
+    $self->SUPER::DESTROY(@_);
+};
 
 sub new {
     my ($invocant, @args) = @_;
@@ -62,25 +65,22 @@
 
     my $self = $class->SUPER::new(@args);
 
-    if (Bugzilla->error_mode eq ERROR_MODE_WEBPAGE) {
-        # This happens here so that command-line scripts don't spit out
-        # their errors in HTML format.
-        require CGI::Carp;
-        import CGI::Carp qw(fatalsToBrowser);
-    }
-
     # Make sure our outgoing cookie list is empty on each invocation
     $self->{Bugzilla_cookie_list} = [];
 
     # Send appropriate charset
     $self->charset(Bugzilla->params->{'utf8'} ? 'UTF-8' : '');
 
-    # Redirect to SSL if required
-    if (Bugzilla->params->{'sslbase'} ne ''
-        && Bugzilla->params->{'ssl'} eq 'always'
-        && i_am_cgi())
-    {
-        $self->require_https(Bugzilla->params->{'sslbase'});
+    # 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;
+        }
     }
 
     # Check for errors
@@ -194,13 +194,13 @@
     
     my %args = @_;
 
-    # CGI.pm::multipart_start doesn't accept a -charset parameter, so
+    # CGI.pm::multipart_start doesn't honour its own charset information, so
     # we do it ourselves here
-    if (defined $args{-charset} && defined $args{-type}) {
+    if (defined $self->charset() && defined $args{-type}) {
         # Remove any existing charset specifier
         $args{-type} =~ s/;.*$//;
         # and add the specified one
-        $args{-type} .= "; charset=$args{-charset}";
+        $args{-type} .= '; charset=' . $self->charset();
     }
         
     my $headers = $self->SUPER::multipart_start(%args);
@@ -233,6 +233,27 @@
     return $self->SUPER::header(@_) || "";
 }
 
+# CGI.pm is not utf8-aware and passes data as bytes instead of UTF-8 strings.
+sub param {
+    my $self = shift;
+    if (Bugzilla->params->{'utf8'} && scalar(@_) == 1) {
+        if (wantarray) {
+            return map { _fix_utf8($_) } $self->SUPER::param(@_);
+        }
+        else {
+            return _fix_utf8(scalar $self->SUPER::param(@_));
+        }
+    }
+    return $self->SUPER::param(@_);
+}
+
+sub _fix_utf8 {
+    my $input = shift;
+    # The is_utf8 is here in case CGI gets smart about utf8 someday.
+    utf8::decode($input) if defined $input && !utf8::is_utf8($input);
+    return $input;
+}
+
 # The various parts of Bugzilla which create cookies don't want to have to
 # pass them around to all of the callers. Instead, store them locally here,
 # and then output as required from |header|.
@@ -280,18 +301,31 @@
 
 # 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;
+}
+
+# Redirect to the urlbase version of the current URL.
+sub redirect_to_urlbase {
     my $self = shift;
-    if ($self->protocol ne 'https') {
-        my $url = shift;
-        if (defined $url) {
-            $url .= $self->url('-path_info' => 1, '-query' => 1, '-relative' => 1);
-        } else {
-            $url = $self->self_url;
-            $url =~ s/^http:/https:/i;
-        }
-        print $self->redirect(-location => $url);
-        exit;
-    }
+    my $path = $self->url('-path_info' => 1, '-query' => 1, '-relative' => 1);
+    print $self->redirect('-location' => correct_urlbase() . $path);
+    exit;
 }
 
 1;
@@ -358,12 +392,16 @@
 
 =item C<require_https($baseurl)>
 
-This routine checks if the current page is being served over https, and
-redirects to the https protocol if required, retaining QUERY_STRING.
+This routine redirects the client to a different location using the https protocol. 
+If the client is using XMLRPC, it will not retain the QUERY_STRING since XMLRPC uses POST.
 
-It takes an option argument which will be used as the base URL.  If $baseurl
+It takes an optional argument which will be used as the base URL.  If $baseurl
 is not provided, the current URL is used.
 
+=item C<redirect_to_urlbase>
+
+Redirects from the current URL to one prefixed by the urlbase parameter.
+
 =back
 
 =head1 SEE ALSO
diff --git a/BugsSite/Bugzilla/Chart.pm b/BugsSite/Bugzilla/Chart.pm
index 9701f7b..a119e4b 100644
--- a/BugsSite/Bugzilla/Chart.pm
+++ b/BugsSite/Bugzilla/Chart.pm
@@ -22,7 +22,6 @@
 #                 A. Karl Kornel <karl@kornel.name>
 
 use strict;
-use lib ".";
 
 # This module represents a chart.
 #
diff --git a/BugsSite/Bugzilla/Component.pm b/BugsSite/Bugzilla/Component.pm
index a615e7a..f5719e8 100644
--- a/BugsSite/Bugzilla/Component.pm
+++ b/BugsSite/Bugzilla/Component.pm
@@ -23,10 +23,12 @@
 
 use base qw(Bugzilla::Object);
 
+use Bugzilla::Constants;
 use Bugzilla::Util;
 use Bugzilla::Error;
 use Bugzilla::User;
 use Bugzilla::FlagType;
+use Bugzilla::Series;
 
 ###############################
 ####    Initialization     ####
@@ -43,8 +45,32 @@
     description
 );
 
-###############################
-####       Methods         ####
+use constant REQUIRED_CREATE_FIELDS => qw(
+    name
+    product
+    initialowner
+    description
+);
+
+use constant UPDATE_COLUMNS => qw(
+    name
+    initialowner
+    initialqacontact
+    description
+);
+
+use constant VALIDATORS => {
+    product          => \&_check_product,
+    initialowner     => \&_check_initialowner,
+    initialqacontact => \&_check_initialqacontact,
+    description      => \&_check_description,
+    initial_cc       => \&_check_cc_list,
+};
+
+use constant UPDATE_VALIDATORS => {
+    name => \&_check_name,
+};
+
 ###############################
 
 sub new {
@@ -79,6 +105,225 @@
     return $component;
 }
 
+sub create {
+    my $class = shift;
+    my $dbh = Bugzilla->dbh;
+
+    $dbh->bz_start_transaction();
+
+    $class->check_required_create_fields(@_);
+    my $params = $class->run_create_validators(@_);
+    my $cc_list = delete $params->{initial_cc};
+
+    my $component = $class->insert_create_data($params);
+
+    # We still have to fill the component_cc table.
+    $component->_update_cc_list($cc_list);
+
+    # Create series for the new component.
+    $component->_create_series();
+
+    $dbh->bz_commit_transaction();
+    return $component;
+}
+
+sub run_create_validators {
+    my $class  = shift;
+    my $params = $class->SUPER::run_create_validators(@_);
+
+    my $product = delete $params->{product};
+    $params->{product_id} = $product->id;
+    $params->{name} = $class->_check_name($params->{name}, $product);
+
+    return $params;
+}
+
+sub update {
+    my $self = shift;
+    my $changes = $self->SUPER::update(@_);
+
+    # Update the component_cc table if necessary.
+    if (defined $self->{cc_ids}) {
+        my $diff = $self->_update_cc_list($self->{cc_ids});
+        $changes->{cc_list} = $diff if defined $diff;
+    }
+    return $changes;
+}
+
+sub remove_from_db {
+    my $self = shift;
+    my $dbh = Bugzilla->dbh;
+
+    $dbh->bz_start_transaction();
+
+    if ($self->bug_count) {
+        if (Bugzilla->params->{'allowbugdeletion'}) {
+            require Bugzilla::Bug;
+            foreach my $bug_id (@{$self->bug_ids}) {
+                # Note: We allow admins to delete bugs even if they can't
+                # see them, as long as they can see the product.
+                my $bug = new Bugzilla::Bug($bug_id);
+                $bug->remove_from_db();
+            }
+        } else {
+            ThrowUserError('component_has_bugs', {nb => $self->bug_count});
+        }
+    }
+
+    $dbh->do('DELETE FROM flaginclusions WHERE component_id = ?',
+             undef, $self->id);
+    $dbh->do('DELETE FROM flagexclusions WHERE component_id = ?',
+             undef, $self->id);
+    $dbh->do('DELETE FROM component_cc WHERE component_id = ?',
+             undef, $self->id);
+    $dbh->do('DELETE FROM components WHERE id = ?', undef, $self->id);
+
+    $dbh->bz_commit_transaction();
+}
+
+################################
+# Validators
+################################
+
+sub _check_name {
+    my ($invocant, $name, $product) = @_;
+
+    $name = trim($name);
+    $name || ThrowUserError('component_blank_name');
+
+    if (length($name) > MAX_COMPONENT_SIZE) {
+        ThrowUserError('component_name_too_long', {'name' => $name});
+    }
+
+    $product = $invocant->product if (ref $invocant);
+    my $component = new Bugzilla::Component({product => $product, name => $name});
+    if ($component && (!ref $invocant || $component->id != $invocant->id)) {
+        ThrowUserError('component_already_exists', { name    => $component->name,
+                                                     product => $product });
+    }
+    return $name;
+}
+
+sub _check_description {
+    my ($invocant, $description) = @_;
+
+    $description = trim($description);
+    $description || ThrowUserError('component_blank_description');
+    return $description;
+}
+
+sub _check_initialowner {
+    my ($invocant, $owner) = @_;
+
+    $owner || ThrowUserError('component_need_initialowner');
+    my $owner_id = Bugzilla::User->check($owner)->id;
+    return $owner_id;
+}
+
+sub _check_initialqacontact {
+    my ($invocant, $qa_contact) = @_;
+
+    my $qa_contact_id;
+    if (Bugzilla->params->{'useqacontact'}) {
+        $qa_contact_id = Bugzilla::User->check($qa_contact)->id if $qa_contact;
+    }
+    elsif (ref $invocant) {
+        $qa_contact_id = $invocant->{initialqacontact};
+    }
+    return $qa_contact_id;
+}
+
+sub _check_product {
+    my ($invocant, $product) = @_;
+    return Bugzilla->user->check_can_admin_product($product->name);
+}
+
+sub _check_cc_list {
+    my ($invocant, $cc_list) = @_;
+
+    my %cc_ids;
+    foreach my $cc (@$cc_list) {
+        my $id = login_to_id($cc, THROW_ERROR);
+        $cc_ids{$id} = 1;
+    }
+    return [keys %cc_ids];
+}
+
+###############################
+####       Methods         ####
+###############################
+
+sub _update_cc_list {
+    my ($self, $cc_list) = @_;
+    my $dbh = Bugzilla->dbh;
+
+    my $old_cc_list =
+      $dbh->selectcol_arrayref('SELECT user_id FROM component_cc
+                                WHERE component_id = ?', undef, $self->id);
+
+    my ($removed, $added) = diff_arrays($old_cc_list, $cc_list);
+    my $diff;
+    if (scalar @$removed || scalar @$added) {
+        $diff = [join(', ', @$removed), join(', ', @$added)];
+    }
+
+    $dbh->do('DELETE FROM component_cc WHERE component_id = ?', undef, $self->id);
+
+    my $sth = $dbh->prepare('INSERT INTO component_cc
+                             (user_id, component_id) VALUES (?, ?)');
+    $sth->execute($_, $self->id) foreach (@$cc_list);
+
+    return $diff;
+}
+
+sub _create_series {
+    my $self = shift;
+
+    # Insert default charting queries for this product.
+    # If they aren't using charting, this won't do any harm.
+    my $prodcomp = "&product="   . url_quote($self->product->name) .
+                   "&component=" . url_quote($self->name);
+
+    my $open_query = 'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.' .
+                     $prodcomp;
+    my $nonopen_query = 'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.' .
+                        $prodcomp;
+
+    my @series = ([get_text('series_all_open'), $open_query],
+                  [get_text('series_all_closed'), $nonopen_query]);
+
+    foreach my $sdata (@series) {
+        my $series = new Bugzilla::Series(undef, $self->product->name,
+                                          $self->name, $sdata->[0],
+                                          Bugzilla->user->id, 1, $sdata->[1], 1);
+        $series->writeToDatabase();
+    }
+}
+
+sub set_name { $_[0]->set('name', $_[1]); }
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_default_assignee {
+    my ($self, $owner) = @_;
+
+    $self->set('initialowner', $owner);
+    # Reset the default owner object.
+    delete $self->{default_assignee};
+}
+sub set_default_qa_contact {
+    my ($self, $qa_contact) = @_;
+
+    $self->set('initialqacontact', $qa_contact);
+    # Reset the default QA contact object.
+    delete $self->{default_qa_contact};
+}
+sub set_cc_list {
+    my ($self, $cc_list) = @_;
+
+    $self->{cc_ids} = $self->_check_cc_list($cc_list);
+    # Reset the list of CC user objects.
+    delete $self->{initial_cc};
+}
+
 sub bug_count {
     my $self = shift;
     my $dbh = Bugzilla->dbh;
@@ -143,15 +388,17 @@
 
 sub initial_cc {
     my $self = shift;
-
     my $dbh = Bugzilla->dbh;
 
     if (!defined $self->{'initial_cc'}) {
-        my $cc_ids = $dbh->selectcol_arrayref(
-            "SELECT user_id FROM component_cc WHERE component_id = ?",
-            undef, $self->id);
-        my $initial_cc = Bugzilla::User->new_from_list($cc_ids);
-        $self->{'initial_cc'} = $initial_cc;
+        # If set_cc_list() has been called but data are not yet written
+        # into the DB, we want the new values defined by it.
+        my $cc_ids = $self->{cc_ids}
+                     || $dbh->selectcol_arrayref('SELECT user_id FROM component_cc
+                                                  WHERE component_id = ?',
+                                                  undef, $self->id);
+
+        $self->{'initial_cc'} = Bugzilla::User->new_from_list($cc_ids);
     }
     return $self->{'initial_cc'};
 }
@@ -178,27 +425,6 @@
 ####      Subroutines      ####
 ###############################
 
-sub check_component {
-    my ($product, $comp_name) = @_;
-
-    $comp_name || ThrowUserError('component_blank_name');
-
-    if (length($comp_name) > 64) {
-        ThrowUserError('component_name_too_long',
-                       {'name' => $comp_name});
-    }
-
-    my $component =
-        new Bugzilla::Component({product => $product,
-                                 name    => $comp_name});
-    unless ($component) {
-        ThrowUserError('component_not_valid',
-                       {'product' => $product->name,
-                        'name' => $comp_name});
-    }
-    return $component;
-}
-
 1;
 
 __END__
@@ -211,9 +437,8 @@
 
     use Bugzilla::Component;
 
-    my $component = new Bugzilla::Component(1);
-    my $component = new Bugzilla::Component({product => $product,
-                                             name    => 'AcmeComp'});
+    my $component = new Bugzilla::Component($comp_id);
+    my $component = new Bugzilla::Component({ product => $product, name => $name });
 
     my $bug_count          = $component->bug_count();
     my $bug_ids            = $component->bug_ids();
@@ -228,7 +453,23 @@
     my $bug_flag_types     = $component->flag_types->{'bug'};
     my $attach_flag_types  = $component->flag_types->{'attachment'};
 
-    my $component  = Bugzilla::Component::check_component($product, 'AcmeComp');
+    my $component = Bugzilla::Component->check({ product => $product, name => $name });
+
+    my $component =
+      Bugzilla::Component->create({ name             => $name,
+                                    product          => $product,
+                                    initialowner     => $user_login1,
+                                    initialqacontact => $user_login2,
+                                    description      => $description});
+
+    $component->set_name($new_name);
+    $component->set_description($new_description);
+    $component->set_default_assignee($new_login_name);
+    $component->set_default_qa_contact($new_login_name);
+    $component->set_cc_list(\@new_login_names);
+    $component->update();
+
+    $component->remove_from_db;
 
 =head1 DESCRIPTION
 
@@ -241,14 +482,15 @@
 =item C<new($param)>
 
  Description: The constructor is used to load an existing component
-              by passing a component id or a hash with the product
-              id and the component name.
+              by passing a component ID or a hash with the product
+              object the component belongs to and the component name.
 
  Params:      $param - If you pass an integer, the integer is the
-                       component id from the database that we want to
-                       read in. If you pass in a hash with 'name' key,
-                       then the value of the name key is the name of a
-                       component from the DB.
+                       component ID from the database that we want to
+                       read in. If you pass in a hash with the 'name'
+                       and 'product' keys, then the value of the name
+                       key is the name of a component being in the given
+                       product.
 
  Returns:     A Bugzilla::Component object.
 
@@ -288,41 +530,117 @@
 
 =item C<initial_cc>
 
-Returns an arrayref of L<Bugzilla::User> objects representing the
-Initial CC List.
+ Description: Returns a list of user objects representing users being
+              in the initial CC list.
+
+ Params:      none.
+
+ Returns:     An arrayref of L<Bugzilla::User> objects.
 
 =item C<flag_types()>
 
-  Description: Returns all bug and attachment flagtypes available for
-               the component.
+ Description: Returns all bug and attachment flagtypes available for
+              the component.
 
-  Params:      none.
+ Params:      none.
 
-  Returns:     Two references to an array of flagtype objects.
+ Returns:     Two references to an array of flagtype objects.
 
 =item C<product()>
 
-  Description: Returns the product the component belongs to.
+ Description: Returns the product the component belongs to.
 
-  Params:      none.
+ Params:      none.
 
-  Returns:     A Bugzilla::Product object.
+ Returns:     A Bugzilla::Product object.
+
+=item C<set_name($new_name)>
+
+ Description: Changes the name of the component.
+
+ Params:      $new_name - new name of the component (string). This name
+                          must be unique within the product.
+
+ Returns:     Nothing.
+
+=item C<set_description($new_desc)>
+
+ Description: Changes the description of the component.
+
+ Params:      $new_desc - new description of the component (string).
+
+ Returns:     Nothing.
+
+=item C<set_default_assignee($new_assignee)>
+
+ Description: Changes the default assignee of the component.
+
+ Params:      $new_owner - login name of the new default assignee of
+                           the component (string). This user account
+                           must already exist.
+
+ Returns:     Nothing.
+
+=item C<set_default_qa_contact($new_qa_contact)>
+
+ Description: Changes the default QA contact of the component.
+
+ Params:      $new_qa_contact - login name of the new QA contact of
+                                the component (string). This user
+                                account must already exist.
+
+ Returns:     Nothing.
+
+=item C<set_cc_list(\@cc_list)>
+
+ Description: Changes the list of users being in the CC list by default.
+
+ Params:      \@cc_list - list of login names (string). All the user
+                          accounts must already exist.
+
+ Returns:     Nothing.
+
+=item C<update()>
+
+ Description: Write changes made to the component into the DB.
+
+ Params:      none.
+
+ Returns:     A hashref with changes made to the component object.
+
+=item C<remove_from_db()>
+
+ Description: Deletes the current component from the DB. The object itself
+              is not destroyed.
+
+ Params:      none.
+
+ Returns:     Nothing.
 
 =back
 
-=head1 SUBROUTINES
+=head1 CLASS METHODS
 
 =over
 
-=item C<check_component($product, $comp_name)>
+=item C<create(\%params)>
 
- Description: Checks if the component name was passed in and if it is a valid
-              component.
+ Description: Create a new component for the given product.
 
- Params:      $product - A Bugzilla::Product object.
-              $comp_name - String with a component name.
+ Params:      The hashref must have the following keys:
+              name            - name of the new component (string). This name
+                                must be unique within the product.
+              product         - a Bugzilla::Product object to which
+                                the Component is being added.
+              description     - description of the new component (string).
+              initialowner    - login name of the default assignee (string).
+              The following keys are optional:
+              initiaqacontact - login name of the default QA contact (string),
+                                or an empty string to clear it.
+              initial_cc      - an arrayref of login names to add to the
+                                CC list by default.
 
- Returns:     Bugzilla::Component object.
+ Returns:     A Bugzilla::Component object.
 
 =back
 
diff --git a/BugsSite/Bugzilla/Config.pm b/BugsSite/Bugzilla/Config.pm
index 85c9d6d..619eb05 100644
--- a/BugsSite/Bugzilla/Config.pm
+++ b/BugsSite/Bugzilla/Config.pm
@@ -53,9 +53,11 @@
 our %params;
 # Load in the param definitions
 sub _load_params {
-    foreach my $module (param_panels()) {
-        eval("require Bugzilla::Config::$module") || die $@;
-        my @new_param_list = "Bugzilla::Config::$module"->get_param_list();
+    my $panels = param_panels();
+    foreach my $panel (keys %$panels) {
+        my $module = $panels->{$panel};
+        eval("require $module") || die $@;
+        my @new_param_list = "$module"->get_param_list();
         foreach my $item (@new_param_list) {
             $params{$item->{'name'}} = $item;
         }
@@ -67,14 +69,16 @@
 # Subroutines go here
 
 sub param_panels {
-    my @param_panels;
+    my $param_panels = {};
     my $libpath = bz_locations()->{'libpath'};
     foreach my $item ((glob "$libpath/Bugzilla/Config/*.pm")) {
         $item =~ m#/([^/]+)\.pm$#;
         my $module = $1;
-        push(@param_panels, $module) unless $module eq 'Common';
+        $param_panels->{$module} = "Bugzilla::Config::$module" unless $module eq 'Common';
     }
-    return @param_panels;
+    # Now check for any hooked params
+    Bugzilla::Hook::process('config', { config => $param_panels });
+    return $param_panels;
 }
 
 sub SetParam {
@@ -199,18 +203,16 @@
 
     # --- REMOVE OLD PARAMS ---
 
-    my @oldparams;
+    my %oldparams;
     # Remove any old params, put them in old-params.txt
     foreach my $item (keys %$param) {
         if (!grep($_ eq $item, map ($_->{'name'}, @param_list))) {
-            local $Data::Dumper::Terse  = 1;
-            local $Data::Dumper::Indent = 0;
-            push (@oldparams, [$item, Data::Dumper->Dump([$param->{$item}])]);
+            $oldparams{$item} = $param->{$item};
             delete $param->{$item};
         }
     }
 
-    if (@oldparams) {
+    if (scalar(keys %oldparams)) {
         my $op_file = new IO::File('old-params.txt', '>>', 0600)
           || die "old-params.txt: $!";
 
@@ -218,11 +220,14 @@
               " and so have been\nmoved from your parameters file into",
               " old-params.txt:\n";
 
-        foreach my $p (@oldparams) {
-            my ($item, $value) = @$p;
-            print $op_file "\n\n$item:\n$value\n";
-            print $item;
-            print ", " unless $item eq $oldparams[$#oldparams]->[0];
+        local $Data::Dumper::Terse  = 1;
+        local $Data::Dumper::Indent = 0;
+
+        my $comma = "";
+        foreach my $item (keys %oldparams) {
+            print $op_file "\n\n$item:\n" . Data::Dumper->Dump([$oldparams{$item}]) . "\n";
+            print "${comma}$item";
+            $comma = ", ";
         }
         print "\n";
         $op_file->close;
@@ -250,6 +255,10 @@
     }
 
     write_params($param);
+
+    # Return deleted params and values so that checksetup.pl has a chance
+    # to convert old params to new data.
+    return %oldparams;
 }
 
 sub write_params {
@@ -259,8 +268,6 @@
     my $datadir    = bz_locations()->{'datadir'};
     my $param_file = "$datadir/params";
 
-    # This only has an affect for Data::Dumper >= 2.12 (ie perl >= 5.8.0)
-    # Its just cosmetic, though, so that doesn't matter
     local $Data::Dumper::Sortkeys = 1;
 
     my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
@@ -272,7 +279,7 @@
     close $fh;
 
     rename $tmpname, $param_file
-      || die "Can't rename $tmpname to $param_file: $!";
+      or die "Can't rename $tmpname to $param_file: $!";
 
     ChmodDataFile($param_file, 0666);
 
@@ -315,6 +322,17 @@
         # Now read the param back out from the sandbox
         %params = %{$s->varglob('param')};
     }
+    elsif ($ENV{'SERVER_SOFTWARE'}) {
+       # We're in a CGI, but the params file doesn't exist. We can't
+       # Template Toolkit, or even install_string, since checksetup
+       # might not have thrown an error. Bugzilla::CGI->new
+       # hasn't even been called yet, so we manually use CGI::Carp here
+       # so that the user sees the error.
+       require CGI::Carp;
+       CGI::Carp->import('fatalsToBrowser');
+       die "The $datadir/params file does not exist."
+           . ' You probably need to run checksetup.pl.',
+    }
     return \%params;
 }
 
diff --git a/BugsSite/Bugzilla/Config/Attachment.pm b/BugsSite/Bugzilla/Config/Attachment.pm
index bbaaaa6..999c479 100644
--- a/BugsSite/Bugzilla/Config/Attachment.pm
+++ b/BugsSite/Bugzilla/Config/Attachment.pm
@@ -41,6 +41,19 @@
   my $class = shift;
   my @param_list = (
   {
+   name => 'allow_attachment_display',
+   type => 'b',
+   default => 0
+  },
+
+  {
+   name => 'attachment_base',
+   type => 't',
+   default => '',
+   checker => \&check_urlbase
+  },
+
+  {
   name => 'allow_attachment_deletion',
   type => 'b',
   default => 0
@@ -61,7 +74,7 @@
    name => 'maxattachmentsize',
    type => 't',
    default => '1000',
-   checker => \&check_numeric
+   checker => \&check_maxattachmentsize
   },
 
   # The maximum size (in bytes) for patches and non-patch attachments.
diff --git a/BugsSite/Bugzilla/Config/Auth.pm b/BugsSite/Bugzilla/Config/Auth.pm
index 65ebc1b..cbd9461 100644
--- a/BugsSite/Bugzilla/Config/Auth.pm
+++ b/BugsSite/Bugzilla/Config/Auth.pm
@@ -76,8 +76,8 @@
 
   {
    name => 'user_verify_class',
-   type => 's',
-   choices => [ 'DB', 'LDAP', 'DB,LDAP', 'LDAP,DB' ],
+   type => 'o',
+   choices => [ 'DB', 'RADIUS', 'LDAP' ],
    default => 'DB',
    checker => \&check_user_verify_class
   },
diff --git a/BugsSite/Bugzilla/Config/BugChange.pm b/BugsSite/Bugzilla/Config/BugChange.pm
index bacacc0..aec6e24 100644
--- a/BugsSite/Bugzilla/Config/BugChange.pm
+++ b/BugsSite/Bugzilla/Config/BugChange.pm
@@ -34,13 +34,35 @@
 use strict;
 
 use Bugzilla::Config::Common;
+use Bugzilla::Status;
 
 $Bugzilla::Config::BugChange::sortkey = "03";
 
 sub get_param_list {
   my $class = shift;
+
+  # Hardcoded bug statuses which existed before Bugzilla 3.1.
+  my @closed_bug_statuses = ('RESOLVED', 'VERIFIED', 'CLOSED');
+
+  # If we are upgrading from 3.0 or older, bug statuses are not customisable
+  # and bug_status.is_open is not yet defined (hence the eval), so we use
+  # the bug statuses above as they are still hardcoded.
+  eval {
+      my @current_closed_states = map {$_->name} closed_bug_statuses();
+      # If no closed state was found, use the default list above.
+      @closed_bug_statuses = @current_closed_states if scalar(@current_closed_states);
+  };
+
   my @param_list = (
   {
+   name => 'duplicate_or_move_bug_status',
+   type => 's',
+   choices => \@closed_bug_statuses,
+   default => $closed_bug_statuses[0],
+   checker => \&check_bug_status
+  },
+
+  {
    name => 'letsubmitterchoosepriority',
    type => 'b',
    default => 1
@@ -59,37 +81,13 @@
   },
 
   {
-   name => 'commentoncreate',
-   type => 'b',
-   default => 0
-  },
-
-  {
-   name => 'commentonaccept',
-   type => 'b',
-   default => 0
-  },
-
-  {
    name => 'commentonclearresolution',
    type => 'b',
    default => 0
   },
 
   {
-   name => 'commentonconfirm',
-   type => 'b',
-   default => 0
-  },
-
-  {
-   name => 'commentonresolve',
-   type => 'b',
-   default => 0
-  },
-
-  {
-   name => 'commentonreassign',
+   name => 'commentonchange_resolution',
    type => 'b',
    default => 0
   },
@@ -101,24 +99,6 @@
   },
 
   {
-   name => 'commentonreopen',
-   type => 'b',
-   default => 0
-  },
-
-  {
-   name => 'commentonverify',
-   type => 'b',
-   default => 0
-  },
-
-  {
-   name => 'commentonclose',
-   type => 'b',
-   default => 0
-  },
-
-  {
    name => 'commentonduplicate',
    type => 'b',
    default => 0
diff --git a/BugsSite/Bugzilla/Config/BugFields.pm b/BugsSite/Bugzilla/Config/BugFields.pm
index ef0f340..db5d0a2 100644
--- a/BugsSite/Bugzilla/Config/BugFields.pm
+++ b/BugsSite/Bugzilla/Config/BugFields.pm
@@ -80,7 +80,7 @@
   {
    name => 'usevotes',
    type => 'b',
-   default => 1
+   default => 0
   },
 
   {
diff --git a/BugsSite/Bugzilla/Config/Common.pm b/BugsSite/Bugzilla/Config/Common.pm
index c309059..5b2cabb 100644
--- a/BugsSite/Bugzilla/Config/Common.pm
+++ b/BugsSite/Bugzilla/Config/Common.pm
@@ -27,6 +27,7 @@
 #                 Joseph Heenan <joseph@heenan.me.uk>
 #                 Erik Stambaugh <erik@dasbistro.com>
 #                 Frédéric Buclin <LpSolit@gmail.com>
+#                 Marc Schumann <wurblzap@gmail.com>
 #
 
 package Bugzilla::Config::Common;
@@ -40,6 +41,7 @@
 use Bugzilla::Constants;
 use Bugzilla::Field;
 use Bugzilla::Group;
+use Bugzilla::Status;
 
 use base qw(Exporter);
 @Bugzilla::Config::Common::EXPORT =
@@ -47,8 +49,9 @@
        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_languages check_mail_delivery_method check_notification
-       check_timezone check_utf8
+       check_mail_delivery_method check_notification check_timezone check_utf8
+       check_bug_status check_smtp_auth 
+       check_maxattachmentsize
 );
 
 # Checking functions for the various values
@@ -63,8 +66,8 @@
 
         return "";
     }
-    elsif ($param->{'type'} eq "m") {
-        foreach my $chkParam (@$value) {
+    elsif ($param->{'type'} eq 'm' || $param->{'type'} eq 'o') {
+        foreach my $chkParam (split(',', $value)) {
             unless (scalar(grep {$_ eq $chkParam} (@{$param->{'choices'}}))) {
                 return "Invalid choice '$chkParam' for multi-select list param '$param->{'name'}'";
             }
@@ -99,17 +102,21 @@
             return "must be a legal URL, that starts with https and ends with a slash.";
         }
         my $host = $1;
-        if ($host =~ /:\d+$/) {
-            return "must not contain a port.";
+        # Fall back to port 443 if for some reason getservbyname() fails.
+        my $port = getservbyname('https', 'tcp') || 443;
+        if ($host =~ /^(.+):(\d+)$/) {
+            $host = $1;
+            $port = $2;
         }
         local *SOCK;
         my $proto = getprotobyname('tcp');
         socket(SOCK, PF_INET, SOCK_STREAM, $proto);
-        my $sin = sockaddr_in(443, inet_aton($host));
+        my $iaddr = inet_aton($host) || return "The host $host cannot be resolved";
+        my $sin = sockaddr_in($port, $iaddr);
         if (!connect(SOCK, $sin)) {
-            return "Failed to connect to " . html_quote($host) . 
-                   ":443, unable to enable SSL.";
+            return "Failed to connect to $host:$port; unable to enable SSL";
         }
+        close(SOCK);
     }
     return "";
 }
@@ -166,6 +173,15 @@
     return "";
 }
 
+sub check_bug_status {
+    my $bug_status = shift;
+    my @closed_bug_statuses = map {$_->name} closed_bug_statuses();
+    if (lsearch(\@closed_bug_statuses, $bug_status) < 0) {
+        return "Must be a valid closed status: one of " . join(', ', @closed_bug_statuses);
+    }
+    return "";
+}
+
 sub check_group {
     my $group_name = shift;
     return "" unless $group_name;
@@ -258,18 +274,27 @@
     # So don't do that.
 
     my ($list, $entry) = @_;
+    $list || return 'You need to specify at least one authentication mechanism';
     for my $class (split /,\s*/, $list) {
         my $res = check_multi($class, $entry);
         return $res if $res;
         if ($class eq 'DB') {
             # No params
-        } elsif ($class eq 'LDAP') {
+        }
+        elsif ($class eq 'RADIUS') {
+            eval "require Authen::Radius";
+            return "Error requiring Authen::Radius: '$@'" if $@;
+            return "RADIUS servername (RADIUS_server) is missing" unless Bugzilla->params->{"RADIUS_server"};
+            return "RADIUS_secret is empty" unless Bugzilla->params->{"RADIUS_secret"};
+        }
+        elsif ($class eq 'LDAP') {
             eval "require Net::LDAP";
             return "Error requiring Net::LDAP: '$@'" if $@;
-            return "LDAP servername is missing" unless Bugzilla->params->{"LDAPserver"};
+            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";
+        }
+        else {
+            return "Unknown user_verify_class '$class' in check_user_verify_class";
         }
     }
     return "";
@@ -284,30 +309,6 @@
     return "";
 }
 
-sub check_languages {
-    my ($lang, $param) = @_;
-    my @languages = split(/[,\s]+/, trim($lang));
-    if(!scalar(@languages)) {
-       return "You need to specify a language tag."
-    }
-    if (scalar(@languages) > 1 && $param && $param->{'name'} eq 'defaultlanguage') {
-        return "You can only specify one language tag";
-    }
-    my $templatedir = bz_locations()->{'templatedir'};
-    my %lang_seen;
-    my @validated_languages;
-    foreach my $language (@languages) {
-       if(   ! -d "$templatedir/$language/custom" 
-          && ! -d "$templatedir/$language/default") {
-          return "The template directory for $language does not exist";
-       }
-       push(@validated_languages, $language) unless $lang_seen{$language}++;
-    }
-    # Rebuild the list of language tags, avoiding duplicates.
-    $_[0] = join(', ', @validated_languages);
-    return "";
-}
-
 sub check_mail_delivery_method {
     my $check = check_multi(@_);
     return $check if $check;
@@ -320,6 +321,24 @@
     return "";
 }
 
+sub check_maxattachmentsize {
+    my $check = check_numeric(@_);
+    return $check if $check;
+    my $size = shift;
+    my $dbh = Bugzilla->dbh;
+    if ($dbh->isa('Bugzilla::DB::Mysql')) {
+        my (undef, $max_packet) = $dbh->selectrow_array(
+            q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
+        my $byte_size = $size * 1024;
+        if ($max_packet < $byte_size) {
+            return "You asked for a maxattachmentsize of $byte_size bytes,"
+                   . " but the max_allowed_packet setting in MySQL currently"
+                   . " only allows packets up to $max_packet bytes";
+        }
+    }
+    return "";
+}
+
 sub check_notification {
     my $option = shift;
     my @current_version =
@@ -341,6 +360,14 @@
     return "";
 }
 
+sub check_smtp_auth {
+    my $username = shift;
+    if ($username) {
+        eval "require Authen::SASL";
+        return "Error requiring Authen::SASL: '$@'" if $@;
+    }
+    return "";
+}
 
 # OK, here are the parameter definitions themselves.
 #
@@ -358,13 +385,14 @@
 # The type value can be one of the following:
 #
 # t -- A short text entry field (suitable for a single line)
+# p -- A short text entry field (as with type = 't'), but the string is
+#      replaced by asterisks (appropriate for passwords)
 # l -- A long text field (suitable for many lines)
 # b -- A boolean value (either 1 or 0)
 # m -- A list of values, with many selectable (shows up as a select box)
 #      To specify the list of values, make the 'choices' key be an array
-#      reference of the valid choices. The 'default' key should be an array
-#      reference for the list of selected values (which must appear in the
-#      first anonymous array), i.e.:
+#      reference of the valid choices. The 'default' key should be a string
+#      with a list of selected values (as a comma-separated list), i.e.:
 #       {
 #         name => 'multiselect',
 #         desc => 'A list of options, choose many',
@@ -380,6 +408,11 @@
 #      &check_multi should always be used as the param verification function
 #      for list (single and multiple) parameter types.
 #
+# o -- A list of values, orderable, and with many selectable (shows up as a
+#      JavaScript-enhanced select box if JavaScript is enabled, and a text
+#      entry field if not)
+#      Set up in the same way as type m.
+#
 # s -- A list of values, with one selectable (shows up as a select box)
 #      To specify the list of values, make the 'choices' key be an array
 #      reference of the valid choices. The 'default' key should be one of
@@ -421,7 +454,7 @@
 
 =item C<check_multi>
 
-Checks that a multi-valued parameter (ie type C<s> or type C<m>) satisfies
+Checks that a multi-valued parameter (ie types C<s>, C<o> or C<m>) satisfies
 its contraints.
 
 =item C<check_numeric>
diff --git a/BugsSite/Bugzilla/Config/Core.pm b/BugsSite/Bugzilla/Config/Core.pm
index a0aca9c..b307dd7 100644
--- a/BugsSite/Bugzilla/Config/Core.pm
+++ b/BugsSite/Bugzilla/Config/Core.pm
@@ -56,7 +56,7 @@
   {
    name => 'docs_urlbase',
    type => 't',
-   default => 'docs/html/',
+   default => 'docs/%lang%/html/',
    checker => \&check_url
   },
 
diff --git a/BugsSite/Bugzilla/Config/L10n.pm b/BugsSite/Bugzilla/Config/L10n.pm
deleted file mode 100644
index 957dce7..0000000
--- a/BugsSite/Bugzilla/Config/L10n.pm
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- Mode: perl; indent-tabs-mode: nil -*-
-#
-# The contents of this file are subject to the Mozilla Public
-# License Version 1.1 (the "License"); you may not use this file
-# except in compliance with the License. You may obtain a copy of
-# the License at http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS
-# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-# implied. See the License for the specific language governing
-# rights and limitations under the License.
-#
-# The Original Code is the Bugzilla Bug Tracking System.
-#
-# The Initial Developer of the Original Code is Netscape Communications
-# Corporation. Portions created by Netscape are
-# Copyright (C) 1998 Netscape Communications Corporation. All
-# Rights Reserved.
-#
-# Contributor(s): Terry Weissman <terry@mozilla.org>
-#                 Dawn Endico <endico@mozilla.org>
-#                 Dan Mosedale <dmose@mozilla.org>
-#                 Joe Robins <jmrobins@tgix.com>
-#                 Jacob Steenhagen <jake@bugzilla.org>
-#                 J. Paul Reed <preed@sigkill.com>
-#                 Bradley Baetz <bbaetz@student.usyd.edu.au>
-#                 Joseph Heenan <joseph@heenan.me.uk>
-#                 Erik Stambaugh <erik@dasbistro.com>
-#                 Frédéric Buclin <LpSolit@gmail.com>
-#
-
-package Bugzilla::Config::L10n;
-
-use strict;
-
-use File::Spec; # for find_languages
-
-use Bugzilla::Constants;
-use Bugzilla::Config::Common;
-
-$Bugzilla::Config::L10n::sortkey = "08";
-
-sub get_param_list {
-  my $class = shift;
-  my @param_list = (
-  {
-   name => 'languages' ,
-   extra_desc => { available_languages => find_languages() },
-   type => 't' ,
-   default => 'en' ,
-   checker => \&check_languages
-  },
-
-  {
-   name => 'defaultlanguage',
-   type => 't' ,
-   default => 'en' ,
-   checker => \&check_languages
-  } );
-  return @param_list;
-}
-
-sub find_languages {
-    my @languages = ();
-    opendir(DIR, bz_locations()->{'templatedir'})
-      || return "Can't open 'template' directory: $!";
-    foreach my $dir (readdir(DIR)) {
-        next unless $dir =~ /^([a-z-]+)$/i;
-        my $lang = $1;
-        next if($lang =~ /^CVS$/i);
-        my $deft_path = File::Spec->catdir('template', $lang, 'default');
-        my $cust_path = File::Spec->catdir('template', $lang, 'custom');
-        push(@languages, $lang) if(-d $deft_path or -d $cust_path);
-    }
-    closedir DIR;
-    return join(', ', @languages);
-}
-
-1;
diff --git a/BugsSite/Bugzilla/Config/MTA.pm b/BugsSite/Bugzilla/Config/MTA.pm
index 686d5b3..37d99d9 100644
--- a/BugsSite/Bugzilla/Config/MTA.pm
+++ b/BugsSite/Bugzilla/Config/MTA.pm
@@ -69,6 +69,17 @@
    default => 'localhost'
   },
   {
+   name => 'smtp_username',
+   type => 't',
+   default => '',
+   checker => \&check_smtp_auth
+  },
+  {
+   name => 'smtp_password',
+   type => 'p',
+   default => ''
+  },
+  {
    name => 'smtp_debug',
    type => 'b',
    default => 0
diff --git a/BugsSite/Bugzilla/Config/RADIUS.pm b/BugsSite/Bugzilla/Config/RADIUS.pm
new file mode 100644
index 0000000..6701d6f
--- /dev/null
+++ b/BugsSite/Bugzilla/Config/RADIUS.pm
@@ -0,0 +1,60 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Marc Schumann.
+# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
+# All rights reserved.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+#
+
+package Bugzilla::Config::RADIUS;
+
+use strict;
+
+use Bugzilla::Config::Common;
+
+$Bugzilla::Config::RADIUS::sortkey = "09";
+
+sub get_param_list {
+  my $class = shift;
+  my @param_list = (
+  {
+   name =>    'RADIUS_server',
+   type =>    't',
+   default => ''
+  },
+
+  {
+   name =>    'RADIUS_secret',
+   type =>    't',
+   default => ''
+  },
+
+  {
+   name =>    'RADIUS_NAS_IP',
+   type =>    't',
+   default => ''
+  },
+
+  {
+   name =>    'RADIUS_email_suffix',
+   type =>    't',
+   default => ''
+  },
+  );
+  return @param_list;
+}
+
+1;
diff --git a/BugsSite/Bugzilla/Constants.pm b/BugsSite/Bugzilla/Constants.pm
index 9fb6a31..016e1fa 100644
--- a/BugsSite/Bugzilla/Constants.pm
+++ b/BugsSite/Bugzilla/Constants.pm
@@ -40,6 +40,10 @@
     BUGZILLA_VERSION
 
     bz_locations
+
+    IS_NULL
+    NOT_NULL
+
     CONTROLMAPNA
     CONTROLMAPSHOWN
     CONTROLMAPDEFAULT
@@ -80,6 +84,7 @@
     LIST_OF_BUGS
 
     COMMENT_COLS
+    MAX_COMMENT_LENGTH
 
     CMT_NORMAL
     CMT_DUPE_OF
@@ -87,7 +92,6 @@
     CMT_POPULAR_VOTES
     CMT_MOVED_TO
 
-    UNLOCK_ABORT
     THROW_ERROR
     
     RELATIONSHIPS
@@ -115,8 +119,9 @@
     FIELD_TYPE_UNKNOWN
     FIELD_TYPE_FREETEXT
     FIELD_TYPE_SINGLE_SELECT
-
-    BUG_STATE_OPEN
+    FIELD_TYPE_MULTI_SELECT
+    FIELD_TYPE_TEXTAREA
+    FIELD_TYPE_DATETIME
 
     USAGE_MODE_BROWSER
     USAGE_MODE_CMDLINE
@@ -135,10 +140,17 @@
     ON_WINDOWS
 
     MAX_TOKEN_AGE
+    MAX_LOGINCOOKIE_AGE
 
     SAFE_PROTOCOLS
 
+    MIN_SMALLINT
+    MAX_SMALLINT
+
     MAX_LEN_QUERY_NAME
+    MAX_MILESTONE_SIZE
+    MAX_COMPONENT_SIZE
+    MAX_FREETEXT_LENGTH
 );
 
 @Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
@@ -146,7 +158,18 @@
 # CONSTANTS
 #
 # Bugzilla version
-use constant BUGZILLA_VERSION => "3.0.3";
+use constant BUGZILLA_VERSION => "3.2.3";
+
+# These are unique values that are unlikely to match a string or a number,
+# to be used in criteria for match() functions and other things. They start
+# and end with spaces because most Bugzilla stuff has trim() called on it,
+# so this is unlikely to match anything we get out of the DB.
+#
+# We can't use a reference, because Template Toolkit doesn't work with
+# them properly (constants.IS_NULL => {} just returns an empty string instead
+# of the reference).
+use constant IS_NULL  => '  __IS_NULL__  ';
+use constant NOT_NULL => '  __NOT_NULL__  ';
 
 #
 # ControlMap constants for group_control_map.
@@ -239,6 +262,8 @@
 
 # The column length for displayed (and wrapped) bug comments.
 use constant COMMENT_COLS => 80;
+# Used in _check_comment(). Gives the max length allowed for a comment.
+use constant MAX_COMMENT_LENGTH => 65535;
 
 # The type of bug comments.
 use constant CMT_NORMAL => 0;
@@ -247,10 +272,6 @@
 use constant CMT_POPULAR_VOTES => 3;
 use constant CMT_MOVED_TO => 4;
 
-# used by Bugzilla::DB to indicate that tables are being unlocked
-# because of error
-use constant UNLOCK_ABORT => 1;
-
 # Determine whether a validation routine should return 0 or throw
 # an error when the validation fails.
 use constant THROW_ERROR => 1;
@@ -327,19 +348,20 @@
 use constant FIELD_TYPE_UNKNOWN   => 0;
 use constant FIELD_TYPE_FREETEXT  => 1;
 use constant FIELD_TYPE_SINGLE_SELECT => 2;
+use constant FIELD_TYPE_MULTI_SELECT => 3;
+use constant FIELD_TYPE_TEXTAREA  => 4;
+use constant FIELD_TYPE_DATETIME  => 5;
 
 # The maximum number of days a token will remain valid.
 use constant MAX_TOKEN_AGE => 3;
+# How many days a logincookie will remain valid if not used.
+use constant MAX_LOGINCOOKIE_AGE => 30;
 
 # 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');
 
-# States that are considered to be "open" for bugs.
-use constant BUG_STATE_OPEN => ('NEW', 'REOPENED', 'ASSIGNED', 
-                                'UNCONFIRMED');
-
 # Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode.
 use constant USAGE_MODE_BROWSER    => 0;
 use constant USAGE_MODE_CMDLINE    => 1;
@@ -362,10 +384,10 @@
                 dbd => { 
                     package => 'DBD-mysql',
                     module  => 'DBD::mysql',
-                    version => '2.9003',
-                    # Certain versions are broken, development versions are
-                    # always disallowed.
-                    blacklist => ['^3\.000[3-6]', '_'],
+                    # Disallow development versions
+                    blacklist => ['_'],
+                    # For UTF-8 support
+                    version => '4.00',
                 },
                 name => 'MySQL'},
     'pg'    => {db => 'Bugzilla::DB::Pg', db_version => '8.00.0000',
@@ -375,6 +397,13 @@
                     version => '1.45',
                 },
                 name => 'PostgreSQL'},
+     'oracle'=> {db => 'Bugzilla::DB::Oracle', db_version => '10.02.0',
+                dbd => {
+                     package => 'DBD-Oracle',
+                     module  => 'DBD::Oracle',
+                     version => '1.19',
+                },
+                name => 'Oracle'},
 };
 
 # The user who should be considered "root" when we're giving
@@ -384,9 +413,21 @@
 # True if we're on Win32.
 use constant ON_WINDOWS => ($^O =~ /MSWin32/i);
 
+use constant MIN_SMALLINT => -32768;
+use constant MAX_SMALLINT => 32767;
+
 # The longest that a saved search name can be.
 use constant MAX_LEN_QUERY_NAME => 64;
 
+# The longest milestone name allowed.
+use constant MAX_MILESTONE_SIZE => 20;
+
+# The longest component name allowed.
+use constant MAX_COMPONENT_SIZE => 64;
+
+# Maximum length allowed for free text fields.
+use constant MAX_FREETEXT_LENGTH => 255;
+
 sub bz_locations {
     # We know that Bugzilla/Constants.pm must be in %INC at this point.
     # So the only question is, what's the name of the directory
@@ -418,6 +459,7 @@
     # That means that if you modify these paths, they must be absolute paths.
     return {
         'libpath'     => $libpath,
+        'ext_libpath' => "$libpath/lib",
         # If you put the libraries in a different location than the CGIs,
         # make sure this still points to the CGIs.
         'cgi_path'    => $libpath,
@@ -427,7 +469,7 @@
         'datadir'     => "$libpath/$datadir",
         'attachdir'   => "$libpath/$datadir/attachments",
         'skinsdir'    => "$libpath/skins",
-        # $webdotdir must be in the webtree somewhere. Even if you use a 
+        # $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 
diff --git a/BugsSite/Bugzilla/DB.pm b/BugsSite/Bugzilla/DB.pm
index 7a19f8f..d68c5dc 100644
--- a/BugsSite/Bugzilla/DB.pm
+++ b/BugsSite/Bugzilla/DB.pm
@@ -37,18 +37,22 @@
 
 use Bugzilla::Constants;
 use Bugzilla::Install::Requirements;
+use Bugzilla::Install::Util qw(vers_cmp);
 use Bugzilla::Install::Localconfig;
 use Bugzilla::Util;
 use Bugzilla::Error;
 use Bugzilla::DB::Schema;
 
 use List::Util qw(max);
+use Storable qw(dclone);
 
 #####################################################################
 # Constants
 #####################################################################
 
 use constant BLOB_TYPE => DBI::SQL_BLOB;
+use constant ISOLATION_LEVEL => 'REPEATABLE READ';
+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
@@ -270,8 +274,7 @@
 # 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
-                            bz_lock_tables bz_unlock_tables);
+                            sql_date_format sql_interval);
 
 # This overridden import method will check implementation of inherited classes
 # for missing implementation of abstract methods
@@ -311,6 +314,13 @@
     return "LOWER($string)";
 }
 
+sub sql_iposition {
+    my ($self, $fragment, $text) = @_;
+    $fragment = $self->sql_istring($fragment);
+    $text = $self->sql_istring($text);
+    return $self->sql_position($fragment, $text);
+}
+
 sub sql_position {
     my ($self, $fragment, $text) = @_;
 
@@ -332,6 +342,11 @@
     return '(' . join(' || ', @params) . ')';
 }
 
+sub sql_in {
+    my ($self, $column_name, $in_list_ref) = @_;
+    return " $column_name IN (" . join(',', @$in_list_ref) . ") ";
+}
+
 sub sql_fulltext_search {
     my ($self, $column, $text) = @_;
 
@@ -376,26 +391,6 @@
                                  $table, $column);
 }
 
-sub bz_get_field_defs {
-    my ($self) = @_;
-
-    my $extra = "";
-    if (!Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
-        $extra = "AND name NOT IN ('estimated_time', 'remaining_time', " .
-                 "'work_time', 'percentage_complete', 'deadline')";
-    }
-
-    my @fields;
-    my $sth = $self->prepare("SELECT name, description FROM fielddefs
-                              WHERE obsolete = 0 $extra
-                              ORDER BY sortkey");
-    $sth->execute();
-    while (my $field_ref = $sth->fetchrow_hashref()) {
-        push(@fields, $field_ref);
-    }
-    return(@fields);
-}
-
 #####################################################################
 # Database Setup
 #####################################################################
@@ -428,6 +423,36 @@
     }
 }
 
+sub bz_setup_foreign_keys {
+    my ($self) = @_;
+
+    # We use _bz_schema because bz_add_table has removed all REFERENCES
+    # items from _bz_real_schema.
+    my @tables = $self->_bz_schema->get_table_list();
+    foreach my $table (@tables) {
+        my @columns = $self->_bz_schema->get_table_columns($table);
+        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});
+            }
+        }
+    }
+}
+
+# This is used by contrib/bzdbcopy.pl, mostly.
+sub bz_drop_foreign_keys {
+    my ($self) = @_;
+
+    my @tables = $self->_bz_real_schema->get_table_list();
+    foreach my $table (@tables) {
+        my @columns = $self->_bz_real_schema->get_table_columns($table);
+        foreach my $column (@columns) {
+            $self->bz_drop_fk($table, $column);
+        }
+    }
+}
+
 #####################################################################
 # Schema Modification Methods
 #####################################################################
@@ -463,6 +488,24 @@
     }
 }
 
+sub bz_add_fk {
+    my ($self, $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_alter_column {
     my ($self, $table, $name, $new_def, $set_nulls_to) = @_;
 
@@ -568,8 +611,17 @@
 
     if (!$table_exists) {
         $self->_bz_add_table_raw($name);
-        $self->_bz_real_schema->add_table($name,
-            $self->_bz_schema->get_table_abstract($name));
+        my $table_def = dclone($self->_bz_schema->get_table_abstract($name));
+
+        my %fields = @{$table_def->{FIELDS}};
+        foreach my $col (keys %fields) {
+            # Foreign Key references have to be added by Install::DB after
+            # initial table creation, because column names have changed
+            # over history and it's impossible to keep track of that info
+            # in ABSTRACT_SCHEMA.
+            delete $fields{$col}->{REFERENCES};
+        }
+        $self->_bz_real_schema->add_table($name, $table_def);
         $self->_bz_store_real_schema;
     }
 }
@@ -595,23 +647,47 @@
     $self->do($_) foreach (@statements);
 }
 
-sub bz_add_field_table {
-    my ($self, $name) = @_;
-    my $table_schema = $self->_bz_schema->FIELD_TABLE_SCHEMA;
+sub _bz_add_field_table {
+    my ($self, $name, $schema_ref) = @_;
     # We do nothing if the table already exists.
     return if $self->bz_table_info($name);
-    my $indexes      = $table_schema->{INDEXES};
-    # $indexes is an arrayref, not a hash. In order to fix the keys,
-    # we have to fix every other item.
-    for (my $i = 0; $i < scalar @$indexes; $i++) {
-        next if ($i % 2 && $i != 0); # We skip 1, 3, 5, 7, etc.
-        $indexes->[$i] = $name . "_" . $indexes->[$i];
+
+    # Copy this so that we're not modifying the passed reference.
+    # (This avoids modifying a constant in Bugzilla::DB::Schema.)
+    my %table_schema = %$schema_ref;
+    my %indexes = @{ $table_schema{INDEXES} };
+    my %fixed_indexes;
+    foreach my $key (keys %indexes) {
+        $fixed_indexes{$name . "_" . $key} = $indexes{$key};
     }
+    # INDEXES is supposed to be an arrayref, so we have to convert back.
+    my @indexes_array = %fixed_indexes;
+    $table_schema{INDEXES} = \@indexes_array;
     # We add this to the abstract schema so that bz_add_table can find it.
-    $self->_bz_schema->add_table($name, $table_schema);
+    $self->_bz_schema->add_table($name, \%table_schema);
     $self->bz_add_table($name);
 }
 
+sub bz_add_field_tables {
+    my ($self, $field) = @_;
+    
+    $self->_bz_add_field_table($field->name,
+                                $self->_bz_schema->FIELD_TABLE_SCHEMA);
+    if ( $field->type == FIELD_TYPE_MULTI_SELECT ) {
+        $self->_bz_add_field_table('bug_' . $field->name,
+                $self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
+    }
+
+}
+
+sub bz_drop_field_tables {
+    my ($self, $field) = @_;
+    if ($field->type == FIELD_TYPE_MULTI_SELECT) {
+        $self->bz_drop_table('bug_' . $field->name);
+    }
+    $self->bz_drop_table($field->name);
+}
+
 sub bz_drop_column {
     my ($self, $table, $column) = @_;
 
@@ -634,6 +710,24 @@
     }
 }
 
+sub bz_drop_fk {
+    my ($self, $table, $column) = @_;
+
+    my $col_def = $self->bz_column_info($table, $column);
+    if ($col_def && exists $col_def->{REFERENCES}) {
+        my $def = $col_def->{REFERENCES};
+        print get_text('install_fk_drop',
+                       { table => $table, column => $column, fk => $def })
+            . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+        my @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);
+        $self->_bz_store_real_schema;
+    }
+
+}
+
 sub bz_drop_index {
     my ($self, $table, $name) = @_;
 
@@ -767,7 +861,10 @@
 
 sub bz_column_info {
     my ($self, $table, $column) = @_;
-    return $self->_bz_real_schema->get_column_abstract($table, $column);
+    my $def = $self->_bz_real_schema->get_column_abstract($table, $column);
+    # We dclone it so callers can't modify the Schema.
+    $def = dclone($def) if defined $def;
+    return $def;
 }
 
 sub bz_index_info {
@@ -839,39 +936,51 @@
 # Transaction Methods
 #####################################################################
 
+sub bz_in_transaction {
+    return $_[0]->{private_bz_transaction_count} ? 1 : 0;
+}
+
 sub bz_start_transaction {
     my ($self) = @_;
 
-    if ($self->{private_bz_in_transaction}) {
-        ThrowCodeError("nested_transaction");
+    if ($self->bz_in_transaction) {
+        $self->{private_bz_transaction_count}++;
     } else {
         # Turn AutoCommit off and start a new transaction
         $self->begin_work();
-        $self->{private_bz_in_transaction} = 1;
+        # REPEATABLE READ means "We work on a snapshot of the DB that
+        # is created when we execute our first SQL statement." It's
+        # what we need in Bugzilla to be safe, for what we do.
+        # Different DBs have different defaults for their isolation
+        # level, so we just set it here manually.
+        $self->do('SET TRANSACTION ISOLATION LEVEL ' . $self->ISOLATION_LEVEL);
+        $self->{private_bz_transaction_count} = 1;
     }
 }
 
 sub bz_commit_transaction {
     my ($self) = @_;
-
-    if (!$self->{private_bz_in_transaction}) {
-        ThrowCodeError("not_in_transaction");
-    } else {
+    
+    if ($self->{private_bz_transaction_count} > 1) {
+        $self->{private_bz_transaction_count}--;
+    } elsif ($self->bz_in_transaction) {
         $self->commit();
-
-        $self->{private_bz_in_transaction} = 0;
+        $self->{private_bz_transaction_count} = 0;
+    } else {
+       ThrowCodeError('not_in_transaction');
     }
 }
 
 sub bz_rollback_transaction {
     my ($self) = @_;
 
-    if (!$self->{private_bz_in_transaction}) {
+    # Unlike start and commit, if we rollback at any point it happens
+    # instantly, even if we're in a nested transaction.
+    if (!$self->bz_in_transaction) {
         ThrowCodeError("not_in_transaction");
     } else {
         $self->rollback();
-
-        $self->{private_bz_in_transaction} = 0;
+        $self->{private_bz_transaction_count} = 0;
     }
 }
 
@@ -880,22 +989,28 @@
 #####################################################################
 
 sub db_new {
-    my ($class, $dsn, $user, $pass, $attributes) = @_;
+    my ($class, $dsn, $user, $pass, $override_attrs) = @_;
 
     # set up default attributes used to connect to the database
-    # (if not defined by DB specific implementation)
-    $attributes = { RaiseError => 0,
-                    AutoCommit => 1,
-                    PrintError => 0,
-                    ShowErrorStatement => 1,
-                    HandleError => \&_handle_error,
-                    TaintIn => 1,
-                    FetchHashKeyName => 'NAME',  
-                    # Note: NAME_lc causes crash on ActiveState Perl
-                    # 5.8.4 (see Bug 253696)
-                    # XXX - This will likely cause problems in DB
-                    # back ends that twiddle column case (Oracle?)
-                  } if (!defined($attributes));
+    # (may be overridden by DB driver implementations)
+    my $attributes = { RaiseError => 0,
+                       AutoCommit => 1,
+                       PrintError => 0,
+                       ShowErrorStatement => 1,
+                       HandleError => \&_handle_error,
+                       TaintIn => 1,
+                       FetchHashKeyName => 'NAME',  
+                       # Note: NAME_lc causes crash on ActiveState Perl
+                       # 5.8.4 (see Bug 253696)
+                       # XXX - This will likely cause problems in DB
+                       # back ends that twiddle column case (Oracle?)
+                     };
+
+    if ($override_attrs) {
+        foreach my $key (keys %$override_attrs) {
+            $attributes->{$key} = $override_attrs->{$key};
+        }
+    }
 
     # connect using our known info to the specified db
     my $self = DBI->connect($dsn, $user, $pass, $attributes)
@@ -907,9 +1022,6 @@
     # above "die" condition.
     $self->{RaiseError} = 1;
 
-    # class variables
-    $self->{private_bz_in_transaction} = 0;
-
     bless ($self, $class);
 
     return $self;
@@ -1076,6 +1188,39 @@
     }
 }
 
+# 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 $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");
+
+    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;
+    }
+}
+
 1;
 
 __END__
@@ -1116,9 +1261,6 @@
   my $column = $dbh->bz_column_info($table, $column);
   my $index  = $dbh->bz_index_info($table, $index);
 
-  # General Information
-  my @fields    = $dbh->bz_get_field_defs();
-
 =head1 DESCRIPTION
 
 Functions in this module allows creation of a database handle to connect
@@ -1135,13 +1277,21 @@
 Subclasses of Bugzilla::DB are required to define certain constants. These
 constants are required to be subroutines or "use constant" variables.
 
-=over 4
+=over
 
 =item C<BLOB_TYPE>
 
 The C<\%attr> argument that must be passed to bind_param in order to 
 correctly escape a C<LONGBLOB> type.
 
+=item C<ISOLATION_LEVEL>
+
+The argument that this database should send to 
+C<SET TRANSACTION ISOLATION LEVEL> when starting a transaction. If you
+override this in a subclass, the isolation level you choose should
+be as strict as or more strict than the default isolation level defined in
+L<Bugzilla::DB>.
+
 =back
 
 
@@ -1422,7 +1572,7 @@
 
 =item C<$limit> - number of rows to return from query (scalar)
 
-=item C<$offset> - number of rows to skip prior counting (scalar)
+=item C<$offset> - number of rows to skip before counting (scalar)
 
 =back
 
@@ -1542,10 +1692,13 @@
 
 =item B<Description>
 
-Outputs proper SQL syntax determinig position of a substring
+Outputs proper SQL syntax determining position of a substring
 (fragment) withing a string (text). Note: if the substring or
 text are string constants, they must be properly quoted (e.g. "'pattern'").
 
+It searches for the string in a case-sensitive manner. If you want to do
+a case-insensitive search, use L</sql_iposition>.
+
 =item B<Params>
 
 =over
@@ -1562,6 +1715,10 @@
 
 =back
 
+=item C<sql_iposition>
+
+Just like L</sql_position>, but case-insensitive.
+
 =item C<sql_group_by>
 
 =over
@@ -1713,60 +1870,29 @@
 
 =back
 
-=item C<bz_lock_tables>
+=item C<sql_in>
 
 =over
 
 =item B<Description>
 
-Performs a table lock operation on specified tables. If the underlying 
-database supports transactions, it should also implicitly start a new 
-transaction.
+Returns SQL syntax for the C<IN ()> operator. 
 
-Abstract method, should be overridden by database specific code.
+Only necessary where an C<IN> clause can have more than 1000 items.
 
 =item B<Params>
 
 =over
 
-=item C<@tables> - list of names of tables to lock in MySQL
-notation (ex. 'bugs AS bugs2 READ', 'logincookies WRITE')
+=item C<$column_name> - Column name (e.g. C<bug_id>)
+
+=item C<$in_list_ref> - an arrayref containing values for C<IN ()>
 
 =back
 
-=item B<Returns> (nothing)
+=item B<Returns>
 
-=back
-
-=item C<bz_unlock_tables>
-
-=over
-
-=item B<Description>
-
-Performs a table unlock operation.
-
-If the underlying database supports transactions, it should also implicitly 
-commit or rollback the transaction.
-
-Also, this function should allow to be called with the abort flag
-set even without locking tables first without raising an error
-to simplify error handling.
-
-Abstract method, should be overridden by database specific code.
-
-=item B<Params>
-
-=over
-
-=item C<$abort> - C<UNLOCK_ABORT> if the operation on locked tables
-failed (if transactions are supported, the action will be rolled
-back). No param if the operation succeeded. This is only used by
-L<Bugzilla::Error/throw_error>.
-
-=back
-
-=item B<Returns> (none)
+Formatted SQL for the C<IN> operator.
 
 =back
 
@@ -1815,23 +1941,6 @@
 
 =back
 
-=item C<bz_get_field_defs>
-
-=over
-
-=item B<Description>
-
-Returns a list of all the "bug" fields in Bugzilla. The list
-contains hashes, with a C<name> key and a C<description> key.
-
-=item B<Params> (none)
-
-=item B<Returns>
-
-List of all the "bug" fields
-
-=back
-
 =back
 
 =head2 Database Setup Methods
@@ -2183,20 +2292,33 @@
 
 =over
 
+=item C<bz_in_transaction>
+
+Returns C<1> if we are currently in the middle of an uncommitted transaction,
+C<0> otherwise.
+
 =item C<bz_start_transaction>
 
-Starts a transaction if supported by the database being used. Returns nothing
-and takes no parameters.
+Starts a transaction.
+
+It is OK to call C<bz_start_transaction> when you are already inside of
+a transaction. However, you must call L</bz_commit_transaction> as many
+times as you called C<bz_start_transaction>, in order for your transaction
+to actually commit.
+
+Bugzilla uses C<REPEATABLE READ> transactions.
+
+Returns nothing and takes no parameters.
 
 =item C<bz_commit_transaction>
 
-Ends a transaction, commiting all changes, if supported by the database 
-being used. Returns nothing and takes no parameters.
+Ends a transaction, commiting all changes. Returns nothing and takes
+no parameters.
 
 =item C<bz_rollback_transaction>
 
-Ends a transaction, rolling back all changes, if supported by the database 
-being used. Returns nothing and takes no parameters.
+Ends a transaction, rolling back all changes. Returns nothing and takes 
+no parameters.
 
 =back
 
@@ -2226,7 +2348,9 @@
 
 =item C<$pass> - password used to log in to the database
 
-=item C<\%attributes> - set of attributes for DB connection (optional)
+=item C<\%override_attrs> - set of attributes for DB connection (optional).
+You only have to set attributes that you want to be different from
+the default attributes set inside of C<db_new>.
 
 =back
 
diff --git a/BugsSite/Bugzilla/DB/Mysql.pm b/BugsSite/Bugzilla/DB/Mysql.pm
index 9b4102e..92d1df1 100644
--- a/BugsSite/Bugzilla/DB/Mysql.pm
+++ b/BugsSite/Bugzilla/DB/Mysql.pm
@@ -44,8 +44,17 @@
 use strict;
 
 use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(install_string);
 use Bugzilla::Util;
 use Bugzilla::Error;
+use Bugzilla::DB::Schema::Mysql;
+
+use List::Util qw(max);
+
+# 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);
@@ -57,8 +66,10 @@
     my $dsn = "DBI:mysql:host=$host;database=$dbname";
     $dsn .= ";port=$port" if $port;
     $dsn .= ";mysql_socket=$sock" if $sock;
+
+    my $attrs = { mysql_enable_utf8 => Bugzilla->params->{'utf8'} };
     
-    my $self = $class->db_new($dsn, $user, $pass);
+    my $self = $class->db_new($dsn, $user, $pass, $attrs);
 
     # This makes sure that if the tables are encoded as UTF-8, we
     # return their data correctly.
@@ -87,6 +98,10 @@
         }
     }
 
+    # Allow large GROUP_CONCATs (largely for inserting comments 
+    # into bugs_fulltext).
+    $self->do('SET SESSION group_concat_max_len = 128000000');
+
     return $self;
 }
 
@@ -100,6 +115,15 @@
     return $last_insert_id;
 }
 
+sub sql_group_concat {
+    my ($self, $column, $separator) = @_;
+    my $sep_sql;
+    if ($separator) {
+        $sep_sql = " SEPARATOR $separator";
+    }
+    return "GROUP_CONCAT($column$sep_sql)";
+}
+
 sub sql_regexp {
     my ($self, $expr, $pattern) = @_;
 
@@ -133,7 +157,7 @@
 
     # Add the boolean mode modifier if the search string contains
     # boolean operators.
-    my $mode = ($text =~ /[+-<>()~*"]/ ? "IN BOOLEAN MODE" : "");
+    my $mode = ($text =~ /[+\-<>()~*"]/ ? "IN BOOLEAN MODE" : "");
 
     # quote the text for use in the MATCH AGAINST expression
     $text = $self->quote($text);
@@ -176,74 +200,28 @@
     return "INTERVAL $interval $units";
 }
 
+sub sql_iposition {
+    my ($self, $fragment, $text) = @_;
+    return "INSTR($text, $fragment)";
+}
+
 sub sql_position {
     my ($self, $fragment, $text) = @_;
 
-    # mysql 4.0.1 and lower do not support CAST
-    # (checksetup has a check for unsupported versions)
-
-    my $server_version = $self->bz_server_version;
     return "INSTR(CAST($text AS BINARY), CAST($fragment AS BINARY))";
 }
 
 sub sql_group_by {
     my ($self, $needed_columns, $optional_columns) = @_;
 
-    # MySQL allows to specify all columns as ANSI SQL requires, but also
-    # allow you to specify just minimal subset to get unique result.
-    # According to MySQL documentation, the less columns you specify
-    # the faster the query runs.
+    # MySQL allows you to specify the minimal subset of columns to get
+    # a unique result. While it does allow specifying all columns as
+    # ANSI SQL requires, according to MySQL documentation, the fewer
+    # columns you specify, the faster the query runs.
     return "GROUP BY $needed_columns";
 }
 
 
-sub bz_lock_tables {
-    my ($self, @tables) = @_;
-
-    my $list = join(', ', @tables);
-    # Check first if there was no lock before
-    if ($self->{private_bz_tables_locked}) {
-        ThrowCodeError("already_locked", { current => $self->{private_bz_tables_locked},
-                                           new => $list });
-    } else {
-        $self->do('LOCK TABLE ' . $list); 
-    
-        $self->{private_bz_tables_locked} = $list;
-    }
-}
-
-sub bz_unlock_tables {
-    my ($self, $abort) = @_;
-    
-    # Check first if there was previous matching lock
-    if (!$self->{private_bz_tables_locked}) {
-        # Abort is allowed even without previous lock for error handling
-        return if $abort;
-        ThrowCodeError("no_matching_lock");
-    } else {
-        $self->do("UNLOCK TABLES");
-    
-        $self->{private_bz_tables_locked} = "";
-    }
-}
-
-# As Bugzilla currently runs on MyISAM storage, which does not support
-# transactions, these functions die when called.
-# Maybe we should just ignore these calls for now, but as we are not
-# using transactions in MySQL yet, this just hints the developers.
-sub bz_start_transaction {
-    die("Attempt to start transaction on DB without transaction support");
-}
-
-sub bz_commit_transaction {
-    die("Attempt to commit transaction on DB without transaction support");
-}
-
-sub bz_rollback_transaction {
-    die("Attempt to rollback transaction on DB without transaction support");
-}
-
-
 sub _bz_get_initial_schema {
     my ($self) = @_;
     return $self->_bz_build_schema_from_disk();
@@ -256,14 +234,50 @@
 sub bz_setup_database {
     my ($self) = @_;
 
+    # The "comments" field of the bugs_fulltext table could easily exceed
+    # MySQL's default max_allowed_packet. Also, MySQL should never have
+    # a max_allowed_packet smaller than our max_attachment_size. So, we
+    # warn the user here if max_allowed_packet is too small.
+    my $min_max_allowed = MAX_COMMENTS * MAX_COMMENT_LENGTH;
+    my (undef, $current_max_allowed) = $self->selectrow_array(
+        q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
+    # This parameter is not yet defined when the DB is being built for
+    # the very first time. The code below still works properly, however,
+    # because the default maxattachmentsize is smaller than $min_max_allowed.
+    my $max_attachment = (Bugzilla->params->{'maxattachmentsize'} || 0) * 1024;
+    my $needed_max_allowed = max($min_max_allowed, $max_attachment);
+    if ($current_max_allowed < $needed_max_allowed) {
+        warn install_string('max_allowed_packet',
+                            { current => $current_max_allowed,
+                              needed  => $needed_max_allowed }) . "\n";
+    }
+
+    # Make sure the installation has InnoDB turned on, or we're going to be
+    # doing silly things like making foreign keys on MyISAM tables, which is
+    # hard to fix later. We do this up here because none of the code below
+    # works if InnoDB is off. (Particularly if we've already converted the
+    # tables to InnoDB.)
+    my ($innodb_on) = @{$self->selectcol_arrayref(
+        q{SHOW VARIABLES LIKE '%have_innodb%'}, {Columns=>[2]})};
+    if ($innodb_on ne 'YES') {
+        print <<EOT;
+InnoDB is disabled in your MySQL installation. 
+Bugzilla requires InnoDB to be enabled. 
+Please enable it and then re-run checksetup.pl.
+
+EOT
+        exit 3;
+    }
+
+
     # Figure out if any existing tables are of type ISAM and convert them
     # to type MyISAM if so.  ISAM tables are deprecated in MySQL 3.23,
     # which Bugzilla now requires, and they don't support more than 16
     # indexes per table, which Bugzilla needs.
-    my $sth = $self->prepare("SHOW TABLE STATUS");
-    $sth->execute;
-    my @isam_tables = ();
-    while (my ($name, $type) = $sth->fetchrow_array) {
+    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";
     }
 
@@ -281,17 +295,52 @@
         print "\nISAM->MyISAM table conversion done.\n\n";
     }
 
-    # 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.
+    my ($sd_index_deleted, $longdescs_index_deleted);
     my @tables = $self->bz_table_list_real();
-    if (grep($_ eq 'bugs', @tables)
-        && $self->bz_column_info_real("bugs", "bug_id"))
-    {
-        $self->do('SELECT 1 FROM bugs WHERE bug_id IS NULL');
+    # We want to convert tables to InnoDB, but it's possible that they have 
+    # fulltext indexes on them, and conversion will fail unless we remove
+    # the indexes.
+    if (grep($_ eq 'bugs', @tables)) {
+        if ($self->bz_index_info_real('bugs', 'short_desc')) {
+            $self->bz_drop_index_raw('bugs', 'short_desc');
+        }
+        if ($self->bz_index_info_real('bugs', 'bugs_short_desc_idx')) {
+            $self->bz_drop_index_raw('bugs', 'bugs_short_desc_idx');
+            $sd_index_deleted = 1; # Used for later schema cleanup.
+        }
+    }
+    if (grep($_ eq 'longdescs', @tables)) {
+        if ($self->bz_index_info_real('longdescs', 'thetext')) {
+            $self->bz_drop_index_raw('longdescs', 'thetext');
+        }
+        if ($self->bz_index_info_real('longdescs', 'longdescs_thetext_idx')) {
+            $self->bz_drop_index_raw('longdescs', 'longdescs_thetext_idx');
+            $longdescs_index_deleted = 1; # For later schema cleanup.
+        }
     }
 
+    # Upgrade tables from MyISAM to InnoDB
+    my @myisam_tables;
+    foreach my $row (@$table_status) {
+        my ($name, $type) = @$row;
+        if ($type =~ /^MYISAM$/i 
+            && !grep($_ eq $name, Bugzilla::DB::Schema::Mysql::MYISAM_TABLES))
+        {
+            push(@myisam_tables, $name) ;
+        }
+    }
+    if (scalar @myisam_tables) {
+        print "Bugzilla now uses the InnoDB storage engine in MySQL for",
+              " most tables.\nConverting tables to InnoDB:\n";
+        foreach my $table (@myisam_tables) {
+            print "Converting table $table... ";
+            $self->do("ALTER TABLE $table TYPE = InnoDB");
+            print "done.\n";
+        }
+    }
+    
+    $self->_after_table_status(\@tables);
+    
     # Versions of Bugzilla before the existence of Bugzilla::DB::Schema did 
     # not provide explicit names for the table indexes. This means
     # that our upgrades will not be reliable, because we look for the name
@@ -461,10 +510,30 @@
         } # foreach table
     } # if old-name indexes
 
+    # If there are no tables, but the DB isn't utf8 and it should be,
+    # then we should alter the database to be utf8. We know it should be
+    # if the utf8 parameter is true or there are no params at all.
+    # This kind of situation happens when people create the database
+    # themselves, and if we don't do this they will get the big
+    # scary WARNING statement about conversion to UTF8.
+    if ( !$self->bz_db_is_utf8 && !@tables 
+         && (Bugzilla->params->{'utf8'} || !scalar keys %{Bugzilla->params}) )
+    {
+        $self->_alter_db_charset_to_utf8();
+    }
 
     # And now we create the tables and the Schema object.
     $self->SUPER::bz_setup_database();
 
+    if ($sd_index_deleted) {
+        $self->_bz_real_schema->delete_index('bugs', 'bugs_short_desc_idx');
+        $self->_bz_store_real_schema;
+    }
+    if ($longdescs_index_deleted) {
+        $self->_bz_real_schema->delete_index('longdescs', 
+                                             'longdescs_thetext_idx');
+        $self->_bz_store_real_schema;
+    }
 
     # The old timestamp fields need to be adjusted here instead of in
     # checksetup. Otherwise the UPDATE statements inside of bz_add_column
@@ -551,7 +620,16 @@
     }
 
     # Convert the database to UTF-8 if the utf8 parameter is on.
-    if (Bugzilla->params->{'utf8'} && !$self->bz_db_is_utf8) {
+    # We check if any table isn't utf8, because lots of crazy
+    # partial-conversion situations can happen, and this handles anything
+    # that could come up (including having the DB charset be utf8 but not
+    # the table charsets.
+    my $utf_table_status =
+        $self->selectall_arrayref("SHOW TABLE STATUS", {Slice=>{}});
+    $self->_after_table_status([map($_->{Name}, @$utf_table_status)]);
+    my @non_utf8_tables = grep($_->{Collation} !~ /^utf8/, @$utf_table_status);
+    
+    if (Bugzilla->params->{'utf8'} && scalar @non_utf8_tables) {
         print <<EOT;
 
 WARNING: We are about to convert your table storage format to UTF8. This
@@ -604,8 +682,7 @@
                     my $name = $column->{Field};
 
                     # The code below doesn't work on a field with a FULLTEXT
-                    # index. So we drop it. The upgrade code will re-create
-                    # it later.
+                    # 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');
@@ -613,6 +690,14 @@
                     if ($table eq 'bugs' && $name eq 'short_desc') {
                         $self->bz_drop_index('bugs', 'bugs_short_desc_idx');
                     }
+                    my %ft_indexes;
+                    if ($table eq 'bugs_fulltext') {
+                        %ft_indexes = $self->_bz_real_schema->get_indexes_on_column_abstract(
+                            'bugs_fulltext', $name);
+                        foreach my $index (keys %ft_indexes) {
+                            $self->bz_drop_index('bugs_fulltext', $index);
+                        }
+                    }
 
                     print "Converting $table.$name to be stored as UTF-8...\n";
                     my $col_info = 
@@ -636,15 +721,47 @@
                               $binary");
                     $self->do("ALTER TABLE $table CHANGE COLUMN $name $name 
                               $utf8");
+
+                    if ($table eq 'bugs_fulltext') {
+                        foreach my $index (keys %ft_indexes) {
+                            $self->bz_add_index('bugs_fulltext', $index,
+                                                $ft_indexes{$index});
+                        }
+                    }
                 }
             }
 
             $self->do("ALTER TABLE $table DEFAULT CHARACTER SET utf8");
         } # foreach my $table (@tables)
-
-        my $db_name = Bugzilla->localconfig->{db_name};
-        $self->do("ALTER DATABASE $db_name CHARACTER SET utf8");
     }
+
+    # Sometimes you can have a situation where all the tables are utf8,
+    # but the database isn't. (This tends to happen when you've done
+    # a mysqldump.) So we have this change outside of the above block,
+    # so that it just happens silently if no actual *table* conversion
+    # needs to happen.
+    if (Bugzilla->params->{'utf8'} && !$self->bz_db_is_utf8) {
+        $self->_alter_db_charset_to_utf8();
+    }
+}
+
+# There is a bug in MySQL 4.1.0 - 4.1.15 that makes certain SELECT
+# statements fail after a SHOW TABLE STATUS: 
+# http://bugs.mysql.com/bug.php?id=13535
+# This is a workaround, a dummy SELECT to reset the LAST_INSERT_ID.
+sub _after_table_status {
+    my ($self, $tables) = @_;
+    if (grep($_ eq 'bugs', @$tables)
+        && $self->bz_column_info_real("bugs", "bug_id"))
+    {
+        $self->do('SELECT 1 FROM bugs WHERE bug_id IS NULL');
+    }
+}
+
+sub _alter_db_charset_to_utf8 {
+    my $self = shift;
+    my $db_name = Bugzilla->localconfig->{db_name};
+    $self->do("ALTER DATABASE $db_name CHARACTER SET utf8"); 
 }
 
 sub bz_db_is_utf8 {
diff --git a/BugsSite/Bugzilla/DB/Oracle.pm b/BugsSite/Bugzilla/DB/Oracle.pm
new file mode 100644
index 0000000..ef3f62f
--- /dev/null
+++ b/BugsSite/Bugzilla/DB/Oracle.pm
@@ -0,0 +1,608 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Oracle Corporation. 
+# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation. 
+# All Rights Reserved.
+#
+# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
+#                 Xiaoou Wu   <xiaoou.wu@oracle.com>
+#                 Max Kanat-Alexander <mkanat@bugzilla.org>
+
+=head1 NAME
+
+Bugzilla::DB::Oracle - Bugzilla database compatibility layer for Oracle
+
+=head1 DESCRIPTION
+
+This module overrides methods of the Bugzilla::DB module with Oracle
+specific implementation. It is instantiated by the Bugzilla::DB module
+and should never be used directly.
+
+For interface details see L<Bugzilla::DB> and L<DBI>.
+
+=cut
+
+package Bugzilla::DB::Oracle;
+
+use strict;
+
+use DBD::Oracle;
+use DBD::Oracle qw(:ora_types);
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+# This module extends the DB interface via inheritance
+use base qw(Bugzilla::DB);
+
+#####################################################################
+# Constants
+#####################################################################
+use constant EMPTY_STRING  => '__BZ_EMPTY_STR__';
+use constant ISOLATION_LEVEL => 'READ COMMITTED';
+use constant BLOB_TYPE => { ora_type => ORA_BLOB };
+use constant GROUPBY_REGEXP => '((CASE\s+WHEN.+END)|(TO_CHAR\(.+\))|(\(SCORE.+\))|(\(MATCH.+\))|(\w+(\.\w+)?))(\s+AS\s+)?(.*)?$';
+
+sub new {
+    my ($class, $user, $pass, $host, $dbname, $port) = @_;
+
+    # You can never connect to Oracle without a DB name,
+    # and there is no default DB.
+    $dbname ||= Bugzilla->localconfig->{db_name};
+
+    # Set the language enviroment
+    $ENV{'NLS_LANG'} = '.AL32UTF8' if Bugzilla->params->{'utf8'};
+
+    # construct the DSN from the parameters we got
+    my $dsn = "DBI:Oracle:host=$host;sid=$dbname";
+    $dsn .= ";port=$port" if $port;
+    my $attrs = { FetchHashKeyName => 'NAME_lc',  
+                  LongReadLen => ( Bugzilla->params->{'maxattachmentsize'}
+                                     || 1000 ) * 1024, 
+                };
+    my $self = $class->db_new($dsn, $user, $pass, $attrs);
+
+    bless ($self, $class);
+
+    # Set the session's default date format to match MySQL
+    $self->do("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
+    $self->do("ALTER SESSION SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'");
+    $self->do("ALTER SESSION SET NLS_LENGTH_SEMANTICS='CHAR'") 
+        if Bugzilla->params->{'utf8'};
+    # To allow case insensitive query.
+    $self->do("ALTER SESSION SET NLS_COMP='ANSI'");
+    $self->do("ALTER SESSION SET NLS_SORT='BINARY_AI'");
+    return $self;
+}
+
+sub bz_last_key {
+    my ($self, $table, $column) = @_;
+
+    my $seq = $table . "_" . $column . "_SEQ";
+    my ($last_insert_id) = $self->selectrow_array("SELECT $seq.CURRVAL "
+                                                  . " FROM DUAL");
+    return $last_insert_id;
+}
+
+sub sql_regexp {
+    my ($self, $expr, $pattern) = @_;
+
+    return "REGEXP_LIKE($expr, $pattern)";
+}
+
+sub sql_not_regexp {
+    my ($self, $expr, $pattern) = @_;
+
+    return "NOT REGEXP_LIKE($expr, $pattern)" 
+}
+
+sub sql_limit {
+    my ($self, $limit, $offset) = @_;
+
+    if(defined $offset) {
+        return  "/* LIMIT $limit $offset */";
+    }
+    return "/* LIMIT $limit */";
+}
+
+sub sql_string_concat {
+    my ($self, @params) = @_;
+
+    return 'CONCAT(' . join(', ', @params) . ')';
+}
+
+sub sql_to_days {
+    my ($self, $date) = @_;
+
+    return " TO_CHAR(TO_DATE($date),'J') ";
+}
+sub sql_from_days{
+    my ($self, $date) = @_;
+
+    return " TO_DATE($date,'J') ";
+}
+sub sql_fulltext_search {
+    my ($self, $column, $text, $label) = @_;
+    $text = $self->quote($text);
+    trick_taint($text);
+    return "CONTAINS($column,$text,$label)", "SCORE($label)";
+}
+
+sub sql_date_format {
+    my ($self, $date, $format) = @_;
+    
+    $format = "%Y.%m.%d %H:%i:%s" if !$format;
+
+    $format =~ s/\%Y/YYYY/g;
+    $format =~ s/\%y/YY/g;
+    $format =~ s/\%m/MM/g;
+    $format =~ s/\%d/DD/g;
+    $format =~ s/\%a/Dy/g;
+    $format =~ s/\%H/HH24/g;
+    $format =~ s/\%i/MI/g;
+    $format =~ s/\%s/SS/g;
+
+    return "TO_CHAR($date, " . $self->quote($format) . ")";
+}
+
+sub sql_interval {
+    my ($self, $interval, $units) = @_;
+    if ($units =~ /YEAR|MONTH/i) {
+        return "NUMTOYMINTERVAL($interval,'$units')";
+    } else{
+        return "NUMTODSINTERVAL($interval,'$units')";
+    }
+}
+
+sub sql_position {
+    my ($self, $fragment, $text) = @_;
+    return "INSTR($text, $fragment)";
+}
+
+sub sql_in {
+    my ($self, $column_name, $in_list_ref) = @_;
+    my @in_list = @$in_list_ref;
+    return $self->SUPER::sql_in($column_name, $in_list_ref) if $#in_list < 1000;
+    my @in_str;
+    while (@in_list) {
+        my $length = $#in_list + 1;
+        my $splice = $length > 1000 ? 1000 : $length;
+        my @sub_in_list = splice(@in_list, 0, $splice);
+        push(@in_str, 
+             $self->SUPER::sql_in($column_name, \@sub_in_list)); 
+    }
+    return "( " . join(" OR ", @in_str) . " )";
+}
+
+sub bz_drop_table {
+     my ($self, $name) = @_;
+     my $table_exists = $self->bz_table_info($name);
+     if ($table_exists) {
+         $self->_bz_drop_fks($name);
+         $self->SUPER::bz_drop_table($name);
+     }
+}
+
+# Dropping all FKs for a specified table. 
+sub _bz_drop_fks {
+    my ($self, $table) = @_;
+    my @columns = $self->_bz_real_schema->get_table_columns($table);
+    foreach my $column (@columns) {
+        $self->bz_drop_fk($table, $column);
+    }
+}
+
+sub _fix_empty {
+    my ($string) = @_;
+    $string = '' if $string eq EMPTY_STRING;
+    return $string;
+}
+
+sub _fix_arrayref {
+    my ($row) = @_;
+    return undef if !defined $row;
+    foreach my $field (@$row) {
+        $field = _fix_empty($field) if defined $field;
+    }
+    return $row;
+}
+
+sub _fix_hashref {
+     my ($row) = @_;
+     return undef if !defined $row;
+     foreach my $value (values %$row) {
+       $value = _fix_empty($value) if defined $value;
+     }
+     return $row;
+}
+
+sub adjust_statement {
+    my ($sql) = @_;
+
+    # We can't just assume any occurrence of "''" in $sql is an empty
+    # string, since "''" can occur inside a string literal as a way of
+    # escaping a single "'" in the literal.  Therefore we must be trickier...
+
+    # split the statement into parts by single-quotes.  The negative value
+    # at the end to the split operator from dropping trailing empty strings
+    # (e.g., when $sql ends in "''")
+    my @parts = split /'/, $sql, -1;
+
+    if( !(@parts % 2) ) {
+        # Either the string is empty or the quotes are mismatched
+        # Returning input unmodified.
+        return $sql;
+    }
+
+    # We already verified that we have an odd number of parts.  If we take
+    # the first part off now, we know we're entering the loop with an even
+    # number of parts
+    my @result;
+    my $part = shift @parts;
+    
+    # Oracle requires a FROM clause in all SELECT statements, so append
+    # "FROM dual" to queries without one (e.g., "SELECT NOW()")
+    my $is_select = ($part =~ m/^\s*SELECT\b/io);
+    my $has_from =  ($part =~ m/\bFROM\b/io) if $is_select;
+
+    # Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
+    $part =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
+    
+    # Oracle use SUBSTR instead of SUBSTRING
+    $part =~ s/\bSUBSTRING\b/SUBSTR/io;
+   
+    # Oracle need no 'AS'
+    $part =~ s/\bAS\b//ig;
+    
+    # Oracle doesn't have LIMIT, so if we find the LIMIT comment, wrap the
+    # query with "SELECT * FROM (...) WHERE rownum < $limit"
+    my ($limit,$offset) = ($part =~ m{/\* LIMIT (\d*) (\d*) \*/}o);
+    
+    push @result, $part;
+    while( @parts ) {
+        my $string = shift @parts;
+        my $nonstring = shift @parts;
+    
+        # if the non-string part is zero-length and there are more parts left,
+        # then this is an escaped quote inside a string literal   
+        while( !(length $nonstring) && @parts  ) {
+            # we know it's safe to remove two parts at a time, since we
+            # entered the loop with an even number of parts
+            $string .= "''" . shift @parts;
+            $nonstring = shift @parts;
+        }
+
+        # Look for a FROM if this is a SELECT and we haven't found one yet
+        $has_from = ($nonstring =~ m/\bFROM\b/io) 
+                    if ($is_select and !$has_from);
+
+        # Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
+        $nonstring =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
+
+        # Oracle use SUBSTR instead of SUBSTRING
+        $nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
+        
+        # Oracle need no 'AS'
+        $nonstring =~ s/\bAS\b//ig;
+
+        # Look for a LIMIT clause
+        ($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
+
+        if(!length($string)){
+           push @result, EMPTY_STRING;
+           push @result, $nonstring;
+        } else {
+           push @result, $string;
+           push @result, $nonstring;
+        }
+    }
+
+    my $new_sql = join "'", @result;
+
+    # Append "FROM dual" if this is a SELECT without a FROM clause
+    $new_sql .= " FROM DUAL" if ($is_select and !$has_from);
+
+    # Wrap the query with a "WHERE rownum <= ..." if we found LIMIT
+    
+    if (defined($limit)) {
+        if ($new_sql !~ /\bWHERE\b/) {
+            $new_sql = $new_sql." WHERE 1=1";
+        }
+         my ($before_where, $after_where) = split /\bWHERE\b/i,$new_sql;
+         if (defined($offset)) {
+             if ($new_sql =~ /(.*\s+)FROM(\s+.*)/i) { 
+                 my ($before_from,$after_from) = ($1,$2);
+                 $before_where = "$before_from FROM ($before_from,"
+                             . " ROW_NUMBER() OVER (ORDER BY 1) R "
+                             . " FROM $after_from ) "; 
+                 $after_where = " R BETWEEN $offset+1 AND $limit+$offset";
+             }
+         } else {
+                 $after_where = " rownum <=$limit AND ".$after_where;
+         }
+
+         $new_sql = $before_where." WHERE ".$after_where;
+    }
+    return $new_sql;
+}
+
+sub do {
+    my $self = shift;
+    my $sql  = shift;
+    $sql = adjust_statement($sql);
+    unshift @_, $sql;
+    return $self->SUPER::do(@_);
+}
+
+sub selectrow_array {
+    my $self = shift;
+    my $stmt = shift;
+    my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+    unshift @_, $new_stmt;
+    if ( wantarray ) {
+        my @row = $self->SUPER::selectrow_array(@_);
+        _fix_arrayref(\@row);
+        return @row;
+    } else {
+        my $row = $self->SUPER::selectrow_array(@_);
+        $row = _fix_empty($row) if defined $row;
+        return $row;
+    }
+}
+
+sub selectrow_arrayref {
+    my $self = shift;
+    my $stmt = shift;
+    my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+    unshift @_, $new_stmt;
+    my $ref = $self->SUPER::selectrow_arrayref(@_);
+    return undef if !defined $ref;
+
+    _fix_arrayref($ref);
+    return $ref;
+}
+
+sub selectrow_hashref {
+    my $self = shift;
+    my $stmt = shift;
+    my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+    unshift @_, $new_stmt;
+    my $ref = $self->SUPER::selectrow_hashref(@_);
+    return undef if !defined $ref;
+
+    _fix_hashref($ref);
+    return $ref;
+}
+
+sub selectall_arrayref {
+    my $self = shift;
+    my $stmt = shift;
+    my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+    unshift @_, $new_stmt;
+    my $ref = $self->SUPER::selectall_arrayref(@_);
+    return undef if !defined $ref;
+    
+    foreach my $row (@$ref) {
+       if (ref($row) eq 'ARRAY') {
+            _fix_arrayref($row);
+       }
+       elsif (ref($row) eq 'HASH') {
+            _fix_hashref($row);
+       }
+    }
+
+    return $ref;
+}
+
+sub selectall_hashref {
+    my $self = shift;
+    my $stmt = shift;
+    my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+    unshift @_, $new_stmt;
+    my $rows = $self->SUPER::selectall_hashref(@_);
+    return undef if !defined $rows;
+    foreach my $row (values %$rows) { 
+          _fix_hashref($row);
+    }
+    return $rows;
+}
+
+sub selectcol_arrayref {
+    my $self = shift;
+    my $stmt = shift;
+    my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
+    unshift @_, $new_stmt;
+    my $ref = $self->SUPER::selectcol_arrayref(@_);
+    return undef if !defined $ref;
+    _fix_arrayref($ref);
+    return $ref;
+}
+
+sub prepare {
+    my $self = shift;
+    my $sql  = shift;
+    my $new_sql = adjust_statement($sql);
+    unshift @_, $new_sql;
+    return bless $self->SUPER::prepare(@_),
+                        'Bugzilla::DB::Oracle::st';
+}
+
+sub prepare_cached {
+    my $self = shift;
+    my $sql  = shift;
+    my $new_sql = adjust_statement($sql);
+    unshift @_, $new_sql;
+    return bless $self->SUPER::prepare_cached(@_),
+                      'Bugzilla::DB::Oracle::st';
+}
+
+sub quote_identifier {
+     my ($self,$id) = @_;
+     return $id;
+}
+
+#####################################################################
+# Protected "Real Database" Schema Information Methods
+#####################################################################
+
+sub bz_table_columns_real {
+    my ($self, $table) = @_;
+    $table = uc($table);
+    my $cols = $self->selectcol_arrayref(
+        "SELECT LOWER(COLUMN_NAME) FROM USER_TAB_COLUMNS WHERE 
+         TABLE_NAME = ?  ORDER BY COLUMN_NAME", undef, $table);
+    return @$cols;
+}
+
+sub bz_table_list_real {
+    my ($self) = @_;
+    my $tables = $self->selectcol_arrayref(
+        "SELECT LOWER(TABLE_NAME) FROM USER_TABLES WHERE 
+        TABLE_NAME NOT LIKE ?  ORDER BY TABLE_NAME", undef, 'DR$%');
+    return @$tables;
+}
+
+#####################################################################
+# Custom Database Setup
+#####################################################################
+
+sub bz_setup_database {
+    my $self = shift;
+    
+    # Create a function that returns SYSDATE to emulate MySQL's "NOW()".
+    # Function NOW() is used widely in Bugzilla SQLs, but Oracle does not 
+    # have that function, So we have to create one ourself. 
+    $self->do("CREATE OR REPLACE FUNCTION NOW "
+              . " RETURN DATE IS BEGIN RETURN SYSDATE; END;");
+    $self->do("CREATE OR REPLACE FUNCTION CHAR_LENGTH(COLUMN_NAME VARCHAR2)" 
+              . " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
+    # Create a WORLD_LEXER named BZ_LEX for multilingual fulltext search
+    my $lexer = $self->selectcol_arrayref(
+       "SELECT pre_name FROM CTXSYS.CTX_PREFERENCES WHERE pre_name = ? AND
+          pre_owner = ?",
+          undef,'BZ_LEX',uc(Bugzilla->localconfig->{db_user}));
+    if(!@$lexer) {
+       $self->do("BEGIN CTX_DDL.CREATE_PREFERENCE
+                        ('BZ_LEX', 'WORLD_LEXER'); END;");
+    }
+
+    $self->SUPER::bz_setup_database(@_);
+
+    my @tables = $self->bz_table_list_real();
+    foreach my $table (@tables) {
+        my @columns = $self->bz_table_columns_real($table);
+        foreach my $column (@columns) {
+            my $def = $self->bz_column_info($table, $column);
+            if ($def->{REFERENCES}) {
+                my $references = $def->{REFERENCES};
+                my $update = $references->{UPDATE} || 'CASCADE';
+                my $to_table  = $references->{TABLE};
+                my $to_column = $references->{COLUMN};
+                my $fk_name = $self->_bz_schema->_get_fk_name($table,
+                                                              $column,
+                                                              $references);
+                if ( $update =~ /CASCADE/i ){
+                     my $trigger_name = uc($fk_name . "_UC");
+                     my $exist_trigger = $self->selectcol_arrayref(
+                         "SELECT OBJECT_NAME FROM USER_OBJECTS 
+                          WHERE OBJECT_NAME = ?", undef, $trigger_name);
+                    if(@$exist_trigger) {
+                        $self->do("DROP TRIGGER $trigger_name");
+                    }
+  
+                     my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
+                         . " AFTER  UPDATE  ON ". $to_table
+                         . " REFERENCING "
+                         . " NEW AS NEW "
+                         . " OLD AS OLD "
+                         . " FOR EACH ROW "
+                         . " BEGIN "
+                         . "     UPDATE $table"
+                         . "        SET $column = :NEW.$to_column"
+                         . "      WHERE $column = :OLD.$to_column;"
+                         . " END $trigger_name;";
+                         $self->do($tr_str);
+               }
+         }
+     }
+   }
+
+}
+
+package Bugzilla::DB::Oracle::st;
+use base qw(DBI::st);
+ 
+sub fetchrow_arrayref {
+    my $self = shift;
+    my $ref = $self->SUPER::fetchrow_arrayref(@_);
+    return undef if !defined $ref;
+    Bugzilla::DB::Oracle::_fix_arrayref($ref);
+    return $ref;
+}
+
+sub fetchrow_array {
+    my $self = shift;
+    if ( wantarray ) {
+        my @row = $self->SUPER::fetchrow_array(@_);
+        Bugzilla::DB::Oracle::_fix_arrayref(\@row);
+        return @row;
+    } else {
+        my $row = $self->SUPER::fetchrow_array(@_);
+        $row = Bugzilla::DB::Oracle::_fix_empty($row) if defined $row;
+        return $row;
+    }
+}
+
+sub fetchrow_hashref {
+    my $self = shift;
+    my $ref = $self->SUPER::fetchrow_hashref(@_);
+    return undef if !defined $ref;
+    Bugzilla::DB::Oracle::_fix_hashref($ref);
+    return $ref;
+}
+
+sub fetchall_arrayref {
+    my $self = shift;
+    my $ref = $self->SUPER::fetchall_arrayref(@_);
+    return undef if !defined $ref;
+    foreach my $row (@$ref) {
+        if (ref($row) eq 'ARRAY') {
+             Bugzilla::DB::Oracle::_fix_arrayref($row);
+        }
+        elsif (ref($row) eq 'HASH') {
+            Bugzilla::DB::Oracle::_fix_hashref($row);
+        }
+    }
+    return $ref;
+}
+
+sub fetchall_hashref {
+    my $self = shift;
+    my $ref = $self->SUPER::fetchall_hashref(@_);
+    return undef if !defined $ref;
+    foreach my $row (values %$ref) {
+         Bugzilla::DB::Oracle::_fix_hashref($row);
+    }
+     return $ref;
+}
+
+sub fetch {
+    my $self = shift;
+    my $row = $self->SUPER::fetch(@_);
+    if ($row) {
+      Bugzilla::DB::Oracle::_fix_arrayref($row);
+    }
+   return $row;
+}
+1;
diff --git a/BugsSite/Bugzilla/DB/Pg.pm b/BugsSite/Bugzilla/DB/Pg.pm
index 9f5b677..4777ba8 100644
--- a/BugsSite/Bugzilla/DB/Pg.pm
+++ b/BugsSite/Bugzilla/DB/Pg.pm
@@ -68,7 +68,9 @@
     # creating tables.
     $dsn .= ";options='-c client_min_messages=warning'";
 
-    my $self = $class->db_new($dsn, $user, $pass);
+    my $attrs = { pg_enable_utf8 => Bugzilla->params->{'utf8'} };
+
+    my $self = $class->db_new($dsn, $user, $pass, $attrs);
 
     # all class local variables stored in DBI derived class needs to have
     # a prefix 'private_'. See DBI documentation.
@@ -156,62 +158,6 @@
     return '(CAST(' . join(' AS text) || CAST(', @params) . ' AS text))';
 }
 
-sub bz_lock_tables {
-    my ($self, @tables) = @_;
-   
-    my $list = join(', ', @tables);
-    # Check first if there was no lock before
-    if ($self->{private_bz_tables_locked}) {
-        ThrowCodeError("already_locked", { current => $self->{private_bz_tables_locked},
-                                           new => $list });
-    } else {
-        my %read_tables;
-        my %write_tables;
-        foreach my $table (@tables) {
-            $table =~ /^([\d\w]+)([\s]+AS[\s]+[\d\w]+)?[\s]+(WRITE|READ)$/i;
-            my $table_name = $1;
-            if ($3 =~ /READ/i) {
-                if (!exists $read_tables{$table_name}) {
-                    $read_tables{$table_name} = undef;
-                }
-            }
-            else {
-                if (!exists $write_tables{$table_name}) {
-                    $write_tables{$table_name} = undef;
-                }
-            }
-        }
-    
-        # Begin Transaction
-        $self->bz_start_transaction();
-        
-        Bugzilla->dbh->do('LOCK TABLE ' . join(', ', keys %read_tables) .
-                          ' IN ROW SHARE MODE') if keys %read_tables;
-        Bugzilla->dbh->do('LOCK TABLE ' . join(', ', keys %write_tables) .
-                          ' IN ROW EXCLUSIVE MODE') if keys %write_tables;
-        $self->{private_bz_tables_locked} = $list;
-    }
-}
-
-sub bz_unlock_tables {
-    my ($self, $abort) = @_;
-    
-    # Check first if there was previous matching lock
-    if (!$self->{private_bz_tables_locked}) {
-        # Abort is allowed even without previous lock for error handling
-        return if $abort;
-        ThrowCodeError("no_matching_lock");
-    } else {
-        $self->{private_bz_tables_locked} = "";
-        # End transaction, tables will be unlocked automatically
-        if ($abort) {
-            $self->bz_rollback_transaction();
-        } else {
-            $self->bz_commit_transaction();
-        }
-    }
-}
-
 # Tell us whether or not a particular sequence exists in the DB.
 sub bz_sequence_exists {
     my ($self, $seq_name) = @_;
@@ -233,6 +179,10 @@
     # field, because it can't have index data longer than 2770
     # characters on that field.
     $self->bz_drop_index('longdescs', 'longdescs_thetext_idx');
+    # Same for all the comments fields in the fulltext table.
+    $self->bz_drop_index('bugs_fulltext', 'bugs_fulltext_comments_idx');
+    $self->bz_drop_index('bugs_fulltext', 
+                         'bugs_fulltext_comments_noprivate_idx');
 
     # PostgreSQL also wants an index for calling LOWER on
     # login_name, which we do with sql_istrcmp all over the place.
diff --git a/BugsSite/Bugzilla/DB/Schema.pm b/BugsSite/Bugzilla/DB/Schema.pm
index f4c1c30..02e4bfd 100644
--- a/BugsSite/Bugzilla/DB/Schema.pm
+++ b/BugsSite/Bugzilla/DB/Schema.pm
@@ -40,6 +40,8 @@
 use Bugzilla::Util;
 use Bugzilla::Constants;
 
+use Carp qw(confess);
+use Digest::MD5 qw(md5_hex);
 use Hash::Util qw(lock_value unlock_hash lock_keys unlock_keys);
 use Safe;
 # Historical, needed for SCHEMA_VERSION = '1.00'
@@ -96,7 +98,7 @@
 
 =head1 CONSTANTS
 
-=over 4
+=over
 
 =item C<SCHEMA_VERSION>
 
@@ -157,9 +159,57 @@
 
 =back
 
+=head2 Referential Integrity
+
+Bugzilla::DB::Schema supports "foreign keys", a way of saying
+that "Column X may only contain values from Column Y in Table Z".
+For example, in Bugzilla, bugs.resolution should only contain
+values from the resolution.values field.
+
+It does this by adding an additional item to a column, called C<REFERENCES>.
+This is a hash with the following members:
+
+=over
+
+=item C<TABLE>
+
+The table the foreign key points at
+
+=item C<COLUMN>
+
+The column pointed at in that table.
+
+=item C<DELETE>
+
+What to do if the row in the parent table is deleted. Choices are
+C<RESTRICT>, C<CASCADE>, or C<SET NULL>. 
+
+C<RESTRICT> means the deletion of the row in the parent table will 
+be forbidden by the database if there is a row in I<this> table that 
+still refers to it. This is the default, if you don't specify
+C<DELETE>.
+
+C<CASCADE> means that this row will be deleted along with that row.
+
+C<SET NULL> means that the column will be set to C<NULL> when the parent
+row is deleted. Note that this is only valid if the column can actually
+be set to C<NULL>. (That is, the column isn't C<NOT NULL>.)
+
+=item C<UPDATE>
+
+What to do if the value in the parent table is updated. You can set this
+to C<CASCADE> or C<RESTRICT>, which mean the same thing as they do for
+L</DELETE>. This variable defaults to C<CASCADE>, which means "also 
+update this column in this table."
+
+=back
+
 =cut
 
 use constant SCHEMA_VERSION  => '2.00';
+use constant ADD_COLUMN      => 'ADD COLUMN';
+# This is a reasonable default that's true for both PostgreSQL and MySQL.
+use constant MAX_IDENTIFIER_LEN => 63;
 use constant ABSTRACT_SCHEMA => {
 
     # BUG-RELATED TABLES
@@ -172,7 +222,7 @@
             bug_id              => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
                                     PRIMARYKEY => 1},
             assigned_to         => {TYPE => 'INT3', NOTNULL => 1},
-            bug_file_loc        => {TYPE => 'TEXT'},
+            bug_file_loc        => {TYPE => 'MEDIUMTEXT'},
             bug_severity        => {TYPE => 'varchar(64)', NOTNULL => 1},
             bug_status          => {TYPE => 'varchar(64)', NOTNULL => 1},
             creation_ts         => {TYPE => 'DATETIME'},
@@ -232,11 +282,36 @@
         ],
     },
 
+    bugs_fulltext => {
+        FIELDS => [
+            bug_id     => {TYPE => 'INT3', NOTNULL => 1, PRIMARYKEY => 1,
+                           REFERENCES => {TABLE  => 'bugs',
+                                          COLUMN => 'bug_id',
+                                          DELETE => 'CASCADE'}},
+            short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
+            # Comments are stored all together in one column for searching.
+            # This allows us to examine all comments together when deciding
+            # the relevance of a bug in fulltext search.
+            comments   => {TYPE => 'LONGTEXT'},
+            comments_noprivate => {TYPE => 'LONGTEXT'},
+        ],
+        INDEXES => [
+            bugs_fulltext_short_desc_idx => {FIELDS => ['short_desc'],
+                                               TYPE => 'FULLTEXT'},
+            bugs_fulltext_comments_idx   => {FIELDS => ['comments'],
+                                               TYPE => 'FULLTEXT'},
+            bugs_fulltext_comments_noprivate_idx => {
+                FIELDS => ['comments_noprivate'], TYPE => 'FULLTEXT'},
+        ],
+    },
+
     bugs_activity => {
         FIELDS => [
             bug_id    => {TYPE => 'INT3', NOTNULL => 1},
             attach_id => {TYPE => 'INT3'},
-            who       => {TYPE => 'INT3', NOTNULL => 1},
+            who       => {TYPE => 'INT3', NOTNULL => 1,
+                          REFERENCES => {TABLE  => 'profiles',
+                                         COLUMN => 'userid'}},
             bug_when  => {TYPE => 'DATETIME', NOTNULL => 1},
             fieldid   => {TYPE => 'INT3', NOTNULL => 1},
             added     => {TYPE => 'TINYTEXT'},
@@ -253,7 +328,10 @@
     cc => {
         FIELDS => [
             bug_id => {TYPE => 'INT3', NOTNULL => 1},
-            who    => {TYPE => 'INT3', NOTNULL => 1},
+            who    => {TYPE => 'INT3', NOTNULL => 1,
+                       REFERENCES => {TABLE  => 'profiles',
+                                      COLUMN => 'userid',
+                                      DELETE => 'CASCADE'}},
         ],
         INDEXES => [
             cc_bug_id_idx => {FIELDS => [qw(bug_id who)],
@@ -271,7 +349,7 @@
             bug_when        => {TYPE => 'DATETIME', NOTNULL => 1},
             work_time       => {TYPE => 'decimal(5,2)', NOTNULL => 1,
                                 DEFAULT => '0'},
-            thetext         => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+            thetext         => {TYPE => 'LONGTEXT', NOTNULL => 1},
             isprivate       => {TYPE => 'BOOLEAN', NOTNULL => 1,
                                 DEFAULT => 'FALSE'},
             already_wrapped => {TYPE => 'BOOLEAN', NOTNULL => 1,
@@ -284,8 +362,6 @@
             longdescs_bug_id_idx   => ['bug_id'],
             longdescs_who_idx     => [qw(who bug_id)],
             longdescs_bug_when_idx => ['bug_when'],
-            longdescs_thetext_idx => {FIELDS => ['thetext'],
-                                      TYPE => 'FULLTEXT'},
         ],
     },
 
@@ -302,7 +378,10 @@
 
     votes => {
         FIELDS => [
-            who        => {TYPE => 'INT3', NOTNULL => 1},
+            who        => {TYPE => 'INT3', NOTNULL => 1,
+                           REFERENCES => {TABLE  => 'profiles',
+                                          COLUMN => 'userid',
+                                          DELETE => 'CASCADE'}},
             bug_id     => {TYPE => 'INT3', NOTNULL => 1},
             vote_count => {TYPE => 'INT2', NOTNULL => 1},
         ],
@@ -318,11 +397,14 @@
                              PRIMARYKEY => 1},
             bug_id       => {TYPE => 'INT3', NOTNULL => 1},
             creation_ts  => {TYPE => 'DATETIME', NOTNULL => 1},
-            description  => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
-            mimetype     => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+            modification_time => {TYPE => 'DATETIME', NOTNULL => 1},
+            description  => {TYPE => 'TINYTEXT', NOTNULL => 1},
+            mimetype     => {TYPE => 'TINYTEXT', NOTNULL => 1},
             ispatch      => {TYPE => 'BOOLEAN'},
             filename     => {TYPE => 'varchar(100)', NOTNULL => 1},
-            submitter_id => {TYPE => 'INT3', NOTNULL => 1},
+            submitter_id => {TYPE => 'INT3', NOTNULL => 1,
+                             REFERENCES => {TABLE => 'profiles',
+                                            COLUMN => 'userid'}},
             isobsolete   => {TYPE => 'BOOLEAN', NOTNULL => 1,
                              DEFAULT => 'FALSE'},
             isprivate    => {TYPE => 'BOOLEAN', NOTNULL => 1,
@@ -333,6 +415,7 @@
         INDEXES => [
             attachments_bug_id_idx => ['bug_id'],
             attachments_creation_ts_idx => ['creation_ts'],
+            attachments_modification_time_idx => ['modification_time'],
             attachments_submitter_id_idx => ['submitter_id', 'bug_id'],
         ],
     },
@@ -411,7 +494,7 @@
             id               => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
                                  PRIMARYKEY => 1},
             name             => {TYPE => 'varchar(50)', NOTNULL => 1},
-            description      => {TYPE => 'TEXT'},
+            description      => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
             cc_list          => {TYPE => 'varchar(200)'},
             target_type      => {TYPE => 'char(1)', NOTNULL => 1,
                                  DEFAULT => "'b'"},
@@ -469,7 +552,7 @@
                             DEFAULT => FIELD_TYPE_UNKNOWN},
             custom      => {TYPE => 'BOOLEAN', NOTNULL => 1,
                             DEFAULT => 'FALSE'},
-            description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+            description => {TYPE => 'TINYTEXT', NOTNULL => 1},
             mailhead    => {TYPE => 'BOOLEAN', NOTNULL => 1,
                             DEFAULT => 'FALSE'},
             sortkey     => {TYPE => 'INT2', NOTNULL => 1},
@@ -527,6 +610,7 @@
             sortkey  => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
             isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, 
                          DEFAULT => 'TRUE'},
+            is_open  => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
         ],
         INDEXES => [
             bug_status_value_idx  => {FIELDS => ['value'],
@@ -615,6 +699,19 @@
         ],
     },
 
+    status_workflow => {
+        FIELDS => [
+            # On bug creation, there is no old value.
+            old_status      => {TYPE => 'INT2'},
+            new_status      => {TYPE => 'INT2', NOTNULL => 1},
+            require_comment => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => 0},
+        ],
+        INDEXES => [
+            status_workflow_idx  => {FIELDS => ['old_status', 'new_status'],
+                                     TYPE => 'UNIQUE'},
+        ],
+    },
+
     # USER INFO
     # ---------
 
@@ -645,10 +742,17 @@
 
     profiles_activity => {
         FIELDS => [
-            userid        => {TYPE => 'INT3', NOTNULL => 1},
-            who           => {TYPE => 'INT3', NOTNULL => 1},
+            userid        => {TYPE => 'INT3', NOTNULL => 1,
+                              REFERENCES => {TABLE  => 'profiles', 
+                                             COLUMN => 'userid',
+                                             DELETE => 'CASCADE'}},
+            who           => {TYPE => 'INT3', NOTNULL => 1,
+                              REFERENCES => {TABLE  => 'profiles',
+                                             COLUMN => 'userid'}},
             profiles_when => {TYPE => 'DATETIME', NOTNULL => 1},
-            fieldid       => {TYPE => 'INT3', NOTNULL => 1},
+            fieldid       => {TYPE => 'INT3', NOTNULL => 1,
+                              REFERENCES => {TABLE  => 'fielddefs',
+                                             COLUMN => 'id'}},
             oldvalue      => {TYPE => 'TINYTEXT'},
             newvalue      => {TYPE => 'TINYTEXT'},
         ],
@@ -661,7 +765,10 @@
 
     email_setting => {
         FIELDS => [
-            user_id      => {TYPE => 'INT3', NOTNULL => 1},
+            user_id      => {TYPE => 'INT3', NOTNULL => 1,
+                             REFERENCES => {TABLE  => 'profiles',
+                                            COLUMN => 'userid',
+                                            DELETE => 'CASCADE'}},
             relationship => {TYPE => 'INT1', NOTNULL => 1},
             event        => {TYPE => 'INT1', NOTNULL => 1},
         ],
@@ -674,8 +781,14 @@
 
     watch => {
         FIELDS => [
-            watcher => {TYPE => 'INT3', NOTNULL => 1},
-            watched => {TYPE => 'INT3', NOTNULL => 1},
+            watcher => {TYPE => 'INT3', NOTNULL => 1,
+                        REFERENCES => {TABLE  => 'profiles',
+                                       COLUMN => 'userid',
+                                       DELETE => 'CASCADE'}},
+            watched => {TYPE => 'INT3', NOTNULL => 1,
+                        REFERENCES => {TABLE  => 'profiles',
+                                       COLUMN => 'userid',
+                                       DELETE => 'CASCADE'}},
         ],
         INDEXES => [
             watch_watcher_idx => {FIELDS => [qw(watcher watched)],
@@ -688,10 +801,13 @@
         FIELDS => [
             id           => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
                              PRIMARYKEY => 1},
-            userid       => {TYPE => 'INT3', NOTNULL => 1},
+            userid       => {TYPE => 'INT3', NOTNULL => 1,
+                             REFERENCES => {TABLE  => 'profiles',
+                                            COLUMN => 'userid',
+                                            DELETE => 'CASCADE'}},
             name         => {TYPE => 'varchar(64)', NOTNULL => 1},
-            query        => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
-            query_type   => {TYPE => 'BOOLEAN', NOTNULL => 1},
+            query        => {TYPE => 'LONGTEXT', NOTNULL => 1},
+            query_type   => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0},
         ],
         INDEXES => [
             namedqueries_userid_idx => {FIELDS => [qw(userid name)],
@@ -701,8 +817,14 @@
 
     namedqueries_link_in_footer => {
         FIELDS => [
-            namedquery_id => {TYPE => 'INT3', NOTNULL => 1},
-            user_id       => {TYPE => 'INT3', NOTNULL => 1},
+            namedquery_id => {TYPE => 'INT3', NOTNULL => 1,
+                              REFERENCES => {TABLE  => 'namedqueries',
+                                             COLUMN => 'id',
+                                             DELETE => 'CASCADE'}},
+            user_id       => {TYPE => 'INT3', NOTNULL => 1,
+                              REFERENCES => {TABLE  => 'profiles',
+                                             COLUMN => 'userid',
+                                             DELETE => 'CASCADE'}},
         ],
         INDEXES => [
             namedqueries_link_in_footer_id_idx => {FIELDS => [qw(namedquery_id user_id)],
@@ -714,7 +836,10 @@
     component_cc => {
 
         FIELDS => [
-            user_id      => {TYPE => 'INT3', NOTNULL => 1},
+            user_id      => {TYPE => 'INT3', NOTNULL => 1,
+                             REFERENCES => {TABLE  => 'profiles',
+                                            COLUMN => 'userid',
+                                            DELETE => 'CASCADE'}},
             component_id => {TYPE => 'INT2', NOTNULL => 1},
         ],
         INDEXES => [
@@ -730,7 +855,10 @@
         FIELDS => [
             cookie   => {TYPE => 'varchar(16)', NOTNULL => 1,
                          PRIMARYKEY => 1},
-            userid   => {TYPE => 'INT3', NOTNULL => 1},
+            userid   => {TYPE => 'INT3', NOTNULL => 1,
+                         REFERENCES => {TABLE  => 'profiles',
+                                        COLUMN => 'userid',
+                                        DELETE => 'CASCADE'}},
             ipaddr   => {TYPE => 'varchar(40)', NOTNULL => 1},
             lastused => {TYPE => 'DATETIME', NOTNULL => 1},
         ],
@@ -744,7 +872,9 @@
     #     for these changes.
     tokens => {
         FIELDS => [
-            userid    => {TYPE => 'INT3'},
+            userid    => {TYPE => 'INT3', REFERENCES => {TABLE  => 'profiles',
+                                                         COLUMN => 'userid',
+                                                         DELETE => 'CASCADE'}},
             issuedate => {TYPE => 'DATETIME', NOTNULL => 1} ,
             token     => {TYPE => 'varchar(16)', NOTNULL => 1,
                           PRIMARYKEY => 1},
@@ -764,12 +894,13 @@
             id           => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
                              PRIMARYKEY => 1},
             name         => {TYPE => 'varchar(255)', NOTNULL => 1},
-            description  => {TYPE => 'TEXT', NOTNULL => 1},
+            description  => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
             isbuggroup   => {TYPE => 'BOOLEAN', NOTNULL => 1},
             userregexp   => {TYPE => 'TINYTEXT', NOTNULL => 1,
                              DEFAULT => "''"},
             isactive     => {TYPE => 'BOOLEAN', NOTNULL => 1,
                              DEFAULT => 'TRUE'},
+            icon_url     => {TYPE => 'TINYTEXT'},
         ],
         INDEXES => [
             groups_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},
@@ -932,8 +1063,13 @@
                                  PRIMARYKEY => 1},
             name             => {TYPE => 'varchar(64)', NOTNULL => 1},
             product_id       => {TYPE => 'INT2', NOTNULL => 1},
-            initialowner     => {TYPE => 'INT3', NOTNULL => 1},
-            initialqacontact => {TYPE => 'INT3'},
+            initialowner     => {TYPE => 'INT3', NOTNULL => 1,
+                                 REFERENCES => {TABLE  => 'profiles',
+                                                COLUMN => 'userid'}},
+            initialqacontact => {TYPE => 'INT3',
+                                 REFERENCES => {TABLE  => 'profiles',
+                                                COLUMN => 'userid',
+                                                DELETE => 'SET NULL'}},
             description      => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
         ],
         INDEXES => [
@@ -999,7 +1135,10 @@
         FIELDS => [
             id            => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
                               NOTNULL => 1},
-            eventid       => {TYPE => 'INT3', NOTNULL => 1},
+            eventid       => {TYPE => 'INT3', NOTNULL => 1,
+                              REFERENCES => {TABLE => 'whine_events',
+                                             COLUMN => 'id',
+                                             DELETE => 'CASCADE'}},
             query_name    => {TYPE => 'varchar(64)', NOTNULL => 1,
                               DEFAULT => "''"},
             sortkey       => {TYPE => 'INT2', NOTNULL => 1,
@@ -1018,7 +1157,10 @@
         FIELDS => [
             id          => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
                             NOTNULL => 1},
-            eventid     => {TYPE => 'INT3', NOTNULL => 1},
+            eventid     => {TYPE => 'INT3', NOTNULL => 1,
+                            REFERENCES => {TABLE  => 'whine_events',
+                                           COLUMN => 'id',
+                                           DELETE => 'CASCADE'}},
             run_day     => {TYPE => 'varchar(32)'},
             run_time    => {TYPE => 'varchar(32)'},
             run_next    => {TYPE => 'DATETIME'},
@@ -1035,7 +1177,10 @@
         FIELDS => [
             id           => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
                              NOTNULL => 1},
-            owner_userid => {TYPE => 'INT3', NOTNULL => 1},
+            owner_userid => {TYPE => 'INT3', NOTNULL => 1,
+                             REFERENCES => {TABLE  => 'profiles', 
+                                            COLUMN => 'userid',
+                                            DELETE => 'CASCADE'}},
             subject      => {TYPE => 'varchar(128)'},
             body         => {TYPE => 'MEDIUMTEXT'},
         ],
@@ -1049,7 +1194,7 @@
             quipid   => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
                          PRIMARYKEY => 1},
             userid   => {TYPE => 'INT3'},
-            quip     => {TYPE => 'TEXT', NOTNULL => 1},
+            quip     => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
             approved => {TYPE => 'BOOLEAN', NOTNULL => 1,
                          DEFAULT => 'TRUE'},
         ],
@@ -1095,7 +1240,10 @@
 
     profile_setting => {
         FIELDS => [
-            user_id       => {TYPE => 'INT3', NOTNULL => 1},
+            user_id       => {TYPE => 'INT3', NOTNULL => 1,
+                              REFERENCES => {TABLE  => 'profiles',
+                                             COLUMN => 'userid',
+                                             DELETE => 'CASCADE'}},
             setting_name  => {TYPE => 'varchar(32)', NOTNULL => 1},
             setting_value => {TYPE => 'varchar(32)', NOTNULL => 1},
         ],
@@ -1133,6 +1281,18 @@
         sortkey_idx => ['sortkey', 'value'],
     ],
 };
+
+use constant MULTI_SELECT_VALUE_TABLE => {
+    FIELDS => [
+        bug_id => {TYPE => 'INT3', NOTNULL => 1},
+        value  => {TYPE => 'varchar(64)', NOTNULL => 1},
+    ],
+    INDEXES => [
+        bug_id_idx => {FIELDS => [qw( bug_id value)], TYPE => 'UNIQUE'},
+    ],
+};
+
+
 #--------------------------------------------------------------------------
 
 =head1 METHODS
@@ -1285,25 +1445,39 @@
 
 =item C<get_type_ddl>
 
- Description: Public method to convert abstract (database-generic) field
-              specifiers to database-specific data types suitable for use
-              in a C<CREATE TABLE> or C<ALTER TABLE> SQL statment. If no
-              database-specific field type has been defined for the given
-              field type, then it will just return the same field type.
- Parameters:  a hash or a reference to a hash of a field containing the
-              following keys: C<TYPE> (required), C<NOTNULL> (optional),
-              C<DEFAULT> (optional), C<PRIMARYKEY> (optional), C<REFERENCES>
-              (optional)
- Returns:     a DDL string suitable for describing a field in a
-              C<CREATE TABLE> or C<ALTER TABLE> SQL statement
+=over
+
+=item B<Description>
+
+Public method to convert abstract (database-generic) field specifiers to
+database-specific data types suitable for use in a C<CREATE TABLE> or 
+C<ALTER TABLE> SQL statment. If no database-specific field type has been
+defined for the given field type, then it will just return the same field type.
+
+=item B<Parameters>
+
+=over
+
+=item C<$def> - A reference to a hash of a field containing the following keys:
+C<TYPE> (required), C<NOTNULL> (optional), C<DEFAULT> (optional), 
+C<PRIMARYKEY> (optional), C<REFERENCES> (optional)
+
+=back
+
+=item B<Returns>
+
+A DDL string suitable for describing a field in a C<CREATE TABLE> or 
+C<ALTER TABLE> SQL statement
+
+=back
 
 =cut
 
     my $self = shift;
     my $finfo = (@_ == 1 && ref($_[0]) eq 'HASH') ? $_[0] : { @_ };
-
     my $type = $finfo->{TYPE};
-    die "A valid TYPE was not specified for this column." unless ($type);
+    confess "A valid TYPE was not specified for this column (got " 
+            . Dumper($finfo) . ")" unless ($type);
 
     my $default = $finfo->{DEFAULT};
     # Replace any abstract default value (such as 'TRUE' or 'FALSE')
@@ -1312,19 +1486,98 @@
         $default = $self->{db_specific}->{$default};
     }
 
-    my $fkref = $self->{enable_references} ? $finfo->{REFERENCES} : undef;
     my $type_ddl = $self->convert_type($type);
     # DEFAULT attribute must appear before any column constraints
     # (e.g., NOT NULL), for Oracle
     $type_ddl .= " DEFAULT $default" if (defined($default));
     $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL});
     $type_ddl .= " PRIMARY KEY" if ($finfo->{PRIMARYKEY});
-    $type_ddl .= "\n\t\t\t\tREFERENCES $fkref" if $fkref;
 
     return($type_ddl);
 
 } #eosub--get_type_ddl
 
+
+sub get_fk_ddl {
+=item C<_get_fk_ddl>
+
+=over
+
+=item B<Description>
+
+Protected method. Translates the C<REFERENCES> item of a column into SQL.
+
+=item B<Params>
+
+=over
+
+=item C<$table>  - The name of the table the reference is from.
+=item C<$column> - The name of the column the reference is from
+=item C<$references> - The C<REFERENCES> hashref from a column.
+
+=back
+
+=item B<Returns>
+
+SQL for to define the foreign key, or an empty string if C<$references> 
+is undefined.
+
+=back
+
+=cut
+
+    my ($self, $table, $column, $references) = @_;
+    return "" if !$references;
+
+    my $update    = $references->{UPDATE} || 'CASCADE';
+    my $delete    = $references->{DELETE} || 'RESTRICT';
+    my $to_table  = $references->{TABLE}  || confess "No table in reference";
+    my $to_column = $references->{COLUMN} || confess "No column in reference";
+    my $fk_name   = $self->_get_fk_name($table, $column, $references);
+
+    return "\n     CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
+         . "     REFERENCES $to_table($to_column)\n"
+         . "      ON UPDATE $update ON DELETE $delete";
+}
+
+# Generates a name for a Foreign Key. It's separate from get_fk_ddl
+# so that certain databases can override it (for shorter identifiers or
+# other reasons).
+sub _get_fk_name {
+    my ($self, $table, $column, $references) = @_;
+    my $to_table  = $references->{TABLE}; 
+    my $to_column = $references->{COLUMN};
+    my $name = "fk_${table}_${column}_${to_table}_${to_column}";
+
+    if (length($name) > $self->MAX_IDENTIFIER_LEN) {
+        $name = 'fk_' . $self->_hash_identifier($name);
+    }
+
+    return $name;
+}
+
+sub _hash_identifier {
+    my ($invocant, $value) = @_;
+    # We do -7 to allow prefixes like "idx_" or "fk_", or perhaps something
+    # longer in the future.
+    return substr(md5_hex($value), 0, $invocant->MAX_IDENTIFIER_LEN - 7);
+}
+
+
+sub get_add_fk_sql {
+    my ($self, $table, $column, $def) = @_;
+
+    my $fk_string = $self->get_fk_ddl($table, $column, $def);
+    return ("ALTER TABLE $table ADD $fk_string");
+}
+
+sub get_drop_fk_sql { 
+    my ($self, $table, $column, $references) = @_;
+    my $fk_name = $self->_get_fk_name($table, $column, $references);
+
+    return ("ALTER TABLE $table DROP CONSTRAINT $fk_name");
+}
+
 sub convert_type {
 
 =item C<convert_type>
@@ -1367,17 +1620,17 @@
 
  Description: Public method for discovering what tables should exist in the
               Bugzilla database.
+
  Parameters:  none
- Returns:     an array of table names
+
+ Returns:     An array of table names, in alphabetical order.
 
 =cut
 
     my $self = shift;
+    return sort keys %{$self->{schema}};   
+}
 
-    return(sort(keys %{ $self->{schema} }));
-
-} #eosub--get_table_list
-#--------------------------------------------------------------------------
 sub get_table_columns {
 
 =item C<get_table_columns>
@@ -1536,7 +1789,7 @@
 
     my ($self, $table, $column, $definition, $init_value) = @_;
     my @statements;
-    push(@statements, "ALTER TABLE $table ADD COLUMN $column " .
+    push(@statements, "ALTER TABLE $table ". $self->ADD_COLUMN ." $column " .
         $self->get_type_ddl($definition));
 
     # XXX - Note that although this works for MySQL, most databases will fail
@@ -1544,6 +1797,11 @@
     (push(@statements, "UPDATE $table SET $column = $init_value"))
         if defined $init_value;
 
+    if (defined $definition->{REFERENCES}) {
+        push(@statements, $self->get_add_fk_sql($table, $column,
+                                                $definition->{REFERENCES}));
+    }
+
     return (@statements);
 }
 
@@ -2099,13 +2357,16 @@
     $col_one->{TYPE} = uc($col_one->{TYPE});
     $col_two->{TYPE} = uc($col_two->{TYPE});
 
+    # We don't care about foreign keys when comparing column definitions.
+    delete $col_one->{REFERENCES};
+    delete $col_two->{REFERENCES};
+
     my @col_one_array = %$col_one;
     my @col_two_array = %$col_two;
 
     my ($removed, $added) = diff_arrays(\@col_one_array, \@col_two_array);
 
-    # If there are no differences between the arrays,
-    # then they are equal.
+    # If there are no differences between the arrays, then they are equal.
     return !scalar(@$removed) && !scalar(@$added) ? 1 : 0;
 }
 
@@ -2253,18 +2514,16 @@
 
 =item C<TINYTEXT>
 
-Variable length string of characters up to 255 (2^8 - 1) characters wide 
-or more depending on the character set used.
+Variable length string of characters up to 255 (2^8 - 1) characters wide.
 
 =item C<MEDIUMTEXT>
 
-Variable length string of characters up to 16M (2^24 - 1) characters wide 
-or more depending on the character set used.
+Variable length string of characters up to 4000 characters wide.
+May be longer on some databases.
 
-=item C<TEXT>
+=item C<LONGTEXT>
 
-Variable length string of characters up to 64K (2^16 - 1) characters wide 
-or more depending on the character set used.
+Variable length string of characters up to 16M (2^24 - 1) characters wide.
 
 =item C<LONGBLOB>
 
diff --git a/BugsSite/Bugzilla/DB/Schema/Mysql.pm b/BugsSite/Bugzilla/DB/Schema/Mysql.pm
index d7cd708..6277169 100644
--- a/BugsSite/Bugzilla/DB/Schema/Mysql.pm
+++ b/BugsSite/Bugzilla/DB/Schema/Mysql.pm
@@ -81,10 +81,13 @@
     SMALLINT  => 'INT2',
     MEDIUMINT => 'INT3',
     INTEGER   => 'INT4',
+
     # All the other types have the same name in their abstract version
     # as in their db-specific version, so no reverse mapping is needed.
 };
 
+use constant MYISAM_TABLES => qw(bugs_fulltext);
+
 #------------------------------------------------------------------------------
 sub _initialize {
 
@@ -109,7 +112,7 @@
 
         TINYTEXT =>     'tinytext',
         MEDIUMTEXT =>   'mediumtext',
-        TEXT =>         'text',
+        LONGTEXT =>     'mediumtext',
 
         LONGBLOB =>     'longblob',
 
@@ -130,8 +133,9 @@
     my($self, $table) = @_;
 
     my $charset = Bugzilla->dbh->bz_db_is_utf8 ? "CHARACTER SET utf8" : '';
+    my $type    = grep($_ eq $table, MYISAM_TABLES) ? 'MYISAM' : 'InnoDB';
     return($self->SUPER::_get_create_table_ddl($table) 
-           . " ENGINE = MYISAM $charset");
+           . " ENGINE = $type $charset");
 
 } #eosub--_get_create_table_ddl
 #------------------------------------------------------------------------------
@@ -176,6 +180,7 @@
 
     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 
@@ -184,9 +189,26 @@
         # Dropping a PRIMARY KEY needs an explicit DROP PRIMARY KEY
         push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
     }
+
     return @statements;
 }
 
+sub get_drop_fk_sql {
+    my ($self, $table, $column, $references) = @_;
+    my $fk_name = $self->_get_fk_name($table, $column, $references);
+    my @sql = ("ALTER TABLE $table DROP FOREIGN KEY $fk_name");
+    my $dbh = Bugzilla->dbh;
+
+    # MySQL requires, and will create, an index on any column with
+    # an FK. It will name it after the fk, which we never do.
+    # So if there's an index named after the fk, we also have to delete it. 
+    if ($dbh->bz_index_info_real($table, $fk_name)) {
+        push(@sql, $self->get_drop_index_ddl($table, $fk_name));
+    }
+
+    return @sql;
+}
+
 sub get_drop_index_ddl {
     my ($self, $table, $name) = @_;
     return ("DROP INDEX \`$name\` ON $table");
diff --git a/BugsSite/Bugzilla/DB/Schema/Oracle.pm b/BugsSite/Bugzilla/DB/Schema/Oracle.pm
new file mode 100644
index 0000000..bd5c724
--- /dev/null
+++ b/BugsSite/Bugzilla/DB/Schema/Oracle.pm
@@ -0,0 +1,403 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Oracle Corporation.
+# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation.
+# All Rights Reserved.
+#
+# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
+#                 Xiaoou Wu <xiaoou.wu@oracle.com>
+#                 Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::DB::Schema::Oracle;
+
+###############################################################################
+#
+# DB::Schema implementation for Oracle
+#
+###############################################################################
+
+use strict;
+
+use base qw(Bugzilla::DB::Schema);
+use Carp qw(confess);
+use Bugzilla::Util;
+
+use constant ADD_COLUMN => 'ADD';
+# Whether this is true or not, this is what it needs to be in order for
+# hash_identifier to maintain backwards compatibility with versions before
+# 3.2rc2.
+use constant MAX_IDENTIFIER_LEN => 27;
+
+#------------------------------------------------------------------------------
+sub _initialize {
+
+    my $self = shift;
+
+    $self = $self->SUPER::_initialize(@_);
+
+    $self->{db_specific} = {
+
+        BOOLEAN =>      'integer',
+        FALSE =>        '0', 
+        TRUE =>         '1',
+
+        INT1 =>         'integer',
+        INT2 =>         'integer',
+        INT3 =>         'integer',
+        INT4 =>         'integer',
+
+        SMALLSERIAL  => 'integer',
+        MEDIUMSERIAL => 'integer',
+        INTSERIAL    => 'integer',
+
+        TINYTEXT   =>   'varchar(255)',
+        MEDIUMTEXT =>   'varchar(4000)',
+        LONGTEXT   =>   'clob',
+
+        LONGBLOB =>     'blob',
+
+        DATETIME =>     'date',
+
+    };
+
+    $self->_adjust_schema;
+
+    return $self;
+
+} #eosub--_initialize
+#--------------------------------------------------------------------
+
+sub get_table_ddl {
+    my $self = shift;
+    my $table = shift;
+    unshift @_, $table;
+    my @ddl = $self->SUPER::get_table_ddl(@_);
+
+    my @fields = @{ $self->{abstract_schema}{$table}{FIELDS} || [] };
+    while (@fields) {
+        my $field_name = shift @fields;
+        my $field_info = shift @fields;
+        # Create triggers to deal with empty string. 
+        if ( $field_info->{TYPE} =~ /varchar|TEXT/i 
+                && $field_info->{NOTNULL} ) {
+             push (@ddl, _get_notnull_trigger_ddl($table, $field_name));
+        }
+        # Create sequences and triggers to emulate SERIAL datatypes.
+        if ( $field_info->{TYPE} =~ /SERIAL/i ) {
+            push (@ddl, $self->_get_create_seq_ddl($table, $field_name));
+        }
+    }
+    return @ddl;
+
+} #eosub--get_table_ddl
+
+# Extend superclass method to create Oracle Text indexes if index type 
+# is FULLTEXT from schema. Returns a "create index" SQL statement.
+sub _get_create_index_ddl {
+
+    my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
+    $index_name = "idx_" . $self->_hash_identifier($index_name);
+    if ($index_type eq 'FULLTEXT') {
+        my $sql = "CREATE INDEX $index_name ON $table_name (" 
+                  . join(',',@$index_fields)
+                  . ") INDEXTYPE IS CTXSYS.CONTEXT "
+                  . " PARAMETERS('LEXER BZ_LEX SYNC(ON COMMIT)')" ;
+        return $sql;
+    }
+
+    return($self->SUPER::_get_create_index_ddl($table_name, $index_name, 
+                                               $index_fields, $index_type));
+
+}
+
+sub get_drop_index_ddl {
+    my $self = shift;
+    my ($table, $name) = @_;
+
+    $name = 'idx_' . $self->_hash_identifier($name);
+    return $self->SUPER::get_drop_index_ddl($table, $name);
+}
+
+# Oracle supports the use of FOREIGN KEY integrity constraints 
+# to define the referential integrity actions, including:
+# - Update and delete No Action (default)
+# - Delete CASCADE
+# - Delete SET NULL
+sub get_fk_ddl {
+    my ($self, $table, $column, $references) = @_;
+    return "" if !$references;
+
+    my $update    = $references->{UPDATE} || 'CASCADE';
+    my $delete    = $references->{DELETE};
+    my $to_table  = $references->{TABLE}  || confess "No table in reference";
+    my $to_column = $references->{COLUMN} || confess "No column in reference";
+    my $fk_name   = $self->_get_fk_name($table, $column, $references);
+
+    my $fk_string = "\n     CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
+                    . "     REFERENCES $to_table($to_column)\n";
+   
+    $fk_string    = $fk_string . "     ON DELETE $delete" if $delete; 
+    
+    if ( $update =~ /CASCADE/i ){
+        my $tr_str = "CREATE OR REPLACE TRIGGER ${fk_name}_UC"
+                     . " AFTER  UPDATE  ON ". $to_table
+                     . " REFERENCING "
+                     . " NEW AS NEW "
+                     . " OLD AS OLD "
+                     . " FOR EACH ROW "
+                     . " BEGIN "
+                     . "     UPDATE $table"
+                     . "        SET $column = :NEW.$to_column"
+                     . "      WHERE $column = :OLD.$to_column;"
+                     . " END ${fk_name}_UC;";
+        my $dbh = Bugzilla->dbh; 
+        $dbh->do($tr_str);      
+    }
+
+    return $fk_string;
+}
+
+sub get_drop_fk_sql {
+    my $self = shift;
+    my ($table, $column, $references) = @_;
+    my $fk_name = $self->_get_fk_name(@_);
+    my @sql;
+    if (!$references->{UPDATE} || $references->{UPDATE} =~ /CASCADE/i) {
+        push(@sql, "DROP TRIGGER ${fk_name}_uc");
+    }
+    push(@sql, $self->SUPER::get_drop_fk_sql(@_));
+    return @sql;
+}
+
+sub _get_fk_name {
+    my ($self, $table, $column, $references) = @_;
+    my $to_table  = $references->{TABLE};
+    my $to_column = $references->{COLUMN};
+    my $fk_name   = "${table}_${column}_${to_table}_${to_column}";
+    $fk_name      = "fk_" . $self->_hash_identifier($fk_name);
+    
+    return $fk_name;
+}
+
+sub get_alter_column_ddl {
+    my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
+
+    my @statements;
+    my $old_def = $self->get_column_abstract($table, $column);
+    my $specific = $self->{db_specific};
+
+    # If the types have changed, we have to deal with that.
+    if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
+        push(@statements, $self->_get_alter_type_sql($table, $column, 
+                                                     $new_def, $old_def));
+    }
+
+    my $default = $new_def->{DEFAULT};
+    my $default_old = $old_def->{DEFAULT};
+    # This first condition prevents "uninitialized value" errors.
+    if (!defined $default && !defined $default_old) {
+        # Do Nothing
+    }
+    # If we went from having a default to not having one
+    elsif (!defined $default && defined $default_old) {
+        push(@statements, "ALTER TABLE $table MODIFY $column"
+                        . " DEFAULT NULL");
+    }
+    # If we went from no default to a default, or we changed the default.
+    elsif ( (defined $default && !defined $default_old) || 
+            ($default ne $default_old) ) 
+    {
+        $default = $specific->{$default} if exists $specific->{$default};
+        push(@statements, "ALTER TABLE $table MODIFY $column "
+                         . " DEFAULT $default");
+    }
+
+    # If we went from NULL to NOT NULL.
+    if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
+        my $setdefault;
+        # Handle any fields that were NULL before, if we have a default,
+        $setdefault = $new_def->{DEFAULT} if exists $new_def->{DEFAULT};
+        # But if we have a set_nulls_to, that overrides the DEFAULT 
+        # (although nobody would usually specify both a default and 
+        # a set_nulls_to.)
+        $setdefault = $set_nulls_to if defined $set_nulls_to;
+        if (defined $setdefault) {
+            push(@statements, "UPDATE $table SET $column = $setdefault"
+                            . "  WHERE $column IS NULL");
+        }
+        push(@statements, "ALTER TABLE $table MODIFY $column"
+                        . " NOT NULL");
+        push (@statements, _get_notnull_trigger_ddl($table, $column))
+                                   if $old_def->{TYPE} =~ /varchar|text/i
+                                     && $new_def->{TYPE} =~ /varchar|text/i;
+    }
+    # If we went from NOT NULL to NULL
+    elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
+        push(@statements, "ALTER TABLE $table MODIFY $column"
+                        . " NULL");
+        push(@statements, "DROP TRIGGER ${table}_${column}")
+                           if $new_def->{TYPE} =~ /varchar|text/i 
+                             && $old_def->{TYPE} =~ /varchar|text/i;
+    }
+
+    # If we went from not being a PRIMARY KEY to being a PRIMARY KEY.
+    if (!$old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
+        push(@statements, "ALTER TABLE $table ADD PRIMARY KEY ($column)");
+    }
+    # If we went from being a PK to not being a PK
+    elsif ( $old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY} ) {
+        push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
+    }
+
+    return @statements;
+}
+
+sub _get_alter_type_sql {
+    my ($self, $table, $column, $new_def, $old_def) = @_;
+    my @statements;
+
+    my $type = $new_def->{TYPE};
+    $type = $self->{db_specific}->{$type} 
+        if exists $self->{db_specific}->{$type};
+
+    if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
+        die("You cannot specify a DEFAULT on a SERIAL-type column.") 
+            if $new_def->{DEFAULT};
+    }
+
+    if ( ($old_def->{TYPE} =~ /LONGTEXT/i && $new_def->{TYPE} !~ /LONGTEXT/i) 
+         || ($old_def->{TYPE} !~ /LONGTEXT/i && $new_def->{TYPE} =~ /LONGTEXT/i)
+       ) {
+        # LONG to VARCHAR or VARCHAR to LONG is not allowed in Oracle, 
+        # just a way to work around.
+        # Determine whether column_temp is already exist.
+        my $dbh=Bugzilla->dbh;
+        my $column_exist = $dbh->selectcol_arrayref(
+                          "SELECT CNAME FROM COL WHERE TNAME = UPPER(?) AND 
+                             CNAME = UPPER(?)", undef,$table,$column . "_temp");
+        if(!@$column_exist) {
+        push(@statements, 
+            "ALTER TABLE $table ADD ${column}_temp $type");  
+        }
+        push(@statements, "UPDATE $table SET ${column}_temp = $column");
+        push(@statements, "COMMIT");
+        push(@statements, "ALTER TABLE $table DROP COLUMN $column");
+        push(@statements, 
+            "ALTER TABLE $table RENAME COLUMN ${column}_temp TO $column");
+    } else {  
+        push(@statements, "ALTER TABLE $table MODIFY $column $type");
+    }
+
+    if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
+         push(@statements, _get_create_seq_ddl($table, $column));
+    }
+
+    # If this column is no longer SERIAL, we need to drop the sequence
+    # that went along with it.
+    if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
+        push(@statements, "DROP SEQUENCE ${table}_${column}_SEQ");
+        push(@statements, "DROP TRIGGER ${table}_${column}_TR");
+    }
+    
+    # If this column is changed to type TEXT/VARCHAR, we need to deal with
+    # empty string.
+    if ( $old_def->{TYPE} !~ /varchar|text/i 
+            && $new_def->{TYPE} =~ /varchar|text/i 
+            && $new_def->{NOTNULL} )
+    {
+             push (@statements, _get_notnull_trigger_ddl($table, $column));
+    } 
+    # If this column is no longer TEXT/VARCHAR, we need to drop the trigger
+    # that went along with it.
+    if ( $old_def->{TYPE} =~ /varchar|text/i
+            && $old_def->{NOTNULL}
+            && $new_def->{TYPE} !~ /varchar|text/i )
+    {
+        push(@statements, "DROP TRIGGER ${table}_${column}");
+    } 
+    return @statements;
+}
+
+sub get_rename_column_ddl {
+    my ($self, $table, $old_name, $new_name) = @_;
+    if (lc($old_name) eq lc($new_name)) {
+        # if the only change is a case change, return an empty list.
+        return ();
+    }
+    my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
+    my $def = $self->get_column_abstract($table, $old_name);
+    if ($def->{TYPE} =~ /SERIAL/i) {
+        # We have to rename the series also, and fix the default of the series.
+        push(@sql, "RENAME ${table}_${old_name}_SEQ TO 
+                      ${table}_${new_name}_seq");
+        my $serial_sql =
+                       "CREATE OR REPLACE TRIGGER ${table}_${new_name}_TR "
+                     . " BEFORE INSERT ON ${table} "
+                     . " FOR EACH ROW "
+                     . " BEGIN "
+                     . "   SELECT ${table}_${new_name}_SEQ.NEXTVAL "
+                     . "   INTO :NEW.${new_name} FROM DUAL; "
+                     . " END;";
+        push(@sql, $serial_sql);
+        push(@sql, "DROP TRIGGER ${table}_${old_name}_TR");
+    }
+    if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL} ) {
+        push(@sql, _get_notnull_trigger_ddl($table,$new_name));
+        push(@sql, "DROP TRIGGER ${table}_${old_name}");
+    }
+    return @sql;
+}
+
+sub _get_notnull_trigger_ddl {
+      my ($table, $column) = @_;
+
+      my $notnull_sql = "CREATE OR REPLACE TRIGGER "
+                        . " ${table}_${column}"
+                        . " BEFORE INSERT OR UPDATE ON ". $table
+                        . " FOR EACH ROW"
+                        . " BEGIN "
+                        . " IF :NEW.". $column ." IS NULL THEN  "
+                        . " SELECT '" . Bugzilla::DB::Oracle->EMPTY_STRING
+                        . "' INTO :NEW.". $column ." FROM DUAL; "
+                        . " END IF; "
+                        . " END ".$table.";";
+     return $notnull_sql;
+}
+
+sub _get_create_seq_ddl {
+     my ($self, $table, $column, $start_with) = @_;
+     $start_with ||= 1;
+     my @ddl;
+     my $seq_name = "${table}_${column}_SEQ";
+     my $seq_sql = "CREATE SEQUENCE $seq_name "
+                   . " INCREMENT BY 1 "
+                   . " START WITH $start_with "
+                   . " NOMAXVALUE "
+                   . " NOCYCLE "
+                   . " NOCACHE";
+     my $serial_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
+                    . " BEFORE INSERT ON ${table} "
+                    . " FOR EACH ROW "
+                    . " BEGIN "
+                    . "   SELECT ${seq_name}.NEXTVAL "
+                    . "   INTO :NEW.${column} FROM DUAL; "
+                    . " END;";
+    push (@ddl, $seq_sql);
+    push (@ddl, $serial_sql);
+
+    return @ddl;
+}
+
+1;
diff --git a/BugsSite/Bugzilla/DB/Schema/Pg.pm b/BugsSite/Bugzilla/DB/Schema/Pg.pm
index 7a951e2d..070c0b03 100644
--- a/BugsSite/Bugzilla/DB/Schema/Pg.pm
+++ b/BugsSite/Bugzilla/DB/Schema/Pg.pm
@@ -75,7 +75,7 @@
 
         TINYTEXT =>     'varchar(255)',
         MEDIUMTEXT =>   'text',
-        TEXT =>         'text',
+        LONGTEXT =>     'text',
 
         LONGBLOB =>     'bytea',
 
diff --git a/BugsSite/Bugzilla/Error.pm b/BugsSite/Bugzilla/Error.pm
index 18f9aae..d15336a 100644
--- a/BugsSite/Bugzilla/Error.pm
+++ b/BugsSite/Bugzilla/Error.pm
@@ -46,16 +46,15 @@
 
 sub _throw_error {
     my ($name, $error, $vars) = @_;
-
+    my $dbh = Bugzilla->dbh;
     $vars ||= {};
 
     $vars->{error} = $error;
 
-    # Make sure any locked tables are unlocked
-    # and the transaction is rolled back (if supported)
-    # If we are within an eval(), do not unlock tables as we are
+    # Make sure any transaction is rolled back (if supported).
+    # If we are within an eval(), do not roll back transactions as we are
     # eval'uating some test on purpose.
-    Bugzilla->dbh->bz_unlock_tables(UNLOCK_ABORT) unless _in_eval();
+    $dbh->bz_rollback_transaction() if ($dbh->bz_in_transaction() && !_in_eval());
 
     my $datadir = bz_locations()->{'datadir'};
     # If a writable $datadir/errorlog exists, log error details there.
@@ -103,7 +102,12 @@
             die("$message\n");
         }
         elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
-            my $code = WS_ERROR_CODE->{$error};
+            # 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;
@@ -124,10 +128,10 @@
 
 sub ThrowTemplateError {
     my ($template_err) = @_;
+    my $dbh = Bugzilla->dbh;
 
-    # Make sure any locked tables are unlocked
-    # and the transaction is rolled back (if supported)
-    Bugzilla->dbh->bz_unlock_tables(UNLOCK_ABORT);
+    # Make sure the transaction is rolled back (if supported).
+    $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction();
 
     my $vars = {};
     if (Bugzilla->error_mode == ERROR_MODE_DIE) {
diff --git a/BugsSite/Bugzilla/Field.pm b/BugsSite/Bugzilla/Field.pm
index 9177ae4..0d74790 100644
--- a/BugsSite/Bugzilla/Field.pm
+++ b/BugsSite/Bugzilla/Field.pm
@@ -30,17 +30,14 @@
   print Dumper(Bugzilla->get_fields());
 
   # Display information about non-obsolete custom fields.
-  print Dumper(Bugzilla->get_fields({ obsolete => 1, custom => 1 }));
-
-  # Display a list of the names of non-obsolete custom fields.
-  print Bugzilla->custom_field_names;
+  print Dumper(Bugzilla->active_custom_fields);
 
   use Bugzilla::Field;
 
   # Display information about non-obsolete custom fields.
   # Bugzilla->get_fields() is a wrapper around Bugzilla::Field->match(),
   # so both methods take the same arguments.
-  print Dumper(Bugzilla::Field->match({ obsolete => 1, custom => 1 }));
+  print Dumper(Bugzilla::Field->match({ obsolete => 0, custom => 1 }));
 
   # Create or update a custom field or field definition.
   my $field = Bugzilla::Field->create(
@@ -126,6 +123,8 @@
     FIELD_TYPE_FREETEXT,      { TYPE => 'varchar(255)' },
     FIELD_TYPE_SINGLE_SELECT, { TYPE => 'varchar(64)', NOTNULL => 1,
                                 DEFAULT => "'---'" },
+    FIELD_TYPE_TEXTAREA,      { TYPE => 'MEDIUMTEXT' },
+    FIELD_TYPE_DATETIME,      { TYPE => 'DATETIME'   },
 };
 
 # Field definitions for the fields that ship with Bugzilla.
@@ -252,9 +251,9 @@
 sub _check_type {
     my ($invocant, $type) = @_;
     my $saved_type = $type;
-    # FIELD_TYPE_SINGLE_SELECT here should be updated every time a new,
+    # The constant here should be updated every time a new,
     # higher field type is added.
-    (detaint_natural($type) && $type <= FIELD_TYPE_SINGLE_SELECT)
+    (detaint_natural($type) && $type <= FIELD_TYPE_DATETIME)
       || ThrowCodeError('invalid_customfield_type', { type => $saved_type });
     return $type;
 }
@@ -413,6 +412,89 @@
 
 =pod
 
+=head2 Instance Method
+
+=over
+
+=item C<remove_from_db>
+
+Attempts to remove the passed in field from the database.
+Deleting a field is only successful if the field is obsolete and
+there are no values specified (or EVER specified) for the field.
+
+=back
+
+=cut
+
+sub remove_from_db {
+    my $self = shift;
+    my $dbh = Bugzilla->dbh;
+
+    my $name = $self->name;
+
+    if (!$self->custom) {
+        ThrowCodeError('field_not_custom', {'name' => $name });
+    }
+
+    if (!$self->obsolete) {
+        ThrowUserError('customfield_not_obsolete', {'name' => $self->name });
+    }
+
+    $dbh->bz_start_transaction();
+
+    # Check to see if bug activity table has records (should be fast with index)
+    my $has_activity = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs_activity
+                                      WHERE fieldid = ?", undef, $self->id);
+    if ($has_activity) {
+        ThrowUserError('customfield_has_activity', {'name' => $name });
+    }
+
+    # Check to see if bugs table has records (slow)
+    my $bugs_query = "";
+
+    if ($self->type == FIELD_TYPE_MULTI_SELECT) {
+        $bugs_query = "SELECT COUNT(*) FROM bug_$name";
+    }
+    else {
+        $bugs_query = "SELECT COUNT(*) FROM bugs WHERE $name IS NOT NULL
+                                AND $name != ''";
+        # Ignore the default single select value
+        if ($self->type == FIELD_TYPE_SINGLE_SELECT) {
+            $bugs_query .= " AND $name != '---'";
+        }
+        # Ignore blank dates.
+        if ($self->type == FIELD_TYPE_DATETIME) {
+            $bugs_query .= " AND $name != '00-00-00 00:00:00'";
+        }
+    }
+
+    my $has_bugs = $dbh->selectrow_array($bugs_query);
+    if ($has_bugs) {
+        ThrowUserError('customfield_has_contents', {'name' => $name });
+    }
+
+    # Once we reach here, we should be OK to delete.
+    $dbh->do('DELETE FROM fielddefs WHERE id = ?', undef, $self->id);
+
+    my $type = $self->type;
+
+    # the values for multi-select are stored in a seperate table
+    if ($type != FIELD_TYPE_MULTI_SELECT) {
+        $dbh->bz_drop_column('bugs', $name);
+    }
+
+    if ($type == FIELD_TYPE_SINGLE_SELECT
+        || $type == FIELD_TYPE_MULTI_SELECT)
+    {
+        # Delete the table that holds the legal values for this field.
+        $dbh->bz_drop_field_tables($self);
+    }
+
+    $dbh->bz_commit_transaction()
+}
+
+=pod
+
 =head2 Class Methods
 
 =over
@@ -454,13 +536,20 @@
     if ($field->custom) {
         my $name = $field->name;
         my $type = $field->type;
-        # Create the database column that stores the data for this field.
-        $dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type});
+        if (SQL_DEFINITIONS->{$type}) {
+            # Create the database column that stores the data for this field.
+            $dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type});
+        }
+
+        if ($type == FIELD_TYPE_SINGLE_SELECT
+                || $type == FIELD_TYPE_MULTI_SELECT) 
+        {
+            # Create the table that holds the legal values for this field.
+            $dbh->bz_add_field_tables($field);
+        }
 
         if ($type == FIELD_TYPE_SINGLE_SELECT) {
-            # Create the table that holds the legal values for this field.
-            $dbh->bz_add_field_table($name);
-            # And insert a default value of "---" into it.
+            # Insert a default value of "---" into the legal values table.
             $dbh->do("INSERT INTO $name (value) VALUES ('---')");
         }
     }
@@ -487,81 +576,6 @@
 
 =over
 
-=item C<match>
-
-=over
-
-=item B<Description>
-
-Returns a list of fields that match the specified criteria.
-
-You should be using L<Bugzilla/get_fields> and
-L<Bugzilla/get_custom_field_names> instead of this function.
-
-=item B<Params>
-
-Takes named parameters in a hashref:
-
-=over
-
-=item C<name> - The name of the field.
-
-=item C<custom> - Boolean. True to only return custom fields. False
-to only return non-custom fields. 
-
-=item C<obsolete> - Boolean. True to only return obsolete fields.
-False to not return obsolete fields.
-
-=item C<type> - The type of the field. A C<FIELD_TYPE> constant from
-L<Bugzilla::Constants>.
-
-=item C<enter_bug> - Boolean. True to only return fields that appear
-on F<enter_bug.cgi>. False to only return fields that I<don't> appear
-on F<enter_bug.cgi>.
-
-=back
-
-=item B<Returns>
-
-A reference to an array of C<Bugzilla::Field> objects.
-
-=back
-
-=back
-
-=cut
-
-sub match {
-    my ($class, $criteria) = @_;
-  
-    my @terms;
-    if (defined $criteria->{name}) {
-        push(@terms, "name=" . Bugzilla->dbh->quote($criteria->{name}));
-    }
-    if (defined $criteria->{custom}) {
-        push(@terms, "custom=" . ($criteria->{custom} ? "1" : "0"));
-    }
-    if (defined $criteria->{obsolete}) {
-        push(@terms, "obsolete=" . ($criteria->{obsolete} ? "1" : "0"));
-    }
-    if (defined $criteria->{enter_bug}) {
-        push(@terms, "enter_bug=" . ($criteria->{enter_bug} ? '1' : '0'));
-    }
-    if (defined $criteria->{type}) {
-        push(@terms, "type = " . $criteria->{type});
-    }
-    my $where = (scalar(@terms) > 0) ? "WHERE " . join(" AND ", @terms) : "";
-
-    my $ids = Bugzilla->dbh->selectcol_arrayref(
-        "SELECT id FROM fielddefs $where", {Slice => {}});
-
-    return $class->new_from_list($ids);
-}
-
-=pod
-
-=over
-
 =item C<get_legal_field_values($field)>
 
 Description: returns all the legal values for a field that has a
diff --git a/BugsSite/Bugzilla/Flag.pm b/BugsSite/Bugzilla/Flag.pm
index 19ecf7f..a0c9567 100644
--- a/BugsSite/Bugzilla/Flag.pm
+++ b/BugsSite/Bugzilla/Flag.pm
@@ -54,6 +54,7 @@
 =cut
 
 use Bugzilla::FlagType;
+use Bugzilla::Hook;
 use Bugzilla::User;
 use Bugzilla::Util;
 use Bugzilla::Error;
@@ -191,6 +192,26 @@
 
 =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
@@ -202,16 +223,26 @@
 =cut
 
 sub match {
+    my $class = shift;
     my ($criteria) = @_;
-    my $dbh = Bugzilla->dbh;
 
-    my @criteria = sqlify_criteria($criteria);
-    $criteria = join(' AND ', @criteria);
+    # If the caller specified only bug or attachment flags,
+    # limit the query to those kinds of flags.
+    if (my $type = delete $criteria->{'target_type'}) {
+        if ($type eq 'bug') {
+            $criteria->{'attach_id'} = IS_NULL;
+        }
+        elsif (!defined $criteria->{'attach_id'}) {
+            $criteria->{'attach_id'} = NOT_NULL;
+        }
+    }
+    # Flag->snapshot() calls Flag->match() with bug_id and attach_id
+    # as hash keys, even if attach_id is undefined.
+    if (exists $criteria->{'attach_id'} && !defined $criteria->{'attach_id'}) {
+        $criteria->{'attach_id'} = IS_NULL;
+    }
 
-    my $flag_ids = $dbh->selectcol_arrayref("SELECT id FROM flags
-                                             WHERE $criteria");
-
-    return Bugzilla::Flag->new_from_list($flag_ids);
+    return $class->SUPER::match(@_);
 }
 
 =pod
@@ -229,15 +260,8 @@
 =cut
 
 sub count {
-    my ($criteria) = @_;
-    my $dbh = Bugzilla->dbh;
-
-    my @criteria = sqlify_criteria($criteria);
-    $criteria = join(' AND ', @criteria);
-
-    my $count = $dbh->selectrow_array("SELECT COUNT(*) FROM flags WHERE $criteria");
-
-    return $count;
+    my $class = shift;
+    return scalar @{$class->match(@_)};
 }
 
 ######################################################################
@@ -248,7 +272,7 @@
 
 =over
 
-=item C<validate($cgi, $bug_id, $attach_id, $skip_requestee_on_error)>
+=item C<validate($bug_id, $attach_id, $skip_requestee_on_error)>
 
 Validates fields containing flag modifications.
 
@@ -260,8 +284,8 @@
 =cut
 
 sub validate {
-    my ($cgi, $bug_id, $attach_id, $skip_requestee_on_error) = @_;
-
+    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
@@ -295,11 +319,12 @@
         my $not = ($attach_id) ? "" : "NOT";
 
         my $invalid_data =
-            $dbh->selectrow_array("SELECT 1 FROM flags
-                                   WHERE id IN (" . join(',', @flag_ids) . ")
-                                   AND ($field != ? OR attach_id IS $not NULL) " .
-                                   $dbh->sql_limit(1),
-                                   undef, $field_id);
+            $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',
@@ -484,10 +509,10 @@
 }
 
 sub snapshot {
-    my ($bug_id, $attach_id) = @_;
+    my ($class, $bug_id, $attach_id) = @_;
 
-    my $flags = match({ 'bug_id'    => $bug_id,
-                        'attach_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;
@@ -502,22 +527,22 @@
 
 =over
 
-=item C<process($bug, $attachment, $timestamp, $cgi)>
+=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), the cgi is the CGI
-object used to obtain the flag fields that the user submitted.
+to the flag can be stamped with the same date/time).
 
 =back
 
 =cut
 
 sub process {
-    my ($bug, $attachment, $timestamp, $cgi) = @_;
+    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,
@@ -532,15 +557,15 @@
     $timestamp ||= $dbh->selectrow_array('SELECT NOW()');
 
     # Take a snapshot of flags before any changes.
-    my @old_summaries = snapshot($bug_id, $attach_id);
+    my @old_summaries = $class->snapshot($bug_id, $attach_id);
 
     # Cancel pending requests if we are obsoleting an attachment.
     if ($attachment && $cgi->param('isobsolete')) {
-        CancelRequests($bug, $attachment);
+        $class->CancelRequests($bug, $attachment);
     }
 
     # Create new flags and update existing flags.
-    my $new_flags = FormToNewFlags($bug, $attachment, $cgi);
+    my $new_flags = FormToNewFlags($bug, $attachment, $cgi, $hr_vars);
     foreach my $flag (@$new_flags) { create($flag, $bug, $attachment, $timestamp) }
     modify($bug, $attachment, $cgi, $timestamp);
 
@@ -562,7 +587,10 @@
     my $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;
+        unless ($is_retargetted) {
+            clear($flag, $bug, $flag->attachment);
+            $hr_vars->{'message'} = 'flag_cleared';
+        }
     }
 
     $flag_ids = $dbh->selectcol_arrayref(
@@ -582,9 +610,15 @@
     }
 
     # Take a snapshot of flags after changes.
-    my @new_summaries = snapshot($bug_id, $attach_id);
+    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 {
@@ -763,6 +797,14 @@
             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) {
@@ -782,11 +824,11 @@
                          SET setter_id = ?, requestee_id = ?,
                              status = ?, modification_date = ?
                        WHERE id = ?',
-                       undef, ($setter->id, $requestee_id, $status,
+                       undef, ($new_setter->id, $requestee_id, $status,
                                $timestamp, $flag->id));
 
             # Now update the flag object with its new values.
-            $flag->{'setter'} = $setter;
+            $flag->{'setter'} = $new_setter;
             $flag->{'status'} = $status;
 
             # Send an email notifying the relevant parties about the request.
@@ -853,7 +895,7 @@
 
     foreach my $flagtype (@$flagtypes) {
         # Get the number of flags of this type already set for this target.
-        my $has_flags = count(
+        my $has_flags = __PACKAGE__->count(
             { 'type_id'     => $flagtype->id,
               'bug_id'      => $bug->bug_id,
               'attach_id'   => $flag->attach_id });
@@ -931,7 +973,7 @@
 
 =over
 
-=item C<FormToNewFlags($bug, $attachment, $cgi)>
+=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().
@@ -941,7 +983,7 @@
 =cut
 
 sub FormToNewFlags {
-    my ($bug, $attachment, $cgi) = @_;
+    my ($bug, $attachment, $cgi, $hr_vars) = @_;
     my $dbh = Bugzilla->dbh;
     my $setter = Bugzilla->user;
     
@@ -951,22 +993,33 @@
 
     return () unless scalar(@type_ids);
 
-    # Get a list of active flag types available for this target.
+    # Get a list of active flag types available for this product/component.
     my $flag_types = Bugzilla::FlagType::match(
-        { 'target_type'  => $attachment ? 'attachment' : 'bug',
-          'product_id'   => $bug->{'product_id'},
+        { 'product_id'   => $bug->{'product_id'},
           'component_id' => $bug->{'component_id'},
           'is_active'    => 1 });
 
+    foreach my $type_id (@type_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';
+            last;
+        }
+    }
+
     my @flags;
     foreach my $flag_type (@$flag_types) {
         my $type_id = $flag_type->id;
 
+        # Bug flags are only valid for bugs, and attachment flags are
+        # only valid for attachments. So don't mix both.
+        next unless ($flag_type->target_type eq 'bug' xor $attachment);
+
         # We are only interested in flags the user tries to create.
         next unless scalar(grep { $_ == $type_id } @type_ids);
 
         # Get the number of flags of this type already set for this target.
-        my $has_flags = count(
+        my $has_flags = __PACKAGE__->count(
             { 'type_id'     => $type_id,
               'target_type' => $attachment ? 'attachment' : 'bug',
               'bug_id'      => $bug->bug_id,
@@ -1017,63 +1070,67 @@
 sub notify {
     my ($flag, $bug, $attachment) = @_;
 
-    my $template = Bugzilla->template;
-
     # There is nobody to notify.
     return unless ($flag->{'addressee'} || $flag->type->cc_list);
 
-    my $attachment_is_private = $attachment ? $attachment->isprivate : undef;
-
     # If the target bug is restricted to one or more groups, then we need
     # to make sure we don't send email about it to unauthorized users
     # on the request type's CC: list, so we have to trawl the list for users
     # not in those groups or email addresses that don't have an account.
     my @bug_in_groups = grep {$_->{'ison'} || $_->{'mandatory'}} @{$bug->groups};
+    my $attachment_is_private = $attachment ? $attachment->isprivate : undef;
 
-    if (scalar(@bug_in_groups) || $attachment_is_private) {
-        my @new_cc_list;
-        foreach my $cc (split(/[, ]+/, $flag->type->cc_list)) {
-            my $ccuser = new Bugzilla::User({ name => $cc }) || next;
-
-            next if (scalar(@bug_in_groups) && !$ccuser->can_see_bug($bug->bug_id));
-            next if $attachment_is_private
-              && Bugzilla->params->{"insidergroup"}
-              && !$ccuser->in_group(Bugzilla->params->{"insidergroup"});
-            push(@new_cc_list, $cc);
-        }
-        $flag->type->{'cc_list'} = join(", ", @new_cc_list);
+    my %recipients;
+    foreach my $cc (split(/[, ]+/, $flag->type->cc_list)) {
+        my $ccuser = new Bugzilla::User({ name => $cc });
+        next if (scalar(@bug_in_groups) && (!$ccuser || !$ccuser->can_see_bug($bug->bug_id)));
+        next if $attachment_is_private && (!$ccuser || !$ccuser->is_insider);
+        # Prevent duplicated entries due to case sensitivity.
+        $cc = $ccuser ? $ccuser->email : $cc;
+        $recipients{$cc} = $ccuser;
     }
 
-    # If there is nobody left to notify, return.
-    return unless ($flag->{'addressee'} || $flag->type->cc_list);
-
-    my @recipients = split(/[, ]+/, $flag->type->cc_list);
     # Only notify if the addressee is allowed to receive the email.
     if ($flag->{'addressee'} && $flag->{'addressee'}->email_enabled) {
-        push @recipients, $flag->{'addressee'}->email;
+        $recipients{$flag->{'addressee'}->email} = $flag->{'addressee'};
     }
-    # Process and send notification for each recipient
-    foreach my $to (@recipients)
-    {
-        next unless $to;
-        my $vars = { 'flag'       => $flag,
-                     'to'         => $to,
-                     'bug'        => $bug,
-                     'attachment' => $attachment};
-        my $message;
-        my $rv = $template->process("request/email.txt.tmpl", $vars, \$message);
-        if (!$rv) {
-            Bugzilla->cgi->header();
-            ThrowTemplateError($template->error());
-        }
+    # 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'};
+    }
 
+    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 $vars = { 'flag'            => $flag,
+                     'to'              => $to,
+                     'bug'             => $bug,
+                     'attachment'      => $attachment,
+                     'threadingmarker' => $threadingmarker };
+
+        my $lang = $recipients{$to} ?
+          $recipients{$to}->settings->{'lang'}->{'value'} : $default_lang;
+
+        my $template = Bugzilla->template_inner($lang);
+        my $message;
+        $template->process("request/email.txt.tmpl", $vars, \$message)
+          || ThrowTemplateError($template->error());
+
+        Bugzilla->template_inner("");
         MessageToMTA($message);
     }
 }
 
 # Cancel all request flags from the attachment being obsoleted.
 sub CancelRequests {
-    my ($bug, $attachment, $timestamp) = @_;
+    my ($class, $bug, $attachment, $timestamp) = @_;
     my $dbh = Bugzilla->dbh;
 
     my $request_ids =
@@ -1088,7 +1145,8 @@
     return if (!scalar(@$request_ids));
 
     # Take a snapshot of flags before any changes.
-    my @old_summaries = snapshot($bug->bug_id, $attachment->id) if ($timestamp);
+    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) }
 
@@ -1096,60 +1154,11 @@
     return unless ($timestamp);
 
     # Take a snapshot of flags after any changes.
-    my @new_summaries = snapshot($bug->bug_id, $attachment->id);
+    my @new_summaries = $class->snapshot($bug->bug_id, $attachment->id);
     update_activity($bug->bug_id, $attachment->id, $timestamp,
                     \@old_summaries, \@new_summaries);
 }
 
-######################################################################
-# Private Functions
-######################################################################
-
-=begin private
-
-=head1 PRIVATE FUNCTIONS
-
-=over
-
-=item C<sqlify_criteria($criteria)>
-
-Converts a hash of criteria into a list of SQL criteria.
-
-=back
-
-=cut
-
-sub sqlify_criteria {
-    # a reference to a hash containing the criteria (field => value)
-    my ($criteria) = @_;
-
-    # the generated list of SQL criteria; "1=1" is a clever way of making sure
-    # there's something in the list so calling code doesn't have to check list
-    # size before building a WHERE clause out of it
-    my @criteria = ("1=1");
-    
-    # If the caller specified only bug or attachment flags,
-    # limit the query to those kinds of flags.
-    if (defined($criteria->{'target_type'})) {
-        if    ($criteria->{'target_type'} eq 'bug')        { push(@criteria, "attach_id IS NULL") }
-        elsif ($criteria->{'target_type'} eq 'attachment') { push(@criteria, "attach_id IS NOT NULL") }
-    }
-    
-    # Go through each criterion from the calling code and add it to the query.
-    foreach my $field (keys %$criteria) {
-        my $value = $criteria->{$field};
-        next unless defined($value);
-        if    ($field eq 'type_id')      { push(@criteria, "type_id      = $value") }
-        elsif ($field eq 'bug_id')       { push(@criteria, "bug_id       = $value") }
-        elsif ($field eq 'attach_id')    { push(@criteria, "attach_id    = $value") }
-        elsif ($field eq 'requestee_id') { push(@criteria, "requestee_id = $value") }
-        elsif ($field eq 'setter_id')    { push(@criteria, "setter_id    = $value") }
-        elsif ($field eq 'status')       { push(@criteria, "status       = '$value'") }
-    }
-    
-    return @criteria;
-}
-
 =head1 SEE ALSO
 
 =over
diff --git a/BugsSite/Bugzilla/FlagType.pm b/BugsSite/Bugzilla/FlagType.pm
index 1504be8..2892a83 100644
--- a/BugsSite/Bugzilla/FlagType.pm
+++ b/BugsSite/Bugzilla/FlagType.pm
@@ -177,6 +177,8 @@
 sub is_requesteeble  { return $_[0]->{'is_requesteeble'};  }
 sub is_multiplicable { return $_[0]->{'is_multiplicable'}; }
 sub sortkey          { return $_[0]->{'sortkey'};          }
+sub request_group_id { return $_[0]->{'request_group_id'}; }
+sub grant_group_id   { return $_[0]->{'grant_group_id'};   }
 
 ###############################
 ####       Methods         ####
@@ -186,6 +188,11 @@
 
 =over
 
+=item C<grant_list>
+
+Returns a reference to an array of users who have permission to grant this flag type.
+The arrays are populated with hashrefs containing the login, identity and visibility of users.
+
 =item C<grant_group>
 
 Returns the group (as a Bugzilla::Group object) in which a user
@@ -214,6 +221,17 @@
 
 =cut
 
+sub grant_list {
+    my $self = shift;
+    my @custusers;
+    my @allusers = @{Bugzilla->user->get_userlist};
+    foreach my $user (@allusers) {
+        my $user_obj = new Bugzilla::User({name => $user->{login}});
+        push(@custusers, $user) if $user_obj->can_set_flag($self);
+    }
+    return \@custusers;
+}
+
 sub grant_group {
     my $self = shift;
 
diff --git a/BugsSite/Bugzilla/Group.pm b/BugsSite/Bugzilla/Group.pm
index c80d233..d9f49c0 100644
--- a/BugsSite/Bugzilla/Group.pm
+++ b/BugsSite/Bugzilla/Group.pm
@@ -31,6 +31,7 @@
 use Bugzilla::Constants;
 use Bugzilla::Util;
 use Bugzilla::Error;
+use Bugzilla::Config qw(:admin);
 
 ###############################
 ##### Module Initialization ###
@@ -43,6 +44,7 @@
     groups.isbuggroup
     groups.userregexp
     groups.isactive
+    groups.icon_url
 );
 
 use constant DB_TABLE => 'groups';
@@ -53,11 +55,25 @@
     name        => \&_check_name,
     description => \&_check_description,
     userregexp  => \&_check_user_regexp,
+    isactive    => \&_check_is_active,
     isbuggroup  => \&_check_is_bug_group,
+    icon_url    => \&_check_icon_url,
 };
 
 use constant REQUIRED_CREATE_FIELDS => qw(name description isbuggroup);
 
+use constant UPDATE_COLUMNS => qw(
+    name
+    description
+    userregexp
+    isactive
+    icon_url
+);
+
+# Parameters that are lists of groups.
+use constant GROUP_PARAMS => qw(chartgroup insidergroup timetrackinggroup
+                                querysharegroup);
+
 ###############################
 ####      Accessors      ######
 ###############################
@@ -66,11 +82,115 @@
 sub is_bug_group { return $_[0]->{'isbuggroup'};   }
 sub user_regexp  { return $_[0]->{'userregexp'};   }
 sub is_active    { return $_[0]->{'isactive'};     }
+sub icon_url     { return $_[0]->{'icon_url'};     }
+
+sub members_direct {
+    my ($self) = @_;
+    return $self->{members_direct} if defined $self->{members_direct};
+    my $dbh = Bugzilla->dbh;
+    my $user_ids = $dbh->selectcol_arrayref(
+        "SELECT user_group_map.user_id
+           FROM user_group_map
+          WHERE user_group_map.group_id = ?
+                AND grant_type = " . GRANT_DIRECT . "
+                AND isbless = 0", undef, $self->id);
+    require Bugzilla::User;
+    $self->{members_direct} = Bugzilla::User->new_from_list($user_ids);
+    return $self->{members_direct};
+}
+
+sub grant_direct {
+    my ($self, $type) = @_;
+    $self->{grant_direct} ||= {};
+    return $self->{grant_direct}->{$type} 
+        if defined $self->{members_direct}->{$type};
+    my $dbh = Bugzilla->dbh;
+
+    my $ids = $dbh->selectcol_arrayref(
+      "SELECT member_id FROM group_group_map
+        WHERE grantor_id = ? AND grant_type = $type", 
+      undef, $self->id) || [];
+
+    $self->{grant_direct}->{$type} = $self->new_from_list($ids);
+    return $self->{grant_direct}->{$type};
+}
+
+sub granted_by_direct {
+    my ($self, $type) = @_;
+    $self->{granted_by_direct} ||= {};
+    return $self->{granted_by_direct}->{$type}
+         if defined $self->{granted_by_direct}->{$type};
+    my $dbh = Bugzilla->dbh;
+
+    my $ids = $dbh->selectcol_arrayref(
+      "SELECT grantor_id FROM group_group_map
+        WHERE member_id = ? AND grant_type = $type",
+      undef, $self->id) || [];
+
+    $self->{granted_by_direct}->{$type} = $self->new_from_list($ids);
+    return $self->{granted_by_direct}->{$type};
+}
 
 ###############################
 ####        Methods        ####
 ###############################
 
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_is_active   { $_[0]->set('isactive', $_[1]);    }
+sub set_name        { $_[0]->set('name', $_[1]);        }
+sub set_user_regexp { $_[0]->set('userregexp', $_[1]);  }
+sub set_icon_url    { $_[0]->set('icon_url', $_[1]);    }
+
+sub update {
+    my $self = shift;
+    my $changes = $self->SUPER::update(@_);
+
+    if (exists $changes->{name}) {
+        my ($old_name, $new_name) = @{$changes->{name}};
+        my $update_params;
+        foreach my $group (GROUP_PARAMS) {
+            if ($old_name eq Bugzilla->params->{$group}) {
+                SetParam($group, $new_name);
+                $update_params = 1;
+            }
+        }
+        write_params() if $update_params;
+    }
+
+    # If we've changed this group to be active, fix any Mandatory groups.
+    $self->_enforce_mandatory if (exists $changes->{isactive} 
+                                  && $changes->{isactive}->[1]);
+
+    $self->_rederive_regexp() if exists $changes->{userregexp};
+    return $changes;
+}
+
+# Add missing entries in bug_group_map for bugs created while
+# a mandatory group was disabled and which is now enabled again.
+sub _enforce_mandatory {
+    my ($self) = @_;
+    my $dbh = Bugzilla->dbh;
+    my $gid = $self->id;
+
+    my $bug_ids =
+      $dbh->selectcol_arrayref('SELECT bugs.bug_id
+                                  FROM bugs
+                            INNER JOIN group_control_map
+                                    ON group_control_map.product_id = bugs.product_id
+                             LEFT JOIN bug_group_map
+                                    ON bug_group_map.bug_id = bugs.bug_id
+                                   AND bug_group_map.group_id = group_control_map.group_id
+                                 WHERE group_control_map.group_id = ?
+                                   AND group_control_map.membercontrol = ?
+                                   AND bug_group_map.group_id IS NULL',
+                                 undef, ($gid, CONTROLMAPMANDATORY));
+
+    my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
+    foreach my $bug_id (@$bug_ids) {
+        $sth->execute($bug_id, $gid);
+    }
+}
+
 sub is_active_bug_group {
     my $self = shift;
     return $self->is_active && $self->is_bug_group;
@@ -183,8 +303,11 @@
     my ($invocant, $name) = @_;
     $name = trim($name);
     $name || ThrowUserError("empty_group_name");
-    my $exists = new Bugzilla::Group({name => $name });
-    ThrowUserError("group_exists", { name => $name }) if $exists;
+    # If we're creating a Group or changing the name...
+    if (!ref($invocant) || $invocant->name ne $name) {
+        my $exists = new Bugzilla::Group({name => $name });
+        ThrowUserError("group_exists", { name => $name }) if $exists;
+    }
     return $name;
 }
 
@@ -202,9 +325,13 @@
     return $regex;
 }
 
+sub _check_is_active { return $_[1] ? 1 : 0; }
 sub _check_is_bug_group {
     return $_[1] ? 1 : 0;
 }
+
+sub _check_icon_url { return $_[1] ? clean_text($_[1]) : undef; }
+
 1;
 
 __END__
@@ -225,6 +352,7 @@
     my $description  = $group->description;
     my $user_reg_exp = $group->user_reg_exp;
     my $is_active    = $group->is_active;
+    my $icon_url     = $group->icon_url;
     my $is_active_bug_group = $group->is_active_bug_group;
 
     my $group_id = Bugzilla::Group::ValidateGroupName('admin', @users);
diff --git a/BugsSite/Bugzilla/Hook.pm b/BugsSite/Bugzilla/Hook.pm
index 8ce2d4b..de33cf5 100644
--- a/BugsSite/Bugzilla/Hook.pm
+++ b/BugsSite/Bugzilla/Hook.pm
@@ -41,8 +41,13 @@
         # 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 $@;
@@ -50,7 +55,28 @@
             Bugzilla->hook_args({});
         }
     }
-    
+}
+
+sub enabled_plugins {
+    my $extdir = bz_locations()->{'extensionsdir'};
+    my @extensions = glob("$extdir/*");
+    my %enabled;
+    foreach my $extension (@extensions) {
+        trick_taint($extension);
+        my $extname = $extension;
+        $extname =~ s{^\Q$extdir\E/}{};
+        next if $extname eq 'CVS' || $extname =~ /^\./;
+        next if -e "$extension/disabled";
+        # Allow extensions to load their own libraries.
+        local @INC = ("$extension/lib", @INC);
+        $enabled{$extname} = do("$extension/info.pl");
+        ThrowCodeError('extension_invalid',
+                { errstr => $@, name => 'version',
+                  extension => $extension }) if $@;
+
+    }
+
+    return \%enabled;
 }
 
 1;
@@ -59,7 +85,7 @@
 
 =head1 NAME
 
-Bugzilla::Hook - Extendible extension hooks for Bugzilla code
+Bugzilla::Hook - Extendable extension hooks for Bugzilla code
 
 =head1 SYNOPSIS
 
@@ -70,11 +96,15 @@
 =head1 DESCRIPTION
 
 Bugzilla allows extension modules to drop in and add routines at 
-arbitrary points in Bugzilla code. These points are refered to as 
+arbitrary points in Bugzilla code. These points are referred to as
 hooks. When a piece of standard Bugzilla code wants to allow an extension
 to perform additional functions, it uses Bugzilla::Hook's L</process>
 subroutine to invoke any extension code if installed. 
 
+There is a sample extension in F<extensions/example/> that demonstrates
+most of the things described in this document, as well as many of the
+hooks available.
+
 =head2 How Hooks Work
 
 When a hook named C<HOOK_NAME> is run, Bugzilla will attempt to invoke any 
@@ -93,6 +123,15 @@
 That returns a hashref. Very frequently, if you want your
 hook to do anything, you have to modify these variables.
 
+=head2 Versioning Extensions
+
+Every extension must have a file in its root called F<info.pl>.
+This file must return a hash when called with C<do>.
+The hash must contain a 'version' key with the current version of the
+extension. Extension authors can also add any extra infomration to this hash if
+required, by adding a new key beginning with x_ which will not be used the
+core Bugzilla code. 
+
 =head1 SUBROUTINES
 
 =over
@@ -127,7 +166,72 @@
 
 =head1 HOOKS
 
-This describes what hooks exist in Bugzilla currently.
+This describes what hooks exist in Bugzilla currently. They are mostly
+in alphabetical order, but some related hooks are near each other instead
+of being alphabetical.
+
+=head2 bug-end_of_update
+
+This happens at the end of L<Bugzilla::Bug/update>, after all other changes are
+made to the database. This generally occurs inside a database transaction.
+
+Params:
+
+=over
+
+=item C<bug> - The changed bug object, with all fields set to their updated
+values.
+
+=item C<timestamp> - The timestamp used for all updates in this transaction.
+
+=item C<changes> - The hash of changed fields. 
+C<$changes-E<gt>{field} = [old, new]>
+
+=back
+
+=head2 buglist-columns
+
+This happens in buglist.cgi after the standard columns have been defined and
+right before the display column determination.  It gives you the opportunity
+to add additional display columns.
+
+Params:
+
+=over
+
+=item C<columns> - A hashref, where the keys are unique string identifiers
+for the column being defined and the values are hashrefs with the
+following fields:
+
+=over
+
+=item C<name> - The name of the column in the database.
+
+=item C<title> - The title of the column as displayed to users.
+
+=back
+
+The definition is structured as:
+
+ $columns->{$id} = { name => $name, title => $title };
+
+=back
+
+=head2 colchange-columns
+
+This happens in F<colchange.cgi> right after the list of possible display
+columns have been defined and gives you the opportunity to add additional
+display columns to the list of selectable columns.
+
+Params:
+
+=over
+
+=item C<columns> - An arrayref containing an array of column IDs.  Any IDs
+added by this hook must have been defined in the the buglist-columns hook.
+See L</buglist-columns>.
+
+=back
 
 =head2 enter_bug-entrydefaultvars
 
@@ -141,6 +245,47 @@
 
 =back
 
+=head2 flag-end_of_update
+
+This happens at the end of L<Bugzilla::Flag/process>, after all other changes
+are made to the database and after emails are sent. It gives you a before/after
+snapshot of flags so you can react to specific flag changes.
+This generally occurs inside a database transaction.
+
+Note that the interface to this hook is B<UNSTABLE> and it may change in the
+future.
+
+Params:
+
+=over
+
+=item C<bug> - The changed bug object.
+
+=item C<timestamp> - The timestamp used for all updates in this transaction.
+
+=item C<old_flags> - The snapshot of flag summaries from before the change.
+
+=item C<new_flags> - The snapshot of flag summaries after the change. Call
+C<my ($removed, $added) = diff_arrays(old_flags, new_flags)> to get the list of
+changed flags, and search for a specific condition like C<added eq 'review-'>.
+
+=back
+
+=head2 install-before_final_checks
+
+Allows execution of custom code before the final checks are done in 
+checksetup.pl.
+
+Params:
+
+=over
+
+=item C<silent>
+
+A flag that indicates whether or not checksetup is running in silent mode.
+
+=back
+
 =head2 install-requirements
 
 Because of the way Bugzilla installation works, there can't be a normal
@@ -190,3 +335,67 @@
 database when run.
 
 =back
+
+=head2 product-confirm_delete
+
+Called before displaying the confirmation message when deleting a product.
+
+Params:
+
+=over
+
+=item C<vars> - The template vars hashref.
+
+=back
+
+=head2 webservice
+
+This hook allows you to add your own modules to the WebService. (See
+L<Bugzilla::WebService>.)
+
+Params:
+
+=over
+
+=item C<dispatch>
+
+A hashref that you can specify the names of your modules and what Perl
+module handles the functions for that module. (This is actually sent to 
+L<SOAP::Lite/dispatch_with>. You can see how that's used in F<xmlrpc.cgi>.)
+
+The Perl module name must start with C<extensions::yourextension::lib::>
+(replace C<yourextension> with the name of your extension). The C<package>
+declaration inside that module must also start with 
+C<extensions::yourextension::lib::> in that module's code.
+
+Example:
+
+  $dispatch->{Example} = "extensions::example::lib::Example";
+
+And then you'd have a module F<extensions/example/lib/Example.pm>
+
+It's recommended that all the keys you put in C<dispatch> start with the
+name of your extension, so that you don't conflict with the standard Bugzilla
+WebService functions (and so that you also don't conflict with other
+plugins).
+
+=back
+
+=head2 webservice-error_codes
+
+If your webservice extension throws custom errors, you can set numeric
+codes for those errors here.
+
+Extensions should use error codes above 10000, unless they are re-using
+an already-existing error code.
+
+Params:
+
+=over
+
+=item C<error_map>
+
+A hash that maps the names of errors (like C<invalid_param>) to numbers.
+See L<Bugzilla::WebService::Constants/WS_ERROR_CODE> for an example.
+
+=back
diff --git a/BugsSite/Bugzilla/Install.pm b/BugsSite/Bugzilla/Install.pm
index 36254a9..c70f8a8 100644
--- a/BugsSite/Bugzilla/Install.pm
+++ b/BugsSite/Bugzilla/Install.pm
@@ -56,10 +56,13 @@
     state_addselfcc    => { options => ['always', 'never',  'cc_unless_role'],
                             default => 'cc_unless_role' },
     # 2006-08-04 wurblzap@gmail.com -- Bug 322693
-    skin               => { subclass => 'Skin', default => 'standard' },
+    skin               => { subclass => 'Skin', default => 'Dusk' },
     # 2006-12-10 LpSolit@gmail.com -- Bug 297186
-    lang               => { options => [split(/[\s,]+/, Bugzilla->params->{'languages'})],
-                            default => Bugzilla->params->{'defaultlanguage'} }
+    lang               => { subclass => 'Lang',
+                            default => ${Bugzilla->languages}[0] },
+    # 2007-07-02 altlist@gmail.com -- Bug 225731
+    quote_replies      => { options => ['quoted_reply', 'simple_reply', 'off'],
+                            default => "quoted_reply" }
     }
 };
 
@@ -304,27 +307,9 @@
         chomp($full_name);
     }
 
-    while (!$password) {
-        # trap a few interrupts so we can fix the echo if we get aborted.
-        local $SIG{HUP}  = \&_create_admin_exit;
-        local $SIG{INT}  = \&_create_admin_exit;
-        local $SIG{QUIT} = \&_create_admin_exit;
-        local $SIG{TERM} = \&_create_admin_exit;
-
-        system("stty","-echo") unless ON_WINDOWS;  # disable input echoing
-
-        print get_text('install_admin_get_password') . ' ';
-        $password = <STDIN>;
-        chomp $password;
-        print "\n", get_text('install_admin_get_password2') . ' ';
-        my $pass2 = <STDIN>;
-        chomp $pass2;
-        eval { validate_password($password, $pass2); };
-        if ($@) {
-            print "\n$@\n";
-            undef $password;
-        }
-        system("stty","echo") unless ON_WINDOWS;
+    if (!$password) {
+        $password = _prompt_for_password(
+            get_text('install_admin_get_password'));
     }
 
     my $admin = Bugzilla::User->create({ login_name    => $login, 
@@ -367,13 +352,52 @@
     print "\n", get_text('install_admin_created', { user => $user }), "\n";
 }
 
-# This is just in case we get interrupted while getting the admin's password.
-sub _create_admin_exit {
+sub _prompt_for_password {
+    my $prompt = shift;
+
+    my $password;
+    while (!$password) {
+        # trap a few interrupts so we can fix the echo if we get aborted.
+        local $SIG{HUP}  = \&_password_prompt_exit;
+        local $SIG{INT}  = \&_password_prompt_exit;
+        local $SIG{QUIT} = \&_password_prompt_exit;
+        local $SIG{TERM} = \&_password_prompt_exit;
+
+        system("stty","-echo") unless ON_WINDOWS;  # disable input echoing
+
+        print $prompt, ' ';
+        $password = <STDIN>;
+        chomp $password;
+        print "\n", get_text('install_confirm_password'), ' ';
+        my $pass2 = <STDIN>;
+        chomp $pass2;
+        eval { validate_password($password, $pass2); };
+        if ($@) {
+            print "\n$@\n";
+            undef $password;
+        }
+        system("stty","echo") unless ON_WINDOWS;
+    }
+    return $password;
+}
+
+# This is just in case we get interrupted while getting a password.
+sub _password_prompt_exit {
     # re-enable input echoing
     system("stty","echo") unless ON_WINDOWS;
     exit 1;
 }
 
+sub reset_password {
+    my $login = shift;
+    my $user = Bugzilla::User->check($login);
+    my $prompt = "\n" . get_text('install_reset_password', { user => $user });
+    my $password = _prompt_for_password($prompt);
+    $user->set_password($password);
+    $user->update();
+    print "\n", get_text('install_reset_password_done'), "\n";
+}
+
 1;
 
 __END__
diff --git a/BugsSite/Bugzilla/Install/CPAN.pm b/BugsSite/Bugzilla/Install/CPAN.pm
new file mode 100644
index 0000000..b37e6d4
--- /dev/null
+++ b/BugsSite/Bugzilla/Install/CPAN.pm
@@ -0,0 +1,251 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by Everything Solved are Copyright (C) 2007
+# Everything Solved, Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Install::CPAN;
+use strict;
+use base qw(Exporter);
+our @EXPORT = qw(set_cpan_config install_module BZ_LIB);
+
+use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(bin_loc install_string);
+
+use CPAN;
+use Cwd qw(abs_path);
+use File::Path qw(rmtree);
+use List::Util qw(shuffle);
+
+# We need the absolute path of ext_libpath, because CPAN chdirs around
+# and so we can't use a relative directory.
+#
+# We need it often enough (and at compile time, in install-module.pl) so 
+# we make it a constant.
+use constant BZ_LIB => abs_path(bz_locations()->{ext_libpath});
+
+# CPAN requires nearly all of its parameters to be set, or it will start
+# asking questions to the user. We want to avoid that, so we have
+# defaults here for most of the required parameters we know about, in case
+# any of them aren't set. The rest are handled by set_cpan_defaults().
+use constant CPAN_DEFAULTS => {
+    auto_commit => 0,
+    # We always force builds, so there's no reason to cache them.
+    build_cache => 0,
+    cache_metadata => 1,
+    index_expire => 1,
+    scan_cache => 'atstart',
+
+    inhibit_startup_message => 1,
+    mbuild_install_build_command => './Build',
+
+    curl => bin_loc('curl'),
+    gzip => bin_loc('gzip'),
+    links => bin_loc('links'),
+    lynx => bin_loc('lynx'),
+    make => bin_loc('make'),
+    pager => bin_loc('less'),
+    tar => bin_loc('tar'),
+    unzip => bin_loc('unzip'),
+    wget => bin_loc('wget'),
+
+    urllist => [shuffle qw(
+        http://cpan.pair.com/
+        http://mirror.hiwaay.net/CPAN/
+        ftp://ftp.dc.aleron.net/pub/CPAN/
+        http://perl.secsup.org/
+        http://mirrors.kernel.org/cpan/)],
+};
+
+sub install_module {
+    my ($name, $notest) = @_;
+    my $bzlib = BZ_LIB;
+
+    # Certain modules require special stuff in order to not prompt us.
+    my $original_makepl = $CPAN::Config->{makepl_arg};
+    # This one's a regex in case we're doing Template::Plugin::GD and it
+    # pulls in Template-Toolkit as a dependency.
+    if ($name =~ /^Template/) {
+        $CPAN::Config->{makepl_arg} .= " TT_ACCEPT=y TT_EXTRAS=n";
+    }
+    elsif ($name eq 'XML::Twig') {
+        $CPAN::Config->{makepl_arg} = "-n $original_makepl";
+    }
+    elsif ($name eq 'Net::LDAP') {
+        $CPAN::Config->{makepl_arg} .= " --skipdeps";
+    }
+    elsif ($name eq 'SOAP::Lite') {
+        $CPAN::Config->{makepl_arg} .= " --noprompt";
+    }
+
+    my $module = CPAN::Shell->expand('Module', $name);
+    print install_string('install_module', 
+              { module => $name, version => $module->cpan_version }) . "\n";
+    if ($notest) {
+        CPAN::Shell->notest('install', $name);
+    }
+    else {
+        CPAN::Shell->force('install', $name);
+    }
+
+    # If it installed any binaries in the Bugzilla directory, delete them.
+    if (-d "$bzlib/bin") {
+        File::Path::rmtree("$bzlib/bin");
+    }
+
+    $CPAN::Config->{makepl_arg} = $original_makepl;
+}
+
+sub set_cpan_config {
+    my $do_global = shift;
+    my $bzlib = BZ_LIB;
+
+    # We set defaults before we do anything, otherwise CPAN will
+    # start asking us questions as soon as we load its configuration.
+    eval { require CPAN::Config; };
+    _set_cpan_defaults();
+
+    # Calling a senseless autoload that does nothing makes us
+    # automatically load any existing configuration.
+    # We want to avoid the "invalid command" message.
+    open(my $saveout, ">&STDOUT");
+    open(STDOUT, '>/dev/null');
+    eval { CPAN->ignore_this_error_message_from_bugzilla; };
+    undef $@;
+    close(STDOUT);
+    open(STDOUT, '>&', $saveout);
+
+    my $dir = $CPAN::Config->{cpan_home};
+    if (!defined $dir || !-w $dir) {
+        # If we can't use the standard CPAN build dir, we try to make one.
+        $dir = "$ENV{HOME}/.cpan";
+        mkdir $dir;
+
+        # If we can't make one, we finally try to use the Bugzilla directory.
+        if (!-w $dir) {
+            print "WARNING: Using the Bugzilla directory as the CPAN home.\n";
+            $dir = "$bzlib/.cpan";
+        }
+    }
+    $CPAN::Config->{cpan_home} = $dir;
+    $CPAN::Config->{build_dir} = "$dir/build";
+    # We always force builds, so there's no reason to cache them.
+    $CPAN::Config->{keep_source_where} = "$dir/source";
+    # This is set both here and in defaults so that it's always true.
+    $CPAN::Config->{inhibit_startup_message} = 1;
+    # Automatically install dependencies.
+    $CPAN::Config->{prerequisites_policy} = 'follow';
+    
+    # Unless specified, we install the modules into the Bugzilla directory.
+    if (!$do_global) {
+        $CPAN::Config->{makepl_arg} .= " LIB=\"$bzlib\""
+            . " INSTALLMAN1DIR=\"$bzlib/man/man1\""
+            . " INSTALLMAN3DIR=\"$bzlib/man/man3\""
+            # The bindirs are here because otherwise we'll try to write to
+            # the system binary dirs, and that will cause CPAN to die.
+            . " INSTALLBIN=\"$bzlib/bin\""
+            . " INSTALLSCRIPT=\"$bzlib/bin\""
+            # INSTALLDIRS=perl is set because that makes sure that MakeMaker
+            # always uses the directories we've specified here.
+            . " INSTALLDIRS=perl";
+        $CPAN::Config->{mbuild_arg} = "--install_base \"$bzlib\"";
+
+        # When we're not root, sometimes newer versions of CPAN will
+        # try to read/modify things that belong to root, unless we set
+        # certain config variables.
+        $CPAN::Config->{histfile} = "$dir/histfile";
+        $CPAN::Config->{use_sqlite} = 0;
+        $CPAN::Config->{prefs_dir} = "$dir/prefs";
+
+        # Unless we actually set PERL5LIB, some modules can't install
+        # themselves, like DBD::mysql, DBD::Pg, and XML::Twig.
+        my $current_lib = $ENV{PERL5LIB} ? $ENV{PERL5LIB} . ':' : '';
+        $ENV{PERL5LIB} = $current_lib . $bzlib;
+    }
+}
+
+sub _set_cpan_defaults {
+    # If CPAN hasn't been configured, we try to use some reasonable defaults.
+    foreach my $key (keys %{CPAN_DEFAULTS()}) {
+        $CPAN::Config->{$key} = CPAN_DEFAULTS->{$key}
+            if !defined $CPAN::Config->{$key};
+    }
+
+    my @missing;
+    # In newer CPANs, this is in HandleConfig. In older CPANs, it's in
+    # Config.
+    if (eval { require CPAN::HandleConfig }) {
+        @missing = CPAN::HandleConfig->missing_config_data;
+    }
+    else {
+        @missing = CPAN::Config->missing_config_data;
+    }
+
+    foreach my $key (@missing) {
+        $CPAN::Config->{$key} = '';
+    }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Install::CPAN - Routines to install Perl modules from CPAN.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Install::CPAN;
+
+ set_cpan_config();
+ install_module('Module::Name', 1);
+
+=head1 DESCRIPTION
+
+This is primarily used by L<install-module> to do the "hard work" of
+installing CPAN modules.
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<set_cpan_config>
+
+Sets up the configuration of CPAN for this session. Must be called
+before L</install_module>. Takes one boolean parameter. If true,
+L</install_module> will install modules globally instead of to the
+local F<lib/> directory. On most systems, you have to be root to do that.
+
+=item C<install_module>
+
+Installs a module from CPAN. Takes two arguments:
+
+=over
+
+=item C<$name> - The name of the module, just like you'd pass to the
+C<install> command in the CPAN shell.
+
+=item C<$notest> - If true, we skip running tests on this module. This
+can greatly speed up the installation time.
+
+=back
+
+Note that calling this function prints a B<lot> of information to
+STDOUT and STDERR.
+
+=back
diff --git a/BugsSite/Bugzilla/Install/DB.pm b/BugsSite/Bugzilla/Install/DB.pm
index 020646b..1bda135 100644
--- a/BugsSite/Bugzilla/Install/DB.pm
+++ b/BugsSite/Bugzilla/Install/DB.pm
@@ -22,9 +22,9 @@
 
 use strict;
 
-use Bugzilla::Bug qw(is_open_state);
 use Bugzilla::Constants;
 use Bugzilla::Hook;
+use Bugzilla::Install::Util qw(indicate_progress install_string);
 use Bugzilla::Util;
 use Bugzilla::Series;
 
@@ -32,23 +32,6 @@
 use Date::Format;
 use IO::File;
 
-use base qw(Exporter);
-our @EXPORT_OK = qw(
-    indicate_progress
-);
-
-sub indicate_progress {
-    my ($params) = @_;
-    my $current = $params->{current};
-    my $total   = $params->{total};
-    my $every   = $params->{every} || 1;
-
-    print "." if !($current % $every);
-    if ($current % ($every * 60) == 0) {
-        print "$current/$total (" . int($current * 100 / $total) . "%)\n";
-    }
-}
-
 # NOTE: This is NOT the function for general table updates. See
 # update_table_definitions for that. This is only for the fielddefs table.
 sub update_fielddefs_definition {
@@ -131,6 +114,7 @@
 # the purpose of a column.
 #
 sub update_table_definitions {
+    my $old_params = shift;
     my $dbh = Bugzilla->dbh;
     _update_pre_checksetup_bugzillas();
 
@@ -331,12 +315,6 @@
         $dbh->do('UPDATE quips SET userid = NULL WHERE userid = 0');
     }
 
-    # Right now, we only create the "thetext" index on MySQL.
-    if ($dbh->isa('Bugzilla::DB::Mysql')) {
-        $dbh->bz_add_index('longdescs', 'longdescs_thetext_idx',
-                           {TYPE => 'FULLTEXT', FIELDS => [qw(thetext)]});
-    }
-
     _convert_attachments_filename_from_mediumtext();
 
     $dbh->bz_add_column('quips', 'approved',
@@ -380,9 +358,9 @@
     if ($dbh->bz_column_info('components', 'initialqacontact')->{NOTNULL}) {
         $dbh->bz_alter_column('components', 'initialqacontact', 
                               {TYPE => 'INT3'});
-        $dbh->do("UPDATE components SET initialqacontact = NULL " .
-                  "WHERE initialqacontact = 0");
     }
+    $dbh->do("UPDATE components SET initialqacontact = NULL " .
+              "WHERE initialqacontact = 0");
 
     _migrate_email_prefs_to_new_table();
     _initialize_dependency_tree_changes_email_pref();
@@ -499,7 +477,7 @@
     $dbh->bz_add_column('setting', 'subclass', {TYPE => 'varchar(32)'});
 
     $dbh->bz_alter_column('longdescs', 'thetext', 
-        { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, '');
+        {TYPE => 'LONGTEXT', NOTNULL => 1}, '');
 
     # 2006-10-20 LpSolit@gmail.com - Bug 189627
     $dbh->bz_add_column('group_control_map', 'editcomponents',
@@ -522,11 +500,39 @@
     _fix_uppercase_custom_field_names();
     _fix_uppercase_index_names();
 
+    # 2007-05-17 LpSolit@gmail.com - Bug 344965
+    _initialize_workflow($old_params);
+
+    # 2007-08-08 LpSolit@gmail.com - Bug 332149
+    $dbh->bz_add_column('groups', 'icon_url', {TYPE => 'TINYTEXT'});
+
+    # 2007-08-21 wurblzap@gmail.com - Bug 365378
+    _make_lang_setting_dynamic();
+    
+    # 2007-11-29 xiaoou.wu@oracle.com - Bug 153129
+    _change_text_types();
+
+    # 2007-09-09 LpSolit@gmail.com - Bug 99215
+    _fix_attachment_modification_date();
+
+    # This had the wrong definition in DB::Schema.
+    $dbh->bz_alter_column('namedqueries', 'query_type',
+                          {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
+
+    $dbh->bz_drop_index('longdescs', 'longdescs_thetext_idx');
+    _populate_bugs_fulltext();
+
+    # 2008-01-18 xiaoou.wu@oracle.com - Bug 414292
+    $dbh->bz_alter_column('series', 'query',
+        { TYPE => 'MEDIUMTEXT', NOTNULL => 1 });
+
     ################################################################
     # New --TABLE-- changes should go *** A B O V E *** this point #
     ################################################################
 
     Bugzilla::Hook::process('install-update_db');
+
+    $dbh->bz_setup_foreign_keys();
 }
 
 # Subroutines should be ordered in the order that they are called.
@@ -664,8 +670,9 @@
               " for each 50.\n\n";
         local $| = 1;
 
-        $dbh->bz_lock_tables('bugs write', 'longdescs write', 'profiles write',
-                             'bz_schema WRITE');
+        # On MySQL, longdescs doesn't benefit from transactions, but this
+        # doesn't hurt.
+        $dbh->bz_start_transaction();
 
         $dbh->do('DELETE FROM longdescs');
 
@@ -727,7 +734,7 @@
 
         print "\n\n";
         $dbh->bz_drop_column('bugs', 'long_desc');
-        $dbh->bz_unlock_tables();
+        $dbh->bz_commit_transaction();
     } # main if
 }
 
@@ -745,8 +752,7 @@
                            [qw(fieldid)]);
         print "Populating new bugs_activity.fieldid field...\n";
 
-        $dbh->bz_lock_tables('bugs_activity WRITE', 'fielddefs WRITE');
-
+        $dbh->bz_start_transaction();
 
         my $ids = $dbh->selectall_arrayref(
             'SELECT DISTINCT fielddefs.id, bugs_activity.field
@@ -765,7 +771,7 @@
             $dbh->do("UPDATE bugs_activity SET fieldid = ? WHERE field = ?",
                      undef, $id, $field);
         }
-        $dbh->bz_unlock_tables();
+        $dbh->bz_commit_transaction();
 
         $dbh->bz_drop_column('bugs_activity', 'field');
     }
@@ -1034,10 +1040,10 @@
 ENDTEXT
 
         # Re-crypt everyone's password.
+        my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM profiles');
         my $sth = $dbh->prepare("SELECT userid, password FROM profiles");
         $sth->execute();
 
-        my $total = $sth->rows;
         my $i = 1;
 
         print "Fixing passwords...\n";
@@ -1076,12 +1082,12 @@
 
         # Now we need to process the bugs_activity table and reformat the data
         print "Fixing activity log...\n";
+        my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM bugs_activity');
         my $sth = $dbh->prepare("SELECT bug_id, who, bug_when, fieldid,
                                 oldvalue, newvalue FROM bugs_activity");
         $sth->execute;
         my $i = 0;
-        my $total = $sth->rows;
-        while (my ($bug_id, $who, $bug_when, $fieldid, $oldvalue, $newvalue) 
+        while (my ($bug_id, $who, $bug_when, $fieldid, $oldvalue, $newvalue)
                    = $sth->fetchrow_array()) 
         {
             $i++;
@@ -1974,7 +1980,7 @@
                         $data{$fields[$i]}{$numbers[0]} = $numbers[$i + 1];
 
                         # Keep a total of the number of open bugs for this day
-                        if (is_open_state($fields[$i])) {
+                        if (grep { $_ eq $fields[$i] } @openedstatuses) {
                             $data{$open_name}{$numbers[0]} += $numbers[$i + 1];
                         }
                     }
@@ -2140,20 +2146,19 @@
         # group_$gid and add _<n> if necessary.
         my $trycount = 0;
         my $trygroupname;
-        my $trygroupsth = $dbh->prepare("SELECT id FROM groups where name = ?");
-        do {
+        my $sth = $dbh->prepare("SELECT 1 FROM groups where name = ?");
+        my $name_exists = 1;
+
+        while ($name_exists) {
             $trygroupname = "group_$emptygroupid";
             if ($trycount > 0) {
                $trygroupname .= "_$trycount";
             }
-            $trygroupsth->execute($trygroupname);
-            if ($trygroupsth->rows > 0) {
-                $trycount ++;
-            }
-        } while ($trygroupsth->rows > 0);
-        my $sth = $dbh->prepare("UPDATE groups SET name = ? " .
-                                 "WHERE id = $emptygroupid");
-        $sth->execute($trygroupname);
+            $name_exists = $dbh->selectrow_array($sth, undef, $trygroupname);
+            $trycount++;
+        }
+        $dbh->do("UPDATE groups SET name = ? WHERE id = ?",
+                 undef, $trygroupname, $emptygroupid);
         print "Group $emptygroupid had an empty name; renamed as",
               " '$trygroupname'.\n";
     }
@@ -2206,11 +2211,14 @@
         my %requestprefs = ("FlagRequestee" => EVT_FLAG_REQUESTED,
                             "FlagRequester" => EVT_REQUESTED_FLAG);
 
+        # We run the below code in a transaction to speed things up.
+        $dbh->bz_start_transaction();
+
         # Select all emailflags flag strings
+        my $total = $dbh->selectrow_array('SELECT COUNT(*) FROM profiles');
         my $sth = $dbh->prepare("SELECT userid, emailflags FROM profiles");
         $sth->execute();
         my $i = 0;
-        my $total = $sth->rows;
 
         while (my ($userid, $flagstring) = $sth->fetchrow_array()) {
             $i++;
@@ -2272,6 +2280,7 @@
         # EVT_ATTACHMENT.
         _clone_email_event(EVT_ATTACHMENT, EVT_ATTACHMENT_DATA);
 
+        $dbh->bz_commit_transaction();
         $dbh->bz_drop_column("profiles", "emailflags");
     }
 }
@@ -2444,7 +2453,8 @@
             join("&", map { "bug_status=" . url_quote($_) }
                           ('UNCONFIRMED', 'NEW', 'ASSIGNED', 'REOPENED'));
         my $open_bugs_query_base_new =
-            join("&", map { "bug_status=" . url_quote($_) } BUG_STATE_OPEN);
+            join("&", map { "bug_status=" . url_quote($_) }
+                          ('NEW', 'REOPENED', 'ASSIGNED', 'UNCONFIRMED'));
         my $sth_openbugs_series =
             $dbh->prepare("SELECT series_id FROM series WHERE query IN (?, ?)");
         # Statement to find the series which has collected the most data.
@@ -2606,7 +2616,7 @@
         # and then truncate the summary.
         my $long_summary_bugs = $dbh->selectall_arrayref(
             'SELECT bug_id, short_desc, reporter
-               FROM bugs WHERE LENGTH(short_desc) > 255');
+               FROM bugs WHERE CHAR_LENGTH(short_desc) > 255');
 
         if (@$long_summary_bugs) {
             print <<EOT;
@@ -2785,6 +2795,263 @@
     }
 }
 
+sub _initialize_workflow {
+    my $old_params = shift;
+    my $dbh = Bugzilla->dbh;
+
+    $dbh->bz_add_column('bug_status', 'is_open',
+                        {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+
+    # Till now, bug statuses were not customizable. Nevertheless, local
+    # changes are possible and so we will try to respect these changes.
+    # This means: get the status of bugs having a resolution different from ''
+    # and mark these statuses as 'closed', even if some of these statuses are
+    # expected to be open statuses. Bug statuses we have no information about
+    # are left as 'open'.
+    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
+    # one closed state in the DB (i.e. with is_open = 0). This would mean that
+    # the DB has already been updated at least once and maybe the admin decided
+    # that e.g. 'RESOLVED' is now an open state, in which case we don't want to
+    # override this attribute. At least one bug status has to be a closed state
+    # anyway (due to the 'duplicate_or_move_bug_status' parameter) so it's safe
+    # to use this criteria.
+    my $num_closed_states = $dbh->selectrow_array('SELECT COUNT(*) FROM bug_status
+                                                   WHERE is_open = 0');
+
+    if (!$num_closed_states) {
+        @closed_statuses =
+          map {$dbh->quote($_)} (@closed_statuses, qw(RESOLVED VERIFIED CLOSED));
+
+        print "Marking closed bug statuses as such...\n";
+        $dbh->do('UPDATE bug_status SET is_open = 0 WHERE value IN (' .
+                  join(', ', @closed_statuses) . ')');
+    }
+
+    # Populate the status_workflow table. We do nothing if the table already
+    # has entries. If all bug status transitions have been deleted, the
+    # workflow will be restored to its default schema.
+    my $count = $dbh->selectrow_array('SELECT COUNT(*) FROM status_workflow');
+
+    if (!$count) {
+        # Make sure the variables below are defined as
+        # status_workflow.require_comment cannot be NULL.
+        my $create = $old_params->{'commentoncreate'} || 0;
+        my $confirm = $old_params->{'commentonconfirm'} || 0;
+        my $accept = $old_params->{'commentonaccept'} || 0;
+        my $resolve = $old_params->{'commentonresolve'} || 0;
+        my $verify = $old_params->{'commentonverify'} || 0;
+        my $close = $old_params->{'commentonclose'} || 0;
+        my $reopen = $old_params->{'commentonreopen'} || 0;
+        # This was till recently the only way to get back to NEW for
+        # confirmed bugs, so we use this parameter here.
+        my $reassign = $old_params->{'commentonreassign'} || 0;
+
+        # This is the default workflow.
+        my @workflow = ([undef, 'UNCONFIRMED', $create],
+                        [undef, 'NEW', $create],
+                        [undef, 'ASSIGNED', $create],
+                        ['UNCONFIRMED', 'NEW', $confirm],
+                        ['UNCONFIRMED', 'ASSIGNED', $accept],
+                        ['UNCONFIRMED', 'RESOLVED', $resolve],
+                        ['NEW', 'ASSIGNED', $accept],
+                        ['NEW', 'RESOLVED', $resolve],
+                        ['ASSIGNED', 'NEW', $reassign],
+                        ['ASSIGNED', 'RESOLVED', $resolve],
+                        ['REOPENED', 'NEW', $reassign],
+                        ['REOPENED', 'ASSIGNED', $accept],
+                        ['REOPENED', 'RESOLVED', $resolve],
+                        ['RESOLVED', 'UNCONFIRMED', $reopen],
+                        ['RESOLVED', 'REOPENED', $reopen],
+                        ['RESOLVED', 'VERIFIED', $verify],
+                        ['RESOLVED', 'CLOSED', $close],
+                        ['VERIFIED', 'UNCONFIRMED', $reopen],
+                        ['VERIFIED', 'REOPENED', $reopen],
+                        ['VERIFIED', 'CLOSED', $close],
+                        ['CLOSED', 'UNCONFIRMED', $reopen],
+                        ['CLOSED', 'REOPENED', $reopen]);
+
+        print "Now filling the 'status_workflow' table with valid bug status transitions...\n";
+        my $sth_select = $dbh->prepare('SELECT id FROM bug_status WHERE value = ?');
+        my $sth = $dbh->prepare('INSERT INTO status_workflow (old_status, new_status,
+                                             require_comment) VALUES (?, ?, ?)');
+
+        foreach my $transition (@workflow) {
+            my ($from, $to);
+            # If it's an initial state, there is no "old" value.
+            $from = $dbh->selectrow_array($sth_select, undef, $transition->[0])
+              if $transition->[0];
+            $to = $dbh->selectrow_array($sth_select, undef, $transition->[1]);
+            # If one of the bug statuses doesn't exist, the transition is invalid.
+            next if (($transition->[0] && !$from) || !$to);
+
+            $sth->execute($from, $to, $transition->[2] ? 1 : 0);
+        }
+    }
+
+    # Make sure the bug status used by the 'duplicate_or_move_bug_status'
+    # parameter has all the required transitions set.
+    Bugzilla::Status::add_missing_bug_status_transitions();
+}
+
+sub _make_lang_setting_dynamic {
+    my $dbh = Bugzilla->dbh;
+    my $count = $dbh->selectrow_array(q{SELECT 1 FROM setting
+                                         WHERE name = 'lang'
+                                           AND subclass IS NULL});
+    if ($count) {
+        $dbh->do(q{UPDATE setting SET subclass = 'Lang' WHERE name = 'lang'});
+        $dbh->do(q{DELETE FROM setting_value WHERE name = 'lang'});
+    }
+}
+
+sub _fix_attachment_modification_date {
+    my $dbh = Bugzilla->dbh;
+    if (!$dbh->bz_column_info('attachments', 'modification_time')) {
+        # Allow NULL values till the modification time has been set.
+        $dbh->bz_add_column('attachments', 'modification_time', {TYPE => 'DATETIME'});
+
+        print "Setting the modification time for attachments...\n";
+        $dbh->do('UPDATE attachments SET modification_time = creation_ts');
+
+        # Now force values to be always defined.
+        $dbh->bz_alter_column('attachments', 'modification_time',
+                              {TYPE => 'DATETIME', NOTNULL => 1});
+
+        # Update the modification time for attachments which have been modified.
+        my $attachments =
+          $dbh->selectall_arrayref('SELECT attach_id, MAX(bug_when) FROM bugs_activity
+                                    WHERE attach_id IS NOT NULL ' .
+                                    $dbh->sql_group_by('attach_id'));
+
+        my $sth = $dbh->prepare('UPDATE attachments SET modification_time = ?
+                                 WHERE attach_id = ?');
+        $sth->execute($_->[1], $_->[0]) foreach (@$attachments);
+    }
+    # We add this here to be sure to have the index being added, due to the original
+    # patch omitting it.
+    $dbh->bz_add_index('attachments', 'attachments_modification_time_idx',
+                       [qw(modification_time)]);
+}
+
+sub _change_text_types {
+    my $dbh = Bugzilla->dbh; 
+    return if 
+        $dbh->bz_column_info('namedqueries', 'query')->{TYPE} eq 'LONGTEXT';
+    _check_content_length('attachments', 'mimetype',    255, 'attach_id');
+    _check_content_length('fielddefs',   'description', 255, 'id');
+    _check_content_length('attachments', 'description', 255, 'attach_id');
+
+    $dbh->bz_alter_column('bugs', 'bug_file_loc',
+        { TYPE => 'MEDIUMTEXT'});
+    $dbh->bz_alter_column('longdescs', 'thetext',
+        { TYPE => 'LONGTEXT', NOTNULL => 1 });
+    $dbh->bz_alter_column('attachments', 'description',
+        { TYPE => 'TINYTEXT', NOTNULL => 1 });
+    $dbh->bz_alter_column('attachments', 'mimetype',
+        { TYPE => 'TINYTEXT', NOTNULL => 1 });
+    # This also changes NULL to NOT NULL.
+    $dbh->bz_alter_column('flagtypes', 'description',
+        { TYPE => 'MEDIUMTEXT', NOTNULL => 1 }, '');
+    $dbh->bz_alter_column('fielddefs', 'description',
+        { TYPE => 'TINYTEXT', NOTNULL => 1 });
+    $dbh->bz_alter_column('groups', 'description',
+        { TYPE => 'MEDIUMTEXT', NOTNULL => 1 });
+    $dbh->bz_alter_column('quips', 'quip',
+        { TYPE => 'MEDIUMTEXT', NOTNULL => 1 });
+    $dbh->bz_alter_column('namedqueries', 'query',
+        { TYPE => 'LONGTEXT', NOTNULL => 1 });
+
+} 
+
+sub _check_content_length {
+    my ($table_name, $field_name, $max_length, $id_field) = @_;
+    my $dbh = Bugzilla->dbh;
+    my %contents = @{ $dbh->selectcol_arrayref(
+        "SELECT $id_field, $field_name FROM $table_name 
+          WHERE CHAR_LENGTH($field_name) > ?", {Columns=>[1,2]}, $max_length) };
+
+    if (scalar keys %contents) {
+        print install_string('install_data_too_long',
+                             { column     => $field_name,
+                               id_column  => $id_field,
+                               table      => $table_name,
+                               max_length => $max_length });
+        foreach my $id (keys %contents) {
+            my $string = $contents{$id};
+            # Don't dump the whole string--it could be 16MB.
+            if (length($string) > 80) {
+                $string = substr($string, 0, 30) . "..." 
+                         . substr($string, -30) . "\n";
+            }
+            print "$id: $string\n";
+        }
+        exit 3;
+    }
+}
+
+sub _populate_bugs_fulltext {
+    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');
+        return if !@$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++ });
+            }
+            print "\n";
+        }
+    }
+}
+
 1;
 
 __END__
@@ -2831,18 +3098,4 @@
 
 Returns:     nothing
 
-=item C<indicate_progress({ total => $total, current => $count, every => 1 })>
-
-Description: This prints out lines of dots as a long update is going on,
-             to let the user know where we are and that we're not frozen.
-             A new line of dots will start every 60 dots.
-
-Params:      C<total> - The total number of items we're processing.
-             C<current> - The number of the current item we're processing.
-             C<every> - How often the function should print out a dot.
-               For example, if this is 10, the function will print out
-               a dot every ten items.
-
-Returns:     nothing
-
 =back
diff --git a/BugsSite/Bugzilla/Install/Filesystem.pm b/BugsSite/Bugzilla/Install/Filesystem.pm
index 31944bc..54350be 100644
--- a/BugsSite/Bugzilla/Install/Filesystem.pm
+++ b/BugsSite/Bugzilla/Install/Filesystem.pm
@@ -62,6 +62,7 @@
     my $webdotdir     = bz_locations()->{'webdotdir'};
     my $templatedir   = bz_locations()->{'templatedir'};
     my $libdir        = bz_locations()->{'libpath'};
+    my $extlib        = bz_locations()->{'ext_libpath'};
     my $skinsdir      = bz_locations()->{'skinsdir'};
 
     my $ws_group      = Bugzilla->localconfig->{'webservergroup'};
@@ -87,7 +88,7 @@
     my $owner_dir_readable = 0700;
     # Writeable by the web server.
     my $ws_dir_writeable = $ws_group ? 0770 : 01777;
-    # The webserver can overwrite files owned by other users, 
+    # The web server can overwrite files owned by other users, 
     # in this directory.
     my $ws_dir_full_control = $ws_group ? 0770 : 0777;
 
@@ -112,10 +113,13 @@
         'whine.pl'        => { perms => $ws_executable },
         'customfield.pl'  => { perms => $owner_executable },
         'email_in.pl'     => { perms => $ws_executable },
+        'sanitycheck.pl'  => { perms => $ws_executable },
+        'install-module.pl' => { perms => $owner_executable },
 
         'docs/makedocs.pl'   => { perms => $owner_executable },
-        'docs/rel_notes.txt' => { perms => $ws_readable },
-        'docs/README.docs'   => { perms => $owner_readable },
+        '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 },
@@ -151,29 +155,33 @@
                                      dirs => $ws_dir_readable },
          "$libdir/Bugzilla"    => { files => $ws_readable,
                                      dirs => $ws_dir_readable },
+         $extlib               => { files => $ws_readable,
+                                     dirs => $ws_dir_readable },
          $templatedir          => { files => $ws_readable,
                                      dirs => $ws_dir_readable },
+         $extensionsdir        => { files => $ws_readable,
+                                     dirs => $ws_dir_readable },
          images                => { files => $ws_readable,
                                      dirs => $ws_dir_readable },
          css                   => { files => $ws_readable,
                                      dirs => $ws_dir_readable },
          js                    => { files => $ws_readable,
                                      dirs => $ws_dir_readable },
-         skins                 => { files => $ws_readable,
+         $skinsdir             => { files => $ws_readable,
                                      dirs => $ws_dir_readable },
          t                     => { files => $owner_readable,
                                      dirs => $owner_dir_readable },
-         'docs/html'           => { files => $ws_readable,
+         'docs/*/html'         => { files => $ws_readable,
                                      dirs => $ws_dir_readable },
-         'docs/pdf'            => { files => $ws_readable,
+         'docs/*/pdf'          => { files => $ws_readable,
                                      dirs => $ws_dir_readable },
-         'docs/txt'            => { files => $ws_readable,
+         'docs/*/txt'          => { files => $ws_readable,
                                      dirs => $ws_dir_readable },
-         'docs/images'         => { files => $ws_readable,
+         'docs/*/images'       => { files => $ws_readable,
                                      dirs => $ws_dir_readable },
          'docs/lib'            => { files => $owner_readable,
                                      dirs => $owner_dir_readable },
-         'docs/xml'            => { files => $owner_readable,
+         'docs/*/xml'          => { files => $owner_readable,
                                      dirs => $owner_dir_readable },
     );
 
@@ -183,39 +191,32 @@
     # pointing at its default permissions.
     my %create_dirs = (
         $datadir                => $ws_dir_full_control,
-        "$datadir/mimedump-tmp" => $ws_dir_writeable,
         "$datadir/mining"       => $ws_dir_readable,
         "$datadir/duplicates"   => $ws_dir_readable,
         $attachdir              => $ws_dir_writeable,
         $extensionsdir          => $ws_dir_readable,
         graphs                  => $ws_dir_writeable,
         $webdotdir              => $ws_dir_writeable,
-        'skins/custom'          => $ws_dir_readable,
-        'skins/contrib'         => $ws_dir_readable,
+        "$skinsdir/custom"      => $ws_dir_readable,
+        "$skinsdir/contrib"     => $ws_dir_readable,
     );
 
     # The name of each file, pointing at its default permissions and
     # default contents.
-    my %create_files = (
-        "$datadir/mail"    => { perms => $ws_readable },
-    );
+    my %create_files = ();
 
     # Each standard stylesheet has an associated custom stylesheet that
     # we create. Also, we create placeholders for standard stylesheets
     # for contrib skins which don't provide them themselves.
     foreach my $skin_dir ("$skinsdir/custom", <$skinsdir/contrib/*>) {
-        next unless -d $skin_dir;
         next if basename($skin_dir) =~ /^cvs$/i;
-        foreach (<$skinsdir/standard/*.css>) {
-            my $standard_css_file = basename($_);
-            my $custom_css_file = "$skin_dir/$standard_css_file";
-            $create_files{$custom_css_file} = { perms => $ws_readable, contents => <<EOT
-/*
- * Custom rules for $standard_css_file.
- * The rules you put here override rules in that stylesheet.
- */
-EOT
-            }
+        $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);
         }
     }
 
@@ -250,6 +251,8 @@
                                           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 },
 
@@ -280,13 +283,13 @@
 EOT
         },
 
-        # Even though $datadir may not (and should not) be in the webtree,
-        # we can't know for sure, so create the .htaccess anyway. It's harmless
-        # if it's not accessible...
+        # 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 loadable over the web
+# duplicates.xul and must be accessible from the web server
 deny from all
 <Files duplicates.rdf>
   allow from all
@@ -371,6 +374,18 @@
 
 }
 
+# A simple helper for creating "empty" CSS files.
+sub _add_custom_css {
+    my ($skin_dir, $path, $create_files, $perms) = @_;
+    $create_files->{"$skin_dir/$path"} = { perms => $perms, contents => <<EOT
+/*
+ * Custom rules for $path.
+ * The rules you put here override rules in that stylesheet.
+ */
+EOT
+    };
+}
+
 sub create_htaccess {
     _create_files(%{FILESYSTEM()->{htaccess}});
 
diff --git a/BugsSite/Bugzilla/Install/Localconfig.pm b/BugsSite/Bugzilla/Install/Localconfig.pm
index ed502d8..8857b75 100644
--- a/BugsSite/Bugzilla/Install/Localconfig.pm
+++ b/BugsSite/Bugzilla/Install/Localconfig.pm
@@ -31,8 +31,11 @@
 use strict;
 
 use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(bin_loc);
+use Bugzilla::Util qw(generate_random_password);
 
 use Data::Dumper;
+use File::Basename qw(dirname);
 use IO::File;
 use Safe;
 
@@ -50,7 +53,7 @@
         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 (like your local configuration data and non-cgi
+# 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
@@ -183,6 +186,17 @@
 # Please specify the directory name only; do not use trailing slash.
 EOT
     },
+    {
+        name    => 'site_wide_secret',
+        default => sub { generate_random_password(256) },
+        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(
@@ -349,44 +363,11 @@
     return { old_vars => \@old_vars, new_vars => \@new_vars };
 }
 
-sub _get_default_cvsbin {
-    return '' if ON_WINDOWS;
-
-    my $cvs_executable = `which cvs`;
-    if ($cvs_executable =~ /no cvs/ || $cvs_executable eq '') {
-        # If which didn't find it, just set to blank
-        $cvs_executable = "";
-    } else {
-        chomp $cvs_executable;
-    }
-    return $cvs_executable;
-}
-
-sub _get_default_interdiffbin {
-    return '' if ON_WINDOWS;
-
-    my $interdiff = `which interdiff`;
-    if ($interdiff =~ /no interdiff/ || $interdiff eq '') {
-        # If which didn't find it, just set to blank
-        $interdiff = '';
-    } else {
-        chomp $interdiff;
-    }
-    return $interdiff;
-}
-
+sub _get_default_cvsbin       { return bin_loc('cvs') }
+sub _get_default_interdiffbin { return bin_loc('interdiff') }
 sub _get_default_diffpath {
-    return '' if ON_WINDOWS;
-
-    my $diff_binaries;
-    $diff_binaries = `which diff`;
-    if ($diff_binaries =~ /no diff/ || $diff_binaries eq '') {
-        # If which didn't find it, set to blank
-        $diff_binaries = "";
-    } else {
-        $diff_binaries =~ s:/diff\n$::;
-    }
-    return $diff_binaries;
+    my $diff_bin = bin_loc('diff');
+    return dirname($diff_bin);
 }
 
 1;
diff --git a/BugsSite/Bugzilla/Install/Requirements.pm b/BugsSite/Bugzilla/Install/Requirements.pm
index 899821c..47699e4 100644
--- a/BugsSite/Bugzilla/Install/Requirements.pm
+++ b/BugsSite/Bugzilla/Install/Requirements.pm
@@ -25,8 +25,8 @@
 
 use strict;
 
+use Bugzilla::Install::Util qw(vers_cmp install_string);
 use List::Util qw(max);
-use POSIX ();
 use Safe;
 
 use base qw(Exporter);
@@ -36,9 +36,7 @@
 
     check_requirements
     check_graphviz
-    display_version_and_os
     have_vers
-    vers_cmp
     install_command
 );
 
@@ -56,11 +54,15 @@
 # are 'blacklisted'--that is, even if the version is high enough, Bugzilla
 # will refuse to say that it's OK to run with that version.
 sub REQUIRED_MODULES {
+    my $perl_ver = sprintf('%vd', $^V);
     my @modules = (
     {
-        package => 'CGI',
+        package => 'CGI.pm',
         module  => 'CGI',
-        version => '2.93'
+        # Perl 5.10 requires CGI 3.33 due to a taint issue when
+        # uploading attachments, see bug 416382.
+        # Require CGI 3.21 for -httponly support, see bug 368502.
+        version => (vers_cmp($perl_ver, '5.10') > -1) ? '3.33' : '3.21'
     },
     {
         package => 'TimeDate',
@@ -68,19 +70,19 @@
         version => '2.21'
     },
     {
-        package => 'DBI',
-        module  => 'DBI',
-        version => '1.41'
-    },
-    {
         package => 'PathTools',
         module  => 'File::Spec',
         version => '0.84'
     },
     {
+        package => 'DBI',
+        module  => 'DBI',
+        version => '1.41'
+    },
+    {
         package => 'Template-Toolkit',
         module  => 'Template',
-        version => '2.12'
+        version => '2.15'
     },
     {
         package => 'Email-Send',
@@ -88,10 +90,14 @@
         version => ON_WINDOWS ? '2.16' : '2.00'
     },
     {
-        # This will pull in Email::MIME for us, also. 
+        package => 'Email-MIME',
+        module  => 'Email::MIME',
+        version => '1.861'
+    },
+    {
         package => 'Email-MIME-Modifier',
         module  => 'Email::MIME::Modifier',
-        version => 0
+        version => '1.442'
     },
     );
 
@@ -109,6 +115,12 @@
         feature => 'Graphical Reports, New Charts, Old Charts'
     },
     {
+        package => 'Chart',
+        module  => 'Chart::Base',
+        version => '1.0',
+        feature => 'New Charts, Old Charts'
+    },
+    {
         package => 'Template-GD',
         # This module tells us whether or not Template-GD is installed
         # on Template-Toolkits after 2.14, and still works with 2.14 and lower.
@@ -117,10 +129,10 @@
         feature => 'Graphical Reports'
     },
     {
-        package => 'Chart',
-        module  => 'Chart::Base',
-        version => '1.0',
-        feature => 'New Charts, Old Charts'
+        package => 'GDTextUtil',
+        module  => 'GD::Text',
+        version => 0,
+        feature => 'Graphical Reports'
     },
     {
         package => 'GDGraph',
@@ -128,12 +140,6 @@
         version => 0,
         feature => 'Graphical Reports'
     },
-    { 
-        package => 'GDTextUtil',
-        module  => 'GD::Text',
-        version => 0,
-        feature => 'Graphical Reports'
-    },
     {
         package => 'XML-Twig',
         module  => 'XML::Twig',
@@ -172,9 +178,23 @@
         feature => 'LDAP Authentication'
     },
     {
+        package => 'Authen-SASL',
+        module  => 'Authen::SASL',
+        version => 0,
+        feature => 'SMTP Authentication'
+    },
+    {
+        package => 'RadiusPerl',
+        module  => 'Authen::Radius',
+        version => 0,
+        feature => 'RADIUS Authentication'
+    },
+    {
         package => 'SOAP-Lite',
         module  => 'SOAP::Lite',
         version => 0,
+        # These versions (0.70 -> 0.710.05) are affected by bug 468009
+        blacklist => ['^0\.70', '^0\.710?\.0[1-5]$'],
         feature => 'XML-RPC Interface'
     },
     {
@@ -212,15 +232,6 @@
         version => '1.999022',
         feature => 'mod_perl'
     },
-    # Even very new releases of perl (5.8.5) don't come with this version,
-    # so I didn't want to make it a general requirement just for
-    # running under mod_cgi.
-    {
-        package => 'CGI',
-        module  => 'CGI',
-        version => '3.11',
-        feature => 'mod_perl'
-    },
     );
 
     my $all_modules = _get_extension_requirements(
@@ -258,11 +269,11 @@
 sub check_requirements {
     my ($output) = @_;
 
-    print "\nChecking perl modules...\n" if $output;
+    print "\n", install_string('checking_modules'), "\n" if $output;
     my $root = ROOT_USER;
-    my %missing = _check_missing(REQUIRED_MODULES, $output);
+    my $missing = _check_missing(REQUIRED_MODULES, $output);
 
-    print "\nChecking available perl DBD modules...\n" if $output;
+    print "\n", install_string('checking_dbd'), "\n" if $output;
     my $have_one_dbd = 0;
     my $db_modules = DB_MODULE;
     foreach my $db (keys %$db_modules) {
@@ -270,20 +281,20 @@
         $have_one_dbd = 1 if have_vers($dbd, $output);
     }
 
-    print "\nThe following Perl modules are optional:\n" if $output;
-    my %missing_optional = _check_missing(OPTIONAL_MODULES, $output);
+    print "\n", install_string('checking_optional'), "\n" if $output;
+    my $missing_optional = _check_missing(OPTIONAL_MODULES, $output);
 
     # If we're running on Windows, reset the input line terminator so that
     # console input works properly - loading CGI tends to mess it up
     $/ = "\015\012" if ON_WINDOWS;
 
-    my $pass = !scalar(keys %missing) && $have_one_dbd;
+    my $pass = !scalar(@$missing) && $have_one_dbd;
     return {
         pass     => $pass,
         one_dbd  => $have_one_dbd,
-        missing  => \%missing,
-        optional => \%missing_optional,
-        any_missing => !$pass || scalar(keys %missing_optional),
+        missing  => $missing,
+        optional => $missing_optional,
+        any_missing => !$pass || scalar(@$missing_optional),
     };
 }
 
@@ -291,35 +302,53 @@
 sub _check_missing {
     my ($modules, $output) = @_;
 
-    my %missing;
+    my @missing;
     foreach my $module (@$modules) {
         unless (have_vers($module, $output)) {
-            $missing{$module->{package}} = $module;
+            push(@missing, $module);
         }
     }
 
-    return %missing;
+    return \@missing;
+}
+
+# Returns the build ID of ActivePerl. If several versions of
+# ActivePerl are installed, it won't be able to know which one
+# you are currently running. But that's our best guess.
+sub _get_activestate_build_id {
+    eval 'use Win32::TieRegistry';
+    return 0 if $@;
+    my $key = Win32::TieRegistry->new('LMachine\Software\ActiveState\ActivePerl')
+      or return 0;
+    return $key->GetValue("CurrentVersion");
 }
 
 sub print_module_instructions {
     my ($check_results, $output) = @_;
 
     # We only print these notes if we have to.
-    if ((!$output && %{$check_results->{missing}})
+    if ((!$output && @{$check_results->{missing}})
         || ($output && $check_results->{any_missing}))
     {
-        print "\n* NOTE: You must run any commands listed below as "
-              . ROOT_USER . ".\n\n";
-
+        
         if (ON_WINDOWS) {
-            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 http://theoryx5.uwinnipeg.ca/ppms          *
+
+            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):                     *
 *                                                                     *
@@ -327,13 +356,24 @@
 *                                                                     *
 * 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}}) {
+    if (my @missing = @{$check_results->{missing}}) {
         print <<EOT;
 ***********************************************************************
 * REQUIRED MODULES                                                    *
@@ -347,8 +387,8 @@
 EOT
 
         print "COMMANDS:\n\n";
-        foreach my $package (keys %missing) {
-            my $command = install_command($missing{$package});
+        foreach my $package (@missing) {
+            my $command = install_command($package);
             print "    $command\n";
         }
         print "\n";
@@ -382,7 +422,7 @@
 
     return unless $output;
 
-    if (my %missing = %{$check_results->{optional}}) {
+    if (my @missing = @{$check_results->{optional}}) {
         print <<EOT;
 **********************************************************************
 * OPTIONAL MODULES                                                   *
@@ -398,12 +438,8 @@
 **********************************************************************
 
 EOT
-        # We want to sort them so that they are ordered by feature.
-        my @missing_names = sort {$missing{$a}->{feature} 
-                                  cmp $missing{$b}->{feature}} (keys %missing);
-
         # Now we have to determine how large the table cols will be.
-        my $longest_name = max(map(length($_), @missing_names));
+        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;
@@ -416,18 +452,22 @@
         printf "* \%${longest_name}s * %-${remaining_space}s *\n",
                'MODULE NAME', 'ENABLES FEATURE(S)';
         print '*' x 71 . "\n";
-        foreach my $name (@missing_names) {
+        foreach my $package (@missing) {
             printf "* \%${longest_name}s * %-${remaining_space}s *\n",
-                   $name, $missing{$name}->{feature};
+                   $package->{package}, $package->{feature};
         }
         print '*' x 71 . "\n";
 
         print "COMMANDS TO INSTALL:\n\n";
-        foreach my $module (@missing_names) {
-            my $command = install_command($missing{$module});
-            printf "%15s: $command\n", $module;
+        foreach my $module (@missing) {
+            my $command = install_command($module);
+            printf "%15s: $command\n", $module->{package};
         }
     }
+
+    if ($output && $check_results->{any_missing} && !ON_WINDOWS) {
+        print install_string('install_all', { perl => $^X });
+    }
 }
 
 sub check_graphviz {
@@ -460,21 +500,6 @@
     return $return;
 }
 
-sub display_version_and_os {
-    # Display version information
-    printf "\n* This is Bugzilla " . BUGZILLA_VERSION . " on perl %vd\n",
-           $^V;
-    my @os_details = POSIX::uname;
-    # 0 is the name of the OS, 2 is the major version,
-    my $os_name = $os_details[0] . ' ' . $os_details[2];
-    if (ON_WINDOWS) {
-        require Win32;
-        $os_name = Win32::GetOSName();
-    }
-    # 3 is the minor version.
-    print "* Running on $os_name $os_details[3]\n"
-}
-
 # This was originally clipped from the libnet Makefile.PL, adapted here to
 # use the below vers_cmp routine for accurate version checking.
 sub have_vers {
@@ -487,15 +512,10 @@
     }
     my $wanted  = $params->{version};
 
-    my ($msg, $vnum, $vstr);
-    no strict 'refs';
-    printf("Checking for %15s %-9s ", $package, !$wanted?'(any)':"(v$wanted)") 
-        if $output;
-
     eval "require $module;";
 
     # VERSION is provided by UNIVERSAL::
-    $vnum = eval { $module->VERSION } || -1;
+    my $vnum = eval { $module->VERSION } || -1;
 
     # CGI's versioning scheme went 2.75, 2.751, 2.752, 2.753, 2.76
     # That breaks the standard version tests, so we need to manually correct
@@ -504,14 +524,15 @@
         $vnum = $1 . "." . $2;
     }
 
+    my $vstr;
     if ($vnum eq "-1") { # string compare just in case it's non-numeric
-        $vstr = "not found";
+        $vstr = install_string('module_not_found');
     }
     elsif (vers_cmp($vnum,"0") > -1) {
-        $vstr = "found v$vnum";
+        $vstr = install_string('module_found', { ver => $vnum });
     }
     else {
-        $vstr = "found unknown version";
+        $vstr = install_string('module_unknown_version');
     }
 
     my $vok = (vers_cmp($vnum,$wanted) > -1);
@@ -521,53 +542,17 @@
         $vok = 0 if $blacklisted;
     }
 
-    my $ok = $vok ? "ok:" : "";
-    my $black_string = $blacklisted ? "(blacklisted)" : "";
-    print "$ok $vstr $black_string\n" if $output;
-    return $vok ? 1 : 0;
-}
+    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');
 
-# This is taken straight from Sort::Versions 1.5, which is not included
-# with perl by default.
-sub vers_cmp {
-    my ($a, $b) = @_;
-
-    # Remove leading zeroes - Bug 344661
-    $a =~ s/^0*(\d.+)/$1/;
-    $b =~ s/^0*(\d.+)/$1/;
-
-    my @A = ($a =~ /([-.]|\d+|[^-.\d]+)/g);
-    my @B = ($b =~ /([-.]|\d+|[^-.\d]+)/g);
-
-    my ($A, $B);
-    while (@A and @B) {
-        $A = shift @A;
-        $B = shift @B;
-        if ($A eq '-' and $B eq '-') {
-            next;
-        } elsif ( $A eq '-' ) {
-            return -1;
-        } elsif ( $B eq '-') {
-            return 1;
-        } elsif ($A eq '.' and $B eq '.') {
-            next;
-        } elsif ( $A eq '.' ) {
-            return -1;
-        } elsif ( $B eq '.' ) {
-            return 1;
-        } elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
-            if ($A =~ /^0/ || $B =~ /^0/) {
-                return $A cmp $B if $A cmp $B;
-            } else {
-                return $A <=> $B if $A <=> $B;
-            }
-        } else {
-            $A = uc $A;
-            $B = uc $B;
-            return $A cmp $B if $A cmp $B;
-        }
+        $ok = "$ok:" if $ok;
+        printf "%s %19s %-9s $ok $vstr $black_string\n",
+            install_string('checking_for'), $package, "($want_string)";
     }
-    @A <=> @B;
+    
+    return $vok ? 1 : 0;
 }
 
 sub install_command {
@@ -579,7 +564,7 @@
         $package = $module->{package};
     }
     else {
-        $command = "$^X -MCPAN -e 'install \"\%s\"'";
+        $command = "$^X install-module.pl \%s";
         # Non-Windows installations need to use module names, because
         # CPAN doesn't understand package names.
         $package = $module->{module};
@@ -587,7 +572,6 @@
     return sprintf $command, $package;
 }
 
-
 1;
 
 __END__
@@ -619,26 +603,46 @@
 
 =over 4
 
-=item C<check_requirements($output)>
+=item C<check_requirements>
 
- Description: This checks what optional or required perl modules
-              are installed, like C<checksetup.pl> does.
+=over
 
- Params:      C<$output> - C<true> if you want the function to print
-                           out information about what it's doing,
-                           and the versions of everything installed.
-                           If you don't pass the minimum requirements,
-                           the will always print out something, 
-                           regardless of this parameter.
+=item B<Description>
 
- Returns:    A hashref containing three values:
-             C<pass> - Whether or not we have all the mandatory 
-                       requirements.
-             C<missing> - A hash showing which mandatory requirements
-                          are missing. The key is the module name,
-                          and the value is the version we require.
-             C<optional> - Which optional modules are installed and
-                           up-to-date enough for Bugzilla.
+This checks what optional or required perl modules are installed, like
+C<checksetup.pl> does.
+
+=item B<Params>
+
+=over
+
+=item C<$output> - C<true> if you want the function to print out information
+about what it's doing, and the versions of everything installed.
+
+=back
+
+=item B<Returns>
+
+A hashref containing these values:
+
+=over
+
+=item C<pass> - Whether or not we have all the mandatory requirements.
+
+=item C<missing> - An arrayref containing any required modules that
+are not installed or that are not up-to-date. Each item in the array is
+a hashref in the format of items from L</REQUIRED_MODULES>.
+
+=item C<optional> - The same as C<missing>, but for optional modules.
+
+=item C<have_one_dbd> - True if at least one C<DBD::> module is installed.
+
+=item C<any_missing> - True if there are any missing modules, even optional
+modules.
+
+=back
+
+=back
 
 =item C<check_graphviz($output)>
 
@@ -650,18 +654,6 @@
 
 Returns:     C<1> if the check was successful, C<0> otherwise.
 
-=item C<vers_cmp($a, $b)>
-
- Description: This is a comparison function, like you would use in
-              C<sort>, except that it compares two version numbers.
-              It's actually identical to versioncmp from 
-              L<Sort::Versions>.
-
- Params:      c<$a> and C<$b> are versions you want to compare.
-
- Returns:     -1 if $a is less than $b, 0 if they are equal, and
-              1 if $a is greater than $b.
-
 =item C<have_vers($module, $output)>
 
  Description: Tells you whether or not you have the appropriate
diff --git a/BugsSite/Bugzilla/Install/Util.pm b/BugsSite/Bugzilla/Install/Util.pm
new file mode 100644
index 0000000..9cec8c4
--- /dev/null
+++ b/BugsSite/Bugzilla/Install/Util.pm
@@ -0,0 +1,553 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2006
+# Everything Solved. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Install::Util;
+
+# The difference between this module and Bugzilla::Util is that this
+# module may require *only* Bugzilla::Constants and built-in
+# perl modules.
+
+use strict;
+
+use Bugzilla::Constants;
+
+use File::Basename;
+use POSIX qw(setlocale LC_CTYPE);
+use Safe;
+
+use base qw(Exporter);
+our @EXPORT_OK = qw(
+    bin_loc
+    get_version_and_os
+    indicate_progress
+    install_string
+    include_languages
+    template_include_path
+    vers_cmp
+    get_console_locale
+);
+
+sub bin_loc {
+    my ($bin) = @_;
+    return '' if ON_WINDOWS;
+    # Don't print any errors from "which"
+    open(my $saveerr, ">&STDERR");
+    open(STDERR, '>/dev/null');
+    my $loc = `which $bin`;
+    close(STDERR);
+    open(STDERR, ">&", $saveerr);
+    my $exit_code = $? >> 8; # See the perlvar manpage.
+    return '' if $exit_code > 0;
+    chomp($loc);
+    return $loc;
+}
+
+sub get_version_and_os {
+    # Display version information
+    my @os_details = POSIX::uname;
+    # 0 is the name of the OS, 2 is the major version,
+    my $os_name = $os_details[0] . ' ' . $os_details[2];
+    if (ON_WINDOWS) {
+        require Win32;
+        $os_name = Win32::GetOSName();
+    }
+    # $os_details[3] is the minor version.
+    return { bz_ver   => BUGZILLA_VERSION,
+             perl_ver => sprintf('%vd', $^V),
+             os_name  => $os_name,
+             os_ver   => $os_details[3] };
+}
+
+sub indicate_progress {
+    my ($params) = @_;
+    my $current = $params->{current};
+    my $total   = $params->{total};
+    my $every   = $params->{every} || 1;
+
+    print "." if !($current % $every);
+    if ($current == $total || $current % ($every * 60) == 0) {
+        print "$current/$total (" . int($current * 100 / $total) . "%)\n";
+    }
+}
+
+sub install_string {
+    my ($string_id, $vars) = @_;
+    _cache()->{template_include_path} ||= template_include_path();
+    my $path = _cache()->{template_include_path};
+    
+    my $string_template;
+    # Find the first template that defines this string.
+    foreach my $dir (@$path) {
+        my $base = "$dir/setup/strings";
+        $string_template = _get_string_from_file($string_id, "$base.txt.pl")
+            if !defined $string_template;
+        last if defined $string_template;
+    }
+    
+    die "No language defines the string '$string_id'"
+        if !defined $string_template;
+
+    $vars ||= {};
+    my @replace_keys = keys %$vars;
+    foreach my $key (@replace_keys) {
+        my $replacement = $vars->{$key};
+        die "'$key' in '$string_id' is tainted: '$replacement'"
+            if is_tainted($replacement);
+        # We don't want people to start getting clever and inserting
+        # ##variable## into their values. So we check if any other
+        # key is listed in the *replacement* string, before doing
+        # the replacement. This is mostly to protect programmers from
+        # making mistakes.
+        if (grep($replacement =~ /##$key##/, @replace_keys)) {
+            die "Unsafe replacement for '$key' in '$string_id': '$replacement'";
+        }
+        $string_template =~ s/\Q##$key##\E/$replacement/g;
+    }
+    
+    return $string_template;
+}
+
+sub include_languages {
+    my ($params) = @_;
+    $params ||= {};
+
+    # Basically, the way this works is that we have a list of languages
+    # that we *want*, and a list of languages that Bugzilla actually
+    # supports. The caller tells us what languages they want, by setting
+    # $ENV{HTTP_ACCEPT_LANGUAGE} or $params->{only_language}. The languages
+    # we support are those specified in $params->{use_languages}. Otherwise
+    # we support every language installed in the template/ directory.
+    
+    my @wanted;
+    if ($params->{only_language}) {
+        @wanted = ($params->{only_language});
+    }
+    else {
+        @wanted = _sort_accept_language($ENV{'HTTP_ACCEPT_LANGUAGE'} || '');
+    }
+    
+    my @supported;
+    if (defined $params->{use_languages}) {
+        @supported = @{$params->{use_languages}};
+    }
+    else {
+        my @dirs = glob(bz_locations()->{'templatedir'} . "/*");
+        @dirs = map(basename($_), @dirs);
+        @supported = grep($_ ne 'CVS', @dirs);
+    }
+    
+    my @usedlanguages;
+    foreach my $wanted (@wanted) {
+        # If we support the language we want, or *any version* of
+        # the language we want, it gets pushed into @usedlanguages.
+        #
+        # Per RFC 1766 and RFC 2616, things like 'en' match 'en-us' and
+        # 'en-uk', but not the other way around. (This is unfortunately
+        # not very clearly stated in those RFC; see comment just over 14.5
+        # in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)
+        if(my @found = grep /^\Q$wanted\E(-.+)?$/i, @supported) {
+            push (@usedlanguages, @found);
+        }
+    }
+
+    # We always include English at the bottom if it's not there, even if
+    # somebody removed it from use_languages.
+    if (!grep($_ eq 'en', @usedlanguages)) {
+        push(@usedlanguages, 'en');
+    }
+
+    return @usedlanguages;
+}
+    
+sub template_include_path {
+    my @usedlanguages = include_languages(@_);
+    # Now, we add template directories in the order they will be searched:
+    
+    # First, we add extension template directories, because extension templates
+    # override standard templates. Extensions may be localized in the same way
+    # that Bugzilla templates are localized.
+    my @include_path;
+    my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
+    foreach my $extension (@extensions) {
+        foreach my $lang (@usedlanguages) {
+            _add_language_set(\@include_path, $lang, "$extension/template");
+        }
+    }
+    
+    # Then, we add normal template directories, sorted by language.
+    foreach my $lang (@usedlanguages) {
+        _add_language_set(\@include_path, $lang);
+    }
+    
+    return \@include_path;
+}
+
+# This is taken straight from Sort::Versions 1.5, which is not included
+# with perl by default.
+sub vers_cmp {
+    my ($a, $b) = @_;
+
+    # Remove leading zeroes - Bug 344661
+    $a =~ s/^0*(\d.+)/$1/;
+    $b =~ s/^0*(\d.+)/$1/;
+
+    my @A = ($a =~ /([-.]|\d+|[^-.\d]+)/g);
+    my @B = ($b =~ /([-.]|\d+|[^-.\d]+)/g);
+
+    my ($A, $B);
+    while (@A and @B) {
+        $A = shift @A;
+        $B = shift @B;
+        if ($A eq '-' and $B eq '-') {
+            next;
+        } elsif ( $A eq '-' ) {
+            return -1;
+        } elsif ( $B eq '-') {
+            return 1;
+        } elsif ($A eq '.' and $B eq '.') {
+            next;
+        } elsif ( $A eq '.' ) {
+            return -1;
+        } elsif ( $B eq '.' ) {
+            return 1;
+        } elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
+            if ($A =~ /^0/ || $B =~ /^0/) {
+                return $A cmp $B if $A cmp $B;
+            } else {
+                return $A <=> $B if $A <=> $B;
+            }
+        } else {
+            $A = uc $A;
+            $B = uc $B;
+            return $A cmp $B if $A cmp $B;
+        }
+    }
+    @A <=> @B;
+}
+
+######################
+# Helper Subroutines #
+######################
+
+# Used by install_string
+sub _get_string_from_file {
+    my ($string_id, $file) = @_;
+    
+    return undef if !-e $file;
+    my $safe = new Safe;
+    $safe->rdo($file);
+    my %strings = %{$safe->varglob('strings')};
+    return $strings{$string_id};
+}
+
+# Used by template_include_path.
+sub _add_language_set {
+    my ($array, $lang, $templatedir) = @_;
+    
+    $templatedir ||= bz_locations()->{'templatedir'};
+    my @add = ("$templatedir/$lang/custom", "$templatedir/$lang/default");
+    
+    my $project = bz_locations->{'project'};
+    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.
+sub _sort_accept_language {
+    sub sortQvalue { $b->{'qvalue'} <=> $a->{'qvalue'} }
+    my $accept_language = $_[0];
+
+    # clean up string.
+    $accept_language =~ s/[^A-Za-z;q=0-9\.\-,]//g;
+    my @qlanguages;
+    my @languages;
+    foreach(split /,/, $accept_language) {
+        if (m/([A-Za-z\-]+)(?:;q=(\d(?:\.\d+)))?/) {
+            my $lang   = $1;
+            my $qvalue = $2;
+            $qvalue = 1 if not defined $qvalue;
+            next if $qvalue == 0;
+            $qvalue = 1 if $qvalue > 1;
+            push(@qlanguages, {'qvalue' => $qvalue, 'language' => $lang});
+        }
+    }
+
+    return map($_->{'language'}, (sort sortQvalue @qlanguages));
+}
+
+sub get_console_locale {
+    require Locale::Language;
+    my $locale = setlocale(LC_CTYPE);
+    my $language;
+    # Some distros set e.g. LC_CTYPE = fr_CH.UTF-8. We clean it up.
+    if ($locale =~ /^([^\.]+)/) {
+        $locale = $1;
+    }
+    $locale =~ s/_/-/;
+    # It's pretty sure that there is no language pack of the form fr-CH
+    # installed, so we also include fr as a wanted language.
+    if ($locale =~ /^(\S+)\-/) {
+        $language = $1;
+        $locale .= ",$language";
+    }
+    else {
+        $language = $locale;
+    }
+
+    # Some OSs or distributions may have setlocale return a string of the form
+    # German_Germany.1252 (this example taken from a Windows XP system), which
+    # is unsuitable for our needs because Bugzilla works on language codes.
+    # We try and convert them here.
+    if ($language = Locale::Language::language2code($language)) {
+        $locale .= ",$language";
+    }
+
+    return $locale;
+}
+
+
+# This is like request_cache, but it's used only by installation code
+# for setup.cgi and things like that.
+our $_cache = {};
+sub _cache {
+    if ($ENV{MOD_PERL}) {
+        require Apache2::RequestUtil;
+        return Apache2::RequestUtil->request->pnotes();
+    }
+    return $_cache;
+}
+
+###############################
+# Copied from Bugzilla::Util #
+##############################
+
+sub trick_taint {
+    require Carp;
+    Carp::confess("Undef to trick_taint") unless defined $_[0];
+    my $match = $_[0] =~ /^(.*)$/s;
+    $_[0] = $match ? $1 : undef;
+    return (defined($_[0]));
+}
+
+sub is_tainted {
+    return not eval { my $foo = join('',@_), kill 0; 1; };
+}
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Install::Util - Utility functions that are useful both during
+installation and afterwards.
+
+=head1 DESCRIPTION
+
+This module contains various subroutines that are used primarily
+during installation. However, these subroutines can also be useful to
+non-installation code, so they have been split out into this module.
+
+The difference between this module and L<Bugzilla::Util> is that this
+module is safe to C<use> anywhere in Bugzilla, even during installation,
+because it depends only on L<Bugzilla::Constants> and built-in perl modules.
+
+None of the subroutines are exported by default--you must explicitly
+export them.
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<bin_loc>
+
+On *nix systems, given the name of a binary, returns the path to that
+binary, if the binary is in the C<PATH>.
+
+=item C<get_version_and_os>
+
+Returns a hash containing information about what version of Bugzilla we're
+running, what perl version we're using, and what OS we're running on.
+
+=item C<get_console_locale>
+
+Returns the language to use based on the LC_CTYPE value returned by the OS.
+If LC_CTYPE is of the form fr-CH, then fr is appended to the list.
+
+=item C<indicate_progress>
+
+=over
+
+=item B<Description>
+
+This prints out lines of dots as a long update is going on, to let the user
+know where we are and that we're not frozen. A new line of dots will start
+every 60 dots.
+
+Sample usage: C<indicate_progress({ total =E<gt> $total, current =E<gt>
+$count, every =E<gt> 1 })>
+
+=item B<Sample Output>
+
+Here's some sample output with C<total = 1000> and C<every = 10>:
+
+ ............................................................600/1000 (60%)
+ ........................................
+
+=item B<Params>
+
+=over
+
+=item C<total> - The total number of items we're processing.
+
+=item C<current> - The number of the current item we're processing.
+
+=item C<every> - How often the function should print out a dot.
+For example, if this is 10, the function will print out a dot every
+ten items. Defaults to 1 if not specified.
+
+=back
+
+=item B<Returns>: nothing
+
+=back
+
+=item C<install_string>
+
+=over
+
+=item B<Description>
+
+This is a very simple method of templating strings for installation.
+It should only be used by code that has to run before the Template Toolkit
+can be used. (See the comments at the top of the various L<Bugzilla::Install>
+modules to find out when it's safe to use Template Toolkit.)
+
+It pulls strings out of the F<strings.txt.pl> "template" and replaces
+any variable surrounded by double-hashes (##) with a value you specify.
+
+This allows for localization of strings used during installation.
+
+=item B<Example>
+
+Let's say your template string looks like this:
+
+ The ##animal## jumped over the ##plant##.
+
+Let's say that string is called 'animal_jump_plant'. So you call the function
+like this:
+
+ install_string('animal_jump_plant', { animal => 'fox', plant => 'tree' });
+
+That will output this:
+
+ The fox jumped over the tree.
+
+=item B<Params>
+
+=over
+
+=item C<$string_id> - The name of the string from F<strings.txt.pl>.
+
+=item C<$vars> - A hashref containing the replacement values for variables
+inside of the string.
+
+=back
+
+=item B<Returns>: The appropriate string, with variables replaced.
+
+=back
+
+=item C<template_include_path>
+
+Used by L<Bugzilla::Template> and L</install_string> to determine the
+directories where templates are installed. Templates can be installed
+in many places. They're listed here in the basic order that they're
+searched:
+
+=over
+
+=item extensions/C<$extension>/template/C<$language>/C<$project>
+
+=item extensions/C<$extension>/template/C<$language>/custom
+
+=item extensions/C<$extension>/template/C<$language>/default
+
+=item template/C<$language>/C<$project>
+
+=item template/C<$language>/custom
+
+=item template/C<$language>/default
+
+=back
+
+C<$project> has to do with installations that are using the C<$ENV{PROJECT}>
+variable to have different "views" on a single Bugzilla.
+
+The F<default> directory includes templates shipped with Bugzilla.
+
+The F<custom> directory is a directory for local installations to override
+the F<default> templates. Any individual template in F<custom> will
+override a template of the same name and path in F<default>.
+
+C<$language> is a language code, C<en> being the default language shipped
+with Bugzilla. Localizers ship other languages.
+
+C<$extension> is the name of any directory in the F<extensions/> directory.
+Each extension has its own directory.
+
+Note that languages are sorted by the user's preference (as specified
+in their browser, usually), and extensions are sorted alphabetically.
+
+=item C<include_languages>
+
+Used by L<Bugzilla::Template> to determine the languages' list which 
+are compiled with the browser's I<Accept-Language> and the languages 
+of installed templates.
+
+=item C<vers_cmp>
+
+=over
+
+=item B<Description>
+
+This is a comparison function, like you would use in C<sort>, except that
+it compares two version numbers. So, for example, 2.10 would be greater
+than 2.2.
+
+It's based on versioncmp from L<Sort::Versions>, with some Bugzilla-specific
+fixes.
+
+=item B<Params>: C<$a> and C<$b> - The versions you want to compare.
+
+=item B<Returns>
+
+C<-1> if C<$a> is less than C<$b>, C<0> if they are equal, or C<1> if C<$a>
+is greater than C<$b>.
+
+=back
+
+=back
diff --git a/BugsSite/Bugzilla/Mailer.pm b/BugsSite/Bugzilla/Mailer.pm
index 956f301..645e65e 100644
--- a/BugsSite/Bugzilla/Mailer.pm
+++ b/BugsSite/Bugzilla/Mailer.pm
@@ -35,7 +35,7 @@
 use strict;
 
 use base qw(Exporter);
-@Bugzilla::Mailer::EXPORT = qw(MessageToMTA);
+@Bugzilla::Mailer::EXPORT = qw(MessageToMTA build_thread_marker);
 
 use Bugzilla::Constants;
 use Bugzilla::Error;
@@ -44,6 +44,7 @@
 use Date::Format qw(time2str);
 
 use Encode qw(encode);
+use Encode::MIME::Header;
 use Email::Address;
 use Email::MIME;
 # Loading this gives us encoding_set.
@@ -55,22 +56,61 @@
     my $method = Bugzilla->params->{'mail_delivery_method'};
     return if $method eq 'None';
 
-    my $email = ref($msg) ? $msg : Email::MIME->new($msg);
-    foreach my $part ($email->parts) {
-        $part->charset_set('UTF-8') if Bugzilla->params->{'utf8'};
-        $part->encoding_set('quoted-printable') if !is_7bit_clean($part->body);
+    my $email;
+    if (ref $msg) {
+        $email = $msg;
     }
+    else {
+        # RFC 2822 requires us to have CRLF for our line endings and
+        # Email::MIME doesn't do this for us. We use \015 (CR) and \012 (LF)
+        # directly because Perl translates "\n" depending on what platform
+        # you're running on. See http://perldoc.perl.org/perlport.html#Newlines
+        # We check for multiple CRs because of this Template-Toolkit bug:
+        # https://rt.cpan.org/Ticket/Display.html?id=43345
+        $msg =~ s/(?:\015+)?\012/\015\012/msg;
+        $email = new Email::MIME($msg);
+    }
+
+    # We add this header to mark the mail as "auto-generated" and
+    # thus to hopefully avoid auto replies.
+    $email->header_set('Auto-Submitted', 'auto-generated');
+
+    $email->walk_parts(sub {
+        my ($part) = @_;
+        return if $part->parts > 1; # Top-level
+        my $content_type = $part->content_type || '';
+        if ($content_type !~ /;/) {
+            my $body = $part->body;
+            if (Bugzilla->params->{'utf8'}) {
+                $part->charset_set('UTF-8');
+                # encoding_set works only with bytes, not with utf8 strings.
+                my $raw = $part->body_raw;
+                if (utf8::is_utf8($raw)) {
+                    utf8::encode($raw);
+                    $part->body_set($raw);
+                }
+            }
+            $part->encoding_set('quoted-printable') if !is_7bit_clean($body);
+        }
+    });
 
     # MIME-Version must be set otherwise some mailsystems ignore the charset
     $email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version');
 
     # Encode the headers correctly in quoted-printable
-    foreach my $header qw(From To Cc Reply-To Sender Errors-To Subject) {
-        if (my $value = $email->header($header)) {
-            $value = Encode::decode("UTF-8", $value) if Bugzilla->params->{'utf8'};
+    foreach my $header ($email->header_names) {
+        my @values = $email->header($header);
+        # We don't recode headers that happen multiple times.
+        next if scalar(@values) > 1;
+        if (my $value = $values[0]) {
+            if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($value)) {
+                utf8::decode($value);
+            }
+
+            # avoid excessive line wrapping done by Encode.
+            local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998;
+
             my $encoded = encode('MIME-Q', $value);
-            # Encode adds unnecessary line breaks, with two spaces after each.
-            $encoded =~ s/\n  / /g;
             $email->header_set($header, $encoded);
         }
     }
@@ -111,6 +151,8 @@
 
     if ($method eq "SMTP") {
         push @args, Host  => Bugzilla->params->{"smtpserver"},
+                    username => Bugzilla->params->{"smtp_username"},
+                    password => Bugzilla->params->{"smtp_password"},
                     Hello => $hostname, 
                     Debug => Bugzilla->params->{'smtp_debug'};
     }
@@ -118,7 +160,8 @@
     if ($method eq "Test") {
         my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
         open TESTFILE, '>>', $filename;
-        print TESTFILE "\n\n---\n\n" . $email->as_string;
+        # From - <date> is required to be a valid mbox file.
+        print TESTFILE "\n\nFrom - " . $email->header('Date') . "\n" . $email->as_string;
         close TESTFILE;
     }
     else {
@@ -132,4 +175,31 @@
     }
 }
 
+# Builds header suitable for use as a threading marker in email notifications
+sub build_thread_marker {
+    my ($bug_id, $user_id, $is_new) = @_;
+
+    if (!defined $user_id) {
+        $user_id = Bugzilla->user->id;
+    }
+
+    my $sitespec = '@' . Bugzilla->params->{'urlbase'};
+    $sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain
+    $sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate
+    if ($2) {
+        $sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
+    }
+
+    my $threadingmarker;
+    if ($is_new) {
+        $threadingmarker = "Message-ID: <bug-$bug_id-$user_id$sitespec>";
+    }
+    else {
+        $threadingmarker = "In-Reply-To: <bug-$bug_id-$user_id$sitespec>" .
+                           "\nReferences: <bug-$bug_id-$user_id$sitespec>";
+    }
+
+    return $threadingmarker;
+}
+
 1;
diff --git a/BugsSite/Bugzilla/Milestone.pm b/BugsSite/Bugzilla/Milestone.pm
index 2e70b4e..fc44cf1 100644
--- a/BugsSite/Bugzilla/Milestone.pm
+++ b/BugsSite/Bugzilla/Milestone.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;
 
@@ -21,6 +22,7 @@
 
 use base qw(Bugzilla::Object);
 
+use Bugzilla::Constants;
 use Bugzilla::Util;
 use Bugzilla::Error;
 
@@ -31,6 +33,8 @@
 use constant DEFAULT_SORTKEY => 0;
 
 use constant DB_TABLE => 'milestones';
+use constant NAME_FIELD => 'value';
+use constant LIST_ORDER => 'sortkey, value';
 
 use constant DB_COLUMNS => qw(
     id
@@ -39,8 +43,26 @@
     sortkey
 );
 
-use constant NAME_FIELD => 'value';
-use constant LIST_ORDER => 'sortkey, value';
+use constant REQUIRED_CREATE_FIELDS => qw(
+    name
+    product
+);
+
+use constant UPDATE_COLUMNS => qw(
+    value
+    sortkey
+);
+
+use constant VALIDATORS => {
+    product => \&_check_product,
+    sortkey => \&_check_sortkey,
+};
+
+use constant UPDATE_VALIDATORS => {
+    value => \&_check_value,
+};
+
+################################
 
 sub new {
     my $class = shift;
@@ -71,6 +93,119 @@
     return $class->SUPER::new(@_);
 }
 
+sub run_create_validators {
+    my $class  = shift;
+    my $params = $class->SUPER::run_create_validators(@_);
+
+    my $product = delete $params->{product};
+    $params->{product_id} = $product->id;
+    $params->{value} = $class->_check_value($params->{name}, $product);
+    delete $params->{name};
+
+    return $params;
+}
+
+sub update {
+    my $self = shift;
+    my $changes = $self->SUPER::update(@_);
+
+    if (exists $changes->{value}) {
+        my $dbh = Bugzilla->dbh;
+        # The milestone value is stored in the bugs table instead of its ID.
+        $dbh->do('UPDATE bugs SET target_milestone = ?
+                  WHERE target_milestone = ? AND product_id = ?',
+                 undef, ($self->name, $changes->{value}->[0], $self->product_id));
+
+        # The default milestone also stores the value instead of the ID.
+        $dbh->do('UPDATE products SET defaultmilestone = ?
+                  WHERE id = ? AND defaultmilestone = ?',
+                 undef, ($self->name, $self->product_id, $changes->{value}->[0]));
+    }
+    return $changes;
+}
+
+sub remove_from_db {
+    my $self = shift;
+    my $dbh = Bugzilla->dbh;
+
+    # The default milestone cannot be deleted.
+    if ($self->name eq $self->product->default_milestone) {
+        ThrowUserError('milestone_is_default', { milestone => $self });
+    }
+
+    if ($self->bug_count) {
+        # We don't want to delete bugs when deleting a milestone.
+        # Bugs concerned are reassigned to the default milestone.
+        my $bug_ids =
+          $dbh->selectcol_arrayref('SELECT bug_id FROM bugs
+                                    WHERE product_id = ? AND target_milestone = ?',
+                                    undef, ($self->product->id, $self->name));
+
+        my $timestamp = $dbh->selectrow_array('SELECT NOW()');
+
+        $dbh->do('UPDATE bugs SET target_milestone = ?, delta_ts = ?
+                   WHERE ' . $dbh->sql_in('bug_id', $bug_ids),
+                 undef, ($self->product->default_milestone, $timestamp));
+
+        require Bugzilla::Bug;
+        import Bugzilla::Bug qw(LogActivityEntry);
+        foreach my $bug_id (@$bug_ids) {
+            LogActivityEntry($bug_id, 'target_milestone',
+                             $self->name,
+                             $self->product->default_milestone,
+                             Bugzilla->user->id, $timestamp);
+        }
+    }
+
+    $dbh->do('DELETE FROM milestones WHERE id = ?', undef, $self->id);
+}
+
+################################
+# Validators
+################################
+
+sub _check_value {
+    my ($invocant, $name, $product) = @_;
+
+    $name = trim($name);
+    $name || ThrowUserError('milestone_blank_name');
+    if (length($name) > MAX_MILESTONE_SIZE) {
+        ThrowUserError('milestone_name_too_long', {name => $name});
+    }
+
+    $product = $invocant->product if (ref $invocant);
+    my $milestone = new Bugzilla::Milestone({product => $product, name => $name});
+    if ($milestone && (!ref $invocant || $milestone->id != $invocant->id)) {
+        ThrowUserError('milestone_already_exists', { name    => $milestone->name,
+                                                     product => $product->name });
+    }
+    return $name;
+}
+
+sub _check_sortkey {
+    my ($invocant, $sortkey) = @_;
+
+    # Keep a copy in case detaint_signed() clears the sortkey
+    my $stored_sortkey = $sortkey;
+
+    if (!detaint_signed($sortkey) || $sortkey < MIN_SMALLINT || $sortkey > MAX_SMALLINT) {
+        ThrowUserError('milestone_sortkey_invalid', {sortkey => $stored_sortkey});
+    }
+    return $sortkey;
+}
+
+sub _check_product {
+    my ($invocant, $product) = @_;
+    return Bugzilla->user->check_can_admin_product($product->name);
+}
+
+################################
+# Methods
+################################
+
+sub set_name { $_[0]->set('value', $_[1]); }
+sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+
 sub bug_count {
     my $self = shift;
     my $dbh = Bugzilla->dbh;
@@ -92,39 +227,12 @@
 sub product_id { return $_[0]->{'product_id'}; }
 sub sortkey    { return $_[0]->{'sortkey'};    }
 
-################################
-#####     Subroutines      #####
-################################
+sub product {
+    my $self = shift;
 
-sub check_milestone {
-    my ($product, $milestone_name) = @_;
-
-    unless ($milestone_name) {
-        ThrowUserError('milestone_not_specified');
-    }
-
-    my $milestone = new Bugzilla::Milestone({ product => $product,
-                                              name    => $milestone_name });
-    unless ($milestone) {
-        ThrowUserError('milestone_not_valid',
-                       {'product' => $product->name,
-                        'milestone' => $milestone_name});
-    }
-    return $milestone;
-}
-
-sub check_sort_key {
-    my ($milestone_name, $sortkey) = @_;
-    # Keep a copy in case detaint_signed() clears the sortkey
-    my $stored_sortkey = $sortkey;
-
-    if (!detaint_signed($sortkey) || $sortkey < -32768
-        || $sortkey > 32767) {
-        ThrowUserError('milestone_sortkey_invalid',
-                       {'name' => $milestone_name,
-                        'sortkey' => $stored_sortkey});
-    }
-    return $sortkey;
+    require Bugzilla::Product;
+    $self->{'product'} ||= new Bugzilla::Product($self->product_id);
+    return $self->{'product'};
 }
 
 1;
@@ -139,13 +247,21 @@
 
     use Bugzilla::Milestone;
 
-    my $milestone = new Bugzilla::Milestone(
-        { product => $product, name => 'milestone_value' });
+    my $milestone = new Bugzilla::Milestone({ name => $name, product => $product });
 
+    my $name       = $milestone->name;
     my $product_id = $milestone->product_id;
-    my $value = $milestone->value;
+    my $product    = $milestone->product;
+    my $sortkey    = $milestone->sortkey;
 
-    my $milestone = $hash_ref->{'milestone_value'};
+    my $milestone = Bugzilla::Milestone->create(
+        { name => $name, product => $product, sortkey => $sortkey });
+
+    $milestone->set_name($new_name);
+    $milestone->set_sortkey($new_sortkey);
+    $milestone->update();
+
+    $milestone->remove_from_db;
 
 =head1 DESCRIPTION
 
@@ -155,16 +271,48 @@
 
 =over
 
-=item C<new($product_id, $value)>
+=item C<new({name => $name, product => $product})>
 
  Description: The constructor is used to load an existing milestone
-              by passing a product id and a milestone value.
+              by passing a product object and a milestone name.
 
- Params:      $product_id - Integer with a Bugzilla product id.
-              $value - String with a milestone value.
+ Params:      $product - a Bugzilla::Product object.
+              $name - the name of a milestone (string).
 
  Returns:     A Bugzilla::Milestone object.
 
+=item C<name()>
+
+ Description: Name (value) of the milestone.
+
+ Params:      none.
+
+ Returns:     The name of the milestone.
+
+=item C<product_id()>
+
+ Description: ID of the product the milestone belongs to.
+
+ Params:      none.
+
+ Returns:     The ID of a product.
+
+=item C<product()>
+
+ Description: The product object of the product the milestone belongs to.
+
+ Params:      none.
+
+ Returns:     A Bugzilla::Product object.
+
+=item C<sortkey()>
+
+ Description: Sortkey of the milestone.
+
+ Params:      none.
+
+ Returns:     The sortkey of the milestone.
+
 =item C<bug_count()>
 
  Description: Returns the total of bugs that belong to the milestone.
@@ -173,22 +321,55 @@
 
  Returns:     Integer with the number of bugs.
 
+=item C<set_name($new_name)>
+
+ Description: Changes the name of the milestone.
+
+ Params:      $new_name - new name of the milestone (string). This name
+                          must be unique within the product.
+
+ Returns:     Nothing.
+
+=item C<set_sortkey($new_sortkey)>
+
+ Description: Changes the sortkey of the milestone.
+
+ Params:      $new_sortkey - new sortkey of the milestone (signed integer).
+
+ Returns:     Nothing.
+
+=item C<update()>
+
+ Description: Writes the new name and/or the new sortkey into the DB.
+
+ Params:      none.
+
+ Returns:     A hashref with changes made to the milestone object.
+
+=item C<remove_from_db()>
+
+ Description: Deletes the current milestone from the DB. The object itself
+              is not destroyed.
+
+ Params:      none.
+
+ Returns:     Nothing.
+
 =back
 
-=head1 SUBROUTINES
+=head1 CLASS METHODS
 
 =over
 
-=item C<check_milestone($product, $milestone_name)>
+=item C<create({name => $name, product => $product, sortkey => $sortkey})>
 
- Description: Checks if a milestone name was passed in
-              and if it is a valid milestone.
+ Description: Create a new milestone for the given product.
 
- Params:      $product - Bugzilla::Product object.
-              $milestone_name - String with a milestone name.
+ Params:      $name    - name of the new milestone (string). This name
+                         must be unique within the product.
+              $product - a Bugzilla::Product object.
+              $sortkey - the sortkey of the new milestone (signed integer)
 
- Returns:     Bugzilla::Milestone object.
+ Returns:     A Bugzilla::Milestone object.
 
 =back
-
-=cut
diff --git a/BugsSite/Bugzilla/Object.pm b/BugsSite/Bugzilla/Object.pm
index 30ecc77..d616bb2 100644
--- a/BugsSite/Bugzilla/Object.pm
+++ b/BugsSite/Bugzilla/Object.pm
@@ -23,13 +23,20 @@
 
 package Bugzilla::Object;
 
+use Bugzilla::Constants;
 use Bugzilla::Util;
 use Bugzilla::Error;
 
+use Date::Parse;
+
 use constant NAME_FIELD => 'name';
 use constant ID_FIELD   => 'id';
 use constant LIST_ORDER => NAME_FIELD;
 
+use constant UPDATE_VALIDATORS => {};
+use constant NUMERIC_COLUMNS   => ();
+use constant DATE_COLUMNS      => ();
+
 ###############################
 ####    Initialization     ####
 ###############################
@@ -100,44 +107,107 @@
     return $object;
 }
 
+sub check {
+    my ($invocant, $param) = @_;
+    my $class = ref($invocant) || $invocant;
+    # If we were just passed a name, then just use the name.
+    if (!ref $param) {
+        $param = { name => $param };
+    }
+    # Don't allow empty names.
+    if (exists $param->{name}) {
+        $param->{name} = trim($param->{name});
+        $param->{name} || ThrowUserError('object_name_not_specified',
+                                          { class => $class });
+    }
+    my $obj = $class->new($param)
+        || ThrowUserError('object_does_not_exist', {%$param, class => $class});
+    return $obj;
+}
+
 sub new_from_list {
     my $invocant = shift;
     my $class = ref($invocant) || $invocant;
     my ($id_list) = @_;
-    my $dbh = Bugzilla->dbh;
-    my $columns = join(',', $class->DB_COLUMNS);
-    my $table   = $class->DB_TABLE;
-    my $order   = $class->LIST_ORDER;
     my $id_field = $class->ID_FIELD;
 
-    my $objects;
-    if (@$id_list) {
-        my @detainted_ids;
-        foreach my $id (@$id_list) {
-            detaint_natural($id) ||
-                ThrowCodeError('param_must_be_numeric',
-                              {function => $class . '::new_from_list'});
-            push(@detainted_ids, $id);
+    my @detainted_ids;
+    foreach my $id (@$id_list) {
+        detaint_natural($id) ||
+            ThrowCodeError('param_must_be_numeric',
+                          {function => $class . '::new_from_list'});
+        push(@detainted_ids, $id);
+    }
+    # We don't do $invocant->match because some classes have
+    # their own implementation of match which is not compatible
+    # with this one. However, match() still needs to have the right $invocant
+    # in order to do $class->DB_TABLE and so on.
+    return match($invocant, { $id_field => \@detainted_ids });
+}
+
+# Note: Future extensions to this could be:
+#  * Add a MATCH_JOIN constant so that we can join against
+#    certain other tables for the WHERE criteria.
+sub match {
+    my ($invocant, $criteria) = @_;
+    my $class = ref($invocant) || $invocant;
+    my $dbh   = Bugzilla->dbh;
+
+    return [$class->get_all] if !$criteria;
+
+    my (@terms, @values);
+    foreach my $field (keys %$criteria) {
+        my $value = $criteria->{$field};
+        if (ref $value eq 'ARRAY') {
+            # IN () is invalid SQL, and if we have an empty list
+            # to match against, we're just returning an empty
+            # array anyhow.
+            return [] if !scalar @$value;
+
+            my @qmarks = ("?") x @$value;
+            push(@terms, $dbh->sql_in($field, \@qmarks));
+            push(@values, @$value);
         }
-        $objects = $dbh->selectall_arrayref(
-            "SELECT $columns FROM $table WHERE $id_field IN (" 
-            . join(',', @detainted_ids) . ") ORDER BY $order", {Slice=>{}});
-    } else {
-        return [];
+        elsif ($value eq NOT_NULL) {
+            push(@terms, "$field IS NOT NULL");
+        }
+        elsif ($value eq IS_NULL) {
+            push(@terms, "$field IS NULL");
+        }
+        else {
+            push(@terms, "$field = ?");
+            push(@values, $value);
+        }
     }
 
-    foreach my $object (@$objects) {
-        bless($object, $class);
+    my $where = join(' AND ', @terms);
+    return $class->_do_list_select($where, \@values);
+}
+
+sub _do_list_select {
+    my ($class, $where, $values) = @_;
+    my $table = $class->DB_TABLE;
+    my $cols  = join(',', $class->DB_COLUMNS);
+    my $order = $class->LIST_ORDER;
+
+    my $sql = "SELECT $cols FROM $table";
+    if (defined $where) {
+        $sql .= " WHERE $where ";
     }
-    return $objects;
+    $sql .= " ORDER BY $order";
+
+    my $dbh = Bugzilla->dbh;
+    my $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @$values);
+    bless ($_, $class) foreach @$objects;
+    return $objects
 }
 
 ###############################
 ####      Accessors      ######
 ###############################
 
-sub id                { return $_[0]->{'id'};          }
-sub name              { return $_[0]->{'name'};        }
+sub id   { return $_[0]->{$_[0]->ID_FIELD};   }
+sub name { return $_[0]->{$_[0]->NAME_FIELD}; }
 
 ###############################
 ####        Methods        ####
@@ -153,10 +223,15 @@
                             superclass => __PACKAGE__,
                             function   => 'Bugzilla::Object->set' });
 
-    my $validators = $self->VALIDATORS;
-    if (exists $validators->{$field}) {
-        my $validator = $validators->{$field};
+    my %validators = (%{$self->VALIDATORS}, %{$self->UPDATE_VALIDATORS});
+    if (exists $validators{$field}) {
+        my $validator = $validators{$field};
         $value = $self->$validator($value, $field);
+        trick_taint($value) if (defined $value && !ref($value));
+
+        if ($self->can('_set_global_validator')) {
+            $self->_set_global_validator($value, $field);
+        }
     }
 
     $self->{$field} = $value;
@@ -169,19 +244,33 @@
     my $table    = $self->DB_TABLE;
     my $id_field = $self->ID_FIELD;
 
+    $dbh->bz_start_transaction();
+
     my $old_self = $self->new($self->id);
     
+    my %numeric = map { $_ => 1 } $self->NUMERIC_COLUMNS;
+    my %date    = map { $_ => 1 } $self->DATE_COLUMNS;
     my (@update_columns, @values, %changes);
     foreach my $column ($self->UPDATE_COLUMNS) {
-        if ($old_self->{$column} ne $self->{$column}) {
-            my $value = $self->{$column};
-            trick_taint($value) if defined $value;
-            push(@values, $value);
-            push(@update_columns, $column);
-            # We don't use $value because we don't want to detaint this for
-            # the caller.
-            $changes{$column} = [$old_self->{$column}, $self->{$column}];
+        my ($old, $new) = ($old_self->{$column}, $self->{$column});
+        # This has to be written this way in order to allow us to set a field
+        # from undef or to undef, and avoid warnings about comparing an undef
+        # with the "eq" operator.
+        if (!defined $new || !defined $old) {
+            next if !defined $new && !defined $old;
         }
+        elsif ( ($numeric{$column} && $old == $new) 
+                || ($date{$column} && str2time($old) == str2time($new))
+                || $old eq $new ) {
+            next;
+        }
+
+        trick_taint($new) if defined $new;
+        push(@values, $new);
+        push(@update_columns, $column);
+        # We don't use $new because we don't want to detaint this for
+        # the caller.
+        $changes{$column} = [$old, $self->{$column}];
     }
 
     my $columns = join(', ', map {"$_ = ?"} @update_columns);
@@ -189,6 +278,8 @@
     $dbh->do("UPDATE $table SET $columns WHERE $id_field = ?", undef, 
              @values, $self->id) if @values;
 
+    $dbh->bz_commit_transaction();
+
     return \%changes;
 }
 
@@ -200,9 +291,13 @@
     my ($class, $params) = @_;
     my $dbh = Bugzilla->dbh;
 
+    $dbh->bz_start_transaction();
     $class->check_required_create_fields($params);
     my $field_values = $class->run_create_validators($params);
-    return $class->insert_create_data($field_values);
+    my $object = $class->insert_create_data($field_values);
+    $dbh->bz_commit_transaction();
+
+    return $object;
 }
 
 sub check_required_create_fields {
@@ -262,18 +357,15 @@
 
 sub get_all {
     my $class = shift;
-    my $dbh = Bugzilla->dbh;
-    my $table = $class->DB_TABLE;
-    my $order = $class->LIST_ORDER;
-    my $id_field = $class->ID_FIELD;
-
-    my $ids = $dbh->selectcol_arrayref(qq{
-        SELECT $id_field FROM $table ORDER BY $order});
-
-    my $objects = $class->new_from_list($ids);
-    return @$objects;
+    return @{$class->_do_list_select()};
 }
 
+###############################
+####      Validators     ######
+###############################
+
+sub check_boolean { return $_[1] ? 1 : 0 }
+
 1;
 
 __END__
@@ -366,12 +458,34 @@
 
 The validator must return the validated value.
 
+=item C<UPDATE_VALIDATORS>
+
+This is just like L</VALIDATORS>, but these validators are called only
+when updating an object, not when creating it. Any validator that appears
+here must not appear in L</VALIDATORS>.
+
+L<Bugzilla::Bug> has good examples in its code of when to use this.
+
 =item C<UPDATE_COLUMNS>
 
 A list of columns to update when L</update> is called.
 If a field can't be changed, it shouldn't be listed here. (For example,
 the L</ID_FIELD> usually can't be updated.)
 
+=item C<NUMERIC_COLUMNS>
+
+When L</update> is called, it compares each column in the object to its
+current value in the database. It only updates columns that have changed.
+
+Any column listed in NUMERIC_COLUMNS is treated as a number, not as a string,
+during these comparisons.
+
+=item C<DATE_COLUMNS>
+
+This is much like L</NUMERIC_COLUMNS>, except that it treats strings as
+dates when being compared. So, for example, C<2007-01-01> would be
+equal to C<2007-01-01 00:00:00>.
+
 =back
 
 =head1 METHODS
@@ -380,7 +494,7 @@
 
 =over
 
-=item C<new($param)>
+=item C<new>
 
 =over
 
@@ -395,7 +509,7 @@
 from the database, that we  want to read in. (id is defined
 as the value in the L</ID_FIELD> column).
 
-If you pass in a hash, you can pass a C<name> key. The 
+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.
 
@@ -420,7 +534,35 @@
 
 =item B<Returns>
 
-A fully-initialized object.
+A fully-initialized object, or C<undef> if there is no object in the
+database matching the parameters you passed in.
+
+=back
+
+=item C<check>
+
+=over
+
+=item B<Description>
+
+Checks if there is an object in the database with the specified name, and
+throws an error if you specified an empty name, or if there is no object
+in the database with that name.
+
+=item B<Params>
+
+The parameters are the same as for L</new>, except that if you don't pass
+a hashref, the single argument is the I<name> of the object, not the id.
+
+=item B<Returns>
+
+A fully initialized object, guaranteed.
+
+=item B<Notes For Implementors>
+
+If you implement this in your subclass, make sure that you also update
+the C<object_name> block at the bottom of the F<global/user-error.html.tmpl>
+template.
 
 =back
 
@@ -436,6 +578,38 @@
 
  Returns:     A reference to an array of objects.
 
+=item C<match>
+
+=over
+
+=item B<Description>
+
+Gets a list of objects from the database based on certain criteria.
+
+Basically, a simple way of doing a sort of "SELECT" statement (like SQL)
+to get objects.
+
+All criteria are joined by C<AND>, so adding more criteria will give you
+a smaller set of results, not a larger set.
+
+=item B<Params>
+
+A hashref, where the keys are column names of the table, pointing to the 
+value that you want to match against for that column. 
+
+There are two special values, the constants C<NULL> and C<NOT_NULL>,
+which means "give me objects where this field is NULL or NOT NULL,
+respectively."
+
+If you don't specify any criteria, calling this function is the same
+as doing C<[$class-E<gt>get_all]>.
+
+=item B<Returns>
+
+An arrayref of objects, or an empty arrayref if there are no matches.
+
+=back
+
 =back
 
 =head2 Database Manipulation
@@ -555,6 +729,12 @@
 to implement the various C<set_> mutators for their different
 fields.
 
+If your class defines a method called C<_set_global_validator>,
+C<set> will call it with C<($value, $field)> as arguments, after running
+the validator for this particular field. C<_set_global_validator> does not
+return anything.
+
+
 See L</VALIDATORS> for more information.
 
 =item B<Params>
@@ -574,6 +754,20 @@
 
 =back
 
+=head2 Simple Validators
+
+You can use these in your subclass L</VALIDATORS> or L</UPDATE_VALIDATORS>.
+Note that you have to reference them like C<\&Bugzilla::Object::check_boolean>,
+you can't just write C<\&check_boolean>.
+
+=over
+
+=item C<check_boolean>
+
+Returns C<1> if the passed-in value is true, C<0> otherwise.
+
+=back
+
 =head1 CLASS FUNCTIONS
 
 =over
diff --git a/BugsSite/Bugzilla/Product.pm b/BugsSite/Bugzilla/Product.pm
index 728bd06..95a0e38 100644
--- a/BugsSite/Bugzilla/Product.pm
+++ b/BugsSite/Bugzilla/Product.pm
@@ -21,6 +21,7 @@
 use Bugzilla::Version;
 use Bugzilla::Milestone;
 
+use Bugzilla::Constants;
 use Bugzilla::Util;
 use Bugzilla::Group;
 use Bugzilla::Error;
@@ -50,6 +51,32 @@
    products.defaultmilestone
 );
 
+###############################
+####     Constructors     #####
+###############################
+
+# This is considerably faster than calling new_from_list three times
+# for each product in the list, particularly with hundreds or thousands
+# of products.
+sub preload {
+    my ($products) = @_;
+    my %prods = map { $_->id => $_ } @$products;
+    my @prod_ids = keys %prods;
+    return unless @prod_ids;
+
+    my $dbh = Bugzilla->dbh;
+    foreach my $field (qw(component version milestone)) {
+        my $classname = "Bugzilla::" . ucfirst($field);
+        my $objects = $classname->match({ product_id => \@prod_ids });
+
+        # Now populate the products with this set of objects.
+        foreach my $obj (@$objects) {
+            my $product_id = $obj->product_id;
+            $prods{$product_id}->{"${field}s"} ||= [];
+            push(@{$prods{$product_id}->{"${field}s"}}, $obj);
+        }
+    }
+}
 
 ###############################
 ####       Methods         ####
@@ -93,14 +120,48 @@
                   ORDER BY groups.name};
         $self->{group_controls} = 
             $dbh->selectall_hashref($query, 'id', undef, $self->id);
-        foreach my $group (keys(%{$self->{group_controls}})) {
-            $self->{group_controls}->{$group}->{'group'} = 
-                new Bugzilla::Group($group);
-        }
+
+        # For each group ID listed above, create and store its group object.
+        my @gids = keys %{$self->{group_controls}};
+        my $groups = Bugzilla::Group->new_from_list(\@gids);
+        $self->{group_controls}->{$_->id}->{group} = $_ foreach @$groups;
     }
     return $self->{group_controls};
 }
 
+sub groups_mandatory_for {
+    my ($self, $user) = @_;
+    my $groups = $user->groups_as_string;
+    my $mandatory = CONTROLMAPMANDATORY;
+    # For membercontrol we don't check group_id IN, because if membercontrol
+    # is Mandatory, the group is Mandatory for everybody, regardless of their
+    # group membership.
+    my $ids = Bugzilla->dbh->selectcol_arrayref(
+        "SELECT group_id FROM group_control_map
+          WHERE product_id = ?
+                AND (membercontrol = $mandatory
+                     OR (othercontrol = $mandatory
+                         AND group_id NOT IN ($groups)))",
+        undef, $self->id);
+    return Bugzilla::Group->new_from_list($ids);
+}
+
+sub groups_valid {
+    my ($self) = @_;
+    return $self->{groups_valid} if defined $self->{groups_valid};
+    
+    # Note that we don't check OtherControl below, because there is no
+    # valid NA/* combination.
+    my $ids = Bugzilla->dbh->selectcol_arrayref(
+        "SELECT DISTINCT group_id
+          FROM group_control_map AS gcm
+               INNER JOIN groups ON gcm.group_id = groups.id
+         WHERE product_id = ? AND isbuggroup = 1
+               AND membercontrol != " . CONTROLMAPNA,  undef, $self->id);
+    $self->{groups_valid} = Bugzilla::Group->new_from_list($ids);
+    return $self->{groups_valid};
+}
+
 sub versions {
     my $self = shift;
     my $dbh = Bugzilla->dbh;
@@ -266,7 +327,7 @@
 
 =over
 
-=item C<components()>
+=item C<components>
 
  Description: Returns an array of component objects belonging to
               the product.
@@ -286,7 +347,43 @@
               a Bugzilla::Group object and the properties of group
               relative to the product.
 
-=item C<versions()>
+=item C<groups_mandatory_for>
+
+=over
+
+=item B<Description>
+
+Tells you what groups are mandatory for bugs in this product.
+
+=item B<Params>
+
+C<$user> - The user who you want to check.
+
+=item B<Returns> An arrayref of C<Bugzilla::Group> objects.
+
+=back
+
+=item C<groups_valid>
+
+=over
+
+=item B<Description>
+
+Returns an arrayref of L<Bugzilla::Group> objects, representing groups
+that bugs could validly be restricted to within this product. Used mostly
+by L<Bugzilla::Bug> to assure that you're adding valid groups to a bug.
+
+B<Note>: This doesn't check whether or not the current user can add/remove
+bugs to/from these groups. It just tells you that bugs I<could be in> these
+groups, in this product.
+
+=item B<Params> (none)
+
+=item B<Returns> An arrayref of L<Bugzilla::Group> objects.
+
+=back
+
+=item C<versions>
 
  Description: Returns all valid versions for that product.
 
@@ -294,7 +391,7 @@
 
  Returns:     An array of Bugzilla::Version objects.
 
-=item C<milestones()>
+=item C<milestones>
 
  Description: Returns all valid milestones for that product.
 
@@ -345,6 +442,15 @@
 
 =over
 
+=item C<preload>
+
+When passed an arrayref of C<Bugzilla::Product> objects, preloads their
+L</milestones>, L</components>, and L</versions>, which is much faster
+than calling those accessors on every item in the array individually.
+
+This function is not exported, so must be called like 
+C<Bugzilla::Product::preload($products)>.
+
 =item C<check_product($product_name)>
 
  Description: Checks if the product name was passed in and if is a valid
diff --git a/BugsSite/Bugzilla/Search.pm b/BugsSite/Bugzilla/Search.pm
index 2673efa..499cc07 100644
--- a/BugsSite/Bugzilla/Search.pm
+++ b/BugsSite/Bugzilla/Search.pm
@@ -27,6 +27,7 @@
 #                 Max Kanat-Alexander <mkanat@bugzilla.org>
 #                 Joel Peshkin <bugreport@peshkin.net>
 #                 Lance Larsh <lance.larsh@oracle.com>
+#                 Jesse Clark <jjclark1982@gmail.com>
 
 use strict;
 
@@ -40,17 +41,12 @@
 use Bugzilla::Group;
 use Bugzilla::User;
 use Bugzilla::Field;
-use Bugzilla::Bug;
+use Bugzilla::Status;
 use Bugzilla::Keyword;
 
 use Date::Format;
 use Date::Parse;
 
-# How much we should add to the relevance, for each word that matches
-# in bugs.short_desc, during fulltext search. This is high because
-# we want summary matches to be *very* relevant, by default.
-use constant SUMMARY_RELEVANCE_FACTOR => 7;
-
 # 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 
@@ -96,7 +92,8 @@
     my $self = shift;
     my $fieldsref = $self->{'fields'};
     my $params = $self->{'params'};
-    my $user = $self->{'user'} || Bugzilla->user;
+    $self->{'user'} ||= Bugzilla->user;
+    my $user = $self->{'user'};
 
     my $orderref = $self->{'order'} || 0;
     my @inputorder;
@@ -122,6 +119,9 @@
 
     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" ],
@@ -227,6 +227,7 @@
 
     # 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) {
@@ -352,8 +353,8 @@
                     $extra .= " AND actcheck.added = $sql_chvalue";
                 }
                 push(@supptables, "LEFT JOIN bugs_activity AS actcheck " .
-                                  "ON $extra AND actcheck.fieldid IN (" .
-                                  join(",", @actlist) . ")");
+                                  "ON $extra AND " 
+                                 . $dbh->sql_in('actcheck.fieldid', \@actlist));
             }
 
             # Now that we're done using @list to determine if there are any
@@ -368,7 +369,7 @@
 
     my $sql_deadlinefrom;
     my $sql_deadlineto;
-    if (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})){
+    if ($user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
       my $deadlinefrom;
       my $deadlineto;
             
@@ -411,6 +412,8 @@
         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.
@@ -422,772 +425,90 @@
     my $v;
     my $term;
     my %funcsbykey;
-    my @funcdefs =
-        (
-         "^(?:assigned_to|reporter|qa_contact),(?:notequals|equals|anyexact),%group\\.([^%]+)%" => sub {
-             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";
-             }
-          },
-         "^(?:assigned_to|reporter|qa_contact),(?:equals|anyexact),(%\\w+%)" => sub {
-             $term = "bugs.$f = " . pronoun($1, $user);
-          },
-         "^(?:assigned_to|reporter|qa_contact),(?:notequals),(%\\w+%)" => sub {
-             $term = "bugs.$f <> " . pronoun($1, $user);
-          },
-         "^(assigned_to|reporter),(?!changed)" => sub {
-             my $list = $self->ListIDsForEmail($t, $v);
-             if ($list) {
-                 $term = "bugs.$f IN ($list)"; 
-             } else {
-                 push(@supptables, "INNER JOIN profiles AS map_$f " .
-                                   "ON bugs.$f = map_$f.userid");
-                 $f = "map_$f.login_name";
-             }
-         },
-         "^qa_contact,(?!changed)" => sub {
-             push(@supptables, "LEFT JOIN profiles AS map_qa_contact " .
-                               "ON bugs.qa_contact = map_qa_contact.userid");
-             $f = "COALESCE(map_$f.login_name,'')";
-         },
-
-         "^(?:cc),(?:notequals|equals|anyexact),%group\\.([^%]+)%" => sub {
-             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";
-             }
-          },
-
-         "^cc,(?:equals|anyexact),(%\\w+%)" => sub {
-             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";
-         },
-         "^cc,(?:notequals),(%\\w+%)" => sub {
-             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";
-         },
-         "^cc,(anyexact|substring|regexp)" => sub {
-             my $list;
-             $list = $self->ListIDsForEmail($t, $v);
-             my $chartseq = $chartid;
-             if ($chartid eq "") {
-                 $chartseq = "CC$sequence";
-                 $sequence++;
-             }
-             if ($list) {
-                 push(@supptables, "LEFT JOIN cc AS cc_$chartseq " .
-                                   "ON bugs.bug_id = cc_$chartseq.bug_id " .
-                                   "AND cc_$chartseq.who IN($list)");
-                 $term = "cc_$chartseq.who IS NOT NULL";
-             } else {
-                 push(@supptables, "LEFT JOIN cc AS cc_$chartseq " .
-                                   "ON bugs.bug_id = cc_$chartseq.bug_id");
-                 push(@supptables,
-                            "LEFT JOIN profiles AS map_cc_$chartseq " .
-                            "ON cc_$chartseq.who = map_cc_$chartseq.userid");
-
-                 $ff = $f = "map_cc_$chartseq.login_name";
-                 my $ref = $funcsbykey{",$t"};
-                 &$ref;
-             }
-         },
-         "^cc,(?!changed)" => sub {
-             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");
-
-            $ff = $f = "map_cc_$chartseq.login_name";
-            my $ref = $funcsbykey{",$t"};
-            &$ref;
-            push(@supptables, 
-                        "LEFT JOIN profiles AS map_cc_$chartseq " .
-                        "ON (cc_$chartseq.who = map_cc_$chartseq.userid " .
-                        "AND ($term))"
-                );
-            $term = "$f IS NOT NULL";
-         },
-
-         "^long_?desc,changedby" => sub {
-             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";
-         },
-         "^long_?desc,changedbefore" => sub {
-             my $table = "longdescs_$chartid";
-             push(@supptables, "LEFT JOIN longdescs AS $table " .
-                               "ON $table.bug_id = bugs.bug_id");
-             $term = "$table.bug_when < " . $dbh->quote(SqlifyDate($v));
-         },
-         "^long_?desc,changedafter" => sub {
-             my $table = "longdescs_$chartid";
-             push(@supptables, "LEFT JOIN longdescs AS $table " .
-                               "ON $table.bug_id = bugs.bug_id");
-             $term = "$table.bug_when > " . $dbh->quote(SqlifyDate($v));
-         },
-         "^content,matches" => sub {
-             # "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 longdescs table to the query so we can search comments.
-             my $table = "longdescs_$chartid";
-             my $extra = "";
-             if (Bugzilla->params->{"insidergroup"} 
-                 && !Bugzilla->user->in_group(Bugzilla->params->{"insidergroup"}))
-             {
-                 $extra = "AND $table.isprivate < 1";
-             }
-             push(@supptables, "LEFT JOIN longdescs AS $table " .
-                               "ON bugs.bug_id = $table.bug_id $extra");
-
-             # Create search terms to add to the SELECT and WHERE clauses.
-             # $term1 searches comments.
-             my $term1 = $dbh->sql_fulltext_search("${table}.thetext", $v);
-
-             # short_desc searching for the WHERE clause
-             my @words = _split_words_into_like('bugs.short_desc', $v);
-             my $term2_where = join(' OR ', @words);
-
-             # short_desc relevance
-             my $factor = SUMMARY_RELEVANCE_FACTOR;
-             my @s_words = map("CASE WHEN $_ THEN $factor ELSE 0 END", @words);
-             my $term2_select = join(' + ', @s_words);
-
-             # The term to use in the WHERE clause.
-             $term = "$term1 > 0 OR ($term2_where)";
-
-             # 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.
-             #
-             # Note: We should be calculating the relevance based on all
-             # comments for a bug, not just matching comments, but that's hard
-             # (see http://bugzilla.mozilla.org/show_bug.cgi?id=145588#c35).
-             my $select_term = "(SUM($term1) + $term2_select) AS relevance";
-
-             # add the column not used in aggregate function explicitly
-             push(@groupby, 'bugs.short_desc');
-
-             # 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);
-             }
-         },
-         "^content," => sub {
-             ThrowUserError("search_content_without_matches");
-         },
-         "^(?:deadline|creation_ts|delta_ts),(?:lessthan|greaterthan|equals|notequals),(?:-|\\+)?(?:\\d+)(?:[dDwWmMyY])\$" => sub {
-             $v = SqlifyDate($v);
-             $q = $dbh->quote($v);
-        },
-         "^commenter,(?:equals|anyexact),(%\\w+%)" => sub {
-             my $match = pronoun($1, $user);
-             my $chartseq = $chartid;
-             if ($chartid eq "") {
-                 $chartseq = "LD$sequence";
-                 $sequence++;
-             }
-             my $table = "longdescs_$chartseq";
-             my $extra = "";
-             if (Bugzilla->params->{"insidergroup"} 
-                 && !Bugzilla->user->in_group(Bugzilla->params->{"insidergroup"})) 
-             {
-                 $extra = "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";
-         },
-         "^commenter," => sub {
-             my $chartseq = $chartid;
-             my $list;
-             $list = $self->ListIDsForEmail($t, $v);
-             if ($chartid eq "") {
-                 $chartseq = "LD$sequence";
-                 $sequence++;
-             }
-             my $table = "longdescs_$chartseq";
-             my $extra = "";
-             if (Bugzilla->params->{"insidergroup"} 
-                 && !Bugzilla->user->in_group(Bugzilla->params->{"insidergroup"})) 
-             {
-                 $extra = "AND $table.isprivate < 1";
-             }
-             if ($list) {
-                 push(@supptables, "LEFT JOIN longdescs AS $table " .
-                                   "ON $table.bug_id = bugs.bug_id $extra " .
-                                   "AND $table.who IN ($list)");
-                 $term = "$table.who IS NOT NULL";
-             } else {
-                 push(@supptables, "LEFT JOIN longdescs AS $table " .
-                                   "ON $table.bug_id = bugs.bug_id $extra");
-                 push(@supptables, "LEFT JOIN profiles AS map_$table " .
-                                   "ON $table.who = map_$table.userid");
-                 $ff = $f = "map_$table.login_name";
-                 my $ref = $funcsbykey{",$t"};
-                 &$ref;
-             }
-         },
-         "^long_?desc," => sub {
-             my $table = "longdescs_$chartid";
-             my $extra = "";
-             if (Bugzilla->params->{"insidergroup"} 
-                 && !Bugzilla->user->in_group(Bugzilla->params->{"insidergroup"})) 
-             {
-                 $extra = "AND $table.isprivate < 1";
-             }
-             push(@supptables, "LEFT JOIN longdescs AS $table " .
-                               "ON $table.bug_id = bugs.bug_id $extra");
-             $f = "$table.thetext";
-         },
-         "^longdescs\.isprivate," => sub {
-             my $table = "longdescs_$chartid";
-             my $extra = "";
-             if (Bugzilla->params->{"insidergroup"}
-                 && !Bugzilla->user->in_group(Bugzilla->params->{"insidergroup"}))
-             {
-                 $extra = "AND $table.isprivate < 1";
-             }
-             push(@supptables, "LEFT JOIN longdescs AS $table " .
-                               "ON $table.bug_id = bugs.bug_id $extra");
-             $f = "$table.isprivate";
-         },
-         "^work_time,changedby" => sub {
-             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))";
-         },
-         "^work_time,changedbefore" => sub {
-             my $table = "longdescs_$chartid";
-             push(@supptables, "LEFT JOIN longdescs AS $table " .
-                               "ON $table.bug_id = bugs.bug_id");
-             $term = "(($table.bug_when < " . $dbh->quote(SqlifyDate($v));
-             $term .= ") AND ($table.work_time <> 0))";
-         },
-         "^work_time,changedafter" => sub {
-             my $table = "longdescs_$chartid";
-             push(@supptables, "LEFT JOIN longdescs AS $table " .
-                               "ON $table.bug_id = bugs.bug_id");
-             $term = "(($table.bug_when > " . $dbh->quote(SqlifyDate($v));
-             $term .= ") AND ($table.work_time <> 0))";
-         },
-         "^work_time," => sub {
-             my $table = "longdescs_$chartid";
-             push(@supptables, "LEFT JOIN longdescs AS $table " .
-                               "ON $table.bug_id = bugs.bug_id");
-             $f = "$table.work_time";
-         },
-         "^percentage_complete," => sub {
-             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";
-         },
-         "^bug_group,(?!changed)" => sub {
-            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";
-            my $ref = $funcsbykey{",$t"};
-            &$ref;
-            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";
-         },
-         "^attach_data\.thedata,changed" => sub {
-            # Searches for attachment data's change must search
-            # the creation timestamp of the attachment instead.
-            $f = "attachments.whocares";
-         },
-         "^attach_data\.thedata," => sub {
-             my $atable = "attachments_$chartid";
-             my $dtable = "attachdata_$chartid";
-             my $extra = "";
-             if (Bugzilla->params->{"insidergroup"} 
-                 && !Bugzilla->user->in_group(Bugzilla->params->{"insidergroup"})) 
-             {
-                 $extra = "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";
-         },
-         "^attachments\.submitter," => sub {
-             my $atable = "map_attachment_submitter_$chartid";
-             my $extra = "";
-             if (Bugzilla->params->{"insidergroup"}
-                 && !Bugzilla->user->in_group(Bugzilla->params->{"insidergroup"}))
-             {
-                 $extra = "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";
-         },
-         "^attachments\..*," => sub {
-             my $table = "attachments_$chartid";
-             my $extra = "";
-             if (Bugzilla->params->{"insidergroup"} 
-                 && !Bugzilla->user->in_group(Bugzilla->params->{"insidergroup"})) 
-             {
-                 $extra = "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";
-         },
-         "^flagtypes.name," => sub {
-             # 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"}};
-             
-             # 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";
-             }
-         },
-         "^requestees.login_name," => sub {
-             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";
-         },
-         "^setters.login_name," => sub {
-             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";
-         },
-         
-         "^(changedin|days_elapsed)," => sub {
-             $f = "(" . $dbh->sql_to_days('NOW()') . " - " .
-                        $dbh->sql_to_days('bugs.delta_ts') . ")";
-         },
-
-         "^component,(?!changed)" => sub {
-             $f = $ff = "components.name";
-             $funcsbykey{",$t"}->();
-             $term = build_subselect("bugs.component_id",
-                                     "components.id",
-                                     "components",
-                                     $term);
-         },
-
-         "^product,(?!changed)" => sub {
-             # Generate the restriction condition
-             $f = $ff = "products.name";
-             $funcsbykey{",$t"}->();
-             $term = build_subselect("bugs.product_id",
-                                     "products.id",
-                                     "products",
-                                     $term);
-         },
-
-         "^classification,(?!changed)" => sub {
-             # 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"}->();
-             $term = build_subselect("map_products.classification_id",
-                                     "classifications.id",
-                                     "classifications",
-                                     $term);
-         },
-
-         "^keywords,(?!changed)" => sub {
-             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") {
-                     my $ref = $funcsbykey{",$t"};
-                     &$ref;
-                     if ($term && $haveawordterm) {
-                         $term = "(($term) AND $haveawordterm)";
-                     }
-                 }
-             }
-             if ($term) {
-                 push(@supptables, "LEFT JOIN keywords AS $table " .
-                                   "ON $table.bug_id = bugs.bug_id");
-             }
-         },
-
-         "^dependson,(?!changed)" => sub {
-                my $table = "dependson_" . $chartid;
-                $ff = "$table.$f";
-                my $ref = $funcsbykey{",$t"};
-                &$ref;
-                push(@supptables, "LEFT JOIN dependencies AS $table " .
-                                  "ON $table.blocked = bugs.bug_id " .
-                                  "AND ($term)");
-                $term = "$ff IS NOT NULL";
-         },
-
-         "^blocked,(?!changed)" => sub {
-                my $table = "blocked_" . $chartid;
-                $ff = "$table.$f";
-                my $ref = $funcsbykey{",$t"};
-                &$ref;
-                push(@supptables, "LEFT JOIN dependencies AS $table " .
-                                  "ON $table.dependson = bugs.bug_id " .
-                                  "AND ($term)");
-                $term = "$ff IS NOT NULL";
-         },
-
-         "^alias,(?!changed)" => sub {
-             $ff = "COALESCE(bugs.alias, '')";
-             my $ref = $funcsbykey{",$t"};
-             &$ref;
-         },
-
-         "^owner_idle_time,(greaterthan|lessthan)" => sub {
-                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";
-         },
-
-         ",equals" => sub {
-             $term = "$ff = $q";
-         },
-         ",notequals" => sub {
-             $term = "$ff != $q";
-         },
-         ",casesubstring" => sub {
-             $term = $dbh->sql_position($q, $ff) . " > 0";
-         },
-         ",substring" => sub {
-             $term = $dbh->sql_position(lc($q), "LOWER($ff)") . " > 0";
-         },
-         ",substr" => sub {
-             $funcsbykey{",substring"}->();
-         },
-         ",notsubstring" => sub {
-             $term = $dbh->sql_position(lc($q), "LOWER($ff)") . " = 0";
-         },
-         ",regexp" => sub {
-             $term = $dbh->sql_regexp($ff, $q);
-         },
-         ",notregexp" => sub {
-             $term = $dbh->sql_not_regexp($ff, $q);
-         },
-         ",lessthan" => sub {
-             $term = "$ff < $q";
-         },
-         ",matches" => sub {
-             ThrowUserError("search_content_without_matches");
-         },
-         ",greaterthan" => sub {
-             $term = "$ff > $q";
-         },
-         ",anyexact" => sub {
-             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 = "$ff IN (" . join (',', @list) . ")";
-             }
-         },
-         ",anywordssubstr" => sub {
-             $term = join(" OR ", @{GetByWordListSubstr($ff, $v)});
-         },
-         ",allwordssubstr" => sub {
-             $term = join(" AND ", @{GetByWordListSubstr($ff, $v)});
-         },
-         ",nowordssubstr" => sub {
-             my @list = @{GetByWordListSubstr($ff, $v)};
-             if (@list) {
-                 $term = "NOT (" . join(" OR ", @list) . ")";
-             }
-         },
-         ",anywords" => sub {
-             $term = join(" OR ", @{GetByWordList($ff, $v)});
-         },
-         ",allwords" => sub {
-             $term = join(" AND ", @{GetByWordList($ff, $v)});
-         },
-         ",nowords" => sub {
-             my @list = @{GetByWordList($ff, $v)};
-             if (@list) {
-                 $term = "NOT (" . join(" OR ", @list) . ")";
-             }
-         },
-         ",(changedbefore|changedafter)" => sub {
-             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)";
-         },
-         ",(changedfrom|changedto)" => sub {
-             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)";
-         },
-         ",changedby" => sub {
-             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)";
-         },
-         );
+    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);
@@ -1364,7 +685,7 @@
                         if ($f !~ /\./) {
                             $ff = "bugs.$f";
                         }
-                        &$ref;
+                        $self->$ref(%func_args);
                         if ($debug) {
                             push(@debugdata, "$f / $t / $v / " .
                                              ($term || "undef") . " *");
@@ -1480,8 +801,10 @@
                  $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
-        if ($field =~ /(?:.*\s+AS\s+)?(\w+(\.\w+)?)(?:\s+(ASC|DESC))?$/i) {
+        # 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);
         }
     }
@@ -1550,59 +873,6 @@
     return time2str("%Y-%m-%d %H:%M:%S", $date);
 }
 
-# ListIDsForEmail returns a string with a comma-joined list
-# of userids matching email addresses
-# according to the type specified.
-# Currently, this only supports regexp, exact, anyexact, and substring matches.
-# Matches will return up to 50 matching userids
-# If a match type is unsupported or returns too many matches,
-# ListIDsForEmail returns an undef.
-sub ListIDsForEmail {
-    my ($self, $type, $email) = (@_);
-    my $old = $self->{"emailcache"}{"$type,$email"};
-    return undef if ($old && $old eq "---");
-    return $old if $old;
-    
-    my $dbh = Bugzilla->dbh;
-    my @list = ();
-    my $list = "---";
-    if ($type eq 'anyexact') {
-        foreach my $w (split(/,/, $email)) {
-            $w = trim($w);
-            my $id = login_to_id($w);
-            if ($id > 0) {
-                push(@list,$id)
-            }
-        }
-        $list = join(',', @list);
-    } elsif ($type eq 'substring') {
-        my $sql_email = $dbh->quote($email);
-        trick_taint($sql_email);
-        my $result = $dbh->selectcol_arrayref(
-                    q{SELECT userid FROM profiles WHERE } .
-                    $dbh->sql_position(lc($sql_email), q{LOWER(login_name)}) .
-                    q{ > 0 } . $dbh->sql_limit(51));
-        @list = @{$result};
-        if (scalar(@list) < 50) {
-            $list = join(',', @list);
-        }
-    } elsif ($type eq 'regexp') {
-        my $sql_email = $dbh->quote($email);
-        trick_taint($sql_email);
-        my $result = $dbh->selectcol_arrayref(
-                        qq{SELECT userid FROM profiles WHERE } .
-                        $dbh->sql_regexp("login_name", $sql_email) .
-                        q{ } . $dbh->sql_limit(51));
-        @list = @{$result};
-        if (scalar(@list) < 50) {
-            $list = join(',', @list);
-        }
-    }
-    $self->{"emailcache"}{"$type,$email"} = $list;
-    return undef if ($list eq "---");
-    return $list;
-}
-
 sub build_subselect {
     my ($outer, $inner, $table, $cond) = @_;
     my $q = "SELECT $inner FROM $table WHERE $cond";
@@ -1610,8 +880,7 @@
     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 "$outer IN (" . join(',', @$list) . ")";
-}
+    return $dbh->sql_in($outer, $list);}
 
 sub GetByWordList {
     my ($field, $strs) = (@_);
@@ -1622,12 +891,9 @@
         my $word = $w;
         if ($word ne "") {
             $word =~ tr/A-Z/a-z/;
-            $word = $dbh->quote(quotemeta($word));
+            $word = $dbh->quote('(^|[^a-z0-9])' . quotemeta($word) . '($|[^a-z0-9])');
             trick_taint($word);
-            $word =~ s/^'//;
-            $word =~ s/'$//;
-            $word = '(^|[^a-z0-9])' . $word . '($|[^a-z0-9])';
-            push(@list, $dbh->sql_regexp($field, "'$word'"));
+            push(@list, $dbh->sql_regexp($field, $word));
         }
     }
 
@@ -1645,8 +911,7 @@
         if ($word ne "") {
             $sql_word = $dbh->quote($word);
             trick_taint($sql_word);
-            push(@list, $dbh->sql_position(lc($sql_word),
-                                           "LOWER($field)") . " > 0");
+            push(@list, $dbh->sql_iposition($sql_word, $field) . " > 0");
         }
     }
 
@@ -1749,18 +1014,1055 @@
     push(@$stringlist, trim($orderfield . ' ' . $orderdirection));
 }
 
-# This is a helper for fulltext search
-sub _split_words_into_like {
-    my ($field, $text) = @_;
-    my $dbh = Bugzilla->dbh;
-    # This code is very similar to Bugzilla::DB::sql_fulltext_search,
-    # so you can look there if you'd like an explanation of what it's
-    # doing.
-    my $lower_text = lc($text);
-    my @words = split(/\s+/, $lower_text);
-    @words = map($dbh->quote("%$_%"), @words);
-    map(trick_taint($_), @words);
-    @words = map($dbh->sql_istrcmp($field, $_, 'LIKE'), @words);
-    return @words;
+#####################################################################
+# 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)");
+    }
+    
+    if ($$t eq 'anyexact') {
+        $$term = "(" . join(" OR ", @terms) . ")";
+    }
+    else {
+        $$term = "(" . join(" AND ", @terms) . ")";
+    }
+}
+
+sub _multiselect_nonchanged {
+    my $self = shift;
+    my %func_args = @_;
+    my ($chartid, $f, $ff, $t, $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)";
+}
+
 1;
diff --git a/BugsSite/Bugzilla/Search/Quicksearch.pm b/BugsSite/Bugzilla/Search/Quicksearch.pm
index 151d6f8..04216b8 100644
--- a/BugsSite/Bugzilla/Search/Quicksearch.pm
+++ b/BugsSite/Bugzilla/Search/Quicksearch.pm
@@ -26,7 +26,7 @@
 use Bugzilla::Error;
 use Bugzilla::Constants;
 use Bugzilla::Keyword;
-use Bugzilla::Bug;
+use Bugzilla::Status;
 use Bugzilla::Field;
 use Bugzilla::Util;
 
@@ -78,6 +78,12 @@
                 "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",
@@ -90,6 +96,7 @@
 
 # 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]
              #   ^^^
@@ -269,6 +276,12 @@
                         # votes:xx ("at least xx votes")
                         addChart('votes', 'greaterthan', $1 - 1, $negate);
                     }
+                    elsif ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
+                        # Flag and requestee shortcut
+                        addChart('flagtypes.name', 'substring', $1, $negate);
+                        $chart++; $and = $or = 0; # Next chart for boolean AND
+                        addChart('requestees.login_name', 'substring', $2, $negate);
+                    }
                     elsif ($or_operand =~ /^([^:]+):([^:]+)$/) {
                         # generic field1,field2,field3:value1,value2 notation
                         my @fields = split(/,/, $1);
@@ -291,10 +304,13 @@
                         # 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
-                            if (grep({lc($word) eq $_} PLATFORMS)) {
+                            # 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])?)$/) {
@@ -334,7 +350,7 @@
                                     addChart('component', 'substring',
                                              $word, $negate);
                                 }
-                                if (grep({lc($word) eq $_}
+                                if (grep({lc($word) eq lc($_)}
                                          map($_->name, Bugzilla::Keyword->get_all))) {
                                     addChart('keywords', 'substring',
                                              $word, $negate);
@@ -430,10 +446,14 @@
     # 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;
 }
 
diff --git a/BugsSite/Bugzilla/Search/Saved.pm b/BugsSite/Bugzilla/Search/Saved.pm
index 9162276..c832224 100644
--- a/BugsSite/Bugzilla/Search/Saved.pm
+++ b/BugsSite/Bugzilla/Search/Saved.pm
@@ -55,7 +55,7 @@
     link_in_footer => \&_check_link_in_footer,
 };
 
-use constant UPDATE_COLUMNS => qw(query query_type);
+use constant UPDATE_COLUMNS => qw(name query query_type);
 
 ##############
 # Validators #
@@ -79,6 +79,8 @@
     $query || ThrowUserError("buglist_parameters_required");
     my $cgi = new Bugzilla::CGI($query);
     $cgi->clean_search_url;
+    # Don't store the query name as a parameter.
+    $cgi->delete('known_name');
     return $cgi->query_string;
 }
 
@@ -97,13 +99,12 @@
     Bugzilla->login(LOGIN_REQUIRED);
     my $dbh = Bugzilla->dbh;
     $class->check_required_create_fields(@_);
+    $dbh->bz_start_transaction();
     my $params = $class->run_create_validators(@_);
 
     # Right now you can only create a Saved Search for the current user.
     $params->{userid} = Bugzilla->user->id;
 
-    $dbh->bz_lock_tables('namedqueries WRITE',
-                         'namedqueries_link_in_footer WRITE');
     my $lif = delete $params->{link_in_footer};
     my $obj = $class->insert_create_data($params);
     if ($lif) {
@@ -111,11 +112,29 @@
                   (user_id, namedquery_id) VALUES (?,?)',
                  undef, $params->{userid}, $obj->id);
     }
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     return $obj;
 }
 
+sub preload {
+    my ($searches) = @_;
+    my $dbh = Bugzilla->dbh;
+
+    return unless scalar @$searches;
+
+    my @query_ids = map { $_->id } @$searches;
+    my $queries_in_footer = $dbh->selectcol_arrayref(
+        'SELECT namedquery_id
+           FROM namedqueries_link_in_footer
+          WHERE ' . $dbh->sql_in('namedquery_id', \@query_ids) . ' AND user_id = ?',
+          undef, Bugzilla->user->id);
+
+    my %links_in_footer = map { $_ => 1 } @$queries_in_footer;
+    foreach my $query (@$searches) {
+        $query->{link_in_footer} = ($links_in_footer{$query->id}) ? 1 : 0;
+    }
+}
 #####################
 # Complex Accessors #
 #####################
@@ -170,6 +189,23 @@
     return $self->{shared_with_group};
 }
 
+sub shared_with_users {
+    my $self = shift;
+    my $dbh = Bugzilla->dbh;
+
+    if (!exists $self->{shared_with_users}) {
+        $self->{shared_with_users} =
+          $dbh->selectrow_array('SELECT COUNT(*)
+                                   FROM namedqueries_link_in_footer
+                             INNER JOIN namedqueries
+                                     ON namedquery_id = id
+                                  WHERE namedquery_id = ?
+                                    AND user_id != userid',
+                                  undef, $self->id);
+    }
+    return $self->{shared_with_users};
+}
+
 ####################
 # Simple Accessors #
 ####################
@@ -188,6 +224,7 @@
 # Mutators #
 ############
 
+sub set_name       { $_[0]->set('name',       $_[1]); }
 sub set_url        { $_[0]->set('query',      $_[1]); }
 sub set_query_type { $_[0]->set('query_type', $_[1]); }
 
@@ -208,6 +245,7 @@
  my $edit_link  = $query->edit_link;
  my $search_url = $query->url;
  my $owner      = $query->user;
+ my $num_subscribers = $query->shared_with_users;
 
 =head1 DESCRIPTION
 
@@ -230,6 +268,12 @@
 
 See also: L<Bugzilla::Object/new>.
 
+=item C<preload>
+
+Sets C<link_in_footer> for all given saved searches at once, for the
+currently logged in user. This is much faster than calling this method
+for each saved search individually.
+
 =back
 
 
@@ -262,4 +306,9 @@
 The L<Bugzilla::Group> that this search is shared with. C<undef> if
 this search isn't shared.
 
+=item C<shared_with_users>
+
+Returns how many users (besides the author of the saved search) are
+using the saved search, i.e. have it displayed in their footer.
+
 =back
diff --git a/BugsSite/Bugzilla/Series.pm b/BugsSite/Bugzilla/Series.pm
index 877f698..95d0a8f 100644
--- a/BugsSite/Bugzilla/Series.pm
+++ b/BugsSite/Bugzilla/Series.pm
@@ -21,7 +21,6 @@
 #                 Lance Larsh <lance.larsh@oracle.com>
 
 use strict;
-use lib ".";
 
 # This module implements a series - a set of data to be plotted on a chart.
 #
@@ -125,6 +124,10 @@
     ($self->{'series_id'}, $self->{'category'},  $self->{'subcategory'},
      $self->{'name'}, $self->{'creator'}, $self->{'frequency'},
      $self->{'query'}, $self->{'public'}) = @_;
+
+    # If the first parameter is undefined, check if this series already
+    # exists and update it series_id accordingly
+    $self->{'series_id'} ||= $self->existsInDatabase();
 }
 
 sub initFromCGI {
@@ -171,7 +174,7 @@
     my $self = shift;
 
     my $dbh = Bugzilla->dbh;
-    $dbh->bz_lock_tables('series_categories WRITE', 'series WRITE');
+    $dbh->bz_start_transaction();
 
     my $category_id = getCategoryID($self->{'category'});
     my $subcategory_id = getCategoryID($self->{'subcategory'});
@@ -210,7 +213,7 @@
           || ThrowCodeError("missing_series_id", { 'series' => $self });
     }
     
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 }
 
 # Check whether a series with this name, category and subcategory exists in
diff --git a/BugsSite/Bugzilla/Status.pm b/BugsSite/Bugzilla/Status.pm
new file mode 100644
index 0000000..f8b7733
--- /dev/null
+++ b/BugsSite/Bugzilla/Status.pm
@@ -0,0 +1,278 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (C) 2007
+# Frédéric Buclin. All Rights Reserved.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+package Bugzilla::Status;
+
+use base qw(Bugzilla::Object Exporter);
+@Bugzilla::Status::EXPORT = qw(BUG_STATE_OPEN is_open_state closed_bug_statuses);
+
+################################
+#####   Initialization     #####
+################################
+
+use constant DB_TABLE => 'bug_status';
+
+use constant DB_COLUMNS => qw(
+    id
+    value
+    sortkey
+    isactive
+    is_open
+);
+
+use constant NAME_FIELD => 'value';
+use constant LIST_ORDER => 'sortkey, value';
+
+###############################
+#####     Accessors        ####
+###############################
+
+sub name      { return $_[0]->{'value'};    }
+sub sortkey   { return $_[0]->{'sortkey'};  }
+sub is_active { return $_[0]->{'isactive'}; }
+sub is_open   { return $_[0]->{'is_open'};  }
+
+###############################
+#####       Methods        ####
+###############################
+
+sub BUG_STATE_OPEN {
+    # XXX - We should cache this list.
+    my $dbh = Bugzilla->dbh;
+    return @{$dbh->selectcol_arrayref('SELECT value FROM bug_status WHERE is_open = 1')};
+}
+
+# Tells you whether or not the argument is a valid "open" state.
+sub is_open_state {
+    my ($state) = @_;
+    return (grep($_ eq $state, BUG_STATE_OPEN) ? 1 : 0);
+}
+
+sub closed_bug_statuses {
+    my @bug_statuses = Bugzilla::Status->get_all;
+    @bug_statuses = grep { !$_->is_open } @bug_statuses;
+    return @bug_statuses;
+}
+
+sub can_change_to {
+    my $self = shift;
+    my $dbh = Bugzilla->dbh;
+
+    if (!ref($self) || !defined $self->{'can_change_to'}) {
+        my ($cond, @args, $self_exists);
+        if (ref($self)) {
+            $cond = '= ?';
+            push(@args, $self->id);
+            $self_exists = 1;
+        }
+        else {
+            $cond = 'IS NULL';
+            # Let's do it so that the code below works in all cases.
+            $self = {};
+        }
+
+        my $new_status_ids = $dbh->selectcol_arrayref("SELECT new_status
+                                                         FROM status_workflow
+                                                   INNER JOIN bug_status
+                                                           ON id = new_status
+                                                        WHERE isactive = 1
+                                                          AND old_status $cond
+                                                     ORDER BY sortkey",
+                                                        undef, @args);
+
+        # Allow the bug status to remain unchanged.
+        push(@$new_status_ids, $self->id) if $self_exists;
+        $self->{'can_change_to'} = Bugzilla::Status->new_from_list($new_status_ids);
+    }
+
+    return $self->{'can_change_to'};
+}
+
+sub can_change_from {
+    my $self = shift;
+    my $dbh = Bugzilla->dbh;
+
+    if (!defined $self->{'can_change_from'}) {
+        my $old_status_ids = $dbh->selectcol_arrayref('SELECT old_status
+                                                         FROM status_workflow
+                                                   INNER JOIN bug_status
+                                                           ON id = old_status
+                                                        WHERE isactive = 1
+                                                          AND new_status = ?
+                                                          AND old_status IS NOT NULL',
+                                                        undef, $self->id);
+
+        # Allow the bug status to remain unchanged.
+        push(@$old_status_ids, $self->id);
+        $self->{'can_change_from'} = Bugzilla::Status->new_from_list($old_status_ids);
+    }
+
+    return $self->{'can_change_from'};
+}
+
+sub comment_required_on_change_from {
+    my ($self, $old_status) = @_;
+    my ($cond, $values) = $self->_status_condition($old_status);
+    
+    my ($require_comment) = Bugzilla->dbh->selectrow_array(
+        "SELECT require_comment FROM status_workflow
+          WHERE $cond", undef, @$values);
+    return $require_comment;
+}
+
+# Used as a helper for various functions that have to deal with old_status
+# sometimes being NULL and sometimes having a value.
+sub _status_condition {
+    my ($self, $old_status) = @_;
+    my @values;
+    my $cond = 'old_status IS NULL';
+    # For newly-filed bugs
+    if ($old_status) {
+        $cond = 'old_status = ?';
+        push(@values, $old_status->id);
+    }
+    $cond .= " AND new_status = ?";
+    push(@values, $self->id);
+    return ($cond, \@values);
+}
+
+sub add_missing_bug_status_transitions {
+    my $bug_status = shift || Bugzilla->params->{'duplicate_or_move_bug_status'};
+    my $dbh = Bugzilla->dbh;
+    my $new_status = new Bugzilla::Status({name => $bug_status});
+    # Silently discard invalid bug statuses.
+    $new_status || return;
+
+    my $missing_statuses = $dbh->selectcol_arrayref('SELECT id
+                                                       FROM bug_status
+                                                  LEFT JOIN status_workflow
+                                                         ON old_status = id
+                                                        AND new_status = ?
+                                                      WHERE old_status IS NULL',
+                                                      undef, $new_status->id);
+
+    my $sth = $dbh->prepare('INSERT INTO status_workflow
+                             (old_status, new_status) VALUES (?, ?)');
+
+    foreach my $old_status_id (@$missing_statuses) {
+        next if ($old_status_id == $new_status->id);
+        $sth->execute($old_status_id, $new_status->id);
+    }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Status - Bug status class.
+
+=head1 SYNOPSIS
+
+    use Bugzilla::Status;
+
+    my $bug_status = new Bugzilla::Status({name => 'ASSIGNED'});
+    my $bug_status = new Bugzilla::Status(4);
+
+    my @closed_bug_statuses = closed_bug_statuses();
+
+    Bugzilla::Status::add_missing_bug_status_transitions($bug_status);
+
+=head1 DESCRIPTION
+
+Status.pm represents a bug status object. It is an implementation
+of L<Bugzilla::Object>, and thus provides all methods that
+L<Bugzilla::Object> provides.
+
+The methods that are specific to C<Bugzilla::Status> are listed
+below.
+
+=head1 METHODS
+
+=over
+
+=item C<closed_bug_statuses>
+
+ Description: Returns a list of C<Bugzilla::Status> objects which can have
+              a resolution associated with them ("closed" bug statuses).
+
+ Params:      none.
+
+ Returns:     A list of Bugzilla::Status objects.
+
+=item C<can_change_to>
+
+ Description: Returns the list of active statuses a bug can be changed to
+              given the current bug status. If this method is called as a
+              class method, then it returns all bug statuses available on
+              bug creation.
+
+ Params:      none.
+
+ Returns:     A list of Bugzilla::Status objects.
+
+=item C<can_change_from>
+
+ Description: Returns the list of active statuses a bug can be changed from
+              given the new bug status. If the bug status is available on
+              bug creation, this method doesn't return this information.
+              You have to call C<can_change_to> instead.
+
+ Params:      none.
+
+ Returns:     A list of Bugzilla::Status objects.
+
+=item C<comment_required_on_change_from>
+
+=over
+
+=item B<Description>
+
+Checks if a comment is required to change to this status from another
+status, according to the current settings in the workflow.
+
+Note that this doesn't implement the checks enforced by the various
+C<commenton> parameters--those are checked by internal checks in
+L<Bugzilla::Bug>.
+
+=item B<Params>
+
+C<$old_status> - The status you're changing from.
+
+=item B<Returns>
+
+C<1> if a comment is required on this change, C<0> if not.
+
+=back
+
+=item C<add_missing_bug_status_transitions>
+
+ Description: Insert all missing transitions to a given bug status.
+
+ Params:      $bug_status - The value (name) of a bug status.
+
+ Returns:     nothing.
+
+=back
+
+=cut
diff --git a/BugsSite/Bugzilla/Template.pm b/BugsSite/Bugzilla/Template.pm
index a29557d..1362eca 100644
--- a/BugsSite/Bugzilla/Template.pm
+++ b/BugsSite/Bugzilla/Template.pm
@@ -36,13 +36,16 @@
 
 use Bugzilla::Constants;
 use Bugzilla::Install::Requirements;
+use Bugzilla::Install::Util qw(install_string template_include_path include_languages);
 use Bugzilla::Util;
 use Bugzilla::User;
 use Bugzilla::Error;
-use MIME::Base64;
-use Bugzilla::Bug;
+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);
@@ -53,156 +56,60 @@
 
 use base qw(Template);
 
+# As per the Template::Base documentation, the _init() method is being called 
+# by the new() constructor. We take advantage of this in order to plug our
+# UTF-8-aware Parser object in neatly after the original _init() method has
+# happened, in particular, after having set up the constants namespace.
+# See bug 413121 for details.
+sub _init {
+    my $self = shift;
+    my $config = $_[0];
+
+    $self->SUPER::_init(@_) || return undef;
+
+    $self->{PARSER} = $config->{PARSER}
+        = new Bugzilla::Template::Parser($config);
+
+    # Now we need to re-create the default Service object, making it aware
+    # of our Parser object.
+    $self->{SERVICE} = $config->{SERVICE}
+        = Template::Config->service($config);
+
+    return $self;
+}
+
 # Convert the constants in the Bugzilla::Constants module into a hash we can
 # pass to the template object for reflection into its "constants" namespace
 # (which is like its "variables" namespace, but for constants).  To do so, we
-# traverse the arrays of exported and exportable symbols, pulling out functions
-# (which is how Perl implements constants) and ignoring the rest (which, if
-# Constants.pm exports only constants, as it should, will be nothing else).
+# traverse the arrays of exported and exportable symbols and ignoring the rest
+# (which, if Constants.pm exports only constants, as it should, will be nothing else).
 sub _load_constants {
     my %constants;
     foreach my $constant (@Bugzilla::Constants::EXPORT,
                           @Bugzilla::Constants::EXPORT_OK)
     {
-        if (defined Bugzilla::Constants->$constant) {
-            # Constants can be lists, and we can't know whether we're
-            # getting a scalar or a list in advance, since they come to us
-            # as the return value of a function call, so we have to
-            # retrieve them all in list context into anonymous arrays,
-            # then extract the scalar ones (i.e. the ones whose arrays
-            # contain a single element) from their arrays.
-            $constants{$constant} = [&{$Bugzilla::Constants::{$constant}}];
-            if (scalar(@{$constants{$constant}}) == 1) {
-                $constants{$constant} = @{$constants{$constant}}[0];
-            }
+        if (ref Bugzilla::Constants->$constant) {
+            $constants{$constant} = Bugzilla::Constants->$constant;
+        }
+        else {
+            my @list = (Bugzilla::Constants->$constant);
+            $constants{$constant} = (scalar(@list) == 1) ? $list[0] : \@list;
         }
     }
     return \%constants;
 }
 
-# Make an ordered list out of a HTTP Accept-Language header see RFC 2616, 14.4
-# We ignore '*' and <language-range>;q=0
-# For languages with the same priority q the order remains unchanged.
-sub sortAcceptLanguage {
-    sub sortQvalue { $b->{'qvalue'} <=> $a->{'qvalue'} }
-    my $accept_language = $_[0];
-
-    # clean up string.
-    $accept_language =~ s/[^A-Za-z;q=0-9\.\-,]//g;
-    my @qlanguages;
-    my @languages;
-    foreach(split /,/, $accept_language) {
-        if (m/([A-Za-z\-]+)(?:;q=(\d(?:\.\d+)))?/) {
-            my $lang   = $1;
-            my $qvalue = $2;
-            $qvalue = 1 if not defined $qvalue;
-            next if $qvalue == 0;
-            $qvalue = 1 if $qvalue > 1;
-            push(@qlanguages, {'qvalue' => $qvalue, 'language' => $lang});
-        }
-    }
-
-    return map($_->{'language'}, (sort sortQvalue @qlanguages));
-}
-
 # Returns the path to the templates based on the Accept-Language
 # settings of the user and of the available languages
 # If no Accept-Language is present it uses the defined default
 # Templates may also be found in the extensions/ tree
 sub getTemplateIncludePath {
-    my $lang = Bugzilla->request_cache->{'language'} || "";
-    # Return cached value if available
-
-    my $include_path = Bugzilla->request_cache->{"template_include_path_$lang"};
-    return $include_path if $include_path;
-
-    my $templatedir = bz_locations()->{'templatedir'};
-    my $project     = bz_locations()->{'project'};
-
-    my $languages = trim(Bugzilla->params->{'languages'});
-    if (not ($languages =~ /,/)) {
-       if ($project) {
-           $include_path = [
-               "$templatedir/$languages/$project",
-               "$templatedir/$languages/custom",
-               "$templatedir/$languages/default"
-           ];
-       } else {
-           $include_path = [
-               "$templatedir/$languages/custom",
-               "$templatedir/$languages/default"
-           ];
-       }
-    }
-    my @languages       = sortAcceptLanguage($languages);
-    # If $lang is specified, only consider this language.
-    my @accept_language = ($lang) || sortAcceptLanguage($ENV{'HTTP_ACCEPT_LANGUAGE'} || "");
-    my @usedlanguages;
-    foreach my $language (@accept_language) {
-        # Per RFC 1766 and RFC 2616 any language tag matches also its 
-        # primary tag. That is 'en' (accept language)  matches 'en-us',
-        # 'en-uk' etc. but not the otherway round. (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$language\E(-.+)?$/i, @languages) {
-            push (@usedlanguages, @found);
-        }
-    }
-    push(@usedlanguages, Bugzilla->params->{'defaultlanguage'});
-    if ($project) {
-        $include_path = [
-           map((
-               "$templatedir/$_/$project",
-               "$templatedir/$_/custom",
-               "$templatedir/$_/default"
-               ), @usedlanguages
-            )
-        ];
-    } else {
-        $include_path = [
-           map((
-               "$templatedir/$_/custom",
-               "$templatedir/$_/default"
-               ), @usedlanguages
-            )
-        ];
-    }
-    
-    # add in extension template directories:
-    my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
-    foreach my $extension (@extensions) {
-        trick_taint($extension); # since this comes right from the filesystem
-                                 # we have bigger issues if it is insecure
-        push(@$include_path,
-            map((
-                $extension."/template/".$_),
-               @usedlanguages));
-    }
-    
-    # remove duplicates since they keep popping up:
-    my @dirs;
-    foreach my $dir (@$include_path) {
-        push(@dirs, $dir) unless grep ($dir eq $_, @dirs);
-    }
-    Bugzilla->request_cache->{"template_include_path_$lang"} = \@dirs;
-    
-    return Bugzilla->request_cache->{"template_include_path_$lang"};
-}
-
-sub put_header {
-    my $self = shift;
-    my $vars = {};
-    ($vars->{'title'}, $vars->{'h1'}, $vars->{'h2'}) = (@_);
-     
-    $self->process("global/header.html.tmpl", $vars)
-      || ThrowTemplateError($self->error());
-    $vars->{'header_done'} = 1;
-}
-
-sub put_footer {
-    my $self = shift;
-    $self->process("global/footer.html.tmpl")
-      || ThrowTemplateError($self->error());
+    my $cache = Bugzilla->request_cache;
+    my $lang  = $cache->{'language'} || '';
+    $cache->{"template_include_path_$lang"} ||= template_include_path({
+        use_languages => Bugzilla->languages,
+        only_language => $lang });
+    return $cache->{"template_include_path_$lang"};
 }
 
 sub get_format {
@@ -263,8 +170,6 @@
     # Do this by escaping \0 to \1\0, and replacing matches with \0\0$count\0\0
     # \0 is used because it's unlikely to occur in the text, so the cost of
     # doing this should be very small
-    # Also, \0 won't appear in the value_quote'd bug title, so we don't have
-    # to worry about bogus substitutions from there
 
     # escape the 2nd escape char we're using
     my $chr1 = chr(1);
@@ -384,7 +289,7 @@
             $className = "bz_obsolete";
         }
         # Prevent code injection in the title.
-        $title = value_quote($title);
+        $title = html_quote(clean_text($title));
 
         $link_text =~ s/ \[details\]$//;
         my $linkval = "attachment.cgi?id=$attachid";
@@ -426,21 +331,21 @@
         # if we don't change them below (which is highly likely).
         my ($pre, $title, $post) = ("", "", "");
 
-        $title = $bug_state;
+        $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 .= " $bug_res";
+            $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 = value_quote($title);
+        $title = html_quote(clean_text($title));
 
         my $linkval = "show_bug.cgi?id=$bug_num";
         if (defined $comment_num) {
@@ -484,6 +389,13 @@
       return 0;
   };
 
+# Clone the array reference to leave the original one unaltered.
+$Template::Stash::LIST_OPS->{ clone } =
+  sub {
+      my $list = shift;
+      return [@$list];
+  };
+
 # Allow us to still get the scalar if we use the list operation ".0" on it,
 # as we often do for defaults in query.cgi and other places.
 $Template::Stash::SCALAR_OPS->{ 0 } = 
@@ -731,11 +643,7 @@
                     # |U+200e|Left-To-Right Mark        |0xe2 0x80 0x8e      |
                     # |U+200f|Right-To-Left Mark        |0xe2 0x80 0x8f      |
                     # --------------------------------------------------------
-                    #
-                    # Do the replacing in a loop so that we don't get tricked
-                    # by stuff like 0xe2 0xe2 0x80 0xae 0x80 0xae.
-                    while ($var =~ s/\xe2\x80(\xaa|\xab|\xac|\xad|\xae)//g) {
-                    }
+                    $var =~ s/[\x{202a}-\x{202e}]//g;
                 }
                 return $var;
             },
@@ -784,7 +692,11 @@
             },
 
             # Wrap a displayed comment to the appropriate length
-            wrap_comment => \&Bugzilla::Util::wrap_comment,
+            wrap_comment => [
+                sub {
+                    my ($context, $cols) = @_;
+                    return sub { wrap_comment($_[0], $cols) }
+                }, 1],
 
             # We force filtering of every variable in key security-critical
             # places; we have a none filter for people to use when they 
@@ -822,6 +734,20 @@
                 Bugzilla::BugMail::Send($id, $mailrecipients);
             },
 
+            # Allow templates to access the "corect" URLBase value
+            'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
+
+            # Allow templates to access docs url with users' preferred language
+            'docs_urlbase' => sub { 
+                my ($language) = include_languages();
+                my $docs_urlbase = Bugzilla->params->{'docs_urlbase'};
+                $docs_urlbase =~ s/\%lang\%/$language/;
+                return $docs_urlbase;
+            },
+
+            # Allow templates to generate a token themselves.
+            'issue_hash_token' => \&Bugzilla::Token::issue_hash_token,
+
             # These don't work as normal constants.
             DB_MODULE        => \&Bugzilla::Constants::DB_MODULE,
             REQUIRED_MODULES => 
@@ -846,7 +772,7 @@
     # Remove the compiled templates.
     my $datadir = bz_locations()->{'datadir'};
     if (-e "$datadir/template") {
-        print "Removing existing compiled templates ...\n" if $output;
+        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
@@ -862,7 +788,7 @@
         }
     }
 
-    print "Precompiling templates...\n" if $output;
+    print install_string('template_precompile') if $output;
 
     my $templatedir = bz_locations()->{'templatedir'};
     # Don't hang on templates which use the CGI library
@@ -877,9 +803,6 @@
         -d "$templatedir/$dir/default" || -d "$templatedir/$dir/custom" 
             || next;
         local $ENV{'HTTP_ACCEPT_LANGUAGE'} = $dir;
-        # We locally hack this parameter so that Bugzilla::Template
-        # accepts this language no matter what.
-        local Bugzilla->params->{'languages'} = "$dir,en";
         my $template = Bugzilla::Template->create(clean_cache => 1);
 
         # Precompile all the templates found in all the directories.
@@ -922,6 +845,11 @@
 
     # If anything created a Template object before now, clear it out.
     delete Bugzilla->request_cache->{template};
+    # This is the single variable used to precompile templates,
+    # which needs to be cleared as well.
+    delete Bugzilla->request_cache->{template_include_path_};
+
+    print install_string('done') . "\n" if $output;
 }
 
 # Helper for precompile_templates
@@ -946,10 +874,6 @@
 =head1 SYNOPSIS
 
   my $template = Bugzilla::Template->create;
-
-  $template->put_header($title, $h1, $h2);
-  $template->put_footer();
-
   my $format = $template->get_format("foo/bar",
                                      scalar($cgi->param('format')),
                                      scalar($cgi->param('ctype')));
@@ -982,24 +906,6 @@
 
 =over
 
-=item C<put_header($title, $h1, $h2)>
-
- Description: Display the header of the page for non yet templatized .cgi files.
-
- Params:      $title - Page title.
-              $h1    - Main page header.
-              $h2    - Page subheader.
-
- Returns:     nothing
-
-=item C<put_footer()>
-
- Description: Display the footer of the page for non yet templatized .cgi files.
-
- Params:      none
-
- Returns:     nothing
-
 =item C<get_format($file, $format, $ctype)>
 
  Description: Construct a format object from URL parameters.
diff --git a/BugsSite/Bugzilla/Template/Parser.pm b/BugsSite/Bugzilla/Template/Parser.pm
new file mode 100644
index 0000000..0965e07
--- /dev/null
+++ b/BugsSite/Bugzilla/Template/Parser.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 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/BugsSite/Bugzilla/Template/Plugin/Hook.pm b/BugsSite/Bugzilla/Template/Plugin/Hook.pm
index a5eec82..99ece08 100644
--- a/BugsSite/Bugzilla/Template/Plugin/Hook.pm
+++ b/BugsSite/Bugzilla/Template/Plugin/Hook.pm
@@ -19,6 +19,7 @@
 #
 # Contributor(s): Myk Melez <myk@mozilla.org>
 #                 Zach Lipton <zach@zachlipton.com>
+#                 Elliotte Martin <everythingsolved.com>
 #
 
 package Bugzilla::Template::Plugin::Hook;
@@ -26,6 +27,7 @@
 use strict;
 
 use Bugzilla::Constants;
+use Bugzilla::Install::Util qw(include_languages);
 use Bugzilla::Template;
 use Bugzilla::Util;
 use Bugzilla::Error;
@@ -44,12 +46,11 @@
 }
 
 sub process {
-    my ($self, $hook_name) = @_;
+    my ($self, $hook_name, $template) = @_;
+    $template ||= $self->{_CONTEXT}->stash->{component}->{name};
 
-    my $paths = $self->{_CONTEXT}->{LOAD_TEMPLATES}->[0]->paths;
-    my $template = $self->{_CONTEXT}->stash->{component}->{name};
-    my @hooks = ();
-    
+    my @hooks;
+
     # sanity check:
     if (!$template =~ /[\w\.\/\-_\\]+/) {
         ThrowCodeError('template_invalid', { name => $template});
@@ -65,8 +66,9 @@
     # 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 = getLanguages();
+    my @usedlanguages = include_languages({use_languages => Bugzilla->languages});
     foreach my $extension (@extensions) {
+        next if -e "$extension/disabled";
         foreach my $language (@usedlanguages) {
             my $file = $extension.'/template/'.$language.'/'.$extensiontemplate;
             if (-e $file) {
@@ -78,6 +80,8 @@
             }
         }
     }
+
+    my $paths = $self->{_CONTEXT}->{LOAD_TEMPLATES}->[0]->paths;
     
     # we keep this too since you can still put hook templates in 
     # template/en/custom/hook
@@ -103,24 +107,6 @@
     return $output;
 }
 
-# get a list of languages we accept so we can find the hook 
-# that corresponds to our desired languages:
-sub getLanguages() {
-    my $languages = trim(Bugzilla->params->{'languages'});
-    if (not ($languages =~ /,/)) { # only one language
-        return $languages;
-    }
-    my @languages       = Bugzilla::Template::sortAcceptLanguage($languages);
-    my @accept_language = Bugzilla::Template::sortAcceptLanguage($ENV{'HTTP_ACCEPT_LANGUAGE'} || "" );
-    my @usedlanguages;
-    foreach my $lang (@accept_language) {
-        if(my @found = grep /^\Q$lang\E(-.+)?$/i, @languages) {
-            push (@usedlanguages, @found);
-        }
-    }
-    return @usedlanguages;
-}
-
 1;
 
 __END__
@@ -133,9 +119,54 @@
 
 Template Toolkit plugin to process hooks added into templates by extensions.
 
+=head1 METHODS
+
+=over
+
+=item B<process>
+
+=over
+
+=item B<Description>
+
+Processes hooks added into templates by extensions.
+
+=item B<Params>
+
+=over
+
+=item C<hook_name>
+
+The unique name of the template hook.
+
+=item C<template> (optional)
+
+The path of the calling template.
+This is used as a work around to a bug which causes the path to the hook
+to be incorrect when the hook is called from inside a block.
+
+Example: If the hook C<lastrow> is added to the template
+F<show-multiple.html.tmpl> and it is desired to force the correct template
+path, the template hook would be:
+
+ [% Hook.process("lastrow", "bug/show-multiple.html.tmpl") %]
+
+=back
+
+=item B<Returns> 
+
+Output from processing template extension.
+
+=back
+
+=back
+
 =head1 SEE ALSO
 
 L<Template::Plugin>
-L<http:E<sol>E<sol>www.bugzilla.orgE<sol>docsE<sol>tipE<sol>htmlE<sol>customization.html>
-L<http:E<sol>E<sol>bugzilla.mozilla.orgE<sol>show_bug.cgi?id=229658>
-L<http:E<sol>E<sol>bugzilla.mozilla.orgE<sol>show_bug.cgi?id=298341>
+
+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>
diff --git a/BugsSite/Bugzilla/Token.pm b/BugsSite/Bugzilla/Token.pm
index 7db6b91..a8862bd 100644
--- a/BugsSite/Bugzilla/Token.pm
+++ b/BugsSite/Bugzilla/Token.pm
@@ -34,14 +34,17 @@
 use Bugzilla::Error;
 use Bugzilla::Mailer;
 use Bugzilla::Util;
+use Bugzilla::User;
 
 use Date::Format;
 use Date::Parse;
 use File::Basename;
+use Digest::MD5 qw(md5_hex);
 
 use base qw(Exporter);
 
-@Bugzilla::Token::EXPORT = qw(issue_session_token check_token_data delete_token);
+@Bugzilla::Token::EXPORT = qw(issue_session_token check_token_data delete_token
+                              issue_hash_token check_hash_token);
 
 ################################################################################
 # Public Functions
@@ -80,20 +83,24 @@
     $template->process('account/email/request-new.txt.tmpl', $vars, \$message)
       || ThrowTemplateError($template->error());
 
+    # In 99% of cases, the user getting the confirmation email is the same one
+    # who made the request, and so it is reasonable to send the email in the same
+    # language used to view the "Create a New Account" page (we cannot use his
+    # user prefs as the user has no account yet!).
     MessageToMTA($message);
 }
 
 sub IssueEmailChangeToken {
-    my ($userid, $old_email, $new_email) = @_;
+    my ($user, $old_email, $new_email) = @_;
     my $email_suffix = Bugzilla->params->{'emailsuffix'};
 
-    my ($token, $token_ts) = _create_token($userid, 'emailold', $old_email . ":" . $new_email);
+    my ($token, $token_ts) = _create_token($user->id, 'emailold', $old_email . ":" . $new_email);
 
-    my $newtoken = _create_token($userid, 'emailnew', $old_email . ":" . $new_email);
+    my $newtoken = _create_token($user->id, 'emailnew', $old_email . ":" . $new_email);
 
     # Mail the user the token along with instructions for using it.
 
-    my $template = Bugzilla->template;
+    my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
     my $vars = {};
 
     $vars->{'oldemailaddress'} = $old_email . $email_suffix;
@@ -118,38 +125,34 @@
     $template->process("account/email/change-new.txt.tmpl", $vars, \$message)
       || ThrowTemplateError($template->error());
 
+    Bugzilla->template_inner("");
     MessageToMTA($message);
 }
 
 # Generates a random token, adds it to the tokens table, and sends it
 # to the user with instructions for using it to change their password.
 sub IssuePasswordToken {
-    my $loginname = shift;
+    my $user = shift;
     my $dbh = Bugzilla->dbh;
-    my $template = Bugzilla->template;
-    my $vars = {};
 
-    # Retrieve the user's ID from the database.
-    trick_taint($loginname);
-    my ($userid, $too_soon) =
-        $dbh->selectrow_array('SELECT profiles.userid, tokens.issuedate
-                                 FROM profiles
-                            LEFT JOIN tokens
-                                   ON tokens.userid = profiles.userid
-                                  AND tokens.tokentype = ?
-                                  AND tokens.issuedate > NOW() - ' .
-                                      $dbh->sql_interval(10, 'MINUTE') . '
-                                WHERE ' . $dbh->sql_istrcmp('login_name', '?'),
-                                undef, ('password', $loginname));
+    my $too_soon =
+        $dbh->selectrow_array('SELECT 1 FROM tokens
+                                WHERE userid = ?
+                                  AND tokentype = ?
+                                  AND issuedate > NOW() - ' .
+                                      $dbh->sql_interval(10, 'MINUTE'),
+                                undef, ($user->id, 'password'));
 
     ThrowUserError('too_soon_for_new_token', {'type' => 'password'}) if $too_soon;
 
-    my ($token, $token_ts) = _create_token($userid, 'password', $::ENV{'REMOTE_ADDR'});
+    my ($token, $token_ts) = _create_token($user->id, 'password', $::ENV{'REMOTE_ADDR'});
 
     # Mail the user the token along with instructions for using it.
-    $vars->{'token'} = $token;
-    $vars->{'emailaddress'} = $loginname . Bugzilla->params->{'emailsuffix'};
+    my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+    my $vars = {};
 
+    $vars->{'token'} = $token;
+    $vars->{'emailaddress'} = $user->email;
     $vars->{'max_token_age'} = MAX_TOKEN_AGE;
     $vars->{'token_ts'} = $token_ts;
 
@@ -158,6 +161,7 @@
                                                                $vars, \$message)
       || ThrowTemplateError($template->error());
 
+    Bugzilla->template_inner("");
     MessageToMTA($message);
 }
 
@@ -169,14 +173,65 @@
     return _create_token(Bugzilla->user->id, 'session', $data);
 }
 
+sub issue_hash_token {
+    my ($data, $time) = @_;
+    $data ||= [];
+    $time ||= time();
+
+    # The concatenated string is of the form
+    # token creation time + site-wide secret + user ID + data
+    my @args = ($time, Bugzilla->localconfig->{'site_wide_secret'}, Bugzilla->user->id, @$data);
+
+    my $token = join('*', @args);
+    # Wide characters cause md5_hex() to die.
+    if (Bugzilla->params->{'utf8'}) {
+        utf8::encode($token) if utf8::is_utf8($token);
+    }
+    $token = md5_hex($token);
+
+    # Prepend the token creation time, unencrypted, so that the token
+    # lifetime can be validated.
+    return $time . '-' . $token;
+}
+
+sub check_hash_token {
+    my ($token, $data) = @_;
+    $data ||= [];
+    my ($time, $expected_token);
+
+    if ($token) {
+        ($time, undef) = split(/-/, $token);
+        # Regenerate the token based on the information we have.
+        $expected_token = issue_hash_token($data, $time);
+    }
+
+    if (!$token
+        || $expected_token ne $token
+        || time() - $time > MAX_TOKEN_AGE * 86400)
+    {
+        my $template = Bugzilla->template;
+        my $vars = {};
+        $vars->{'script_name'} = basename($0);
+        $vars->{'token'} = issue_hash_token($data);
+        $vars->{'reason'} = (!$token) ?                   'missing_token' :
+                            ($expected_token ne $token) ? 'invalid_token' :
+                                                          'expired_token';
+        print Bugzilla->cgi->header();
+        $template->process('global/confirm-action.html.tmpl', $vars)
+          || ThrowTemplateError($template->error());
+        exit;
+    }
+
+    # If we come here, then the token is valid and not too old.
+    return 1;
+}
+
 sub CleanTokenTable {
     my $dbh = Bugzilla->dbh;
-    $dbh->bz_lock_tables('tokens WRITE');
     $dbh->do('DELETE FROM tokens
               WHERE ' . $dbh->sql_to_days('NOW()') . ' - ' .
                         $dbh->sql_to_days('issuedate') . ' >= ?',
               undef, MAX_TOKEN_AGE);
-    $dbh->bz_unlock_tables();
 }
 
 sub GenerateUniqueToken {
@@ -207,31 +262,28 @@
     return $token;
 }
 
-# Cancels a previously issued token and notifies the system administrator.
+# Cancels a previously issued token and notifies the user.
 # This should only happen when the user accidentally makes a token request
 # or when a malicious hacker makes a token request on behalf of a user.
 sub Cancel {
     my ($token, $cancelaction, $vars) = @_;
     my $dbh = Bugzilla->dbh;
-    my $template = Bugzilla->template;
     $vars ||= {};
 
     # Get information about the token being canceled.
     trick_taint($token);
-    my ($issuedate, $tokentype, $eventdata, $loginname) =
+    my ($issuedate, $tokentype, $eventdata, $userid) =
         $dbh->selectrow_array('SELECT ' . $dbh->sql_date_format('issuedate') . ',
-                                      tokentype, eventdata, login_name
+                                      tokentype, eventdata, userid
                                  FROM tokens
-                            LEFT JOIN profiles
-                                   ON tokens.userid = profiles.userid
                                 WHERE token = ?',
                                 undef, $token);
 
-    # If we are cancelling the creation of a new user account, then there
+    # If we are canceling the creation of a new user account, then there
     # is no entry in the 'profiles' table.
-    $loginname ||= $eventdata;
-    $vars->{'emailaddress'} = $loginname . Bugzilla->params->{'emailsuffix'};
-    $vars->{'maintainer'} = Bugzilla->params->{'maintainer'};
+    my $user = new Bugzilla::User($userid);
+
+    $vars->{'emailaddress'} = $userid ? $user->email : $eventdata;
     $vars->{'remoteaddress'} = $::ENV{'REMOTE_ADDR'};
     $vars->{'token'} = $token;
     $vars->{'tokentype'} = $tokentype;
@@ -240,11 +292,13 @@
     $vars->{'cancelaction'} = $cancelaction;
 
     # Notify the user via email about the cancellation.
+    my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
 
     my $message;
     $template->process("account/cancel-token.txt.tmpl", $vars, \$message)
       || ThrowTemplateError($template->error());
 
+    Bugzilla->template_inner("");
     MessageToMTA($message);
 
     # Delete the token from the database.
@@ -301,9 +355,7 @@
     return unless defined $token;
     trick_taint($token);
 
-    $dbh->bz_lock_tables('tokens WRITE');
     $dbh->do("DELETE FROM tokens WHERE token = ?", undef, $token);
-    $dbh->bz_unlock_tables();
 }
 
 # Given a token, makes sure it comes from the currently logged in user
@@ -311,7 +363,7 @@
 # Note: this routine must not be called while tables are locked as it will try
 # to lock some tables itself, see CleanTokenTable().
 sub check_token_data {
-    my ($token, $expected_action) = @_;
+    my ($token, $expected_action, $alternate_script) = @_;
     my $user = Bugzilla->user;
     my $template = Bugzilla->template;
     my $cgi = Bugzilla->cgi;
@@ -331,6 +383,7 @@
         $vars->{'token_action'} = $token_action;
         $vars->{'expected_action'} = $expected_action;
         $vars->{'script_name'} = basename($0);
+        $vars->{'alternate_script'} = $alternate_script || basename($0);
 
         # Now is a good time to remove old tokens from the DB.
         CleanTokenTable();
@@ -364,14 +417,14 @@
     trick_taint($tokentype);
     trick_taint($eventdata);
 
-    $dbh->bz_lock_tables('tokens WRITE');
+    $dbh->bz_start_transaction();
 
     my $token = GenerateUniqueToken();
 
     $dbh->do("INSERT INTO tokens (userid, issuedate, token, tokentype, eventdata)
         VALUES (?, NOW(), ?, ?, ?)", undef, ($userid, $token, $tokentype, $eventdata));
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     if (wantarray) {
         my (undef, $token_ts, undef) = GetTokenData($token);
@@ -395,8 +448,8 @@
     use Bugzilla::Token;
 
     Bugzilla::Token::issue_new_user_account_token($login_name);
-    Bugzilla::Token::IssueEmailChangeToken($user_id, $old_email, $new_email);
-    Bugzilla::Token::IssuePasswordToken($login_name);
+    Bugzilla::Token::IssueEmailChangeToken($user, $old_email, $new_email);
+    Bugzilla::Token::IssuePasswordToken($user);
     Bugzilla::Token::DeletePasswordTokens($user_id, $reason);
     Bugzilla::Token::Cancel($token, $cancelaction, $vars);
 
@@ -426,26 +479,26 @@
  Returns:     Nothing. It throws an error if the same user made the same
               request in the last few minutes.
 
-=item C<sub IssueEmailChangeToken($user_id, $old_email, $new_email)>
+=item C<sub IssueEmailChangeToken($user, $old_email, $new_email)>
 
  Description: Sends two distinct tokens per email to the old and new email
               addresses to confirm the email address change for the given
-              user ID. These tokens remain valid for the next MAX_TOKEN_AGE days.
+              user. These tokens remain valid for the next MAX_TOKEN_AGE days.
 
- Params:      $user_id - The user ID of the user account requesting a new
-                         email address.
+ Params:      $user      - User object of the user requesting a new
+                           email address.
               $old_email - The current (old) email address of the user.
               $new_email - The new email address of the user.
 
  Returns:     Nothing.
 
-=item C<IssuePasswordToken($login_name)>
+=item C<IssuePasswordToken($user)>
 
- Description: Sends a token per email to the given login name. This token
+ Description: Sends a token per email to the given user. This token
               can be used to change the password (e.g. in case the user
               cannot remember his password and wishes to enter a new one).
 
- Params:      $login_name - The login name of the user requesting a new password.
+ Params:      $user - User object of the user requesting a new password.
 
  Returns:     Nothing. It throws an error if the same user made the same
               request in the last few minutes.
diff --git a/BugsSite/Bugzilla/Update.pm b/BugsSite/Bugzilla/Update.pm
index f2b17a6..d3f7805 100644
--- a/BugsSite/Bugzilla/Update.pm
+++ b/BugsSite/Bugzilla/Update.pm
@@ -40,12 +40,9 @@
     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 = 1;
-        if (-e $local_file) {
-            # Try to alter its last modification time.
-            $can_alter = utime(undef, undef, $local_file);
-        }
+        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;
diff --git a/BugsSite/Bugzilla/User.pm b/BugsSite/Bugzilla/User.pm
index 003337f..41ded04 100644
--- a/BugsSite/Bugzilla/User.pm
+++ b/BugsSite/Bugzilla/User.pm
@@ -67,7 +67,7 @@
 use constant MATCH_SKIP_CONFIRM  => 1;
 
 use constant DEFAULT_USER => {
-    'id'             => 0,
+    'userid'         => 0,
     'realname'       => '',
     'login_name'     => '',
     'showmybugslink' => 0,
@@ -82,7 +82,7 @@
 # Bugzilla::User used "name" for the realname field. This should be
 # fixed one day.
 use constant DB_COLUMNS => (
-    'profiles.userid     AS id',
+    'profiles.userid',
     'profiles.login_name',
     'profiles.realname',
     'profiles.mybugslink AS showmybugslink',
@@ -281,6 +281,11 @@
         'SELECT id FROM namedqueries WHERE userid = ?', undef, $self->id);
     require Bugzilla::Search::Saved;
     $self->{queries} = Bugzilla::Search::Saved->new_from_list($query_ids);
+
+    # We preload link_in_footer from here as this information is always requested.
+    # This only works if the user object represents the current logged in user.
+    Bugzilla::Search::Saved::preload($self->{queries}) if $self->id == Bugzilla->user->id;
+
     return $self->{queries};
 }
 
@@ -368,7 +373,7 @@
                                                AND user_id=?
                                                AND isbless=0},
                                           { Columns=>[1,2] },
-                                          $self->{id});
+                                          $self->id);
 
     # The above gives us an arrayref [name, id, name, id, ...]
     # Convert that into a hashref
@@ -578,7 +583,7 @@
     my ($self, $bugid) = @_;
     my $dbh = Bugzilla->dbh;
     my $sth  = $self->{sthCanSeeBug};
-    my $userid  = $self->{id};
+    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
@@ -680,9 +685,10 @@
         return unless $warn == THROW_ERROR;
         ThrowUserError('no_products');
     }
-    trick_taint($product_name);
+    my $product = new Bugzilla::Product({name => $product_name});
+
     my $can_enter =
-        grep($_->name eq $product_name, @{$self->get_enterable_products});
+      $product && grep($_->name eq $product->name, @{$self->get_enterable_products});
 
     return 1 if $can_enter;
 
@@ -691,8 +697,6 @@
     # Check why access was denied. These checks are slow,
     # but that's fine, because they only happen if we fail.
 
-    my $product = new Bugzilla::Product({name => $product_name});
-
     # The product could not exist or you could be denied...
     if (!$product || !$product->user_has_access($self)) {
         ThrowUserError('entry_access_denied', {product => $product_name});
@@ -776,17 +780,35 @@
     my ($self, $flag_type) = @_;
 
     return ($self->can_set_flag($flag_type)
-            || !$flag_type->request_group
-            || $self->in_group_id($flag_type->request_group->id)) ? 1 : 0;
+            || !$flag_type->request_group_id
+            || $self->in_group_id($flag_type->request_group_id)) ? 1 : 0;
 }
 
 sub can_set_flag {
     my ($self, $flag_type) = @_;
 
-    return (!$flag_type->grant_group
-            || $self->in_group_id($flag_type->grant_group->id)) ? 1 : 0;
+    return (!$flag_type->grant_group_id
+            || $self->in_group_id($flag_type->grant_group_id)) ? 1 : 0;
 }
 
+sub direct_group_membership {
+    my $self = shift;
+    my $dbh = Bugzilla->dbh;
+
+    if (!$self->{'direct_group_membership'}) {
+        my $gid = $dbh->selectcol_arrayref('SELECT id
+                                              FROM groups
+                                        INNER JOIN user_group_map
+                                                ON groups.id = user_group_map.group_id
+                                             WHERE user_id = ?
+                                               AND isbless = 0',
+                                             undef, $self->id);
+        $self->{'direct_group_membership'} = Bugzilla::Group->new_from_list($gid);
+    }
+    return $self->{'direct_group_membership'};
+}
+
+
 # visible_groups_inherited returns a reference to a list of all the groups
 # whose members are visible to this user.
 sub visible_groups_inherited {
@@ -912,14 +934,33 @@
     return $self->{'product_resp'} if defined $self->{'product_resp'};
     return [] unless $self->id;
 
-    my $comp_ids = $dbh->selectcol_arrayref('SELECT id FROM components
-                                              WHERE initialowner = ?
-                                                 OR initialqacontact = ?',
-                                              undef, ($self->id, $self->id));
+    my $list = $dbh->selectall_arrayref('SELECT product_id, id
+                                           FROM components
+                                          WHERE initialowner = ?
+                                             OR initialqacontact = ?',
+                                  {Slice => {}}, ($self->id, $self->id));
 
+    unless ($list) {
+        $self->{'product_resp'} = [];
+        return $self->{'product_resp'};
+    }
+
+    my @prod_ids = map {$_->{'product_id'}} @$list;
+    my $products = Bugzilla::Product->new_from_list(\@prod_ids);
     # We cannot |use| it, because Component.pm already |use|s User.pm.
     require Bugzilla::Component;
-    $self->{'product_resp'} = Bugzilla::Component->new_from_list($comp_ids);
+    my @comp_ids = map {$_->{'id'}} @$list;
+    my $components = Bugzilla::Component->new_from_list(\@comp_ids);
+
+    my @prod_list;
+    # @$products is already sorted alphabetically.
+    foreach my $prod (@$products) {
+        # We use @components instead of $prod->components because we only want
+        # components where the user is either the default assignee or QA contact.
+        push(@prod_list, {product    => $prod,
+                          components => [grep {$_->product_id == $prod->id} @$components]});
+    }
+    $self->{'product_resp'} = \@prod_list;
     return $self->{'product_resp'};
 }
 
@@ -1114,9 +1155,6 @@
 
     # prepare default form values
 
-    # What does a "--do_not_change--" field look like (if any)?
-    my $dontchange = $cgi->param('dontchange');
-
     # Fields can be regular expressions matching multiple form fields
     # (f.e. "requestee-(\d+)"), so expand each non-literal field
     # into the list of form fields it matches.
@@ -1175,9 +1213,6 @@
 
         next if !defined $cgi->param($field);
 
-        # Skip it if this is a --do_not_change-- field
-        next if $dontchange && $dontchange eq $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.
@@ -1312,6 +1347,7 @@
     $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();
 
@@ -1464,7 +1500,7 @@
                                   AND relationship = ?
                                   AND event IN (' . join(',', @$events) . ') ' .
                                       $dbh->sql_limit(1),
-                              undef, ($self->{'id'}, $relationship));
+                              undef, ($self->id, $relationship));
 
     return defined($wants_mail) ? 1 : 0;
 }
@@ -1545,9 +1581,7 @@
     my $class = ref($invocant) || $invocant;
     my $dbh = Bugzilla->dbh;
 
-    $dbh->bz_lock_tables('profiles WRITE', 'profiles_activity WRITE',
-        'user_group_map WRITE', 'email_setting WRITE', 'groups READ', 
-        'tokens READ', 'fielddefs READ');
+    $dbh->bz_start_transaction();
 
     my $user = $class->SUPER::create(@_);
 
@@ -1584,7 +1618,7 @@
                    VALUES (?, ?, NOW(), ?, NOW())',
                    undef, ($user->id, $who, $creation_date_fieldid));
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     # Return the newly created user account.
     return $user;
@@ -1993,6 +2027,11 @@
 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
+by group inheritance are excluded from the list.
+
 =item C<visible_groups_inherited>
 
 Returns a list of all groups whose members should be visible to this user.
@@ -2005,7 +2044,7 @@
 
 =item C<visible_groups_as_string>
 
-Returns the result of C<visible_groups_direct> as a string (a comma-separated
+Returns the result of C<visible_groups_inherited> as a string (a comma-separated
 list).
 
 =item C<product_responsibilities>
@@ -2073,6 +2112,11 @@
         disable_mail - If 1, bug-related mail will not be  sent to this user; 
             if 0, mail will be sent depending on the user's  email preferences.
 
+=item C<check>
+
+Takes a username as its only argument. Throws an error if there is no
+user with that username. Returns a C<Bugzilla::User> object.
+
 =item C<is_available_username>
 
 Returns a boolean indicating whether or not the supplied username is
diff --git a/BugsSite/Bugzilla/User/Setting.pm b/BugsSite/Bugzilla/User/Setting.pm
index bdc653b..7a6c72f 100644
--- a/BugsSite/Bugzilla/User/Setting.pm
+++ b/BugsSite/Bugzilla/User/Setting.pm
@@ -99,15 +99,12 @@
         }
     }
     else {
-        ($subclass) = $dbh->selectrow_array(
-            q{SELECT subclass FROM setting WHERE name = ?},
-            undef,
-            $setting_name);
         # If the values were passed in, simply assign them and return.
         $self->{'is_enabled'}    = shift;
         $self->{'default_value'} = shift;
         $self->{'value'}         = shift;
         $self->{'is_default'}    = shift;
+        $subclass                = shift;
     }
     if ($subclass) {
         eval('require ' . $class . '::' . $subclass);
@@ -142,10 +139,12 @@
         $dbh->do('DELETE FROM setting_value WHERE name = ?', undef, $name);
         $dbh->do('DELETE FROM setting WHERE name = ?', undef, $name);
         # Remove obsolete user preferences for this setting.
-        my $list = join(', ', map {$dbh->quote($_)} @$values);
-        $dbh->do("DELETE FROM profile_setting
-                  WHERE setting_name = ? AND setting_value NOT IN ($list)",
-                  undef, $name);
+        if (defined $values && scalar(@$values)) {
+            my $list = join(', ', map {$dbh->quote($_)} @$values);
+            $dbh->do("DELETE FROM profile_setting
+                      WHERE setting_name = ? AND setting_value NOT IN ($list)",
+                      undef, $name);
+        }
     }
     else {
         print get_text('install_setting_new', { name => $name }) . "\n";
@@ -170,7 +169,7 @@
     my $dbh = Bugzilla->dbh;
 
     my $sth = $dbh->prepare(
-           q{SELECT name, default_value, is_enabled, setting_value
+           q{SELECT name, default_value, is_enabled, setting_value, subclass
                FROM setting
           LEFT JOIN profile_setting
                  ON setting.name = profile_setting.setting_name
@@ -178,8 +177,9 @@
            ORDER BY name});
 
     $sth->execute($user_id);
-    while (my ($name, $default_value, $is_enabled, $value) 
-               = $sth->fetchrow_array()) {
+    while (my ($name, $default_value, $is_enabled, $value, $subclass) 
+               = $sth->fetchrow_array()) 
+    {
 
         my $is_default;
 
@@ -192,7 +192,7 @@
 
         $settings->{$name} = new Bugzilla::User::Setting(
            $name, $user_id, $is_enabled, 
-           $default_value, $value, $is_default);
+           $default_value, $value, $is_default, $subclass);
     }
 
     return $settings;
@@ -205,14 +205,17 @@
 
     $user_id ||= 0;
 
-    my $sth = $dbh->prepare(q{SELECT name, default_value, is_enabled
+    my $sth = $dbh->prepare(q{SELECT name, default_value, is_enabled, subclass
                                 FROM setting
                             ORDER BY name});
     $sth->execute();
-    while (my ($name, $default_value, $is_enabled) = $sth->fetchrow_array()) {
+    while (my ($name, $default_value, $is_enabled, $subclass) 
+           = $sth->fetchrow_array()) 
+    {
 
         $default_settings->{$name} = new Bugzilla::User::Setting(
-            $name, $user_id, $is_enabled, $default_value, $default_value, 1);
+            $name, $user_id, $is_enabled, $default_value, $default_value, 1,
+            $subclass);
     }
 
     return $default_settings;
@@ -231,9 +234,8 @@
 sub _setting_exists {
     my ($setting_name) = @_;
     my $dbh = Bugzilla->dbh;
-    my $sth = $dbh->prepare("SELECT name FROM setting WHERE name = ?");
-    $sth->execute($setting_name);
-    return ($sth->rows) ? 1 : 0;
+    return $dbh->selectrow_arrayref(
+        "SELECT 1 FROM setting WHERE name = ?", undef, $setting_name) || 0;
 }
 
 
@@ -335,7 +337,7 @@
 
 =over 4
 
-=item C<add_setting($name, \@values, $default_value)>
+=item C<add_setting($name, \@values, $default_value, $subclass, $force_check)>
 
 Description: Checks for the existence of a setting, and adds it 
              to the database if it does not yet exist.
@@ -344,6 +346,11 @@
              C<$values> - arrayref - contains the new choices
                for the new Setting.
              C<$default_value> - string - the site default
+             C<$subclass> - string - name of the module returning
+               the list of valid values. This means legal values are
+               not stored in the DB.
+             C<$force_check> - boolean - when true, the existing setting
+               and all its values are deleted and replaced by new data.
 
 Returns:     a pointer to a hash of settings
 
diff --git a/BugsSite/Bugzilla/User/Setting/Lang.pm b/BugsSite/Bugzilla/User/Setting/Lang.pm
new file mode 100644
index 0000000..7937270
--- /dev/null
+++ b/BugsSite/Bugzilla/User/Setting/Lang.pm
@@ -0,0 +1,60 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Marc Schumann.
+# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
+# All rights reserved.
+#
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
+
+package Bugzilla::User::Setting::Lang;
+
+use strict;
+
+use base qw(Bugzilla::User::Setting);
+
+use Bugzilla::Constants;
+
+sub legal_values {
+    my ($self) = @_;
+
+    return $self->{'legal_values'} if defined $self->{'legal_values'};
+
+    return $self->{'legal_values'} = Bugzilla->languages;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::User::Setting::Lang - Object for a user preference setting for preferred language
+
+=head1 DESCRIPTION
+
+Lang.pm extends Bugzilla::User::Setting and implements a class specialized for
+setting the preferred language.
+
+=head1 METHODS
+
+=over
+
+=item C<legal_values()>
+
+Description: Returns all legal languages
+Params:      none
+Returns:     A reference to an array containing the names of all legal languages
+
+=back
diff --git a/BugsSite/Bugzilla/User/Setting/Skin.pm b/BugsSite/Bugzilla/User/Setting/Skin.pm
index 0b0adfd..f69f3e6 100644
--- a/BugsSite/Bugzilla/User/Setting/Skin.pm
+++ b/BugsSite/Bugzilla/User/Setting/Skin.pm
@@ -41,6 +41,7 @@
     foreach my $direntry (glob(catdir($dirbase, '*'))) {
         if (-d $direntry) {
             # Stylesheet set
+            next if basename($direntry) =~ /^cvs$/i;
             push(@legal_values, basename($direntry));
         }
         elsif ($direntry =~ /\.css$/) {
diff --git a/BugsSite/Bugzilla/Util.pm b/BugsSite/Bugzilla/Util.pm
index e97bb11..dbeaaa5 100644
--- a/BugsSite/Bugzilla/Util.pm
+++ b/BugsSite/Bugzilla/Util.pm
@@ -33,18 +33,18 @@
 use base qw(Exporter);
 @Bugzilla::Util::EXPORT = qw(is_tainted trick_taint detaint_natural
                              detaint_signed
-                             html_quote url_quote value_quote xml_quote
+                             html_quote url_quote xml_quote
                              css_class_quote html_light_quote url_decode
                              i_am_cgi get_netaddr correct_urlbase
-                             lsearch
+                             lsearch ssl_require_redirect use_attachbase
                              diff_arrays diff_strings
-                             trim wrap_comment find_wrap_point
-                             perform_substs
+                             trim wrap_hard wrap_comment find_wrap_point
                              format_time format_time_decimal validate_date
+                             validate_time
                              file_mod_time is_7bit_clean
                              bz_crypt generate_random_password
                              validate_email_syntax clean_text
-                             get_text);
+                             get_text disable_utf8);
 
 use Bugzilla::Constants;
 
@@ -100,15 +100,15 @@
 
     # List of allowed HTML elements having no attributes.
     my @allow = qw(b strong em i u p br abbr acronym ins del cite code var
-                   dfn samp kbd big small sub sup tt dd dt dl ul li ol);
+                   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;
     };
 
-    # We need utf8_mode() from HTML::Parser 3.40 if running Perl >= 5.8.
-    if ($@ || ($] >= 5.008 && $HTML::Parser::VERSION < 3.40)) { # Package(s) not installed.
+    if ($@) { # Package(s) not installed.
         my $safe = join('|', @allow);
         my $chr = chr(1);
 
@@ -172,12 +172,6 @@
                                            comment => 0,
                                            process => 0);
 
-        # Avoid filling the web server error log with Perl 5.8.x.
-        # In HTML::Scrubber 0.08, the HTML::Parser object is stored in
-        # the "_p" key, but this may change in future versions.
-        if ($] >= 5.008 && ref($scrubber->{_p}) eq 'HTML::Parser') {
-            $scrubber->{_p}->utf8_mode(1);
-        }
         return $scrubber->scrub($text);
     }
 }
@@ -185,6 +179,8 @@
 # This originally came from CGI.pm, by Lincoln D. Stein
 sub url_quote {
     my ($toencode) = (@_);
+    utf8::encode($toencode) # The below regex works only on bytes
+        if Bugzilla->params->{'utf8'} && utf8::is_utf8($toencode);
     $toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg;
     return $toencode;
 }
@@ -196,22 +192,6 @@
     return $toencode;
 }
 
-sub value_quote {
-    my ($var) = (@_);
-    $var =~ s/\&/\&amp;/g;
-    $var =~ s/</\&lt;/g;
-    $var =~ s/>/\&gt;/g;
-    $var =~ s/\"/\&quot;/g;
-    # See bug http://bugzilla.mozilla.org/show_bug.cgi?id=4928 for 
-    # explanation of why Bugzilla does this linebreak substitution. 
-    # This caused form submission problems in mozilla (bug 22983, 32000).
-    $var =~ s/\r\n/\&#013;/g;
-    $var =~ s/\n\r/\&#013;/g;
-    $var =~ s/\r/\&#013;/g;
-    $var =~ s/\n/\&#013;/g;
-    return $var;
-}
-
 sub xml_quote {
     my ($var) = (@_);
     $var =~ s/\&/\&amp;/g;
@@ -219,9 +199,23 @@
     $var =~ s/>/\&gt;/g;
     $var =~ s/\"/\&quot;/g;
     $var =~ s/\'/\&apos;/g;
+    
+    # the following nukes characters disallowed by the XML 1.0
+    # spec, Production 2.2. 1.0 declares that only the following 
+    # are valid:
+    # (#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF])
+    $var =~ s/([\x{0001}-\x{0008}]|
+               [\x{000B}-\x{000C}]|
+               [\x{000E}-\x{001F}]|
+               [\x{D800}-\x{DFFF}]|
+               [\x{FFFE}-\x{FFFF}])//gx;
     return $var;
 }
 
+# This function must not be relied upon to return a valid string to pass to
+# the DB or the user in UTF-8 situations. The only thing you  can rely upon
+# it for is that if you url_decode a string, it will url_encode back to the 
+# exact same thing.
 sub url_decode {
     my ($todecode) = (@_);
     $todecode =~ tr/+/ /;       # pluses become spaces
@@ -235,6 +229,46 @@
     return exists $ENV{'SERVER_SOFTWARE'} ? 1 : 0;
 }
 
+sub ssl_require_redirect {
+    my $method = shift;
+
+    # If currently not in a protected SSL 
+    # connection, determine if a redirection is 
+    # needed based on value in Bugzilla->params->{ssl}.
+    # If we are already in a protected connection or
+    # sslbase is not set then no action is required.
+    if (uc($ENV{'HTTPS'}) ne 'ON' 
+        && $ENV{'SERVER_PORT'} != 443 
+        && Bugzilla->params->{'sslbase'} ne '')
+    {
+        # System is configured to never require SSL 
+        # so no redirection is needed.
+        return 0 
+            if Bugzilla->params->{'ssl'} eq 'never';
+            
+        # System is configured to always require a SSL
+        # connection so we need to redirect.
+        return 1
+            if Bugzilla->params->{'ssl'} eq 'always';
+
+        # System is configured such that if we are inside
+        # of an authenticated session, then we need to make
+        # sure that all of the connections are over SSL. Non
+        # authenticated sessions SSL is not mandatory.
+        # For XMLRPC requests, if the method is User.login
+        # then we always want the connection to be over SSL
+        # if the system is configured for authenticated
+        # sessions since the user's username and password
+        # will be passed before the user is logged in.
+        return 1 
+            if Bugzilla->params->{'ssl'} eq 'authenticated sessions'
+                && (Bugzilla->user->id 
+                    || (defined $method && $method eq 'User.login'));
+    }
+
+    return 0;
+}
+
 sub correct_urlbase {
     my $ssl = Bugzilla->params->{'ssl'};
     return Bugzilla->params->{'urlbase'} if $ssl eq 'never';
@@ -251,6 +285,13 @@
     return Bugzilla->params->{'urlbase'};
 }
 
+sub use_attachbase {
+    my $attachbase = Bugzilla->params->{'attachment_base'};
+    return ($attachbase ne ''
+            && $attachbase ne Bugzilla->params->{'urlbase'}
+            && $attachbase ne Bugzilla->params->{'sslbase'}) ? 1 : 0;
+}
+
 sub lsearch {
     my ($list,$item) = (@_);
     my $count = 0;
@@ -313,11 +354,11 @@
 }
 
 sub wrap_comment {
-    my ($comment) = @_;
+    my ($comment, $cols) = @_;
     my $wrappedcomment = "";
 
     # Use 'local', as recommended by Text::Wrap's perldoc.
-    local $Text::Wrap::columns = COMMENT_COLS;
+    local $Text::Wrap::columns = $cols || COMMENT_COLS;
     # Make words that are longer than COMMENT_COLS not wrap.
     local $Text::Wrap::huge    = 'overflow';
     # Don't mess with tabs.
@@ -329,10 +370,16 @@
         $wrappedcomment .= ($line . "\n");
       }
       else {
+        # Due to a segfault in Text::Tabs::expand() when processing tabs with
+        # Unicode (see http://rt.perl.org/rt3/Public/Bug/Display.html?id=52104),
+        # we have to remove tabs before processing the comment. This restriction
+        # can go away when we require Perl 5.8.9 or newer.
+        $line =~ s/\t/    /g;
         $wrappedcomment .= (wrap('', '', $line) . "\n");
       }
     }
 
+    chomp($wrappedcomment); # Text::Wrap adds an extra newline at the end.
     return $wrappedcomment;
 }
 
@@ -355,10 +402,15 @@
     return $wrappoint;
 }
 
-sub perform_substs {
-    my ($str, $substs) = (@_);
-    $str =~ s/%([a-z]*)%/(defined $substs->{$1} ? $substs->{$1} : Bugzilla->params->{$1})/eg;
-    return $str;
+sub wrap_hard {
+    my ($string, $columns) = @_;
+    local $Text::Wrap::columns = $columns;
+    local $Text::Wrap::unexpand = 0;
+    local $Text::Wrap::huge = 'wrap';
+    
+    my $wrapped = wrap('', '', $string);
+    chomp($wrapped);
+    return $wrapped;
 }
 
 sub format_time {
@@ -440,6 +492,11 @@
         $salt .= $saltchars[rand(64)];
     }
 
+    # Wide characters cause crypt to die
+    if (Bugzilla->params->{'utf8'}) {
+        utf8::encode($password) if utf8::is_utf8($password);
+    }
+    
     # Crypt the password.
     my $cryptedpassword = crypt($password, $salt);
 
@@ -479,6 +536,22 @@
     return $ret ? 1 : 0;
 }
 
+sub validate_time {
+    my ($time) = @_;
+    my $time2;
+
+    # $ts is undefined if the parser fails.
+    my $ts = str2time($time);
+    if ($ts) {
+        $time2 = time2str("%H:%M:%S", $ts);
+        if ($time =~ /^(\d{1,2}):(\d\d)(?::(\d\d))?$/) {
+            $time = sprintf("%02d:%02d:%02d", $1, $2, $3 || 0);
+        }
+    }
+    my $ret = ($ts && $time eq $time2);
+    return $ret ? 1 : 0;
+}
+
 sub is_7bit_clean {
     return $_[0] !~ /[^\x20-\x7E\x0A\x0D]/;
 }
@@ -524,6 +597,12 @@
     return join(".", unpack("CCCC", pack("N", $addr)));
 }
 
+sub disable_utf8 {
+    if (Bugzilla->params->{'utf8'}) {
+        binmode STDOUT, ':bytes'; # Turn off UTF8 encoding.
+    }
+}
+
 1;
 
 __END__
@@ -545,7 +624,6 @@
   # Functions for quoting
   html_quote($var);
   url_quote($var);
-  value_quote($var);
   xml_quote($var);
 
   # Functions for decoding
@@ -566,7 +644,6 @@
   $val = trim(" abc ");
   ($removed, $added) = diff_strings($old, $new);
   $wrapped = wrap_comment($comment);
-  $msg = perform_substs($str, $substs);
 
   # Functions for formatting time
   format_time($time);
@@ -659,11 +736,6 @@
 Quotes characters so that they may be used as CSS class names. Spaces
 are replaced by underscores.
 
-=item C<value_quote($val)>
-
-As well as escaping html like C<html_quote>, this routine converts newlines
-into &#013;, suitable for use in html attributes.
-
 =item C<xml_quote($val)>
 
 This is similar to C<html_quote>, except that ' is escaped to &apos;. This
@@ -700,6 +772,11 @@
 Returns either the C<sslbase> or C<urlbase> parameter, depending on the
 current setting for the C<ssl> parameter.
 
+=item C<use_attachbase()>
+
+Returns true if an alternate host is used to display attachments; false
+otherwise.
+
 =back
 
 =head2 Searching
@@ -752,6 +829,11 @@
 containing removed items, and the second entry is a scalar containing added
 items.
 
+=item C<wrap_hard($string, $size)>
+
+Wraps a string, so that a line is I<never> longer than C<$size>.
+Returns the string, wrapped.
+
 =item C<wrap_comment($comment)>
 
 Takes a bug comment, and wraps it to the appropriate length. The length is
@@ -768,29 +850,15 @@
 $maxpos characters. If none of them is found, just split $string at $maxpos.
 The search starts at $maxpos and goes back to the beginning of the string.
 
-=item C<perform_substs($str, $substs)>
-
-Performs substitutions for sending out email with variables in it,
-or for inserting a parameter into some other string.
-
-Takes a string and a reference to a hash containing substitution 
-variables and their values.
-
-If the hash is not specified, or if we need to substitute something
-that's not in the hash, then we will use parameters to do the 
-substitution instead.
-
-Substitutions are always enclosed with '%' symbols. So they look like:
-%some_variable_name%. If "some_variable_name" is a key in the hash, then
-its value will be placed into the string. If it's not a key in the hash,
-then the value of the parameter called "some_variable_name" will be placed
-into the string.
-
 =item C<is_7bit_clean($str)>
 
 Returns true is the string contains only 7-bit characters (ASCII 32 through 126,
 ASCII 10 (LineFeed) and ASCII 13 (Carrage Return).
 
+=item C<disable_utf8()>
+
+Disable utf8 on STDOUT (and display raw data instead).
+
 =item C<clean_text($str)>
 Returns the parameter "cleaned" by exchanging non-printable characters with spaces.
 Specifically characters (ASCII 0 through 31) and (ASCII 127) will become ASCII 32 (Space).
diff --git a/BugsSite/Bugzilla/Version.pm b/BugsSite/Bugzilla/Version.pm
index ba7631a..a2ef6b0 100644
--- a/BugsSite/Bugzilla/Version.pm
+++ b/BugsSite/Bugzilla/Version.pm
@@ -21,7 +21,7 @@
 
 use base qw(Bugzilla::Object);
 
-use Bugzilla::Install::Requirements qw(vers_cmp);
+use Bugzilla::Install::Util qw(vers_cmp);
 use Bugzilla::Util;
 use Bugzilla::Error;
 
@@ -150,20 +150,6 @@
 #####     Subroutines       ###
 ###############################
 
-sub check_version {
-    my ($product, $version_name) = @_;
-
-    $version_name || ThrowUserError('version_not_specified');
-    my $version = new Bugzilla::Version(
-        { product => $product, name => $version_name });
-    unless ($version) {
-        ThrowUserError('version_not_valid',
-                       {'product' => $product->name,
-                        'version' => $version_name});
-    }
-    return $version;
-}
-
 sub create {
     my ($name, $product) = @_;
     my $dbh = Bugzilla->dbh;
@@ -212,9 +198,6 @@
 
     my $version = $hash_ref->{'version_value'};
 
-    my $version = Bugzilla::Version::check_version($product_obj,
-                                                   'acme_version');
-
     my $version = Bugzilla::Version::create($version_name, $product);
 
 =head1 DESCRIPTION
@@ -266,15 +249,6 @@
 
 =over
 
-=item C<check_version($product, $version_name)>
-
- Description: Checks if the version name exists for the product name.
-
- Params:      $product - A Bugzilla::Product object.
-              $version_name - String with a version name.
-
- Returns:     Bugzilla::Version object.
-
 =item C<create($version_name, $product)>
 
  Description: Create a new version for the given product.
diff --git a/BugsSite/Bugzilla/WebService.pm b/BugsSite/Bugzilla/WebService.pm
index dddc87a..0e42924 100644
--- a/BugsSite/Bugzilla/WebService.pm
+++ b/BugsSite/Bugzilla/WebService.pm
@@ -19,6 +19,7 @@
 
 use strict;
 use Bugzilla::WebService::Constants;
+use Bugzilla::Util;
 use Date::Parse;
 
 sub fail_unimplemented {
@@ -42,16 +43,53 @@
 }
 
 sub handle_login {
-    my ($self, $module, $method) = @_;
-    my $exempt = LOGIN_EXEMPT->{$module};
-    return if $exempt && grep { $_ eq $method } @$exempt;
-    Bugzilla->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;
 }
 
-package Bugzilla::WebService::XMLRPC::Transport::HTTP::CGI;
+# For some methods, we shouldn't call Bugzilla->login before we call them
+use constant LOGIN_EXEMPT => { };
 
+sub login_exempt {
+    my ($class, $method) = @_;
+
+    return $class->LOGIN_EXEMPT->{$method};
+}
+
+1;
+
+package Bugzilla::WebService::XMLRPC::Transport::HTTP::CGI;
 use strict;
-eval 'use base qw(XMLRPC::Transport::HTTP::CGI)';
+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;
@@ -67,6 +105,40 @@
 
 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
@@ -78,6 +150,87 @@
 This is the standard API for external programs that want to interact
 with Bugzilla. It provides various methods in various modules.
 
+Currently the only method of accessing the API is via XML-RPC. The XML-RPC
+standard is described here: L<http://www.xmlrpc.com/spec>
+
+The endpoint for Bugzilla WebServices is the C<xmlrpc.cgi> script in
+your Bugzilla installation. For example, if your Bugzilla is at
+C<bugzilla.yourdomain.com>, then your XML-RPC client would access the
+API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi>
+
+=head1 CALLING METHODS
+
+Methods are called in the normal XML-RPC fashion. Bugzilla does not currently
+implement any extensions to the standard method of XML-RPC method calling.
+
+Methods are grouped into "packages", like C<Bug> for 
+L<Bugzilla::WebService::Bug>. So, for example,
+L<Bugzilla::WebService::Bug/get>, is called as C<Bug.get> in XML-RPC.
+
+=head1 PARAMETERS
+
+In addition to the standard parameter types like C<int>, C<string>, etc.,
+XML-RPC has two data structures, a C<< <struct> >> and an C<< <array> >>.
+
+=head2 Structs
+
+In Perl, we call a C<< <struct> >> a "hash" or a "hashref". You may see
+us refer to it that way in the API documentation.
+
+In example code, you will see the characters C<{> and C<}> used to represent
+the beginning and end of structs.
+
+For example, here's a struct in XML-RPC:
+
+ <struct>
+   <member>
+     <name>fruit</name>
+     <value><string>oranges</string></value>
+   </member>
+   <member>
+     <name>vegetable</name>
+     <value><string>lettuce</string></value>
+   </member>
+ </struct>
+
+In our example code in these API docs, that would look like:
+
+ { fruit => 'oranges', vegetable => 'lettuce' }
+
+=head2 Arrays
+
+In example code, you will see the characters C<[> and C<]> used to
+represent the beginning and end of arrays.
+
+For example, here's an array in XML-RPC:
+
+ <array>
+   <data>
+     <value><i4>1</i4></value>
+     <value><i4>2</i4></value>
+     <value><i4>3</i4></value>
+   </data>
+ </array>
+
+In our example code in these API docs, that would look like:
+
+ [1, 2, 3]
+
+=head2 How Bugzilla WebService Methods Take Parameters
+
+B<All> Bugzilla WebServices functions take their parameters in
+a C<< <struct> >>. Another way of saying this would be: All functions
+take a single argument, a C<< <struct> >> that contains all parameters.
+The names of the parameters listed in the API docs for each function are
+the C<name> element for the struct C<member>s.
+
+=head1 LOGGING IN
+
+You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla 
+user. This issues standard HTTP cookies that you must then use in future
+calls, so your XML-RPC client must be capable of receiving and transmitting
+cookies.
+
 =head1 STABLE, EXPERIMENTAL, and UNSTABLE
 
 Methods are marked B<STABLE> if you can expect their parameters and
diff --git a/BugsSite/Bugzilla/WebService/Bug.pm b/BugsSite/Bugzilla/WebService/Bug.pm
index 14ed2e7..5df7c7d 100644
--- a/BugsSite/Bugzilla/WebService/Bug.pm
+++ b/BugsSite/Bugzilla/WebService/Bug.pm
@@ -14,6 +14,8 @@
 #
 # Contributor(s): Marc Schumann <wurblzap@gmail.com>
 #                 Max Kanat-Alexander <mkanat@bugzilla.org>
+#                 Mads Bondo Dydensborg <mbd@dbc.dk>
+#                 Tsahi Asher <tsahi_75@yahoo.com>
 
 package Bugzilla::WebService::Bug;
 
@@ -25,10 +27,9 @@
 use Bugzilla::Error;
 use Bugzilla::Field;
 use Bugzilla::WebService::Constants;
-use Bugzilla::Util qw(detaint_natural);
 use Bugzilla::Bug;
 use Bugzilla::BugMail;
-use Bugzilla::Constants;
+use Bugzilla::Util qw(trim);
 
 #############
 # Constants #
@@ -56,11 +57,17 @@
 
 use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
 
+######################################################
+# Add aliases here for old method name compatibility #
+######################################################
+
+BEGIN { *get_bugs = \&get }
+
 ###########
 # Methods #
 ###########
 
-sub get_bugs {
+sub get {
     my ($self, $params) = @_;
     my $ids = $params->{ids};
     defined $ids || ThrowCodeError('param_required', { param => 'ids' });
@@ -116,11 +123,6 @@
         $field_values{$field_name} = $params->{$field}; 
     }
 
-    # Make sure all the required fields are in the hash.
-    foreach my $field (Bugzilla::Bug::REQUIRED_CREATE_FIELDS) {
-        $field_values{$field} = undef unless exists $field_values{$field};
-    }
-
     # WebService users can't set the creation date of a bug.
     delete $field_values{'creation_ts'};
 
@@ -135,8 +137,8 @@
     my ($self, $params) = @_;
     my $field = FIELD_MAP->{$params->{field}} || $params->{field};
 
-    my @custom_select =
-        Bugzilla->get_fields({ type => FIELD_TYPE_SINGLE_SELECT });
+    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;
 
@@ -177,6 +179,36 @@
     return { values => \@result };
 }
 
+sub add_comment {
+    my ($self, $params) = @_;
+    
+    #The user must login in order add a comment
+    Bugzilla->login(LOGIN_REQUIRED);
+    
+    # Check parameters
+    defined $params->{id} 
+        || ThrowCodeError('param_required', { param => 'id' });
+    ValidateBugID($params->{id});
+    
+    my $comment = $params->{comment}; 
+    (defined $comment && trim($comment) ne '')
+        || ThrowCodeError('param_required', { param => 'comment' });
+    
+    my $bug = new Bugzilla::Bug($params->{id});
+    
+    Bugzilla->user->can_edit_product($bug->product_id)
+        || ThrowUserError("product_edit_denied", {product => $bug->product});
+        
+    # Append comment
+    $bug->add_comment($comment, { isprivate => $params->{private},
+                                  work_time => $params->{work_time} });
+    $bug->update();
+    
+    # Send mail.
+    Bugzilla::BugMail::Send($bug->bug_id, { changer => Bugzilla->user->login });
+    return undef;
+}
+
 1;
 
 __END__
@@ -193,14 +225,16 @@
 
 =head1 METHODS
 
-See L<Bugzilla::WebService> for a description of B<STABLE>, B<UNSTABLE>,
-and B<EXPERIMENTAL>.
+See L<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
 
 =head2 Utility Functions
 
 =over
 
-=item C<legal_values> B<EXPERIMENTAL>
+=item C<legal_values> 
+
+B<EXPERIMENTAL>
 
 =over
 
@@ -245,11 +279,13 @@
 
 =back
 
-=head2 Bug Creation and Modification
+=head2 Bug Information
 
 =over
 
-=item C<get_bugs> B<EXPERIMENTAL>
+=item C<get> 
+
+B<EXPERIMENTAL>
 
 =over
 
@@ -257,6 +293,8 @@
 
 Gets information about particular bugs in the database.
 
+Note: Can also be called as "get_bugs" for compatibilty with Bugzilla 3.0 API.
+
 =item B<Params>
 
 =over
@@ -333,8 +371,13 @@
 
 =back
 
+=back
 
 
+=head2 Bug Creation and Modification
+
+=over
+
 =item C<create> B<EXPERIMENTAL>
 
 =over
@@ -431,6 +474,10 @@
 
 =over
 
+=item 51 (Invalid Object)
+
+The component you specified is not valid for this Product.
+
 =item 103 (Invalid Alias)
 
 The alias you specified is invalid for some reason. See the error message
@@ -438,13 +485,12 @@
 
 =item 104 (Invalid Field)
 
-One of the drop-down fields has an invalid value. The error message will
-have more detail.
+One of the drop-down fields has an invalid value, or a value entered in a
+text field is too long. The error message will have more detail.
 
 =item 105 (Invalid Component)
 
-Either you didn't specify a component, or the component you specified was
-invalid.
+You didn't specify a component.
 
 =item 106 (Invalid Product)
 
@@ -462,6 +508,75 @@
 
 =back
 
+=item B<History>
+
+=over
+
+=item Before B<3.0.4>, parameters marked as B<Defaulted> were actually
+B<Required>, due to a bug in Bugzilla.
+
+=back
+
+=back
+
+=item C<add_comment> 
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+This allows you to add a comment to a bug in Bugzilla.
+
+=item B<Params>
+
+=over
+
+=item C<id> (int) B<Required> - The id or alias of the bug to append a 
+comment to.
+
+=item C<comment> (string) B<Required> - The comment to append to the bug.
+If this is empty or all whitespace, an error will be thrown saying that
+you did not set the C<comment> parameter.
+
+=item C<private> (boolean) - If set to true, the comment is private, otherwise
+it is assumed to be public.
+
+=item C<work_time> (double) - Adds this many hours to the "Hours Worked"
+on the bug. If you are not in the time tracking group, this value will
+be ignored.
+
+
+=back
+
+=item B<Errors>
+
+=over
+
+=item 100 (Invalid Bug Alias) 
+
+If you specified an alias and either: (a) the Bugzilla you're querying
+doesn't support aliases or (b) there is no bug with that alias.
+
+=item 101 (Invalid Bug ID)
+
+The id you specified doesn't exist in the database.
+
+=item 108 (Bug Edit Denied)
+
+You did not have the necessary rights to edit the bug.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.2>.
+
+=back
+
 =back
 
 
diff --git a/BugsSite/Bugzilla/WebService/Bugzilla.pm b/BugsSite/Bugzilla/WebService/Bugzilla.pm
index 1eeeebd..53e0d7d 100644
--- a/BugsSite/Bugzilla/WebService/Bugzilla.pm
+++ b/BugsSite/Bugzilla/WebService/Bugzilla.pm
@@ -21,14 +21,33 @@
 use strict;
 use base qw(Bugzilla::WebService);
 use Bugzilla::Constants;
+use Bugzilla::Hook;
 import SOAP::Data qw(type);
 
 use Time::Zone;
 
+# Basic info that is needed before logins
+use constant LOGIN_EXEMPT => {
+    timezone => 1,
+    version => 1,
+};
+
 sub version {
     return { version => type('string')->value(BUGZILLA_VERSION) };
 }
 
+sub extensions {
+    my $extensions = Bugzilla::Hook::enabled_plugins();
+    foreach my $name (keys %$extensions) {
+        my $info = $extensions->{$name};
+        foreach my $data (keys %$info)
+        {
+            $extensions->{$name}->{$data} = type('string')->value($info->{$data});
+        }
+    }
+    return { extensions => $extensions };
+}
+
 sub timezone {
     my $offset = tz_offset();
     $offset = (($offset / 60) / 60) * 100;
@@ -50,12 +69,14 @@
 
 =head1 METHODS
 
-See L<Bugzilla::WebService> for a description of what B<STABLE>, B<UNSTABLE>,
-and B<EXPERIMENTAL> mean.
+See L<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
 
 =over
 
-=item C<version> B<EXPERIMENTAL>
+=item C<version>
+
+B<STABLE>
 
 =over
 
@@ -74,7 +95,39 @@
 
 =back
 
-=item C<timezone> B<EXPERIMENTAL>
+=item C<extensions>
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Gets information about the extensions that are currently installed and enabled
+in this Bugzilla.
+
+=item B<Params> (none)
+
+=item B<Returns>
+
+A hash with a single item, C<extesions>. This points to a hash. I<That> hash
+contains the names of extensions as keys, and information about the extension
+as values. One of the values that must be returned is the 'version' of the
+extension
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.2>.
+
+=back
+
+=back
+
+=item C<timezone>
+
+B<STABLE>
 
 =over
 
@@ -88,7 +141,7 @@
 
 =item B<Returns>
 
-A hash with a single item, C<timezone>, that is the timezone as a
+A hash with a single item, C<timezone>, that is the timezone offset as a
 string in (+/-)XXXX (RFC 2822) format.
 
 =back
diff --git a/BugsSite/Bugzilla/WebService/Constants.pm b/BugsSite/Bugzilla/WebService/Constants.pm
index 0b73114..98597a9 100644
--- a/BugsSite/Bugzilla/WebService/Constants.pm
+++ b/BugsSite/Bugzilla/WebService/Constants.pm
@@ -27,8 +27,6 @@
 
     ERROR_AUTH_NODATA
     ERROR_UNIMPLEMENTED
-
-    LOGIN_EXEMPT
 );
 
 # This maps the error names in global/*-error.html.tmpl to numbers.
@@ -50,23 +48,26 @@
 # comment that it was retired. Also, if an error changes its name, you'll
 # have to fix it here.
 use constant WS_ERROR_CODE => {
+    # Generic Bugzilla::Object errors are 50-99.
+    object_name_not_specified   => 50,
+    param_required              => 50,
+    object_does_not_exist       => 51,
     # Bug errors usually occupy the 100-200 range.
-    invalid_bug_id_or_alias     => 100,
-    invalid_bug_id_non_existent => 101,
+    improper_bug_id_field_value => 100,
+    bug_id_does_not_exist       => 101,
     bug_access_denied           => 102,
-    invalid_field_name          => 108,
+    bug_access_query            => 102,
     # These all mean "invalid alias"
-    alias_not_defined        => 103,
     alias_too_long           => 103,
     alias_in_use             => 103,
     alias_is_numeric         => 103,
     alias_has_comma_or_space => 103,
     # Misc. bug field errors
     illegal_field => 104,
+    freetext_too_long => 104,
     # Component errors
     require_component       => 105,
     component_name_too_long => 105,
-    component_not_valid     => 105,
     # Invalid Product
     no_products         => 106,
     entry_access_denied => 106,
@@ -74,6 +75,10 @@
     product_disabled    => 106,
     # Invalid Summary
     require_summary => 107,
+    # Invalid field name
+    invalid_field_name => 108,
+    # Not authorized to edit the bug
+    product_edit_denied => 109,
 
     # Authentication errors are usually 300-400.
     invalid_username_or_password => 300,
@@ -85,6 +90,7 @@
     account_exists        => 500,
     illegal_email_address => 501,
     account_creation_disabled   => 501,
+    account_creation_restricted => 501,
     password_too_short    => 502,
     password_too_long     => 503,
     invalid_username      => 504,
@@ -101,13 +107,4 @@
 use constant ERROR_UNIMPLEMENTED => 910;
 use constant ERROR_GENERAL       => 999;
 
-# For some methods, we shouldn't call Bugzilla->login before we call them.
-# This is a hash--package names pointing to an arrayref of method names.
-use constant LOGIN_EXEMPT => {
-    # Callers may have to know the Bugzilla version before logging in,
-    # even on a requirelogin installation.
-    Bugzilla => ['version', 'timezone'],
-    User     => ['offer_account_by_email', 'login'],
-};
-
 1;
diff --git a/BugsSite/Bugzilla/WebService/Product.pm b/BugsSite/Bugzilla/WebService/Product.pm
index fb11a00..cd86296 100644
--- a/BugsSite/Bugzilla/WebService/Product.pm
+++ b/BugsSite/Bugzilla/WebService/Product.pm
@@ -23,6 +23,12 @@
 use Bugzilla::User;
 import SOAP::Data qw(type);
 
+##################################################
+# Add aliases here for method name compatibility #
+##################################################
+
+BEGIN { *get_products = \&get }
+
 # Get the ids of the products the user can search
 sub get_selectable_products {
     return {ids => [map {$_->id} @{Bugzilla->user->get_selectable_products}]}; 
@@ -39,7 +45,7 @@
 }
 
 # Get a list of actual products, based on list of ids
-sub get_products {
+sub get {
     my ($self, $params) = @_;
     
     # Only products that are in the users accessible products, 
@@ -81,14 +87,16 @@
 
 =head1 METHODS
 
-See L<Bugzilla::WebService> for a description of what B<STABLE>, B<UNSTABLE>,
-and B<EXPERIMENTAL> mean, and for more information about error codes.
+See L<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
 
 =head2 List Products
 
 =over
 
-=item C<get_selectable_products> B<UNSTABLE>
+=item C<get_selectable_products> 
+
+B<EXPERIMENTAL>
 
 =over
 
@@ -107,7 +115,9 @@
 
 =back
 
-=item C<get_enterable_products> B<UNSTABLE>
+=item C<get_enterable_products> 
+
+B<EXPERIMENTAL>
 
 =over
 
@@ -127,7 +137,9 @@
 
 =back
 
-=item C<get_accessible_products> B<UNSTABLE>
+=item C<get_accessible_products> 
+
+B<UNSTABLE>
 
 =over
 
@@ -147,7 +159,9 @@
 
 =back
 
-=item C<get_products> B<UNSTABLE>
+=item C<get> 
+
+B<EXPERIMENTAL>
 
 =over
 
@@ -155,6 +169,8 @@
 
 Returns a list of information about the products passed to it.
 
+Note: Can also be called as "get_products" for compatibilty with Bugzilla 3.0 API.
+
 =item B<Params>
 
 A hash containing one item, C<ids>, that is an array of product ids. 
diff --git a/BugsSite/Bugzilla/WebService/User.pm b/BugsSite/Bugzilla/WebService/User.pm
index 12ca0a4..5446d80 100644
--- a/BugsSite/Bugzilla/WebService/User.pm
+++ b/BugsSite/Bugzilla/WebService/User.pm
@@ -30,6 +30,12 @@
 use Bugzilla::Util qw(trim);
 use Bugzilla::Token;
 
+# Don't need auth to login
+use constant LOGIN_EXEMPT => {
+    login => 1,
+    offer_account_by_email => 1,
+};
+
 ##############
 # User Login #
 ##############
@@ -37,7 +43,13 @@
 sub login {
     my ($self, $params) = @_;
     my $remember = $params->{remember};
-    
+
+    # Username and password params are required 
+    foreach my $param ("login", "password") {
+        defined $params->{$param} 
+            || ThrowCodeError('param_required', { param => $param });
+    }
+
     # Convert $remember from a boolean 0/1 value to a CGI-compatible one.
     if (defined($remember)) {
         $remember = $remember? 'on': '';
@@ -75,9 +87,12 @@
         || ThrowCodeError('param_required', { param => 'email' });
 
     my $createexp = Bugzilla->params->{'createemailregexp'};
-    if (!$createexp || $email !~ /$createexp/) {
+    if (!$createexp) {
         ThrowUserError("account_creation_disabled");
     }
+    elsif ($email !~ /$createexp/) {
+        ThrowUserError("account_creation_restricted");
+    }
 
     $email = Bugzilla::User->check_login_name_for_creation($email);
 
@@ -125,14 +140,16 @@
 
 =head1 METHODS
 
-See L<Bugzilla::WebService> for a description of what B<STABLE>, B<UNSTABLE>,
-and B<EXPERIMENTAL> mean, and for more information about error codes.
+See L<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
 
 =head2 Logging In and Out
 
 =over
 
-=item C<login> B<EXPERIMENTAL>
+=item C<login> 
+
+B<STABLE>
 
 =over
 
@@ -180,11 +197,17 @@
 The account has been disabled.  A reason may be specified with the
 error.
 
+=item 50 (Param Required)
+
+A login or password parameter was not provided.
+
 =back
 
 =back
 
-=item C<logout> B<EXPERIMENTAL>
+=item C<logout> 
+
+B<STABLE>
 
 =over
 
@@ -206,7 +229,9 @@
 
 =over
 
-=item C<offer_account_by_email> B<EXPERIMENTAL>
+=item C<offer_account_by_email> 
+
+B<STABLE>
 
 =over
 
@@ -245,7 +270,9 @@
 
 =back
 
-=item C<create> B<EXPERIMENTAL>
+=item C<create> 
+
+B<EXPERIMENTAL>
 
 =over
 
@@ -256,6 +283,9 @@
 possible, because that makes sure that the email address specified can
 actually receive an email. This function does not check that.
 
+You must be logged in and have the C<editusers> privilege in order to
+call this function.
+
 =item B<Params>
 
 =over
@@ -298,6 +328,14 @@
 
 =back
 
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=back
+
 =back
 
 =back
diff --git a/BugsSite/ChangeLog b/BugsSite/ChangeLog
index 3afcdfb..2050f90 100644
--- a/BugsSite/ChangeLog
+++ b/BugsSite/ChangeLog
@@ -1,5 +1,11 @@
 2009-07-02  David Kilzer  <ddkilzer@webkit.org>
 
+        Merged BugsSite to Bugzilla-3.2.3
+
+        Updated to the latest-and-greatest stable version.
+
+2009-07-02  David Kilzer  <ddkilzer@webkit.org>
+
         Merged BugsSite to Bugzilla-3.0.3
 
         Nothing to see here.  Move along.
diff --git a/BugsSite/admin.cgi b/BugsSite/admin.cgi
new file mode 100755
index 0000000..83cc55d
--- /dev/null
+++ b/BugsSite/admin.cgi
@@ -0,0 +1,49 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (C) 2007
+# Frédéric Buclin. All Rights Reserved.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+print $cgi->header();
+
+$user->in_group('admin')
+  || $user->in_group('tweakparams')
+  || $user->in_group('editusers')
+  || $user->can_bless
+  || (Bugzilla->params->{'useclassification'} && $user->in_group('editclassifications'))
+  || $user->in_group('editcomponents')
+  || scalar(@{$user->get_products_by_permission('editcomponents')})
+  || $user->in_group('creategroups')
+  || $user->in_group('editkeywords')
+  || $user->in_group('bz_canusewhines')
+  || ThrowUserError('auth_failure', {action => 'access', object => 'administrative_pages'});
+
+$template->process('admin/admin.html.tmpl')
+  || ThrowTemplateError($template->error());
diff --git a/BugsSite/attachment.cgi b/BugsSite/attachment.cgi
index 2771fe4..6be0bf8 100755
--- a/BugsSite/attachment.cgi
+++ b/BugsSite/attachment.cgi
@@ -27,6 +27,7 @@
 #                 Greg Hendricks <ghendricks@novell.com>
 #                 Frédéric Buclin <LpSolit@gmail.com>
 #                 Marc Schumann <wurblzap@gmail.com>
+#                 Byron Jones <bugzilla@glob.com.au>
 
 ################################################################################
 # Script Initialization
@@ -35,7 +36,7 @@
 # Make it harder for us to do dangerous things in Perl.
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -49,13 +50,12 @@
 use Bugzilla::Attachment;
 use Bugzilla::Attachment::PatchReader;
 use Bugzilla::Token;
+use Bugzilla::Keyword;
 
 #if WEBKIT_CHANGES
 use IPC::Open2;
 #endif // WEBKIT_CHANGES
 
-Bugzilla->login();
-
 # For most scripts we don't make $cgi and $template global variables. But
 # when preparing Bugzilla for mod_perl, this script used these
 # variables in so many subroutines that it was easier to just
@@ -76,7 +76,27 @@
 # Determine whether to use the action specified by the user or the default.
 my $action = $cgi->param('action') || 'view';
 
-if ($action eq "view")  
+# 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/) {
+        $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")
 {
     view();
 }
@@ -144,26 +164,22 @@
 # Validates an attachment ID. Optionally takes a parameter of a form
 # variable name that contains the ID to be validated. If not specified,
 # uses 'id'.
-# 
+# If the second parameter is true, the attachment ID will be validated,
+# however the current user's access to the attachment will not be checked.
 # Will throw an error if 1) attachment ID is not a valid number,
 # 2) attachment does not exist, or 3) user isn't allowed to access the
 # attachment.
 #
-# Returns a list, where the first item is the validated, detainted
-# attachment id, and the 2nd item is the bug id corresponding to the
-# attachment.
-# 
-sub validateID
-{
-    my $param = @_ ? $_[0] : 'id';
-    my $dbh = Bugzilla->dbh;
-    my $user = Bugzilla->user;
+# Returns an attachment object.
+
+sub validateID {
+    my($param, $dont_validate_access) = @_;
+    $param ||= 'id';
 
     # If we're not doing interdiffs, check if id wasn't specified and
     # prompt them with a page that allows them to choose an attachment.
     # Happens when calling plain attachment.cgi from the urlbar directly
     if ($param eq 'id' && !$cgi->param('id')) {
-
         print $cgi->header();
         $template->process("attachment/choose.html.tmpl", $vars) ||
             ThrowTemplateError($template->error());
@@ -179,22 +195,36 @@
      || ThrowUserError("invalid_attach_id", { attach_id => $cgi->param($param) });
   
     # Make sure the attachment exists in the database.
-    my ($bugid, $isprivate, $submitter_id) = $dbh->selectrow_array(
-                                    "SELECT bug_id, isprivate, submitter_id
-                                     FROM attachments 
-                                     WHERE attach_id = ?",
-                                     undef, $attach_id);
-    ThrowUserError("invalid_attach_id", { attach_id => $attach_id }) 
-        unless $bugid;
+    my $attachment = Bugzilla::Attachment->get($attach_id)
+      || ThrowUserError("invalid_attach_id", { attach_id => $attach_id });
+
+    return $attachment if ($dont_validate_access || check_can_access($attachment));
+}
+
+# Make sure the current user has access to the specified attachment.
+sub check_can_access {
+    my $attachment = shift;
+    my $user = Bugzilla->user;
 
     # Make sure the user is authorized to access this attachment's bug.
-    ValidateBugID($bugid);
-    if ($isprivate && $user->id != $submitter_id && !$user->is_insider) {
+    ValidateBugID($attachment->bug_id);
+    if ($attachment->isprivate && $user->id != $attachment->attacher->id && !$user->is_insider) {
         ThrowUserError('auth_failure', {action => 'access',
                                         object => 'attachment'});
     }
+    return 1;
+}
 
-    return ($attach_id, $bugid);
+# Determines if the attachment is public -- that is, if users who are
+# not logged in have access to the attachment
+sub attachmentIsPublic {
+    my $attachment = shift;
+
+    return 0 if Bugzilla->params->{'requirelogin'};
+    return 0 if $attachment->isprivate;
+
+    my $anon_user = new Bugzilla::User;
+    return $anon_user->can_see_bug($attachment->bug_id);
 }
 
 # Validates format of a diff/interdiff. Takes a list as an parameter, which
@@ -225,22 +255,6 @@
   return $context;
 }
 
-sub validateCanChangeAttachment 
-{
-    my ($attachid) = @_;
-    my $dbh = Bugzilla->dbh;
-    my ($productid) = $dbh->selectrow_array(
-            "SELECT product_id
-             FROM attachments
-             INNER JOIN bugs
-             ON bugs.bug_id = attachments.bug_id
-             WHERE attach_id = ?", undef, $attachid);
-
-    Bugzilla->user->can_edit_product($productid)
-      || ThrowUserError("illegal_attachment_edit",
-                        { attach_id => $attachid });
-}
-
 sub validateCanChangeBug
 {
     my ($bugid) = @_;
@@ -255,71 +269,69 @@
                         { bug_id => $bugid });
 }
 
-sub validateIsObsolete
-{
-    # Set the isobsolete 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('isobsolete', $cgi->param('isobsolete') ? 1 : 0);
-}
-
-sub validatePrivate
-{
-    # Set the isprivate 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('isprivate', $cgi->param('isprivate') ? 1 : 0);
-}
-
-# Returns 1 if the parameter is a content-type viewable in this browser
-# Note that we don't use $cgi->Accept()'s ability to check if a content-type
-# matches, because this will return a value even if it's matched by the generic
-# */* which most browsers add to the end of their Accept: headers.
-sub isViewable
-{
-  my $contenttype = trim(shift);
-    
-  # We assume we can view all text and image types  
-  if ($contenttype =~ /^(text|image)\//) {
-    return 1;
-  }
-  
-  # Mozilla can view XUL. Note the trailing slash on the Gecko detection to
-  # avoid sending XUL to Safari.
-  if (($contenttype =~ /^application\/vnd\.mozilla\./) &&
-      ($cgi->user_agent() =~ /Gecko\//))
-  {
-    return 1;
-  }
-
-  # If it's not one of the above types, we check the Accept: header for any 
-  # types mentioned explicitly.
-  my $accept = join(",", $cgi->Accept());
-  
-  if ($accept =~ /^(.*,)?\Q$contenttype\E(,.*)?$/) {
-    return 1;
-  }
-  
-  return 0;
-}
-
 ################################################################################
 # Functions
 ################################################################################
 
 # Display an attachment.
-sub view
-{
-    # Retrieve and validate parameters
-    my ($attach_id) = validateID();
-    my $dbh = Bugzilla->dbh;
-    
-    # Retrieve the attachment content and its content type from the database.
-    my ($contenttype, $filename, $thedata) = $dbh->selectrow_array(
-            "SELECT mimetype, filename, thedata FROM attachments " .
-            "INNER JOIN attach_data ON id = attach_id " .
-            "WHERE attach_id = ?", undef, $attach_id);
-   
+sub view {
+    my $attachment;
+
+    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;
+
+        # 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 {
+            # No need to validate the token for public attachments. We cannot request
+            # credentials as we are on the alternate host.
+            if (!attachmentIsPublic($attachment)) {
+                my $token = $cgi->param('t');
+                my ($userid, undef, $token_attach_id) = Bugzilla::Token::GetTokenData($token);
+                unless ($userid
+                        && detaint_natural($token_attach_id)
+                        && ($token_attach_id == $attachment->id))
+                {
+                    # Not a valid token.
+                    print $cgi->redirect('-location' => correct_urlbase() . $path);
+                    exit;
+                }
+                # Change current user without creating cookies.
+                Bugzilla->set_user(new Bugzilla::User($userid));
+                # Tokens are single use only, delete it.
+                delete_token($token);
+            }
+        }
+    } else {
+        # No alternate host is used. Request credentials if required.
+        Bugzilla->login();
+        $attachment = validateID();
+    }
+
+    # At this point, Bugzilla->login has been called if it had to.
+    my $contenttype = $attachment->contenttype;
+    my $filename = $attachment->filename;
+
     # Bug 111522: allow overriding content-type manually in the posted form
     # params.
     if (defined $cgi->param('content_type'))
@@ -331,57 +343,29 @@
     }
 
     # Return the appropriate HTTP response headers.
+    $attachment->datasize || ThrowUserError("attachment_removed");
+
     $filename =~ s/^.*[\/\\]//;
-    my $filesize = length($thedata);
-    # A zero length attachment in the database means the attachment is 
-    # stored in a local file
-    if ($filesize == 0)
-    {
-        my $hash = ($attach_id % 100) + 100;
-        $hash =~ s/.*(\d\d)$/group.$1/;
-        if (open(AH, bz_locations()->{'attachdir'} . "/$hash/attachment.$attach_id")) {
-            binmode AH;
-            $filesize = (stat(AH))[7];
-        }
-    }
-    if ($filesize == 0)
-    {
-        ThrowUserError("attachment_removed");
-    }
-
-
     # escape quotes and backslashes in the filename, per RFCs 2045/822
     $filename =~ s/\\/\\\\/g; # escape backslashes
     $filename =~ s/"/\\"/g; # escape quotes
 
+    my $disposition = Bugzilla->params->{'allow_attachment_display'} ? 'inline' : 'attachment';
+
     print $cgi->header(-type=>"$contenttype; name=\"$filename\"",
-                       -content_disposition=> "inline; filename=\"$filename\"",
-                       -content_length => $filesize);
-
-    if ($thedata) {
-        print $thedata;
-    } else {
-        while (<AH>) {
-            print $_;
-        }
-        close(AH);
-    }
-
+                       -content_disposition=> "$disposition; filename=\"$filename\"",
+                       -content_length => $attachment->datasize);
+    disable_utf8();
+    print $attachment->data;
 }
 
 sub interdiff {
     # Retrieve and validate parameters
-    my ($old_id) = validateID('oldid');
-    my ($new_id) = validateID('newid');
+    my $old_attachment = validateID('oldid');
+    my $new_attachment = validateID('newid');
     my $format = validateFormat('html', 'raw');
     my $context = validateContext();
 
-    # XXX - validateID should be replaced by Attachment::check_attachment()
-    # and should return an attachment object. This would save us a lot of
-    # trouble.
-    my $old_attachment = Bugzilla::Attachment->get($old_id);
-    my $new_attachment = Bugzilla::Attachment->get($new_id);
-
     Bugzilla::Attachment::PatchReader::process_interdiff(
         $old_attachment, $new_attachment, $format, $context);
 }
@@ -389,48 +373,37 @@
 #if WEBKIT_CHANGES
 sub prettyPatch
 {
-  # Retrieve and validate parameters
-  my ($attach_id) = validateID();
-  my $dbh = Bugzilla->dbh;
-  my $format = validateFormat('html', 'raw');
-  my $context = validateContext();
+    # Retrieve and validate parameters
+    my $attachment = validateID();
+    my $format = validateFormat('html', 'raw');
+    my $context = validateContext();
 
-  # Get patch data
-  my ($bugid, $description, $ispatch, $thedata) = $dbh->selectrow_array(
-          "SELECT bug_id, description, ispatch, thedata " .
-            "FROM attachments " .
-      "INNER JOIN attach_data ON id = attach_id " .
-           "WHERE attach_id = ?", undef, $attach_id);
+    # If it is not a patch, view normally.
+    if (!$attachment->ispatch) {
+      view();
+      return;
+    }
 
-  # If it is not a patch, view normally
-  if (!$ispatch)
-  {
-    view();
-    return;
-  }
+    use vars qw($cgi);
+    print $cgi->header(-type => 'text/html',
+                       -expires => '+3M');
 
-  use vars qw($cgi);
-  print $cgi->header(-type => 'text/html',
-                     -expires => '+3M');
-
-  open2(\*OUT, \*IN, "/usr/bin/ruby", "-I", "PrettyPatch", "PrettyPatch/prettify.rb", "--html-exceptions");
-  print IN $thedata . "\n";
-  close(IN);
-  while (<OUT>) {
-      print;
-  }
-  close(OUT);
+    open2(\*OUT, \*IN, "/usr/bin/ruby", "-I", "PrettyPatch", "PrettyPatch/prettify.rb", "--html-exceptions");
+    print IN $attachment->data . "\n";
+    close(IN);
+    while (<OUT>) {
+        print;
+    }
+    close(OUT);
 }
 #endif // WEBKIT_CHANGES
 
 sub diff {
     # Retrieve and validate parameters
-    my ($attach_id) = validateID();
+    my $attachment = validateID();
     my $format = validateFormat('html', 'raw');
     my $context = validateContext();
 
-    my $attachment = Bugzilla::Attachment->get($attach_id);
-
     # If it is not a patch, view normally.
     if (!$attachment->ispatch) {
         view();
@@ -450,10 +423,6 @@
 
     my $attachments = Bugzilla::Attachment->get_attachments_by_bug($bugid);
 
-    foreach my $a (@$attachments) {
-        $a->{'isviewable'} = isViewable($a->contenttype);
-    }
-
     # Define the variables and functions that will be passed to the UI template.
     $vars->{'bug'} = $bug;
     $vars->{'attachments'} = $attachments;
@@ -466,8 +435,7 @@
 }
 
 # Display a form for entering a new attachment.
-sub enter
-{
+sub enter {
   # Retrieve and validate parameters
   my $bugid = $cgi->param('bugid');
   ValidateBugID($bugid);
@@ -482,22 +450,20 @@
   if (!$user->in_group('editbugs', $bug->product_id)) {
       $canEdit = "AND submitter_id = " . $user->id;
   }
-  my $attachments = $dbh->selectall_arrayref(
-          "SELECT attach_id AS id, description, isprivate
-           FROM attachments
-           WHERE bug_id = ? 
-           AND isobsolete = 0 $canEdit
-           ORDER BY attach_id",{'Slice' =>{}}, $bugid);
+  my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
+                                             WHERE bug_id = ? AND isobsolete = 0 $canEdit
+                                             ORDER BY attach_id", undef, $bugid);
 
   # Define the variables and functions that will be passed to the UI template.
   $vars->{'bug'} = $bug;
-  $vars->{'attachments'} = $attachments;
+  $vars->{'attachments'} = Bugzilla::Attachment->get_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:');
 
   print $cgi->header();
 
@@ -507,84 +473,93 @@
 }
 
 # Insert a new attachment into the database.
-sub insert
-{
+sub insert {
     my $dbh = Bugzilla->dbh;
     my $user = Bugzilla->user;
 
+    $dbh->bz_start_transaction;
+
     # Retrieve and validate parameters
     my $bugid = $cgi->param('bugid');
     ValidateBugID($bugid);
     validateCanChangeBug($bugid);
-    ValidateComment(scalar $cgi->param('comment'));
-    my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()"); 
+    my ($timestamp) = Bugzilla->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;
+        }
+    }
 
     my $bug = new Bugzilla::Bug($bugid);
-    my $attachid =
+    my $attachment =
         Bugzilla::Attachment->insert_attachment_for_bug(THROW_ERROR, $bug, $user,
-                                                        $timestamp, \$vars);
+                                                        $timestamp, $vars);
 
-  # Insert a comment about the new attachment into the database.
-  my $comment = "Created an attachment (id=$attachid)\n" .
-                $cgi->param('description') . "\n";
-  $comment .= ("\n" . $cgi->param('comment')) if defined $cgi->param('comment');
+    # 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');
 
-  my $isprivate = $cgi->param('isprivate') ? 1 : 0;
-  AppendComment($bugid, $user->id, $comment, $isprivate, $timestamp);
+    $bug->add_comment($comment, { isprivate => $attachment->isprivate });
 
   # Assign the bug to the user, if they are allowed to take it
   my $owner = "";
-  
   if ($cgi->param('takebug') && $user->in_group('editbugs', $bug->product_id)) {
-      
-      my @fields = ("assigned_to", "bug_status", "resolution", "everconfirmed",
-                    "login_name");
-      
-      # Get the old values, for the bugs_activity table
-      my @oldvalues = $dbh->selectrow_array(
-              "SELECT " . join(", ", @fields) . " " .
-              "FROM bugs " .
-              "INNER JOIN profiles " .
-              "ON profiles.userid = bugs.assigned_to " .
-              "WHERE bugs.bug_id = ?", undef, $bugid);
-      
-      my @newvalues = ($user->id, "ASSIGNED", "", 1, $user->login);
-      
+      # When taking a bug, we have to follow the workflow.
+      my $bug_status = $cgi->param('bug_status') || '';
+      ($bug_status) = grep {$_->name eq $bug_status} @{$bug->status->can_change_to};
+
+      if ($bug_status && $bug_status->is_open
+          && ($bug_status->name ne 'UNCONFIRMED' || $bug->product_obj->votes_to_confirm))
+      {
+          $bug->set_status($bug_status->name);
+          $bug->clear_resolution();
+      }
       # Make sure the person we are taking the bug from gets mail.
-      $owner = $oldvalues[4];  
+      $owner = $bug->assigned_to->login;
+      $bug->set_assigned_to($user);
+  }
+  $bug->update($timestamp);
 
-      # Update the bug record. Note that this doesn't involve login_name.
-      $dbh->do('UPDATE bugs SET delta_ts = ?, ' .
-               join(', ', map("$fields[$_] = ?", (0..3))) . ' WHERE bug_id = ?',
-               undef, ($timestamp, map($newvalues[$_], (0..3)) , $bugid));
+  if ($token) {
+      trick_taint($token);
+      $dbh->do('UPDATE tokens SET eventdata = ? WHERE token = ?', undef,
+               ("createattachment:" . $attachment->id, $token));
+  }
 
-      # If the bug was a dupe, we have to remove its entry from the
-      # 'duplicates' table.
-      $dbh->do('DELETE FROM duplicates WHERE dupe = ?', undef, $bugid);
-
-      # We store email addresses in the bugs_activity table rather than IDs.
-      $oldvalues[0] = $oldvalues[4];
-      $newvalues[0] = $newvalues[4];
-
-      for (my $i = 0; $i < 4; $i++) {
-          if ($oldvalues[$i] ne $newvalues[$i]) {
-              LogActivityEntry($bugid, $fields[$i], $oldvalues[$i],
-                               $newvalues[$i], $user->id, $timestamp);
-          }
-      }      
-  }   
+  $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->{'bugid'} = $bugid;
-  $vars->{'attachid'} = $attachid;
-  $vars->{'description'} = $cgi->param('description');
+  $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->{'contenttype'} = $cgi->param('contenttype');
+  $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/created.html.tmpl", $vars)
     || ThrowTemplateError($template->error());
@@ -600,12 +575,9 @@
   $template_name = $template_name || "edit";
 #endif // WEBKIT_CHANGES
 
-  my ($attach_id) = validateID();
+  my $attachment = validateID();
   my $dbh = Bugzilla->dbh;
 
-  my $attachment = Bugzilla::Attachment->get($attach_id);
-  my $isviewable = !$attachment->isurl && isViewable($attachment->contenttype);
-
   # 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 =
@@ -623,15 +595,14 @@
                                                '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,
+    $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);
   $vars->{'attachment'} = $attachment;
   $vars->{'bugsummary'} = $bugsummary; 
-  $vars->{'isviewable'} = $isviewable; 
-  $vars->{'attachments'} = $bugattachments; 
+  $vars->{'attachments'} = $bugattachments;
 
 #if WEBKIT_CHANGES
   if ($attachment->ispatch) {
@@ -641,11 +612,6 @@
   }
 #endif // WEBKIT_CHANGES
 
-  # Determine if PatchReader is installed
-  eval {
-    require PatchReader;
-    $vars->{'patchviewerinstalled'} = 1;
-  };
   print $cgi->header();
 
   # Generate and return the UI (HTML page) from the appropriate template.
@@ -658,24 +624,50 @@
 # content type, ispatch and isobsolete flags, and statuses, and they can
 # also submit a comment that appears in the bug.
 # Users cannot edit the content of the attachment itself.
-sub update
-{
+sub update {
     my $user = Bugzilla->user;
-    my $userid = $user->id;
     my $dbh = Bugzilla->dbh;
 
     # Retrieve and validate parameters
-    ValidateComment(scalar $cgi->param('comment'));
-    my ($attach_id, $bugid) = validateID();
-    my $bug = new Bugzilla::Bug($bugid);
-    my $attachment = Bugzilla::Attachment->get($attach_id);
+    my $attachment = validateID();
+    my $bug = new Bugzilla::Bug($attachment->bug_id);
     $attachment->validate_can_edit($bug->product_id);
-    validateCanChangeAttachment($attach_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');
-    validateIsObsolete();
-    validatePrivate();
+    $cgi->param('isobsolete', $cgi->param('isobsolete') ? 1 : 0);
+    $cgi->param('isprivate', $cgi->param('isprivate') ? 1 : 0);
+
+    # Now make sure the attachment has not been edited since we loaded the page.
+    if (defined $cgi->param('delta_ts')
+        && $cgi->param('delta_ts') ne $attachment->modification_time)
+    {
+        ($vars->{'operations'}) =
+            Bugzilla::Bug::GetBugActivity($bug->id, $attachment->id, $cgi->param('delta_ts'));
+
+        # The token contains the old modification_time. We need a new one.
+        $cgi->param('token', issue_hash_token([$attachment->id, $attachment->modification_time]));
+
+        # If the modification date changed but there is no entry in
+        # the activity table, this means someone commented only.
+        # In this case, there is no reason to midair.
+        if (scalar(@{$vars->{'operations'}})) {
+            $cgi->param('delta_ts', $attachment->modification_time);
+            $vars->{'attachment'} = $attachment;
+
+            print $cgi->header();
+            # Warn the user about the mid-air collision and ask them what to do.
+            $template->process("attachment/midair.html.tmpl", $vars)
+              || ThrowTemplateError($template->error());
+            exit;
+        }
+    }
+
+    # We couldn't do this check earlier as we first had to validate attachment ID
+    # and display the mid-air collision page if modification_time changed.
+    my $token = $cgi->param('token');
+    check_hash_token($token, [$attachment->id, $attachment->modification_time]);
 
     # If the submitter of the attachment is not in the insidergroup,
     # be sure that he cannot overwrite the private bit.
@@ -685,9 +677,18 @@
     # old private bit twice (first here, and then below again), but this is
     # the less risky change.
     unless ($user->is_insider) {
-        my $oldisprivate = $dbh->selectrow_array('SELECT isprivate FROM attachments
-                                                  WHERE attach_id = ?', undef, $attach_id);
-        $cgi->param('isprivate', $oldisprivate);
+        $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') });
     }
 
     # The order of these function calls is important, as Flag::validate
@@ -696,27 +697,10 @@
     Bugzilla::User::match_field($cgi, {
         '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' }
     });
-    Bugzilla::Flag::validate($cgi, $bugid, $attach_id);
+    Bugzilla::Flag::validate($bug->id, $attachment->id);
 
-    # Lock database tables in preparation for updating the attachment.
-    $dbh->bz_lock_tables('attachments WRITE', 'flags WRITE' ,
-          'flagtypes READ', 'fielddefs READ', 'bugs_activity WRITE',
-          'flaginclusions AS i READ', 'flagexclusions AS e READ',
-          # cc, bug_group_map, user_group_map, and groups are in here so we
-          # can check the permissions of flag requestees and email addresses
-          # on the flag type cc: lists via the CanSeeBug
-          # function call in Flag::notify. group_group_map is in here si
-          # Bugzilla::User can flatten groups.
-          'bugs WRITE', 'profiles READ', 'email_setting READ',
-          'cc READ', 'bug_group_map READ', 'user_group_map READ',
-          'group_group_map READ', 'groups READ', 'group_control_map READ');
-
-  # Get a copy of the attachment record before we make changes
-  # so we can record those changes in the activity table.
-  my ($olddescription, $oldcontenttype, $oldfilename, $oldispatch,
-      $oldisobsolete, $oldisprivate) = $dbh->selectrow_array(
-      "SELECT description, mimetype, filename, ispatch, isobsolete, isprivate
-       FROM attachments WHERE attach_id = ?", undef, $attach_id);
+    # Start a transaction in preparation for updating the attachment.
+    $dbh->bz_start_transaction();
 
   # Quote the description and content type for use in the SQL UPDATE statement.
   my $description = $cgi->param('description');
@@ -734,7 +718,7 @@
   # 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, $cgi);
+  Bugzilla::Flag->process($bug, $attachment, $timestamp, $vars);
 
   # Update the attachment record in the database.
   $dbh->do("UPDATE  attachments 
@@ -743,82 +727,64 @@
                     filename    = ?,
                     ispatch     = ?,
                     isobsolete  = ?,
-                    isprivate   = ?
+                    isprivate   = ?,
+                    modification_time = ?
             WHERE   attach_id   = ?",
             undef, ($description, $contenttype, $filename,
             $cgi->param('ispatch'), $cgi->param('isobsolete'), 
-            $cgi->param('isprivate'), $attach_id));
+            $cgi->param('isprivate'), $timestamp, $attachment->id));
 
+  my $updated_attachment = Bugzilla::Attachment->get($attachment->id);
   # Record changes in the activity table.
-  if ($olddescription ne $cgi->param('description')) {
+  my $sth = $dbh->prepare('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
+                                                      fieldid, removed, added)
+                           VALUES (?, ?, ?, ?, ?, ?, ?)');
+
+  if ($attachment->description ne $updated_attachment->description) {
     my $fieldid = get_field_id('attachments.description');
-    $dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
-                                        fieldid, removed, added)
-              VALUES (?,?,?,?,?,?,?)",
-              undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
-                     $olddescription, $description));
+    $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
+                  $attachment->description, $updated_attachment->description);
   }
-  if ($oldcontenttype ne $cgi->param('contenttype')) {
+  if ($attachment->contenttype ne $updated_attachment->contenttype) {
     my $fieldid = get_field_id('attachments.mimetype');
-    $dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
-                                        fieldid, removed, added)
-              VALUES (?,?,?,?,?,?,?)",
-              undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
-                     $oldcontenttype, $contenttype));
+    $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
+                  $attachment->contenttype, $updated_attachment->contenttype);
   }
-  if ($oldfilename ne $cgi->param('filename')) {
+  if ($attachment->filename ne $updated_attachment->filename) {
     my $fieldid = get_field_id('attachments.filename');
-    $dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
-                                        fieldid, removed, added)
-              VALUES (?,?,?,?,?,?,?)", 
-              undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
-                     $oldfilename, $filename));
+    $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
+                  $attachment->filename, $updated_attachment->filename);
   }
-  if ($oldispatch ne $cgi->param('ispatch')) {
+  if ($attachment->ispatch != $updated_attachment->ispatch) {
     my $fieldid = get_field_id('attachments.ispatch');
-    $dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
-                                        fieldid, removed, added)
-              VALUES (?,?,?,?,?,?,?)",
-              undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
-                     $oldispatch, $cgi->param('ispatch')));
+    $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
+                  $attachment->ispatch, $updated_attachment->ispatch);
   }
-  if ($oldisobsolete ne $cgi->param('isobsolete')) {
+  if ($attachment->isobsolete != $updated_attachment->isobsolete) {
     my $fieldid = get_field_id('attachments.isobsolete');
-    $dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
-                                        fieldid, removed, added)
-              VALUES (?,?,?,?,?,?,?)",
-              undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
-                     $oldisobsolete, $cgi->param('isobsolete')));
+    $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
+                  $attachment->isobsolete, $updated_attachment->isobsolete);
   }
-  if ($oldisprivate ne $cgi->param('isprivate')) {
+  if ($attachment->isprivate != $updated_attachment->isprivate) {
     my $fieldid = get_field_id('attachments.isprivate');
-    $dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
-                                        fieldid, removed, added)
-              VALUES (?,?,?,?,?,?,?)",
-              undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
-                     $oldisprivate, $cgi->param('isprivate')));
+    $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
+                  $attachment->isprivate, $updated_attachment->isprivate);
   }
   
-  # Unlock all database tables now that we are finished updating the database.
-  $dbh->bz_unlock_tables();
+  # Commit the transaction now that we are finished updating the database.
+  $dbh->bz_commit_transaction();
 
-  # If the user submitted a comment while editing the attachment,
-  # add the comment to the bug.
-  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 = qq|(From update of attachment $attach_id)\n| .
-                  $cgi->param('comment');
+  # Commit the comment, if any.
+  $bug->update();
 
-    # Append the comment to the list of comments in the database.
-    AppendComment($bugid, $userid, $comment, $cgi->param('isprivate'), $timestamp);
-  }
-  
   # Define the variables and functions that will be passed to the UI template.
   $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
-  $vars->{'attachid'} = $attach_id; 
-  $vars->{'bugid'} = $bugid; 
+  $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();
 
@@ -843,9 +809,8 @@
       || ThrowUserError('attachment_deletion_disabled');
 
     # Make sure the administrator is allowed to edit this attachment.
-    my ($attach_id, $bug_id) = validateID();
-    my $attachment = Bugzilla::Attachment->get($attach_id);
-    validateCanChangeAttachment($attach_id);
+    my $attachment = validateID();
+    validateCanChangeBug($attachment->bug_id);
 
     $attachment->datasize || ThrowUserError('attachment_removed');
 
@@ -855,16 +820,17 @@
         my ($creator_id, $date, $event) = Bugzilla::Token::GetTokenData($token);
         unless ($creator_id
                   && ($creator_id == $user->id)
-                  && ($event eq "attachment$attach_id"))
+                  && ($event eq 'attachment' . $attachment->id))
         {
             # The token is invalid.
-            ThrowUserError('token_inexistent');
+            ThrowUserError('token_does_not_exist');
         }
 
+        my $bug = new Bugzilla::Bug($attachment->bug_id);
+
         # The token is valid. Delete the content of the attachment.
         my $msg;
-        $vars->{'attachid'} = $attach_id;
-        $vars->{'bugid'} = $bug_id;
+        $vars->{'attachment'} = $attachment;
         $vars->{'date'} = $date;
         $vars->{'reason'} = clean_text($cgi->param('reason') || '');
         $vars->{'mailrecipients'} = { 'changer' => $user->login };
@@ -872,32 +838,32 @@
         $template->process("attachment/delete_reason.txt.tmpl", $vars, \$msg)
           || ThrowTemplateError($template->error());
 
-        $dbh->bz_lock_tables('attachments WRITE', 'attach_data WRITE', 'flags WRITE');
-        $dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $attach_id);
-        $dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isurl = ?,
-                         isobsolete = ?
-                  WHERE attach_id = ?', undef,
-                 ('text/plain', 0, 0, 1, $attach_id));
-        $dbh->do('DELETE FROM flags WHERE attach_id = ?', undef, $attach_id);
-        $dbh->bz_unlock_tables;
+        # Paste the reason provided by the admin into a comment.
+        $bug->add_comment($msg);
 
         # If the attachment is stored locally, remove it.
         if (-e $attachment->_get_local_filename) {
             unlink $attachment->_get_local_filename;
         }
+        $attachment->remove_from_db();
 
         # Now delete the token.
         delete_token($token);
 
-        # Paste the reason provided by the admin into a comment.
-        AppendComment($bug_id, $user->id, $msg);
+        # Insert the comment.
+        $bug->update();
+
+        # 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();
 
         $template->process("attachment/updated.html.tmpl", $vars)
           || ThrowTemplateError($template->error());
     }
     else {
         # Create a token.
-        $token = issue_session_token('attachment' . $attach_id);
+        $token = issue_session_token('attachment' . $attachment->id);
 
         $vars->{'a'} = $attachment;
         $vars->{'token'} = $token;
diff --git a/BugsSite/buglist.cgi b/BugsSite/buglist.cgi
index e8ed317..5325b1e 100755
--- a/BugsSite/buglist.cgi
+++ b/BugsSite/buglist.cgi
@@ -32,7 +32,7 @@
 # Make it harder for us to do dangerous things in Perl.
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -46,6 +46,8 @@
 use Bugzilla::Product;
 use Bugzilla::Keyword;
 use Bugzilla::Field;
+use Bugzilla::Status;
+use Bugzilla::Token;
 
 use Date::Parse;
 
@@ -93,10 +95,6 @@
 # Log the user in
 if ($dotweak) {
     Bugzilla->login(LOGIN_REQUIRED);
-    Bugzilla->user->in_group("editbugs")
-      || ThrowUserError("auth_failure", {group  => "editbugs",
-                                         action => "modify",
-                                         object => "multiple_bugs"});
 }
 
 # Hack to support legacy applications that think the RDF ctype is at format=rdf.
@@ -253,6 +251,12 @@
                                                 WHERE userid = ? AND name = ?
                                                       $extra",
                                                undef, @args);
+
+    # 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);
+
     if (!defined($result)) {
         return 0 unless $throw_error;
         ThrowUserError("missing_query", {'queryname' => $name,
@@ -273,7 +277,7 @@
     $result
        || ThrowUserError("buglist_parameters_required", {'queryname' => $name});
 
-    return $result;
+    return wantarray ? ($result, $id) : $result;
 }
 
 # Inserts a Named Query (a "Saved Search") into the database, or
@@ -301,9 +305,10 @@
     my $dbh = Bugzilla->dbh;
 
     $query_name = trim($query_name);
-    my ($query_obj) = grep {$_->name eq $query_name} @{Bugzilla->user->queries};
+    my ($query_obj) = grep {lc($_->name) eq lc($query_name)} @{Bugzilla->user->queries};
 
     if ($query_obj) {
+        $query_obj->set_name($query_name);
         $query_obj->set_url($query);
         $query_obj->set_query_type($query_type);
         $query_obj->update();
@@ -345,23 +350,47 @@
     return $quip;
 }
 
+# Return groups available for at least one product of the buglist.
 sub GetGroups {
-    my $dbh = Bugzilla->dbh;
+    my $product_names = shift;
     my $user = Bugzilla->user;
+    my %legal_groups;
 
-    # Create an array where each item is a hash. The hash contains 
-    # as keys the name of the columns, which point to the value of 
-    # the columns for that row.
-    my $grouplist = $user->groups_as_string;
-    my $groups = $dbh->selectall_arrayref(
-                "SELECT  id, name, description, isactive
-                   FROM  groups
-                  WHERE  id IN ($grouplist)
-                    AND  isbuggroup = 1
-               ORDER BY  description "
-               , {Slice => {}});
+    foreach my $product_name (@$product_names) {
+        my $product = new Bugzilla::Product({name => $product_name});
 
-    return $groups;
+        foreach my $gid (keys %{$product->group_controls}) {
+            # The user can only edit groups he belongs to.
+            next unless $user->in_group_id($gid);
+
+            # The user has no control on groups marked as NA or MANDATORY.
+            my $group = $product->group_controls->{$gid};
+            next if ($group->{membercontrol} == CONTROLMAPMANDATORY
+                     || $group->{membercontrol} == CONTROLMAPNA);
+
+            # It's fine to include inactive groups. Those will be marked
+            # as "remove only" when editing several bugs at once.
+            $legal_groups{$gid} ||= $group->{group};
+        }
+    }
+    # Return a list of group objects.
+    return [values %legal_groups];
+}
+
+sub _close_standby_message {
+    my ($contenttype, $disposition, $serverpush) = @_;
+    my $cgi = Bugzilla->cgi;
+
+    # Close the "please wait" page, then open the buglist page
+    if ($serverpush) {
+        print $cgi->multipart_end();
+        print $cgi->multipart_start(-type                => $contenttype,
+                                    -content_disposition => $disposition);
+    }
+    else {
+        print $cgi->header(-type                => $contenttype,
+                           -content_disposition => $disposition);
+    }
 }
 
 
@@ -400,18 +429,22 @@
     # with the HTTP headers.
     $filename =~ s/\s/_/g;
 }
+$filename =~ s/\\/\\\\/g; # escape backslashes
+$filename =~ s/"/\\"/g; # escape quotes
 
 # Take appropriate action based on user's request.
 if ($cgi->param('cmdtype') eq "dorem") {  
     if ($cgi->param('remaction') eq "run") {
-        $buffer = LookupNamedQuery(scalar $cgi->param("namedcmd"),
-                                   scalar $cgi->param('sharer_id'));
+        my $query_id;
+        ($buffer, $query_id) = LookupNamedQuery(scalar $cgi->param("namedcmd"),
+                                                scalar $cgi->param('sharer_id'));
         # If this is the user's own query, remember information about it
         # so that it can be modified easily.
         $vars->{'searchname'} = $cgi->param('namedcmd');
         if (!$cgi->param('sharer_id') ||
             $cgi->param('sharer_id') == Bugzilla->user->id) {
             $vars->{'searchtype'} = "saved";
+            $vars->{'search_id'} = $query_id;
         }
         $params = new Bugzilla::CGI($buffer);
         $order = $params->param('order') || $order;
@@ -460,6 +493,10 @@
             # The user has no query of this name. Play along.
         }
         else {
+            # Make sure the user really wants to delete his saved search.
+            my $token = $cgi->param('token');
+            check_hash_token($token, [$query_id, $qname]);
+
             $dbh->do('DELETE FROM namedqueries
                             WHERE id = ?',
                      undef, $query_id);
@@ -513,9 +550,12 @@
             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 (LookupNamedQuery($query_name, undef, QUERY_LIST, !THROW_ERROR)) {
-                    ThrowUserError('query_name_exists', {'name' => $query_name});
+                if ($query) {
+                    ThrowUserError('query_name_exists', {name     => $query_name,
+                                                         query_id => $query_id});
                 }
                 $is_new_name = 1;
             }
@@ -661,10 +701,14 @@
 DefineColumn("relevance"         , "relevance"                  , "Relevance"        );
 DefineColumn("deadline"          , $dbh->sql_date_format('bugs.deadline', '%Y-%m-%d') . " AS deadline", "Deadline");
 
-foreach my $field (Bugzilla->get_fields({ custom => 1, obsolete => 0})) {
+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} );
+
 ################################################################################
 # Display Column Determination
 ################################################################################
@@ -789,8 +833,12 @@
       'priority',
       'bug_severity',
       'assigned_to_realname',
-      'bug_status'
+      'bug_status',
+      'product',
+      'component',
+      'resolution'
     );
+    push(@required_atom_columns, 'target_milestone') if Bugzilla->params->{'usetargetmilestone'};
 
     foreach my $required (@required_atom_columns) {
         push(@selectcolumns, $required) if !grep($_ eq $required,@selectcolumns);
@@ -858,6 +906,7 @@
             # 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))) {
@@ -878,11 +927,12 @@
             $order = join(",", @order);
             # Now that we have checked that all columns in the order are valid,
             # detaint the order string.
-            trick_taint($order);
+            trick_taint($order) if $order;
         };
     }
 }
-else {
+
+if (!$order) {
     # DEFAULT
     $order = "bugs.bug_status, bugs.priority, map_assigned_to.login_name, bugs.bug_id";
 }
@@ -948,7 +998,7 @@
 }
 elsif ($fulltext) {
     $query .= " " . $dbh->sql_limit(FULLTEXT_BUGLIST_LIMIT);
-    $vars->{'sorted_by_relevance'} = 1;
+    $vars->{'message'} = 'buglist_sorted_by_relevance' if ($cgi->param('order') =~ /^relevance/);
 }
 
 
@@ -964,14 +1014,9 @@
 
 # Time to use server push to display an interim message to the user until
 # the query completes and we can display the bug list.
-my $disposition = '';
 if ($serverpush) {
-    $filename =~ s/\\/\\\\/g; # escape backslashes
-    $filename =~ s/"/\\"/g; # escape quotes
-    $disposition = qq#inline; filename="$filename"#;
-
-    print $cgi->multipart_init(-content_disposition => $disposition);
-    print $cgi->multipart_start();
+    print $cgi->multipart_init();
+    print $cgi->multipart_start(-type => 'text/html');
 
     # Generate and return the UI (HTML page) from the appropriate template.
     $template->process("list/server-push.html.tmpl", $vars)
@@ -1072,7 +1117,7 @@
      "LEFT JOIN group_control_map " .
             "ON group_control_map.product_id = bugs.product_id " .
            "AND group_control_map.group_id = bug_group_map.group_id " .
-         "WHERE bugs.bug_id IN (" . join(',',@bugidlist) . ") " .
+         "WHERE " . $dbh->sql_in('bugs.bug_id', \@bugidlist) . 
             $dbh->sql_group_by('bugs.bug_id'));
     $sth->execute();
     while (my ($bug_id, $min_membercontrol) = $sth->fetchrow_array()) {
@@ -1101,9 +1146,8 @@
 $vars->{'columns'} = $columns;
 $vars->{'displaycolumns'} = \@displaycolumns;
 
-my @openstates = BUG_STATE_OPEN;
-$vars->{'openstates'} = \@openstates;
-$vars->{'closedstates'} = ['CLOSED', 'VERIFIED', 'RESOLVED'];
+$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
@@ -1114,7 +1158,17 @@
                                                       'cmdtype',
                                                       'query_based_on');
 $vars->{'order'} = $order;
-$vars->{'caneditbugs'} = Bugzilla->user->in_group('editbugs');
+$vars->{'caneditbugs'} = 1;
+
+if (!Bugzilla->user->in_group('editbugs')) {
+    foreach my $product (keys %$bugproducts) {
+        my $prod = new Bugzilla::Product({name => $product});
+        if (!Bugzilla->user->in_group('editbugs', $prod->id)) {
+            $vars->{'caneditbugs'} = 0;
+            last;
+        }
+    }
+}
 
 my @bugowners = keys %$bugowners;
 if (scalar(@bugowners) > 1 && Bugzilla->user->in_group('editbugs')) {
@@ -1132,32 +1186,68 @@
 $vars->{'currenttime'} = time();
 
 # The following variables are used when the user is making changes to multiple bugs.
-if ($dotweak) {
+if ($dotweak && scalar @bugs) {
+    if (!$vars->{'caneditbugs'}) {
+        _close_standby_message('text/html', 'inline', $serverpush);
+        ThrowUserError('auth_failure', {group  => 'editbugs',
+                                        action => 'modify',
+                                        object => 'multiple_bugs'});
+    }
     $vars->{'dotweak'} = 1;
     $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
+  
+    # issue_session_token needs to write to the master DB.
+    Bugzilla->switch_to_main_db();
+    $vars->{'token'} = issue_session_token('buglist_mass_change');
+    Bugzilla->switch_to_shadow_db();
 
     $vars->{'products'} = Bugzilla->user->get_enterable_products;
     $vars->{'platforms'} = get_legal_field_values('rep_platform');
     $vars->{'op_sys'} = get_legal_field_values('op_sys');
     $vars->{'priorities'} = get_legal_field_values('priority');
     $vars->{'severities'} = get_legal_field_values('bug_severity');
-    $vars->{'resolutions'} = Bugzilla::Bug->settable_resolutions;
+    $vars->{'resolutions'} = get_legal_field_values('resolution');
 
     $vars->{'unconfirmedstate'} = 'UNCONFIRMED';
 
-    $vars->{'bugstatuses'} = [ keys %$bugstatuses ];
+    # Convert bug statuses to their ID.
+    my @bug_statuses = map {$dbh->quote($_)} keys %$bugstatuses;
+    my $bug_status_ids =
+      $dbh->selectcol_arrayref('SELECT id FROM bug_status
+                               WHERE ' . $dbh->sql_in('value', \@bug_statuses));
 
-    # The groups to which the user belongs.
-    $vars->{'groups'} = GetGroups();
+    # This query collects new statuses which are common to all current bug statuses.
+    # It also accepts transitions where the bug status doesn't change.
+    $bug_status_ids =
+      $dbh->selectcol_arrayref(
+            'SELECT DISTINCT sw1.new_status
+               FROM status_workflow sw1
+         INNER JOIN bug_status
+                 ON bug_status.id = sw1.new_status
+              WHERE bug_status.isactive = 1
+                AND NOT EXISTS 
+                   (SELECT * FROM status_workflow sw2
+                     WHERE sw2.old_status != sw1.new_status 
+                           AND '
+                         . $dbh->sql_in('sw2.old_status', $bug_status_ids)
+                         . ' AND NOT EXISTS 
+                           (SELECT * FROM status_workflow sw3
+                             WHERE sw3.new_status = sw1.new_status
+                                   AND sw3.old_status = sw2.old_status))');
+
+    $vars->{'current_bug_statuses'} = [keys %$bugstatuses];
+    $vars->{'new_bug_statuses'} = Bugzilla::Status->new_from_list($bug_status_ids);
+
+    # The groups the user belongs to and which are editable for the given buglist.
+    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.
-    $vars->{'bugproducts'} = [ keys %$bugproducts ];
-    if (scalar(@{$vars->{'bugproducts'}}) == 1) {
-        my $product = new Bugzilla::Product(
-            {name => $vars->{'bugproducts'}->[0]});
+    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})]
@@ -1177,7 +1267,7 @@
 # Generate HTTP headers
 
 my $contenttype;
-my $disp = "inline";
+my $disposition = "inline";
 
 if ($format->{'extension'} eq "html" && !$agent) {
     if ($order) {
@@ -1209,25 +1299,13 @@
 if ($format->{'extension'} eq "csv") {
     # We set CSV files to be downloaded, as they are designed for importing
     # into other programs.
-    $disp = "attachment";
+    $disposition = "attachment";
 }
 
-if ($serverpush) {
-    # close the "please wait" page, then open the buglist page
-    print $cgi->multipart_end();
-    my @extra;
-    push @extra, (-charset => "utf8") if Bugzilla->params->{"utf8"};
-    print $cgi->multipart_start(-type => $contenttype, 
-                                -content_disposition => $disposition, 
-                                @extra);
-} else {
-    # Suggest a name for the bug list if the user wants to save it as a file.
-    # If we are doing server push, then we did this already in the HTTP headers
-    # that started the server push, so we don't have to do it again here.
-    print $cgi->header(-type => $contenttype,
-                       -content_disposition => "$disp; filename=$filename");
-}
+# Suggest a name for the bug list if the user wants to save it as a file.
+$disposition .= "; filename=\"$filename\"";
 
+_close_standby_message($contenttype, $disposition, $serverpush);
 
 ################################################################################
 # Content Generation
diff --git a/BugsSite/chart.cgi b/BugsSite/chart.cgi
index db48745..c74df4f 100755
--- a/BugsSite/chart.cgi
+++ b/BugsSite/chart.cgi
@@ -43,7 +43,7 @@
 # Offer subscription when you get a "series already exists" error?
 
 use strict;
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -71,6 +71,7 @@
 
 my $action = $cgi->param('action');
 my $series_id = $cgi->param('series_id');
+$vars->{'doc_section'} = 'reporting.html#charts';
 
 # Because some actions are chosen by buttons, we can't encode them as the value
 # of the action param, because that value is localization-dependent. So, we
@@ -281,6 +282,8 @@
     }
 
     print $cgi->header($format->{'ctype'});
+    disable_utf8() if ($format->{'ctype'} =~ /^image\//);
+
     $template->process($format->{'template'}, $vars)
       || ThrowTemplateError($template->error());
 }
diff --git a/BugsSite/checksetup.pl b/BugsSite/checksetup.pl
index eefff49..d7a787f 100755
--- a/BugsSite/checksetup.pl
+++ b/BugsSite/checksetup.pl
@@ -43,27 +43,30 @@
 ######################################################################
 
 use strict;
-use 5.008;
+use 5.008001;
 use File::Basename;
 use Getopt::Long qw(:config bundling);
 use Pod::Usage;
-use POSIX qw(setlocale LC_CTYPE);
 use Safe;
 
 BEGIN { chdir dirname($0); }
-use lib ".";
+use lib qw(. lib);
 use Bugzilla::Constants;
 use Bugzilla::Install::Requirements;
-
-require 5.008001 if ON_WINDOWS; # for CGI 2.93 or higher
+use Bugzilla::Install::Util qw(install_string get_version_and_os get_console_locale);
 
 ######################################################################
 # Live Code
 ######################################################################
 
+# 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();
+
 my %switch;
 GetOptions(\%switch, 'help|h|?', 'check-modules', 'no-templates|t',
-                     'verbose|v|no-silent', 'make-admin=s');
+                     'verbose|v|no-silent', 'make-admin=s', 
+                     'reset-password=s', 'version|V');
 
 # Print the help message if that switch was selected.
 pod2usage({-verbose => 1, -exitval => 1}) if $switch{'help'};
@@ -73,7 +76,8 @@
 my $answers_file = $ARGV[0];
 my $silent = $answers_file && !$switch{'verbose'};
 
-display_version_and_os() unless $silent;
+print(install_string('header', get_version_and_os()) . "\n") unless $silent;
+exit if $switch{'version'};
 # Check required --MODULES--
 my $module_results = check_requirements(!$silent);
 Bugzilla::Install::Requirements::print_module_instructions(
@@ -115,10 +119,6 @@
 Bugzilla->installation_mode(INSTALLATION_MODE_NON_INTERACTIVE) if $answers_file;
 Bugzilla->installation_answers($answers_file);
 
-# When we're running at the command line, we need to pick the right
-# language before ever creating a template object.
-$ENV{'HTTP_ACCEPT_LANGUAGE'} ||= setlocale(LC_CTYPE);
-
 ###########################################################################
 # Check and update --LOCAL-- configuration
 ###########################################################################
@@ -154,7 +154,7 @@
 
 # Remove parameters from the params file that no longer exist in Bugzilla,
 # and set the defaults for new ones
-update_params();
+my %old_params = update_params();
 
 ###########################################################################
 # Pre-compile --TEMPLATE-- code
@@ -192,7 +192,7 @@
 # Update the tables to the current definition --TABLE--
 ###########################################################################
 
-Bugzilla::Install::DB::update_table_definitions();        
+Bugzilla::Install::DB::update_table_definitions(\%old_params);
 
 ###########################################################################
 # Bugzilla uses --GROUPS-- to assign various rights to its users.
@@ -213,12 +213,17 @@
 Bugzilla::Install::make_admin($switch{'make-admin'}) if $switch{'make-admin'};
 Bugzilla::Install::create_admin();
 
+Bugzilla::Install::reset_password($switch{'reset-password'})
+    if $switch{'reset-password'};
+
 ###########################################################################
 # Create default Product and Classification
 ###########################################################################
 
 Bugzilla::Install::create_default_product();
 
+Bugzilla::Hook::process('install-before_final_checks', {'silent' => $silent });
+
 ###########################################################################
 # Final checks
 ###########################################################################
@@ -238,9 +243,10 @@
 
 =head1 SYNOPSIS
 
- ./checksetup.pl [--help|--check-modules]
+ ./checksetup.pl [--help|--check-modules|--version]
  ./checksetup.pl [SCRIPT [--verbose]] [--no-templates|-t]
                  [--make-admin=user@domain.com]
+                 [--reset-password=user@domain.com]
 
 =head1 OPTIONS
 
@@ -268,6 +274,11 @@
 in case you accidentally lock yourself out of the Bugzilla administrative
 interface.
 
+=item B<--reset-password>=user@domain.com
+
+Resets the specified user's password. checksetup.pl will prompt you to 
+enter a new password for the user.
+
 =item B<--no-templates> (B<-t>)
 
 Don't compile the templates at all. Existing compiled templates will
@@ -278,6 +289,11 @@
 
 Output results of SCRIPT being processed.
 
+=item B<--version>
+
+Display the version of Bugzilla, Perl, and some info about the
+system that Bugzilla is being installed on, and then exit.
+
 =back
 
 =head1 DESCRIPTION
diff --git a/BugsSite/colchange.cgi b/BugsSite/colchange.cgi
index de834b8..5e84dcc 100755
--- a/BugsSite/colchange.cgi
+++ b/BugsSite/colchange.cgi
@@ -24,10 +24,13 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::CGI;
+use Bugzilla::Search::Saved;
 use Bugzilla::Error;
 use Bugzilla::User;
 use Bugzilla::Keyword;
@@ -78,7 +81,11 @@
 
 push(@masterlist, ("short_desc", "short_short_desc"));
 
-push(@masterlist, Bugzilla->custom_field_names);
+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;
 
@@ -101,9 +108,13 @@
     my $urlbase = Bugzilla->params->{"urlbase"};
 
     if ($list) {
-        $cgi->send_cookie(-name => 'COLUMNLIST',
-                          -value => $list,
-                          -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
+        # Only set the cookie if this is not a saved search.
+        # Saved searches have their own column list
+        if (!$cgi->param('save_columns_for_search')) {
+            $cgi->send_cookie(-name => 'COLUMNLIST',
+                              -value => $list,
+                              -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
+        }
     }
     else {
         $cgi->remove_cookie('COLUMNLIST');
@@ -118,7 +129,27 @@
     }
 
     $vars->{'message'} = "change_columns";
-    $vars->{'redirect_url'} = "buglist.cgi?".$cgi->param('rememberedquery');
+
+    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) 
+    {
+        my $params = new Bugzilla::CGI($search->url);
+        $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();
+    }
+
 
     # If we're running on Microsoft IIS, using cgi->redirect discards
     # the Set-Cookie lines -- workaround is to use the old-fashioned 
@@ -149,6 +180,24 @@
 
 $vars->{'buffer'} = $cgi->query_string();
 
+my $search;
+if (defined $cgi->param('query_based_on')) {
+    my $searches = Bugzilla->user->queries;
+    my ($search) = grep($_->name eq $cgi->param('query_based_on'), @$searches);
+
+    # Only allow users to edit their own queries.
+    if ($search && $search->user->id == Bugzilla->user->id) {
+        $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);
+        }
+    }
+}
+
 # Generate and return the UI (HTML page) from the appropriate template.
 print $cgi->header();
 $template->process("list/change-columns.html.tmpl", $vars)
diff --git a/BugsSite/collectstats.pl b/BugsSite/collectstats.pl
index 80c70fe..6afa259 100755
--- a/BugsSite/collectstats.pl
+++ b/BugsSite/collectstats.pl
@@ -33,8 +33,9 @@
 use AnyDBM_File;
 use strict;
 use IO::Handle;
+use Cwd;
 
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -50,10 +51,12 @@
 $| = 1;
 
 # Tidy up after graphing module
+my $cwd = Cwd::getcwd();
 if (chdir("graphs")) {
     unlink <./*.gif>;
     unlink <./*.png>;
-    chdir("..");
+    # chdir("..") doesn't work if graphs is a symlink, see bug 429378
+    chdir($cwd);
 }
 
 # This is a pure command line script.
@@ -61,6 +64,7 @@
 
 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") {
@@ -73,19 +77,21 @@
 my @myproducts = map {$_->name} Bugzilla::Product->get_all;
 unshift(@myproducts, "-All-");
 
-# As we can now customize the list of resolutions, looking at the actual list
-# of available resolutions only is not enough as some now removed resolutions
+# As we can now customize statuses and resolutions, looking at the current list
+# of legal values only is not enough as some now removed statuses and resolutions
 # may have existed in the past, or have been renamed. We want them all.
-my @resolutions = @{get_legal_field_values('resolution')};
-my $old_resolutions =
-    $dbh->selectcol_arrayref('SELECT bugs_activity.added
+my $fields = {};
+foreach my $field ('bug_status', 'resolution') {
+    my $values = get_legal_field_values($field);
+    my $old_values = $dbh->selectcol_arrayref(
+                             "SELECT bugs_activity.added
                                 FROM bugs_activity
                           INNER JOIN fielddefs
                                   ON fielddefs.id = bugs_activity.fieldid
-                           LEFT JOIN resolution
-                                  ON resolution.value = bugs_activity.added
+                           LEFT JOIN $field
+                                  ON $field.value = bugs_activity.added
                                WHERE fielddefs.name = ?
-                                 AND resolution.id IS NULL
+                                 AND $field.id IS NULL
 
                                UNION
 
@@ -93,19 +99,21 @@
                                 FROM bugs_activity
                           INNER JOIN fielddefs
                                   ON fielddefs.id = bugs_activity.fieldid
-                           LEFT JOIN resolution
-                                  ON resolution.value = bugs_activity.removed
+                           LEFT JOIN $field
+                                  ON $field.value = bugs_activity.removed
                                WHERE fielddefs.name = ?
-                                 AND resolution.id IS NULL',
-                               undef, ('resolution', 'resolution'));
+                                 AND $field.id IS NULL",
+                               undef, ($field, $field));
 
-push(@resolutions, @$old_resolutions);
+    push(@$values, @$old_values);
+    $fields->{$field} = $values;
+}
+
+my @statuses = @{$fields->{'bug_status'}};
+my @resolutions = @{$fields->{'resolution'}};
 # Exclude "" from the resolution list.
 @resolutions = grep {$_} @resolutions;
 
-# Actually, the list of statuses is predefined. This will change in the near future.
-my @statuses = qw(NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED VERIFIED CLOSED);
-
 my $tstart = time;
 foreach (@myproducts) {
     my $dir = "$datadir/mining";
@@ -394,13 +402,13 @@
     # 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, } . 
-                $dbh->sql_to_days('current_date') . q{ AS end, } . 
+                $dbh->sql_to_days('creation_ts') . q{ AS start_day, } . 
+                $dbh->sql_to_days('current_date') . q{ AS end_day, } . 
                 $dbh->sql_to_days("'1970-01-01'") . 
                  qq{ FROM bugs $from_product 
                    WHERE } . $dbh->sql_to_days('creation_ts') . 
                          qq{ IS NOT NULL $and_product 
-                ORDER BY start } . $dbh->sql_limit(1);
+                ORDER BY start_day } . $dbh->sql_limit(1);
     my ($start, $end, $base) = $dbh->selectrow_array($query, undef, @values);
 
     if (!defined $start) {
@@ -557,7 +565,7 @@
     my $serieses = $dbh->selectall_hashref("SELECT series_id, query, creator " .
                       "FROM series " .
                       "WHERE frequency != 0 AND " . 
-                      "($days_since_epoch + series_id) % frequency = 0",
+                      "MOD(($days_since_epoch + series_id), frequency) = 0",
                       "series_id");
 
     # We prepare the insertion into the data table, for efficiency.
diff --git a/BugsSite/config.cgi b/BugsSite/config.cgi
index 57c5842..ebdf712 100755
--- a/BugsSite/config.cgi
+++ b/BugsSite/config.cgi
@@ -28,13 +28,13 @@
 # Make it harder for us to do dangerous things in Perl.
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
 use Bugzilla::Error;
 use Bugzilla::Keyword;
-use Bugzilla::Bug;
+use Bugzilla::Status;
 use Bugzilla::Field;
 
 my $user = Bugzilla->login(LOGIN_OPTIONAL);
@@ -56,7 +56,8 @@
 $vars->{'resolution'} = get_legal_field_values('resolution');
 $vars->{'status'}    = get_legal_field_values('bug_status');
 $vars->{'custom_fields'} =
-  [Bugzilla->get_fields({custom => 1, obsolete => 0, type => FIELD_TYPE_SINGLE_SELECT})];
+  [ grep {$_->type == FIELD_TYPE_SINGLE_SELECT || $_->type == FIELD_TYPE_MULTI_SELECT}
+         Bugzilla->active_custom_fields ];
 
 # Include a list of product objects.
 if ($cgi->param('product')) {
@@ -86,7 +87,12 @@
 $vars->{'closed_status'} = \@closed_status;
 
 # Generate a list of fields that can be queried.
-$vars->{'field'} = [Bugzilla->dbh->bz_get_field_defs()];
+my @fields = @{Bugzilla::Field->match({obsolete => 0})};
+# Exclude fields the user cannot query.
+if (!Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
+    @fields = grep { $_->name !~ /^(estimated_time|remaining_time|work_time|percentage_complete|deadline)$/ } @fields;
+}
+$vars->{'field'} = \@fields;
 
 display_data($vars);
 
diff --git a/BugsSite/contrib/bugzilla_ldapsync.rb b/BugsSite/contrib/bugzilla_ldapsync.rb
index 4fca2a7d..1635301 100755
--- a/BugsSite/contrib/bugzilla_ldapsync.rb
+++ b/BugsSite/contrib/bugzilla_ldapsync.rb
@@ -4,7 +4,7 @@
 # and makes nice bugzilla user entries out of them. Also disables Bugzilla users
 # that are not found in LDAP.
 
-# $Id: bugzilla_ldapsync.rb,v 1.2 2003/04/26 16:35:04 jake%bugzilla.org Exp $
+# $Id$
 
 require 'ldap'
 require 'dbi'
diff --git a/BugsSite/contrib/bz_webservice_demo.pl b/BugsSite/contrib/bz_webservice_demo.pl
index 70fb6c2..32c8561 100755
--- a/BugsSite/contrib/bz_webservice_demo.pl
+++ b/BugsSite/contrib/bz_webservice_demo.pl
@@ -29,6 +29,7 @@
 =cut
 
 use strict;
+use lib qw(lib);
 use Getopt::Long;
 use Pod::Usage;
 use File::Basename qw(dirname);
@@ -50,6 +51,10 @@
 my $product_name;
 my $create_file_name;
 my $legal_field_values;
+my $add_comment;
+my $private;
+my $work_time;
+my $fetch_extension_info = 0;
 
 GetOptions('help|h|?'       => \$help,
            'uri=s'          => \$Bugzilla_uri,
@@ -59,7 +64,11 @@
            'bug_id:s'       => \$bug_id,
            'product_name:s' => \$product_name,
            'create:s'       => \$create_file_name,
-           'field:s'        => \$legal_field_values
+           'field:s'        => \$legal_field_values,
+           'comment:s'      => \$add_comment,
+           'private:i'      => \$private,
+           'worktime:f'     => \$work_time,
+           'extension_info'    => \$fetch_extension_info
           ) or pod2usage({'-verbose' => 0, '-exitval' => 1});
 
 =head1 OPTIONS
@@ -87,7 +96,7 @@
 
 =item --rememberlogin
 
-Gives access to Bugzilla's “Bugzilla_remember” option.
+Gives access to Bugzilla's "Bugzilla_remember" option.
 Specify this option while logging in to do the same thing as ticking the
 C<Bugzilla_remember> box on Bugilla's log in form.
 Don't specify this option to do the same thing as unchecking the box.
@@ -113,6 +122,24 @@
 global select field (such as bug_status, resolution, rep_platform, op_sys,
 priority, bug_severity) or a custom select field.
 
+=item --comment
+
+A comment to add to a bug identified by B<--bug_id>. You must also pass a B<--login>
+and B<--password> to log in to Bugzilla.
+
+=item --private
+
+An optional non-zero value to specify B<--comment> as private.
+
+=item --worktime
+
+An optional double precision number specifying the work time for B<--comment>.
+
+=item --extension_info
+
+If specified on the command line, the script returns the information about the
+extensions that are installed.
+
 =back
 
 =head1 DESCRIPTION
@@ -172,6 +199,25 @@
 _die_on_fault($soapresult);
 print 'Bugzilla\'s timezone is ' . $soapresult->result()->{timezone} . ".\n";
 
+=head2 Getting Extension Information
+
+Returns all the information any extensions have decided to provide to the webservice.
+
+=cut
+
+if ($fetch_extension_info) {
+    $soapresult = $proxy->call('Bugzilla.extensions');
+    _die_on_fault($soapresult);
+    my $extensions = $soapresult->result()->{extensions};
+    foreach my $extensionname (keys(%$extensions)) {
+        print "Extensionn '$extensionname' information\n";
+        my $extension = $extensions->{$extensionname};
+        foreach my $data (keys(%$extension)) {
+            print '  ' . $data . ' => ' . $extension->{$data} . "\n";
+        }
+    }
+}
+
 =head2 Logging In and Out
 
 =head3 Using Bugzilla's Environment Authentication
@@ -214,13 +260,15 @@
 
 =head2 Retrieving Bug Information
 
-Call C<Bug.get_bug> with the ID of the bug you want to know more of.
-The call will return a C<Bugzilla::Bug> object.
+Call C<Bug.get> with the ID of the bug you want to know more of.
+The call will return a C<Bugzilla::Bug> object. 
+
+Note: You can also use "Bug.get_bugs" for compatibility with Bugzilla 3.0 API.
 
 =cut
 
 if ($bug_id) {
-    $soapresult = $proxy->call('Bug.get_bugs', { ids => [$bug_id] });
+    $soapresult = $proxy->call('Bug.get', { ids => [$bug_id] });
     _die_on_fault($soapresult);
     $result = $soapresult->result;
     my $bug = $result->{bugs}->[0];
@@ -301,6 +349,25 @@
     print join("\n", @{$result->{values}}) . "\n";
 }
 
+=head2 Adding a comment to a bug
+
+Call C<Bug.add_comment> with the bug id, the comment text, and optionally the number
+of hours you worked on the bug, and a boolean indicating if the comment is private
+or not.
+
+=cut
+
+if ($add_comment) {
+    if ($bug_id) {
+        $soapresult = $proxy->call('Bug.add_comment', {id => $bug_id,
+            comment => $add_comment, private => $private, work_time => $work_time});
+        _die_on_fault($soapresult);
+        print "Comment added.\n";
+    }
+    else {
+        print "A --bug_id must be supplied to add a comment.";
+    }
+}
 
 =head1 NOTES
 
diff --git a/BugsSite/contrib/bzdbcopy.pl b/BugsSite/contrib/bzdbcopy.pl
index 4899098..eade756 100755
--- a/BugsSite/contrib/bzdbcopy.pl
+++ b/BugsSite/contrib/bzdbcopy.pl
@@ -19,9 +19,11 @@
 # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 use Bugzilla;
+use Bugzilla::Constants;
 use Bugzilla::DB;
+use Bugzilla::Install::Util qw(indicate_progress);
 use Bugzilla::Util;
 
 #####################################################################
@@ -33,25 +35,37 @@
 use constant SOURCE_DB_NAME => 'bugs';
 use constant SOURCE_DB_USER => 'bugs';
 use constant SOURCE_DB_PASSWORD => '';
+use constant SOURCE_DB_HOST => 'localhost';
 
 # Settings for the 'Target' DB that you are copying to.
 use constant TARGET_DB_TYPE => 'Pg';
 use constant TARGET_DB_NAME => 'bugs';
 use constant TARGET_DB_USER => 'bugs';
 use constant TARGET_DB_PASSWORD => '';
+use constant TARGET_DB_HOST => 'localhost';
 
 #####################################################################
 # MAIN SCRIPT
 #####################################################################
 
+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, 'localhost', 
+my $source_db = Bugzilla::DB::_connect(SOURCE_DB_TYPE, SOURCE_DB_HOST, 
     SOURCE_DB_NAME, undef, undef, SOURCE_DB_USER, SOURCE_DB_PASSWORD);
+# Don't read entire tables into memory.
+if (SOURCE_DB_TYPE eq 'Mysql') {
+    $source_db->{'mysql_use_result'}=1;
+
+    # MySQL cannot have two queries running at the same time. Ensure the schema
+    # is loaded from the database so bz_column_info will not execute a query
+    $source_db->_bz_real_schema;
+}
 
 print "Connecting to the '" . TARGET_DB_NAME . "' target database on "
       . TARGET_DB_TYPE . "...\n";
-my $target_db = Bugzilla::DB::_connect(TARGET_DB_TYPE, 'localhost', 
+my $target_db = Bugzilla::DB::_connect(TARGET_DB_TYPE, TARGET_DB_HOST, 
     TARGET_DB_NAME, undef, undef, TARGET_DB_USER, TARGET_DB_PASSWORD);
 my $ident_char = $target_db->get_info( 29 ); # SQL_IDENTIFIER_QUOTE_CHAR
 
@@ -65,11 +79,12 @@
 my $bz_schema_location = lsearch(\@table_list, 'bz_schema');
 splice(@table_list, $bz_schema_location, 1) if $bz_schema_location > 0;
 
-# We turn off autocommit on the target DB, because we're doing so
-# much copying.
-$target_db->{AutoCommit} = 0;
-$target_db->{AutoCommit} == 0 
-    || warn "Failed to disable autocommit on " . TARGET_DB_TYPE;
+# Instead of figuring out some fancy algorithm to insert data in the right
+# order and not break FK integrity, we just drop them all.
+$target_db->bz_drop_foreign_keys();
+# We start a transaction on the target DB, which helps when we're doing
+# so many inserts.
+$target_db->bz_start_transaction();
 foreach my $table (@table_list) {
     my @serial_cols;
     print "Reading data from the source '$table' table on " 
@@ -80,8 +95,10 @@
     @table_columns = map { s/^\Q$ident_char\E?(.*?)\Q$ident_char\E?$/$1/; $_ }
                          @table_columns;
 
+    my ($total) = $source_db->selectrow_array("SELECT COUNT(*) FROM $table");
     my $select_query = "SELECT " . join(',', @table_columns) . " FROM $table";
-    my $data_in = $source_db->selectall_arrayref($select_query);
+    my $select_sth = $source_db->prepare($select_query);
+    $select_sth->execute();
 
     my $insert_query = "INSERT INTO $table ( " . join(',', @table_columns) 
                        . " ) VALUES (";
@@ -94,10 +111,25 @@
     print "Clearing out the target '$table' table on " 
           . TARGET_DB_TYPE . "...\n";
     $target_db->do("DELETE FROM $table");
+
+    # Oracle doesn't like us manually inserting into tables that have
+    # auto-increment PKs set, because of the way we made auto-increment
+    # fields work.
+    if ($target_db->isa('Bugzilla::DB::Oracle')) {
+        foreach my $column (@table_columns) {
+            my $col_info = $source_db->bz_column_info($table, $column);
+            if ($col_info && $col_info->{TYPE} =~ /SERIAL/i) {
+                print "Dropping the sequence + trigger on $table.$column...\n";
+                $target_db->do("DROP TRIGGER ${table}_${column}_TR");
+                $target_db->do("DROP SEQUENCE ${table}_${column}_SEQ");
+            }
+        }
+    }
     
     print "Writing data to the target '$table' table on " 
-          . TARGET_DB_TYPE . "...";
-    foreach my $row (@$data_in) {
+          . TARGET_DB_TYPE . "...\n";
+    my $count = 0;
+    while (my $row = $select_sth->fetchrow_arrayref) {
         # Each column needs to be bound separately, because
         # many columns need to be dealt with specially.
         my $colnum = 0;
@@ -144,24 +176,39 @@
         }
 
         $insert_sth->execute();
+        $count++;
+        indicate_progress({ current => $count, total => $total, every => 100 });
     }
 
-    # PostgreSQL doesn't like it when you insert values into
-    # a serial field; it doesn't increment the counter 
-    # automatically.
-    if ($target_db->isa('Bugzilla::DB::Pg')) {
-        foreach my $column (@table_columns) {
-            my $col_info = $source_db->bz_column_info($table, $column);
-            if ($col_info && $col_info->{TYPE} =~ /SERIAL/i) {
-                # Set the sequence to the current max value + 1.
-                my ($max_val) = $target_db->selectrow_array(
+    # For some DBs, we have to do clever things with auto-increment fields.
+    foreach my $column (@table_columns) {
+        next if $target_db->isa('Bugzilla::DB::Mysql');
+        my $col_info = $source_db->bz_column_info($table, $column);
+        if ($col_info && $col_info->{TYPE} =~ /SERIAL/i) {
+            my ($max_val) = $target_db->selectrow_array(
                     "SELECT MAX($column) FROM $table");
-                $max_val = 0 if !defined $max_val;
-                $max_val++;
-                print "\nSetting the next value for $table.$column to $max_val.";
+            # Set the sequence to the current max value + 1.
+            $max_val = 0 if !defined $max_val;
+            $max_val++;
+            print "\nSetting the next value for $table.$column to $max_val.";
+            if ($target_db->isa('Bugzilla::DB::Pg')) {
+                # PostgreSQL doesn't like it when you insert values into
+                # a serial field; it doesn't increment the counter 
+                # automatically.
                 $target_db->do("SELECT pg_catalog.setval 
                                 ('${table}_${column}_seq', $max_val, false)");
             }
+            elsif ($target_db->isa('Bugzilla::DB::Oracle')) {
+                # Oracle increments the counter on every insert, and *always*
+                # sets the field, even if you gave it a value. So if there
+                # were already rows in the target DB (like the default rows
+                # created by checksetup), you'll get crazy values in your
+                # id columns. So we just dropped the sequences above and
+                # we re-create them here, starting with the right number.
+                my @sql = $target_db->_bz_real_schema->_get_create_seq_ddl(
+                    $table, $column, $max_val);
+                $target_db->do($_) foreach @sql;
+            }
         }
     }
 
@@ -169,9 +216,10 @@
 }
 
 print "Committing changes to the target database...\n";
-$target_db->commit;
+$target_db->bz_commit_transaction();
+$target_db->bz_setup_foreign_keys();
 
-print "All done! Make sure to run checksetup on the new DB.\n";
+print "All done! Make sure to run checksetup.pl on the new DB.\n";
 $source_db->disconnect;
 $target_db->disconnect;
 
diff --git a/BugsSite/contrib/merge-users.pl b/BugsSite/contrib/merge-users.pl
index f071f7c..80c516e 100644
--- a/BugsSite/contrib/merge-users.pl
+++ b/BugsSite/contrib/merge-users.pl
@@ -44,11 +44,12 @@
 
 =cut
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
 use Bugzilla::Util;
+use Bugzilla::User;
 
 use Getopt::Long;
 use Pod::Usage;
@@ -154,9 +155,8 @@
     profiles        => [], # ['userid'],
 };
 
-# Lock tables
-my @locked_tables = map {"$_ WRITE"} keys(%$changes);
-$dbh->bz_lock_tables(@locked_tables);
+# Start the transaction
+$dbh->bz_start_transaction();
 
 # Delete old records from logincookies and tokens tables.
 $dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $old_id);
@@ -230,7 +230,13 @@
 # Delete the old record from the profiles table.
 $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $old_id);
 
-# Unlock tables
-$dbh->bz_unlock_tables();
+# rederive regexp-based group memberships, because we merged all memberships
+# from all of the accounts, and since the email address isn't the same on
+# them, some of them may no longer match the regexps.
+my $user = new Bugzilla::User($new_id);
+$user->derive_regexp_groups();
+
+# Commit the transaction
+$dbh->bz_commit_transaction();
 
 print "Done.\n";
diff --git a/BugsSite/contrib/recode.pl b/BugsSite/contrib/recode.pl
index 49e824f6..f7ba034 100755
--- a/BugsSite/contrib/recode.pl
+++ b/BugsSite/contrib/recode.pl
@@ -20,8 +20,7 @@
 # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
 
 use strict;
-# Allow the script to be run from contrib or as contrib/recode.pl
-use lib '..';
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -150,16 +149,9 @@
         my $root = ROOT_USER;
         print STDERR <<EOT;
 Using --guess requires that Encode::Detect be installed. To install
-Encode::Detect, first download it from:
+Encode::Detect, run the following command:
 
-  http://search.cpan.org/dist/Encode-Detect/
-
-Then, unpack it into its own directory and run the following commands
-in that directory, as $root:
-
-  ./Build.PL
-  ./Build
-  ./Build install
+  $^X install-module.pl Encode::Detect
 
 EOT
         exit;
@@ -249,7 +241,10 @@
 
             while (my @result = $sth->fetchrow_array) {
                 my $data = shift @result;
-                my $digest = md5_base64($data);
+                # Wide characters cause md5_base64() to die.
+                my $digest_data = utf8::is_utf8($data) 
+                                  ? Encode::encode_utf8($data) : $data;
+                my $digest = md5_base64($digest_data);
 
                 my @primary_keys = reverse split(',', $pk);
                 # We copy the array so that we can pop things from it without
diff --git a/BugsSite/contrib/sendbugmail.pl b/BugsSite/contrib/sendbugmail.pl
index ae282ca..c9e0706 100644
--- a/BugsSite/contrib/sendbugmail.pl
+++ b/BugsSite/contrib/sendbugmail.pl
@@ -4,7 +4,7 @@
 #
 # Nick Barnes, Ravenbrook Limited, 2004-04-01.
 #
-# $Id: sendbugmail.pl,v 1.7 2006/07/03 21:42:47 mkanat%bugzilla.org Exp $
+# $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.
@@ -14,7 +14,7 @@
 # 
 # Usage: perl -T contrib/sendbugmail.pl bug_id user_email
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Util;
diff --git a/BugsSite/contrib/sendunsentbugmail.pl b/BugsSite/contrib/sendunsentbugmail.pl
index 5ed49b2..ec92a97 100644
--- a/BugsSite/contrib/sendunsentbugmail.pl
+++ b/BugsSite/contrib/sendunsentbugmail.pl
@@ -23,7 +23,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
diff --git a/BugsSite/contrib/syncLDAP.pl b/BugsSite/contrib/syncLDAP.pl
index 6370fc8..3da25a65 100755
--- a/BugsSite/contrib/syncLDAP.pl
+++ b/BugsSite/contrib/syncLDAP.pl
@@ -22,7 +22,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Net::LDAP;
 use Bugzilla;
@@ -79,13 +79,13 @@
 ###
 # Get current bugzilla users
 ###
-my $bugzilla_users = $dbh->selectall_hashref(
+my %bugzilla_users = %{ $dbh->selectall_hashref(
     'SELECT login_name AS new_login_name, realname, disabledtext ' .
-    'FROM profiles', 'new_login_name');
+    'FROM profiles', 'new_login_name') };
 
-foreach my $login_name (keys %$bugzilla_users) {
+foreach my $login_name (keys %bugzilla_users) {
     # remove whitespaces
-    $bugzilla_users->{$login_name}{'realname'} =~ s/^\s+|\s+$//g;
+    $bugzilla_users{$login_name}{'realname'} =~ s/^\s+|\s+$//g;
 }
 
 ###
@@ -96,12 +96,19 @@
    print "No LDAP server defined in bugzilla preferences.\n";
    exit;
 }
-my $LDAPport = "389";  # default LDAP port
-if($LDAPserver =~ /:/) {
-    ($LDAPserver, $LDAPport) = split(":",$LDAPserver);
+
+my $LDAPconn;
+if($LDAPserver =~ /:\/\//) {
+    # if the "LDAPserver" parameter is in uri scheme
+    $LDAPconn = Net::LDAP->new($LDAPserver, version => 3);
+} else {
+    my $LDAPport = "389";  # default LDAP port
+    if($LDAPserver =~ /:/) {
+        ($LDAPserver, $LDAPport) = split(":",$LDAPserver);
+    }
+    $LDAPconn = Net::LDAP->new($LDAPserver, port => $LDAPport, version => 3);
 }
 
-my $LDAPconn = Net::LDAP->new($LDAPserver, port => $LDAPport, version => 3);
 if(!$LDAPconn) {
    print "Connecting to LDAP server failed. Check LDAPserver setting.\n";
    exit;
@@ -131,26 +138,26 @@
    exit;
 }
    
-my $val = $mesg->as_struct;
+my %val = %{ $mesg->as_struct };
 
-while( my ($key, $value) = each(%$val) ) {
+while( my ($key, $value) = each(%val) ) {
 
-   my $login_name = @$value{Bugzilla->params->{"LDAPmailattribute"}};
-   my $realname  = @$value{"cn"};
+   my @login_name = @{ $value->{Bugzilla->params->{"LDAPmailattribute"}} };
+   my @realname  = @{ $value->{"cn"} };
 
    # no mail entered? go to next
-   if(! defined $login_name) { 
+   if(! @login_name) { 
       print "$key has no valid mail address\n";
       next; 
    }
 
    # no cn entered? use uid instead
-   if(! defined $realname) { 
-      $realname = @$value{Bugzilla->params->{"LDAPuidattribute"}};
+   if(! @realname) { 
+       @realname = @{ $value->{Bugzilla->params->{"LDAPuidattribute"}} };
    }
   
-   my $login = shift @$login_name;
-   my $real = shift @$realname;
+   my $login = shift @login_name;
+   my $real = shift @realname;
    $ldap_users{$login} = { realname => $real };
 }
 
@@ -164,10 +171,10 @@
 my %create_users;
 
 print "Bugzilla-Users: \n" unless $quiet;
-while( my ($key, $value) = each(%$bugzilla_users) ) {
-  print " " . $key . " '" . @$value{'realname'} . "' " . @$value{'disabledtext'} ."\n" unless $quiet==1;
+while( my ($key, $value) = each(%bugzilla_users) ) {
+  print " " . $key . " '" . $value->{'realname'} . "' " . $value->{'disabledtext'} ."\n" unless $quiet==1;
   if(!exists $ldap_users{$key}){
-     if(@$value{'disabledtext'} eq '') {
+     if($value->{'disabledtext'} eq '') {
        $disable_users{$key} = $value;
      }
   }
@@ -175,13 +182,13 @@
 
 print "\nLDAP-Users: \n" unless $quiet;
 while( my ($key, $value) = each(%ldap_users) ) {
-  print " " . $key . " '" . @$value{'realname'} . "'\n" unless $quiet==1;
-  if(!defined $bugzilla_users->{$key}){
+  print " " . $key . " '" . $value->{'realname'} . "'\n" unless $quiet==1;
+  if(!defined $bugzilla_users{$key}){
     $create_users{$key} = $value;
   }
   else { 
-    my $bugzilla_user_value = $bugzilla_users->{$key};
-    if(@$bugzilla_user_value{'realname'} ne @$value{'realname'}) {
+    my $bugzilla_user_value = $bugzilla_users{$key};
+    if($bugzilla_user_value->{'realname'} ne $value->{'realname'}) {
       $update_users{$key} = $value;
     }
   }
@@ -190,9 +197,9 @@
 print "\nDetecting email changes: \n" unless $quiet;
 while( my ($create_key, $create_value) = each(%create_users) ) {
   while( my ($disable_key, $disable_value) = each(%disable_users) ) {
-    if(@$create_value{'realname'} eq @$disable_value{'realname'}) {
+    if($create_value->{'realname'} eq $disable_value->{'realname'}) {
        print " " . $disable_key . " => " . $create_key ."'\n" unless $quiet==1;
-       $update_users{$disable_key} = { realname => @$create_value{'realname'},
+       $update_users{$disable_key} = { realname => $create_value->{'realname'},
                                        new_login_name => $create_key };
        delete $create_users{$create_key};
        delete $disable_users{$disable_key};
@@ -203,21 +210,21 @@
 if($quiet == 0) {
    print "\nUsers to disable: \n";
    while( my ($key, $value) = each(%disable_users) ) {
-     print " " . $key . " '" . @$value{'realname'} . "'\n";
+     print " " . $key . " '" . $value->{'realname'} . "'\n";
    }
    
    print "\nUsers to update: \n";
    while( my ($key, $value) = each(%update_users) ) {
-     print " " . $key . " '" . @$value{'realname'} . "' ";
-     if(defined @$value{'new_login_name'}) {
-       print "has changed email to " . @$value{'new_login_name'};
+     print " " . $key . " '" . $value->{'realname'} . "' ";
+     if(defined $value->{'new_login_name'}) {
+       print "has changed email to " . $value->{'new_login_name'};
      }
      print "\n";
    }
    
    print "\nUsers to create: \n";
    while( my ($key, $value) = each(%create_users) ) {
-     print " " . $key . " '" . @$value{'realname'} . "'\n";
+     print " " . $key . " '" . $value->{'realname'} . "'\n";
    }
    
    print "\n\n";
@@ -258,10 +265,10 @@
 
    if($noupdate == 0) {
       while( my ($key, $value) = each(%update_users) ) {
-        if(defined @$value{'new_login_name'}) {
-          $sth_update_login->execute(@$value{'new_login_name'}, $key);
+        if(defined $value->{'new_login_name'}) {
+          $sth_update_login->execute($value->{'new_login_name'}, $key);
         } else {
-          $sth_update_realname->execute(@$value{'realname'}, $key);
+          $sth_update_realname->execute($value->{'realname'}, $key);
         }
       }
       print "done!\n" unless $quiet;
@@ -275,7 +282,7 @@
       while( my ($key, $value) = each(%create_users) ) {
         Bugzilla::User->create({
             login_name => $key, 
-            realname   => @$value{'realname'},
+            realname   => $value->{'realname'},
             cryptpassword   => '*'});
       }
       print "done!\n" unless $quiet;
diff --git a/BugsSite/contrib/yp_nomail.sh b/BugsSite/contrib/yp_nomail.sh
index 36bbc82..961916c 100644
--- a/BugsSite/contrib/yp_nomail.sh
+++ b/BugsSite/contrib/yp_nomail.sh
@@ -1,7 +1,7 @@
 #!/bin/sh
 # -*- Mode: ksh -*-
 ##############################################################################
-# $Id: yp_nomail.sh,v 1.1 2000/09/12 23:50:31 cyeh%bluemartini.com Exp $
+# $Id$
 # yp_nomail
 #
 # Our mail admins got annoyed when bugzilla kept sending email
diff --git a/BugsSite/createaccount.cgi b/BugsSite/createaccount.cgi
index 1f372fe..c2941bc 100755
--- a/BugsSite/createaccount.cgi
+++ b/BugsSite/createaccount.cgi
@@ -26,7 +26,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -45,6 +45,8 @@
 my $template = Bugzilla->template;
 my $vars = {};
 
+$vars->{'doc_section'} = 'myaccount.html';
+
 print $cgi->header();
 
 # If we're using LDAP for login, then we can't create a new account here.
@@ -64,7 +66,7 @@
     $vars->{'login'} = $login;
 
     if ($login !~ /$createexp/) {
-        ThrowUserError("account_creation_disabled");
+        ThrowUserError("account_creation_restricted");
     }
 
     # Create and send a token for this new account.
diff --git a/BugsSite/describecomponents.cgi b/BugsSite/describecomponents.cgi
index b282040..8061837 100755
--- a/BugsSite/describecomponents.cgi
+++ b/BugsSite/describecomponents.cgi
@@ -23,7 +23,7 @@
 #                 Frédéric Buclin <LpSolit@gmail.com>
 
 use strict;
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
diff --git a/BugsSite/describekeywords.cgi b/BugsSite/describekeywords.cgi
index 70c0ba4e..5ff5c50 100755
--- a/BugsSite/describekeywords.cgi
+++ b/BugsSite/describekeywords.cgi
@@ -22,7 +22,7 @@
 # Contributor(s): Gervase Markham <gerv@gerv.net>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Error;
diff --git a/BugsSite/docs/.cvsignore b/BugsSite/docs/en/.cvsignore
similarity index 61%
rename from BugsSite/docs/.cvsignore
rename to BugsSite/docs/en/.cvsignore
index 8453d5c..19d1c43 100644
--- a/BugsSite/docs/.cvsignore
+++ b/BugsSite/docs/en/.cvsignore
@@ -1,2 +1,3 @@
 txt
 pdf
+html
diff --git a/BugsSite/docs/README.docs b/BugsSite/docs/en/README.docs
similarity index 100%
rename from BugsSite/docs/README.docs
rename to BugsSite/docs/en/README.docs
diff --git a/BugsSite/docs/images/bzLifecycle.png b/BugsSite/docs/en/images/bzLifecycle.png
similarity index 100%
rename from BugsSite/docs/images/bzLifecycle.png
rename to BugsSite/docs/en/images/bzLifecycle.png
Binary files differ
diff --git a/BugsSite/docs/images/bzLifecycle.xml b/BugsSite/docs/en/images/bzLifecycle.xml
similarity index 100%
rename from BugsSite/docs/images/bzLifecycle.xml
rename to BugsSite/docs/en/images/bzLifecycle.xml
diff --git a/BugsSite/docs/images/callouts/1.gif b/BugsSite/docs/en/images/callouts/1.gif
similarity index 100%
rename from BugsSite/docs/images/callouts/1.gif
rename to BugsSite/docs/en/images/callouts/1.gif
Binary files differ
diff --git a/BugsSite/docs/images/callouts/2.gif b/BugsSite/docs/en/images/callouts/2.gif
similarity index 100%
rename from BugsSite/docs/images/callouts/2.gif
rename to BugsSite/docs/en/images/callouts/2.gif
Binary files differ
diff --git a/BugsSite/docs/images/callouts/3.gif b/BugsSite/docs/en/images/callouts/3.gif
similarity index 100%
rename from BugsSite/docs/images/callouts/3.gif
rename to BugsSite/docs/en/images/callouts/3.gif
Binary files differ
diff --git a/BugsSite/docs/en/images/caution.gif b/BugsSite/docs/en/images/caution.gif
new file mode 100644
index 0000000..c6bfd65
--- /dev/null
+++ b/BugsSite/docs/en/images/caution.gif
Binary files differ
diff --git a/BugsSite/docs/images/note.gif b/BugsSite/docs/en/images/note.gif
similarity index 100%
rename from BugsSite/docs/images/note.gif
rename to BugsSite/docs/en/images/note.gif
Binary files differ
diff --git a/BugsSite/docs/en/images/tip.gif b/BugsSite/docs/en/images/tip.gif
new file mode 100644
index 0000000..fe2a1d5
--- /dev/null
+++ b/BugsSite/docs/en/images/tip.gif
Binary files differ
diff --git a/BugsSite/docs/en/images/warning.gif b/BugsSite/docs/en/images/warning.gif
new file mode 100644
index 0000000..c6f3ea9
--- /dev/null
+++ b/BugsSite/docs/en/images/warning.gif
Binary files differ
diff --git a/BugsSite/docs/rel_notes.txt b/BugsSite/docs/en/rel_notes.txt
similarity index 100%
rename from BugsSite/docs/rel_notes.txt
rename to BugsSite/docs/en/rel_notes.txt
diff --git a/BugsSite/docs/xml/.cvsignore b/BugsSite/docs/en/xml/.cvsignore
similarity index 100%
rename from BugsSite/docs/xml/.cvsignore
rename to BugsSite/docs/en/xml/.cvsignore
diff --git a/BugsSite/docs/xml/Bugzilla-Guide.xml b/BugsSite/docs/en/xml/Bugzilla-Guide.xml
similarity index 89%
rename from BugsSite/docs/xml/Bugzilla-Guide.xml
rename to BugsSite/docs/en/xml/Bugzilla-Guide.xml
index 0de0f8d..4b77c10 100644
--- a/BugsSite/docs/xml/Bugzilla-Guide.xml
+++ b/BugsSite/docs/en/xml/Bugzilla-Guide.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
-                      "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" [
     <!ENTITY % myents SYSTEM "bugzilla.ent">
     %myents;
 
@@ -8,7 +7,6 @@
 <!ENTITY about SYSTEM "about.xml">
 <!ENTITY conventions SYSTEM "conventions.xml">
 <!ENTITY doc-index SYSTEM "index.xml">
-<!ENTITY faq SYSTEM "faq.xml">
 <!ENTITY gfdl SYSTEM "gfdl.xml">
 <!ENTITY glossary SYSTEM "glossary.xml">
 <!ENTITY installation SYSTEM "installation.xml">
@@ -27,6 +25,8 @@
      * bz-ver to current stable
      * bz-nexver to next stable
      * bz-date to the release date
+     * landfillbase to the branch install
+     * Remove the BZ-DEVEL comments
      - COMPILE DOCS AND CHECKIN -
        Also, tag and tarball before completing
      * bz-ver to devel version
@@ -34,18 +34,17 @@
      For a devel release, simple bump bz-ver and bz-date
 -->
 
-<!ENTITY bz-ver "3.0.3">
-<!ENTITY bz-nextver "3.2">
-<!ENTITY bz-date "2008-01-08">
-<!ENTITY current-year "2008">
+<!ENTITY bz-ver "3.2.3">
+<!ENTITY bz-nextver "3.4">
+<!ENTITY bz-date "2009-03-30">
+<!ENTITY current-year "2009">
 
-<!ENTITY landfillbase "http://landfill.bugzilla.org/bugzilla-3.0-branch/">
+<!ENTITY landfillbase "http://landfill.bugzilla.org/bugzilla-3.2-branch/">
 <!ENTITY bz "http://www.bugzilla.org/">
 <!ENTITY bzg-bugs "<ulink url='https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&amp;component=Documentation'>Bugzilla Documentation</ulink>">
 <!ENTITY mysql "http://www.mysql.com/">
 
-<!ENTITY min-perl-ver "5.8.0">
-<!ENTITY min-perl-ver-win "5.8.1">
+<!ENTITY min-perl-ver "5.8.1">
 ]>
 
 
@@ -76,7 +75,8 @@
 <!-- Header -->
 
   <bookinfo>
-    <title>The Bugzilla Guide - &bz-ver;  Release</title>
+    <title>The Bugzilla Guide - &bz-ver; 
+    Release</title>
 
     <authorgroup>
       <corpauthor>The Bugzilla Team</corpauthor>
@@ -132,9 +132,6 @@
 <!-- Customizing Bugzilla -->
 &customization;
 
-<!-- Appendix: The Frequently Asked Questions -->
-&faq;
-
 <!-- Appendix: Troubleshooting -->
 &troubleshooting;
 
diff --git a/BugsSite/docs/xml/about.xml b/BugsSite/docs/en/xml/about.xml
similarity index 98%
rename from BugsSite/docs/xml/about.xml
rename to BugsSite/docs/en/xml/about.xml
index 48e6be7..7bd4da4 100644
--- a/BugsSite/docs/xml/about.xml
+++ b/BugsSite/docs/en/xml/about.xml
@@ -1,6 +1,6 @@
 <!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
 <!ENTITY conventions SYSTEM "conventions.xml"> ] > -->
-<!-- $Id: about.xml,v 1.24.2.3 2007/08/02 07:22:33 wurblzap%gmail.com Exp $ -->
+<!-- $Id$ -->
 
 <chapter id="about">
 <title>About This Guide</title>
diff --git a/BugsSite/docs/en/xml/administration.xml b/BugsSite/docs/en/xml/administration.xml
new file mode 100644
index 0000000..2ed0376
--- /dev/null
+++ b/BugsSite/docs/en/xml/administration.xml
@@ -0,0 +1,3111 @@
+<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
+<chapter id="administration">
+  <title>Administering Bugzilla</title>
+
+  <section id="parameters">
+    <title>Bugzilla Configuration</title>
+
+    <para>
+      Bugzilla is configured by changing various parameters, accessed
+      from the "Parameters" link in the Administration page (the 
+      Administration page can be found by clicking the "Administration"
+      link in the footer). The parameters are divided into several categories,
+      accessed via the menu on the left. Following is a description of the 
+      different categories and important parameters within those categories. 
+    </para>
+
+      <section id="param-requiredsettings">
+        <title>Required Settings</title>
+
+        <para>
+          The core required parameters for any Bugzilla installation are set
+          here. These parameters must be set before a new Bugzilla installation
+          can be used. Administrators should review this list before 
+          deploying a new Bugzilla installation.
+        </para>
+
+        <indexterm>
+          <primary>checklist</primary>
+        </indexterm>
+
+        <variablelist>
+
+          <varlistentry>
+            <term>
+              maintainer
+            </term>
+            <listitem>
+              <para> 
+              Email address of the person 
+              responsible for maintaining this Bugzilla installation.
+              The address need not be that of a valid Bugzilla account.
+            </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              urlbase
+            </term>
+            <listitem>
+              <para>
+                Defines the fully qualified domain name and web 
+                server path to this Bugzilla installation.
+              </para>
+              <para>
+                For example, if the Bugzilla query page is
+                <filename>http://www.foo.com/bugzilla/query.cgi</filename>, 
+                the <quote>urlbase</quote> should be set
+                to <filename>http://www.foo.com/bugzilla/</filename>.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              docs_urlbase
+            </term>
+            <listitem>
+              <para>
+                Defines path to the Bugzilla documentation. This can be a fully
+                qualified domain name, or a path relative to "urlbase".
+              </para>
+              <para>
+                For example, if the "Bugzilla Configuration" page 
+                of the documentation is
+                <filename>http://www.foo.com/bugzilla/docs/html/parameters.html</filename>, 
+                set the <quote>docs_urlbase</quote>
+                to <filename>http://www.foo.com/bugzilla/docs/html/</filename>.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              sslbase
+            </term>
+            <listitem>
+              <para>
+                Defines the fully qualified domain name and web 
+                server path for HTTPS (SSL) connections to this Bugzilla installation.
+              </para>
+              <para>
+                For example, if the Bugzilla main page is
+                <filename>https://www.foo.com/bugzilla/index.cgi</filename>, 
+                the <quote>sslbase</quote> should be set
+                to <filename>https://www.foo.com/bugzilla/</filename>.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              ssl
+            </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". 
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              cookiedomain
+            </term>
+            <listitem>
+              <para>
+               Defines the domain for Bugzilla cookies. This is typically left blank.
+               If there are multiple hostnames that point to the same webserver, which
+               require the same cookie, then this parameter can be utilized. For
+               example, If your website is at 
+               <filename>https://www.foo.com/</filename>, setting this to 
+               <filename>.foo.com/</filename> will also allow 
+               <filename>bar.foo.com/</filename> to access Bugzilla cookies.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              cookiepath
+            </term>
+            <listitem>
+              <para>
+                Defines a path, relative to the web server root, that Bugzilla
+                cookies will be restricted to. For example, if the 
+                <command>urlbase</command> is set to
+                <filename>http://www.foo.com/bugzilla/</filename>, the 
+                <command>cookiepath</command> should be set to 
+                <filename>/bugzilla/</filename>. Setting it to "/" will allow all sites 
+                served by this web server or virtual host to read Bugzilla cookies.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              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>
+              <para>
+               Determines whether to use UTF-8 (Unicode) encoding for all text in 
+               Bugzilla. New installations should set this to true to avoid character 
+               encoding problems. Existing databases should set this to true only 
+               after the data has been converted from existing legacy character 
+               encoding to UTF-8, using the 
+               <filename>contrib/recode.pl</filename> script.
+              </para>
+              <note>
+                <para>
+                  If you turn this parameter from "off" to "on", you must re-run 
+                  <filename>checksetup.pl</filename> immediately afterward.
+                </para>
+              </note>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              shutdownhtml
+            </term>
+            <listitem>
+              <para>
+                If there is any text in this field, this Bugzilla installation will
+                be completely disabled and this text will appear instead of all
+                Bugzilla pages for all users, including Admins. Used in the event 
+                of site maintenance or outage situations. 
+              </para>
+              <note>
+                <para>
+                  Although regular log-in capability is disabled while 
+                  <command>shutdownhtml</command>
+                  is enabled, safeguards are in place to protect the unfortunate 
+                  admin who loses connection to Bugzilla. Should this happen to you,
+                  go directly to the <filename>editparams.cgi</filename> (by typing
+                  the URL in manually, if necessary). Doing this will prompt you to
+                  log in, and your name/password will be accepted here (but nowhere
+                  else). 
+                </para>
+              </note>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              announcehtml
+            </term>
+            <listitem>
+              <para>
+                Any text in this field will be displayed at the top of every HTML
+                page in this Bugzilla installation. The text is not wrapped in any
+                tags. For best results, wrap the text in a <quote>&lt;div&gt;</quote>
+                tag. Any style attributes from the CSS can be applied. For example,
+                to make the text green inside of a red box, add <quote>id=message</quote>
+                to the <quote>&lt;div&gt;</quote> tag.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              proxy_url
+            </term>
+            <listitem>
+              <para>
+                If this Bugzilla installation is behind a proxy, enter the proxy 
+                information here to enable Bugzilla to access the Internet. Bugzilla
+                requires Internet access to utilize the 
+                <command>upgrade_notification</command> parameter (below). If the 
+                proxy requires authentication, use the syntax:
+                <filename>http://user:pass@proxy_url/</filename>.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              upgrade_notification
+            </term>
+            <listitem>
+              <para>
+                Enable or disable a notification on the homepage of this Bugzilla
+                installation when a newer version of Bugzilla is available. This
+                notification is only visible to administrators. Choose "disabled",
+                to turn off the notification. Otherwise, choose which version of
+                Bugzilla you want to be notified about: "development_snapshot" is the
+                latest release on the trunk; "latest_stable_release" is the most 
+                recent release available on the most recent stable branch; 
+                "stable_branch_release" the most recent release on the branch 
+                this installation is based on.
+              </para>
+            </listitem>
+          </varlistentry>
+
+        </variablelist>
+
+        </section>
+
+        <section id="param-admin-policies">
+          <title>Administrative Policies</title>
+        <para>
+        This page contains parameters for basic administrative functions.
+        Options include whether to allow the deletion of bugs and users, 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>
+
+        </section>
+
+        <section id="param-user-authentication">
+          <title>User Authentication</title>
+          <para>
+            This page contains the settings that control how this Bugzilla
+            installation will do its authentication. Choose what authentication
+            mechanism to use (the Bugzilla database, or an external source such
+            as LDAP), and set basic behavioral parameters. For example, choose
+            whether to require users to login to browse bugs, the management
+            of authentication cookies, and the regular expression used to
+            validate email addresses. Some parameters are highlighted below.
+          </para>
+
+        <variablelist>
+
+          <varlistentry>
+            <term>
+              emailregexp
+            </term>
+            <listitem>
+              <para>
+                Defines the regular expression used to validate email addresses
+                used for login names. The default attempts to match fully
+                qualified email addresses (i.e. 'user@example.com'). Some
+                Bugzilla installations allow only local user names (i.e 'user' 
+                instead of 'user@example.com'). In that case, the 
+                <command>emailsuffix</command> parameter should be used to define
+                the email domain.    
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              emailsuffix
+            </term>
+            <listitem>
+              <para>
+                This string is appended to login names when actually sending 
+                email to a user. For example,
+                If <command>emailregexp</command> has been set to allow
+                local usernames,
+                then this parameter would contain the email domain for all users
+                (i.e. '@example.com').   
+              </para>
+            </listitem>
+          </varlistentry>
+
+        </variablelist>
+
+        </section>
+
+        <section id="param-attachments">
+          <title>Attachments</title>
+          <para>
+            This page allows for setting restrictions and other parameters
+            regarding attachments to bugs. For example, control size limitations
+            and whether to allow pointing to external files via a URI.
+          </para>
+        </section>
+
+        <section id="param-bug-change-policies">
+          <title>Bug Change Policies</title>
+          <para>
+            Set policy on default behavior for bug change events. For example,
+            choose which status to set a bug to when it is marked as a duplicate,
+            and choose whether to allow bug reporters to set the priority or
+            target milestone. Also allows for configuration of what changes
+            should require the user to make a comment, described below.    
+          </para>
+
+        <variablelist>
+
+          <varlistentry>
+            <term>
+              commenton*
+            </term>
+            <listitem>
+              <para>
+                All these fields allow you to dictate what changes can pass
+                without comment, and which must have a comment from the
+                person who changed them.  Often, administrators will allow
+                users to add themselves to the CC list, accept bugs, or
+                change the Status Whiteboard without adding a comment as to
+                their reasons for the change, yet require that most other
+                changes come with an explanation.
+              </para>
+
+              <para>
+                Set the "commenton" options according to your site policy. It
+                is a wise idea to require comments when users resolve, reassign, or
+                reopen bugs at the very least. 
+              </para>
+
+              <note>
+                <para>
+                  It is generally far better to require a developer comment
+                  when resolving bugs than not. Few things are more annoying to bug
+                  database users than having a developer mark a bug "fixed" without
+                  any comment as to what the fix was (or even that it was truly
+                  fixed!)
+                </para>
+              </note>
+            </listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>
+              noresolveonopenblockers
+            </term>
+            <listitem>
+              <para>
+                This option will prevent users from resolving bugs as FIXED if
+                they have unresolved dependencies. Only the FIXED resolution
+                is affected. Users will be still able to resolve bugs to
+                resolutions other than FIXED if they have unresolved dependent
+                bugs.
+              </para>
+            </listitem>
+          </varlistentry>
+
+        </variablelist>
+
+        </section>
+
+        <section id="param-bugfields">
+          <title>Bug Fields</title>
+          <para>
+            The parameters in this section determine the default settings of
+            several Bugzilla fields for new bugs, and also control whether
+            certain fields are used. For example, choose whether to use the
+            "target milestone" field or the "status whiteboard" field.
+          </para>
+
+        <variablelist>
+
+          <varlistentry>    
+            <term>
+              useqacontact
+            </term>
+            <listitem>
+              <para>
+                This allows you to define an email address for each component, 
+                in addition to that of the default assignee, who will be sent
+                carbon copies of incoming bugs.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              usestatuswhiteboard
+            </term>
+            <listitem>
+              <para>
+                This defines whether you wish to have a free-form, overwritable field
+                associated with each bug. The advantage of the Status Whiteboard is
+                that it can be deleted or modified with ease, and provides an
+                easily-searchable field for indexing some bugs that have some trait
+                in common.         
+              </para>
+            </listitem>
+          </varlistentry>
+
+        </variablelist>
+
+        </section>
+
+        <section id="param-bugmoving">
+          <title>Bug Moving</title>
+          <para>
+            This page controls whether this Bugzilla installation allows certain
+            users to move bugs to an external database. If bug moving is enabled,
+            there are a number of parameters that control bug moving behaviors. 
+            For example, choose which users are allowed to move bugs, the location
+            of the external database, and the default product and component that
+            bugs moved <emphasis>from</emphasis> other bug databases to this 
+            Bugzilla installation are assigned to. 
+          </para>
+
+        </section>
+
+        <section id="param-dependency-graphs">
+          <title>Dependency Graphs</title>
+          <para>
+            This page has one parameter that sets the location of a Web Dot
+            server, or of the Web Dot binary on the local system, that is used
+            to generate dependency graphs. Web Dot is a CGI program that creates
+            images from <filename>.dot</filename> graphic description files. If
+            no Web Dot server or binary is specified, then dependency graphs will
+            be disabled.
+          </para>
+        </section>
+
+        <section id="param-group-security">
+          <title>Group Security</title>
+            <para>
+              Bugzilla allows for the creation of different groups, with the
+              ability to restrict the visibility of bugs in a group to a set of 
+              specific users. Specific products can also be associated with
+              groups, and users restricted to only see products in their groups.
+              Several parameters are described in more detail below. Most of the
+              configuration of groups and their relationship to products is done
+              on the "Groups" and "Product" pages of the "Administration" area.
+              The options on this page control global default behavior. 
+              For more information on Groups and Group Security, see
+              <xref linkend="groups"/> 
+            </para>
+
+        <variablelist>
+
+          <varlistentry>
+            <term>
+              makeproductgroups
+            </term>
+            <listitem>
+              <para>
+                Determines whether or not to automatically create groups
+                when new products are created. If this is on, the groups will be
+                used for querying bugs.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              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>
+              <para>
+                If selected, user visibility will be restricted to members of
+                groups, as selected in the group configuration settings. 
+                Each user-defined group can be allowed to see members of selected
+                other groups. 
+                For details on configuring groups (including the visibility 
+                restrictions) see <xref linkend="edit-groups"/>. 
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              querysharegroup
+            </term>
+            <listitem>
+              <para>
+                The name of the group of users who are allowed to share saved
+                searches with one another. For more information on using 
+                saved searches, see <xref linkend="savedsearches"/>.
+              </para>
+            </listitem>
+          </varlistentry>
+
+        </variablelist>
+    
+        </section>
+
+        <section id="bzldap">
+          <title>LDAP Authentication</title>
+
+          <para>LDAP authentication is a module for Bugzilla's plugin 
+          authentication architecture. This page contains all the parameters
+          necessary to configure Bugzilla for use with LDAP authentication.
+          </para>
+
+          <para>
+          The existing authentication
+          scheme for Bugzilla uses email addresses as the primary user ID, and a
+          password to authenticate that user. All places within Bugzilla that
+          require a user ID (e.g assigning a bug) use the email
+          address. The LDAP authentication builds on top of this scheme, rather
+          than replacing it. The initial log-in is done with a username and
+          password for the LDAP directory. Bugzilla tries to bind to LDAP using
+          those credentials and, if successful, tries to map this account to a
+          Bugzilla account. If an LDAP mail attribute is defined, the value of this
+          attribute is used, otherwise the "emailsuffix" parameter is appended to LDAP
+          username to form a full email address. If an account for this address
+          already exists in the Bugzilla installation, it will log in to that account.
+          If no account for that email address exists, one is created at the time
+          of login. (In this case, Bugzilla will attempt to use the "displayName"
+          or "cn" attribute to determine the user's full name.) After
+          authentication, all other user-related tasks are still handled by email
+          address, not LDAP username. For example, bugs are still assigned by
+          email address and users are still queried by email address.
+          </para>
+
+          <caution>
+            <para>Because the Bugzilla account is not created until the first time
+            a user logs in, a user who has not yet logged is unknown to Bugzilla.
+            This means they cannot be used as an assignee or QA contact (default or
+            otherwise), added to any CC list, or any other such operation. One
+            possible workaround is the <filename>bugzilla_ldapsync.rb</filename>
+            script in the
+            <glossterm linkend="gloss-contrib">
+            <filename class="directory">contrib</filename></glossterm> 
+            directory. Another possible solution is fixing
+            <ulink url="https://bugzilla.mozilla.org/show_bug.cgi?id=201069">bug
+            201069</ulink>.
+            </para>
+          </caution>
+
+          <para>Parameters required to use LDAP Authentication:</para>
+
+          <variablelist>
+            <varlistentry id="param-user_verify_class_for_ldap">
+              <term>user_verify_class</term>
+              <listitem>
+                <para>If you want to list <quote>LDAP</quote> here,
+                make sure to have set up the other parameters listed below.
+                Unless you have other (working) authentication methods listed as
+                well, you may otherwise not be able to log back in to Bugzilla once
+                you log out.
+                If this happens to you, you will need to manually edit
+                <filename>data/params</filename> and set user_verify_class to
+                <quote>DB</quote>.
+                </para>
+              </listitem>
+            </varlistentry>
+    
+            <varlistentry id="param-LDAPserver">
+              <term>LDAPserver</term>
+              <listitem>
+                <para>This parameter should be set to the name (and optionally the
+                port) of your LDAP server. If no port is specified, it assumes
+                the default LDAP port of 389.
+                </para>
+                <para>For example: <quote>ldap.company.com</quote>
+                 or <quote>ldap.company.com:3268</quote>
+                </para>
+                <para>You can also specify a LDAP URI, so as to use other
+                protocols, such as LDAPS or LDAPI. If port was not specified in
+                the URI, the default is either 389 or 636 for 'LDAP' and 'LDAPS'
+                schemes respectively.
+                </para>
+                <tip>
+                  <para>
+                    In order to use SSL with LDAP, specify a URI with "ldaps://".
+                    This will force the use of SSL over port 636.
+                  </para>
+                </tip>
+                <para>For example, normal LDAP: 
+                <quote>ldap://ldap.company.com</quote>, LDAP over SSL:
+                <quote>ldaps://ldap.company.com</quote> or LDAP over a UNIX 
+                domain socket <quote>ldapi://%2fvar%2flib%2fldap_sock</quote>.
+                </para>
+               </listitem>
+             </varlistentry>
+    
+             <varlistentry id="param-LDAPbinddn">
+               <term>LDAPbinddn [Optional]</term>
+               <listitem>
+                 <para>Some LDAP servers will not allow an anonymous bind to search
+                 the directory. If this is the case with your configuration you
+                 should set the LDAPbinddn parameter to the user account Bugzilla
+                 should use instead of the anonymous bind.
+                 </para>
+                 <para>Ex. <quote>cn=default,cn=user:password</quote></para>
+               </listitem>
+             </varlistentry>
+    
+             <varlistentry id="param-LDAPBaseDN">
+               <term>LDAPBaseDN</term>
+               <listitem>
+                 <para>The LDAPBaseDN parameter should be set to the location in
+                 your LDAP tree that you would like to search for email addresses.
+                 Your uids should be unique under the DN specified here.
+                 </para>
+                 <para>Ex. <quote>ou=People,o=Company</quote></para>
+               </listitem>
+             </varlistentry>
+    
+             <varlistentry id="param-LDAPuidattribute">
+               <term>LDAPuidattribute</term>
+               <listitem>
+                 <para>The LDAPuidattribute parameter should be set to the attribute
+                 which contains the unique UID of your users. The value retrieved
+                 from this attribute will be used when attempting to bind as the
+                 user to confirm their password.
+                 </para>
+                 <para>Ex. <quote>uid</quote></para>
+               </listitem>
+             </varlistentry>
+
+             <varlistentry id="param-LDAPmailattribute">
+               <term>LDAPmailattribute</term>
+               <listitem>
+                 <para>The LDAPmailattribute parameter should be the name of the
+                 attribute which contains the email address your users will enter
+                 into the Bugzilla login boxes.
+                 </para>
+                 <para>Ex. <quote>mail</quote></para>
+               </listitem>
+              </varlistentry>
+          </variablelist>
+
+        </section>
+
+        <section id="bzradius">
+          <title>RADIUS Authentication</title>
+
+          <para>
+          RADIUS authentication is a module for Bugzilla's plugin 
+          authentication architecture. This page contains all the parameters
+          necessary for configuring Bugzilla to use RADIUS authentication.
+          </para>
+          <note>
+            <para>
+              Most caveats that apply to LDAP authentication apply to RADIUS
+              authentication as well. See <xref linkend="bzldap"/> for details.
+            </para>
+          </note>
+
+          <para>Parameters required to use RADIUS Authentication:</para>
+
+          <variablelist>
+            <varlistentry id="param-user_verify_class_for_radius">
+              <term>user_verify_class</term>
+              <listitem>
+                <para>If you want to list <quote>RADIUS</quote> here,
+                make sure to have set up the other parameters listed below.
+                Unless you have other (working) authentication methods listed as
+                well, you may otherwise not be able to log back in to Bugzilla once
+                you log out.
+                If this happens to you, you will need to manually edit
+                <filename>data/params</filename> and set user_verify_class to
+                <quote>DB</quote>.
+                </para>
+              </listitem>
+            </varlistentry>
+    
+            <varlistentry id="param-RADIUS_server">
+              <term>RADIUS_server</term>
+              <listitem>
+                <para>This parameter should be set to the name (and optionally the
+                port) of your RADIUS server.
+                </para>
+              </listitem>
+            </varlistentry>
+    
+            <varlistentry id="param-RADIUS_secret">
+              <term>RADIUS_secret</term>
+              <listitem>
+                <para>This parameter should be set to the RADIUS server's secret.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry id="param-RADIUS_email_suffix">
+              <term>RADIUS_email_suffix</term>
+              <listitem>
+                <para>Bugzilla needs an e-mail address for each user account.
+                Therefore, it needs to determine the e-mail address corresponding
+                to a RADIUS user.
+                Bugzilla offers only a simple way to do this: it can concatenate
+                a suffix to the RADIUS user name to convert it into an e-mail
+                address.
+                You can specify this suffix in the RADIUS_email_suffix parameter.
+                </para>
+                <para>If this simple solution does not work for you, you'll
+                probably need to modify
+                <filename>Bugzilla/Auth/Verify/RADIUS.pm</filename> to match your
+                requirements.
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+
+        </section>
+    
+        <section id="param-email">
+          <title>Email</title>
+          <para>
+            This page contains all of the parameters for configuring how
+            Bugzilla deals with the email notifications it sends. See below
+            for a summary of important options. 
+          </para>
+
+        <variablelist>
+
+          <varlistentry>
+            <term>
+              mail_delivery_method
+            </term>
+            <listitem>
+              <para>
+                This is used to specify how email is sent, or if it is sent at 
+                all.  There are several options included for different MTAs, 
+                along with two additional options that disable email sending.  
+                "Test" does not send mail, but instead saves it in 
+                <filename>data/mailer.testfile</filename> for later review.  
+                "None" disables email sending entirely.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              mailfrom
+            </term>
+            <listitem>
+              <para>
+                This is the email address that will appear in the "From" field
+                of all emails sent by this Bugzilla installation. Some email
+                servers require mail to be from a valid email address, therefore
+                it is recommended to choose a valid email address here.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              sendmailnow
+            </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).
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              whinedays
+            </term>
+            <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
+                untouched new bugs. If you do not plan to use this feature, simply 
+                do not set up the whining cron job described in the installation
+                instructions, or set this value to "0" (never whine).
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>
+              globalwatcher
+            </term>
+            <listitem>
+              <para>
+                This allows you to define specific users who will
+                receive notification each time a new bug in entered, or when
+                an existing bug changes, according to the normal groupset
+                permissions. It may be useful for sending notifications to a
+                mailing-list, for instance.
+              </para>
+            </listitem>
+          </varlistentry>
+
+        </variablelist>
+
+        </section>
+
+        <section id="param-patchviewer">
+          <title>Patch Viewer</title>
+          <para>
+            This page contains configuration parameters for the CVS server, 
+            Bonsai server and LXR server that Bugzilla will use to enable the
+            features of the Patch Viewer. Bonsai is a tool that enables queries 
+            to a CVS tree. LXR is a tool that can cross reference and index source
+            code.
+          </para>
+        </section>
+
+        <section id="param-querydefaults">
+          <title>Query Defaults</title>
+          <para>
+            This page controls the default behavior of Bugzilla in regards to 
+            several aspects of querying bugs. Options include what the default
+            query options are, what the "My Bugs" page returns, whether users
+            can freely add bugs to the quip list, and how many duplicate bugs are 
+            needed to add a bug to the "most frequently reported" list.
+          </para>
+        </section>
+
+        <section id="param-shadowdatabase">
+          <title>Shadow Database</title>
+          <para>
+            This page controls whether a shadow database is used, and all the
+            parameters associated with the shadow database. Versions of Bugzilla
+            prior to 3.2 used the MyISAM table type, which supports
+            only table-level write locking. With MyISAM, any time someone is making a change to 
+            a bug, the entire table is locked until the write operation is complete.
+            Locking for write also blocks reads until the write is complete.
+          </para> 
+          <para>
+            The <quote>shadowdb</quote> parameter was designed to get around
+            this limitation. While only a single user is allowed to write to
+            a table at a time, reads can continue unimpeded on a read-only
+            shadow copy of the database.
+          </para>
+
+          <note>
+            <para>
+              As of version 3.2, Bugzilla no longer uses the MyISAM table type.
+              Instead, InnoDB is used, which can do transaction-based locking.
+              Therefore, the limitations the Shadow Database feature was designed
+              to workaround no longer exist.
+            </para>
+          </note>
+
+        </section>
+
+        <section id="admin-usermatching">
+          <title>User Matching</title>
+          <para>
+            The settings on this page control how users are selected and queried
+            when adding a user to a bug. For example, users need to be selected
+            when choosing who the bug is assigned to, adding to the CC list or 
+            selecting a QA contact. With the "usemenuforusers" parameter, it is 
+            possible to configure Bugzilla to 
+            display a list of users in the fields instead of an empty text field.
+            This should only be used in Bugzilla installations with a small number
+            of users. If users are selected via a text box, this page also
+            contains parameters for how user names can be queried and matched
+            when entered.
+          </para>
+
+        </section>
+
+  </section>
+
+  <section id="useradmin">
+    <title>User Administration</title>
+
+    <section id="defaultuser">
+      <title>Creating the Default User</title>
+
+      <para>When you first run checksetup.pl after installing Bugzilla, it
+      will prompt you for the administrative username (email address) and
+      password for this "super user". If for some reason you delete
+      the "super user" account, re-running checksetup.pl will again prompt
+      you for this username and password.</para>
+
+      <tip>
+        <para>If you wish to add more administrative users, add them to 
+        the "admin" group and, optionally, edit the tweakparams, editusers,
+        creategroups, editcomponents, and editkeywords groups to add the
+        entire admin group to those groups (which is the case by default).
+        </para>
+      </tip>
+    </section>
+
+    <section id="manageusers">
+      <title>Managing Other Users</title>
+
+      <section id="user-account-search">
+        <title>Searching for existing users</title>
+
+        <para>
+          If you have <quote>editusers</quote> privileges or if you are allowed
+          to grant privileges for some groups, the <quote>Users</quote> link
+          will appear in the Administration page.
+        </para>
+
+        <para>
+          The first screen is a search form to search for existing user
+          accounts. You can run searches based either on the user ID, real
+          name or login name (i.e. the email address, or just the first part
+          of the email address if the "emailsuffix" parameter is set).
+          The search can be conducted
+          in different ways using the listbox to the right of the text entry
+          box. You can match by case-insensitive substring (the default),
+          regular expression, a <emphasis>reverse</emphasis> regular expression
+          match (which finds every user name which does NOT match the regular
+          expression), or the exact string if you know exactly who you are
+          looking for. The search can be restricted to users who are in a
+          specific group. By default, the restriction is turned off.
+        </para>
+
+        <para>
+          The search returns a list of
+          users matching your criteria. User properties can be edited by clicking
+          the login name. The Account History of a user can be viewed by clicking
+          the "View" link in the Account History column. The Account History
+          displays changes that have been made to the user account, the time of
+          the change and the user who made the change. For example, the Account
+          History page will display details of when a user was added or removed
+          from a group.
+        </para>
+      </section>
+
+      <section id="createnewusers">
+        <title>Creating new users</title>
+
+        <section id="self-registration">
+          <title>Self-registration</title>
+
+          <para>
+            By default, users can create their own user accounts by clicking the
+            <quote>New Account</quote> link at the bottom of each page (assuming
+            they aren't logged in as someone else already). If you want to disable
+            this self-registration, or if you want to restrict who can create his
+            own user account, you have to edit the <quote>createemailregexp</quote>
+            parameter in the <quote>Configuration</quote> page, see
+            <xref linkend="parameters" />.
+          </para>
+        </section>
+
+        <section id="user-account-creation">
+          <title>Accounts created by an administrator</title>
+
+          <para>
+            Users with <quote>editusers</quote> privileges, such as administrators,
+            can create user accounts for other users:
+          </para>
+
+          <orderedlist>
+            <listitem>
+              <para>After logging in, click the "Users" link at the footer of
+              the query page, and then click "Add a new user".</para>
+            </listitem>
+
+            <listitem>
+              <para>Fill out the form presented. This page is self-explanatory.
+              When done, click "Submit".</para>
+
+              <note>
+                <para>Adding a user this way will <emphasis>not</emphasis>
+                send an email informing them of their username and password.
+                While useful for creating dummy accounts (watchers which
+                shuttle mail to another system, for instance, or email
+                addresses which are a mailing list), in general it is
+                preferable to log out and use the <quote>New Account</quote>
+                button to create users, as it will pre-populate all the
+                required fields and also notify the user of her account name
+                and password.</para>
+              </note>
+            </listitem>
+          </orderedlist>
+        </section>
+      </section>
+
+      <section id="modifyusers">
+        <title>Modifying Users</title>
+
+        <para>Once you have found your user, you can change the following
+        fields:</para>
+
+        <itemizedlist>
+          <listitem>
+            <para>
+            <emphasis>Login Name</emphasis>: 
+            This is generally the user's full email address. However, if you
+            have are using the <quote>emailsuffix</quote> parameter, this may
+            just be the user's login name. Note that users can now change their
+            login names themselves (to any valid email address).
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+            <emphasis>Real Name</emphasis>: The user's real name. Note that
+            Bugzilla does not require this to create an account.</para>
+          </listitem>
+
+          <listitem>
+            <para>
+            <emphasis>Password</emphasis>: 
+            You can change the user's password here. Users can automatically
+            request a new password, so you shouldn't need to do this often.
+            If you want to disable an account, see Disable Text below.
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+              <emphasis>Bugmail Disabled</emphasis>:
+              Mark this checkbox to disable bugmail and whinemail completely
+              for this account. This checkbox replaces the data/nomail file
+              which existed in older versions of Bugzilla.
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+              <emphasis>Disable Text</emphasis>: 
+              If you type anything in this box, including just a space, the
+              user is prevented from logging in, or making any changes to 
+              bugs via the web interface. 
+              The HTML you type in this box is presented to the user when
+              they attempt to perform these actions, and should explain
+              why the account was disabled.
+            </para>
+            <para>
+              Users with disabled accounts will continue to receive
+              mail from Bugzilla; furthermore, they will not be able
+              to log in themselves to change their own preferences and
+              stop it. If you want an account (disabled or active) to
+              stop receiving mail, simply check the
+              <quote>Bugmail Disabled</quote> checkbox above.
+            </para>
+            <note>
+              <para>
+                Even users whose accounts have been disabled can still
+                submit bugs via the e-mail gateway, if one exists.
+                The e-mail gateway should <emphasis>not</emphasis> be
+                enabled for secure installations of Bugzilla.
+              </para>
+            </note>
+            <warning>
+              <para>
+                Don't disable all the administrator accounts!
+              </para>
+            </warning>
+          </listitem>
+
+          <listitem>
+            <para>
+            <emphasis>&lt;groupname&gt;</emphasis>: 
+            If you have created some groups, e.g. "securitysensitive", then
+            checkboxes will appear here to allow you to add users to, or
+            remove them from, these groups.
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+            <emphasis>canconfirm</emphasis>: 
+            This field is only used if you have enabled the "unconfirmed"
+            status. If you enable this for a user,
+            that user can then move bugs from "Unconfirmed" to a "Confirmed"
+            status (e.g.: "New" status).</para>
+          </listitem>
+
+          <listitem>
+            <para>
+            <emphasis>creategroups</emphasis>: 
+            This option will allow a user to create and destroy groups in
+            Bugzilla.</para>
+          </listitem>
+
+          <listitem>
+            <para>
+            <emphasis>editbugs</emphasis>: 
+            Unless a user has this bit set, they can only edit those bugs
+            for which they are the assignee or the reporter. Even if this
+            option is unchecked, users can still add comments to bugs.
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+            <emphasis>editcomponents</emphasis>: 
+            This flag allows a user to create new products and components,
+            as well as modify and destroy those that have no bugs associated
+            with them. If a product or component has bugs associated with it,
+            those bugs must be moved to a different product or component
+            before Bugzilla will allow them to be destroyed.
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+            <emphasis>editkeywords</emphasis>: 
+            If you use Bugzilla's keyword functionality, enabling this
+            feature allows a user to create and destroy keywords. As always,
+            the keywords for existing bugs containing the keyword the user
+            wishes to destroy must be changed before Bugzilla will allow it
+            to die.</para>
+          </listitem>
+
+          <listitem>
+            <para>
+            <emphasis>editusers</emphasis>: 
+            This flag allows a user to do what you're doing right now: edit
+            other users. This will allow those with the right to do so to
+            remove administrator privileges from other users or grant them to
+            themselves. Enable with care.</para>
+          </listitem>
+
+
+          <listitem>
+            <para>
+            <emphasis>tweakparams</emphasis>: 
+            This flag allows a user to change Bugzilla's Params 
+            (using <filename>editparams.cgi</filename>.)</para>
+          </listitem>
+
+          <listitem>
+            <para> 
+            <emphasis>&lt;productname&gt;</emphasis>:
+            This allows an administrator to specify the products 
+            in which a user can see bugs. If you turn on the 
+            <quote>makeproductgroups</quote> parameter in
+            the Group Security Panel in the Parameters page, 
+            then Bugzilla creates one group per product (at the time you create 
+            the product), and this group has exactly the same name as the 
+            product itself. Note that for products that already exist when
+            the parameter is turned on, the corresponding group will not be
+            created. The user must still have the <quote>editbugs</quote> 
+            privilege to edit bugs in these products.</para>
+          </listitem>
+        </itemizedlist>
+      </section>
+
+      <section id="user-account-deletion">
+        <title>Deleting Users</title>
+        <para>
+          If the <quote>allowuserdeletion</quote> parameter is turned on, see
+          <xref linkend="parameters" />, then you can also delete user accounts.
+          Note that this is most of the time not the best thing to do. If only
+          a warning in a yellow box is displayed, then the deletion is safe.
+          If a warning is also displayed in a red box, then you should NOT try
+          to delete the user account, else you will get referential integrity
+          problems in your database, which can lead to unexpected behavior,
+          such as bugs not appearing in bug lists anymore, or data displaying
+          incorrectly. You have been warned!
+        </para>
+      </section>
+
+      <section id="impersonatingusers">
+        <title>Impersonating Users</title>
+        
+        <para>
+        There may be times when an administrator would like to do something as
+        another user.  The <command>sudo</command> feature may be used to do 
+        this.
+        </para>
+        
+        <note>
+          <para>
+          To use the sudo feature, you must be in the
+          <emphasis>bz_sudoers</emphasis> group.  By default, all
+          administrators are in this group.</para>
+        </note>
+        
+        <para>
+        If you have access to this feature, you may start a session by
+        going to the Edit Users page, Searching for a user and clicking on 
+        their login.  You should see a link below their login name titled 
+        "Impersonate this user".  Click on the link.  This will take you 
+        to a page where you will see a description of the feature and 
+        instructions for using it.  After reading the text, simply 
+        enter the login of the user you would like to impersonate, provide 
+        a short message explaining why you are doing this, and press the 
+        button.</para>
+        
+        <para>
+        As long as you are using this feature, everything you do will be done 
+        as if you were logged in as the user you are impersonating.</para>
+        
+        <warning>
+          <para>
+          The user you are impersonating will not be told about what you are 
+          doing.  If you do anything that results in mail being sent, that 
+          mail will appear to be from the user you are impersonating.  You 
+          should be extremely careful while using this feature.</para>
+        </warning>
+      </section>
+    </section>
+  </section>
+
+  <section id="classifications" xreflabel="Classifications">
+    <title>Classifications</title>
+
+    <para>Classifications tend to be used in order to group several related
+    products into one distinct entity.</para>
+
+    <para>The classifications layer is disabled by default; it can be turned
+    on or off using the useclassification parameter,
+    in the <emphasis>Bug Fields</emphasis> section of the edit parameters screen.</para>
+
+    <para>Access to the administration of classifications is controlled using
+    the <emphasis>editclassifications</emphasis> system group, which defines
+    a privilege for creating, destroying, and editing classifications.</para>
+
+    <para>When activated, classifications will introduce an additional
+    step when filling bugs (dedicated to classification selection), and they
+    will also appear in the advanced search form.</para>
+  </section>
+
+  <section id="products" xreflabel="Products">
+    <title>Products</title>
+
+    <para>
+    <glossterm linkend="gloss-product" baseform="product">
+    Products</glossterm> typically represent real-world
+    shipping products. Products can be given 
+    <xref linkend="classifications"/>. 
+    For example, if a company makes computer games, 
+    they could have a classification of "Games", and a separate
+    product for each game. This company might also have a 
+    <quote>Common</quote> product for units of technology used 
+    in multiple games, and perhaps a few special products that
+    represent items that are not actually shipping products 
+    (for example, "Website", or "Administration").
+    </para>
+
+    <para>
+    Many of Bugzilla's settings are configurable on a per-product
+    basis. The number of <quote>votes</quote> available to 
+    users is set per-product, as is the number of votes
+    required to move a bug automatically from the UNCONFIRMED 
+    status to the NEW status.
+    </para>
+
+    <para>
+    When creating or editing products the following options are
+    available: 
+    </para> 
+
+    <variablelist>
+
+      <varlistentry>
+        <term>
+          Product
+        </term>
+        <listitem>
+          <para> 
+            The name of the product
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          Description
+        </term>
+        <listitem>
+          <para> 
+            A brief description of the product
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          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>
+          <para> 
+            Select the default milestone for this product.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          Closed for bug entry
+        </term>
+        <listitem>
+          <para> 
+            Select this box to prevent new bugs from being
+            entered against this product.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          Maximum votes per person
+        </term>
+        <listitem>
+          <para> 
+            Maximum votes a user is allowed to give for this 
+            product
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          Maximum votes a person can put on a single bug
+        </term>
+        <listitem>
+          <para> 
+            Maximum votes a user is allowed to give for this 
+            product in a single bug
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          Confirmation threshold
+        </term>
+        <listitem>
+          <para> 
+            Number of votes needed to automatically remove any
+            bug against this product from the UNCONFIRMED state
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          Version
+        </term>
+        <listitem>
+          <para> 
+            Specify which version of the product bugs will be
+            entered against.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          Create chart datasets for this product
+        </term>
+        <listitem>
+          <para> 
+            Select to make chart datasets available for this product.
+          </para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+
+    <para>
+    When editing a product there is also a link to edit Group Access Controls,
+    see <xref linkend="product-group-controls"/>.
+    </para>
+
+    <section id="create-product">
+      <title>Creating New Products</title>
+
+    <para>
+    To create a new product:
+    </para>
+
+    <orderedlist>
+      <listitem>
+        <para> 
+        Select <quote>Administration</quote> from the footer and then
+        choose <quote>Products</quote> from the main administration page.
+        </para>
+      </listitem>
+
+      <listitem>
+        <para>
+        Select the <quote>Add</quote> link in the bottom right.
+        </para>
+      </listitem>
+
+      <listitem>
+        <para>
+        Enter the name of the product and a description. The
+        Description field may contain HTML.
+        </para>
+      </listitem>
+
+      <listitem>
+        <para>
+        When the product is created, Bugzilla will give a message
+        stating that a component must be created before any bugs can
+        be entered against the new product. Follow the link to create
+        a new component. See <xref linkend="components"/> for more
+        information.
+        </para>
+      </listitem>
+    </orderedlist>
+
+    </section>
+
+    <section id="edit-products">
+      <title>Editing Products</title>
+
+      <para>
+      To edit an existing product, click the "Products" link from the 
+      "Administration" page. If the 'useclassification' parameter is
+      turned on, a table of existing classifications is displayed,
+      including an "Unclassified" category. The table indicates how many products
+      are in each classification. Click on the classification name to see its
+      products. If the 'useclassification' parameter is not in use, the table 
+      lists all products directly. The product table summarizes the information 
+      about the product defined
+      when the product was created. Click on the product name to edit these
+      properties, and to access links to other product attributes such as the
+      product's components, versions, milestones, and group access controls.
+      </para>
+
+      </section>
+
+      <section id="comps-vers-miles-products">
+        <title>Adding or Editing Components, Versions and Target Milestones</title>
+        <para>
+          To edit existing, or add new, Components, Versions or Target Milestones 
+          to a Product, select the "Edit Components", "Edit Versions" or "Edit
+          Milestones" links from the "Edit Product" page. A table of existing 
+          Components, Versions or Milestones is displayed. Click on a item name 
+          to edit the properties of that item. Below the table is a link to add 
+          a new Component, Version or Milestone. 
+        </para>
+        <para>
+          For more information on components, see <xref linkend="components"/>.
+        </para>
+        <para>
+          For more information on versions, see <xref linkend="versions"/>.
+        </para>
+        <para>
+          For more information on milestones, see <xref linkend="milestones"/>.
+        </para>
+      </section>
+
+      <section id="product-group-controls">
+        <title>Assigning Group Controls to Products</title>
+
+        <para> 
+        On the <quote>Edit Product</quote> page, there is a link called 
+        <quote>Edit Group Access Controls</quote>. The settings on this page 
+        control the relationship of the groups to the product being edited.
+        </para> 
+
+        <para>
+        Group Access Controls are an important aspect of using groups for 
+        isolating products and restricting access to bugs filed against those
+        products. For more information on groups, including how to create, edit
+        add users to, and alter permission of, see <xref linkend="groups"/>.
+        </para>
+
+        <para>
+        After selecting the "Edit Group Access Controls" link from the "Edit
+        Product" page, a table containing all user-defined groups for this 
+        Bugzilla installation is displayed. The system groups that are created
+        when Bugzilla is installed are not applicable to Group Access Controls.
+        Below is description of what each of these fields means.
+        </para>
+
+        <para>
+        Groups may be applicable (e.g bugs in this product can be associated
+        with this group) , default (e.g. bugs in this product are in this group
+        by default), and mandatory (e.g. bugs in this product must be associated
+        with this group) for each product. Groups can also control access 
+        to bugs for a given product, or be used to make bugs for a product 
+        totally read-only unless the group restrictions are met. The best way to
+        understand these relationships is by example. See 
+        <xref linkend="group-control-examples"/> for examples of 
+        product and group relationships.
+        </para>
+  
+        <note>
+          <para>
+            Products and Groups are not limited to a one-to-one relationship. 
+            Multiple groups can be associated with the same product, and groups
+            can be associated with more than one product. 
+          </para>
+        </note> 
+
+        <para>
+        If any group has <emphasis>Entry</emphasis> selected, then the 
+        product will restrict bug entry to only those users 
+        who are members of <emphasis>all</emphasis> the groups with 
+        <emphasis>Entry</emphasis> selected.
+        </para>
+
+        <para>
+        If any group has <emphasis>Canedit</emphasis> selected, 
+        then the product will be read-only for any users 
+        who are not members of <emphasis>all</emphasis> of the groups with
+        <emphasis>Canedit</emphasis> selected. <emphasis>Only</emphasis> users who 
+        are members of all the <emphasis>Canedit</emphasis> groups 
+        will be able to edit bugs for this product. This is an additional 
+        restriction that enables finer-grained control over products rather 
+        than just all-or-nothing access levels.
+        </para>
+
+        <para>
+        The following settings let you 
+        choose privileges on a <emphasis>per-product basis</emphasis>.
+        This is a convenient way to give privileges to 
+        some users for some products only, without having 
+        to give them global privileges which would affect 
+        all products.
+        </para>
+
+        <para>
+        Any group having <emphasis>editcomponents</emphasis> 
+        selected  allows users who are in this group to edit all 
+        aspects of this product, including components, milestones 
+        and versions.
+        </para>
+
+        <para>
+        Any group having <emphasis>canconfirm</emphasis> selected 
+        allows users who are in this group to confirm bugs 
+        in this product.
+        </para>
+
+        <para>
+        Any group having <emphasis>editbugs</emphasis> selected allows 
+        users who are in this group to edit all fields of 
+        bugs in this product.
+        </para>
+
+        <para>
+        The <emphasis>MemberControl</emphasis> and 
+        <emphasis>OtherControl</emphasis> are used in tandem to determine which 
+        bugs will be placed in this group. The only allowable combinations of
+        these two parameters are listed in a table on the "Edit Group Access Controls"
+        page. Consult this table for details on how these fields can be used.
+        Examples of different uses are described below.
+        </para>
+
+      <section id="group-control-examples">
+      <title>Common Applications of Group Controls</title>
+
+      <para>
+        The use of groups is best explained by providing examples that illustrate
+        configurations for common use cases. The examples follow a common syntax:
+        <emphasis>Group: Entry, MemberControl, OtherControl, CanEdit,
+        EditComponents, CanConfirm, EditBugs</emphasis>. Where "Group" is the name
+        of the group being edited for this product. The other fields all
+        correspond to the table on the "Edit Group Access Controls" page. If any
+        of these options are not listed, it means they are not checked.      
+      </para>
+      
+          <para>
+            Basic Product/Group Restriction
+          </para>
+
+        <para>
+          Suppose there is a product called "Bar". The 
+          "Bar" product can only have bugs entered against it by users in the
+          group "Foo". Additionally, bugs filed against product "Bar" must stay
+          restricted to users to "Foo" at all times. Furthermore, only members
+          of group "Foo" can edit bugs filed against product "Bar", even if other
+          users could see the bug. This arrangement would achieved by the
+          following:
+        </para>
+
+        <programlisting>
+Product Bar: 
+foo: ENTRY, MANDATORY/MANDATORY, CANEDIT
+        </programlisting>
+
+        <para>
+          Perhaps such strict restrictions are not needed for product "Bar". A 
+          more lenient way to configure product "Bar" and group "Foo" would be:
+        </para>
+
+        <programlisting>
+Product Bar: 
+foo: ENTRY, SHOWN/SHOWN, EDITCOMPONENTS, CANCONFIRM, EDITBUGS
+        </programlisting>
+
+        <para>
+          The above indicates that for product "Bar", members of group "Foo" can
+          enter bugs. Any one with permission to edit a bug against product "Bar"
+          can put the bug
+          in group "Foo", even if they themselves are not in "Foo". Anyone in group
+          "Foo" can edit all aspects of the components of product "Bar", can confirm
+          bugs against product "Bar", and can edit all fields of any bug against
+          product "Bar".
+        </para>
+ 
+          <para>
+            General User Access With Security Group
+          </para>
+
+             <para>
+               To permit any user to file bugs against "Product A",
+               and to permit any user to submit those bugs into a
+               group called "Security":  
+            </para>
+
+      <programlisting> 
+Product A:
+security: SHOWN/SHOWN
+      </programlisting>
+
+          <para>
+            General User Access With A Security Product
+          </para>
+
+          <para>
+            To permit any user to file bugs against product called "Security"
+            while keeping those bugs from becoming visible to anyone
+            outside the group "SecurityWorkers" (unless a member of the
+            "SecurityWorkers" group removes that restriction):
+          </para>
+
+          <programlisting> 
+Product Security:
+securityworkers: DEFAULT/MANDATORY
+          </programlisting>
+
+          <para>
+            Product Isolation With a Common Group
+          </para>
+
+          <para>
+            To permit users of "Product A" to access the bugs for
+            "Product A", users of "Product B" to access the bugs for 
+            "Product B", and support staff, who are members of the "Support 
+            Group" to access both, three groups are needed:
+          </para>
+
+          <orderedlist>
+
+          <listitem>
+            <para>Support Group: Contains members of the support staff.</para>
+          </listitem>
+
+          <listitem>
+            <para>AccessA Group: Contains users of product A and the Support group.</para>
+          </listitem>
+
+          <listitem>
+            <para>AccessB Group: Contains users of product B and the Support group.</para>
+          </listitem>
+
+          </orderedlist>
+
+          <para>
+            Once these three groups are defined, the product group controls
+            can be set to:
+          </para>
+
+          <programlisting>
+Product A:
+AccessA: ENTRY, MANDATORY/MANDATORY
+Product B:
+AccessB: ENTRY, MANDATORY/MANDATORY
+        </programlisting>
+
+        <para>
+         Perhaps the "Support Group" wants more control. For example, 
+         the "Support Group"  could be permitted to make bugs inaccessible to 
+         users of both groups "AccessA" and "AccessB". 
+         Then, the "Support Group" could be permitted to publish
+         bugs relevant to all users in a third product (let's call it 
+         "Product Common") that is read-only
+         to anyone outside the "Support Group". In this way the "Support Group" 
+         could control bugs that should be seen by both groups. 
+         That configuration would be:
+        </para>
+
+      <programlisting>
+Product A:
+AccessA: ENTRY, MANDATORY/MANDATORY
+Support: SHOWN/NA
+Product B:
+AccessB: ENTRY, MANDATORY/MANDATORY
+Support: SHOWN/NA
+Product Common:
+Support: ENTRY, DEFAULT/MANDATORY, CANEDIT
+      </programlisting>
+
+          <para>
+            Make a Product Read Only
+          </para>
+
+          <para>
+            Sometimes a product is retired and should no longer have
+            new bugs filed against it (for example, an older version of a software
+            product that is no longer supported). A product can be made read-only
+            by creating a group called "readonly" and adding products to the
+            group as needed:
+          </para>
+
+         <programlisting>
+Product A:
+ReadOnly: ENTRY, NA/NA, CANEDIT
+         </programlisting>
+
+        <note>
+          <para>
+            For more information on Groups outside of how they relate to products
+            see <xref linkend="groups"/>.
+          </para>
+        </note>
+
+       </section>
+
+    </section>
+
+  </section>
+
+  <section id="components" xreflabel="Components">
+    <title>Components</title>
+
+    <para>Components are subsections of a Product. E.g. the computer game 
+    you are designing may have a "UI"
+    component, an "API" component, a "Sound System" component, and a
+    "Plugins" component, each overseen by a different programmer. It
+    often makes sense to divide Components in Bugzilla according to the
+    natural divisions of responsibility within your Product or
+    company.</para>
+
+    <para>
+    Each component has a default assignee and (if you turned it on in the parameters),
+    a QA Contact. The default assignee should be the primary person who fixes bugs in
+    that component. The QA Contact should be the person who will ensure
+    these bugs are completely fixed. The Assignee, QA Contact, and Reporter
+    will get email when new bugs are created in this Component and when
+    these bugs change. Default Assignee and Default QA Contact fields only
+    dictate the 
+    <emphasis>default assignments</emphasis>; 
+    these can be changed on bug submission, or at any later point in
+    a bug's life.</para>
+
+    <para>To create a new Component:</para>
+
+   <orderedlist>
+      <listitem>
+        <para>Select the <quote>Edit components</quote> link 
+        from the <quote>Edit product</quote> page</para>
+      </listitem>
+
+      <listitem>
+        <para>Select the <quote>Add</quote> link in the bottom right.</para>
+      </listitem>
+
+      <listitem>
+        <para>Fill out the <quote>Component</quote> field, a 
+        short <quote>Description</quote>, the 
+        <quote>Default Assignee</quote>, <quote>Default CC List</quote> 
+        and <quote>Default QA Contact</quote> (if enabled). 
+        The <quote>Component Description</quote> field may contain a 
+        limited subset of HTML tags. The <quote>Default Assignee</quote> 
+        field must be a login name already existing in the Bugzilla database. 
+        </para>
+      </listitem>
+    </orderedlist>
+  </section>
+
+  <section id="versions">
+    <title>Versions</title>
+
+    <para>Versions are the revisions of the product, such as "Flinders
+    3.1", "Flinders 95", and "Flinders 2000". Version is not a multi-select
+    field; the usual practice is to select the earliest version known to have
+    the bug.
+    </para>
+
+    <para>To create and edit Versions:</para>
+
+    <orderedlist>
+      <listitem>
+        <para>From the "Edit product" screen, select "Edit Versions"</para>
+      </listitem>
+
+      <listitem>
+        <para>You will notice that the product already has the default
+        version "undefined". Click the "Add" link in the bottom right.</para>
+      </listitem>
+
+      <listitem>
+        <para>Enter the name of the Version. This field takes text only. 
+        Then click the "Add" button.</para>
+      </listitem>
+
+    </orderedlist>
+  </section>
+
+  <section id="milestones">
+    <title>Milestones</title>
+
+    <para>Milestones are "targets" that you plan to get a bug fixed by. For
+    example, you have a bug that you plan to fix for your 3.0 release, it
+    would be assigned the milestone of 3.0.</para>
+
+    <note>
+      <para>Milestone options will only appear for a Product if you turned
+      on the "usetargetmilestone" Param in the "Edit Parameters" screen.
+      </para>
+    </note>
+
+    <para>To create new Milestones, set Default Milestones, and set
+    Milestone URL:</para>
+
+    <orderedlist>
+      <listitem>
+        <para>Select "Edit milestones" from the "Edit product" page.</para>
+      </listitem>
+
+      <listitem>
+        <para>Select "Add" in the bottom right corner.
+        text</para>
+      </listitem>
+
+      <listitem>
+        <para>Enter the name of the Milestone in the "Milestone" field. You
+        can optionally set the "sortkey", which is a positive or negative
+        number (-32768 to 32767) that defines where in the list this particular
+        milestone appears. This is because milestones often do not 
+        occur in alphanumeric order For example, "Future" might be
+        after "Release 1.2". Select "Add".</para>
+      </listitem>
+
+      <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>
+  
+ <section id="flags-overview">
+   <title>Flags</title>
+   
+   <para>
+     Flags are a way to attach a specific status to a bug or attachment, 
+     either <quote>+</quote> or <quote>-</quote>. The meaning of these symbols depends on the text
+     the flag itself, but contextually they could mean pass/fail, 
+     accept/reject, approved/denied, or even a simple yes/no. If your site
+     allows requestable flags, then users may set a flag to <quote>?</quote> as a 
+     request to another user that they look at the bug/attachment, and set
+     the flag to its correct status.
+   </para>
+
+   <section id="flags-simpleexample">
+     <title>A Simple Example</title>
+
+     <para>
+       A developer might want to ask their manager, 
+       <quote>Should we fix this bug before we release version 2.0?</quote> 
+       They might want to do this for a <emphasis>lot</emphasis> of bugs,
+       so it would be nice to streamline the process...
+     </para>
+     <para>
+       In Bugzilla, it would work this way:
+       <orderedlist>
+         <listitem>
+           <para>
+             The Bugzilla administrator creates a flag type called 
+             <quote>blocking2.0</quote> that shows up on all bugs in 
+             your product.
+           </para>
+ 
+           <para>
+             It shows up on the <quote>Show Bug</quote> screen
+             as the text <quote>blocking2.0</quote> with a drop-down box next
+             to it. The drop-down box contains four values: an empty space,
+             <quote>?</quote>, <quote>-</quote>, and <quote>+</quote>.
+           </para>
+         </listitem>
+         <listitem>
+           <para>The developer sets the flag to <quote>?</quote>.</para>
+         </listitem>
+         <listitem>
+           <para>
+             The manager sees the <computeroutput>blocking2.0</computeroutput>
+             flag with a <quote>?</quote> value.
+           </para>
+         </listitem>
+         <listitem>
+           <para>
+             If the manager thinks the feature should go into the product
+             before version 2.0 can be released, he sets the flag to 
+             <quote>+</quote>. Otherwise, he sets it to <quote>-</quote>.
+           </para>
+         </listitem>
+         <listitem>
+           <para>
+             Now, every Bugzilla user who looks at the bug knows whether or 
+             not the bug needs to be fixed before release of version 2.0.
+           </para>
+         </listitem>
+       </orderedlist>
+     </para>
+
+   </section>
+
+   <section id="flags-about">
+     <title>About Flags</title>
+
+     <section id="flag-values">
+       <title>Values</title>
+       <para>
+         Flags can have three values:
+         <variablelist>
+           <varlistentry>
+             <term><computeroutput>?</computeroutput></term>
+             <listitem><simpara>
+               A user is requesting that a status be set. (Think of it as 'A question is being asked'.)
+             </simpara></listitem>
+           </varlistentry>
+           <varlistentry>
+             <term><computeroutput>-</computeroutput></term>
+             <listitem><simpara>
+               The status has been set negatively. (The question has been answered <quote>no</quote>.)
+             </simpara></listitem>
+           </varlistentry>
+           <varlistentry>
+             <term><computeroutput>+</computeroutput></term>
+             <listitem><simpara>
+               The status has been set positively.
+               (The question has been answered <quote>yes</quote>.)
+             </simpara></listitem>
+           </varlistentry>
+         </variablelist>
+       </para>
+       <para>
+         Actually, there's a fourth value a flag can have -- 
+         <quote>unset</quote> -- which shows up as a blank space. This 
+         just means that nobody has expressed an opinion (or asked
+         someone else to express an opinion) about this bug or attachment.
+       </para>
+     </section>
+   </section>
+
+   <section id="flag-askto">
+     <title>Using flag requests</title>
+     <para>
+       If a flag has been defined as 'requestable', and a user has enough privileges
+       to request it (see below), the user can set the flag's status to <quote>?</quote>.
+       This status indicates that someone (a.k.a. <quote>the requester</quote>) is asking
+       someone else to set the flag to either <quote>+</quote> or <quote>-</quote>.
+     </para>
+     <para>
+       If a flag has been defined as 'specifically requestable', 
+       a text box will appear next to the flag into which the requester may
+       enter a Bugzilla username. That named person (a.k.a. <quote>the requestee</quote>)
+       will receive an email notifying them of the request, and pointing them
+       to the bug/attachment in question.
+     </para>
+     <para>
+       If a flag has <emphasis>not</emphasis> been defined as 'specifically requestable',
+       then no such text-box will appear. A request to set this flag cannot be made of
+       any specific individual, but must be asked <quote>to the wind</quote>.
+       A requester may <quote>ask the wind</quote> on any flag simply by leaving the text-box blank.
+     </para>
+   </section>
+
+   <section id="flag-types">
+     <title>Two Types of Flags</title>
+    
+     <para>
+       Flags can go in two places: on an attachment, or on a bug.
+     </para>
+
+     <section id="flag-type-attachment">
+       <title>Attachment Flags</title>
+      
+       <para>
+         Attachment flags are used to ask a question about a specific 
+         attachment on a bug.
+       </para>
+       <para>
+         Many Bugzilla installations use this to 
+         request that one developer <quote>review</quote> another 
+         developer's code before they check it in. They attach the code to
+         a bug report, and then set a flag on that attachment called
+         <quote>review</quote> to 
+         <computeroutput>review?boss@domain.com</computeroutput>.
+         boss@domain.com is then notified by email that
+         he has to check out that attachment and approve it or deny it.
+       </para>
+
+       <para>
+         For a Bugzilla user, attachment flags show up in three places:
+         <orderedlist>
+           <listitem>
+             <para>
+               On the list of attachments in the <quote>Show Bug</quote>
+               screen, you can see the current state of any flags that
+               have been set to ?, +, or -. You can see who asked about 
+               the flag (the requester), and who is being asked (the 
+               requestee).
+             </para>
+           </listitem>
+           <listitem>
+             <para>
+              When you <quote>Edit</quote> an attachment, you can 
+              see any settable flag, along with any flags that have 
+              already been set. This <quote>Edit Attachment</quote> 
+              screen is where you set flags to ?, -, +, or unset them.
+             </para>
+           </listitem>
+           <listitem>
+             <para>
+               Requests are listed in the <quote>Request Queue</quote>, which
+               is accessible from the <quote>My Requests</quote> link (if you are
+               logged in) or <quote>Requests</quote> link (if you are logged out)
+               visible in the footer of all pages.
+             </para>
+           </listitem>
+         </orderedlist>
+       </para>
+
+     </section>
+
+     <section id="flag-type-bug">
+       <title>Bug Flags</title>
+
+       <para>
+         Bug flags are used to set a status on the bug itself. You can 
+         see Bug Flags in the <quote>Show Bug</quote> and <quote>Requests</quote>
+         screens, as described above.
+       </para>
+       <para>
+         Only users with enough privileges (see below) may set flags on bugs.
+         This doesn't necessarily include the assignee, reporter, or users with the
+         <computeroutput>editbugs</computeroutput> permission.
+       </para>
+     </section>
+
+   </section>
+
+   <section id="flags-admin">
+     <title>Administering Flags</title>
+
+     <para>
+       If you have the <quote>editcomponents</quote> permission, you can
+       edit Flag Types from the main administration page. Clicking the
+       <quote>Flags</quote> link will bring you to the <quote>Administer
+       Flag Types</quote> page. Here, you can select whether you want 
+       to create (or edit) a Bug flag, or an Attachment flag.
+     </para>
+     <para>
+       No matter which you choose, the interface is the same, so we'll 
+       just go over it once.
+     </para>
+
+     <section id="flags-edit">
+       <title>Editing a Flag</title>
+       <para>
+         To edit a flag's properties, just click on the <quote>Edit</quote>
+         link next to the flag's description. That will take you to the same
+         form as described below (<xref linkend="flags-create"/>).
+       </para>
+     </section>
+
+     <section id="flags-create">
+       <title>Creating a Flag</title>
+       
+        <para>
+          When you click on the <quote>Create a Flag Type for...</quote>
+          link, you will be presented with a form. Here is what the fields in 
+          the form mean:
+        </para>
+
+        <section id="flags-create-field-name">
+          <title>Name</title>
+          <para>
+            This is the name of the flag. This will be displayed 
+            to Bugzilla users who are looking at or setting the flag. 
+            The name may contain any valid Unicode characters except commas
+            and spaces.
+          </para>
+        </section>
+
+        <section id="flags-create-field-description">
+          <title>Description</title>
+          <para>
+            The description describes the flag in more detail. It is visible
+            in a tooltip when hovering over a flag either in the <quote>Show Bug</quote>
+            or <quote>Edit Attachment</quote> pages. This field can be as
+            long as you like, and can contain any character you want.
+          </para>
+        </section>
+
+        <section id="flags-create-field-category">
+          <title>Category</title>
+
+          <para>
+            Default behaviour for a newly-created flag is to appear on
+            products and all components, which is why <quote>__Any__:__Any__</quote>
+            is already entered in the <quote>Inclusions</quote> box.
+            If this is not your desired behaviour, you must either set some
+            exclusions (for products on which you don't want the flag to appear),
+            or you must remove <quote>__Any__:__Any__</quote> from the Inclusions box
+            and define products/components specifically for this flag.
+          </para>
+
+          <para>
+            To create an Inclusion, select a Product from the top drop-down box.
+            You may also select a specific component from the bottom drop-down box.
+            (Setting <quote>__Any__</quote> for Product translates to, 
+            <quote>all the products in this Bugzilla</quote>.
+            Selecting  <quote>__Any__</quote> in the Component field means
+            <quote>all components in the selected product.</quote>) 
+            Selections made, press <quote>Include</quote>, and your
+            Product/Component pairing will show up in the <quote>Inclusions</quote> box on the right.
+          </para>
+
+          <para>
+            To create an Exclusion, the process is the same; select a Product from the
+            top drop-down box, select a specific component if you want one, and press
+            <quote>Exclude</quote>. The Product/Component pairing will show up in the 
+            <quote>Exclusions</quote> box on the right.
+          </para>
+
+          <para>
+            This flag <emphasis>will</emphasis> and <emphasis>can</emphasis> be set for any
+            products/components that appearing in the <quote>Inclusions</quote> box 
+            (or which fall under the appropriate <quote>__Any__</quote>). 
+            This flag <emphasis>will not</emphasis> appear (and therefore cannot be set) on
+            any products appearing in the <quote>Exclusions</quote> box.
+            <emphasis> IMPORTANT: Exclusions override inclusions.</emphasis>
+          </para>
+
+          <para>
+            You may select a Product without selecting a specific Component,
+            but you can't select a Component without a Product, or to select a
+            Component that does not belong to the named Product. If you do so,
+            Bugzilla will display an error message, even if all your products
+            have a component by that name.
+          </para>
+
+          <para><emphasis>Example:</emphasis> Let's say you have a product called 
+            <quote>Jet Plane</quote> that has thousands of components. You want
+            to be able to ask if a problem should be fixed in the next model of 
+            plane you release. We'll call the flag <quote>fixInNext</quote>.
+            But, there's one component in <quote>Jet Plane,</quote> 
+            called <quote>Pilot.</quote> It doesn't make sense to release a 
+            new pilot, so you don't want to have the flag show up in that component.
+            So, you include <quote>Jet Plane:__Any__</quote> and you exclude 
+            <quote>Jet Plane:Pilot</quote>.
+          </para>
+        </section>
+
+        <section id="flags-create-field-sortkey">
+          <title>Sort Key</title>
+          <para>
+            Flags normally show up in alphabetical order. If you want them to 
+            show up in a different order, you can use this key set the order on each flag. 
+            Flags with a lower sort key will appear before flags with a higher
+            sort key. Flags that have the same sort key will be sorted alphabetically,
+            but they will still be after flags with a lower sort key, and before flags
+            with a higher sort key.
+          </para>
+          <para>
+            <emphasis>Example:</emphasis> I have AFlag (Sort Key 100), BFlag (Sort Key 10), 
+            CFlag (Sort Key 10), and DFlag (Sort Key 1). These show up in
+            the order: DFlag, BFlag, CFlag, AFlag.
+          </para>
+        </section>
+
+        <section id="flags-create-field-active">
+          <title>Active</title>
+          <para>
+            Sometimes, you might want to keep old flag information in the 
+            Bugzilla database, but stop users from setting any new flags of this type.
+            To do this, uncheck <quote>active</quote>. Deactivated
+            flags will still show up in the UI if they are ?, +, or -, but they
+            may only be cleared (unset), and cannot be changed to a new value.
+            Once a deactivated flag is cleared, it will completely disappear from a 
+            bug/attachment, and cannot be set again.
+          </para>
+        </section>
+
+        <section id="flags-create-field-requestable">
+          <title>Requestable</title>
+          <para>
+            New flags are, by default, <quote>requestable</quote>, meaning that they
+            offer users the <quote>?</quote> option, as well as <quote>+</quote>
+            and <quote>-</quote>.
+            To remove the ? option, uncheck <quote>requestable</quote>.
+          </para>
+        </section>
+
+        <section id="flags-create-field-specific">
+          <title>Specifically Requestable</title>
+          <para>
+            By default this box is checked for new flags, meaning that users may make
+            flag requests of specific individuals. Unchecking this box will remove the
+            text box next to a flag; if it is still requestable, then requests may
+            only be made <quote>to the wind.</quote> Removing this after specific
+            requests have been made will not remove those requests; that data will
+            stay in the database (though it will no longer appear to the user).
+          </para>
+        </section>
+
+        <section id="flags-create-field-multiplicable">
+          <title>Multiplicable</title>
+          <para>
+            Any flag with <quote>Multiplicable</quote> set (default for new flags is 'on')
+            may be set more than once. After being set once, an unset flag
+            of the same type will appear below it with <quote>addl.</quote> (short for 
+            <quote>additional</quote>) before the name. There is no limit to the number of
+            times a Multiplicable flags may be set on the same bug/attachment.
+          </para>
+        </section>
+
+	<section id="flags-create-field-cclist">
+          <title>CC List</title>
+
+          <para>
+            If you want certain users to be notified every time this flag is 
+            set to ?, -, +, or unset, add them here. This is a comma-separated 
+            list of email addresses that need not be restricted to Bugzilla usernames.
+          </para>
+        </section>
+
+        <section id="flags-create-grant-group">
+          <title>Grant Group</title>
+          <para>
+            When this field is set to some given group, only users in the group
+            can set the flag to <quote>+</quote> and <quote>-</quote>. This
+            field does not affect who can request or cancel the flag. For that,
+            see the <quote>Request Group</quote> field below. If this field
+            is left blank, all users can set or delete this flag. This field is
+            useful for restricting which users can approve or reject requests.
+          </para>
+        </section>
+
+        <section id="flags-create-request-group">
+          <title>Request Group</title>
+          <para>
+            When this field is set to some given group, only users in the group
+            can request or cancel this flag. Note that this field has no effect
+            if the <quote>grant group</quote> field is empty. You can set the
+            value of this field to a different group, but both fields have to be
+            set to a group for this field to have an effect.
+          </para>
+        </section>
+      </section> <!-- flags-create -->
+
+      <section id="flags-delete">
+        <title>Deleting a Flag</title>
+
+        <para>
+          When you are at the <quote>Administer Flag Types</quote> screen,
+          you will be presented with a list of Bug flags and a list of Attachment
+          Flags.
+        </para>
+        <para>
+          To delete a flag, click on the <quote>Delete</quote> link next to
+          the flag description.
+        </para>
+        <warning>
+          <para>
+            Once you delete a flag, it is <emphasis>gone</emphasis> from
+            your Bugzilla. All the data for that flag will be deleted.
+            Everywhere that flag was set, it will disappear,
+            and you cannot get that data back. If you want to keep flag data,
+            but don't want anybody to set any new flags or change current flags,
+            unset <quote>active</quote> in the flag Edit form.
+          </para>
+        </warning>
+      </section>
+
+    </section> <!-- flags-admin -->
+
+    <!-- XXX We should add a "Uses of Flags" section, here, with examples. -->
+
+  </section> <!-- flags -->
+
+  <section id="keywords">
+    <title>Keywords</title>
+
+    <para>
+    The administrator can define keywords which can be used to tag and
+    categorise bugs. For example, the keyword "regression" is commonly used.
+    A company might have a policy stating all regressions
+    must be fixed by the next release - this keyword can make tracking those
+    bugs much easier.
+    </para>
+    <para>
+    Keywords are global, rather than per-product. If the administrator changes
+    a keyword currently applied to any bugs, the keyword cache must be rebuilt
+    using the <xref linkend="sanitycheck"/> script. Currently keywords can not
+    be marked obsolete to prevent future usage.
+    </para>
+    <para>
+    Keywords can be created, edited or deleted by clicking the "Keywords"
+    link in the admin page. There are two fields for each keyword - the keyword
+    itself and a brief description. Once created, keywords can be selected
+    and applied to individual bugs in that bug's "Details" section.
+    </para>
+  </section>
+
+  <section id="custom-fields">
+    <title>Custom Fields</title>
+
+    <para>
+      The release of Bugzilla 3.0 added the ability to create Custom Fields. 
+      Custom Fields are treated like any other field - they can be set in bugs
+      and used for search queries. Administrators should keep in mind that
+      adding too many fields can make the user interface more complicated and
+      harder to use. Custom Fields should be added only when necessary and with 
+      careful consideration.
+    </para>
+    <tip>
+      <para>
+        Before adding a Custom Field, make sure that Bugzilla can not already
+        do the desired behavior. Many Bugzilla options are not enabled by 
+        default, and many times Administrators find that simply enabling
+        certain options that already exist is sufficient. 
+      </para>
+    </tip>
+    <para>
+      Administrators can manage Custom Fields using the
+      <quote>Custom Fields</quote> link on the Administration page. The Custom
+      Fields administration page displays a list of Custom Fields, if any exist,
+      and a link to "Add a new custom field". 
+    </para>
+
+    <section id="add-custom-fields">
+      <title>Adding Custom Fields</title>
+
+      <para>
+        To add a new Custom Field, click the "Add a new custom field" link. This
+        page displays several options for the new field, described below.
+      </para>
+
+      <para>
+        The following attributes must be set for each new custom field:
+        <itemizedlist>
+          <listitem>
+            <para>
+              <emphasis>Name:</emphasis>
+              The name of the field in the database, used internally. This name 
+              MUST begin with <quote>cf_</quote> to prevent confusion with 
+              standard fields. If this string is omitted, it will
+              be automatically added to the name entered. 
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+              <emphasis>Description:</emphasis>
+              A brief string which is used as the label for this Custom Field.
+              That is the string that users will see, and should be
+              short and explicit.
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+              <emphasis>Type:</emphasis>
+              The type of field to create. There are
+              several types available:
+               <simplelist>
+                 <member>
+                   Large Text Box: A multiple line box for entering free text.
+                 </member>
+                 <member>
+                   Free Text: A single line box for entering free text.
+                 </member>
+                 <member>
+                   Multiple-Selection Box: A list box where multiple options 
+                   can be selected. After creating this field, it must be edited
+                   to add the selection options. See 
+                   <xref linkend="edit-values-list" /> for information about 
+                   editing legal values.
+                 </member>
+                 <member>
+                   Drop Down: A list box where only one option can be selected.
+                   After creating this field, it must be edited to add the
+                   selection options. See 
+                   <xref linkend="edit-values-list" /> for information about 
+                   editing legal values.
+                 </member>
+                 <member>
+                   Date/Time: A date field. This field appears with a 
+                   calendar widget for choosing the date.
+                 </member>
+               </simplelist>
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+              <emphasis>Sortkey:</emphasis>
+              Integer that determines in which order Custom Fields are
+              displayed in the User Interface, especially when viewing a bug. 
+              Fields with lower values are displayed first.
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+              <emphasis>Can be set on bug creation:</emphasis>
+              Boolean that determines whether this field can be set on
+              bug creation. If not selected, then a bug must be created 
+              before this field can be set. See <xref linkend="bugreports" /> 
+              for information about filing bugs.
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+              <emphasis>Displayed in bugmail for new bugs:</emphasis>
+              Boolean that determines whether the value set on this field
+              should appear in bugmail when the bug is filed. This attribute
+              has no effect if the field cannot be set on bug creation.
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+              <emphasis>Is obsolete:</emphasis>
+              Boolean that determines whether this field should
+              be displayed at all. Obsolete Custom Fields are hidden.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </para>
+    </section>
+
+    <section id="edit-custom-fields">
+      <title>Editing Custom Fields</title>
+
+      <para>
+        As soon as a Custom Field is created, its name and type cannot be
+        changed. If this field is a drop down menu, its legal values can
+        be set as described in <xref linkend="edit-values-list" />. All
+        other attributes can be edited as described above.
+      </para>
+    </section>
+
+    <section id="delete-custom-fields">
+      <title>Deleting Custom Fields</title>
+
+      <para>
+        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.
+      </para>
+    </section>
+  </section>
+
+  <section id="edit-values">
+    <title>Legal Values</title>
+
+    <para>
+      Since Bugzilla 2.20 RC1, legal values for Operating Systems, platforms,
+      bug priorities and severities can be edited from the User Interface
+      directly. This means that it is no longer required to manually edit
+      <filename>localconfig</filename>. Starting with Bugzilla 2.23.3, 
+      the list of valid resolutions can be customized from the same interface.
+      Since Bugzilla 3.1.1 the list of valid bug statuses can be customized
+      as well.
+    </para>
+
+    <section id="edit-values-list">
+      <title>Viewing/Editing legal values</title>
+      <para>
+        Editing legal values requires <quote>admin</quote> privileges.
+        Select "Legal Values" from the Administration page. A list of all
+        fields, both system fields and Custom Fields, for which legal values
+        can be edited appears. Click a field name to edit its legal values.
+      </para>
+      <para>
+        There is no limit to how many values a field can have, but each value 
+        must be unique to that field. The sortkey is important to display these
+        values in the desired order.
+      </para>
+    </section>
+
+    <section id="edit-values-delete">
+      <title>Deleting legal values</title>
+      <para>
+        Legal values from Custom Fields can be deleted, but only if the 
+        following two conditions are respected:
+      </para>
+
+      <orderedlist>
+        <listitem>
+	  <para>The value is not used by default for the field.</para>
+	</listitem>
+
+	<listitem>
+	  <para>No bug is currently using this value.</para>
+        </listitem>
+      </orderedlist>
+
+      <para>
+        If any of these conditions is not respected, the value cannot be deleted.
+	The only way to delete these values is to reassign bugs to another value
+	and to set another value as default for the field.
+      </para>
+    </section>
+  </section>
+
+  <section id="bug_status_workflow">
+    <title>Bug Status Workflow</title>
+
+    <para>
+      The bug status workflow is no longer hardcoded but can be freely customized
+      from the web interface. Only one bug status cannot be renamed nor deleted,
+      UNCONFIRMED, but the workflow involving it is free. The configuration
+      page displays all existing bug statuses twice, first on the left for bug
+      statuses we come from and on the top for bug statuses we move to.
+      If the checkbox is checked, then the transition between the two bug statuses
+      is legal, else it's forbidden independently of your privileges. The bug status
+      used for the "duplicate_or_move_bug_status" parameter must be part of the
+      workflow as that is the bug status which will be used when duplicating or
+      moving a bug, so it must be available from each bug status.
+    </para>
+    <para>
+      When the workflow is set, the "View Current Triggers" link below the table
+      lets you set which transitions require a comment from the user.
+    </para>
+  </section>
+
+  <section id="voting">
+    <title>Voting</title>
+
+    <para>Voting allows users to be given a pot of votes which they can allocate
+    to bugs, to indicate that they'd like them fixed. 
+    This allows developers to gauge
+    user need for a particular enhancement or bugfix. By allowing bugs with
+    a certain number of votes to automatically move from "UNCONFIRMED" to
+    "NEW", users of the bug system can help high-priority bugs garner
+    attention so they don't sit for a long time awaiting triage.</para>
+
+    <para>To modify Voting settings:</para>
+
+    <orderedlist>
+      <listitem>
+        <para>Navigate to the "Edit product" screen for the Product you
+        wish to modify</para>
+      </listitem>
+
+      <listitem>
+        <para><emphasis>Maximum Votes per person</emphasis>:
+        Setting this field to "0" disables voting.</para>
+      </listitem>
+
+      <listitem>
+        <para><emphasis>Maximum Votes a person can put on a single
+         bug</emphasis>: 
+         It should probably be some number lower than the
+        "Maximum votes per person". Don't set this field to "0" if
+        "Maximum votes per person" is non-zero; that doesn't make
+        any sense.</para>
+      </listitem>
+
+      <listitem>
+        <para><emphasis>Number of votes a bug in this product needs to
+        automatically get out of the UNCONFIRMED state</emphasis>: 
+        Setting this field to "0" disables the automatic move of
+        bugs from UNCONFIRMED to NEW. 
+        </para>
+      </listitem>
+
+      <listitem>
+        <para>Once you have adjusted the values to your preference, click
+        "Update".</para>
+      </listitem>
+    </orderedlist>
+  </section>
+
+  <section id="quips">
+    <title>Quips</title>
+
+    <para>
+      Quips are small text messages that can be configured to appear
+      next to search results. A Bugzilla installation can have its own specific
+      quips. Whenever a quip needs to be displayed, a random selection
+      is made from the pool of already existing quips.
+    </para>
+  
+    <para>
+      Quips are controlled by the <emphasis>enablequips</emphasis> parameter.
+      It has several possible values: on, approved, frozen or off.
+      In order to enable quips approval you need to set this parameter
+      to "approved". In this way, users are free to submit quips for
+      addition but an administrator must explicitly approve them before
+      they are actually used.
+    </para>
+
+    <para>
+      In order to see the user interface for the quips, it is enough to click
+      on a quip when it is displayed together with the search results. Or
+      it can be seen directly in the browser by visiting the quips.cgi URL
+      (prefixed with the usual web location of the Bugzilla installation).
+      Once the quip interface is displayed, it is enough to click the
+      "view and edit the whole quip list" in order to see the administration
+      page. A page with all the quips available in the database will
+      be displayed.
+    </para>
+
+    <para>
+      Next to each tip there is a checkbox, under the
+      "Approved" column. Quips who have this checkbox checked are
+      already approved and will appear next to the search results.
+      The ones that have it unchecked are still preserved in the
+      database but they will not appear on search results pages.
+      User submitted quips have initially the checkbox unchecked.
+    </para>
+  
+    <para>
+      Also, there is a delete link next to each quip,
+      which can be used in order to permanently delete a quip.
+    </para>
+  </section>
+
+  <section id="groups">
+    <title>Groups and Group Security</title>
+
+    <para>
+    Groups allow for separating bugs into logical divisions.
+    Groups are typically used to
+    to isolate bugs that should only be seen by certain people. For
+    example, a company might create a different group for each one of its customers
+    or partners. Group permissions could be set so that each partner or customer would
+    only have access to their own bugs. Or, groups might be used to create
+    variable access controls for different departments within an organization.
+    Another common use of groups is to associate groups with products,
+    creating isolation and access control on a per-product basis.
+    </para>
+
+    <para>
+    Groups and group behaviors are controlled in several places:
+    </para>
+
+      <orderedlist>
+
+         <listitem>
+           <para>
+             The group configuration page. To view or edit existing groups, or to
+             create new groups, access the "Groups" link from the "Administration"
+             page. This section of the manual deals primarily with the aspect of
+             group controls accessed on this page.  
+           </para>
+         </listitem> 
+
+        <listitem>
+          <para>
+            Global configuration parameters. Bugzilla has several parameters 
+            that control the overall default group behavior and restriction
+            levels. For more information on the parameters that control 
+            group behavior globally, see <xref linkend="param-group-security"/>.
+          </para>
+
+         </listitem>
+
+         <listitem>
+          <para>
+            Product association with groups. Most of the functionality of groups
+            and group security is controlled at the product level. Some aspects
+            of group access controls for products are discussed in this section,
+            but for more detail see <xref linkend="product-group-controls"/>.
+          </para>
+         </listitem>
+
+         <listitem>
+          <para>
+            Group access for users. See <xref linkend="users-and-groups"/> for
+            details on how users are assigned group access.
+          </para>
+         </listitem>
+
+      </orderedlist>
+
+    <para>
+      Group permissions are such that if a bug belongs to a group, only members
+      of that group can see the bug. If a bug is in more than one group, only
+      members of <emphasis>all</emphasis> the groups that the bug is in can see
+      the bug. For information on granting read-only access to certain people and
+      full edit access to others, see <xref linkend="product-group-controls"/>.
+     </para> 
+
+     <note>
+      <para>
+        By default, bugs can also be seen by the Assignee, the Reporter, and 
+        by everyone on the CC List, regardless of whether or not the bug would 
+        typically be viewable by them. Visibility to the Reporter and CC List can 
+        be overridden (on a per-bug basis) by bringing up the bug, finding the 
+        section that starts with <quote>Users in the roles selected below...</quote>
+        and un-checking the box next to either 'Reporter' or 'CC List' (or both).
+      </para>
+    </note> 
+
+    <section id="create-groups">
+      <title>Creating Groups</title>
+
+      <para>
+        To create a new group, follow the steps below:
+      </para>
+  
+      <orderedlist>
+
+        <listitem>
+          <para>
+            Select the <quote>Administration</quote> link in the page footer, 
+            and then select the <quote>Groups</quote> link from the 
+            Administration page.
+          </para>
+        </listitem>
+  
+        <listitem>
+          <para>
+            A table of all the existing groups is displayed. Below the table is a
+            description of all the fields. To create a new group, select the 
+            <quote>Add Group</quote> link under the table of existing groups.
+          </para>
+        </listitem>
+  
+        <listitem>
+          <para>
+            There are five fields to fill out. These fields are documented below
+            the form. Choose a name and description for the group. Decide whether
+            this group should be used for bugs (in all likelihood this should be
+            selected). Optionally, choose a regular expression that will
+            automatically add any matching users to the group, and choose an
+            icon that will help identify user comments for the group. The regular
+            expression can be useful, for example, to automatically put all users
+            from the same company into one group (if the group is for a specific
+            customer or partner). 
+          </para>
+            <note>
+             <para>
+               If <quote>User RegExp</quote> is filled out, users whose email 
+               addresses match the regular expression will automatically be 
+               members of the group as long as their email addresses continue 
+               to match the regular expression. If their email address changes
+               and no longer matches the regular expression, they will be removed
+               from the group. Versions 2.16 and older of Bugzilla did not automatically
+               remove users who's email addresses no longer matched the RegExp.
+             </para>
+            </note>
+            <warning>
+             <para>
+               If specifying a domain in the regular expression, end
+               the regexp with a "$". Otherwise, when granting access to 
+               "@mycompany\.com", access will also be granted to 
+               'badperson@mycompany.com.cracker.net'. Use the syntax,
+               '@mycompany\.com$' for the regular expression.
+             </para>
+            </warning>
+        </listitem>
+
+        <listitem>
+          <para>
+          After the new group is created, it can be edited for additional options. 
+          The "Edit Group" page allows for specifying other groups that should be included
+          in this group and which groups should be permitted to add and delete
+          users from this group. For more details, see <xref linkend="edit-groups"/>.
+          </para>
+        </listitem>
+      </orderedlist>
+  
+    </section>
+ 
+    <section id="edit-groups">
+      <title>Editing Groups and Assigning Group Permissions</title>
+
+      <para>
+        To access the "Edit Groups" page, select the 
+        <quote>Administration</quote> link in the page footer, 
+        and then select the <quote>Groups</quote> link from the Administration page.
+        A table of all the existing groups is displayed. Click on a group name
+        you wish to edit or control permissions for.
+      </para>
+
+      <para>
+       The "Edit Groups" page contains the same five fields present when 
+       creating a new group. Below that are two additional sections, "Group
+       Permissions," and "Mass Remove". The "Mass Remove" option simply removes
+       all users from the group who match the regular expression entered. The
+       "Group Permissions" section requires further explanation.
+      </para>
+
+      <para>
+       The "Group Permissions" section on the "Edit Groups" page contains four sets
+       of permissions that control the relationship of this group to other
+       groups. If the 'usevisibilitygroups' parameter is in use (see
+       <xref linkend="parameters"/>) two additional sets of permissions are displayed. 
+       Each set consists of two select boxes. On the left, a select box
+       with a list of all existing groups. On the right, a select box listing
+       all groups currently selected for this permission setting (this box will
+       be empty for new groups). The way these controls allow groups to relate
+       to one another is called <emphasis>inheritance</emphasis>. 
+       Each of the six permissions is described below.
+      </para>
+
+      <variablelist>
+
+        <varlistentry>
+
+          <term>
+            <emphasis>Groups That Are a Member of This Group</emphasis>
+          </term>
+
+          <listitem>
+            <para> 
+              Members of any groups selected here will automatically have 
+              membership in this group. In other words, members of any selected 
+              group will inherit membership in this group. 
+            </para>
+          </listitem>
+
+        </varlistentry>
+
+        <varlistentry>
+
+          <term>
+            <emphasis>Groups That This Group Is a Member Of</emphasis>
+          </term>
+
+          <listitem>
+            <para>
+              Members of this group will inherit membership to any group 
+              selected here. For example, suppose the group being edited is
+              an Admin group. If there are two products  (Product1 and Product2) 
+              and each product has its
+              own group (Group1 and Group2), and the Admin group 
+              should have access to both products, 
+              simply select both Group1 and Group2 here. 
+           </para>
+          </listitem>
+
+        </varlistentry>
+
+        <varlistentry>
+
+          <term>
+            <emphasis>Groups That Can Grant Membership in This Group</emphasis>
+          </term>
+
+          <listitem>
+           <para>
+             The members of any group selected here will be able add users
+             to this group, even if they themselves are not in this group.
+           </para>
+          </listitem>
+
+        </varlistentry>
+
+        <varlistentry>
+
+          <term>
+            <emphasis>Groups That This Group Can Grant Membership In</emphasis>
+          </term>
+
+          <listitem>
+           <para>
+             Members of this group can add users to any group selected here,
+             even if they themselves are not in the selected groups.  
+           </para>
+          </listitem>
+
+        </varlistentry>
+
+        <varlistentry>
+
+          <term>
+            <emphasis>Groups That Can See This Group</emphasis>
+          </term>
+
+          <listitem>
+           <para>
+             Members of any selected group can see the users in this group.
+             This setting is only visible if the 'usevisibilitygroups' parameter
+             is enabled on the Bugzilla Configuration page. See
+             <xref linkend="parameters"/> for information on configuring Bugzilla.
+           </para>
+          </listitem>
+
+        </varlistentry>
+
+        <varlistentry>
+
+          <term>
+            <emphasis>Groups That This Group Can See</emphasis>
+          </term>
+
+          <listitem>
+           <para>
+             Members of this group can see members in any of the selected groups.
+             This setting is only visible if the 'usevisibilitygroups' parameter
+             is enabled on the the Bugzilla Configuration page. See
+             <xref linkend="parameters"/> for information on configuring Bugzilla.               
+           </para>
+          </listitem>
+
+        </varlistentry>
+
+    </variablelist>
+
+    </section>
+
+    <section id="users-and-groups">
+      <title>Assigning Users to Groups</title>
+
+      <para>
+        A User can become a member of a group in several ways:
+      </para>
+
+      <orderedlist>
+
+        <listitem>
+          <para>
+            The user can be explicitly placed in the group by editing
+            the user's profile. This can be done by accessing the "Users" page
+            from the "Administration" page. Use the search form to find the user
+            you want to edit group membership for, and click on their email
+            address in the search results to edit their profile. The profile
+            page lists all the groups, and indicates if the user is a member of
+            the group either directly or indirectly. More information on indirect
+            group membership is below. For more details on User administration,
+            see <xref linkend="useradmin"/>.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+           The group can include another group of which the user is
+           a member. This is indicated by square brackets around the checkbox  
+           next to the group name in the user's profile. 
+           See <xref linkend="edit-groups"/> for details on group inheritance.
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
+            The user's email address can match the regular expression
+            that has been specified to automatically grant membership to
+            the group. This is indicated by "*" around the check box by the
+            group name in the user's profile.
+            See <xref linkend="create-groups"/> for details on 
+            the regular expression option when creating groups.
+           </para>
+        </listitem>
+
+      </orderedlist>
+
+    </section>
+
+    <section>
+     <title>Assigning Group Controls to Products</title>
+
+     <para>
+     The primary functionality of groups is derived from the relationship of 
+     groups to products. The concepts around segregating access to bugs with
+     product group controls can be confusing. For details and examples on this
+     topic, see <xref linkend="product-group-controls" />.
+     </para>
+
+    </section>
+  </section>
+
+  <section id="sanitycheck">
+    <title>Checking and Maintaining Database Integrity</title>
+
+    <para>
+    Over time it is possible for the Bugzilla database to become corrupt
+    or to have anomalies.
+    This could happen through normal usage of Bugzilla, manual database
+    administration outside of the Bugzilla user interface, or from some
+    other unexpected event. Bugzilla includes a "Sanity Check" script that
+    can perform several basic database checks, and repair certain problems or
+    inconsistencies. 
+    </para>
+    <para>
+    To run the "Sanity Check" script, log in as an Administrator and click the
+    "Sanity Check" link in the admin page. Any problems that are found will be
+    displayed in red letters. If the script is capable of fixing a problem,
+    it will present a link to initiate the fix. If the script can not
+    fix the problem it will require manual database administration or recovery.
+    </para>
+    <para>
+    The "Sanity Check" script can also be run from the command line via the perl
+    script <filename>sanitycheck.pl</filename>. The script can also be run as
+    a <command>cron</command> job. Results will be delivered by email.
+    </para>
+    <para>
+    The "Sanity Check" script should be run on a regular basis as a matter of
+    best practice.
+    </para>
+    <warning>
+      <para>
+      The "Sanity Check" script is no substitute for a competent database
+      administrator. It is only designed to check and repair basic database
+      problems.
+      </para>
+    </warning>
+
+  </section>
+
+
+</chapter>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-always-quote-attributes:t
+sgml-auto-insert-required-elements:t
+sgml-balanced-tag-edit:t
+sgml-exposed-tags:nil
+sgml-general-insert-case:lower
+sgml-indent-data:t
+sgml-indent-step:2
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+sgml-minimize-attributes:nil
+sgml-namecase-general:t
+sgml-omittag:t
+sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
+sgml-shorttag:t
+sgml-tag-region-if-active:t
+End:
+-->
+
diff --git a/BugsSite/docs/xml/conventions.xml b/BugsSite/docs/en/xml/conventions.xml
similarity index 95%
rename from BugsSite/docs/xml/conventions.xml
rename to BugsSite/docs/en/xml/conventions.xml
index 24986d6..70e6624 100644
--- a/BugsSite/docs/xml/conventions.xml
+++ b/BugsSite/docs/en/xml/conventions.xml
@@ -20,7 +20,7 @@
 
       <tbody>
         <row>
-          <entry>Warning</entry>
+          <entry>Caution</entry>
 
           <entry>
             <caution>
@@ -30,11 +30,11 @@
         </row>
 
         <row>
-          <entry>Hint</entry>
+          <entry>Hint or Tip</entry>
 
           <entry>
             <tip>
-              <para>Would you like a breath mint?</para>
+              <para>For best results... </para>
             </tip>
           </entry>
         </row>
@@ -50,7 +50,7 @@
         </row>
 
         <row>
-          <entry>Information requiring special attention</entry>
+          <entry>Warning</entry>
 
           <entry>
             <warning>
diff --git a/BugsSite/docs/xml/customization.xml b/BugsSite/docs/en/xml/customization.xml
similarity index 97%
rename from BugsSite/docs/xml/customization.xml
rename to BugsSite/docs/en/xml/customization.xml
index c8ef29c..81a5b49 100644
--- a/BugsSite/docs/xml/customization.xml
+++ b/BugsSite/docs/en/xml/customization.xml
@@ -434,27 +434,17 @@
       url="http://www.bugzilla.org/download.html#localizations"/>. Instructions
       for submitting new languages are also available from that location.
       </para>
-
-      <para>After untarring the localizations (or creating your own) in the 
-      <filename class="directory">BUGZILLA_ROOT/template</filename> directory,
-      you must update the <option>languages</option> parameter to contain any
-      localizations you'd like to permit. You may also wish to set the
-      <option>defaultlanguage</option> parameter to something other than
-      <quote>en</quote> if you don't want English to be the default language.
-      </para>
     </section>
       
   </section>
 
   <section id="cust-hooks">
     <title>The Bugzilla Extension Mechanism</title>
-        
+    
     <warning>
       <para>
-        Custom extensions require Template Toolkit version 2.12 or
-        above, or the application of a patch.  See <ulink
-        url="http://bugzilla.mozilla.org/show_bug.cgi?id=239112">bug
-        239112</ulink> for details.
+        Note that the below paths are inconsistent and confusing. They will
+        likely be changed in Bugzilla 4.0.
       </para>
     </warning>
        
@@ -598,7 +588,7 @@
 
     <para>
       The corresponding extension file for this hook is
-      <filename>BUGZILLA_ROOT/extensions/projman/template/en/hook/global/useful-links-edit.html.tmpl</filename>.
+      <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>
 
@@ -696,7 +686,7 @@
         versions, and you upgrade.
       </para>
     </warning>
-       
+      
     <para>
       Companies often have rules about which employees, or classes of employees,
       are allowed to change certain things in the bug system. For example, 
@@ -705,6 +695,16 @@
       designed to make it easy for you to write your own custom rules to define
       who is allowed to make what sorts of value transition.
     </para>
+
+    <para>
+     By default, assignees, QA owners and users
+     with <emphasis>editbugs</emphasis> privileges can edit all fields of bugs, 
+     except group restrictions (unless they are members of the groups they 
+     are trying to change). Bug reporters also have the ability to edit some 
+     fields, but in a more restrictive manner. Other users, without 
+     <emphasis>editbugs</emphasis> privileges, can not edit 
+     bugs, except to comment and add themselves to the CC list.
+    </para> 
     
     <para>
       For maximum flexibility, customizing this means editing Bugzilla's Perl 
diff --git a/BugsSite/docs/xml/gfdl.xml b/BugsSite/docs/en/xml/gfdl.xml
similarity index 100%
rename from BugsSite/docs/xml/gfdl.xml
rename to BugsSite/docs/en/xml/gfdl.xml
diff --git a/BugsSite/docs/xml/glossary.xml b/BugsSite/docs/en/xml/glossary.xml
similarity index 100%
rename from BugsSite/docs/xml/glossary.xml
rename to BugsSite/docs/en/xml/glossary.xml
diff --git a/BugsSite/docs/xml/index.xml b/BugsSite/docs/en/xml/index.xml
similarity index 100%
rename from BugsSite/docs/xml/index.xml
rename to BugsSite/docs/en/xml/index.xml
diff --git a/BugsSite/docs/xml/installation.xml b/BugsSite/docs/en/xml/installation.xml
similarity index 74%
rename from BugsSite/docs/xml/installation.xml
rename to BugsSite/docs/en/xml/installation.xml
index 0591bab..526306f 100644
--- a/BugsSite/docs/xml/installation.xml
+++ b/BugsSite/docs/en/xml/installation.xml
@@ -1,5 +1,5 @@
 <!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"> -->
-<!-- $Id: installation.xml,v 1.136.2.7 2007/12/19 00:43:20 mkanat%bugzilla.org Exp $ -->
+<!-- $Id$ -->
 <chapter id="installing-bugzilla">
   <title>Installing Bugzilla</title>
 
@@ -9,8 +9,8 @@
     <note>
       <para>If you just want to <emphasis>use</emphasis> Bugzilla, 
       you do not need to install it. None of this chapter is relevant to
-      you. Ask your Bugzilla administrator
-      for the URL to access it over the web.
+      you. Ask your Bugzilla administrator for the URL to access it from
+      your web browser.
       </para>
     </note>
 
@@ -21,14 +21,6 @@
     instructions.
     </para>
 
-    <para>
-      As an alternative to following these instructions, you may wish to
-      try Arne Schirmacher's unofficial and unsupported 
-      <ulink url="http://www.softwaretesting.de/article/view/33/1/8/">Bugzilla
-      Installer</ulink>, which installs Bugzilla and all its prerequisites
-      on Linux or Solaris systems.
-    </para>
-
     <para>This guide assumes that you have administrative access to the
     Bugzilla machine. It not possible to
     install and run Bugzilla itself without administrative access except
@@ -54,8 +46,7 @@
     <procedure>
       <step>
         <para><link linkend="install-perl">Install Perl</link>
-        (&min-perl-ver; or above for non-Windows platforms; &min-perl-ver-win;
-        for Windows)
+        (&min-perl-ver; or above)
         </para>
       </step>
       <step>
@@ -89,7 +80,7 @@
     <section id="install-perl">
       <title>Perl</title>
 
-      <para>Installed Version Test: <filename>perl -v</filename></para>
+      <para>Installed Version Test: <programlisting>perl -v</programlisting></para>
       
       <para>Any machine that doesn't have Perl on it is a sad machine indeed.
       If you don't have it and your OS doesn't provide official packages, 
@@ -102,13 +93,14 @@
     <section id="install-database">
       <title>Database Engine</title>
       
-      <para>From Bugzilla 2.20, support is included for using both the MySQL and
-      PostgreSQL database servers. You only require one of these systems to make
-      use of Bugzilla.</para>
+      <para>
+        Bugzilla supports MySQL, PostgreSQL and Oracle as database servers.
+        You only require one of these systems to make use of Bugzilla.
+      </para>
 
       <section id="install-mysql">
           <title>MySQL</title>
-          <para>Installed Version Test: <filename>mysql -V</filename></para>
+          <para>Installed Version Test: <programlisting>mysql -V</programlisting></para>
       
           <para>
           If you don't have it and your OS doesn't provide official packages, 
@@ -135,7 +127,7 @@
       
       <section id="install-pg">
           <title>PostgreSQL</title>
-          <para>Installed Version Test: <filename>psql -V</filename></para>
+          <para>Installed Version Test: <programlisting>psql -V</programlisting></para>
       
           <para>
           If you don't have it and your OS doesn't provide official packages, 
@@ -149,7 +141,27 @@
           PostgreSQL server is started when the machine boots.
           </para>
       </section>
-      
+
+      <section id="install-oracle">
+        <title>Oracle</title>
+        <para>
+          Installed Version Test: <programlisting>select * from v$version</programlisting>
+          (you first have to log in into your DB)
+        </para>
+
+        <para>
+          If you don't have it and your OS doesn't provide official packages,
+          visit <ulink url="http://www.oracle.com/"/>. You need Oracle
+          version &min-oracle-ver; or higher.
+        </para>
+
+        <para>
+          If you install from something other than a packaging/installation
+          system, such as .rpm (Redhat Package), .deb (Debian Package), .exe
+          (Windows Executable), or .msi (Microsoft Installer), make sure the
+          Oracle server is started when the machine boots.
+        </para>
+      </section>
     </section>
     
     <section id="install-webserver">
@@ -164,7 +176,7 @@
        However, we strongly recommend using the Apache web server
        (either 1.3.x or 2.x), and 
        the installation instructions usually assume you are
-        using it. If you have got Bugzilla working using another webserver,
+        using it. If you have got Bugzilla working using another web server,
         please share your experiences with us by filing a bug in &bzg-bugs;.
       </para>
       
@@ -179,13 +191,13 @@
       <title>Bugzilla</title>
 
       <para>
-        Download a Bugzilla tarball (or check it out from CVS) and place
+        <ulink url="http://www.bugzilla.org/download/">Download a Bugzilla tarball</ulink>
+        (or check it out from CVS) and place
         it in a suitable directory, accessible by the default web server user 
         (probably <quote>apache</quote> or <quote>www</quote>). 
-        Good locations are either directly in the main web space for your
-        web server or perhaps in 
-        <filename>/usr/local</filename>
-        with a symbolic link from the web space.
+        Good locations are either directly in the web server's document directories or
+        in <filename>/usr/local</filename> with a symbolic link to the web server's 
+        document directories or an alias in the web server's configuration.
       </para>
 
       <caution>
@@ -197,7 +209,7 @@
       </caution>
       
       <para>Once all the files are in a web accessible directory, make that
-      directory writable by your webserver's user. This is a temporary step
+      directory writable by your web server's user. This is a temporary step
       until you run the 
       <filename>checksetup.pl</filename>
       script, which locks down your installation.</para>
@@ -287,7 +299,7 @@
 
         <listitem>
           <para>
-            CGI &min-cgi-ver; or CGI &min-mp-cgi-ver; if using mod_perl
+            CGI &min-cgi-ver;
           </para>
         </listitem>
 
@@ -318,6 +330,12 @@
 
         <listitem>
           <para>
+            DBD::Oracle (&min-dbd-oracle-ver;) if using Oracle
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
             File::Spec (&min-file-spec-ver;)
           </para>
         </listitem>
@@ -421,6 +439,13 @@
 
         <listitem>
           <para>
+            Authen::Radius
+            (&min-authen-radius-ver;) for RADIUS Authentication
+          </para>
+        </listitem>
+
+        <listitem>
+          <para>
             <link linkend="install-modules-soap-lite">SOAP::Lite</link>
             (&min-soap-lite-ver;) for the web service interface
           </para>
@@ -464,7 +489,7 @@
         <listitem>
           <para>
             CGI
-            (&min-cgi-ver;) for mod_perl
+            (&min-mp-cgi-ver;) for mod_perl
           </para>
         </listitem>
 
@@ -647,7 +672,6 @@
     </section>
   </section>
   
-  
   <section id="configuration">
     <title>Configuration</title>
 
@@ -677,13 +701,22 @@
       </para>
       
       <para>
-        Load this file in your editor. The only value you 
-        <emphasis>need</emphasis> to change is $db_pass, the password for
+        Load this file in your editor. The only two values you
+        <emphasis>need</emphasis> to change are $db_driver and $db_pass,
+        respectively the type of the database and the password for
         the user you will create for your database. Pick a strong
         password (for simplicity, it should not contain single quote
-        characters) and put it here.
+        characters) and put it here. $db_driver can be either 'mysql',
+        'Pg' or 'oracle'.
       </para>
 
+      <note>
+        <para>
+          In Oracle, <literal>$db_name</literal> should actually be 
+          the SID name of your database (e.g. "XE" if you are using Oracle XE).
+        </para>
+      </note>
+
       <para>
         You may need to change the value of 
         <emphasis>webservergroup</emphasis> if your web server does not 
@@ -708,16 +741,8 @@
       <para>
         The other options in the <filename>localconfig</filename> file
         are documented by their accompanying comments. If you have a slightly
-        non-standard MySQL setup, you may wish to change one or more of
-        the other "$db_*" parameters. 
-      </para>
-      
-      <para>
-        You may also wish to change the names of 
-        the priorities, severities, operating systems and platforms for your
-        installation. However, you can always change these after installation
-        has finished; if you then re-run <filename>checksetup.pl</filename>,
-        the changes will get picked up.
+        non-standard database setup, you may wish to change one or more of
+        the other "$db_*" parameters.
       </para>
     </section>
     
@@ -725,8 +750,9 @@
       <title>Database Server</title>
       <para>
         This section deals with configuring your database server for use
-        with Bugzilla. Currently, MySQL (<xref linkend="mysql"/>) and
-        PostgreSQL (<xref linkend="postgresql"/>) are available.
+        with Bugzilla. Currently, MySQL (<xref linkend="mysql"/>),
+        PostgreSQL (<xref linkend="postgresql"/>) and Oracle (<xref linkend="oracle"/>)
+        are available.
       </para>
 
       <section id="database-schema">
@@ -752,35 +778,27 @@
             improving your installation's security.
           </para>
         </caution>
-        
-        <section id="install-setupdatabase">
-          <title>Allow large attachments</title>
-        
-          <para>
-            By default, MySQL will only accept packets up to 64Kb in size.
-            If you want to have attachments larger than this, you will need
-            to modify your <filename>/etc/my.cnf</filename> as below.
-          </para>
+ 
+        <section id="mysql-max-allowed-packet">
+          <title>Allow large attachments and many comments</title>
+          
+          <para>By default, MySQL will only allow you to insert things
+          into the database that are smaller than 64KB. 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>
+          
+          <para>To change MySQL's default, you need to edit your MySQL
+          configuration file, which is usually <filename>/etc/my.cnf</filename>
+          on Linux. We recommend that you allow at least 4MB packets by
+          adding the "max_allowed_packet" parameter to your MySQL 
+          configuration in the "[mysqld]" section, like this:</para>
 
-          <screen>  [mysqld]
-  # Allow packets up to 1M
-  max_allowed_packet=1M</screen>
-
-          <para>
-            There is also a parameter in Bugzilla called 'maxattachmentsize'
-            (default = 1000 Kb) that controls the maximum allowable attachment
-            size. Attachments larger than <emphasis>either</emphasis> the 
-            'max_allowed_packet' or 'maxattachmentsize' value will not be
-            accepted by Bugzilla.
-          </para>
-
-          <note>
-            <para>
-              This does not affect Big Files, attachments that are stored directly
-              on disk instead of in the database.  Their maximum size is
-              controlled using the 'maxlocalattachment' parameter.
-            </para>
-          </note>
+          <screen>[mysqld]
+# Allow packets up to 4MB
+max_allowed_packet=4M
+          </screen>
         </section>
         
         <section>
@@ -833,14 +851,15 @@
             Run the <filename>mysql</filename> command-line client and enter:
           </para>
 
-          <screen>  <prompt>mysql&gt;</prompt> GRANT SELECT, INSERT,
+          <screen>
+    <prompt>mysql&gt;</prompt> GRANT SELECT, INSERT,
            UPDATE, DELETE, INDEX, ALTER, CREATE, LOCK TABLES,
            CREATE TEMPORARY TABLES, DROP, REFERENCES ON bugs.*
            TO bugs@localhost IDENTIFIED BY '<replaceable>$db_pass</replaceable>';
-           <prompt>mysql&gt;</prompt> FLUSH PRIVILEGES;</screen>
+    <prompt>mysql&gt;</prompt> FLUSH PRIVILEGES;
+          </screen>
+        </section>
 
-        </section>      
-        
         <section>
           <title>Permit attachments table to grow beyond 4GB</title>
 
@@ -859,9 +878,9 @@
           </para>
 
           <screen>
-            <prompt>mysql&gt;</prompt> use <replaceable>$bugs_db</replaceable>
-            <prompt>mysql&gt;</prompt> ALTER TABLE attachments 
-            AVG_ROW_LENGTH=1000000, MAX_ROWS=20000;
+    <prompt>mysql&gt;</prompt> use <replaceable>$bugs_db</replaceable>
+    <prompt>mysql&gt;</prompt> ALTER TABLE attachments
+           AVG_ROW_LENGTH=1000000, MAX_ROWS=20000;
           </screen>
 
           <para>
@@ -931,6 +950,77 @@
           to the one you picked previously, while setting up the account.</para> 
         </section>
       </section>
+
+      <section id="oracle">
+        <title>Oracle</title>
+        <section>
+          <title>Create a New Tablespace</title>
+
+          <para>
+            You can use the existing tablespace or create a new one for Bugzilla.
+            To create a new tablespace, run the following command:
+          </para>
+
+          <programlisting>
+    CREATE TABLESPACE bugs
+    DATAFILE '<replaceable>$path_to_datafile</replaceable>' SIZE 500M
+    AUTOEXTEND ON NEXT 30M MAXSIZE UNLIMITED
+          </programlisting>
+
+          <para>
+            Here, the name of the tablespace is 'bugs', but you can
+            choose another name. <replaceable>$path_to_datafile</replaceable> is
+            the path to the file containing your database, for instance
+            <filename>/u01/oradata/bugzilla.dbf</filename>.
+            The initial size of the database file is set in this example to 500 Mb,
+            with an increment of 30 Mb everytime we reach the size limit of the file.
+          </para>
+        </section>
+
+        <section>
+          <title>Add a User to Oracle</title>
+
+          <para>
+            The user name and password must match what you set in
+            <filename>localconfig</filename> (<literal>$db_user</literal>
+            and <literal>$db_pass</literal>, respectively). Here, we assume that
+            the user name is 'bugs' and the tablespace name is the same
+            as above. 
+          </para>
+
+          <programlisting>
+    CREATE USER bugs
+    IDENTIFIED BY "<replaceable>$db_pass</replaceable>"
+    DEFAULT TABLESPACE bugs
+    TEMPORARY TABLESPACE TEMP
+    PROFILE DEFAULT;
+    -- GRANT/REVOKE ROLE PRIVILEGES
+    GRANT CONNECT TO bugs;
+    GRANT RESOURCE TO bugs;
+    -- GRANT/REVOKE SYSTEM PRIVILEGES
+    GRANT UNLIMITED TABLESPACE TO bugs;
+    GRANT EXECUTE ON CTXSYS.CTX_DDL TO bugs;
+          </programlisting>
+        </section>
+
+        <section>
+          <title>Configure the Web Server</title>
+
+          <para>
+            If you use Apache, append these lines to <filename>httpd.conf</filename>
+            to set ORACLE_HOME and LD_LIBRARY_PATH. For instance:
+          </para>
+
+          <programlisting>
+    SetEnv ORACLE_HOME /u01/app/oracle/product/10.2.0/
+    SetEnv LD_LIBRARY_PATH /u01/app/oracle/product/10.2.0/lib/
+          </programlisting>
+
+          <para>
+            When this is done, restart your web server.
+          </para>
+        </section>
+      </section>
     </section>  
 
     <section>
@@ -1048,14 +1138,14 @@
                 <para>
                 <filename>checksetup.pl</filename> can set tighter permissions
                 on Bugzilla's files and directories if it knows what group the
-                webserver runs as. Find the <computeroutput>Group</computeroutput>
+                web server runs as. Find the <computeroutput>Group</computeroutput>
                 line in <filename>httpd.conf</filename>, place the value found
                 there in the <replaceable>$webservergroup</replaceable> variable
                 in <filename>localconfig</filename>, then rerun
                 <filename>checksetup.pl</filename>.
                 </para>
             </step>
-    
+
             <step>
                 <para>
                 Optional: If Bugzilla does not actually reside in the webspace
@@ -1109,7 +1199,7 @@
                 </warning> 
                 
                 <programlisting>
-    PerlSwitches -I/var/www/html/bugzilla -w -T
+    PerlSwitches -I/var/www/html/bugzilla -I/var/www/html/bugzilla/lib -w -T
     PerlConfigRequire /var/www/html/bugzilla/mod_perl.pl
                 </programlisting>
             </step>
@@ -1118,7 +1208,7 @@
 				<para>
 					<filename>checksetup.pl</filename> can set tighter permissions
 					on Bugzilla's files and directories if it knows what group the
-					webserver runs as. Find the <computeroutput>Group</computeroutput>
+					web server runs as. Find the <computeroutput>Group</computeroutput>
 					line in <filename>httpd.conf</filename>, place the value found
 					there in the <replaceable>$webservergroup</replaceable> variable
 					in <filename>localconfig</filename>, then rerun
@@ -1241,7 +1331,7 @@
             The ActiveState install may have already created an entry for
             .pl files that is limited to <quote>GET,HEAD,POST</quote>. If
             so, this mapping should be <emphasis>removed</emphasis> as
-            Bugzilla's .pl files are not designed to be run via a webserver.
+            Bugzilla's .pl files are not designed to be run via a web server.
           </para>
         </note>
 
@@ -1313,7 +1403,6 @@
     </section> 
   </section>
 
-
   <section id="extraconfig">
     <title>Optional Additional Configuration</title>
 
@@ -1345,19 +1434,6 @@
         the Reports page.
       </para>
 
-      <para>
-        When upgrading Bugzilla, this format may change.
-        To create new status data, (re)move old data and run the following 
-        commands:
-      </para>
-
-      <screen>
-        <prompt>bash$</prompt>
-        <command>cd &lt;your-bugzilla-directory&gt;</command>
-        <prompt>bash$</prompt>
-        <command>./collectstats.pl --regenerate</command>
-      </screen>
-
       <note>
         <para>
           Windows does not have 'cron', but it does have the Task
@@ -1368,54 +1444,6 @@
       </note>
     </section>
 
-    <section>
-      <title>Dependency Charts</title>
-
-      <para>As well as the text-based dependency trees, Bugzilla also
-      supports a graphical view of dependency relationships, using a 
-      package called 'dot'.
-      Exactly how this works is controlled by the 'webdotbase' parameter,
-      which can have one of three values:
-      </para>
-
-      <para>
-        <orderedlist>
-          <listitem>
-            <para>
-            A complete file path to the command 'dot' (part of 
-            <ulink url="http://www.graphviz.org/">GraphViz</ulink>) 
-            will generate the graphs locally
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-            A URL prefix pointing to an installation of the webdot package will
-            generate the graphs remotely
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-            A blank value will disable dependency graphing.
-            </para>
-          </listitem>
-        </orderedlist>
-      </para>
-      
-      <para>The easiest way to get this working is to install
-      <ulink url="http://www.graphviz.org/">GraphViz</ulink>. If you
-      do that, you need to
-      <ulink url="http://httpd.apache.org/docs/mod/mod_imap.html">enable
-      server-side image maps</ulink> in Apache.
-      Alternatively, you could set up a webdot server, or use the AT&amp;T 
-      public webdot server. This is the default for the webdotbase param, 
-      but it's often overloaded and slow. Note that AT&amp;T's server 
-      won't work
-      if Bugzilla is only accessible using HARTS. 
-      <emphasis>Editor's note: What the heck is HARTS? Google doesn't know...
-      </emphasis>
-      </para>
-   </section>
-
     <section id="installation-whining-cron">
       <title>The Whining Cron</title>
 
@@ -1480,163 +1508,7 @@
         </para>
       </note>
     </section>
-
-    <section id="patch-viewer">
-      <title>Patch Viewer</title>
-      
-      <para>
-        Patch Viewer is the engine behind Bugzilla's graphical display of
-        code patches. You can integrate this with copies of the
-        <filename>cvs</filename>, <filename>lxr</filename> and
-        <filename>bonsai</filename> tools if you have them, by giving
-        the locations of your installation of these tools in
-        <filename>editparams.cgi</filename>.
-      </para>
         
-      <para>
-        Patch Viewer also optionally will use the 
-        <filename>cvs</filename>, <filename>diff</filename> and 
-        <filename>interdiff</filename>
-        command-line utilities if they exist on the system.
-        Interdiff can be obtained from 
-        <ulink url="http://cyberelk.net/tim/patchutils/"/>.
-        If these programs are not in the system path, you can configure
-        their locations in <filename>localconfig</filename>.
-      </para>
-      
-
-    </section>
-    
-    <section id="bzldap">
-      <title>LDAP Authentication</title>
-
-      <para>LDAP authentication is a module for Bugzilla's plugin 
-      authentication architecture.
-      </para>
-
-      <para>
-      The existing authentication
-      scheme for Bugzilla uses email addresses as the primary user ID, and a
-      password to authenticate that user. All places within Bugzilla where
-      you need to deal with user ID (e.g assigning a bug) use the email
-      address. The LDAP authentication builds on top of this scheme, rather
-      than replacing it. The initial log in is done with a username and
-      password for the LDAP directory. Bugzilla tries to bind to LDAP using
-      those credentials, and if successful, try to map this account to a
-      Bugzilla account. If a  LDAP mail attribute is defined, the value of this
-      attribute is used, otherwise emailsuffix parameter is appended to LDAP
-      username to form a full email address. If an account for this address
-      already exists in your Bugzilla system, it will log in to that account.
-      If no account for that email address exists, one is created at the time
-      of login. (In this case, Bugzilla will attempt to use the "displayName"
-      or "cn" attribute to determine the user's full name.) After
-      authentication, all other user-related tasks are still handled by email
-      address, not LDAP username. You still assign bugs by email address, query
-      on users by email address, etc.
-      </para>
-
-      <caution>
-        <para>Because the Bugzilla account is not created until the first time
-        a user logs in, a user who has not yet logged is unknown to Bugzilla.
-        This means they cannot be used as an assignee or QA contact (default or
-        otherwise), added to any cc list, or any other such operation. One
-        possible workaround is the <filename>bugzilla_ldapsync.rb</filename>
-        script in the
-        <glossterm linkend="gloss-contrib"><filename class="directory">contrib</filename></glossterm> directory. Another possible solution is fixing
-        <ulink url="http://bugzilla.mozilla.org/show_bug.cgi?id=201069">bug
-        201069</ulink>.
-        </para>
-      </caution>
-
-      <para>Parameters required to use LDAP Authentication:</para>
-
-      <variablelist>
-        <varlistentry id="param-user_verify_class">
-          <term>user_verify_class</term>
-          <listitem>
-            <para>This parameter should be set to <quote>LDAP</quote>
-            <emphasis>only</emphasis> if you will be using an LDAP directory
-            for authentication. If you set this param to <quote>LDAP</quote> but
-            fail to set up the other parameters listed below you will not be
-            able to log back in to Bugzilla one you log out. If this happens
-            to you, you will need to manually edit
-            <filename>data/params</filename> and set user_verify_class to
-            <quote>DB</quote>.
-            </para>
-          </listitem>
-        </varlistentry>
-
-        <varlistentry id="param-LDAPserver">
-          <term>LDAPserver</term>
-          <listitem>
-            <para>This parameter should be set to the name (and optionally the
-            port) of your LDAP server. If no port is specified, it assumes
-            the default LDAP port of 389.
-            </para>
-            <para>Ex. <quote>ldap.company.com</quote>
-             or <quote>ldap.company.com:3268</quote>
-            </para>
-            <para>You can also specify a LDAP URI, so as to use other
-            protocols, such as LDAPS or LDAPI. If port was not specified in
-            the URI, the default is either 389 or 636 for 'LDAP' and 'LDAPS'
-            schemes respectively.
-            </para>
-            <para>Ex. <quote>ldap://ldap.company.com</quote>,
-            <quote>ldaps://ldap.company.com</quote> or
-            <quote>ldapi://%2fvar%2flib%2fldap_sock</quote>
-            </para>
-           </listitem>
-         </varlistentry>
-
-         <varlistentry id="param-LDAPbinddn">
-           <term>LDAPbinddn [Optional]</term>
-           <listitem>
-             <para>Some LDAP servers will not allow an anonymous bind to search
-             the directory. If this is the case with your configuration you
-             should set the LDAPbinddn parameter to the user account Bugzilla
-             should use instead of the anonymous bind.
-             </para>
-             <para>Ex. <quote>cn=default,cn=user:password</quote></para>
-           </listitem>
-         </varlistentry>
-
-         <varlistentry id="param-LDAPBaseDN">
-           <term>LDAPBaseDN</term>
-           <listitem>
-             <para>The LDAPBaseDN parameter should be set to the location in
-             your LDAP tree that you would like to search for email addresses.
-             Your uids should be unique under the DN specified here.
-             </para>
-             <para>Ex. <quote>ou=People,o=Company</quote></para>
-           </listitem>
-         </varlistentry>
-
-         <varlistentry id="param-LDAPuidattribute">
-           <term>LDAPuidattribute</term>
-           <listitem>
-             <para>The LDAPuidattribute parameter should be set to the attribute
-             which contains the unique UID of your users. The value retrieved
-             from this attribute will be used when attempting to bind as the
-             user to confirm their password.
-             </para>
-             <para>Ex. <quote>uid</quote></para>
-           </listitem>
-         </varlistentry>
-
-         <varlistentry id="param-LDAPmailattribute">
-           <term>LDAPmailattribute</term>
-           <listitem>
-             <para>The LDAPmailattribute parameter should be the name of the
-             attribute which contains the email address your users will enter
-             into the Bugzilla login boxes.
-             </para>
-             <para>Ex. <quote>mail</quote></para>
-           </listitem>
-          </varlistentry>
-      </variablelist>
-
-    </section>
-    
     <section id="apache-addtype">
       <title>Serving Alternate Formats with the right MIME type</title>
 
@@ -1726,7 +1598,7 @@
         based system such as GNU/Linux.  That said, if you do want to get
         Bugzilla running on Windows, you will need to make the following
         adjustments. A detailed step-by-step
-        <ulink url="http://www.bugzilla.org/docs/win32install.html">
+        <ulink url="https://wiki.mozilla.org/Bugzilla:Win32Install">
         installation guide for Windows</ulink> is also available
         if you need more help with your installation.
       </para>
@@ -1741,6 +1613,15 @@
            The following instructions assume that you are using version
            5.8.1 of ActiveState.
           </para>
+
+          <note>
+            <para>
+             These instructions are for 32-bit versions of Windows. If you are
+             using a 64-bit version of Windows, you will need to install 32-bit
+             Perl in order to install the 32-bit modules as described below.
+            </para>
+          </note>
+
         </section>
   
       <section id="win32-perl-modules">
@@ -1750,7 +1631,9 @@
           Bugzilla on Windows requires the same perl modules found in
           <xref linkend="install-perlmodules"/>. The main difference is that
           windows uses <glossterm linkend="gloss-ppm">PPM</glossterm> instead
-          of CPAN.
+          of CPAN. ActiveState provides a GUI to manage Perl modules. We highly
+          recommend that you use it. If you prefer to use ppm from the
+          command-line, type:
         </para>
 
         <programlisting>
@@ -1759,14 +1642,31 @@
 
         <para>
           The best source for the Windows PPM modules needed for Bugzilla
-          is probably the Bugzilla Test Server (aka 'Landfill'), so 
-          you should add the Landfill package repository as follows:
+          is probably the theory58S website, which you can add to your list
+          of repositories as follows (for Perl 5.8.x):
         </para>
 
         <programlisting>
-<command>ppm repository add landfill http://www.landfill.bugzilla.org/ppm/</command>
+<command>ppm repo add theory58S http://theoryx5.uwinnipeg.ca/ppms/</command>
         </programlisting>
 
+        <para>
+          If you are using Perl 5.10.x, you cannot use the same PPM modules as Perl
+          5.8.x as they are incompatible. In this case, you should add the following
+          repository:
+        </para>
+        <programlisting>
+<command>ppm repo add theory58S http://cpan.uwinnipeg.ca/PPMPackages/10xx/</command>
+        </programlisting>
+
+        <note>
+          <para>
+            In versions prior to 5.8.8 build 819 of PPM the command is 
+            <programlisting>
+<command>ppm repository add theory58S http://theoryx5.uwinnipeg.ca/ppms/</command>
+            </programlisting>
+          </para>
+        </note>
         <note>
           <para>
             The PPM repository stores modules in 'packages' that may have
@@ -1863,19 +1763,20 @@
       <section id="macosx-libraries">
         <title>Libraries &amp; Perl Modules on Mac OS X</title>
 
-        <para>Apple did not include the GD library with Mac OS X. Bugzilla
+        <para>Apple does not include the GD library with Mac OS X. Bugzilla
         needs this for bug graphs.</para>
 
-        <para>You can install it using a program called
-        Fink, which is similar in nature to the CPAN installer, but installs
-        common GNU utilities. Fink is available from
-        <ulink url="http://sourceforge.net/projects/fink/"/>.</para>
+        <para>You can use DarwinPorts (<ulink url="http://darwinports.com/"/>)
+        or Fink (<ulink url="http://sourceforge.net/projects/fink/"/>), both
+        of which are similar in nature to the CPAN installer, but install
+        common unix programs.</para>
 
-        <para>Follow the instructions for setting up Fink. Once it's installed,
-        you'll want to use it to install the <filename>gd2</filename> package.
+        <para>Follow the instructions for setting up DarwinPorts or Fink.
+        Once you have one installed, you'll want to use it to install the
+        <filename>gd2</filename> package.
         </para>
 
-        <para>It will prompt you for a number of dependencies, type 'y' and hit
+        <para>Fink will prompt you for a number of dependencies, type 'y' and hit
         enter to install all of the dependencies and then watch it work. You will
         then be able to use <glossterm linkend="gloss-cpan">CPAN</glossterm> to
         install the GD Perl module.
@@ -1896,9 +1797,10 @@
           </para>
         </note>
 
-        <para>Also available via Fink is <filename>expat</filename>. After using
-        fink to install the expat package you will be able to install
-        XML::Parser using CPAN. There is one caveat. Unlike recent versions of
+        <para>Also available via DarwinPorts and Fink is
+        <filename>expat</filename>. After installing the expat package, you
+        will be able to install XML::Parser using CPAN. If you use fink, there
+        is one caveat. Unlike recent versions of
         the GD module, XML::Parser doesn't prompt for the location of the
         required libraries. When using CPAN, you will need to use the following
         command sequence:
@@ -2065,10 +1967,12 @@
     <section>
       <title>Perl</title>
 
-      <para>On the extremely rare chance that you don't have Perl on
+      <para>
+      On the extremely rare chance that you don't have Perl on
       the machine, you will have to build the sources
       yourself. The following commands should get your system
-      installed with your own personal version of Perl:</para>
+      installed with your own personal version of Perl:
+      </para>
 
       <screen>
         <prompt>bash$</prompt>
@@ -2083,146 +1987,30 @@
         <command>make &amp;&amp; make test &amp;&amp; make install</command>
       </screen>
 
-      <para>Once you have Perl installed into a directory (probably
-      in <filename class="directory">~/perl/bin</filename>), you'll have to
-      change the locations on the scripts, which is detailed later on
-      this page.</para>
+      <para>
+      Once you have Perl installed into a directory (probably
+      in <filename class="directory">~/perl/bin</filename>), you will need to
+      install the Perl Modules, described below.
+      </para>
     </section>
 
     <section id="install-perlmodules-nonroot">
       <title>Perl Modules</title>
 
-      <para>Installing the Perl modules as a non-root user is probably the
-      hardest part of the process. There are two different methods: a
-      completely independant Perl with its own modules, or personal
-      modules using the current (root installed) version of Perl. The
-      independant method takes up quite a bit of disk space, but is
-      less complex, while the mixed method only uses as much space as the
-      modules themselves, but takes more work to setup.</para>
-
-      <section>
-        <title>The Independant Method</title>
-
-        <para>The independant method requires that you install your own
-        personal version of Perl, as detailed in the previous section. Once
-        installed, you can start the CPAN shell with the following
-        command:</para>
-
-        <para>
-          <screen>
-            <prompt>bash$</prompt>
-            <command>/home/foo/perl/bin/perl -MCPAN -e 'shell'</command>
-          </screen>
-        </para>
-
-        <para>And then:</para>
-
-        <para>
-          <screen>
-            <prompt>cpan&gt;</prompt>
-            <command>install Bundle::Bugzilla</command>
-          </screen>
-        </para>
-
-        <para>With this method, module installation will usually go a lot
-        smoother, but if you have any hang-ups, you can consult the next
-        section.</para>
-      </section>
-
-      <section>
-        <title>The Mixed Method</title>
-
-        <para>First, you'll need to configure CPAN to
-        install modules in your home directory. The CPAN FAQ says the
-        following on this issue:</para>
-
-        <para>
-          <programlisting>
-5)  I am not root, how can I install a module in a personal directory?
-
-    You will most probably like something like this:
-
-      o conf makepl_arg "LIB=~/myperl/lib \
-                         INSTALLMAN1DIR=~/myperl/man/man1 \
-                         INSTALLMAN3DIR=~/myperl/man/man3"
-    install Sybase::Sybperl
-
-    You can make this setting permanent like all "o conf" settings with "o conf commit".
-
-    You will have to add ~/myperl/man to the MANPATH environment variable and also tell your Perl programs to
-    look into ~/myperl/lib, e.g. by including
-
-      use lib "$ENV{HOME}/myperl/lib";
-
-    or setting the PERL5LIB environment variable.
-
-    Another thing you should bear in mind is that the UNINST parameter should never be set if you are not root.</programlisting>
-        </para>
-
-        <para>So, you will need to create a Perl directory in your home
-        directory, as well as the <filename class="directory">lib</filename>,
-        <filename class="directory">man</filename>,
-        <filename class="directory">man/man1</filename>, and
-        <filename class="directory">man/man3</filename> directories in that
-        Perl directory. Set the MANPATH variable and PERL5LIB variable, so
-        that the installation of the modules goes smoother. (Setting
-        UNINST=0 in your "make install" options, on the CPAN first-time
-        configuration, is also a good idea.)</para>
-
-        <para>After that, go into the CPAN shell:</para>
-
-        <para>
-          <screen>
-            <prompt>bash$</prompt>
-            <command>perl -MCPAN -e 'shell'</command>
-          </screen>
-        </para>
-
-        <para>From there, you will need to type in the above "o conf" command
-        and commit the changes. Then you can run through the installation:</para>
-
-        <para>
-          <screen>
-            <prompt>cpan&gt;</prompt>
-            <command>install Bundle::Bugzilla</command>
-          </screen>
-        </para>
-
-        <para>Most of the module installation process should go smoothly. However,
-        you may have some problems with Template. When you first start, you will
-        want to try to install Template with the XS Stash options on. If this
-        doesn't work, it may spit out C compiler error messages and croak back
-        to the CPAN shell prompt. So, redo the install, and turn it off. (In fact,
-        say no to all of the Template questions.) It may also start failing on a
-        few of the tests. If the total tests passed is a reasonable figure (90+%),
-        force the install with the following command:</para>
-
-        <para>
-          <screen>
-            <prompt>cpan&gt;</prompt>
-            <command>force install Template</command>
-          </screen>
-        </para>
-
-        <para>You may also want to install the other optional modules:</para>
-
-        <screen>
-          <prompt>cpan&gt;</prompt>
-          <command>install GD</command>
-          <prompt>cpan&gt;</prompt>
-          <command>install Chart::Base</command>
-          <prompt>cpan&gt;</prompt>
-          <command>install MIME::Parser</command>
-        </screen>
-
-      </section>
+      <para>
+      Installing the Perl modules as a non-root user is accomplished by
+      running the <filename>install-module.pl</filename>
+      script. For more details on this script, see 
+      <ulink url="api/install-module.html"><filename>install-module.pl</filename>
+      documentation</ulink>
+      </para>
     </section>
 
     <section>
       <title>HTTP Server</title>
 
       <para>Ideally, this also needs to be installed as root and
-      run under a special webserver account. As long as
+      run under a special web server account. As long as
       the web server will allow the running of *.cgi files outside of a
       cgi-bin, and a way of denying web access to certain files (such as a
       .htaccess file), you should be good in this department.</para>
@@ -2260,30 +2048,16 @@
     <section>
       <title>Bugzilla</title>
 
-      <para>If you had to install Perl modules as a non-root user
-      (<xref linkend="install-perlmodules-nonroot" />) or to non-standard
-      directories, you will need to change the scripts, setting the correct
-      location of the Perl modules:</para>
-
       <para>
-        <programlisting>perl -pi -e
-        's@use strict\;@use strict\; use lib \"/home/foo/perl/lib\"\;@'
-        *cgi *pl Bug.pm processmail syncshadowdb</programlisting>
-
-        Change <filename class="directory">/home/foo/perl/lib</filename> to
-        your personal Perl library directory. You can probably skip this
-        step if you are using the independant method of Perl module
-        installation.
-      </para>
-
-      <para>When you run <command>./checksetup.pl</command> to create
+      When you run <command>./checksetup.pl</command> to create
       the <filename>localconfig</filename> file, it will list the Perl
       modules it finds. If one is missing, go back and double-check the
-      module installation from the CPAN shell, then delete the
-      <filename>localconfig</filename> file and try again.</para>
+      module installation from <xref linkend="install-perlmodules-nonroot"/>, 
+      then delete the <filename>localconfig</filename> file and try again.
+      </para>
 
       <warning>
-        <para>The one option in <filename>localconfig</filename> you
+        <para>One option in <filename>localconfig</filename> you
         might have problems with is the web server group. If you can't
         successfully browse to the <filename>index.cgi</filename> (like
         a Forbidden error), you may have to relax your permissions,
@@ -2306,7 +2080,8 @@
         to do them for you via the system() command).
         <programlisting>        for i in docs graphs images js skins; do find $i -type d -exec chmod o+rx {} \; ; done
         for i in jpg gif css js png html rdf xul; do find . -name \*.$i -exec chmod o+r {} \; ; done
-        find . -name .htaccess -exec chmod o+r {} \;</programlisting>
+        find . -name .htaccess -exec chmod o+r {} \;
+        chmod o+x . data data/webdot</programlisting>
         Pay particular attention to the number of semicolons and dots.
         They are all important.  A future version of Bugzilla will
         hopefully be able to do this for you out of the box.</para>
@@ -2314,6 +2089,424 @@
     </section>
   </section>
 
+
+  <section id="upgrade">
+    <title>Upgrading to New Releases</title>
+    
+    <para>Upgrading to new Bugzilla releases is very simple. There is
+      a script included with Bugzilla that will automatically
+      do all of the database migration for you.</para>
+    
+    <para>The following sections explain how to upgrade from one
+      version of Bugzilla to another. Whether you are upgrading
+      from one bug-fix version to another (such as 3.0.1 to 3.0.2)
+      or from one major version to another (such as from 3.0 to 3.2),
+      the instructions are always the same.</para>
+
+    <note>
+      <para>
+        Any examples in the following sections are written as though the
+        user were updating to version 2.22.1, but the procedures are the
+        same no matter what version you're updating to. Also, in the
+        examples, the user's Bugzilla installation is found at
+        <filename>/var/www/html/bugzilla</filename>. If that is not the
+        same as the location of your Bugzilla installation, simply
+        substitute the proper paths where appropriate.
+      </para>
+    </note>
+
+    <section id="upgrade-before">
+      <title>Before You Upgrade</title>
+    
+      <para>Before you start your upgrade, there are a few important
+        steps to take:</para>
+
+      <orderedlist>
+        <listitem>
+          <para>
+            Read the <ulink url="http://www.bugzilla.org/releases/">Release
+            Notes</ulink> of the version you're upgrading to,
+            particularly the "Notes for Upgraders" section.
+          </para>
+        </listitem>
+        
+        <listitem>
+          <para>
+            View the Sanity Check (<xref linkend="sanitycheck"/>) page
+            on your installation before upgrading. Attempt to fix all warnings
+            that the page produces before you go any further, or you may
+            experience problems  during your upgrade.
+          </para>
+        </listitem>
+        
+        <listitem>
+          <para>
+            Shut down your Bugzilla installation by putting some HTML or
+            text in the shutdownhtml parameter
+            (see <xref linkend="parameters"/>).
+          </para>
+        </listitem>
+        
+        <listitem>
+          <para>
+            Make a backup of the Bugzilla database.
+            <emphasis>THIS IS VERY IMPORTANT</emphasis>. If
+            anything goes wrong during the upgrade, your installation
+            can be corrupted beyond recovery. Having a backup keeps you safe.
+          </para>
+
+          <warning>
+            <para>
+              Upgrading is a one-way process. You cannot "downgrade" an
+              upgraded Bugzilla. If you wish to revert to the old Bugzilla
+              version for any reason, you will have to restore your database
+              from this backup.
+            </para>
+          </warning>
+
+          <para>Here are some sample commands you could use to backup
+            your database, depending on what database system you're
+            using. You may have to modify these commands for your
+            particular setup.</para>
+          
+          <variablelist>
+            <varlistentry>
+              <term>MySQL:</term>
+              <listitem>
+                <para>
+                  <command>mysqldump --opt -u bugs -p bugs > bugs.sql</command>
+                </para>
+              </listitem>
+            </varlistentry>
+              
+            <varlistentry>
+              <term>PostgreSQL:</term>
+              <listitem>
+                <para>
+                  <command>pg_dump --no-privileges --no-owner -h localhost -U bugs
+                    > bugs.sql</command>
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+        </listitem>
+      </orderedlist>
+    </section>
+      
+    <section id="upgrade-files">
+      <title>Getting The New Bugzilla</title>
+      
+      <para>There are three ways to get the new version of Bugzilla.
+        We'll list them here briefly and then explain them
+        more later.</para>
+      
+      <variablelist>
+        <varlistentry>
+          <term>CVS (<xref linkend="upgrade-cvs"/>)</term>
+          <listitem>
+            <para>
+              If have <command>cvs</command> installed on your machine
+              and you have Internet access, this is the easiest way to
+              upgrade, particularly if you have made modifications
+              to the code or templates of Bugzilla.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>Download the tarball (<xref linkend="upgrade-tarball"/>)</term>
+          <listitem>
+            <para>
+              This is a very simple way to upgrade, and good if you
+              haven't made many (or any) modifications to the code or
+              templates of your Bugzilla.
+            </para>
+          </listitem>
+        </varlistentry>
+        
+        <varlistentry>
+          <term>Patches (<xref linkend="upgrade-patches"/>)</term>
+          <listitem>
+            <para>
+              If you have made modifications to your Bugzilla, and
+              you don't have Internet access or you don't want to use
+              cvs, then this is the best way to upgrade.
+            </para>
+            
+            <para>
+              You can only do minor upgrades (such as 3.0 to 3.0.1 or
+              3.0.1 to 3.0.2) with patches.
+            </para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+        
+      <section id="upgrade-modified">
+        <title>If you have modified your Bugzilla</title>
+      
+        <para>
+          If you have modified the code or templates of your Bugzilla,
+          then upgrading requires a bit more thought and effort.
+          A discussion of the various methods of updating compared with
+          degree and methods of local customization can be found in
+          <xref linkend="template-method"/>.
+        </para>
+
+        <para>
+          The larger the jump you are trying to make, the more difficult it
+          is going to be to upgrade if you have made local customizations.
+          Upgrading from 3.0 to 3.0.1 should be fairly painless even if
+          you are heavily customized, but going from 2.18 to 3.0 is going
+          to mean a fair bit of work re-writing your local changes to use
+          the new files, logic, templates, etc. If you have done no local
+          changes at all, however, then upgrading should be approximately
+          the same amount of work regardless of how long it has been since
+          your version was released.
+        </para>
+      </section>
+
+      <section id="upgrade-cvs">
+        <title>Upgrading using CVS</title>
+
+        <para>
+          This requires that you have cvs installed (most Unix machines do),
+          and requires that you are able to access cvs-mirror.mozilla.org
+          on port 2401, which may not be an option if you are behind a
+          highly restrictive firewall or don't have Internet access.
+        </para>
+
+        <para>
+          The following shows the sequence of commands needed to update a
+          Bugzilla installation via CVS, and a typical series of results.
+        </para>
+
+        <programlisting>
+bash$ <command>cd /var/www/html/bugzilla</command>
+bash$ <command>cvs login</command>
+Logging in to :pserver:anonymous@cvs-mirror.mozilla.org:2401/cvsroot
+CVS password: <emphasis>('anonymous', or just leave it blank)</emphasis>
+bash$ <command>cvs -q update -r BUGZILLA-2_22_1 -dP</command>
+P checksetup.pl
+P collectstats.pl
+P docs/rel_notes.txt
+P template/en/default/list/quips.html.tmpl
+<emphasis>(etc.)</emphasis>
+        </programlisting>
+
+        <caution>
+          <para>
+            If a line in the output from <command>cvs update</command> begins
+            with a <computeroutput>C</computeroutput>, then that represents a
+            file with local changes that CVS was unable to properly merge. You
+            need to resolve these conflicts manually before Bugzilla (or at
+            least the portion using that file) will be usable.
+          </para>
+        </caution>
+      </section>
+
+      <section id="upgrade-tarball">
+        <title>Upgrading using the tarball</title>
+
+        <para>
+          If you are unable (or unwilling) to use CVS, another option that's
+          always available is to obtain the latest tarball from the <ulink
+          url="http://www.bugzilla.org/download/">Download Page</ulink> and 
+          create a new Bugzilla installation from that.
+        </para>
+
+        <para>
+          This sequence of commands shows how to get the tarball from the
+          command-line; it is also possible to download it from the site
+          directly in a web browser. If you go that route, save the file
+          to the <filename class="directory">/var/www/html</filename>
+          directory (or its equivalent, if you use something else) and 
+          omit the first three lines of the example.
+        </para>
+
+        <programlisting>
+bash$ <command>cd /var/www/html</command>
+bash$ <command>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22.1.tar.gz</command>
+<emphasis>(Output omitted)</emphasis>
+bash$ <command>tar xzvf bugzilla-2.22.1.tar.gz</command>
+bugzilla-2.22.1/
+bugzilla-2.22.1/.cvsignore
+<emphasis>(Output truncated)</emphasis>
+bash$ <command>cd bugzilla-2.22.1</command>
+bash$ <command>cp ../bugzilla/localconfig* .</command>
+bash$ <command>cp -r ../bugzilla/data .</command>
+bash$ <command>cd ..</command>
+bash$ <command>mv bugzilla bugzilla.old</command>
+bash$ <command>mv bugzilla-2.22.1 bugzilla</command>
+        </programlisting>
+
+        <warning>
+          <para>
+            The <command>cp</command> commands both end with periods which
+            is a very important detail--it means that the destination
+            directory is the current working directory.
+          </para>
+        </warning>
+
+        <para>
+          This upgrade method will give you a clean install of Bugzilla.
+          That's fine if you don't have any local customizations that you
+          want to maintain. If you do have customizations, then you will 
+          need to reapply them by hand to the appropriate files.
+        </para>
+      </section>
+
+      <section id="upgrade-patches">
+        <title>Upgrading using patches</title>
+
+        <para>
+          A patch is a collection of all the bug fixes that have been made
+          since the last bug-fix release.
+        </para>
+        
+        <para>
+          If you are doing a bug-fix upgrade&mdash;that is, one where only the 
+          last number of the revision changes, such as from 2.22 to
+          2.22.1&mdash;then you have the option of obtaining and applying a
+          patch file from the <ulink
+          url="http://www.bugzilla.org/download/">Download Page</ulink>.
+        </para>
+        
+        <para>
+          As above, this example starts with obtaining the file via the 
+          command line. If you have already downloaded it, you can omit the
+          first two commands.
+        </para>
+
+        <programlisting>
+bash$ <command>cd /var/www/html/bugzilla</command>
+bash$ <command>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22-to-2.22.1.diff.gz</command>
+<emphasis>(Output omitted)</emphasis>
+bash$ <command>gunzip bugzilla-2.22-to-2.22.1.diff.gz</command>
+bash$ <command>patch -p1 &lt; bugzilla-2.22-to-2.22.1.diff</command>
+patching file checksetup.pl
+patching file collectstats.pl
+<emphasis>(etc.)</emphasis>
+        </programlisting>
+
+        <warning>
+          <para>
+            Be aware that upgrading from a patch file does not change the
+            entries in your <filename class="directory">CVS</filename> directory.
+            This could make it more difficult to upgrade using CVS
+            (<xref linkend="upgrade-cvs"/>) in the future.
+          </para>
+        </warning>
+
+      </section>
+    </section>
+
+    <section id="upgrade-completion">
+      <title>Completing Your Upgrade</title>
+
+      <para>
+        Now that you have the new Bugzilla code, there are a few final
+        steps to complete your upgrade.
+      </para>
+      
+      <orderedlist>
+        <listitem>
+          <para>
+            If your new Bugzilla installation is in a different
+            directory or on a different machine than your old Bugzilla
+            installation, make sure that you have copied the
+            <filename>data</filename> directory and the
+            <filename>localconfig</filename> file from your old Bugzilla
+            installation. (If you followed the tarball instructions
+            above, this has already happened.)
+          </para>
+        </listitem>
+        
+        <listitem>
+          <para>
+            If this is a major update, check that the configuration
+            (<xref linkend="configuration"/>) for your new Bugzilla is
+            up-to-date. Sometimes the configuration requirements change
+            between major versions.
+          </para>
+        </listitem>
+        
+        <listitem>
+          <para>
+            If you didn't do it as part of the above configuration step,
+            now you need to run <command>checksetup.pl</command>, which
+            will do everything required to convert your existing database
+            and settings for the new version:
+          </para>
+
+          <programlisting>
+bash$ <command>cd /var/www/html/bugzilla</command>
+bash$ <command>./checksetup.pl</command>
+          </programlisting>
+
+          <warning>
+            <para>
+              The period at the beginning of the command
+              <command>./checksetup.pl</command> is important and can not
+              be omitted.
+            </para>
+          </warning>
+          
+          <caution>
+            <para>
+              If this is a major upgrade (say, 2.22 to 3.0 or similar),
+              running <command>checksetup.pl</command> on a large
+              installation (75,000 or more bugs) can take a long time,
+              possibly several hours.
+            </para>
+          </caution>
+        </listitem>
+
+        <listitem>
+          <para>
+            Clear any HTML or text that you put into the shutdownhtml
+            parameter, to re-activate Bugzilla.
+          </para> 
+        </listitem>
+
+        <listitem>
+          <para>
+            View the Sanity Check (<xref linkend="sanitycheck"/>) page in your
+            upgraded Bugzilla.
+          </para>
+          <para>
+            It is recommended that, if possible, you fix any problems
+            you see, immediately. Failure to do this may mean that Bugzilla
+            will not work correctly. Be aware that if the sanity check page
+            contains more errors after an upgrade, it doesn't necessarily
+            mean there are more errors in your database than there were
+            before, as additional tests are added to the sanity check over
+            time, and it is possible that those errors weren't being
+            checked for in the old version.
+          </para>
+        </listitem>
+      </orderedlist>
+
+    </section>
+    
+    <section id="upgrade-notifications">
+      <title>Automatic Notifications of New Releases</title>
+
+      <para>
+        Bugzilla 3.0 introduced the ability to automatically notify
+        administrators when new releases are available, based on the
+        <literal>upgrade_notification</literal> parameter, see
+        <xref linkend="parameters"/>. Administrators will see these
+        notifications when they access the <filename>index.cgi</filename>
+        page, i.e. generally when logging in. Bugzilla will check once per
+        day for new releases, unless the parameter is set to
+        <quote>disabled</quote>. If you are behind a proxy, you may have to set
+        the <literal>proxy_url</literal> parameter accordingly. If the proxy
+        requires authentication, use the
+        <literal>http://user:pass@proxy_url/</literal> syntax.
+      </para>
+    </section>
+  </section>
+
 </chapter>
 
 <!-- Keep this comment at the end of the file
diff --git a/BugsSite/docs/xml/integration.xml b/BugsSite/docs/en/xml/integration.xml
similarity index 100%
rename from BugsSite/docs/xml/integration.xml
rename to BugsSite/docs/en/xml/integration.xml
diff --git a/BugsSite/docs/en/xml/introduction.xml b/BugsSite/docs/en/xml/introduction.xml
new file mode 100644
index 0000000..3390755
--- /dev/null
+++ b/BugsSite/docs/en/xml/introduction.xml
@@ -0,0 +1,149 @@
+<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/BugsSite/docs/xml/modules.xml b/BugsSite/docs/en/xml/modules.xml
similarity index 74%
rename from BugsSite/docs/xml/modules.xml
rename to BugsSite/docs/en/xml/modules.xml
index 6e325c5..3d4f6e5 100644
--- a/BugsSite/docs/xml/modules.xml
+++ b/BugsSite/docs/en/xml/modules.xml
@@ -24,15 +24,13 @@
         a 'make' utility.  The <command>nmake</command> utility provided with
         Microsoft Visual C++ may be used.  As an alternative, there is a
         utility called <command>dmake</command> available from CPAN which is
-        written entirely in Perl. The majority of the links given below, however,
-        are to pre-compiled versions of the modules, which can be installed
-        on Windows simply by issuing the following command once you have
-        downloaded the PPD file (which may be packaged within a ZIP file):
+        written entirely in Perl.
       </para>
       <para>
-        <screen>
-          <prompt>&gt;</prompt> ppm install &lt;filename.ppd&gt;
-        </screen>
+        As described in <xref linkend="modules-manual-download" />, however, most
+        packages already exist and are available from ActiveState or theory58S.
+        We highly recommend that you install them using the ppm GUI available with
+        ActiveState and to add the theory58S repository to your list of repositories.
       </para>
     </note>
   </section>
@@ -43,9 +41,11 @@
     <note>
       <para>
         Running Bugzilla on Windows requires the use of ActiveState
-        Perl 5.8.1 or higher. Some modules already exist in the core
-        distribution of ActiveState Perl so no PPM link is given.
-        (This is noted where it occurs.)
+        Perl 5.8.1 or higher. Many modules already exist in the core
+        distribution of ActiveState Perl. Additional modules can be downloaded
+        from <ulink url="http://theoryx5.uwinnipeg.ca/ppms/" /> if you use
+        Perl 5.8.x or from <ulink url="http://cpan.uwinnipeg.ca/PPMPackages/10xx/" />
+        if you use Perl 5.10.x.
       </para>
     </note>
 
@@ -53,7 +53,6 @@
       CGI:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/CGI.pm/"/>
-        PPM Download Link: Part of core distribution.
         Documentation: <ulink url="http://perldoc.perl.org/CGI.html"/>
       </literallayout>
     </para>
@@ -62,7 +61,6 @@
       Data-Dumper:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/Data-Dumper/"/>
-        PPM Download Page: Part of core distribution.
         Documentation: <ulink url="http://search.cpan.org/dist/Data-Dumper/Dumper.pm"/>
       </literallayout>
     </para>
@@ -71,7 +69,6 @@
       Date::Format (part of TimeDate):
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/TimeDate/"/>
-        PPM Download Link: <ulink url="http://landfill.bugzilla.org/ppm/TimeDate.ppd"/>
         Documentation: <ulink url="http://search.cpan.org/dist/TimeDate/lib/Date/Format.pm"/>
       </literallayout>
     </para>
@@ -80,7 +77,6 @@
       DBI:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/DBI/"/>
-        PPM Download Link: <ulink url="http://landfill.bugzilla.org/ppm/DBI.ppd"/>
         Documentation: <ulink url="http://dbi.perl.org/docs/"/>
       </literallayout>
     </para>
@@ -89,7 +85,6 @@
       DBD::mysql:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/DBD-mysql/"/>
-        PPM Download Link: <ulink url="http://landfill.bugzilla.org/ppm/DBD-mysql.ppd"/>
         Documentation: <ulink url="http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm"/>
       </literallayout>
     </para>
@@ -98,7 +93,6 @@
       DBD::Pg:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/DBD-Pg/"/>
-        PPM Download Link: <ulink url="http://theoryx5.uwinnipeg.ca/ppms/x86/DBD-Pg.tar.gz"/>
         Documentation: <ulink url="http://search.cpan.org/dist/DBD-Pg/Pg.pm"/>
       </literallayout>
     </para>
@@ -107,7 +101,6 @@
       File::Spec:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/File-Spec/"/>
-        PPM Download Page: Part of core distribution.
         Documentation: <ulink url="http://perldoc.perl.org/File/Spec.html"/>
       </literallayout>
     </para>
@@ -116,7 +109,6 @@
       Template-Toolkit:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/Template-Toolkit/"/>
-        PPM Download Link: <ulink url="http://landfill.bugzilla.org/ppm/Template-Toolkit.ppd"/>
         Documentation: <ulink url="http://www.template-toolkit.org/docs.html"/>
       </literallayout>
     </para>
@@ -125,7 +117,6 @@
       GD:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/GD/"/>
-        PPM Download Link: <ulink url="http://landfill.bugzilla.org/ppm/GD.ppd"/>
         Documentation: <ulink url="http://search.cpan.org/dist/GD/GD.pm"/>
       </literallayout>
     </para>
@@ -134,7 +125,6 @@
       Template::Plugin::GD:
       <literallayout>
        CPAN Download Page: <ulink url="http://search.cpan.org/dist/Template-GD/" />
-       PPM Download Link:  (Just install Template-Toolkit using the instructions below)
        Documentation: <ulink url="http://www.template-toolkit.org/docs/aqua/Modules/index.html" />
       </literallayout>
     </para>
@@ -143,7 +133,6 @@
       MIME::Parser (part of MIME-tools):
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/MIME-tools/"/>
-        PPM Download Link: <ulink url="http://ppm.activestate.com/PPMPackages/zips/8xx-builds-only/Windows/MIME-tools-5.411a.zip"/>
         Documentation: <ulink url="http://search.cpan.org/dist/MIME-tools/lib/MIME/Parser.pm"/>
       </literallayout>
     </para>
@@ -157,7 +146,6 @@
       Chart::Base:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/Chart/"/>
-        PPM Download Page: <ulink url="http://landfill.bugzilla.org/ppm/Chart.ppd"/>
         Documentation: <ulink url="http://search.cpan.org/dist/Chart/Chart.pod"/>
       </literallayout>
     </para>
@@ -166,7 +154,6 @@
       GD::Graph:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/GDGraph/"/>
-        PPM Download Link: <ulink url="http://landfill.bugzilla.org/ppm/GDGraph.ppd"/>
         Documentation: <ulink url="http://search.cpan.org/dist/GDGraph/Graph.pm"/>
       </literallayout>
     </para>
@@ -175,7 +162,6 @@
       GD::Text::Align (part of GD::Text::Util):
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/GDTextUtil/"/>
-        PPM Download Page: <ulink url="http://landfill.bugzilla.org/ppm/GDTextUtil.ppd"/>
         Documentation: <ulink url="http://search.cpan.org/dist/GDTextUtil/Text/Align.pm"/>
       </literallayout>
     </para>
@@ -184,7 +170,6 @@
       XML::Twig:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/XML-Twig/"/>
-        PPM Download Link: <ulink url="http://ppm.activestate.com/PPMPackages/zips/8xx-builds-only/Windows/XML-Twig-3.22.zip"/>
         Documentation: <ulink url="http://standards.ieee.org/resources/spasystem/twig/twig_stable.html"/>
       </literallayout>
     </para>
@@ -193,7 +178,6 @@
       PatchReader:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/author/JKEISER/PatchReader/"/>
-        PPM Download Link: <ulink url="http://landfill.bugzilla.org/ppm/PatchReader.ppd"/>
         Documentation: <ulink url="http://www.johnkeiser.com/mozilla/Patch_Viewer.html"/>
       </literallayout>
     </para>
@@ -202,7 +186,6 @@
       Image::Magick:
       <literallayout>
         CPAN Download Page: <ulink url="http://search.cpan.org/dist/PerlMagick/"/>
-        PPM Download Link: Included in Windows binary package.
         Documentation: <ulink url="http://www.imagemagick.org/script/resources.php"/>
       </literallayout>
     </para>
diff --git a/BugsSite/docs/xml/patches.xml b/BugsSite/docs/en/xml/patches.xml
similarity index 98%
rename from BugsSite/docs/xml/patches.xml
rename to BugsSite/docs/en/xml/patches.xml
index 12efb0c..b1d9281 100644
--- a/BugsSite/docs/xml/patches.xml
+++ b/BugsSite/docs/en/xml/patches.xml
@@ -21,7 +21,7 @@
 
     <warning>
       <para>
-        These files pre-date the templatization work done as part of the
+        These files pre-date the templatisation work done as part of the
         2.16 release, and have not been updated.
       </para>
     </warning>
diff --git a/BugsSite/docs/en/xml/requiredsoftware.xml b/BugsSite/docs/en/xml/requiredsoftware.xml
new file mode 100644
index 0000000..564e585
--- /dev/null
+++ b/BugsSite/docs/en/xml/requiredsoftware.xml
@@ -0,0 +1,86 @@
+<!-- <!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/BugsSite/docs/xml/security.xml b/BugsSite/docs/en/xml/security.xml
similarity index 95%
rename from BugsSite/docs/xml/security.xml
rename to BugsSite/docs/en/xml/security.xml
index d53afb8..c9a3f2a 100644
--- a/BugsSite/docs/xml/security.xml
+++ b/BugsSite/docs/en/xml/security.xml
@@ -1,5 +1,5 @@
 <!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"> -->
-<!-- $Id: security.xml,v 1.16.2.1 2007/09/03 10:13:24 lpsolit%gmail.com Exp $ -->
+<!-- $Id$ -->
 
 <chapter id="security">
 <title>Bugzilla Security</title>
@@ -57,7 +57,7 @@
     
       <note>
         <para>You will need to set the <option>webservergroup</option> option
-        in <filename>localconfig</filename> to the group your webserver runs
+        in <filename>localconfig</filename> to the group your web server runs
         as. This will allow <filename>./checksetup.pl</filename> to set file
         permissions on Unix systems so that nothing is world-writable.
         </para>
@@ -137,7 +137,7 @@
     <section id="security-mysql-network">
     <title>Network Access</title>
     
-      <para>If MySQL and your webserver both run on the same machine and you
+      <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
@@ -178,12 +178,12 @@
     
       <para>
         There are many files that are placed in the Bugzilla directory
-        area that should not be accessible from the web. Because of the way
+        area that should not be accessible from the web server. Because of the way
         Bugzilla is currently layed out, the list of what should and should not
         be accessible is rather complicated. A quick way is to run
-	<filename>testserver.pl</filename> to check if your web server serves
+        <filename>testserver.pl</filename> to check if your web server serves
         Bugzilla files as expected. If not, you may want to follow the few
-	steps below.
+        steps below.
       </para>
       
       <tip>
@@ -215,13 +215,6 @@
             <listitem>
               <para>Block everything</para>
             </listitem>
-            <listitem>
-              <para>But allow:
-              <simplelist type="inline">
-                <member><filename>duplicates.rdf</filename></member>
-              </simplelist>
-              </para>
-            </listitem>
           </itemizedlist>
         </listitem>
 
diff --git a/BugsSite/docs/xml/troubleshooting.xml b/BugsSite/docs/en/xml/troubleshooting.xml
similarity index 88%
rename from BugsSite/docs/xml/troubleshooting.xml
rename to BugsSite/docs/en/xml/troubleshooting.xml
index 019ca01..b8d3855 100644
--- a/BugsSite/docs/xml/troubleshooting.xml
+++ b/BugsSite/docs/en/xml/troubleshooting.xml
@@ -1,5 +1,5 @@
 <!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"> -->
-<!-- $Id: troubleshooting.xml,v 1.12 2006/07/31 22:22:51 mkanat%bugzilla.org Exp $ -->
+<!-- $Id$ -->
 
 <appendix id="troubleshooting">
 <title>Troubleshooting</title>
@@ -139,6 +139,49 @@
     </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>
   
diff --git a/BugsSite/docs/xml/using.xml b/BugsSite/docs/en/xml/using.xml
similarity index 87%
rename from BugsSite/docs/xml/using.xml
rename to BugsSite/docs/en/xml/using.xml
index 3c200a3..101a9d1 100644
--- a/BugsSite/docs/xml/using.xml
+++ b/BugsSite/docs/en/xml/using.xml
@@ -12,6 +12,12 @@
     installations there will necessarily have all Bugzilla features enabled,
     and different installations run different versions, so some things may not
     quite work as this document describes.</para>
+
+    <para>
+      Frequently Asked Questions (FAQ) are available and answered on
+      <ulink url="http://wiki.mozilla.org/Bugzilla:FAQ">wiki.mozilla.org</ulink>.
+      They may cover some questions you have which are left unanswered.
+    </para>
   </section>
       
   <section id="myaccount">
@@ -392,18 +398,10 @@
     values. If none is selected, then the field can take any value.</para>
 
     <para>
-      Once you've run a search, you can save it as a Saved Search, which
-      appears in the page footer.
-      On the Saved Searches tab of your User Preferences page (the Prefs link
-      in Bugzilla's footer), members of the group defined in the
-      querysharegroup parameter can share such Saved Searches with user groups
-      so that other users may use them.
-      At the same place, you can see Saved Searches other users are sharing, and
-      have them show up in your personal Bugzilla footer along with your own
-      Saved Searches.
-      If somebody is sharing a Search with a group she or he is allowed to
-      <link linkend="groups">assign users to</link>, the sharer may opt to have
-      the Search show up in the group's direct members' footers by default.
+      After a search is run, you can save it as a Saved Search, which
+      will appear in the page footer. If you are in the group defined 
+      by the "querysharegroup" parameter, you may share your queries 
+      with other users, see <xref linkend="savedsearches"/> for more details.
     </para>
 
     <section id="boolean">
@@ -452,17 +450,26 @@
       <section id="pronouns">
         <title>Pronoun Substitution</title>
         <para>
-          Sometimes, a query needs to compare a field containing
-          a user's ID (such as ReportedBy) with 
-          a user's ID (such as the user running the query or the user
-          to whom each bug is assigned). When the operator is either 
-          "equals" or "notequals", the value can be "%reporter%", 
-          "%assignee%", "%qacontact%", or "%user%."  The user pronoun
+          Sometimes, a query needs to compare a user-related field
+          (such as ReportedBy) with a role-specific user (such as the
+          user running the query or the user to whom each bug is assigned).
+          When the operator is either "equals" or "notequals", the value
+          can be "%reporter%", "%assignee%", "%qacontact%", or "%user%".
+          The user pronoun
           refers to the user who is executing the query or, in the case
           of whining reports, the user who will be the recipient
           of the report. The reporter, assignee, and qacontact
           pronouns refer to the corresponding fields in the bug.
         </para>
+        <para>
+          Boolean charts also let you type a group name in any user-related
+          field if the operator is either "equals", "notequals" or "anyexact".
+          This will let you query for any member belonging (or not) to the
+          specified group. The group name must be entered following the
+          "%group.foo%" syntax, where "foo" is the group name.
+          So if you are looking for bugs reported by any user being in the
+          "editbugs" group, then you can type "%group.editbugs%".
+        </para>
       </section>
       <section id="negation">
         <title>Negation</title>
@@ -566,7 +573,15 @@
         link which details how to use it.
       </para>
     </section>
-
+    <section id="casesensitivity">
+      <title>Case Sensitivity in Searches</title>
+      <para>
+      Bugzilla queries are case-insensitive and accent-insensitive, when
+      used with either MySQL or Oracle databases. When using Bugzilla with
+      PostgreSQL, however, some queries are case-sensitive. This is due to
+      the way PostgreSQL handles case and accent sensitivity. 
+      </para>
+    </section>
     <section id="list">
       <title>Bug Lists</title>
 
@@ -1014,6 +1029,15 @@
       </para>
     </section>
 
+    <section id="comment-wrapping">
+      <title>Server-Side Comment Wrapping</title>
+      <para>
+      Bugzilla stores comments unwrapped and wraps them at display time. This
+      ensures proper wrapping in all browsers. Lines beginning with the ">" 
+      character are assumed to be quotes, and are not wrapped.
+      </para>
+    </section>
+
     <section id="dependencytree">
       <title>Dependency Tree</title>
 
@@ -1064,69 +1088,82 @@
   <section id="userpreferences">
     <title>User Preferences</title>
 
-    <para>Once you have logged in, you can customize various aspects of
-    Bugzilla via the "Edit prefs" link in the page footer.
-    The preferences are split into three tabs:</para>
-
-    <section id="accountpreferences" xreflabel="Account Preferences">
-      <title>Account Preferences</title>
-
-      <para>On this tab, you can change your basic account information,
-      including your password, email address and real name. For security
-      reasons, in order to change anything on this page you must type your
-      <emphasis>current</emphasis>
-      password into the
-      <quote>Password</quote>
-      field at the top of the page.
-      If you attempt to change your email address, a confirmation
-      email is sent to both the old and new addresses, with a link to use to
-      confirm the change. This helps to prevent account hijacking.</para>
-    </section>
+    <para>
+    Once logged in, you can customize various aspects of
+    Bugzilla via the "Preferences" link in the page footer.
+    The preferences are split into five tabs:</para>
 
     <section id="generalpreferences" xreflabel="General Preferences">
       <title>General Preferences</title>
 
       <para>
-        This tab allows you to change several Bugzilla behavior.
+        This tab allows you to change several default settings of Bugzilla.
       </para>
 
       <itemizedlist spacing="compact">
         <listitem>
           <para>
+            Bugzilla's general appearance (skin) - select which skin to use.
+            Bugzilla supports adding custom skins.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            Quote the associated comment when you click on its reply link - sets
+            the behavior of the comment "Reply" link. Options include quoting the
+            full comment, just reference the comment number, or turn the link off.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            Language used in email - select which language email will be sent in,
+            from the list of available languages.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            After changing a bug - This controls what page is displayed after
+            changes to a bug are submitted. The options include to show the bug
+            just modified, to show the next bug in your list, or to do nothing.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            Enable tags for bugs - turn bug tagging on or off.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            Zoom textareas large when in use (requires JavaScript) - enable or
+            disable the automatic expanding of text areas when  text is being
+            entered into them. 
+          </para>
+        </listitem>
+        <listitem>
+          <para>
             Field separator character for CSV files -
-            This controls separator character used in CSV formatted Bug List.
+            Select between a comma and semi-colon for exported CSV bug lists.
           </para>
         </listitem>
         <listitem>
           <para>
-            After changing bugs - This controls which bugs or no bugs
-            are shown in the page after you changed bugs.
-            You can select the bug you've changed this time, or the next
-            bug of the list.
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            Add individual bugs to saved searches - this controls
-            whether you can add individual bugs to saved searches
-            or you can't.
+            Automatically add me to the CC list of bugs I change - set default
+            behavior of CC list. Options include "Always", "Never", and "Only
+            if I have no role on them". 
           </para>
         </listitem>
         <listitem>
           <para>
             When viewing a bug, show comments in this order -
-            This controls the order of comments, you can select below:
-            <simplelist>
-              <member>Initial description, comment 1, comment 2, ...</member>
-              <member>Initial description, last comment, ..., comment 2, comment 1.</member>
-              <member>Initial last comment, ..., comment 2, comment 1, description.</member>
-            </simplelist>
+            controls the order of comments. Options include "Oldest
+            to Newest", "Newest to Oldest" and "Newest to Oldest, but keep the
+            bug description at the top".
           </para>
         </listitem>
         <listitem>
           <para>
-            Show a quip at the top of each bug list - This controls
-            whether a quip will be shown on the Bug list page or not.
+            Show a quip at the top of each bug list - controls
+            whether a quip will be shown on the Bug list page.
           </para>
         </listitem>
       </itemizedlist>
@@ -1136,31 +1173,8 @@
       <title>Email Preferences</title>
 
       <para>
-        This tab controls the amount of email Bugzilla sends you.
-      </para>
-
-      <para>
-        The first item on this page is marked <quote>Users to watch</quote>.
-        When you enter one or more comma-delineated user accounts (usually email
-        addresses) into the text entry box, you will receive a copy of all the
-        bugmail those users are sent (security settings permitting).
-        This powerful functionality enables seamless transitions as developers
-        change projects or users go on holiday.
-      </para>
-
-      <note>
-        <para>
-          The ability to watch other users may not be available in all
-          Bugzilla installations. If you don't see this feature, and feel
-          that you need it, speak to your administrator.
-        </para>
-      </note>
-
-      <para>
-        Each user listed in the <quote>Users watching you</quote> field
-        has you listed in their <quote>Users to watch</quote> list
-        and can get bugmail according to your relationship to the bug and
-        their <quote>Field/recipient specific options</quote> setting.
+        This tab allows you to enable or disable email notification on
+        specific events.
       </para>
 
       <para>
@@ -1173,15 +1187,24 @@
 
       <note>
         <para>
-          Your Bugzilla administrator can stop a user from receiving
-          bugmail by adding the user's name to the 
-          <filename>data/nomail</filename> file. This is a drastic step
+          A Bugzilla administrator can stop a user from receiving
+          bugmail by clicking the <quote>Bugmail Disabled</quote> checkbox
+          when editing the user account. This is a drastic step
           best taken only for disabled accounts, as it overrides 
           the user's individual mail preferences.
         </para>
       </note>
   
       <para>
+        There are two global options -- <quote>Email me when someone
+        asks me to set a flag</quote> and <quote>Email me when someone
+        sets a flag I asked for</quote>. These define how you want to
+        receive bugmail with regards to flags. Their use is quite
+        straightforward; enable the checkboxes if you want Bugzilla to
+        send you mail under either of the above conditions.
+      </para>
+
+      <para>
         If you'd like to set your bugmail to something besides
         'Completely ON' and 'Completely OFF', the
         <quote>Field/recipient specific options</quote> table
@@ -1231,7 +1254,6 @@
         </listitem>
       </itemizedlist>
 
-
       <note>
         <para>
           Some columns may not be visible for your installation, depending
@@ -1264,31 +1286,224 @@
       </note>
 
       <para>
-        Two items not in the table (<quote>Email me when someone
-        asks me to set a flag</quote> and <quote>Email me when someone
-        sets a flag I asked for</quote>) define how you want to
-        receive bugmail with regards to flags. Their use is quite
-        straightforward; enable the checkboxes if you want Bugzilla to
-        send you mail under either of the above conditions.
+        Bugzilla has a feature called <quote>Users Watching</quote>.
+        When you enter one or more comma-delineated user accounts (usually email
+        addresses) into the text entry box, you will receive a copy of all the
+        bugmail those users are sent (security settings permitting).
+        This powerful functionality enables seamless transitions as developers
+        change projects or users go on holiday.
       </para>
 
+      <note>
+        <para>
+          The ability to watch other users may not be available in all
+          Bugzilla installations. If you don't see this feature, and feel
+          that you need it, speak to your administrator.
+        </para>
+      </note>
+
       <para>
-        By default, Bugzilla sends out email regardless of who made the
-        change... even if you were the one responsible for generating
-        the email in the first place. If you don't care to receive bugmail
-        from your own changes, check the box marked <quote>Only email me
-        reports of changes made by other people</quote>.
+        Each user listed in the <quote>Users watching you</quote> field
+        has you listed in their <quote>Users to watch</quote> list
+        and can get bugmail according to your relationship to the bug and
+        their <quote>Field/recipient specific options</quote> setting.
       </para>
 
     </section>
 
+    <section id="savedsearches" xreflabel="Saved Searches">
+      <title>Saved Searches</title>
+      <para>
+      On this tab you can view and run any Saved Searches that you have
+      created, and also any Saved Searches that other members of the group
+      defined in the "querysharegroup" parameter have shared. 
+      Saved Searches can be added to the page footer from this screen. 
+      If somebody is sharing a Search with a group she or he is allowed to
+      <link linkend="groups">assign users to</link>, the sharer may opt to have
+      the Search show up in the footer of the group's direct members by default.
+      </para>
+    </section>
+
+    <section id="accountpreferences" xreflabel="Name and Password">
+      <title>Name and Password</title>
+
+      <para>On this tab, you can change your basic account information,
+      including your password, email address and real name. For security
+      reasons, in order to change anything on this page you must type your
+      <emphasis>current</emphasis> password into the <quote>Password</quote>
+      field at the top of the page.
+      If you attempt to change your email address, a confirmation
+      email is sent to both the old and new addresses, with a link to use to
+      confirm the change. This helps to prevent account hijacking.</para>
+    </section>
+
     <section id="permissionsettings">
       <title>Permissions</title>
       
-      <para>This is a purely informative page which outlines your current
-      permissions on this installation of Bugzilla - what product groups you
-      are in, and whether you can edit bugs or perform various administration
-      functions.</para>
+      <para>
+      This is a purely informative page which outlines your current
+      permissions on this installation of Bugzilla.
+      </para>
+
+      <para>
+      A complete list of permissions is below. Only users with 
+      <emphasis>editusers</emphasis> privileges can change the permissions 
+      of other users.
+      </para>
+
+      <variablelist>
+        <varlistentry>
+          <term>
+            admin
+          </term>
+          <listitem>
+            <para> 
+             Indicates user is an Administrator.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+            bz_canusewhineatothers
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can configure whine reports for other users.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+             bz_canusewhines
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can configure whine reports for self.
+            </para>
+          </listitem>
+        </varlistentry>	 	
+ 
+        <varlistentry>
+          <term>
+             bz_sudoers
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can perform actions as other users.
+            </para>
+          </listitem>
+        </varlistentry>	
+
+        <varlistentry>
+          <term>
+             bz_sudo_protect
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can not be impersonated by other users.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+             canconfirm
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can confirm a bug or mark it a duplicate.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+             creategroups
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can create and destroy groups.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+             editbugs
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can edit all bug fields.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+             editclassifications
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can create, destroy, and edit classifications.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+             editcomponents
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can create, destroy, and edit components.
+            </para>
+          </listitem>
+        </varlistentry>
+ 
+        <varlistentry>
+          <term>
+             editkeywords
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can create, destroy, and edit keywords.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+             editusers
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can edit or disable users.
+            </para>
+          </listitem>
+        </varlistentry>
+	
+        <varlistentry>
+          <term>
+             tweakparams
+          </term>
+          <listitem>
+            <para> 
+             Indicates user can change Parameters.
+            </para>
+          </listitem>
+        </varlistentry>
+
+      </variablelist>
+
+       <note>
+        <para>
+        For more information on how permissions work in Bugzilla (i.e. who can
+        change what), see  <xref linkend="cust-change-permissions"/>. 
+        </para>
+       </note>
+
     </section>
   </section>
   
@@ -1426,7 +1641,7 @@
 
       </section>
       
-      <section>
+      <section id="charts-new-series">
         <title>Creating New Data Sets</title>
         
         <para>
diff --git a/BugsSite/docs/html/.cvsignore b/BugsSite/docs/html/.cvsignore
deleted file mode 100644
index 2d19fc7..0000000
--- a/BugsSite/docs/html/.cvsignore
+++ /dev/null
@@ -1 +0,0 @@
-*.html
diff --git a/BugsSite/docs/html/api/.cvsignore b/BugsSite/docs/html/api/.cvsignore
deleted file mode 100644
index 10d63bf..0000000
--- a/BugsSite/docs/html/api/.cvsignore
+++ /dev/null
@@ -1,3 +0,0 @@
-*.html
-Bugzilla
-contrib
diff --git a/BugsSite/docs/images/caution.gif b/BugsSite/docs/images/caution.gif
deleted file mode 100644
index a482230..0000000
--- a/BugsSite/docs/images/caution.gif
+++ /dev/null
Binary files differ
diff --git a/BugsSite/docs/images/tip.gif b/BugsSite/docs/images/tip.gif
deleted file mode 100644
index c8d5ae9..0000000
--- a/BugsSite/docs/images/tip.gif
+++ /dev/null
Binary files differ
diff --git a/BugsSite/docs/images/warning.gif b/BugsSite/docs/images/warning.gif
deleted file mode 100644
index 693ffc3..0000000
--- a/BugsSite/docs/images/warning.gif
+++ /dev/null
Binary files differ
diff --git a/BugsSite/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm b/BugsSite/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
index 06c76c6..ab298a0 100644
--- a/BugsSite/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
+++ b/BugsSite/docs/lib/Pod/Simple/HTMLBatch/Bugzilla.pm
@@ -31,7 +31,8 @@
 # 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'],
+    Files      => ['importxml', 'contrib', 'checksetup', 'email_in', 'install-module',
+                   'sanitycheck'],
     Modules    => ['bugzilla'],
     Extensions => ['extensions'],
 };
@@ -70,6 +71,7 @@
             my $path = join( "/", '.', esc(@{$e->[3]}) )
                . $Pod::Simple::HTML::HTML_EXTENSION;
             my $description = $self->{bugzilla_desc}->{$name} || '';
+            $description = esc($description);
             my $html = <<END_HTML;
 <tr class="$even_or_odd">
   <th><a href="$path">$name</a></th>
diff --git a/BugsSite/docs/makedocs.pl b/BugsSite/docs/makedocs.pl
index b636a2f..17e6c33 100755
--- a/BugsSite/docs/makedocs.pl
+++ b/BugsSite/docs/makedocs.pl
@@ -26,6 +26,7 @@
 # This script compiles all the documentation.
 
 use strict;
+use Cwd;
 
 # We need to be in this directory to use our libraries.
 BEGIN {
@@ -34,7 +35,7 @@
     chdir dirname($0);
 }
 
-use lib qw(.. lib);
+use lib qw(.. ../lib lib);
 
 # We only compile our POD if Pod::Simple is installed. We do the checks
 # this way so that if there's a compile error in Pod::Simple::HTML::Bugzilla,
@@ -58,7 +59,7 @@
 my $modules = REQUIRED_MODULES;
 my $opt_modules = OPTIONAL_MODULES;
 
-open(ENTITIES, '>', 'xml/bugzilla.ent') or die('Could not open xml/bugzilla.ent: ' . $!);
+open(ENTITIES, '>', 'bugzilla.ent') or die('Could not open bugzilla.ent: ' . $!);
 print ENTITIES '<?xml version="1.0"?>' ."\n\n";
 print ENTITIES '<!-- Module Versions -->' . "\n";
 foreach my $module (@$modules, @$opt_modules)
@@ -72,9 +73,11 @@
     print ENTITIES '<!ENTITY min-' . $name . '-ver "'.$version.'">' . "\n";
 }
 
-# CGI is a special case, because it has an optional version *and* a required
-# version.
-my ($cgi_opt) = grep($_->{package} eq 'CGI', @$opt_modules);
+# 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";
@@ -82,7 +85,8 @@
 my $db_modules = DB_MODULE;
 foreach my $db (keys %$db_modules) {
     my $dbd  = $db_modules->{$db}->{dbd};
-    my $name = $dbd->{package};
+    my $name = $dbd->{module};
+    $name =~ s/::/-/g;
     $name = lc($name);
     my $version    = $dbd->{version} || 'any';
     my $db_version = $db_modules->{$db}->{'db_version'};
@@ -156,10 +160,10 @@
 
     $converter->contents_page_start($contents_start);
     $converter->contents_page_end("</body></html>");
-    $converter->add_css('style.css');
+    $converter->add_css('./../../../style.css');
     $converter->javascript_flurry(0);
     $converter->css_flurry(0);
-    $converter->batch_convert(['../'], 'html/api/');
+    $converter->batch_convert(['../../'], 'html/api/');
 
     print "\n";
 }
@@ -168,38 +172,64 @@
 # Make the docs ...
 ###############################################################################
 
-if (!-d 'txt') {
-    unlink 'txt';
-    mkdir 'txt', 0755;
+my @langs;
+# search for sub directories which have a 'xml' sub-directory
+opendir(LANGS, './');
+foreach my $dir (readdir(LANGS)) {
+    next if (($dir eq '.') || ($dir eq '..') || (! -d $dir));
+    if (-d "$dir/xml") {
+        push(@langs, $dir);
+    }
 }
-if (!-d 'pdf') {
-    unlink 'pdf';
-    mkdir 'pdf', 0755;
-}
+closedir(LANGS);
 
-make_pod() if $pod_simple;
-exit unless $build_docs;
+my $docparent = getcwd();
+foreach my $lang (@langs) {
+    chdir "$docparent/$lang";
+    MakeDocs(undef, 'cp ../bugzilla.ent ./xml/');
 
-chdir 'html';
+    if (!-d 'txt') {
+        unlink 'txt';
+        mkdir 'txt', 0755;
+    }
+    if (!-d 'pdf') {
+        unlink 'pdf';
+        mkdir 'pdf', 0755;
+    }
+    if (!-d 'html') {
+        unlink 'html';
+        mkdir 'html', 0755;
+    }
+    if (!-d 'html/api') {
+        unlink 'html/api';
+        mkdir 'html/api', 0755;
+    }
 
-MakeDocs('separate HTML', "jade -t sgml -i html -d $LDP_HOME/ldp.dsl\#html " .
+    make_pod() if $pod_simple;
+    next unless $build_docs;
+
+    chdir 'html';
+
+    MakeDocs('separate HTML', "jade -t sgml -i html -d $LDP_HOME/ldp.dsl\#html " .
 	 "$JADE_PUB/xml.dcl ../xml/Bugzilla-Guide.xml");
-MakeDocs('big HTML', "jade -V nochunks -t sgml -i html -d " .
+    MakeDocs('big HTML', "jade -V nochunks -t sgml -i html -d " .
          "$LDP_HOME/ldp.dsl\#html $JADE_PUB/xml.dcl " .
 	 "../xml/Bugzilla-Guide.xml > Bugzilla-Guide.html");
-MakeDocs('big text', "lynx -dump -justify=off -nolist Bugzilla-Guide.html " .
+    MakeDocs('big text', "lynx -dump -justify=off -nolist Bugzilla-Guide.html " .
 	 "> ../txt/Bugzilla-Guide.txt");
 
-if (! grep($_ eq "--with-pdf", @ARGV)) {
-    exit;
-}
+    if (! grep($_ eq "--with-pdf", @ARGV)) {
+        next;
+    }
 
-MakeDocs('PDF', "jade -t tex -d $LDP_HOME/ldp.dsl\#print $JADE_PUB/xml.dcl " .
+    MakeDocs('PDF', "jade -t tex -d $LDP_HOME/ldp.dsl\#print $JADE_PUB/xml.dcl " .
          '../xml/Bugzilla-Guide.xml');
-chdir '../pdf';
-MakeDocs(undef, 'mv ../xml/Bugzilla-Guide.tex .');
-MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
-MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
-MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
-MakeDocs(undef, 'rm Bugzilla-Guide.tex Bugzilla-Guide.log Bugzilla-Guide.aux Bugzilla-Guide.out');
+    chdir '../pdf';
+    MakeDocs(undef, 'mv ../xml/Bugzilla-Guide.tex .');
+    MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
+    MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
+    MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
+    MakeDocs(undef, 'rm Bugzilla-Guide.tex Bugzilla-Guide.log Bugzilla-Guide.aux Bugzilla-Guide.out');
+
+}
 
diff --git a/BugsSite/docs/html/api/style.css b/BugsSite/docs/style.css
similarity index 100%
rename from BugsSite/docs/html/api/style.css
rename to BugsSite/docs/style.css
diff --git a/BugsSite/docs/xml/administration.xml b/BugsSite/docs/xml/administration.xml
deleted file mode 100644
index 66c9a2d..0000000
--- a/BugsSite/docs/xml/administration.xml
+++ /dev/null
@@ -1,2212 +0,0 @@
-<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
-<chapter id="administration">
-  <title>Administering Bugzilla</title>
-
-  <section id="parameters">
-    <title>Bugzilla Configuration</title>
-
-    <para>
-      Bugzilla is configured by changing various parameters, accessed
-      from the "Edit parameters" link in the page footer. Here are
-      some of the key parameters on that page. You should run down this
-      list and set them appropriately after installing Bugzilla.
-    </para>
-
-    <indexterm>
-      <primary>checklist</primary>
-    </indexterm>
-
-    <variablelist>
-      <varlistentry>
-        <term>
-          maintainer
-        </term>
-        <listitem>
-          <para> 
-            The maintainer parameter is the email address of the person 
-            responsible for maintaining this Bugzilla installation.
-            The address need not be that of a valid Bugzilla account.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          urlbase
-        </term>
-        <listitem>
-          <para>
-            This parameter defines the fully qualified domain name and web 
-            server path to your Bugzilla installation.
-          </para>
-
-          <para>
-            For example, if your Bugzilla query page is
-            <filename>http://www.foo.com/bugzilla/query.cgi</filename>, 
-            set your <quote>urlbase</quote>
-            to <filename>http://www.foo.com/bugzilla/</filename>.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          makeproductgroups
-        </term>
-        <listitem>
-          <para>
-            This dictates whether or not to automatically create groups
-            when new products are created.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <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>
-          mail_delivery_method
-        </term>
-        <listitem>
-          <para>
-            This is used to specify how email is sent, or if it is sent at 
-            all.  There are several options included for different MTAs, 
-            along with two additional options that disable email sending.  
-            "testfile" does not send mail, but instead saves it in 
-            <filename>data/mailer.testfile</filename> for later review.  
-            "none" disables email sending entirely.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          shadowdb
-        </term>
-        <listitem>
-          <para>
-            You run into an interesting problem when Bugzilla reaches a
-            high level of continuous activity. MySQL supports only table-level
-            write locking. What this means is that if someone needs to make a
-            change to a bug, they will lock the entire table until the operation
-            is complete. Locking for write also blocks reads until the write is
-            complete. Note that more recent versions of mysql support row level
-            locking using different table types. These types are slower than the
-            standard type, and Bugzilla does not yet take advantage of features
-            such as transactions which would justify this speed decrease. The
-            Bugzilla team are, however, happy to hear about any experiences with
-            row level locking and Bugzilla.
-          </para>
-
-          <para>
-            The <quote>shadowdb</quote> parameter was designed to get around
-            this limitation. While only a single user is allowed to write to
-            a table at a time, reads can continue unimpeded on a read-only
-            shadow copy of the database. Although your database size will
-            double, a shadow database can cause an enormous performance
-            improvement when implemented on extremely high-traffic Bugzilla
-            databases.
-          </para>
-        
-          <para>
-            As a guide, on reasonably old hardware, mozilla.org began needing 
-            <quote>shadowdb</quote> when they reached around 40,000 Bugzilla
-            users with several hundred Bugzilla bug changes and comments per day.
-          </para>
-
-          <para>
-            The value of the parameter defines the name of the shadow bug
-            database. You will need to set the host and port settings from
-            the params page, and set up replication in your database server
-            so that updates reach this readonly mirror. Consult your database
-            documentation for more detail.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          shutdownhtml
-        </term>
-        <listitem>
-          <para>
-            If you need to shut down Bugzilla to perform administration, enter
-            some descriptive text (with embedded HTML codes, if you'd like)
-            into this box. Anyone who tries to use Bugzilla (including admins)
-            will receive a page displaying this text. Users can neither log in
-            nor log out while shutdownhtml is enabled.
-          </para>
-
-          <note>
-            <para>
-              Although regular log-in capability is disabled while 'shutdownhtml'
-              is enabled, safeguards are in place to protect the unfortunate 
-              admin who loses connection to Bugzilla. Should this happen to you,
-              go directly to the <filename>editparams.cgi</filename> (by typing
-              the URL in manually, if necessary). Doing this will prompt you to
-              log in, and your name/password will be accepted here (but nowhere
-              else). 
-            </para>
-          </note>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          movebugs
-        </term>
-        <listitem>
-          <para>
-            This option is an undocumented feature to allow moving bugs
-            between separate Bugzilla installations.  You will need to understand
-            the source code in order to use this feature.  Please consult
-            <filename>movebugs.pl</filename> in your Bugzilla source tree for
-            further documentation, such as it is.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          useqacontact
-        </term>
-        <listitem>
-          <para>
-            This allows you to define an email address for each component, 
-            in addition to that of the default assignee, who will be sent
-            carbon copies of incoming bugs.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          globalwatcher
-        </term>
-        <listitem>
-          <para>
-            This allows to define specific users that will
-            receive notification each time a new bug in entered, or when
-            an existing bug changes, according to the normal groupset
-            permissions. It may be useful for sending notifications to a
-            mailing-list, for instance.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          usestatuswhiteboard
-        </term>
-        <listitem>
-          <para>
-            This defines whether you wish to have a free-form, overwritable field
-            associated with each bug. The advantage of the Status Whiteboard is
-            that it can be deleted or modified with ease, and provides an
-            easily-searchable field for indexing some bugs that have some trait
-            in common.         
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          whinedays
-        </term>
-        <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
-            untouched new bugs. If you do not plan to use this feature, simply 
-            do not set up the whining cron job described in the installation
-            instructions, or set this value to "0" (never whine).
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          commenton*
-        </term>
-        <listitem>
-          <para>
-            All these fields allow you to dictate what changes can pass
-            without comment, and which must have a comment from the
-            person who changed them.  Often, administrators will allow
-            users to add themselves to the CC list, accept bugs, or
-            change the Status Whiteboard without adding a comment as to
-            their reasons for the change, yet require that most other
-            changes come with an explanation.
-          </para>
-
-          <para>
-            Set the "commenton" options according to your site policy. It
-            is a wise idea to require comments when users resolve, reassign, or
-            reopen bugs at the very least. 
-          </para>
-
-          <note>
-            <para>
-              It is generally far better to require a developer comment
-              when resolving bugs than not. Few things are more annoying to bug
-              database users than having a developer mark a bug "fixed" without
-              any comment as to what the fix was (or even that it was truly
-              fixed!)
-            </para>
-          </note>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          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>
-
-
-      <varlistentry>
-        <term>
-          noresolveonopenblockers
-        </term>
-        <listitem>
-          <para>
-            This option will prevent users from resolving bugs as FIXED if
-            they have unresolved dependencies. Only the FIXED resolution
-            is affected. Users will be still able to resolve bugs to
-            resolutions other than FIXED if they have unresolved dependent
-            bugs.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          sendmailnow
-        </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).
-          </para>
-        </listitem>
-      </varlistentry>
-
-    </variablelist>
-  </section>
-
-  <section id="useradmin">
-    <title>User Administration</title>
-
-    <section id="defaultuser">
-      <title>Creating the Default User</title>
-
-      <para>When you first run checksetup.pl after installing Bugzilla, it
-      will prompt you for the administrative username (email address) and
-      password for this "super user". If for some reason you delete
-      the "super user" account, re-running checksetup.pl will again prompt
-      you for this username and password.</para>
-
-      <tip>
-        <para>If you wish to add more administrative users, add them to 
-        the "admin" group and, optionally, edit the tweakparams, editusers,
-        creategroups, editcomponents, and editkeywords groups to add the
-        entire admin group to those groups (which is the case by default).
-        </para>
-      </tip>
-    </section>
-
-    <section id="manageusers">
-      <title>Managing Other Users</title>
-
-      <section id="user-account-search">
-        <title>Searching for existing users</title>
-
-        <para>
-          If you have <quote>editusers</quote> privileges or if you are allowed
-          to grant privileges for some groups, the <quote>Users</quote> link
-          appears in the footer.
-        </para>
-
-        <para>
-          The first screen you get is a search form to search for existing user
-          accounts. You can run searches based either on the ID, real name or
-          login name (i.e. the email address in most cases) of users. You can
-          search in different ways the listbox to the right of the text entry
-          box. You can match by case-insensitive substring (the default),
-          regular expression, a <emphasis>reverse</emphasis> regular expression
-          match, which finds every user name which does NOT match the regular
-          expression, or the exact string if you know exactly who you are looking for.
-        </para>
-
-        <para>
-          You can also restrict your search to users being in some specific group.
-          By default, the restriction is turned off. Then you get a list of
-          users matching your criteria, and clicking their login name lets you
-          edit their properties.
-        </para>
-      </section>
-
-      <section id="createnewusers">
-        <title>Creating new users</title>
-
-        <section id="self-registration">
-          <title>Self-registration</title>
-
-          <para>
-            By default, users can create their own user accounts by clicking the
-            <quote>New Account</quote> link at the bottom of each page (assuming
-            they aren't logged in as someone else already). If you want to disable
-            this self-registration, or if you want to restrict who can create his
-            own user account, you have to edit the <quote>createemailregexp</quote>
-            parameter in the <quote>Configuration</quote> page, see
-            <xref linkend="parameters" />.
-          </para>
-        </section>
-
-        <section id="user-account-creation">
-          <title>Accounts created by an administrator</title>
-
-          <para>
-            Users with <quote>editusers</quote> privileges, such as administrators,
-            can create user accounts for other users:
-          </para>
-
-          <orderedlist>
-            <listitem>
-              <para>After logging in, click the "Users" link at the footer of
-              the query page, and then click "Add a new user".</para>
-            </listitem>
-
-            <listitem>
-              <para>Fill out the form presented. This page is self-explanatory.
-              When done, click "Submit".</para>
-
-              <note>
-                <para>Adding a user this way will <emphasis>not</emphasis>
-                send an email informing them of their username and password.
-                While useful for creating dummy accounts (watchers which
-                shuttle mail to another system, for instance, or email
-                addresses which are a mailing list), in general it is
-                preferable to log out and use the <quote>New Account</quote>
-                button to create users, as it will pre-populate all the
-                required fields and also notify the user of her account name
-                and password.</para>
-              </note>
-            </listitem>
-          </orderedlist>
-        </section>
-      </section>
-
-      <section id="modifyusers">
-        <title>Modifying Users</title>
-
-        <para>Once you have found your user, you can change the following
-        fields:</para>
-
-        <itemizedlist>
-          <listitem>
-            <para>
-            <emphasis>Login Name</emphasis>: 
-            This is generally the user's full email address. However, if you
-            have are using the <quote>emailsuffix</quote> parameter, this may
-            just be the user's login name. Note that users can now change their
-            login names themselves (to any valid email address).
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-            <emphasis>Real Name</emphasis>: The user's real name. Note that
-            Bugzilla does not require this to create an account.</para>
-          </listitem>
-
-          <listitem>
-            <para>
-            <emphasis>Password</emphasis>: 
-            You can change the user's password here. Users can automatically
-            request a new password, so you shouldn't need to do this often.
-            If you want to disable an account, see Disable Text below.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-              <emphasis>Bugmail Disabled</emphasis>:
-              Mark this checkbox to disable bugmail and whinemail completely
-              for this account. This checkbox replaces the data/nomail file
-              which existed in older versions of Bugzilla.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-              <emphasis>Disable Text</emphasis>: 
-              If you type anything in this box, including just a space, the
-              user is prevented from logging in, or making any changes to 
-              bugs via the web interface. 
-              The HTML you type in this box is presented to the user when
-              they attempt to perform these actions, and should explain
-              why the account was disabled.
-            </para>
-            <para>
-              Users with disabled accounts will continue to receive
-              mail from Bugzilla; furthermore, they will not be able
-              to log in themselves to change their own preferences and
-              stop it. If you want an account (disabled or active) to
-              stop receiving mail, simply check the
-              <quote>Bugmail Disabled</quote> checkbox above.
-            </para>
-            <note>
-              <para>
-                Even users whose accounts have been disabled can still
-                submit bugs via the e-mail gateway, if one exists.
-                The e-mail gateway should <emphasis>not</emphasis> be
-                enabled for secure installations of Bugzilla.
-              </para>
-            </note>
-            <warning>
-              <para>
-                Don't disable all the administrator accounts!
-              </para>
-            </warning>
-          </listitem>
-
-          <listitem>
-            <para>
-            <emphasis>&lt;groupname&gt;</emphasis>: 
-            If you have created some groups, e.g. "securitysensitive", then
-            checkboxes will appear here to allow you to add users to, or
-            remove them from, these groups.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-            <emphasis>canconfirm</emphasis>: 
-            This field is only used if you have enabled the "unconfirmed"
-            status. If you enable this for a user,
-            that user can then move bugs from "Unconfirmed" to a "Confirmed"
-            status (e.g.: "New" status).</para>
-          </listitem>
-
-          <listitem>
-            <para>
-            <emphasis>creategroups</emphasis>: 
-            This option will allow a user to create and destroy groups in
-            Bugzilla.</para>
-          </listitem>
-
-          <listitem>
-            <para>
-            <emphasis>editbugs</emphasis>: 
-            Unless a user has this bit set, they can only edit those bugs
-            for which they are the assignee or the reporter. Even if this
-            option is unchecked, users can still add comments to bugs.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-            <emphasis>editcomponents</emphasis>: 
-            This flag allows a user to create new products and components,
-            as well as modify and destroy those that have no bugs associated
-            with them. If a product or component has bugs associated with it,
-            those bugs must be moved to a different product or component
-            before Bugzilla will allow them to be destroyed.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-            <emphasis>editkeywords</emphasis>: 
-            If you use Bugzilla's keyword functionality, enabling this
-            feature allows a user to create and destroy keywords. As always,
-            the keywords for existing bugs containing the keyword the user
-            wishes to destroy must be changed before Bugzilla will allow it
-            to die.</para>
-          </listitem>
-
-          <listitem>
-            <para>
-            <emphasis>editusers</emphasis>: 
-            This flag allows a user to do what you're doing right now: edit
-            other users. This will allow those with the right to do so to
-            remove administrator privileges from other users or grant them to
-            themselves. Enable with care.</para>
-          </listitem>
-
-
-          <listitem>
-            <para>
-            <emphasis>tweakparams</emphasis>: 
-            This flag allows a user to change Bugzilla's Params 
-            (using <filename>editparams.cgi</filename>.)</para>
-          </listitem>
-
-          <listitem>
-            <para> 
-            <emphasis>&lt;productname&gt;</emphasis>:
-            This allows an administrator to specify the products 
-            in which a user can see bugs. If you turn on the 
-            <quote>makeproductgroups</quote> parameter in
-            the Group Security Panel in the Parameters page, 
-            then Bugzilla creates one group per product (at the time you create 
-            the product), and this group has exactly the same name as the 
-            product itself. Note that for products that already exist when
-            the parameter is turned on, the corresponding group will not be
-            created. The user must still have the <quote>editbugs</quote> 
-            privilege to edit bugs in these products.</para>
-          </listitem>
-        </itemizedlist>
-      </section>
-
-      <section id="user-account-deletion">
-        <title>Deleting Users</title>
-        <para>
-          If the <quote>allowuserdeletion</quote> parameter is turned on, see
-          <xref linkend="parameters" />, then you can also delete user accounts.
-          Note that this is most of the time not the best thing to do. If only
-          a warning in a yellow box is displayed, then the deletion is safe.
-          If a warning is also displayed in a red box, then you should NOT try
-          to delete the user account, else you will get referential integrity
-          problems in your database, which can lead to unexpected behavior,
-          such as bugs not appearing in bug lists anymore, or data displaying
-          incorrectly. You have been warned!
-        </para>
-      </section>
-
-      <section id="impersonatingusers">
-        <title>Impersonating Users</title>
-        
-        <para>
-        There may be times when an administrator would like to do something as
-        another user.  The <command>sudo</command> feature may be used to do 
-        this.
-        </para>
-        
-        <note>
-          <para>
-          To use the sudo feature, you must be in the
-          <emphasis>bz_sudoers</emphasis> group.  By default, all
-          administrators are in this group.</para>
-        </note>
-        
-        <para>
-        If you have access to this feature, you may start a session by
-        going to the Edit Users page, Searching for a user and clicking on 
-        their login.  You should see a link below their login name titled 
-        "Impersonate this user".  Click on the link.  This will take you 
-        to a page where you will see a description of the feature and 
-        instructions for using it.  After reading the text, simply 
-        enter the login of the user you would like to impersonate, provide 
-        a short message explaining why you are doing this, and press the 
-        button.</para>
-        
-        <para>
-        As long as you are using this feature, everything you do will be done 
-        as if you were logged in as the user you are impersonating.</para>
-        
-        <warning>
-          <para>
-          The user you are impersonating will not be told about what you are 
-          doing.  If you do anything that results in mail being sent, that 
-          mail will appear to be from the user you are impersonating.  You 
-          should be extremely careful while using this feature.</para>
-        </warning>
-      </section>
-    </section>
-  </section>
-
-  <section id="classifications">
-    <title>Classifications</title>
-
-    <para>Classifications tend to be used in order to group several related
-    products into one distinct entity.</para>
-
-    <para>The classifications layer is disabled by default; it can be turned
-    on or off using the useclassification parameter,
-    in the <emphasis>Bug Fields</emphasis> section of the edit parameters screen.</para>
-
-    <para>Access to the administration of classifications is controlled using
-    the <emphasis>editclassifications</emphasis> system group, which defines
-    a privilege for creating, destroying, and editing classifications.</para>
-
-    <para>When activated, classifications will introduce an additional
-    step when filling bugs (dedicated to classification selection), and they
-    will also appear in the advanced search form.</para>
-  </section>
-
-  <section id="products">
-    <title>Products</title>
-
-    <para>
-    <glossterm linkend="gloss-product" baseform="product">
-    Products</glossterm>
-    tend to represent real-world
-    shipping products. E.g. if your company makes computer games, 
-    you should have one product per game, perhaps a 
-    <quote>Common</quote> product for units of technology used 
-    in multiple games, and maybe a few special products 
-    (Website, Administration...)
-    </para>
-
-    <para>
-    Many of Bugzilla's settings are configurable on a per-product
-    basis. The number of <quote>votes</quote> available to 
-    users is set per-product, as is the number of votes
-    required to move a bug automatically from the UNCONFIRMED 
-    status to the NEW status.
-    </para>
-
-    <para>
-    To create a new product:
-    </para>
-
-    <orderedlist>
-      <listitem>
-        <para> 
-        Select <quote>products</quote> from the footer.
-        </para>
-
-      </listitem>
-
-      <listitem>
-        <para>
-        Select the <quote>Add</quote> link in the bottom right.
-        </para>
-      </listitem>
-
-      <listitem>
-        <para>
-        Enter the name of the product and a description. The
-        Description field may contain HTML.
-        </para>
-      </listitem>
-    </orderedlist>
-
-
-<section>
-      <title>Assigning Group Controls to Products</title>
-      <para> 
-      On the <quote>Product Edit</quote> page, 
-      there is a link called 
-      <quote>Edit Group Access Controls</quote>. 
-      The settings on this page control the relationship 
-      of the groups to the product being edited.
-      </para> 
-      <para>
-      Groups may be applicable, default, and 
-      mandatory for each product. Groups can also control access 
-      to bugs for a given product, or be used to make bugs 
-      for a product totally read-only unless the group 
-      restrictions are met.
-      </para>
-      <para>
-      If any group has <emphasis>Entry</emphasis> selected, then the 
-      product will restrict bug entry to only those users 
-      who are members of all the groups with 
-      <emphasis>Entry</emphasis> selected.
-      </para>
-      <para>
-      If any group has <emphasis>Canedit</emphasis> selected, 
-      then the product will be read-only for any users 
-      who are not members of all of the groups with
-      <emphasis>Canedit</emphasis> selected. ONLY users who 
-      are members of all the <emphasis>Canedit</emphasis> groups 
-      will be able to edit. This is an 
-      additional restriction that further limits 
-      what can be edited by a user.
-      </para>
-      <para>
-      The following settings let you 
-      choose privileges on a <emphasis>per-product basis</emphasis>.
-      This is a convenient way to give privileges to 
-      some users for some products only, without having 
-      to give them global privileges which would affect 
-      all products.
-      </para>
-      <para>
-      Any group having <emphasis>editcomponents</emphasis> 
-      selected  allows users who are in this group to edit all 
-      aspects of this product, including components, milestones 
-      and versions.
-      </para>
-      <para>
-      Any group having <emphasis>canconfirm</emphasis> selected 
-      allows users who are in this group to confirm bugs 
-      in this product.
-      </para>
-      <para>
-      Any group having <emphasis>editbugs</emphasis> selected allows 
-      users who are in this group to edit all fields of 
-      bugs in this product.
-      </para>
-      <para>
-      The <emphasis>MemberControl</emphasis> and 
-      <emphasis>OtherControl</emphasis> fields indicate which 
-      bugs will be placed in this group according 
-      to the following definitions.
-      </para>
-      <para>
-      For each group, it is possible to specify if 
-      membership in that group is:
-      </para>
-      <orderedlist>
-        <listitem>
-          <para>
-          Required for bug entry. 
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-          Not applicable to this product(NA),
-          a possible restriction for a member of the 
-          group to place on a bug in this product(Shown),
-          a default restriction for a member of the 
-          group to place on a bug in this product(Default),
-          or a mandatory restriction to be placed on bugs 
-          in this product(Mandatory).
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-          Not applicable by non-members to this product(NA),
-          a possible restriction for a non-member of the 
-          group to place on a bug in this product(Shown),
-          a default restriction for a non-member of the 
-          group to place on a bug in this product(Default),
-          or a mandatory restriction to be placed on bugs 
-          in this product when entered by a non-member(Mandatory).
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-          Required in order to make <emphasis>any</emphasis> change
-          to bugs in this product <emphasis>including comments.</emphasis>
-          </para>
-        </listitem>
-      </orderedlist>
-      <para>These controls are often described in this order, so a 
-      product that requires a user to be a member of group "foo" 
-      to enter a bug and then requires that the bug stay restricted
-      to group "foo" at all times and that only members of group "foo"
-      can edit the bug even if they otherwise could see the bug would 
-      have its controls summarized by...</para>
-      <programlisting> 
-foo: ENTRY, MANDATORY/MANDATORY, CANEDIT
-      </programlisting>
-      
-    </section>
-
-
-
-  </section>
-
-  <section id="components">
-    <title>Components</title>
-
-    <para>Components are subsections of a Product. E.g. the computer game 
-    you are designing may have a "UI"
-    component, an "API" component, a "Sound System" component, and a
-    "Plugins" component, each overseen by a different programmer. It
-    often makes sense to divide Components in Bugzilla according to the
-    natural divisions of responsibility within your Product or
-    company.</para>
-
-    <para>
-    Each component has a default assignee and (if you turned it on in the parameters),
-    a QA Contact. The default assignee should be the primary person who fixes bugs in
-    that component. The QA Contact should be the person who will ensure
-    these bugs are completely fixed. The Assignee, QA Contact, and Reporter
-    will get email when new bugs are created in this Component and when
-    these bugs change. Default Assignee and Default QA Contact fields only
-    dictate the 
-    <emphasis>default assignments</emphasis>; 
-    these can be changed on bug submission, or at any later point in
-    a bug's life.</para>
-
-    <para>To create a new Component:</para>
-
-   <orderedlist>
-      <listitem>
-        <para>Select the <quote>Edit components</quote> link 
-        from the <quote>Edit product</quote> page</para>
-      </listitem>
-
-      <listitem>
-        <para>Select the <quote>Add</quote> link in the bottom right.</para>
-      </listitem>
-
-      <listitem>
-        <para>Fill out the <quote>Component</quote> field, a 
-        short <quote>Description</quote>, the 
-        <quote>Default Assignee</quote>, <quote>Default CC List</quote> 
-        and <quote>Default QA Contact</quote> (if enabled). 
-        The <quote>Component Description</quote> field may contain a 
-        limited subset of HTML tags. The <quote>Default Assignee</quote> 
-        field must be a login name already existing in the Bugzilla database. 
-        </para>
-      </listitem>
-    </orderedlist>
-  </section>
-
-  <section id="versions">
-    <title>Versions</title>
-
-    <para>Versions are the revisions of the product, such as "Flinders
-    3.1", "Flinders 95", and "Flinders 2000". Version is not a multi-select
-    field; the usual practice is to select the earliest version known to have
-    the bug.
-    </para>
-
-    <para>To create and edit Versions:</para>
-
-    <orderedlist>
-      <listitem>
-        <para>From the "Edit product" screen, select "Edit Versions"</para>
-      </listitem>
-
-      <listitem>
-        <para>You will notice that the product already has the default
-        version "undefined". Click the "Add" link in the bottom right.</para>
-      </listitem>
-
-      <listitem>
-        <para>Enter the name of the Version. This field takes text only. 
-        Then click the "Add" button.</para>
-      </listitem>
-
-    </orderedlist>
-  </section>
-
-  <section id="milestones">
-    <title>Milestones</title>
-
-    <para>Milestones are "targets" that you plan to get a bug fixed by. For
-    example, you have a bug that you plan to fix for your 3.0 release, it
-    would be assigned the milestone of 3.0.</para>
-
-    <note>
-      <para>Milestone options will only appear for a Product if you turned
-      on the "usetargetmilestone" Param in the "Edit Parameters" screen.
-      </para>
-    </note>
-
-    <para>To create new Milestones, set Default Milestones, and set
-    Milestone URL:</para>
-
-    <orderedlist>
-      <listitem>
-        <para>Select "Edit milestones" from the "Edit product" page.</para>
-      </listitem>
-
-      <listitem>
-        <para>Select "Add" in the bottom right corner.
-        text</para>
-      </listitem>
-
-      <listitem>
-        <para>Enter the name of the Milestone in the "Milestone" field. You
-        can optionally set the "sortkey", which is a positive or negative
-        number (-32768 to 32767) that defines where in the list this particular
-        milestone appears. This is because milestones often do not 
-        occur in alphanumeric order For example, "Future" might be
-        after "Release 1.2". Select "Add".</para>
-      </listitem>
-
-      <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>
-  
- <section id="flags-overview">
-   <title>Flags</title>
-   
-   <para>
-     Flags are a way to attach a specific status to a bug or attachment, 
-     either <quote>+</quote> or <quote>-</quote>. The meaning of these symbols depends on the text
-     the flag itself, but contextually they could mean pass/fail, 
-     accept/reject, approved/denied, or even a simple yes/no. If your site
-     allows requestable flags, then users may set a flag to <quote>?</quote> as a 
-     request to another user that they look at the bug/attachment, and set
-     the flag to its correct status.
-   </para>
-
-   <section id="flags-simpleexample">
-     <title>A Simple Example</title>
-
-     <para>
-       A developer might want to ask their manager, 
-       <quote>Should we fix this bug before we release version 2.0?</quote> 
-       They might want to do this for a <emphasis>lot</emphasis> of bugs,
-       so it would be nice to streamline the process...
-     </para>
-     <para>
-       In Bugzilla, it would work this way:
-       <orderedlist>
-         <listitem>
-           <para>
-             The Bugzilla administrator creates a flag type called 
-             <quote>blocking2.0</quote> that shows up on all bugs in 
-             your product.
-           </para>
- 
-           <para>
-             It shows up on the <quote>Show Bug</quote> screen
-             as the text <quote>blocking2.0</quote> with a drop-down box next
-             to it. The drop-down box contains four values: an empty space,
-             <quote>?</quote>, <quote>-</quote>, and <quote>+</quote>.
-           </para>
-         </listitem>
-         <listitem>
-           <para>The developer sets the flag to <quote>?</quote>.</para>
-         </listitem>
-         <listitem>
-           <para>
-             The manager sees the <computeroutput>blocking2.0</computeroutput>
-             flag with a <quote>?</quote> value.
-           </para>
-         </listitem>
-         <listitem>
-           <para>
-             If the manager thinks the feature should go into the product
-             before version 2.0 can be released, he sets the flag to 
-             <quote>+</quote>. Otherwise, he sets it to <quote>-</quote>.
-           </para>
-         </listitem>
-         <listitem>
-           <para>
-             Now, every Bugzilla user who looks at the bug knows whether or 
-             not the bug needs to be fixed before release of version 2.0.
-           </para>
-         </listitem>
-       </orderedlist>
-     </para>
-
-   </section>
-
-   <section id="flags-about">
-     <title>About Flags</title>
-
-     <section id="flag-values">
-       <title>Values</title>
-       <para>
-         Flags can have three values:
-         <variablelist>
-           <varlistentry>
-             <term><computeroutput>?</computeroutput></term>
-             <listitem><simpara>
-               A user is requesting that a status be set. (Think of it as 'A question is being asked'.)
-             </simpara></listitem>
-           </varlistentry>
-           <varlistentry>
-             <term><computeroutput>-</computeroutput></term>
-             <listitem><simpara>
-               The status has been set negatively. (The question has been answered <quote>no</quote>.)
-             </simpara></listitem>
-           </varlistentry>
-           <varlistentry>
-             <term><computeroutput>+</computeroutput></term>
-             <listitem><simpara>
-               The status has been set positively.
-               (The question has been answered <quote>yes</quote>.)
-             </simpara></listitem>
-           </varlistentry>
-         </variablelist>
-       </para>
-       <para>
-         Actually, there's a fourth value a flag can have -- 
-         <quote>unset</quote> -- which shows up as a blank space. This 
-         just means that nobody has expressed an opinion (or asked
-         someone else to express an opinion) about this bug or attachment.
-       </para>
-     </section>
-   </section>
-
-   <section id="flag-askto">
-     <title>Using flag requests</title>
-     <para>
-       If a flag has been defined as 'requestable', and a user has enough privileges
-       to request it (see below), the user can set the flag's status to <quote>?</quote>.
-       This status indicates that someone (a.k.a. <quote>the requester</quote>) is asking
-       someone else to set the flag to either <quote>+</quote> or <quote>-</quote>.
-     </para>
-     <para>
-       If a flag has been defined as 'specifically requestable', 
-       a text box will appear next to the flag into which the requester may
-       enter a Bugzilla username. That named person (a.k.a. <quote>the requestee</quote>)
-       will receive an email notifying them of the request, and pointing them
-       to the bug/attachment in question.
-     </para>
-     <para>
-       If a flag has <emphasis>not</emphasis> been defined as 'specifically requestable',
-       then no such text-box will appear. A request to set this flag cannot be made of
-       any specific individual, but must be asked <quote>to the wind</quote>.
-       A requester may <quote>ask the wind</quote> on any flag simply by leaving the text-box blank.
-     </para>
-   </section>
-
-   <section id="flag-types">
-     <title>Two Types of Flags</title>
-    
-     <para>
-       Flags can go in two places: on an attachment, or on a bug.
-     </para>
-
-     <section id="flag-type-attachment">
-       <title>Attachment Flags</title>
-      
-       <para>
-         Attachment flags are used to ask a question about a specific 
-         attachment on a bug.
-       </para>
-       <para>
-         Many Bugzilla installations use this to 
-         request that one developer <quote>review</quote> another 
-         developer's code before they check it in. They attach the code to
-         a bug report, and then set a flag on that attachment called
-         <quote>review</quote> to 
-         <computeroutput>review?boss@domain.com</computeroutput>.
-         boss@domain.com is then notified by email that
-         he has to check out that attachment and approve it or deny it.
-       </para>
-
-       <para>
-         For a Bugzilla user, attachment flags show up in three places:
-         <orderedlist>
-           <listitem>
-             <para>
-               On the list of attachments in the <quote>Show Bug</quote>
-               screen, you can see the current state of any flags that
-               have been set to ?, +, or -. You can see who asked about 
-               the flag (the requester), and who is being asked (the 
-               requestee).
-             </para>
-           </listitem>
-           <listitem>
-             <para>
-              When you <quote>Edit</quote> an attachment, you can 
-              see any settable flag, along with any flags that have 
-              already been set. This <quote>Edit Attachment</quote> 
-              screen is where you set flags to ?, -, +, or unset them.
-             </para>
-           </listitem>
-           <listitem>
-             <para>
-               Requests are listed in the <quote>Request Queue</quote>, which
-               is accessible from the <quote>My Requests</quote> link (if you are
-               logged in) or <quote>Requests</quote> link (if you are logged out)
-               visible in the footer of all pages.
-             </para>
-           </listitem>
-         </orderedlist>
-       </para>
-
-     </section>
-
-     <section id="flag-type-bug">
-       <title>Bug Flags</title>
-
-       <para>
-         Bug flags are used to set a status on the bug itself. You can 
-         see Bug Flags in the <quote>Show Bug</quote> and <quote>Requests</quote>
-         screens, as described above.
-       </para>
-       <para>
-         Only users with enough privileges (see below) may set flags on bugs.
-         This doesn't necessarily include the assignee, reporter, or users with the
-         <computeroutput>editbugs</computeroutput> permission.
-       </para>
-     </section>
-
-   </section>
-
-   <section id="flags-admin">
-     <title>Administering Flags</title>
-
-     <para>
-       If you have the <quote>editcomponents</quote> permission, you will
-       have <quote>Edit: ... | Flags | ...</quote> in your page footer.
-       Clicking on that link will bring you to the <quote>Administer 
-       Flag Types</quote> page. Here, you can select whether you want 
-       to create (or edit) a Bug flag, or an Attachment flag.
-     </para>
-     <para>
-       No matter which you choose, the interface is the same, so we'll 
-       just go over it once.
-     </para>
-
-     <section id="flags-create">
-       <title>Creating a Flag</title>
-       
-        <para>
-          When you click on the <quote>Create a Flag Type for...</quote>
-          link, you will be presented with a form. Here is what the fields in 
-          the form mean:
-        </para>
-
-        <section id="flags-create-field-name">
-          <title>Name</title>
-          <para>
-            This is the name of the flag. This will be displayed 
-            to Bugzilla users who are looking at or setting the flag. 
-            The name may contain any valid Unicode characters except commas
-            and spaces.
-          </para>
-        </section>
-
-        <section id="flags-create-field-description">
-          <title>Description</title>
-          <para>
-            The description describes the flag in more detail. It is visible
-            in a tooltip when hovering over a flag either in the <quote>Show Bug</quote>
-            or <quote>Edit Attachment</quote> pages. This field can be as
-            long as you like, and can contain any character you want.
-          </para>
-        </section>
-
-        <section id="flags-create-field-category">
-          <title>Category</title>
-
-          <para>
-            Default behaviour for a newly-created flag is to appear on
-            products and all components, which is why <quote>__Any__:__Any__</quote>
-            is already entered in the <quote>Inclusions</quote> box.
-            If this is not your desired behaviour, you must either set some
-            exclusions (for products on which you don't want the flag to appear),
-            or you must remove <quote>__Any__:__Any__</quote> from the Inclusions box
-            and define products/components specifically for this flag.
-          </para>
-
-          <para>
-            To create an Inclusion, select a Product from the top drop-down box.
-            You may also select a specific component from the bottom drop-down box.
-            (Setting <quote>__Any__</quote> for Product translates to, 
-            <quote>all the products in this Bugzilla</quote>.
-            Selecting  <quote>__Any__</quote> in the Component field means
-            <quote>all components in the selected product.</quote>) 
-            Selections made, press <quote>Include</quote>, and your
-            Product/Component pairing will show up in the <quote>Inclusions</quote> box on the right.
-          </para>
-
-          <para>
-            To create an Exclusion, the process is the same; select a Product from the
-            top drop-down box, select a specific component if you want one, and press
-            <quote>Exclude</quote>. The Product/Component pairing will show up in the 
-            <quote>Exclusions</quote> box on the right.
-          </para>
-
-          <para>
-            This flag <emphasis>will</emphasis> and <emphasis>can</emphasis> be set for any
-            products/components that appearing in the <quote>Inclusions</quote> box 
-            (or which fall under the appropriate <quote>__Any__</quote>). 
-            This flag <emphasis>will not</emphasis> appear (and therefore cannot be set) on
-            any products appearing in the <quote>Exclusions</quote> box.
-            <emphasis> IMPORTANT: Exclusions override inclusions.</emphasis>
-          </para>
-
-          <para>
-            You may select a Product without selecting a specific Component,
-            but you can't select a Component without a Product, or to select a
-            Component that does not belong to the named Product. If you do so,
-            Bugzilla will display an error message, even if all your products
-            have a component by that name.
-          </para>
-
-          <para><emphasis>Example:</emphasis> Let's say you have a product called 
-            <quote>Jet Plane</quote> that has thousands of components. You want
-            to be able to ask if a problem should be fixed in the next model of 
-            plane you release. We'll call the flag <quote>fixInNext</quote>.
-            But, there's one component in <quote>Jet Plane,</quote> 
-            called <quote>Pilot.</quote> It doesn't make sense to release a 
-            new pilot, so you don't want to have the flag show up in that component.
-            So, you include <quote>Jet Plane:__Any__</quote> and you exclude 
-            <quote>Jet Plane:Pilot</quote>.
-          </para>
-        </section>
-
-        <section id="flags-create-field-sortkey">
-          <title>Sort Key</title>
-          <para>
-            Flags normally show up in alphabetical order. If you want them to 
-            show up in a different order, you can use this key set the order on each flag. 
-            Flags with a lower sort key will appear before flags with a higher
-            sort key. Flags that have the same sort key will be sorted alphabetically,
-            but they will still be after flags with a lower sort key, and before flags
-            with a higher sort key.
-          </para>
-          <para>
-            <emphasis>Example:</emphasis> I have AFlag (Sort Key 100), BFlag (Sort Key 10), 
-            CFlag (Sort Key 10), and DFlag (Sort Key 1). These show up in
-            the order: DFlag, BFlag, CFlag, AFlag.
-          </para>
-        </section>
-
-        <section id="flags-create-field-active">
-          <title>Active</title>
-          <para>
-            Sometimes, you might want to keep old flag information in the 
-            Bugzilla database, but stop users from setting any new flags of this type.
-            To do this, uncheck <quote>active</quote>. Deactivated
-            flags will still show up in the UI if they are ?, +, or -, but they
-            may only be cleared (unset), and cannot be changed to a new value.
-            Once a deactivated flag is cleared, it will completely disappear from a 
-            bug/attachment, and cannot be set again.
-          </para>
-        </section>
-
-        <section id="flags-create-field-requestable">
-          <title>Requestable</title>
-          <para>
-            New flags are, by default, <quote>requestable</quote>, meaning that they
-            offer users the <quote>?</quote> option, as well as <quote>+</quote>
-            and <quote>-</quote>.
-            To remove the ? option, uncheck <quote>requestable</quote>.
-          </para>
-        </section>
-
-        <section id="flags-create-field-specific">
-          <title>Specifically Requestable</title>
-          <para>
-            By default this box is checked for new flags, meaning that users may make
-            flag requests of specific individuals. Unchecking this box will remove the
-            text box next to a flag; if it is still requestable, then requests may
-            only be made <quote>to the wind.</quote> Removing this after specific
-            requests have been made will not remove those requests; that data will
-            stay in the database (though it will no longer appear to the user).
-          </para>
-        </section>
-
-        <section id="flags-create-field-multiplicable">
-          <title>Multiplicable</title>
-          <para>
-            Any flag with <quote>Multiplicable</quote> set (default for new flags is 'on')
-            may be set more than once. After being set once, an unset flag
-            of the same type will appear below it with <quote>addl.</quote> (short for 
-            <quote>additional</quote>) before the name. There is no limit to the number of
-            times a Multiplicable flags may be set on the same bug/attachment.
-          </para>
-        </section>
-
-	<section id="flags-create-field-cclist">
-          <title>CC List</title>
-
-          <para>
-            If you want certain users to be notified every time this flag is 
-            set to ?, -, +, or unset, add them here. This is a comma-separated 
-            list of email addresses that need not be restricted to Bugzilla usernames.
-          </para>
-        </section>
-
-        <section id="flags-create-grant-group">
-          <title>Grant Group</title>
-          <para>
-            When this field is set to some given group, only users in the group
-            can set the flag to <quote>+</quote> and <quote>-</quote>. This
-            field does not affect who can request or cancel the flag. For that,
-            see the <quote>Request Group</quote> field below. If this field
-            is left blank, all users can set or delete this flag. This field is
-            useful for restricting which users can approve or reject requests.
-          </para>
-        </section>
-
-        <section id="flags-create-request-group">
-          <title>Request Group</title>
-          <para>
-            When this field is set to some given group, only users in the group
-            can request or cancel this flag. Note that this field has no effect
-            if the <quote>grant group</quote> field is empty. You can set the
-            value of this field to a different group, but both fields have to be
-            set to a group for this field to have an effect.
-          </para>
-        </section>
-      </section> <!-- flags-create -->
-
-      <section id="flags-delete">
-        <title>Deleting a Flag</title>
-
-        <para>
-          When you are at the <quote>Administer Flag Types</quote> screen,
-          you will be presented with a list of Bug flags and a list of Attachment
-          Flags.
-        </para>
-        <para>
-          To delete a flag, click on the <quote>Delete</quote> link next to
-          the flag description.
-        </para>
-        <warning>
-          <para>
-            Once you delete a flag, it is <emphasis>gone</emphasis> from
-            your Bugzilla. All the data for that flag will be deleted.
-            Everywhere that flag was set, it will disappear,
-            and you cannot get that data back. If you want to keep flag data,
-            but don't want anybody to set any new flags or change current flags,
-            unset <quote>active</quote> in the flag Edit form.
-          </para>
-        </warning>
-      </section>
-
-      <section 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
-          form described in the <quote>Creating a Flag</quote> section.
-        </para>
-      </section>
-
-    </section> <!-- flags-admin -->
-
-    <!-- XXX We should add a "Uses of Flags" section, here, with examples. -->
-
-  </section> <!-- flags -->
-
-  <section id="custom-fields">
-    <title>Custom Fields</title>
-
-    <para>
-      One of the most requested features was the ability to add your own custom
-      fields to bugs, based on your needs. With the release of Bugzilla 3.0, this
-      dream finally comes true. Administrators can manage these fields using the
-      <quote>Custom Fields</quote> link in the footer of pages. The first thing
-      they will see is the list of existing custom fields (which is empty by default).
-    </para>
-
-    <section id="add-custom-fields">
-      <title>Adding Custom Fields</title>
-
-      <para>
-        The <quote>Add a new custom field</quote> link permits you to add a
-        new field which can be either a free text box or a drop down menu.
-        More field types will be available in future releases.
-      </para>
-
-      <para>
-        The following attributes must be set for each new custom field:
-        <itemizedlist>
-          <listitem>
-            <para>
-              <emphasis>Name:</emphasis>
-              the name of the field, used internally. This name MUST begin
-              with <quote>cf_</quote>. If you omit this string, it will
-              be automatically added to the name you entered. This way, all
-              custom fields added to Bugzilla begin with <quote>cf_</quote>,
-              avoiding any conflict with default fields.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-              <emphasis>Description:</emphasis>
-              the string which is used as a label for this custom field.
-              That is the string that users will see, and so should be
-              short and explicit.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-              <emphasis>Type:</emphasis>
-              as mentioned above, only two types are implemented so far.
-              Free text boxes let you type any string, while drop down menus
-              only let you choose one value in the list provided. The list of
-              legal values for this field can be created and edited as soon as
-              this custom field is added to the DB. See
-              <xref linkend="edit-values-list" /> for information about editing
-              legal values.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-              <emphasis>Sortkey:</emphasis>
-              this integer determines in which order custom fields are
-              displayed in the UI, especially when viewing a bug. Fields
-              with lower values are displayed first.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-              <emphasis>Can be set on bug creation:</emphasis>
-              this boolean determines whether this field can be set on
-              bug creation or not. If not, then you have to create the
-              bug first before being able to set this field. Else you
-              can set its value at the same time you file a bug,
-              see <xref linkend="bugreports" /> about filing bugs.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-              <emphasis>Displayed in bugmail for new bugs:</emphasis>
-              this boolean determines whether the value set on this field
-              should appear in bugmail when the bug is filed. This attribute
-              has no effect if the field cannot be set on bug creation.
-            </para>
-          </listitem>
-
-          <listitem>
-            <para>
-              <emphasis>Is obsolete:</emphasis>
-              this boolean determines whether or not this field should
-              be displayed at all. Obsolete custom fields are hidden.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </para>
-    </section>
-
-    <section id="edit-custom-fields">
-      <title>Editing Custom Fields</title>
-
-      <para>
-        As soon as a custom field is created, its name and type cannot be
-        changed. If this field is a drop down menu, its legal values can
-        be set as described in <xref linkend="edit-values-list" />. All
-        other attributes can be edited as described above.
-      </para>
-    </section>
-
-    <section id="delete-custom-fields">
-      <title>Deleting Custom Fields</title>
-
-      <para>
-        At this point, it is not possible to delete custom fields from
-        your web browser. If you don't want to make one available anymore,
-        mark it as obsolete. This way, you will preserve your DB
-        referential integrity.
-      </para>
-    </section>
-  </section>
-
-  <section id="edit-values">
-    <title>Legal Values</title>
-
-    <para>
-      Since Bugzilla 2.20 RC1, legal values for Operating Systems, platforms,
-      bug priorities and severities can be edited from the User Interface
-      directly. This means that it is no longer required to manually edit
-      <filename>localconfig</filename>. Starting with Bugzilla 2.23.3, you
-      can also customize the list of valid resolutions from the same interface.
-    </para>
-
-    <section id="edit-values-list">
-      <title>Viewing/Editing legal values</title>
-      <para>
-        Editing legal values requires <quote>admin</quote> privileges.
-        A link named <quote>Field Values</quote> is visible in your footer and
-        clicking on it displays the list of fields whose values can be edited.
-      </para>
-      <para>
-	You can add as many legal values as you want, and each value must be
-	unique (on a per field basis). The sortkey is important to display these
-	values in the desired order.
-      </para>
-    </section>
-
-    <section id="edit-values-delete">
-      <title>Deleting legal values</title>
-      <para>
-        You can also delete legal values, but only if the two following conditions
-	are respected:
-      </para>
-
-      <orderedlist>
-        <listitem>
-	  <para>The value is not used by default for the field.</para>
-	</listitem>
-
-	<listitem>
-	  <para>No bug is currently using this value.</para>
-        </listitem>
-      </orderedlist>
-
-      <para>
-        If any of these conditions is not respected, the value cannot be deleted.
-	The only way to delete these values is to reassign bugs to another value
-	and to set another value as default for the field.
-      </para>
-    </section>
-  </section>
-
-  <section id="voting">
-    <title>Voting</title>
-
-    <para>Voting allows users to be given a pot of votes which they can allocate
-    to bugs, to indicate that they'd like them fixed. 
-    This allows developers to gauge
-    user need for a particular enhancement or bugfix. By allowing bugs with
-    a certain number of votes to automatically move from "UNCONFIRMED" to
-    "NEW", users of the bug system can help high-priority bugs garner
-    attention so they don't sit for a long time awaiting triage.</para>
-
-    <para>To modify Voting settings:</para>
-
-    <orderedlist>
-      <listitem>
-        <para>Navigate to the "Edit product" screen for the Product you
-        wish to modify</para>
-      </listitem>
-
-      <listitem>
-        <para><emphasis>Maximum Votes per person</emphasis>:
-        Setting this field to "0" disables voting.</para>
-      </listitem>
-
-      <listitem>
-        <para><emphasis>Maximum Votes a person can put on a single
-         bug</emphasis>: 
-         It should probably be some number lower than the
-        "Maximum votes per person". Don't set this field to "0" if
-        "Maximum votes per person" is non-zero; that doesn't make
-        any sense.</para>
-      </listitem>
-
-      <listitem>
-        <para><emphasis>Number of votes a bug in this product needs to
-        automatically get out of the UNCONFIRMED state</emphasis>: 
-        Setting this field to "0" disables the automatic move of
-        bugs from UNCONFIRMED to NEW. 
-        </para>
-      </listitem>
-
-      <listitem>
-        <para>Once you have adjusted the values to your preference, click
-        "Update".</para>
-      </listitem>
-    </orderedlist>
-  </section>
-
-  <section id="quips">
-    <title>Quips</title>
-
-    <para>
-      Quips are small text messages that can be configured to appear
-      next to search results. A Bugzilla installation can have its own specific
-      quips. Whenever a quip needs to be displayed, a random selection
-      is made from the pool of already existing quips.
-    </para>
-  
-    <para>
-      Quips are controlled by the <emphasis>enablequips</emphasis> parameter.
-      It has several possible values: on, approved, frozen or off.
-      In order to enable quips approval you need to set this parameter
-      to "approved". In this way, users are free to submit quips for
-      addition but an administrator must explicitly approve them before
-      they are actually used.
-    </para>
-
-    <para>
-      In order to see the user interface for the quips, it is enough to click
-      on a quip when it is displayed together with the search results. Or
-      it can be seen directly in the browser by visiting the quips.cgi URL
-      (prefixed with the usual web location of the Bugzilla installation).
-      Once the quip interface is displayed, it is enough to click the
-      "view and edit the whole quip list" in order to see the administration
-      page. A page with all the quips available in the database will
-      be displayed.
-    </para>
-
-    <para>
-      Next to each tip there is a checkbox, under the
-      "Approved" column. Quips who have this checkbox checked are
-      already approved and will appear next to the search results.
-      The ones that have it unchecked are still preserved in the
-      database but they will not appear on search results pages.
-      User submitted quips have initially the checkbox unchecked.
-    </para>
-  
-    <para>
-      Also, there is a delete link next to each quip,
-      which can be used in order to permanently delete a quip.
-    </para>
-  </section>
-
-  <section id="groups">
-    <title>Groups and Group Security</title>
-
-    <para>Groups allow the administrator
-    to isolate bugs or products that should only be seen by certain people.
-    The association between products and groups is controlled from
-    the product edit page under <quote>Edit Group Controls.</quote>
-    </para>
-
-    <para>
-    If the makeproductgroups param is on, a new group will be automatically
-    created for every new product. It is primarily available for backward
-    compatibility with older sites. 
-    </para>
-    <para>
-      Note that group permissions are such that you need to be a member
-      of <emphasis>all</emphasis> the groups a bug is in, for whatever
-      reason, to see that bug. Similarly, you must be a member 
-      of <emphasis>all</emphasis> of the entry groups for a product 
-      to add bugs to a product and you must be a member 
-      of <emphasis>all</emphasis> of the canedit groups for a product
-      in order to make <emphasis>any</emphasis> change to bugs in that
-      product.
-    </para>    
-    <note>
-      <para>
-        By default, bugs can also be seen by the Assignee, the Reporter, and 
-        by everyone on the CC List, regardless of whether or not the bug would 
-        typically be viewable by them. Visibility to the Reporter and CC List can 
-        be overridden (on a per-bug basis) by bringing up the bug, finding the 
-        section that starts with <quote>Users in the roles selected below...</quote>
-        and un-checking the box next to either 'Reporter' or 'CC List' (or both).
-      </para>
-    </note>
-    <section>
-      <title>Creating Groups</title>
-      <para>To create Groups:</para>
-  
-      <orderedlist>
-        <listitem>
-          <para>Select the <quote>groups</quote>
-          link in the footer.</para>
-        </listitem>
-  
-        <listitem>
-          <para>Take a moment to understand the instructions on the <quote>Edit
-          Groups</quote> screen, then select the <quote>Add Group</quote> link.</para>
-        </listitem>
-  
-        <listitem>
-          <para>Fill out the <quote>Group</quote>, <quote>Description</quote>, 
-           and <quote>User RegExp</quote> fields. 
-           <quote>User RegExp</quote> allows you to automatically
-           place all users who fulfill the Regular Expression into the new group. 
-           When you have finished, click <quote>Add</quote>.</para>
-           <para>Users whose email addresses match the regular expression
-           will automatically be members of the group as long as their 
-           email addresses continue to match the regular expression.</para>
-           <note>
-             <para>This is a change from 2.16 where the regular expression
-             resulted in a user acquiring permanent membership in a group.
-             To remove a user from a group the user was in due to a regular
-             expression in version 2.16 or earlier, the user must be explicitly
-             removed from the group. This can easily be done by pressing
-             buttons named 'Remove Memberships' or 'Remove Memberships
-             included in regular expression' under the table.</para>
-           </note>
-           <warning>
-             <para>If specifying a domain in the regexp, make sure you end
-             the regexp with a $. Otherwise, when granting access to 
-             "@mycompany\.com", you will allow access to 
-             'badperson@mycompany.com.cracker.net'. You need to use 
-             '@mycompany\.com$' as the regexp.</para>
-           </warning>
-        </listitem>
-        <listitem>
-          <para>If you plan to use this group to directly control
-          access to bugs, check the "use for bugs" box. Groups
-          not used for bugs are still useful because other groups
-          can include the group as a whole.</para>
-        </listitem>
-        <listitem>
-          <para>After you add your new group, edit the new group.  On the
-          edit page, you can specify other groups that should be included
-          in this group and which groups should be permitted to add and delete
-          users from this group.</para>
-        </listitem>
-      </orderedlist>
-  
-    </section>
-    <section>
-      <title>Assigning Users to Groups</title>
-      <para>Users can become a member of a group in several ways.</para>
-      <orderedlist>
-        <listitem>
-          <para>The user can be explicitly placed in the group by editing
-          the user's own profile</para>
-        </listitem>
-        <listitem>
-          <para>The group can include another group of which the user is
-          a member.</para>
-        </listitem>
-        <listitem>
-          <para>The user's email address can match a regular expression
-          that the group specifies to automatically grant membership to
-          the group.</para>
-        </listitem>
-      </orderedlist>
-    </section>
-    <section>
-     <title>Assigning Group Controls to Products</title>
-     <para>
-     For information on assigning group controls to
-     products, see <xref linkend="products" />.
-     </para>
-    </section>
-    
-    <section>
-    <title>Common Applications of Group Controls</title>
-      <section>
-      <title>General User Access With Security Group</title>
-      <para>To permit any user to file bugs in each product (A, B, C...) 
-      and to permit any user to submit those bugs into a security
-      group....</para>
-      <programlisting> 
-Product A...
-security: SHOWN/SHOWN
-Product B...
-security: SHOWN/SHOWN
-Product C...
-security: SHOWN/SHOWN
-      </programlisting>
-      </section>
-      <section>
-      <title>General User Access With A Security Product</title>
-      <para>To permit any user to file bugs in a Security product
-      while keeping those bugs from becoming visible to anyone
-      outside the securityworkers group unless a member of the
-      securityworkers group removes that restriction....</para>
-      <programlisting> 
-Product Security...
-securityworkers: DEFAULT/MANDATORY
-      </programlisting>
-      </section>
-      <section>
-      <title>Product Isolation With Common Group</title>
-      <para>To permit users of product A to access the bugs for
-      product A, users of product B to access product B, and support
-      staff to access both, 3 groups are needed</para>
-      <orderedlist>
-        <listitem>
-          <para>Support: Contains members of the support staff.</para>
-        </listitem>
-        <listitem>
-          <para>AccessA: Contains users of product A and the Support group.</para>
-        </listitem>
-        <listitem>
-          <para>AccessB: Contains users of product B and the Support group.</para>
-        </listitem>
-      </orderedlist>
-      <para>Once these 3 groups are defined, the products group controls
-      can be set to..</para>
-      <programlisting>
-Product A...
-AccessA: ENTRY, MANDATORY/MANDATORY
-Product B...
-AccessB: ENTRY, MANDATORY/MANDATORY
-      </programlisting>
-      <para>Optionally, the support group could be permitted to make
-      bugs inaccessible to the users and could be permitted to publish
-      bugs relevant to all users in a common product that is read-only
-      to anyone outside the support group. That configuration could
-      be...</para>
-      <programlisting>
-Product A...
-AccessA: ENTRY, MANDATORY/MANDATORY
-Support: SHOWN/NA
-Product B...
-AccessB: ENTRY, MANDATORY/MANDATORY
-Support: SHOWN/NA
-Product Common...
-Support: ENTRY, DEFAULT/MANDATORY, CANEDIT
-      </programlisting>
-      </section>
-    </section>
-  </section>
-
-  <section id="upgrading">
-    <title>Upgrading to New Releases</title>
-
-    <para>
-      Upgrading Bugzilla is something we all want to do from time to time,
-      be it to get new features or pick up the latest security fix. How easy
-      it is to update depends on a few factors:
-    </para>
-
-    <itemizedlist>
-      <listitem>
-        <para>
-          If the new version is a revision or a new point release
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          How many local changes (if any) have been made
-        </para>
-      </listitem>
-    </itemizedlist>
-
-    <section id="upgrading-version-defns">
-      <title>Version Definitions</title>
-
-      <para>
-        Bugzilla displays the version you are using at the top of the home
-        page <filename>index.cgi</filename>. It looks something like
-        '2.20.3', '2.22.1' or '3.0rc1'. The first number in this series is
-        the Major Version. This does not change very often;
-        Bugzilla was 1.x.x when it was first created, and went to 2.x.x
-        when it was re-written in perl in Sept 1998. The major version
-        3.x.x, released in early 2007, is pretty far from what the 2.x.x
-        series looked like, both about its UI and its code.
-      </para>
-
-      <para>
-        The second number in the version is called the 'minor number', and
-        a release that changes the minor number is called a 'point release'.
-        An even number in this position (2.18, 2.20, 2.22, 3.0, 3.2, etc.)
-        represents a stable version, while an odd number (2.19, 2.21, 2.23, etc.)
-        represents a development version. In the past, stable point releases
-        were feature-based, coming when certain enhancements had been
-        completed, or the Bugzilla development team felt that enough
-        progress had been made overall. As of version 2.18, however,
-        Bugzilla has moved to a time-based release schedule; current plans
-        are to create a stable point release every 6 months or so after
-        2.18 is deployed.
-      </para>
-
-      <para>
-        The third number in the Bugzilla version represents a bugfix version.
-        Bugfix Revisions are released only to address security vulnerabilities
-        and, for a limited period, bug fixes. Once enough of these
-        bugfixes have accumulated (or a new security vulnerability is
-        identified and closed), a bugfix release is made. As an
-        example, 2.20.3 was a bugfix release, and improved on 2.20.2.
-      </para>
-
-      <note>
-        <para>
-          When reading version numbers, everything separated by a point ('.')
-          should be read as a single number. It is <emphasis>not</emphasis>
-          the same as decimal. 2.22 is newer than 2.8 because minor version
-          22 is greater than minor version 8. The now unsupported release 2.16.11
-          was newer than 2.16.9 (because bugfix 11 is greater than bugfix 9. This is
-          confusing to some people who aren't used to dealing with software.
-        </para>
-      </note>
-    </section>
-
-    <section id="upgrading-notifications">
-      <title>Upgrading - Notifications</title>
-
-      <para>
-        Bugzilla 3.0 introduces the ability to automatically notify
-        administrators when new releases are available, based on the
-        <literal>upgrade_notification</literal> parameter, see
-        <xref linkend="parameters"/>. Administrators will see these
-        notifications when they access the <filename>index.cgi</filename>
-        page, i.e. generally when logging in. Bugzilla will check once per
-        day for new releases, unless the parameter is set to
-        <quote>disabled</quote>. If you are behind a proxy, you may have to set
-        the <literal>proxy_url</literal> parameter accordingly. If the proxy
-        requires authentication, use the
-        <literal>http://user:pass@proxy_url/</literal> syntax.
-      </para>
-    </section>
-
-    <section id="upgrading-methods">
-    <title>Upgrading - Methods and Procedure</title>
-      <para>
-        There are three different ways to upgrade your installation.
-      </para>
-
-      <orderedlist>
-        <listitem>
-          <para>
-            Using CVS (<xref linkend="upgrade-cvs"/>)
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            Downloading a new tarball (<xref linkend="upgrade-tarball"/>)
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            Applying the relevant patches (<xref linkend="upgrade-patches"/>)
-          </para>
-        </listitem>
-      </orderedlist>
-
-      <para>
-        Each of these options has its own pros and cons; the one that's
-        right for you depends on how long it has been since you last
-        installed, the degree to which you have customized your installation,
-        and/or your network configuration. (Some discussion of the various
-        methods of updating compared with degree and methods of local
-        customization can be found in <xref linkend="template-method"/>.)
-      </para>
-
-      <para>
-        The larger the jump you are trying to make, the more difficult it
-        is going to be to upgrade if you have made local customizations.
-        Upgrading from 2.22 to 2.22.1 should be fairly painless even if
-        you are heavily customized, but going from 2.18 to 3.0 is going
-        to mean a fair bit of work re-writing your local changes to use
-        the new files, logic, templates, etc. If you have done no local
-        changes at all, however, then upgrading should be approximately
-        the same amount of work regardless of how long it has been since
-        your version was released.
-      </para>
-
-      <warning>
-        <para>
-          Upgrading is a one-way process. You should backup your database
-          and current Bugzilla directory before attempting the upgrade. If
-          you wish to revert to the old Bugzilla version for any reason, you
-          will have to restore from these backups.
-        </para>
-      </warning>
-
-      <para>
-        The examples in the following sections are written as though the
-        user were updating to version 2.22.1, but the procedures are the
-        same regardless of whether one is updating to a new point release
-        or simply trying to obtain a new bugfix release. Also, in the
-        examples the user's Bugzilla installation is found at
-        <filename>/var/www/html/bugzilla</filename>. If that is not the
-        same as the location of your Bugzilla installation, simply
-        substitute the proper paths where appropriate.
-      </para>
-
-      <section id="upgrade-cvs">
-      <title>Upgrading using CVS</title>
-
-        <para>
-          Every release of Bugzilla, whether it is a point release or a bugfix,
-          is tagged in CVS.  Also, every tarball that has been distributed since
-          version 2.12 has been created in such a way that it can be used with
-          CVS once it is unpacked. Doing so, however, requires that you are able
-          to access cvs-mirror.mozilla.org on port 2401, which may not be an
-          option or a possibility for some users, especially those behind a
-          highly restrictive firewall.
-        </para>
-
-        <tip>
-          <para>
-            If you can, updating using CVS is probably the most painless
-            method, especially if you have a lot of local changes.
-          </para>
-        </tip>
-
-        <para>
-          The following shows the sequence of commands needed to update a
-          Bugzilla installation via CVS, and a typical series of results.
-        </para>
-
-        <programlisting>
-bash$ <command>cd /var/www/html/bugzilla</command>
-bash$ <command>cvs login</command>
-Logging in to :pserver:anonymous@cvs-mirror.mozilla.org:2401/cvsroot
-CVS password: <emphasis>('anonymous', or just leave it blank)</emphasis>
-bash$ <command>cvs -q update -r BUGZILLA-2_22_1 -dP</command>
-P checksetup.pl
-P collectstats.pl
-P docs/rel_notes.txt
-P template/en/default/list/quips.html.tmpl
-<emphasis>(etc.)</emphasis>
-        </programlisting>
-
-        <caution>
-          <para>
-            If a line in the output from <command>cvs update</command> begins
-            with a <computeroutput>C</computeroutput>, then that represents a
-            file with local changes that CVS was unable to properly merge. You
-            need to resolve these conflicts manually before Bugzilla (or at
-            least the portion using that file) will be usable.
-          </para>
-        </caution>
-      </section>
-
-      <section id="upgrade-tarball">
-        <title>Upgrading using the tarball</title>
-
-        <para>
-          If you are unable (or unwilling) to use CVS, another option that's
-          always available is to obtain the latest tarball from the <ulink
-          url="http://www.bugzilla.org/download/">Download Page</ulink> and 
-          create a new Bugzilla installation from that.
-        </para>
-
-        <para>
-          This sequence of commands shows how to get the tarball from the
-          command-line; it is also possible to download it from the site
-          directly in a web browser. If you go that route, save the file
-          to the <filename class="directory">/var/www/html</filename>
-          directory (or its equivalent, if you use something else) and 
-          omit the first three lines of the example.
-        </para>
-
-        <programlisting>
-bash$ <command>cd /var/www/html</command>
-bash$ <command>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22.1.tar.gz</command>
-<emphasis>(Output omitted)</emphasis>
-bash$ <command>tar xzvf bugzilla-2.22.1.tar.gz</command>
-bugzilla-2.22.1/
-bugzilla-2.22.1/.cvsignore
-<emphasis>(Output truncated)</emphasis>
-bash$ <command>cd bugzilla-2.22.1</command>
-bash$ <command>cp ../bugzilla/localconfig* .</command>
-bash$ <command>cp -r ../bugzilla/data .</command>
-bash$ <command>cd ..</command>
-bash$ <command>mv bugzilla bugzilla.old</command>
-bash$ <command>mv bugzilla-2.22.1 bugzilla</command>
-        </programlisting>
-
-        <warning>
-          <para>
-            The <command>cp</command> commands both end with periods which
-            is a very important detail, it tells the shell that the destination
-            directory is the current working directory. 
-          </para>
-        </warning>
-
-        <para>
-          This upgrade method will give you a clean install of Bugzilla with the 
-          same version as the tarball. That's fine if you don't have any local
-          customizations that you want to maintain, but if you do then you will 
-          need to reapply them by hand to the appropriate files. 
-        </para>
-
-        <para>
-          It's worth noting that since 2.12, the Bugzilla tarballs come
-          CVS-ready, so if you decide at a later date that you'd rather use
-          CVS as an upgrade method, your code will already be set up for it.
-        </para>
-      </section>
-
-      <section id="upgrade-patches">
-        <title>Upgrading using patches</title>
-
-        <para>
-          If you are doing a bugfix upgrade -- that is, one where only the 
-          last number of the revision changes, such as from 2.22 to 2.22.1
-          -- then you have the option of obtaining and applying a patch file
-          from the <ulink
-          url="http://www.bugzilla.org/download/">Download Page</ulink>.
-          This file is made available by the <ulink
-          url="http://www.bugzilla.org/developers/profiles.html">Bugzilla
-          Development Team</ulink>, and is a collection of all the bug fixes
-          and security patches that have been made since the last bugfix
-          release. If you are planning to upgrade via patches, it is safer
-          to grab this developer-made patch file than to read the patch
-          notes and apply all (or even just some of) the patches oneself,
-          as sometimes patches on bugs get changed before they get checked in.
-        </para>
-
-        <para>
-          As above, this example starts with obtaining the file via the 
-          command line. If you have already downloaded it, you can omit the
-          first two commands.
-        </para>
-
-        <programlisting>
-bash$ <command>cd /var/www/html/bugzilla</command>
-bash$ <command>wget http://ftp.mozilla.org/pub/mozilla.org/webtools/bugzilla-2.22-to-2.22.1.diff.gz</command>
-<emphasis>(Output omitted)</emphasis>
-bash$ <command>gunzip bugzilla-2.22-to-2.22.1.diff.gz</command>
-bash$ <command>patch -p1 &lt; bugzilla-2.22-to-2.22.1.diff</command>
-patching file checksetup.pl
-patching file collectstats.pl
-<emphasis>(etc.)</emphasis>
-        </programlisting>
-
-        <warning>
-          <para>
-            Be aware that upgrading from a patch file does not change the
-            entries in your <filename class="directory">CVS</filename> directory.
-            This could make it more difficult to upgrade using CVS
-            (<xref linkend="upgrade-cvs"/>) in the future.
-          </para>
-        </warning>
-
-      </section>
-    </section>
-
-    <section id="upgrading-completion">
-    <title>Completing Your Upgrade</title>
-
-      <para>
-        Regardless of which upgrade method you choose, you will need to
-        run <command>./checksetup.pl</command> before your Bugzilla
-        upgrade will be complete.
-      </para>
-
-      <programlisting>
-bash$ <command>cd bugzilla</command>
-bash$ <command>./checksetup.pl</command>
-      </programlisting>
-
-      <warning>
-        <para>
-          The period at the beginning of the command
-          <command>./checksetup.pl</command> is important and can not
-          be omitted.
-        </para>
-      </warning>
-          
-      <para>
-        If you have done a lot of local modifications, it wouldn't hurt
-        to run the Bugzilla Testing suite. This is not a required step,
-        but it isn't going to hurt anything, and might help point out
-        some areas that could be improved. (More information on the
-        test suite can be had by following this link to the appropriate
-        section in the <ulink
-        url="http://www.bugzilla.org/docs/developer.html#testsuite">Developers'
-        Guide</ulink>.)
-      </para>
-
-    </section>
-  </section>
-</chapter>
-
-<!-- Keep this comment at the end of the file
-Local variables:
-mode: sgml
-sgml-always-quote-attributes:t
-sgml-auto-insert-required-elements:t
-sgml-balanced-tag-edit:t
-sgml-exposed-tags:nil
-sgml-general-insert-case:lower
-sgml-indent-data:t
-sgml-indent-step:2
-sgml-local-catalogs:nil
-sgml-local-ecat-files:nil
-sgml-minimize-attributes:nil
-sgml-namecase-general:t
-sgml-omittag:t
-sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
-sgml-shorttag:t
-sgml-tag-region-if-active:t
-End:
--->
-
diff --git a/BugsSite/docs/xml/faq.xml b/BugsSite/docs/xml/faq.xml
deleted file mode 100644
index 22f0144..0000000
--- a/BugsSite/docs/xml/faq.xml
+++ /dev/null
@@ -1,1659 +0,0 @@
-<!-- <!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
-
-<appendix id="faq">
-  <title>The Bugzilla FAQ</title>
-
-  <para>
-    This FAQ includes questions not covered elsewhere in the Guide.
-  </para>
-  
-  <qandaset>
-
-
-    <qandadiv id="faq-general">
-      <title>General Questions</title>
-
-      <qandaentry>
-        <question id="faq-general-tryout">
-          <para>
-            Can I try out Bugzilla somewhere?
-          </para>
-        </question>
-        <answer>
-          <para>
-            If you want to take a test ride, there are test installations
-            at <ulink url="http://landfill.bugzilla.org/"/>,
-            ready to play with directly from your browser.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-general-license">
-          <para>
-            What license is Bugzilla distributed under?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Bugzilla is covered by the Mozilla Public License.
-            See details at <ulink url="http://www.mozilla.org/MPL/"/>.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-general-support">
-          <para>
-            How do I get commercial support for Bugzilla?
-          </para>
-        </question>
-        <answer>
-          <para>
-            <ulink url="http://www.bugzilla.org/support/consulting.html"/>
-            is a list of companies and individuals who have asked us to
-            list them as consultants for Bugzilla.
-          </para>
-          <para>
-            There are several experienced
-            Bugzilla hackers on the mailing list/newsgroup who are willing
-            to make themselves available for generous compensation.
-            Try sending a message to the mailing list asking for a volunteer.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-general-companies">
-          <para>
-            What major companies or projects are currently using Bugzilla
-            for bug-tracking?
-          </para>
-        </question>
-        <answer>
-          <para>
-            There are <emphasis>dozens</emphasis> of major companies with public
-            Bugzilla sites to track bugs in their products. We have a fairly
-            complete list available on our website at
-            <ulink url="http://bugzilla.org/installation-list/"/>. If you
-            have an installation of Bugzilla and would like to be added to the
-            list, whether it's a public install or not, simply e-mail
-            Gerv <email>gerv@mozilla.org</email>.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-general-maintainers">
-          <para>
-            Who maintains Bugzilla?
-          </para>
-        </question>
-        <answer>
-          <para>
-            A <ulink url="http://www.bugzilla.org/developers/profiles.html">core
-            team</ulink>, led by Dave Miller (justdave@bugzilla.org).
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-general-compare">
-          <para>
-            How does Bugzilla stack up against other bug-tracking databases?
-          </para>
-        </question>
-        <answer>
-          <para>
-            We can't find any head-to-head comparisons of Bugzilla against
-            other defect-tracking software. If you know of one, please get
-            in touch. In the experience of Matthew Barnson (the original
-            author of this FAQ), though, Bugzilla offers superior
-            performance on commodity hardware, better price (free!), more
-            developer-friendly features (such as stored queries, email
-            integration, and platform independence), improved scalability,
-            greater flexibility, and superior ease-of-use when compared
-            to commercial bug-tracking software.
-          </para>
-          <para>
-            If you happen to be a vendor for commercial bug-tracking
-            software, and would like to submit a list of advantages your
-            product has over Bugzilla, simply send it to 
-            <email>documentation@bugzilla.org</email> and we'd be happy to
-            include the comparison in our documentation.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-general-bzmissing">
-          <para>
-            Why doesn't Bugzilla offer this or that feature or compatibility
-            with this other tracking software?
-          </para>
-        </question>
-        <answer>
-          <para>
-            It may be that the support has not been built yet, or that you
-            have not yet found it. While Bugzilla makes strides in usability,
-            customizability, scalability, and user interface with each release,
-            that doesn't mean it can't still use improvement!
-          </para>
-          <para>
-            The best way to make an enhancement request is to <ulink 
-            url="https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla">file
-            a bug at bugzilla.mozilla.org</ulink> and set the Severity
-            to 'enhancement'. Your 'request for enhancement' (RFE) will
-            start out in the UNCONFIRMED state, and will stay there until
-            someone with the ability to CONFIRM the bug reviews it.
-            If that person feels it to be a good request that fits in with
-            Bugzilla's overall direction, the status will be changed to
-            NEW; if not, they will probably explain why and set the bug
-            to RESOLVED/WONTFIX. If someone else has made the same (or
-            almost the same) request before, your request will be marked
-            RESOLVED/DUPLICATE, and a pointer to the previous RFE will be
-            added.
-          </para>
-          <para>
-            Even if your RFE gets approved, that doesn't mean it's going
-            to make it right into the next release; there are a limited
-            number of developers, and a whole lot of RFEs... some of
-            which are <emphasis>quite</emphasis> complex. If you're a
-            code-hacking sort of person, you can help the project along
-            by making a patch yourself that supports the functionality
-            you require. If you have never contributed anything to
-            Bugzilla before, please be sure to read the 
-            <ulink url="http://www.bugzilla.org/docs/developer.html">Developers' Guide</ulink>
-            and
-            <ulink url="http://www.bugzilla.org/docs/contributor.html">Contributors' Guide</ulink>
-            before going ahead.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-general-db">
-          <para>
-            What databases does Bugzilla run on?
-          </para>
-        </question>
-        <answer>
-          <para>
-            MySQL is the default database for Bugzilla. It was originally chosen 
-            because it is free, easy to install, and was available for the hardware 
-            Netscape intended to run it on.
-          </para>
-          <para>
-            As of Bugzilla 2.22, complete support for PostgreSQL 
-            is included. With this release using PostgreSQL with Bugzilla 
-            should be as stable as using MySQL. If you experience any problems
-            with PostgreSQL compatibility, they will be taken as
-            seriously as if you were running MySQL.
-          </para>
-          <para>
-            There are plans to include an Oracle driver for Bugzilla 3.1.2. 
-            Track progress at
-            <ulink url="https://bugzilla.mozilla.org/show_bug.cgi?id=189947">
-            Bug 189947</ulink>.
-          </para>
-          <para>
-            Sybase support was worked on for a time. However, several 
-            complicating factors have prevented Sybase support from 
-            being realized. There are currently no plans to revive it.
-          </para>
-          <para>
-            <ulink url="https://bugzilla.mozilla.org/show_bug.cgi?id=237862">
-            Bug 237862</ulink> is a good bug to read through if you'd
-            like to see what progress is being made on general database
-            compatibility.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-general-perlpath">
-          <para>
-            My perl is located at <filename>/usr/local/bin/perl</filename>
-            and not <filename>/usr/bin/perl</filename>. Is there an easy
-            to change that in all the files that have this hard-coded?
-          </para>
-        </question>
-        <answer>
-          <para>
-            The easiest way to get around this is to create a link from
-            one to the other:
-            <command>ln -s /usr/local/bin/perl /usr/bin/perl</command>.
-            If that's not an option for you, the following bit of perl
-            magic will change all the shebang lines (that is to say,
-            the line at the top of each file that starts with '#!' 
-            and contains the path) to something else:
-          </para>
-          <programlisting>
-perl -pi -e 's@#\!/usr/bin/perl@#\!/usr/local/bin/perl@' *cgi *pl
-          </programlisting>
-          <para>
-            Sadly, this command-line won't work on Windows unless you
-            also have Cygwin. However, MySQL comes with a binary called
-            <command>replace</command> which can do the job:
-          </para>
-          <programlisting>
-C:\mysql\bin\replace "#!/usr/bin/perl" "#!C:\perl\bin\perl" -- *.cgi *.pl
-          </programlisting>
-          <note>
-            <para>
-              If your perl path is something else again, just follow the
-              above examples and replace
-              <filename>/usr/local/bin/perl</filename> with your own perl path.
-            </para>            
-          </note>
-          <para>
-            Once you've modified all your files, you'll also need to modify the
-            <filename>t/002goodperl.t</filename> test, as it tests that all
-            shebang lines are equal to <filename>/usr/bin/perl</filename>.
-            (For more information on the test suite, please check out the 
-            appropriate section in the <ulink
-            url="http://www.bugzilla.org/docs/developer.html#testsuite">Developers'
-            Guide</ulink>.) Having done this, run the test itself:
-            <programlisting>
-            perl runtests.pl 2 --verbose
-            </programlisting>
-            to ensure that you've modified all the relevant files.
-          </para>
-          <para>
-            If using Apache on Windows, you can avoid the whole problem
-            by setting the <ulink
-            url="http://httpd.apache.org/docs-2.0/mod/core.html#scriptinterpretersource">
-            ScriptInterpreterSource</ulink> directive to 'Registry'.
-            (If using Apache 2 or higher, set it to 'Registry-Strict'.)
-            ScriptInterperterSource requires a registry entry
-            <quote>HKEY_CLASSES_ROOT\.cgi\Shell\ExecCGI\Command</quote> to
-            associate .cgi files with your perl executable. If one does
-            not already exist, create it with a default value of
-           <quote>&lt;full path to perl&gt; -T</quote>, e.g.
-           <quote>C:\Perl\bin\perl.exe -T</quote>.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-general-cookie">
-          <para>
-            Is there an easy way to change the Bugzilla cookie name?
-          </para>
-        </question>
-        <answer>
-          <para>
-            At present, no.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-general-selinux">
-          <para>
-            How can Bugzilla be made to work under SELinux?
-          </para>
-        </question>
-        <answer>
-          <para>
-            As a web application, Bugzilla simply requires its root
-            directory to have the httpd context applied for it to work
-            properly under SELinux. This should happen automatically
-            on distributions that use SELinux and that package Bugzilla
-            (if it is installed with the native package management tools).
-            Information on how to view and change SELinux file contexts
-            can be found at the 
-            <ulink url="http://docs.fedoraproject.org/selinux-faq-fc5/">
-            SELinux FAQ</ulink>.
-
-          </para>
-        </answer>
-      </qandaentry>
-
-    </qandadiv>
-
-    <qandadiv id="faq-phb">
-      <title>Managerial Questions</title>
-
-      <qandaentry>
-        <question id="faq-phb-client">
-          <para>
-            Is Bugzilla web-based, or do you have to have specific software or
-            a specific operating system on your machine?
-          </para>
-        </question>
-        <answer>
-          <para>
-            It is web and e-mail based.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-priorities">
-          <para>
-            Does Bugzilla allow us to define our own priorities and levels?
-            Do we have complete freedom to change the labels of fields and
-            format of them, and the choice of acceptable values?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Yes. However, modifying some fields, notably those related to bug
-            progression states, also require adjusting the program logic to
-            compensate for the change.
-          </para>
-          <para>
-            As of Bugzilla 3.0 custom fields can be created via the
-            "Custom Fields" admin page.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-reporting">
-          <para>
-            Does Bugzilla provide any reporting features, metrics, graphs,
-            etc? You know, the type of stuff that management likes to see. :)
-          </para>
-        </question>
-        <answer>
-          <para>
-            Yes. Look at <ulink url="https://bugzilla.mozilla.org/report.cgi"/>
-            for samples of what Bugzilla can do in reporting and graphing.
-            Fuller documentation is provided in <xref linkend="reporting"/>.
-          </para>
-          <para>
-            If you can not get the reports you want from the included reporting
-            scripts, it is possible to hook up a professional reporting package
-            such as Crystal Reports using ODBC. If you choose to do this,
-            beware that giving direct access to the database does contain some
-            security implications. Even if you give read-only access to the
-            bugs database it will bypass the secure bugs features of Bugzilla.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-email">
-          <para>
-            Is there email notification? If so, what do you see
-            when you get an email?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Email notification is user-configurable. By default, the bug id
-            and summary of the bug report accompany each email notification,
-            along with a list of the changes made.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-emailapp">
-          <para>
-            Do users have to have any particular type of email application?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Bugzilla email is sent in plain text, the most compatible
-            mail format on the planet.
-            <note>
-              <para>
-                If you decide to use the bugzilla_email integration features
-                to allow Bugzilla to record responses to mail with the
-                associated bug, you may need to caution your users to set
-                their mailer to <quote>respond to messages in the format in
-                which they were sent</quote>. For security reasons Bugzilla
-                ignores HTML tags in comments, and if a user sends HTML-based
-                email into Bugzilla the resulting comment looks downright awful.
-              </para>
-            </note>
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-data">
-          <para>
-            Does Bugzilla allow data to be imported and exported? If I had
-            outsiders write up a bug report using a MS Word bug template,
-            could that template be imported into <quote>matching</quote>
-            fields? If I wanted to take the results of a query and export
-            that data to MS Excel, could I do that?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Bugzilla can output buglists as HTML (the default), CSV or RDF.
-            The link for CSV can be found at the bottom of the buglist in HTML
-            format. This CSV format can easily be imported into MS Excel or
-            other spreadsheet applications.
-          </para>
-          <para>
-            To use the RDF format of the buglist it is necessary to append a
-            <computeroutput>&amp;ctype=rdf</computeroutput> to the URL. RDF
-            is meant to be machine readable and thus it is assumed that the
-            URL would be generated programmatically so there is no user visible
-            link to this format.
-          </para>
-          <para>
-            Currently the only script included with Bugzilla that can import
-            data is <filename>importxml.pl</filename> which is intended to be
-            used for importing the data generated by the XML ctype of
-            <filename>show_bug.cgi</filename> in association with bug moving.
-            Any other use is left as an exercise for the user.
-          </para>
-          <para>
-            There are also scripts included in the <filename>contrib/</filename>
-            directory for using e-mail to import information into Bugzilla,
-            but these scripts are not currently supported and included for
-            educational purposes.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-l10n">
-          <para>
-            Has anyone converted Bugzilla to another language to be
-            used in other countries? Is it localizable?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Yes. For more information including available translated templates,
-            see <ulink
-            url="http://www.bugzilla.org/download.html#localizations"/>.
-            Some admin interfaces have been templatized (for easy localization)
-            but many of them are still available in English only. Also, there
-            may be issues with the charset not being declared. See <ulink
-            url="https://bugzilla.mozilla.org/show_bug.cgi?id=126266">bug 126226</ulink>
-            for more information.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-reports">
-          <para>
-            Can a user create and save reports?
-            Can they do this in Word format? Excel format?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Yes. No. Yes (using the CSV format).
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-backup">
-          <para>
-            Are there any backup features provided?
-          </para>
-        </question>
-        <answer>
-          <para>
-            You should use the backup options supplied by your database platform.  
-            Vendor documentation for backing up a MySQL database can be found at 
-            <ulink url="http://www.mysql.com/doc/B/a/Backup.html"/>. 
-            PostgreSQL backup documentation can be found at
-            <ulink url="http://www.postgresql.org/docs/8.0/static/backup.html"/>.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-maintenance">
-          <para>
-            What type of human resources are needed to be on staff to install
-            and maintain Bugzilla? Specifically, what type of skills does the
-            person need to have? I need to find out what types of individuals
-            would we need to hire and how much would that cost if we were to
-            go with Bugzilla vs. buying an <quote>out-of-the-box</quote>
-            solution.
-          </para>
-        </question>
-        <answer>
-          <para>
-            If Bugzilla is set up correctly from the start, continuing
-            maintenance needs are minimal and can be done easily using
-            the web interface.
-          </para>
-          <para>
-            Commercial Bug-tracking software typically costs somewhere
-            upwards of $20,000 or more for 5-10 floating licenses. Bugzilla
-            consultation is available from skilled members of the newsgroup.
-            Simple questions are answered there and then.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-installtime">
-          <para>
-            What time frame are we looking at if we decide to hire people
-            to install and maintain the Bugzilla? Is this something that
-            takes hours or days to install and a couple of hours per week
-            to maintain and customize, or is this a multi-week install process,
-            plus a full time job for 1 person, 2 people, etc?
-          </para>
-        </question>
-        <answer>
-          <para>
-            It all depends on your level of commitment. Someone with much
-            Bugzilla experience can get you up and running in less than a day,
-            and your Bugzilla install can run untended for years. If your
-            Bugzilla strategy is critical to your business workflow, hire
-            somebody to who has reasonable Perl skills, and a familiarity
-            with the operating system on which Bugzilla will be running,
-            and have them handle your process management, bug-tracking
-            maintenance, and local customization.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-cost">
-          <para>
-            Is there any licensing fee or other fees for using Bugzilla? Any
-            out-of-pocket cost other than the bodies needed as identified above?
-          </para>
-        </question>
-        <answer>
-          <para>
-            No. Bugzilla, Perl, the Template Toolkit, and all other support
-            software needed to make Bugzilla work can be downloaded for free.
-            MySQL and PostgreSQL -- the databases supported by Bugzilla -- 
-            are also open-source. MySQL asks that if you find their product 
-            valuable, you purchase a support contract from them that suits your needs.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-phb-renameBugs">
-          <para>
-            We don't like referring to problems as 'bugs'. Can we change that?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Yes! As of Bugzilla 2.18, it is a simple matter to change the
-            word 'bug' into whatever word/phrase is used by your organization.
-            See the documentation on Customization for more details,
-            specifically <xref linkend="template-specific"/>.
-          </para>
-        </answer>
-      </qandaentry>
-
-    </qandadiv>
-
-    <qandadiv id="faq-admin">
-      <title>Administrative Questions</title>
-
-      <qandaentry>
-        <question id="faq-admin-midair">
-          <para>
-            Does Bugzilla provide record locking when there is simultaneous
-            access to the same bug? Does the second person get a notice
-            that the bug is in use or how are they notified?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Bugzilla does not lock records. It provides mid-air collision
-            detection -- which means that it warns a user when a commit is
-            about to conflict with commits recently made by another user,
-            and offers the second user a choice of options to deal with
-            the conflict.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-admin-livebackup">
-          <para>
-            Can users be on the system while a backup is in progress?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Refer to your database platform documentation for details on how to do hot
-            backups.  
-            Vendor documentation for backing up a MySQL database can be found at 
-            <ulink url="http://www.mysql.com/doc/B/a/Backup.html"/>. 
-            PostgreSQL backup documentation can be found at
-            <ulink url="http://www.postgresql.org/docs/8.0/static/backup.html"/>.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-admin-cvsupdate">
-          <para>
-            How can I update the code and the database using CVS?
-          </para>
-        </question>
-        <answer>
-          <para>
-            <orderedlist>
-              <listitem>
-                <para>
-                  Make a backup of both your Bugzilla directory and the
-                  database. For the Bugzilla directory this is as easy as
-                  doing <command>cp -rp bugzilla bugzilla.bak</command>.
-                  For the database, there's a number of options - see the
-                  MySQL docs and pick the one that fits you best (the easiest
-                  is to just make a physical copy of the database on the disk,
-                  but you have to have the database server shut down to do
-                  that without risking dataloss).
-                </para>
-              </listitem>
-
-              <listitem>
-                <para>
-                  Make the Bugzilla directory your current directory.
-                </para>
-              </listitem>
-
-              <listitem>
-                <para>
-                  Use <command>cvs -q update -AdP</command> if you want to
-                  update to the tip or
-                  <command>cvs -q update -dP -rTAGNAME</command>
-                  if you want a specific version (in that case you'll have to
-                  replace TAGNAME with a CVS tag name such as BUGZILLA-2_16_5).
-                </para>
-
-                <para>
-                  If you've made no local changes, this should be very clean.
-                  If you have made local changes, then watch the cvs output
-                  for C results. If you get any lines that start with a C
-                  it means there  were conflicts between your local changes
-                  and what's in CVS. You'll need to fix those manually before
-                  continuing.
-                </para>
-              </listitem>
-
-              <listitem>
-                <para>
-                  After resolving any conflicts that the cvs update operation
-                  generated, running <command>./checksetup.pl</command> will
-                  take care of updating the database for you as well as any
-                  other changes required for the new version to operate.
-                </para>
-
-                <warning>
-                  <para>
-                    Once you run checksetup.pl, the only way to go back is
-                    to restore the database backups. You can't
-                    <quote>downgrade</quote> the system cleanly under most
-                    circumstances.
-                  </para>
-                </warning>
-              </listitem>
-            </orderedlist>
-          </para>
-          <para>
-            See also the instructions in <xref linkend="upgrade-cvs"/>.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-admin-enable-unconfirmed">
-          <para>
-            How do I make it so that bugs can have an UNCONFIRMED status?
-          </para>
-        </question>
-        <answer>
-          <para>
-            To use the UNCONFIRMED status, you must have the 'usevotes'
-            parameter set to <quote>On</quote>. You must then visit the
-            <filename>editproducts.cgi</filename> page and set the <quote>
-            Number of votes a bug in this product needs to automatically
-            get out of the UNCONFIRMED state</quote> to be a non-zero number.
-            (You will have to do this for each product that wants to use
-            the UNCONFIRMED state.) If you do not actually want users to be
-            able to vote for bugs entered against this product, leave the
-            <quote>Maximum votes per person</quote> value at '0'.
-          </para>            
-          <para>
-            There is work being done to decouple the UNCONFIRMED state from
-            the 'usevotes' parameter for future versions of Bugzilla.
-            Follow the discussion and progress at <ulink 
-            url="https://bugzilla.mozilla.org/show_bug.cgi?id=162060">bug
-            162060</ulink>.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-admin-moving">
-          <para>
-            How do I move a Bugzilla installation from one machine to another?
-          </para>
-        </question>
-
-        <answer>
-          <para>
-            Reference your database vendor's documentation for information on 
-            backing up and restoring your Bugzilla database on to a different server.
-            Vendor documentation for backing up a MySQL database can be found at 
-            <ulink url="http://dev.mysql.com/doc/mysql/en/mysqldump.html"/>.
-            PostgreSQL backup documentation can be found at
-            <ulink url="http://www.postgresql.org/docs/8.0/static/backup.html"/>.
-          </para>
-
-          <para>
-            On your new machine, follow the instructions found in <xref 
-            linkend="installing-bugzilla"/> as far as setting up the physical
-            environment of the new machine with perl, webserver, modules, etc. 
-            Having done that, you can either: copy your entire Bugzilla
-            directory from the old machine to a new one (if you want to keep
-            your existing code and modifications), or download a newer version
-            (if you are planning to upgrade at the same time). Even if you are
-            upgrading to clean code, you will still want to bring over the 
-            <filename>localconfig</filename> file, and the 
-            <filename class="directory">data</filename> directory from the
-            old machine, as they contain configuration information that you 
-            probably won't want to re-create.
-          </para>
-
-          <note>
-            <para>
-              If the hostname or port number of your database server changed
-              as part of the move, you'll need to update the appropriate
-              variables in localconfig before taking the next step.
-            </para>
-          </note>
-
-          <para>
-            Once you have your code in place, and your database has
-            been restored from the backup you made in step 1, run
-            <command>checksetup.pl</command>. This will upgrade your
-            database (if necessary), rebuild your templates, etc.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-admin-makeadmin">
-          <para>
-            How do I make a new Bugzilla administrator?
-            The previous administrator is gone...
-          </para>
-        </question>
-        <answer>
-          <para>
-            Run <command>checksetup.pl</command> with
-            <option>--make-admin</option> option.
-            Its usage is <option>--make-admin=user@example.org</option>.
-            The user account must be exist in the Bugzilla database.
-          </para>
-        </answer>
-      </qandaentry>
-
-    </qandadiv>
-
-    <qandadiv id="faq-security">
-      <title>Bugzilla Security</title>
-      <qandaentry>
-        <question id="faq-security-mysql">
-          <para>
-            How do I completely disable MySQL security if it's giving
-            me problems? (I've followed the instructions in the installation
-            section of this guide...)
-          </para>
-        </question>
-
-        <answer>
-          <para>
-            You can run MySQL like this: <command>mysqld --skip-grant-tables</command>.
-            However, doing so disables all MySQL security. This is a bad idea.
-            Please consult <xref linkend="security-mysql"/> of this guide
-            and the MySQL documentation for better solutions.
-            </para>
-        </answer>
-      </qandaentry>
-      
-      <qandaentry>
-        <question id="faq-security-knownproblems">
-          <para>
-            Are there any security problems with Bugzilla?
-          </para>
-        </question>
-        <answer>
-          <para>
-            The Bugzilla code has undergone a reasonably complete security
-            audit, and user-facing CGIs run under Perl's taint mode. However, 
-            it is recommended that you closely examine permissions on your
-            Bugzilla installation, and follow the recommended security
-            guidelines found in The Bugzilla Guide.
-          </para>
-        </answer>
-      </qandaentry>
-    </qandadiv>
-
-    <qandadiv id="faq-email">
-      <title>Bugzilla Email</title>
-
-      <qandaentry>
-        <question id="faq-email-nomail">
-          <para>
-            I have a user who doesn't want to receive any more email
-            from Bugzilla. How do I stop it entirely for this user?
-          </para>
-        </question>
-        <answer>
-          <para>
-            The user can stop Bugzilla from sending any mail by unchecking
-            all boxes on the 'Edit prefs' -> 'Email settings' page.
-            (As of 2.18,this is made easier by the addition of a 'Disable
-            All Mail' button.) Alternately, you can add their email address
-            to the <filename>data/nomail</filename> file (one email address
-            per line). This will override their personal preferences, and
-            they will never be sent mail again.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-email-testing">
-          <para>
-            I'm evaluating/testing Bugzilla, and don't want it to send email
-            to anyone but me. How do I do it?
-          </para>
-        </question>
-        <answer>
-          <para>
-            To disable email, set the
-            <option>mail_delivery_method</option> parameter to
-            <literal>none</literal> (2.20 and later), or
-            <programlisting>$enableSendMail</programlisting> parameter to '0'
-            in either <filename>BugMail.pm</filename> (2.18 and later) or 
-            <filename>processmail</filename> (up to 2.16.x).
-          </para>
-          <note>
-            <para>
-              Up to 2.16.x, changing
-              <programlisting>$enableSendMail</programlisting>
-              will only affect bugmail; email related to password changes,
-              email address changes, bug imports, flag changes, etc. will
-              still be sent out. As of the final release of 2.18, however,
-              the above step will disable <emphasis>all</emphasis> mail
-              sent from Bugzilla for any purpose.
-            </para>
-          </note>
-          <para>
-            To have bugmail (and only bugmail) redirected to you instead of
-            its intended recipients, leave
-            <programlisting>$enableSendMail</programlisting> alone;
-            instead, edit the <quote>newchangedmail</quote> parameter
-            as follows:
-          </para>
-          <itemizedlist>
-            <listitem>
-              <para>
-                Replace <quote>To:</quote> with <quote>X-Real-To:</quote>
-              </para>
-            </listitem>
-            <listitem>
-              <para>
-                Replace <quote>Cc:</quote> with <quote>X-Real-CC:</quote>
-              </para>
-            </listitem>
-            <listitem>
-              <para>
-                Add a <quote>To: %lt;your_email_address&gt;</quote>
-              </para>
-            </listitem>
-          </itemizedlist>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-email-whine">
-          <para>
-            I want whineatnews.pl to whine at something other than new and
-            reopened bugs. How do I do it?
-          </para>
-        </question>
-        <answer>
-          <para>
-            For older versions of Bugzilla, you may be able to apply 
-            Klaas Freitag's patch for <quote>whineatassigned</quote>,
-            which can be found in
-            <ulink url="https://bugzilla.mozilla.org/show_bug.cgi?id=6679">bug
-            6679</ulink>. Note that this patch was made in 2000, so it may take
-            some work to apply cleanly to any releases of Bugzilla newer than
-            that, but you can use it as a starting point.
-          </para>
-
-          <para>
-            An updated (and much-expanded) version of this functionality is
-            due to be released as part of Bugzilla 2.20; see 
-            <ulink url="https://bugzilla.mozilla.org/show_bug.cgi?id=185090">bug
-            185090</ulink> for the discussion, and for more up-to-date patches
-            if you just can't wait.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-email-in">
-          <para>
-            How do I set up the email interface to submit or change bugs via email?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Bugzilla 3.0 and later offers the ability submit or change
-            bugs via email, using the <filename>email_in.pl</filename>
-            script within the root directory of the Bugzilla installation.
-            More information on the script can be found in
-            <ulink url="api/email_in.html">docs/html/api/email_in.html</ulink>.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-email-sendmailnow">
-          <para>
-            Email takes FOREVER to reach me from Bugzilla -- it's
-            extremely slow. What gives?
-          </para>
-        </question>
-        <answer>
-          <para>
-            If you are using <application>sendmail</application>, try
-            enabling <option>sendmailnow</option> in
-            <filename>editparams.cgi</filename>. For earlier versions of
-            <application>sendmail</application>, one could achieve
-            significant performance improvement in the UI (at the cost of
-            delaying the sending of mail) by setting this parameter to
-            <literal>off</literal>. Sites with
-            <application>sendmail</application> version 8.12 (or higher)
-            should leave this <literal>on</literal>, as they will not see
-            any performance benefit.
-          </para>
-          <para>
-            If you are using an alternate
-            <glossterm linkend="gloss-mta">MTA</glossterm>, make sure the
-            options given in <filename>Bugzilla/BugMail.pm</filename>
-            and any other place where <application>sendmail</application>
-            is called are correct for your MTA.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-email-nonreceived">
-          <para>
-             How come email from Bugzilla changes never reaches me?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Double-check that you have not turned off email in your user
-            preferences. Confirm that Bugzilla is able to send email by
-            visiting the <quote>Log In</quote> link of your Bugzilla
-            installation and clicking the <quote>Submit Request</quote>
-            button after entering your email address.
-          </para>
-          <para>
-            If you never receive mail from Bugzilla, chances are you do
-            not have sendmail in "/usr/lib/sendmail". Ensure sendmail
-            lives in, or is symlinked to, "/usr/lib/sendmail".
-          </para>
-          <para>
-            If you are using an MTA other than
-            <application>sendmail</application> the
-            <option>sendmailnow</option> param must be set to
-            <literal>on</literal> or no mail will be sent.
-          </para>
-        </answer>
-      </qandaentry>
-    </qandadiv>
-
-    <qandadiv id="faq-db">
-      <title>Bugzilla Database</title>
-
-      <qandaentry>
-        <question id="faq-db-corrupted">
-          <para>
-            I think my database might be corrupted, or contain
-            invalid entries. What do I do?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Run the <quote>sanity check</quote> utility
-            (<filename>sanitycheck.cgi</filename>) from your web browser
-            to see! If it finishes without errors, you're
-            <emphasis>probably</emphasis> OK. If it doesn't come back
-            OK (i.e. any red letters), there are certain things
-            Bugzilla can recover from and certain things it can't. If
-            it can't auto-recover, I hope you're familiar with
-            mysqladmin commands or have installed another way to
-            manage your database. Sanity Check, although it is a good
-            basic check on your database integrity, by no means is a
-            substitute for competent database administration and
-            avoiding deletion of data. It is not exhaustive, and was
-            created to do a basic check for the most common problems
-            in Bugzilla databases.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-db-manualedit">
-          <para>
-            I want to manually edit some entries in my database. How?
-          </para>
-        </question>
-        <answer>
-          <para>
-            There is no facility in Bugzilla itself to do this. It's also
-            generally not a smart thing to do if you don't know exactly what
-            you're doing. If you understand SQL, though, you can use the
-            <command>mysql</command> or <command>psql</command> command line 
-            utilities to manually insert, delete and modify table information. 
-            There are also more intuitive GUI clients available for both MySQL 
-            and PostgreSQL. For MySQL, we recommend
-            <ulink url="http://www.phpmyadmin.net/">phpMyAdmin</ulink>.
-          </para>
-
-          <para>
-            Remember, backups are your friend. Everyone makes mistakes, and
-            it's nice to have a safety net in case you mess something up.
-          </para>
-
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-db-permissions">
-          <para>
-            I think I've set up MySQL permissions correctly, but Bugzilla still
-            can't connect.
-          </para>
-        </question>
-        <answer>
-          <para>
-            Try running MySQL from its binary:
-            <command>mysqld --skip-grant-tables</command>.
-            This will allow you to completely rule out grant tables as the
-            cause of your frustration. If this Bugzilla is able to connect
-            at this point then you need to check that you have granted proper
-            permission to the user password combo defined in
-            <filename>localconfig</filename>.
-          </para>
-          <warning>
-            <para>
-              Running MySQL with this command line option is very insecure and
-              should only be done when not connected to the external network
-              as a troubleshooting step.  Please do not run your production
-              database in this mode.
-            </para>
-          </warning>
-          <para>
-            You may also be suffering from a client version mismatch:
-          </para>
-          <para>
-            MySQL 4.1 and up uses an authentication protocol based on
-            a password hashing algorithm that is incompatible with that
-            used by older clients. If you upgrade the server to 4.1,
-            attempts to connect to it with an older client may fail
-            with the following message:
-          </para>
-          <para>
-            <screen><prompt>shell&gt;</prompt> mysql
-            Client does not support authentication protocol requested
-            by server; consider upgrading MySQL client
-            </screen>
-          </para>
-          <para>
-            To solve this problem, you should use one of the following
-            approaches:
-          </para>
-          <para>
-            <itemizedlist>
-              <listitem>
-                <para>
-                  Upgrade all client programs to use a 4.1.1 or newer
-                  client library.
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  When connecting to the server with a pre-4.1 client
-                  program, use an account that still has a
-                  pre-4.1-style password.
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  Reset the password to pre-4.1 style for each user
-                  that needs to use a pre-4.1 client program.
-                  This can be done using the SET PASSWORD statement
-                  and the OLD_PASSWORD() function:
-                  <screen>
-                    <prompt>mysql&gt;</prompt> SET PASSWORD FOR
-                    <prompt>    -&gt;</prompt> ' some_user '@' some_host ' = OLD_PASSWORD(' newpwd ');
-                  </screen>
-                </para>
-              </listitem>
-            </itemizedlist>
-            
-          </para>
-
-
-          <para>
-          </para>
-
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-db-synchronize">
-          <para>
-            How do I synchronize bug information among multiple
-            different Bugzilla databases?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Well, you can synchronize or you can move bugs.
-            Synchronization will only work one way -- you can create
-            a read-only copy of the database at one site, and have it
-            regularly updated at intervals from the main database.
-          </para>
-          <para>
-            MySQL has some synchronization features built-in to the
-            latest releases. It would be great if someone looked into
-            the possibilities there and provided a report to the
-            newsgroup on how to effectively synchronize two Bugzilla
-            installations.
-          </para>
-          <para>
-            If you simply need to transfer bugs from one Bugzilla to another,
-            checkout the <quote>move.pl</quote> script in the Bugzilla
-            distribution.
-          </para>
-        </answer>
-      </qandaentry>
-    </qandadiv>
-
-    <qandadiv id="faq-nt">
-      <title>Can Bugzilla run on a Windows server?</title>
-
-      <qandaentry>
-        <question id="faq-nt-easiest">
-          <para>
-            What is the easiest way to run Bugzilla on Win32 (Win98+/NT/2K)?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Making Bugzilla work easily with Windows
-            was one of the major goals of the 2.18 milestone. If the
-            necessary components are in place (perl, a webserver, an MTA, etc.)
-            then installation of Bugzilla on a Windows box should be no more
-            difficult than on any other platform. As with any installation,
-            we recommend that you carefully and completely follow the
-            installation instructions in <xref linkend="os-win32"/>.
-          </para>
-          <para>
-            While doing so, don't forget to check out the very excellent guide
-            to <ulink url="http://www.bugzilla.org/docs/win32install.html">
-            Installing Bugzilla on Microsoft Windows</ulink> written by
-            Byron Jones. Thanks, Byron!
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-nt-bundle">
-          <para>
-            Is there a "Bundle::Bugzilla" equivalent for Win32?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Not currently. Bundle::Bugzilla enormously simplifies Bugzilla
-            installation on UNIX systems. If someone can volunteer to
-            create a suitable PPM bundle for Win32, it would be appreciated.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-nt-mappings">
-          <para>
-            CGI's are failing with a <quote>something.cgi is not a valid
-            Windows NT application</quote> error. Why?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Depending on what Web server you are using, you will have to
-            configure the Web server to treat *.cgi files as CGI scripts.
-            In IIS, you do this by adding *.cgi to the App Mappings with
-            the &lt;path&gt;\perl.exe %s %s as the executable.
-          </para>
-          <para>
-            Microsoft has some advice on this matter, as well:
-            <blockquote>
-              <para>
-                <quote>Set application mappings. In the ISM, map the extension
-                for the script file(s) to the executable for the script
-                interpreter. For example, you might map the extension .py to
-                Python.exe, the executable for the Python script interpreter.
-                Note For the ActiveState Perl script interpreter, the extension
-                '.pl' is associated with PerlIS.dll by default. If you want
-                to change the association of .pl to perl.exe, you need to
-                change the application mapping. In the mapping, you must add
-                two percent (%) characters to the end of the pathname for
-                perl.exe, as shown in this example: 
-                <command>c:\perl\bin\perl.exe %s %s</command></quote>
-              </para>
-            </blockquote>
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-nt-dbi">
-          <para>
-            I'm having trouble with the perl modules for NT not being
-            able to talk to the database.
-          </para>
-        </question>
-        <answer>
-          <para>
-            Your modules may be outdated or inaccurate. Try:
-            <orderedlist>
-              <listitem>
-                <para>
-                  Hitting <ulink url="http://www.activestate.com/ActivePerl"/>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  Download ActivePerl
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  Go to your prompt
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  Type 'ppm'
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <prompt>PPM></prompt> <command>install DBI DBD-mysql GD</command>
-                </para>
-              </listitem>
-            </orderedlist>
-            I reckon TimeDate comes with the activeperl.
-            You can check the ActiveState site for packages for installation
-            through PPM. <ulink url="http://www.activestate.com/Packages/"/>.
-          </para>
-        </answer>
-      </qandaentry>
-
-    </qandadiv>
-
-    <qandadiv id="faq-use">
-      <title>Bugzilla Usage</title>
-
-      <qandaentry>
-        <question id="faq-use-changeaddress">
-          <para>
-            How do I change my user name (email address) in Bugzilla?
-          </para>
-        </question>
-        <answer>
-          <para>
-            You can change your email address from the Name and Password
-            section in Preferences. You will be emailed at both the old 
-            and new addresses for confirmation. 'Administrative Policies' 
-            must have the 'allowemailchange' parameter set to <quote>On</quote>.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-use-query">
-          <para>
-            The query page is very confusing.
-            Isn't there a simpler way to query?
-          </para>
-        </question>
-        <answer>
-          <para>
-            The interface was simplified by a UI designer for 2.16. Further
-            suggestions for improvement are welcome, but we won't sacrifice
-            power for simplicity.
-          </para>
-          <para>
-            As of 2.18, there is also a 'simpler' search available. At the top
-            of the search page are two links; <quote>Advanced Search</quote>
-            will take you to the familiar full-power/full-complexity search
-            page. The <quote>Find a Specific Bug</quote> link will take you
-            to a much-simplified page where you can pick a product and
-            status (open,closed, or both), then enter words that appear in
-            the bug you want to find. This search will scour the 'Summary'
-            and 'Comment' fields, and return a list of bugs sorted so that
-            the bugs with the most hits/matches are nearer to the top.
-          </para>
-          <note>
-            <para>
-              Matches in the Summary will 'trump' matches in comments,
-              and bugs with summary-matches will be placed higher in
-              the buglist --  even if a lower-ranked bug has more matches
-              in the comments section.
-            </para>
-          </note>
-          <para>
-            Bugzilla uses a cookie to remember which version of the page
-            you visited last, and brings that page up when you next do a
-            search. The default page for new users (or after an upgrade)
-            is the 'simple' search.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-use-accept">
-          <para>
-            I'm confused by the behavior of the <quote>Accept</quote>
-            button in the Show Bug form. Why doesn't it assign the bug
-            to me when I accept it?
-          </para>
-        </question>
-        <answer>
-          <para>
-            The current behavior is acceptable to bugzilla.mozilla.org and
-            most users. If you want to change this behavior, though, you
-            have your choice of patches: 
-            <simplelist>
-              <member>
-                <ulink url="https://bugzilla.mozilla.org/show_bug.cgi?id=35195">Bug 35195</ulink>
-                seeks to add an <quote>...and accept the bug</quote> checkbox
-                to the UI. It has two patches attached to it: 
-                <ulink url="https://bugzilla.mozilla.org/showattachment.cgi?attach_id=8029">attachment 8029</ulink>
-                was originally created for Bugzilla 2.12, while 
-                <ulink url="https://bugzilla.mozilla.org/showattachment.cgi?attach_id=91372">attachment 91372</ulink>
-                is an updated version for Bugzilla 2.16
-              </member>
-              <member>
-                <ulink url="https://bugzilla.mozilla.org/show_bug.cgi?id=37613">Bug
-                37613</ulink> also provides two patches (against Bugzilla
-                2.12): one to add a 'Take Bug' option, and the other to
-                automatically reassign the bug on 'Accept'. 
-              </member>
-            </simplelist>
-            These patches are all somewhat dated now, and cannot be applied
-            directly, but they are simple enough to provide a guide on how
-            Bugzilla can be customized and updated to suit your needs.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-use-attachment">
-          <para>
-            I can't upload anything into the database via the
-            <quote>Create Attachment</quote> link. What am I doing wrong?
-          </para>
-        </question>
-        <answer>
-          <para>
-            The most likely cause is a very old browser or a browser that is
-            incompatible with file upload via POST. Download the latest version
-            of your favourite browser to handle uploads correctly.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-use-keyword">
-          <para>
-            How do I change a keyword in Bugzilla, once some bugs are using it?
-          </para>
-        </question>
-        <answer>
-          <para>
-            In the Bugzilla administrator UI, edit the keyword and
-            it will let you replace the old keyword name with a new one.
-            This will cause a problem with the keyword cache; run
-            <command>sanitycheck.cgi</command> to fix it.
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-use-close">
-          <para>
-            Why can't I close bugs from the <quote>Change Several Bugs
-            at Once</quote> page?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Simple answer; you can.
-          </para>
-
-          <para>
-            The logic behind the page checks every bug in the list to
-            determine legal state changes, and then only shows you controls
-            to do things that could apply to <emphasis>every</emphasis> bug
-            on the list. The reason for this is that if you try to do something
-            illegal to a bug, the whole process will grind to a halt, and all
-            changes after the failed one will <emphasis>also</emphasis> fail.
-            Since that isn't a good outcome, the page doesn't even present
-            you with the option.
-          </para>
-
-          <para>
-            In practical terms, that means that in order to mark
-            multiple bugs as CLOSED, then every bug on the page has to be
-            either RESOLVED or VERIFIED already; if this is not the case,
-            then the option to close the bugs will not appear on the page.
-          </para>
-
-          <para>
-            The rationale is that if you pick one of the bugs that's not
-            VERIFIED and try to CLOSE it, the bug change will fail
-            miserably (thus killing any changes in the list after it
-            while doing the bulk change) so it doesn't even give you the
-            choice.
-          </para>
-        </answer>
-      </qandaentry>
-
-
-    </qandadiv>
-
-    <qandadiv id="faq-hacking">
-      <title>Bugzilla Hacking</title>
-
-      <qandaentry>
-        <question id="faq-hacking-templatestyle">
-          <para>
-            What kind of style should I use for templatization?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Gerv and Myk suggest a 2-space indent, with embedded code sections on
-            their own line, in line with outer tags. Like this:</para>
-            <programlisting><![CDATA[
-<fred>
-[% IF foo %]
-  <bar>
-  [% FOREACH x = barney %]
-    <tr>
-      <td>
-        [% x %]
-      </td>
-    <tr>
-  [% END %]
-[% END %]
-</fred>
-]]></programlisting>
-
-        <para> Myk also recommends you turn on PRE_CHOMP in the template
-        initialization to prevent bloating of HTML with unnecessary whitespace.
-        </para>
-
-        <para>Please note that many have differing opinions on this subject,
-        and the existing templates in Bugzilla espouse both this and a 4-space
-        style. Either is acceptable; the above is preferred.</para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-hacking-bugzillabugs">
-          <para>
-            What bugs are in Bugzilla right now?
-          </para>
-        </question>
-        <answer>
-          <para>
-            Try <ulink url="https://bugzilla.mozilla.org/buglist.cgi?bug_status=NEW&amp;bug_status=ASSIGNED&amp;bug_status=REOPENED&amp;product=Bugzilla">
-            this link</ulink> to view current bugs or requests for
-            enhancement for Bugzilla.
-          </para>
-          <para>
-            You can view bugs marked for &bz-nextver; release
-            <ulink url="https://bugzilla.mozilla.org/buglist.cgi?product=Bugzilla&amp;target_milestone=Bugzilla+&amp;bz-nextver;">here</ulink>.
-            This list includes bugs for the &bz-nextver; release that have already
-            been fixed and checked into CVS. Please consult the
-            <ulink url="http://www.bugzilla.org/">
-            Bugzilla Project Page</ulink> for details on how to
-            check current sources out of CVS so you can have these
-            bug fixes early!
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-hacking-priority">
-          <para>
-            How can I change the default priority to a null value?
-            For instance, have the default priority be <quote>---</quote>
-            instead of <quote>P2</quote>?
-          </para>
-        </question>
-        <answer>
-          <para>
-            This is well-documented in <ulink
-            url="https://bugzilla.mozilla.org/show_bug.cgi?id=49862">bug
-            49862</ulink>. Ultimately, it's as easy as adding the
-            <quote>---</quote> priority field to your localconfig file
-            in the appropriate area, re-running checksetup.pl, and then
-            changing the default priority in your browser using
-            <command>editparams.cgi</command>. 
-          </para>
-        </answer>
-      </qandaentry>
-
-      <qandaentry>
-        <question id="faq-hacking-patches">
-          <para>
-            What's the best way to submit patches?  What guidelines
-            should I follow?
-          </para>
-        </question>
-        <answer>
-          <blockquote>
-            <orderedlist>
-              <listitem>
-                <para>
-                  Enter a bug into bugzilla.mozilla.org for the <quote><ulink
-                  url="https://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla">Bugzilla</ulink></quote>
-                  product.
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  Upload your patch as a unified diff (having used <quote>diff
-                  -u</quote> against the <emphasis>current sources</emphasis>
-                  checked out of CVS), or new source file by clicking
-                  <quote>Create a new attachment</quote> link on the bug
-                  page you've just created, and include any descriptions of
-                  database changes you may make, into the bug ID you submitted
-                  in step #1. Be sure and click the <quote>Patch</quote> checkbox
-                  to indicate the text you are sending is a patch!
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  Announce your patch and the associated URL
-                  (https://bugzilla.mozilla.org/show_bug.cgi?id=XXXXXX)
-                  for discussion in the newsgroup
-                  (mozilla.support.bugzilla). You'll get a
-                  really good, fairly immediate reaction to the
-                  implications of your patch, which will also give us
-                  an idea how well-received the change would be.
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  If it passes muster with minimal modification, the
-                  person to whom the bug is assigned in Bugzilla is
-                  responsible for seeing the patch is checked into CVS.
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  Bask in the glory of the fact that you helped write
-                  the most successful open-source bug-tracking software
-                  on the planet :)
-                </para>
-              </listitem>
-            </orderedlist>
-          </blockquote>
-        </answer>
-      </qandaentry>
-
-
-    </qandadiv>
-
-  </qandaset>
-
-</appendix>
-
-
-<!-- Keep this comment at the end of the file
-Local variables:
-mode: sgml
-sgml-always-quote-attributes:t
-sgml-auto-insert-required-elements:t
-sgml-balanced-tag-edit:t
-sgml-exposed-tags:nil
-sgml-general-insert-case:lower
-sgml-indent-data:t
-sgml-indent-step:2
-sgml-local-catalogs:nil
-sgml-local-ecat-files:nil
-sgml-minimize-attributes:nil
-sgml-namecase-general:t
-sgml-omittag:t
-sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
-sgml-shorttag:t
-sgml-tag-region-if-active:t
-End:
--->
diff --git a/BugsSite/docs/xml/introduction.xml b/BugsSite/docs/xml/introduction.xml
deleted file mode 100644
index 3968702..0000000
--- a/BugsSite/docs/xml/introduction.xml
+++ /dev/null
@@ -1,121 +0,0 @@
-<chapter id="introduction">
-  <title>Introduction</title>
-
-  <section id="what-is-bugzilla">
-    <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 products. 
-    </para>
-    
-    <para><emphasis>Do we need more here?</emphasis></para>
-    
-  </section>
-
-  <section id="why-tracking">
-    <title>Why use a bug-tracking system?</title>
-    
-    <para>Those who do not use a bug-tracking system tend to rely on
-    shared lists, email, spreadsheets and/or Post-It notes to monitor the 
-    status of defects. This procedure
-    is usually error-prone and tends to cause those bugs judged least 
-    significant by developers to be dropped or ignored.</para>
-
-    <para>Integrated defect-tracking systems make sure that nothing gets
-    swept under the carpet; they provide a method of creating, storing,
-    arranging and processing defect reports and enhancement requests.</para>
-    
-  </section>
-    
-  <section id="why-bugzilla">
-    <title>Why use Bugzilla?</title>
-
-    <para>Bugzilla is the leading open-source/free software bug tracking 
-    system. It boasts many advanced features, including: 
-    <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>Completely customizable and/or localizable web user
-        interface</para>
-      </listitem>
-
-      <listitem>
-        <para>Additional XML, email and console interfaces</para>
-      </listitem>
-
-      <listitem>
-        <para>Extensive configurability</para>
-      </listitem>
-
-      <listitem>
-        <para>Smooth upgrade pathway between versions</para>
-      </listitem>
-    </itemizedlist>
-    </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 configuration management solution.</para>
-  </section>
-</chapter>
-
-<!-- Keep this comment at the end of the file
-Local variables:
-mode: sgml
-sgml-always-quote-attributes:t
-sgml-auto-insert-required-elements:t
-sgml-balanced-tag-edit:t
-sgml-exposed-tags:nil
-sgml-general-insert-case:lower
-sgml-indent-data:t
-sgml-indent-step:2
-sgml-local-catalogs:nil
-sgml-local-ecat-files:nil
-sgml-minimize-attributes:nil
-sgml-namecase-general:t
-sgml-omittag:t
-sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
-sgml-shorttag:t
-sgml-tag-region-if-active:t
-End:
--->
diff --git a/BugsSite/docs/xml/requiredsoftware.xml b/BugsSite/docs/xml/requiredsoftware.xml
deleted file mode 100644
index 4a751c0..0000000
--- a/BugsSite/docs/xml/requiredsoftware.xml
+++ /dev/null
@@ -1,77 +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/"/>
-
-  Optional web server for Bugzilla, but recommended because of broad user
-  base and support.</para>
-
-  <para>Bugzilla: 
-  <ulink url="http://www.bugzilla.org/"/>
-  </para>
-
-  <para>MySQL: 
-  <ulink url="http://www.mysql.com/"/>
-  </para>
-
-  <para>Perl: 
-  <ulink url="http://www.perl.org/"/>
-  </para>
-
-  <para>CPAN: 
-  <ulink url="http://www.cpan.org/"/>
-  </para>
-
-  <para>DBI Perl module: 
-  <ulink url="http://www.cpan.org/modules/by-module/DBI/"/>
-  </para>
-
-  <para>MySQL related Perl modules: 
-  <ulink url="http://www.cpan.org/modules/by-module/Mysql/"/>
-  </para>
-
-  <para>TimeDate Perl module collection: 
-  <ulink url="http://www.cpan.org/modules/by-module/Date/"/>
-  </para>
-
-  <para>GD Perl module: 
-  <ulink url="http://www.cpan.org/modules/by-module/GD/"/>
-
-  Alternately, you should be able to find the latest version of GD at 
-  <ulink url="http://www.boutell.com/gd/"/>
-  </para>
-
-  <para>Chart::Base module: 
-  <ulink url="http://www.cpan.org/modules/by-module/Chart/"/>
-  </para>
-
-  <para>(But remember, Bundle::Bugzilla will install all the modules for you.)
-  </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.xml" "book" "chapter")
-sgml-shorttag:t
-sgml-tag-region-if-active:t
-End:
--->
-
diff --git a/BugsSite/duplicates.cgi b/BugsSite/duplicates.cgi
index e393d7c..06334e2 100755
--- a/BugsSite/duplicates.cgi
+++ b/BugsSite/duplicates.cgi
@@ -27,7 +27,7 @@
 
 use AnyDBM_File;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -37,19 +37,6 @@
 use Bugzilla::Product;
 
 my $cgi = Bugzilla->cgi;
-
-# Go directly to the XUL version of the duplicates report (duplicates.xul)
-# if the user specified ctype=xul.  Adds params if they exist, and directs
-# the user to a signed copy of the script in duplicates.jar if it exists.
-if (defined $cgi->param('ctype') && $cgi->param('ctype') eq "xul") {
-    my $params = CanonicaliseParams($cgi->query_string(), ["format", "ctype"]);
-    my $url = (-e "duplicates.jar" ? "duplicates.jar!/" : "") . 
-          "duplicates.xul" . ($params ? "?$params" : "") . "\n\n";
-
-    print $cgi->redirect($url);
-    exit;
-}
-
 my $template = Bugzilla->template;
 my $vars = {};
 
diff --git a/BugsSite/duplicates.xul b/BugsSite/duplicates.xul
deleted file mode 100644
index cebb4e1..0000000
--- a/BugsSite/duplicates.xul
+++ /dev/null
@@ -1,133 +0,0 @@
-<?xml version="1.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>
-   -
-   -->
-
-<!DOCTYPE window [
-  <!ENTITY idColumn.label               "ID">
-  <!ENTITY duplicateCountColumn.label   "Count">
-  <!ENTITY duplicateDeltaColumn.label   "Delta">
-  <!ENTITY componentColumn.label        "Component">
-  <!ENTITY severityColumn.label         "Severity">
-  <!ENTITY osColumn.label               "OS">
-  <!ENTITY targetMilestoneColumn.label  "Milestone">
-  <!ENTITY summaryColumn.label          "Summary">
-]>
-
-<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="skins/standard/duplicates.css" type="text/css"?>
-
-<window id="duplicates_report"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="Duplicates Report">
-
-  // Code for populating the tree from the RDF data source
-  // and loading bug reports when the user selects rows in the tree.
-  <script type="application/x-javascript" src="js/duplicates.js" />
-
-  <tree id="results-tree" flex="1"
-        flags="dont-build-content"
-        enableColumnDrag="true"
-        datasources="rdf:null"
-        ref=""
-        onselect="loadBugInPane();"
-        ondblclick="loadBugInWindow();">
-    <treecols>
-      <treecol id="id_column" label="&idColumn.label;" primary="true" sort="?id" 
-               persist="width hidden sortActive sortDirection ordinal" />
-      <splitter class="tree-splitter"/> 
-
-      <treecol id="duplicate_count_column" label="&duplicateCountColumn.label;" sort="?duplicate_count" 
-               sortActive="true" sortDirection="descending" 
-               persist="width hidden sortActive sortDirection ordinal" />
-      <splitter class="tree-splitter" /> 
-      
-      <treecol id="duplicate_delta_column" label="&duplicateDeltaColumn.label;" sort="?duplicate_delta" 
-               persist="width hidden sortActive sortDirection ordinal" />
-      <splitter class="tree-splitter"/>
-      
-      <treecol id="component_column" label="&componentColumn.label;" flex="3" sort="?component" 
-               persist="width hidden sortActive sortDirection ordinal" />
-      <splitter class="tree-splitter"/> 
-
-      <treecol id="severity_column" label="&severityColumn.label;" flex="1" sort="?severity" 
-               persist="width hidden sortActive sortDirection ordinal" />
-      <splitter class="tree-splitter"/> 
-
-      <treecol id="os_column" label="&osColumn.label;" flex="2" sort="?os" 
-               persist="width hidden sortActive sortDirection ordinal" />
-      <splitter class="tree-splitter"/> 
-
-      <treecol id="target_milestone_column" label="&targetMilestoneColumn.label;" flex="1" sort="?target_milestone" 
-               persist="width hidden sortActive sortDirection ordinal" />
-      <splitter class="tree-splitter"/> 
-
-      <treecol id="summary_column" label="&summaryColumn.label;" flex="12" sort="?summary" 
-               persist="width hidden sortActive sortDirection ordinal" />
-    </treecols>
-    <template>
-      <rule>
-        <conditions>
-          <treeitem uri="?uri" />
-          <triple subject="?uri" predicate="http://www.bugzilla.org/rdf#bugs" object="?bugs" />
-          <member container="?bugs" child="?bug" />
-          <triple subject="?bug" predicate="http://www.bugzilla.org/rdf#id" object="?id" />
-        </conditions>
-        <bindings>
-          <binding subject="?bug" predicate="http://www.bugzilla.org/rdf#duplicate_count"  object="?duplicate_count" />
-          <binding subject="?bug" predicate="http://www.bugzilla.org/rdf#duplicate_delta"  object="?duplicate_delta" />
-          <binding subject="?bug" predicate="http://www.bugzilla.org/rdf#component"        object="?component" />
-          <binding subject="?bug" predicate="http://www.bugzilla.org/rdf#severity"         object="?severity" />
-          <binding subject="?bug" predicate="http://www.bugzilla.org/rdf#priority"         object="?priority" />
-          <binding subject="?bug" predicate="http://www.bugzilla.org/rdf#os"               object="?os" />
-          <binding subject="?bug" predicate="http://www.bugzilla.org/rdf#target_milestone" object="?target_milestone" />
-          <binding subject="?bug" predicate="http://www.bugzilla.org/rdf#summary"          object="?summary" />
-          <binding subject="?bug" predicate="http://www.bugzilla.org/rdf#resolution"       object="?resolution" />
-        </bindings>
-        <action>
-          <treechildren>
-            <treeitem uri="?bug">
-              <treerow properties="resolution-?resolution">
-                <treecell ref="id_column"               label="?id"               properties="resolution-?resolution" />
-                <treecell ref="duplicate_count_column"  label="?duplicate_count"  properties="resolution-?resolution" />
-                <treecell ref="duplicate_delta_column"  label="?duplicate_delta"  properties="resolution-?resolution" />
-                <treecell ref="component_column"        label="?component"        properties="resolution-?resolution" />
-                <treecell ref="severity_column"         label="?severity"         properties="resolution-?resolution" />
-                <treecell ref="os_column"               label="?os"               properties="resolution-?resolution" />
-                <treecell ref="target_milestone_column" label="?target_milestone" properties="resolution-?resolution" />
-                <treecell ref="summary_column"          label="?summary"          properties="resolution-?resolution" />
-              </treerow>
-            </treeitem>
-          </treechildren>
-        </action>
-      </rule>
-    </template>
-  </tree>
-  
-  <splitter id="report-content-splitter" collapse="after" state="open" persist="state">
-    <grippy/>
-  </splitter>
-  
-  <iframe id="content-browser" src="about:blank" flex="2" persist="height" />
-
-</window>
diff --git a/BugsSite/editclassifications.cgi b/BugsSite/editclassifications.cgi
index 0ebfb97..8ef9afe 100755
--- a/BugsSite/editclassifications.cgi
+++ b/BugsSite/editclassifications.cgi
@@ -17,11 +17,11 @@
 #
 # Contributor(s): Albert Ting <alt@sonic.net>
 #                 Max Kanat-Alexander <mkanat@bugzilla.org>
-#
-# Direct any questions on this source code to mozilla.org
+#                 Frédéric Buclin <LpSolit@gmail.com>
+
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -40,6 +40,12 @@
     my $cgi = Bugzilla->cgi;
     my $template = Bugzilla->template;
 
+    $vars->{'classifications'} = [Bugzilla::Classification::get_all_classifications()]
+      if ($action eq 'select');
+    # There is currently only one section about classifications,
+    # so all pages point to it. Let's define it here.
+    $vars->{'doc_section'} = 'classifications.html';
+
     $action =~ /(\w+)/;
     $action = $1;
     print $cgi->header();
@@ -74,14 +80,7 @@
 #
 # action='' -> Show nice list of classifications
 #
-
-unless ($action) {
-    my @classifications =
-        Bugzilla::Classification::get_all_classifications();
-
-    $vars->{'classifications'} = \@classifications;
-    LoadTemplate("select");
-}
+LoadTemplate('select') unless $action;
 
 #
 # action='add' -> present form for parameters for new classification
@@ -126,10 +125,13 @@
     $dbh->do("INSERT INTO classifications (name, description, sortkey)
               VALUES (?, ?, ?)", undef, ($class_name, $description, $sortkey));
 
-    $vars->{'classification'} = $class_name;
-
     delete_token($token);
-    LoadTemplate($action);
+
+    $vars->{'message'} = 'classification_created';
+    $vars->{'classification'} = new Bugzilla::Classification({name => $class_name});
+    $vars->{'classifications'} = [Bugzilla::Classification::get_all_classifications];
+    $vars->{'token'} = issue_session_token('reclassify_classifications');
+    LoadTemplate('reclassify');
 }
 
 #
@@ -172,22 +174,22 @@
     }
 
     # lock the tables before we start to change everything:
-    $dbh->bz_lock_tables('classifications WRITE', 'products WRITE');
-
-    # delete
-    $dbh->do("DELETE FROM classifications WHERE id = ?", undef,
-             $classification->id);
+    $dbh->bz_start_transaction();
 
     # update products just in case
     $dbh->do("UPDATE products SET classification_id = 1
               WHERE classification_id = ?", undef, $classification->id);
 
-    $dbh->bz_unlock_tables();
+    # delete
+    $dbh->do("DELETE FROM classifications WHERE id = ?", undef,
+             $classification->id);
 
-    $vars->{'classification'} = $classification;
+    $dbh->bz_commit_transaction();
 
+    $vars->{'message'} = 'classification_deleted';
+    $vars->{'classification'} = $class_name;
     delete_token($token);
-    LoadTemplate($action);
+    LoadTemplate('select');
 }
 
 #
@@ -229,7 +231,7 @@
       || ThrowUserError('classification_invalid_sortkey', {'name' => $class_old->name,
                                                            'sortkey' => $stored_sortkey});
 
-    $dbh->bz_lock_tables('classifications WRITE');
+    $dbh->bz_start_transaction();
 
     if ($class_name ne $class_old->name) {
 
@@ -262,10 +264,12 @@
         $vars->{'updated_sortkey'} = 1;
     }
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
+    $vars->{'message'} = 'classification_updated';
+    $vars->{'classification'} = $class_name;
     delete_token($token);
-    LoadTemplate($action);
+    LoadTemplate('select');
 }
 
 #
diff --git a/BugsSite/editcomponents.cgi b/BugsSite/editcomponents.cgi
index 09acc0c..7623be5 100755
--- a/BugsSite/editcomponents.cgi
+++ b/BugsSite/editcomponents.cgi
@@ -22,55 +22,30 @@
 #                 Terry Weissman <terry@mozilla.org>
 #                 Frédéric Buclin <LpSolit@gmail.com>
 #                 Akamai Technologies <bugzilla-dev@akamai.com>
-#
-# Direct any questions on this source code to
-#
-# Holger Schurig <holgerschurig@nikocity.de>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
-use Bugzilla::Series;
 use Bugzilla::Util;
 use Bugzilla::Error;
 use Bugzilla::User;
 use Bugzilla::Component;
-use Bugzilla::Bug;
 use Bugzilla::Token;
 
-###############
-# Subroutines #
-###############
-
-# Takes an arrayref of login names and returns an arrayref of user ids.
-sub check_initial_cc {
-    my ($user_names) = @_;
-
-    my %cc_ids;
-    foreach my $cc (@$user_names) {
-        my $id = login_to_id($cc, THROW_ERROR);
-        $cc_ids{$id} = 1;
-    }
-    return [keys %cc_ids];
-}
-
-###############
-# Main Script #
-###############
-
 my $cgi = Bugzilla->cgi;
-my $dbh = Bugzilla->dbh;
 my $template = Bugzilla->template;
 my $vars = {};
+# There is only one section about components in the documentation,
+# so all actions point to the same page.
+$vars->{'doc_section'} = 'components.html';
 
 #
 # Preliminary checks:
 #
 
 my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $whoid = $user->id;
 
 print $cgi->header();
 
@@ -115,16 +90,13 @@
 #
 
 unless ($action) {
-
     $vars->{'showbugcounts'} = $showbugcounts;
     $vars->{'product'} = $product;
     $template->process("admin/components/list.html.tmpl", $vars)
         || ThrowTemplateError($template->error());
-
     exit;
 }
 
-
 #
 # action='add' -> present form for parameters for new component
 #
@@ -136,12 +108,9 @@
     $vars->{'product'} = $product;
     $template->process("admin/components/create.html.tmpl", $vars)
         || ThrowTemplateError($template->error());
-
     exit;
 }
 
-
-
 #
 # action='new' -> add component entered in the 'action=add' screen
 #
@@ -160,103 +129,24 @@
     my $description        = trim($cgi->param('description')      || '');
     my @initial_cc         = $cgi->param('initialcc');
 
-    $comp_name || ThrowUserError('component_blank_name');
-
-    if (length($comp_name) > 64) {
-        ThrowUserError('component_name_too_long',
-                       {'name' => $comp_name});
-    }
-
     my $component =
-        new Bugzilla::Component({product => $product,
-                                 name => $comp_name});
+      Bugzilla::Component->create({ name             => $comp_name,
+                                    product          => $product,
+                                    description      => $description,
+                                    initialowner     => $default_assignee,
+                                    initialqacontact => $default_qa_contact,
+                                    initial_cc       => \@initial_cc });
 
-    if ($component) {
-        ThrowUserError('component_already_exists',
-                       {'name' => $component->name});
-    }
-
-    $description || ThrowUserError('component_blank_description',
-                                   {name => $comp_name});
-
-    $default_assignee || ThrowUserError('component_need_initialowner',
-                                        {name => $comp_name});
-
-    my $default_assignee_id   = login_to_id($default_assignee);
-    my $default_qa_contact_id = Bugzilla->params->{'useqacontact'} ?
-        (login_to_id($default_qa_contact) || undef) : undef;
-
-    my $initial_cc_ids = check_initial_cc(\@initial_cc);
-
-    trick_taint($comp_name);
-    trick_taint($description);
-
-    $dbh->bz_lock_tables('components WRITE', 'component_cc WRITE');
-
-    $dbh->do("INSERT INTO components
-                (product_id, name, description, initialowner,
-                 initialqacontact)
-              VALUES (?, ?, ?, ?, ?)", undef,
-             ($product->id, $comp_name, $description,
-              $default_assignee_id, $default_qa_contact_id));
-
-    $component = new Bugzilla::Component({ product => $product,
-                                           name => $comp_name });
-
-    my $sth = $dbh->prepare("INSERT INTO component_cc 
-                             (user_id, component_id) VALUES (?, ?)");
-    foreach my $user_id (@$initial_cc_ids) {
-        $sth->execute($user_id, $component->id);
-    }
-
-    $dbh->bz_unlock_tables;
-
-    # Insert default charting queries for this product.
-    # If they aren't using charting, this won't do any harm.
-    my @series;
-
-    my $prodcomp = "&product="   . url_quote($product->name) .
-                   "&component=" . url_quote($comp_name);
-
-    # For localization reasons, we get the title of the queries from the
-    # submitted form.
-    my $open_name = $cgi->param('open_name');
-    my $nonopen_name = $cgi->param('nonopen_name');
-    my $open_query = "field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=." .
-                     $prodcomp;
-    my $nonopen_query = "field0-0-0=resolution&type0-0-0=regexp&value0-0-0=." .
-                        $prodcomp;
-
-    # trick_taint is ok here, as these variables aren't used as a command
-    # or in SQL unquoted
-    trick_taint($open_name);
-    trick_taint($nonopen_name);
-    trick_taint($open_query);
-    trick_taint($nonopen_query);
-
-    push(@series, [$open_name, $open_query]);
-    push(@series, [$nonopen_name, $nonopen_query]);
-
-    foreach my $sdata (@series) {
-        my $series = new Bugzilla::Series(undef, $product->name,
-                                          $comp_name, $sdata->[0],
-                                          $whoid, 1, $sdata->[1], 1);
-        $series->writeToDatabase();
-    }
-
+    $vars->{'message'} = 'component_created';
     $vars->{'comp'} = $component;
     $vars->{'product'} = $product;
     delete_token($token);
 
-    $template->process("admin/components/created.html.tmpl",
-                       $vars)
+    $template->process("admin/components/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
-
-
 #
 # action='del' -> ask if user really wants to delete
 #
@@ -266,18 +156,14 @@
 if ($action eq 'del') {
     $vars->{'token'} = issue_session_token('delete_component');
     $vars->{'comp'} =
-        Bugzilla::Component::check_component($product, $comp_name);
-
+      Bugzilla::Component->check({ product => $product, name => $comp_name });
     $vars->{'product'} = $product;
 
     $template->process("admin/components/confirm-delete.html.tmpl", $vars)
         || ThrowTemplateError($template->error());
-
     exit;
 }
 
-
-
 #
 # action='delete' -> really delete the component
 #
@@ -285,47 +171,21 @@
 if ($action eq 'delete') {
     check_token_data($token, 'delete_component');
     my $component =
-        Bugzilla::Component::check_component($product, $comp_name);
+        Bugzilla::Component->check({ product => $product, name => $comp_name });
 
-    if ($component->bug_count) {
-        if (Bugzilla->params->{"allowbugdeletion"}) {
-            foreach my $bug_id (@{$component->bug_ids}) {
-                # Note: We allow admins to delete bugs even if they can't
-                # see them, as long as they can see the product.
-                my $bug = new Bugzilla::Bug($bug_id);
-                $bug->remove_from_db();
-            }
-        } else {
-            ThrowUserError("component_has_bugs",
-                           {nb => $component->bug_count });
-        }
-    }
-    
-    $dbh->bz_lock_tables('components WRITE', 'component_cc WRITE',
-                         'flaginclusions WRITE', 'flagexclusions WRITE');
+    $component->remove_from_db;
 
-    $dbh->do("DELETE FROM flaginclusions WHERE component_id = ?",
-             undef, $component->id);
-    $dbh->do("DELETE FROM flagexclusions WHERE component_id = ?",
-             undef, $component->id);
-    $dbh->do("DELETE FROM component_cc WHERE component_id = ?",
-             undef, $component->id);
-    $dbh->do("DELETE FROM components WHERE id = ?",
-             undef, $component->id);
-
-    $dbh->bz_unlock_tables();
-
+    $vars->{'message'} = 'component_deleted';
     $vars->{'comp'} = $component;
     $vars->{'product'} = $product;
+    $vars->{'no_edit_component_link'} = 1;
     delete_token($token);
 
-    $template->process("admin/components/deleted.html.tmpl", $vars)
+    $template->process("admin/components/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
 }
 
-
-
 #
 # action='edit' -> present the edit component form
 #
@@ -335,7 +195,7 @@
 if ($action eq 'edit') {
     $vars->{'token'} = issue_session_token('edit_component');
     my $component =
-        Bugzilla::Component::check_component($product, $comp_name);
+        Bugzilla::Component->check({ product => $product, name => $comp_name });
     $vars->{'comp'} = $component;
 
     $vars->{'initial_cc_names'} = 
@@ -343,15 +203,11 @@
 
     $vars->{'product'} = $product;
 
-    $template->process("admin/components/edit.html.tmpl",
-                       $vars)
+    $template->process("admin/components/edit.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
-
-
 #
 # action='update' -> update the component
 #
@@ -371,106 +227,24 @@
     my $description           = trim($cgi->param('description')      || '');
     my @initial_cc            = $cgi->param('initialcc');
 
-    my $component_old =
-        Bugzilla::Component::check_component($product, $comp_old_name);
+    my $component =
+        Bugzilla::Component->check({ product => $product, name => $comp_old_name });
 
-    $comp_name || ThrowUserError('component_blank_name');
+    $component->set_name($comp_name);
+    $component->set_description($description);
+    $component->set_default_assignee($default_assignee);
+    $component->set_default_qa_contact($default_qa_contact);
+    $component->set_cc_list(\@initial_cc);
+    my $changes = $component->update();
 
-    if (length($comp_name) > 64) {
-        ThrowUserError('component_name_too_long',
-                       {'name' => $comp_name});
-    }
-
-    if ($comp_name ne $component_old->name) {
-        my $component =
-            new Bugzilla::Component({product => $product,
-                                     name => $comp_name});
-        if ($component) {
-            ThrowUserError('component_already_exists',
-                           {'name' => $component->name});
-        }
-    }
-
-    $description || ThrowUserError('component_blank_description',
-                                   {'name' => $component_old->name});
-
-    $default_assignee || ThrowUserError('component_need_initialowner',
-                                        {name => $comp_name});
-
-    my $default_assignee_id   = login_to_id($default_assignee);
-    my $default_qa_contact_id = login_to_id($default_qa_contact) || undef;
-
-    my $initial_cc_ids = check_initial_cc(\@initial_cc);
-
-    $dbh->bz_lock_tables('components WRITE', 'component_cc WRITE', 
-                         'profiles READ');
-
-    if ($comp_name ne $component_old->name) {
-
-        trick_taint($comp_name);
-        $dbh->do("UPDATE components SET name = ? WHERE id = ?",
-                 undef, ($comp_name, $component_old->id));
-
-        $vars->{'updated_name'} = 1;
-
-    }
-
-    if ($description ne $component_old->description) {
-    
-        trick_taint($description);
-        $dbh->do("UPDATE components SET description = ? WHERE id = ?",
-                 undef, ($description, $component_old->id));
-
-        $vars->{'updated_description'} = 1;
-    }
-
-    if ($default_assignee ne $component_old->default_assignee->login) {
-
-        $dbh->do("UPDATE components SET initialowner = ? WHERE id = ?",
-                 undef, ($default_assignee_id, $component_old->id));
-
-        $vars->{'updated_initialowner'} = 1;
-    }
-
-    if (Bugzilla->params->{'useqacontact'}
-        && $default_qa_contact ne $component_old->default_qa_contact->login) {
-        $dbh->do("UPDATE components SET initialqacontact = ?
-                  WHERE id = ?", undef,
-                 ($default_qa_contact_id, $component_old->id));
-
-        $vars->{'updated_initialqacontact'} = 1;
-    }
-
-    my @initial_cc_old = map($_->id, @{$component_old->initial_cc});
-    my ($removed, $added) = diff_arrays(\@initial_cc_old, $initial_cc_ids);
-
-    foreach my $user_id (@$removed) {
-        $dbh->do('DELETE FROM component_cc 
-                   WHERE component_id = ? AND user_id = ?', undef,
-                 $component_old->id, $user_id);
-        $vars->{'updated_initialcc'} = 1;
-    }
-
-    foreach my $user_id (@$added) {
-        $dbh->do("INSERT INTO component_cc (user_id, component_id) 
-                       VALUES (?, ?)", undef, $user_id, $component_old->id);
-        $vars->{'updated_initialcc'} = 1;
-    }
-
-    $dbh->bz_unlock_tables();
-
-    my $component = new Bugzilla::Component($component_old->id);
-    
+    $vars->{'message'} = 'component_updated';
     $vars->{'comp'} = $component;
-    $vars->{'initial_cc_names'} = 
-        join(', ', map($_->login, @{$component->initial_cc}));
     $vars->{'product'} = $product;
+    $vars->{'changes'} = $changes;
     delete_token($token);
 
-    $template->process("admin/components/updated.html.tmpl",
-                       $vars)
+    $template->process("admin/components/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
diff --git a/BugsSite/editfields.cgi b/BugsSite/editfields.cgi
index e57e195..138c6b7 100755
--- a/BugsSite/editfields.cgi
+++ b/BugsSite/editfields.cgi
@@ -16,7 +16,7 @@
 # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -117,6 +117,49 @@
     $template->process('admin/custom_fields/list.html.tmpl', $vars)
         || ThrowTemplateError($template->error());
 }
+elsif ($action eq 'del') {
+    my $name = $cgi->param('name');
+
+    # Validate field.
+    $name || ThrowUserError('field_missing_name');
+    # Custom field names must start with "cf_".
+    if ($name !~ /^cf_/) {
+        $name = 'cf_' . $name;
+    }
+    my $field = new Bugzilla::Field({'name' => $name});
+    $field || ThrowUserError('customfield_nonexistent', {'name' => $name});
+
+    $vars->{'field'} = $field;
+    $vars->{'token'} = issue_session_token('delete_field');
+
+    $template->process('admin/custom_fields/confirm-delete.html.tmpl', $vars)
+            || ThrowTemplateError($template->error());
+}
+elsif ($action eq 'delete') {
+    check_token_data($token, 'delete_field');
+    my $name = $cgi->param('name');
+
+    # Validate fields.
+    $name || ThrowUserError('field_missing_name');
+    # Custom field names must start with "cf_".
+    if ($name !~ /^cf_/) {
+        $name = 'cf_' . $name;
+    }
+    my $field = new Bugzilla::Field({'name' => $name});
+    $field || ThrowUserError('customfield_nonexistent', {'name' => $name});
+
+    # Calling remove_from_db will check if field can be deleted.
+    # If the field cannot be deleted, it will throw an error.
+    $field->remove_from_db();
+    
+    $vars->{'field'}   = $field;
+    $vars->{'message'} = 'custom_field_deleted';
+    
+    delete_token($token);
+
+    $template->process('admin/custom_fields/list.html.tmpl', $vars)
+        || ThrowTemplateError($template->error());
+}
 else {
     ThrowUserError('no_valid_action', {'field' => 'custom_field'});
 }
diff --git a/BugsSite/editflagtypes.cgi b/BugsSite/editflagtypes.cgi
index 0aec038..5e58266 100755
--- a/BugsSite/editflagtypes.cgi
+++ b/BugsSite/editflagtypes.cgi
@@ -27,7 +27,7 @@
 
 # Make it harder for us to do dangerous things in Perl.
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 # Use Bugzilla's flag modules for handling flag types.
 use Bugzilla;
@@ -80,7 +80,7 @@
 elsif ($action eq 'insert')         { insert($token);   }
 elsif ($action eq 'update')         { update($token);   }
 elsif ($action eq 'confirmdelete')  { confirmDelete();  } 
-elsif ($action eq 'delete')         { deleteType(undef, $token); }
+elsif ($action eq 'delete')         { deleteType($token); }
 elsif ($action eq 'deactivate')     { deactivate($token); }
 else { 
     ThrowCodeError("action_unrecognized", { action => $action });
@@ -309,9 +309,7 @@
 
     my $target_type = $cgi->param('target_type') eq "bug" ? "b" : "a";
 
-    $dbh->bz_lock_tables('flagtypes WRITE', 'products READ',
-                         'components READ', 'flaginclusions WRITE',
-                         'flagexclusions WRITE');
+    $dbh->bz_start_transaction();
 
     # Insert a record for the new flag type into the database.
     $dbh->do('INSERT INTO flagtypes
@@ -332,17 +330,19 @@
     # Populate the list of inclusions/exclusions for this flag type.
     validateAndSubmit($id);
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
-    $vars->{'name'} = $cgi->param('name');
+    $vars->{'name'} = $name;
     $vars->{'message'} = "flag_type_created";
     delete_token($token);
 
+    $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
+    $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+
     # Return the appropriate HTTP response headers.
     print $cgi->header();
 
-    # Generate and return the UI (HTML page) from the appropriate template.
-    $template->process("global/message.html.tmpl", $vars)
+    $template->process("admin/flag-type/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
 }
 
@@ -365,9 +365,7 @@
 
     my $dbh = Bugzilla->dbh;
     my $user = Bugzilla->user;
-    $dbh->bz_lock_tables('flagtypes WRITE', 'products READ',
-                         'components READ', 'flaginclusions WRITE',
-                         'flagexclusions WRITE');
+    $dbh->bz_start_transaction();
     $dbh->do('UPDATE flagtypes
                  SET name = ?, description = ?, cc_list = ?,
                      sortkey = ?, is_active = ?, is_requestable = ?,
@@ -383,7 +381,7 @@
     # Update the list of inclusions/exclusions for this flag type.
     validateAndSubmit($id);
 
-    $dbh->bz_unlock_tables();
+    $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.
@@ -431,23 +429,24 @@
                  undef, $id);
     }
 
-    $vars->{'name'} = $cgi->param('name');
+    $vars->{'name'} = $name;
     $vars->{'message'} = "flag_type_changes_saved";
     delete_token($token);
 
+    $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
+    $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+
     # Return the appropriate HTTP response headers.
     print $cgi->header();
 
-    # Generate and return the UI (HTML page) from the appropriate template.
-    $template->process("global/message.html.tmpl", $vars)
+    $template->process("admin/flag-type/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
 }
 
 
 sub confirmDelete {
-  my $flag_type = validateID();
+    my $flag_type = validateID();
 
-  if ($flag_type->flag_count) {
     $vars->{'flag_type'} = $flag_type;
     $vars->{'token'} = issue_session_token('delete_flagtype');
     # Return the appropriate HTTP response headers.
@@ -456,25 +455,17 @@
     # Generate and return the UI (HTML page) from the appropriate template.
     $template->process("admin/flag-type/confirm-delete.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-  } 
-  else {
-    # We should *always* ask if the admin really wants to delete
-    # a flagtype, even if there is no flag belonging to this type.
-    my $token = issue_session_token('delete_flagtype');
-    deleteType($flag_type, $token);
-  }
 }
 
 
 sub deleteType {
-    my $flag_type = shift || validateID();
     my $token = shift;
     check_token_data($token, 'delete_flagtype');
+    my $flag_type = validateID();
     my $id = $flag_type->id;
     my $dbh = Bugzilla->dbh;
 
-    $dbh->bz_lock_tables('flagtypes WRITE', 'flags WRITE',
-                         'flaginclusions WRITE', 'flagexclusions WRITE');
+    $dbh->bz_start_transaction();
 
     # Get the name of the flag type so we can tell users
     # what was deleted.
@@ -484,16 +475,18 @@
     $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_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     $vars->{'message'} = "flag_type_deleted";
     delete_token($token);
 
+    $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
+    $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+
     # Return the appropriate HTTP response headers.
     print $cgi->header();
 
-    # Generate and return the UI (HTML page) from the appropriate template.
-    $template->process("global/message.html.tmpl", $vars)
+    $template->process("admin/flag-type/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
 }
 
@@ -506,19 +499,22 @@
 
     my $dbh = Bugzilla->dbh;
 
-    $dbh->bz_lock_tables('flagtypes WRITE');
+    $dbh->bz_start_transaction();
     $dbh->do('UPDATE flagtypes SET is_active = 0 WHERE id = ?', undef, $flag_type->id);
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     $vars->{'message'} = "flag_type_deactivated";
     $vars->{'flag_type'} = $flag_type;
     delete_token($token);
 
+    $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
+    $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});
+
     # Return the appropriate HTTP response headers.
     print $cgi->header();
 
     # Generate and return the UI (HTML page) from the appropriate template.
-    $template->process("global/message.html.tmpl", $vars)
+    $template->process("admin/flag-type/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
 }
 
@@ -605,7 +601,8 @@
     ($product && $product->id)
       || ThrowUserError("flag_type_component_without_product");
 
-    my $component = Bugzilla::Component::check_component($product, $component_name);
+    my $component = Bugzilla::Component->check({ product => $product,
+                                                 name => $component_name });
     return $component;
 }
 
diff --git a/BugsSite/editgroups.cgi b/BugsSite/editgroups.cgi
index 0c49db6..c54924c 100755
--- a/BugsSite/editgroups.cgi
+++ b/BugsSite/editgroups.cgi
@@ -25,7 +25,7 @@
 #                 Frédéric Buclin <LpSolit@gmail.com>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -57,34 +57,6 @@
 my $action = trim($cgi->param('action') || '');
 my $token  = $cgi->param('token');
 
-# Add missing entries in bug_group_map for bugs created while
-# a mandatory group was disabled and which is now enabled again.
-sub fix_bug_permissions {
-    my $gid = shift;
-    my $dbh = Bugzilla->dbh;
-
-    detaint_natural($gid);
-    return unless $gid;
-
-    my $bug_ids =
-      $dbh->selectcol_arrayref('SELECT bugs.bug_id
-                                  FROM bugs
-                            INNER JOIN group_control_map
-                                    ON group_control_map.product_id = bugs.product_id
-                             LEFT JOIN bug_group_map
-                                    ON bug_group_map.bug_id = bugs.bug_id
-                                   AND bug_group_map.group_id = group_control_map.group_id
-                                 WHERE group_control_map.group_id = ?
-                                   AND group_control_map.membercontrol = ?
-                                   AND bug_group_map.group_id IS NULL',
-                                 undef, ($gid, CONTROLMAPMANDATORY));
-
-    my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
-    foreach my $bug_id (@$bug_ids) {
-        $sth->execute($bug_id, $gid);
-    }
-}
-
 # CheckGroupID checks that a positive integer is given and is
 # actually a valid group ID. If all tests are successful, the
 # trimmed group ID is returned.
@@ -148,6 +120,66 @@
     return $regexp;
 }
 
+# A helper for displaying the edit.html.tmpl template.
+sub get_current_and_available {
+    my ($group, $vars) = @_;
+
+    my @all_groups         = Bugzilla::Group->get_all;
+    my @members_current    = @{$group->grant_direct(GROUP_MEMBERSHIP)};
+    my @member_of_current  = @{$group->granted_by_direct(GROUP_MEMBERSHIP)};
+    my @bless_from_current = @{$group->grant_direct(GROUP_BLESS)};
+    my @bless_to_current   = @{$group->granted_by_direct(GROUP_BLESS)};
+    my (@visible_from_current, @visible_to_me_current);
+    if (Bugzilla->params->{'usevisibilitygroups'}) {
+        @visible_from_current  = @{$group->grant_direct(GROUP_VISIBLE)};
+        @visible_to_me_current = @{$group->granted_by_direct(GROUP_VISIBLE)};
+    }
+
+    # Figure out what groups are not currently a member of this group,
+    # and what groups this group is not currently a member of.
+    my (@members_available, @member_of_available,
+        @bless_from_available, @bless_to_available,
+        @visible_from_available, @visible_to_me_available);
+    foreach my $group_option (@all_groups) {
+        if (Bugzilla->params->{'usevisibilitygroups'}) {
+            push(@visible_from_available, $group_option)
+                if !grep($_->id == $group_option->id, @visible_from_current);
+            push(@visible_to_me_available, $group_option)
+                if !grep($_->id == $group_option->id, @visible_to_me_current);
+        }
+
+        # The group itself should never show up in the bless or 
+        # membership lists.
+        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);
+    }
+
+    $vars->{'members_current'}     = \@members_current;
+    $vars->{'members_available'}   = \@members_available;
+    $vars->{'member_of_current'}   = \@member_of_current;
+    $vars->{'member_of_available'} = \@member_of_available;
+
+    $vars->{'bless_from_current'}   = \@bless_from_current;
+    $vars->{'bless_from_available'} = \@bless_from_available;
+    $vars->{'bless_to_current'}     = \@bless_to_current;
+    $vars->{'bless_to_available'}   = \@bless_to_available;
+
+    if (Bugzilla->params->{'usevisibilitygroups'}) {
+        $vars->{'visible_from_current'}    = \@visible_from_current;
+        $vars->{'visible_from_available'}  = \@visible_from_available;
+        $vars->{'visible_to_me_current'}   = \@visible_to_me_current;
+        $vars->{'visible_to_me_available'} = \@visible_to_me_available;
+    }
+}
+
 # If no action is specified, get a list of all groups available.
 
 unless ($action) {
@@ -169,62 +201,10 @@
 if ($action eq 'changeform') {
     # Check that an existing group ID is given
     my $group_id = CheckGroupID($cgi->param('group'));
-    my ($name, $description, $regexp, $isactive, $isbuggroup) =
-        $dbh->selectrow_array("SELECT name, description, userregexp, " .
-                              "isactive, isbuggroup " .
-                              "FROM groups WHERE id = ?", undef, $group_id);
+    my $group = new Bugzilla::Group($group_id);
 
-    # For each group, we use left joins to establish the existence of
-    # a record making that group a member of this group
-    # and the existence of a record permitting that group to bless
-    # this one
-
-    my @groups;
-    my $group_list =
-      $dbh->selectall_arrayref('SELECT groups.id, groups.name, groups.description,
-                                       CASE WHEN group_group_map.member_id IS NOT NULL
-                                            THEN 1 ELSE 0 END,
-                                       CASE WHEN B.member_id IS NOT NULL
-                                            THEN 1 ELSE 0 END,
-                                       CASE WHEN C.member_id IS NOT NULL
-                                            THEN 1 ELSE 0 END
-                                  FROM groups
-                                  LEFT JOIN group_group_map
-                                    ON group_group_map.member_id = groups.id
-                                   AND group_group_map.grantor_id = ?
-                                   AND group_group_map.grant_type = ?
-                                  LEFT JOIN group_group_map as B
-                                    ON B.member_id = groups.id
-                                   AND B.grantor_id = ?
-                                   AND B.grant_type = ?
-                                  LEFT JOIN group_group_map as C
-                                    ON C.member_id = groups.id
-                                   AND C.grantor_id = ?
-                                   AND C.grant_type = ?
-                                 ORDER by name',
-                                undef, ($group_id, GROUP_MEMBERSHIP,
-                                        $group_id, GROUP_BLESS,
-                                        $group_id, GROUP_VISIBLE));
-
-    foreach (@$group_list) {
-        my ($grpid, $grpnam, $grpdesc, $grpmember, $blessmember, $membercansee) = @$_;
-        my $group = {};
-        $group->{'grpid'}       = $grpid;
-        $group->{'grpnam'}      = $grpnam;
-        $group->{'grpdesc'}     = $grpdesc;
-        $group->{'grpmember'}   = $grpmember;
-        $group->{'blessmember'} = $blessmember;
-        $group->{'membercansee'}= $membercansee;
-        push(@groups, $group);
-    }
-
-    $vars->{'group_id'}    = $group_id;
-    $vars->{'name'}        = $name;
-    $vars->{'description'} = $description;
-    $vars->{'regexp'}      = $regexp;
-    $vars->{'isactive'}    = $isactive;
-    $vars->{'isbuggroup'}  = $isbuggroup;
-    $vars->{'groups'}      = \@groups;
+    get_current_and_available($group, $vars);
+    $vars->{'group'} = $group;
     $vars->{'token'}       = issue_session_token('edit_group');
 
     print $cgi->header();
@@ -264,14 +244,20 @@
     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)
-              VALUES (?, ?, 1, ?, ?)',
-              undef, ($name, $desc, $regexp, $isactive));
+              (name, description, isbuggroup, userregexp, isactive, icon_url)
+              VALUES (?, ?, 1, ?, ?, ?)',
+              undef, ($name, $desc, $regexp, $isactive, $icon_url));
 
-    my $gid = $dbh->bz_last_key('groups', 'id');
+    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.
@@ -279,9 +265,9 @@
                              (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);
+    $sth->execute($admin, $group->id, GROUP_MEMBERSHIP);
+    $sth->execute($admin, $group->id, GROUP_BLESS);
+    $sth->execute($admin, $group->id, GROUP_VISIBLE);
 
     # Permit all existing products to use the new group if makeproductgroups.
     if ($cgi->param('insertnew')) {
@@ -289,13 +275,18 @@
                   (group_id, product_id, entry, membercontrol,
                    othercontrol, canedit)
                   SELECT ?, products.id, 0, ?, ?, 0 FROM products',
-                  undef, ($gid, CONTROLMAPSHOWN, CONTROLMAPNA));
+                  undef, ($group->id, CONTROLMAPSHOWN, CONTROLMAPNA));
     }
-    Bugzilla::Group::RederiveRegexp($regexp, $gid);
+    Bugzilla::Group::RederiveRegexp($regexp, $group->id);
     delete_token($token);
 
+    $vars->{'message'} = 'group_created';
+    $vars->{'group'} = $group;
+    get_current_and_available($group, $vars);
+    $vars->{'token'} = issue_session_token('edit_group');
+
     print $cgi->header();
-    $template->process("admin/groups/created.html.tmpl", $vars)
+    $template->process("admin/groups/edit.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
 }
@@ -468,10 +459,12 @@
 
     delete_token($token);
 
-    print $cgi->header();
-    $template->process("admin/groups/deleted.html.tmpl", $vars)
-      || ThrowTemplateError($template->error());
+    $vars->{'message'} = 'group_deleted';
+    $vars->{'groups'} = [Bugzilla::Group->get_all];
 
+    print $cgi->header();
+    $template->process("admin/groups/list.html.tmpl", $vars)
+      || ThrowTemplateError($template->error());
     exit;
 }
 
@@ -481,85 +474,67 @@
 
 if ($action eq 'postchanges') {
     check_token_data($token, 'edit_group');
-    # ZLL: Bug 181589: we need to have something to remove explicitly listed users from
-    # groups in order for the conversion to 2.18 groups to work
-    my $action;
-
-    if ($cgi->param('remove_explicit_members')) {
-        $action = 1;
-    } elsif ($cgi->param('remove_explicit_members_regexp')) {
-        $action = 2;
-    } else {
-        $action = 3;
-    }
-    
-    my ($gid, $chgs, $name, $regexp) = doGroupChanges();
-    
-    $vars->{'action'}  = $action;
-    $vars->{'changes'} = $chgs;
-    $vars->{'gid'}     = $gid;
-    $vars->{'name'}    = $name;
-    if ($action == 2) {
-        $vars->{'regexp'} = $regexp;
-    }
+    my $changes = doGroupChanges();
     delete_token($token);
 
+    my $group = new Bugzilla::Group($cgi->param('group_id'));
+    get_current_and_available($group, $vars);
+    $vars->{'message'} = 'group_updated';
+    $vars->{'group'}   = $group;
+    $vars->{'changes'} = $changes;
+    $vars->{'token'} = issue_session_token('edit_group');
+
     print $cgi->header();
-    $template->process("admin/groups/change.html.tmpl", $vars)
+    $template->process("admin/groups/edit.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
 }
 
-if (($action eq 'remove_all_regexp') || ($action eq 'remove_all')) {
+if ($action eq 'confirm_remove') {
+    my $group = new Bugzilla::Group(CheckGroupID($cgi->param('group_id')));
+    $vars->{'group'} = $group;
+    $vars->{'regexp'} = CheckGroupRegexp($cgi->param('regexp'));
+    $vars->{'token'} = issue_session_token('remove_group_members');
+    $template->process('admin/groups/confirm-remove.html.tmpl', $vars)
+        || ThrowTemplateError($template->error());
+    exit;
+}
+
+if ($action eq 'remove_regexp') {
+    check_token_data($token, 'remove_group_members');
     # remove all explicit users from the group with
     # gid = $cgi->param('group') that match the regular expression
     # stored in the DB for that group or all of them period
 
-    my $gid = CheckGroupID($cgi->param('group'));
+    my $group  = new Bugzilla::Group(CheckGroupID($cgi->param('group_id')));
+    my $regexp = CheckGroupRegexp($cgi->param('regexp'));
 
-    my ($name, $regexp) =
-      $dbh->selectrow_array('SELECT name, userregexp FROM groups
-                             WHERE id = ?', undef, $gid);
+    $dbh->bz_start_transaction();
 
-    $dbh->bz_lock_tables('groups WRITE', 'profiles READ',
-                         'user_group_map WRITE');
+    my $users = $group->members_direct();
+    my $sth_delete = $dbh->prepare(
+        "DELETE FROM user_group_map
+           WHERE user_id = ? AND isbless = 0 AND group_id = ?");
 
-    my $sth = $dbh->prepare("SELECT user_group_map.user_id, profiles.login_name
-                               FROM user_group_map
-                         INNER JOIN profiles
-                                 ON user_group_map.user_id = profiles.userid
-                              WHERE user_group_map.group_id = ?
-                                AND grant_type = ?
-                                AND isbless = 0");
-    $sth->execute($gid, GRANT_DIRECT);
-
-    my @users;
-    my $sth2 = $dbh->prepare("DELETE FROM user_group_map
-                              WHERE user_id = ?
-                              AND isbless = 0
-                              AND group_id = ?");
-
-    while ( my ($userid, $userlogin) = $sth->fetchrow_array() ) {
-        if ((($regexp =~ /\S/) && ($userlogin =~ m/$regexp/i))
-            || ($action eq 'remove_all'))
-        {
-            $sth2->execute($userid, $gid);
-
-            my $user = {};
-            $user->{'login'} = $userlogin;
-            push(@users, $user);
+    my @deleted;
+    foreach my $member (@$users) {
+        if ($regexp eq '' || $member->login =~ m/$regexp/i) {
+            $sth_delete->execute($member->id, $group->id);
+            push(@deleted, $member);
         }
     }
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
-    $vars->{'users'}      = \@users;
-    $vars->{'name'}       = $name;
-    $vars->{'regexp'}     = $regexp;
-    $vars->{'remove_all'} = ($action eq 'remove_all');
-    $vars->{'gid'}        = $gid;
-    
+    $vars->{'users'}  = \@deleted;
+    $vars->{'regexp'} = $regexp;
+    delete_token($token);
+
+    $vars->{'message'} = 'group_membership_removed';
+    $vars->{'group'} = $group->name;
+    $vars->{'groups'} = [Bugzilla::Group->get_all];
+
     print $cgi->header();
-    $template->process("admin/groups/remove.html.tmpl", $vars)
+    $template->process("admin/groups/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
 
     exit;
@@ -578,124 +553,108 @@
     my $cgi = Bugzilla->cgi;
     my $dbh = Bugzilla->dbh;
 
-    $dbh->bz_lock_tables('groups WRITE', 'group_group_map WRITE',
-                         'bug_group_map WRITE', 'user_group_map WRITE',
-                         'group_control_map READ', 'bugs READ', 'profiles READ',
-                         # Due to the way Bugzilla::Config::BugFields::get_param_list()
-                         # works, we need to lock these tables too.
-                         'priority READ', 'bug_severity READ', 'rep_platform READ',
-                         'op_sys READ');
+    $dbh->bz_start_transaction();
 
-    # Check that the given group ID and regular expression are valid.
-    # If tests are successful, trimmed values are returned by CheckGroup*.
-    my $gid = CheckGroupID($cgi->param('group'));
-    my $regexp = CheckGroupRegexp($cgi->param('regexp'));
+    # Check that the given group ID is valid and make a Group.
+    my $group = new Bugzilla::Group(CheckGroupID($cgi->param('group_id')));
 
-    # The name and the description of system groups cannot be edited.
-    # We then need to know if the group being edited is a system group.
-    my $isbuggroup = $dbh->selectrow_array('SELECT isbuggroup FROM groups
-                                            WHERE id = ?', undef, $gid);
-    my $name;
-    my $desc;
-    my $isactive;
-    my $chgs = 0;
-
-    # We trust old values given by the template. If they are hacked
-    # in a way that some of the tests below become negative, the
-    # corresponding attributes are not updated in the DB, which does
-    # not hurt.
-    if ($isbuggroup) {
-        # Check that the group name and its description are valid
-        # and return trimmed values if tests are successful.
-        $name = CheckGroupName($cgi->param('name'), $gid);
-        $desc = CheckGroupDesc($cgi->param('desc'));
-        $isactive = $cgi->param('isactive') ? 1 : 0;
-
-        if ($name ne $cgi->param('oldname')) {
-            $chgs = 1;
-            $dbh->do('UPDATE groups SET name = ? WHERE id = ?',
-                      undef, ($name, $gid));
-            # If the group is used by some parameters, we have to update
-            # these parameters too.
-            my $update_params = 0;
-            foreach my $group (SPECIAL_GROUPS) {
-                if ($cgi->param('oldname') eq Bugzilla->params->{$group}) {
-                    SetParam($group, $name);
-                    $update_params = 1;
-                }
-            }
-            write_params() if $update_params;
-        }
-        if ($desc ne $cgi->param('olddesc')) {
-            $chgs = 1;
-            $dbh->do('UPDATE groups SET description = ? WHERE id = ?',
-                      undef, ($desc, $gid));
-        }
-        if ($isactive ne $cgi->param('oldisactive')) {
-            $chgs = 1;
-            $dbh->do('UPDATE groups SET isactive = ? WHERE id = ?',
-                      undef, ($isactive, $gid));
-            # If the group was mandatory for some products before
-            # we deactivated it and we now activate this group again,
-            # we have to add all bugs created while this group was
-            # disabled in bug_group_map to correctly protect them.
-            if ($isactive) { fix_bug_permissions($gid); }
-        }
-    }
-    if ($regexp ne $cgi->param('oldregexp')) {
-        $chgs = 1;
-        $dbh->do('UPDATE groups SET userregexp = ? WHERE id = ?',
-                  undef, ($regexp, $gid));
-        Bugzilla::Group::RederiveRegexp($regexp, $gid);
+    if (defined $cgi->param('regexp')) {
+        $group->set_user_regexp($cgi->param('regexp'));
     }
 
-    my $sthInsert = $dbh->prepare('INSERT INTO group_group_map
-                                   (member_id, grantor_id, grant_type)
-                                   VALUES (?, ?, ?)');
-
-    my $sthDelete = $dbh->prepare('DELETE FROM group_group_map
-                                    WHERE member_id = ?
-                                      AND grantor_id = ?
-                                      AND grant_type = ?');
-
-    foreach my $b (grep {/^oldgrp-\d*$/} $cgi->param()) {
-        if (defined($cgi->param($b))) {
-            $b =~ /^oldgrp-(\d+)$/;
-            my $v = $1;
-            my $grp = $cgi->param("grp-$v") || 0;
-            if (($v != $gid) && ($cgi->param("oldgrp-$v") != $grp)) {
-                $chgs = 1;
-                if ($grp != 0) {
-                    $sthInsert->execute($v, $gid, GROUP_MEMBERSHIP);
-                } else {
-                    $sthDelete->execute($v, $gid, GROUP_MEMBERSHIP);
-                }
-            }
-
-            my $bless = $cgi->param("bless-$v") || 0;
-            my $oldbless = $cgi->param("oldbless-$v");
-            if ((defined $oldbless) and ($oldbless != $bless)) {
-                $chgs = 1;
-                if ($bless != 0) {
-                    $sthInsert->execute($v, $gid, GROUP_BLESS);
-                } else {
-                    $sthDelete->execute($v, $gid, GROUP_BLESS);
-                }
-            }
-
-            my $cansee = $cgi->param("cansee-$v") || 0;
-            if (Bugzilla->params->{"usevisibilitygroups"} 
-               && ($cgi->param("oldcansee-$v") != $cansee)) {
-                $chgs = 1;
-                if ($cansee != 0) {
-                    $sthInsert->execute($v, $gid, GROUP_VISIBLE);
-                } else {
-                    $sthDelete->execute($v, $gid, GROUP_VISIBLE);
-                }
-            }
-
+    if ($group->is_bug_group) {
+        if (defined $cgi->param('name')) {
+            $group->set_name($cgi->param('name'));
+        }
+        if (defined $cgi->param('desc')) {
+            $group->set_description($cgi->param('desc'));
+        }
+        # Only set isactive if we came from the right form.
+        if (defined $cgi->param('regexp')) {
+            $group->set_is_active($cgi->param('isactive'));
         }
     }
-    $dbh->bz_unlock_tables();
-    return $gid, $chgs, $name, $regexp;
+
+    if (defined $cgi->param('icon_url')) {
+        $group->set_icon_url($cgi->param('icon_url'));
+    }
+
+    my $changes = $group->update();
+
+    my $sth_insert = $dbh->prepare('INSERT INTO group_group_map
+                                    (member_id, grantor_id, grant_type)
+                                    VALUES (?, ?, ?)');
+
+    my $sth_delete = $dbh->prepare('DELETE FROM group_group_map
+                                     WHERE member_id = ?
+                                           AND grantor_id = ?
+                                           AND grant_type = ?');
+
+    # First item is the type, second is whether or not it's "reverse" 
+    # (granted_by) (see _do_add for more explanation).
+    my %fields = (
+        members       => [GROUP_MEMBERSHIP, 0],
+        bless_from    => [GROUP_BLESS, 0],
+        visible_from  => [GROUP_VISIBLE, 0],
+        member_of     => [GROUP_MEMBERSHIP, 1],
+        bless_to      => [GROUP_BLESS, 1],
+        visible_to_me => [GROUP_VISIBLE, 1]
+    );
+    while (my ($field, $data) = each %fields) {
+        _do_add($group, $changes, $sth_insert, "${field}_add", 
+                $data->[0], $data->[1]);
+        _do_remove($group, $changes, $sth_delete, "${field}_remove",
+                   $data->[0], $data->[1]);
+    }
+
+    $dbh->bz_commit_transaction();
+    return $changes;
+}
+
+sub _do_add {
+    my ($group, $changes, $sth_insert, $field, $type, $reverse) = @_;
+    my $cgi = Bugzilla->cgi;
+
+    my $current;
+    # $reverse means we're doing a granted_by--that is, somebody else
+    # is granting us something.
+    if ($reverse) {
+        $current = $group->granted_by_direct($type);
+    }
+    else {
+        $current = $group->grant_direct($type);
+    }
+
+    my $add_items = Bugzilla::Group->new_from_list([$cgi->param($field)]);
+
+    foreach my $add (@$add_items) {
+        next if grep($_->id == $add->id, @$current);
+
+        $changes->{$field} ||= [];
+        push(@{$changes->{$field}}, $add->name);
+        # They go this direction for a normal "This group is granting
+        # $add something."
+        my @ids = ($add->id, $group->id);
+        # But they get reversed for "This group is being granted something
+        # by $add."
+        @ids = reverse @ids if $reverse;
+        $sth_insert->execute(@ids, $type);
+    }
+}
+
+sub _do_remove {
+    my ($group, $changes, $sth_delete, $field, $type, $reverse) = @_;
+    my $cgi = Bugzilla->cgi;
+    my $remove_items = Bugzilla::Group->new_from_list([$cgi->param($field)]);
+
+    foreach my $remove (@$remove_items) {
+        my @ids = ($remove->id, $group->id);
+        # See _do_add for an explanation of $reverse
+        @ids = reverse @ids if $reverse;
+        # Deletions always succeed and are harmless if they fail, so we
+        # don't need to do any checks.
+        $sth_delete->execute(@ids, $type);
+        $changes->{$field} ||= [];
+        push(@{$changes->{$field}}, $remove->name);
+    }
 }
diff --git a/BugsSite/editkeywords.cgi b/BugsSite/editkeywords.cgi
index 3aca22e..2e65eb9 100755
--- a/BugsSite/editkeywords.cgi
+++ b/BugsSite/editkeywords.cgi
@@ -21,7 +21,7 @@
 # Contributor(s): Terry Weissman <terry@mozilla.org>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -92,14 +92,15 @@
 
     print $cgi->header();
 
+    $vars->{'message'} = 'keyword_created';
     $vars->{'name'} = $keyword->name;
-    $template->process("admin/keywords/created.html.tmpl", $vars)
-      || ThrowTemplateError($template->error());
+    $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
 
+    $template->process("admin/keywords/list.html.tmpl", $vars)
+      || ThrowTemplateError($template->error());
     exit;
 }
 
-    
 
 #
 # action='edit' -> present the edit keywords from
@@ -132,39 +133,39 @@
 
     $keyword->set_name($cgi->param('name'));
     $keyword->set_description($cgi->param('description'));
-    $keyword->update();
+    my $changes = $keyword->update();
 
     delete_token($token);
 
     print $cgi->header();
 
+    $vars->{'message'} = 'keyword_updated';
     $vars->{'keyword'} = $keyword;
-    $template->process("admin/keywords/rebuild-cache.html.tmpl", $vars)
-      || ThrowTemplateError($template->error());
+    $vars->{'changes'} = $changes;
+    $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
 
+    $template->process("admin/keywords/list.html.tmpl", $vars)
+      || ThrowTemplateError($template->error());
     exit;
 }
 
-
-if ($action eq 'delete') {
+if ($action eq 'del') {
     my $keyword =  new Bugzilla::Keyword($key_id)
         || ThrowCodeError('invalid_keyword_id', { id => $key_id });
 
     $vars->{'keyword'} = $keyword;
+    $vars->{'token'} = issue_session_token('delete_keyword');
 
-    # We need this token even if there is no bug using this keyword.
-    $token = issue_session_token('delete_keyword');
+    print $cgi->header();
+    $template->process("admin/keywords/confirm-delete.html.tmpl", $vars)
+      || ThrowTemplateError($template->error());
+    exit;
+}
 
-    if (!$cgi->param('reallydelete') && $keyword->bug_count) {
-        $vars->{'token'} = $token;
-
-        print $cgi->header();
-        $template->process("admin/keywords/confirm-delete.html.tmpl", $vars)
-            || ThrowTemplateError($template->error());
-        exit;
-    }
-    # We cannot do this check earlier as we have to check 'reallydelete' first.
+if ($action eq 'delete') {
     check_token_data($token, 'delete_keyword');
+    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);
@@ -173,9 +174,11 @@
 
     print $cgi->header();
 
-    $template->process("admin/keywords/rebuild-cache.html.tmpl", $vars)
-      || ThrowTemplateError($template->error());
+    $vars->{'message'} = 'keyword_deleted';
+    $vars->{'keywords'} = Bugzilla::Keyword->get_all_with_bug_count();
 
+    $template->process("admin/keywords/list.html.tmpl", $vars)
+      || ThrowTemplateError($template->error());
     exit;
 }
 
diff --git a/BugsSite/editmilestones.cgi b/BugsSite/editmilestones.cgi
index 17733bd..a5f0c3d 100755
--- a/BugsSite/editmilestones.cgi
+++ b/BugsSite/editmilestones.cgi
@@ -1,43 +1,47 @@
 #!/usr/bin/perl -wT
 # -*- Mode: perl; indent-tabs-mode: nil -*-
-
 #
-# This is a script to edit the target milestones. It is largely a copy of
-# the editversions.cgi script, since the two fields were set up in a
-# very similar fashion.
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
 #
-# (basically replace each occurrence of 'milestone' with 'version', and
-# you'll have the original script)
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
 #
-# Matt Masson <matthew@zeroknowledge.com>
+# The Initial Developer of the Original Code is Matt Masson.
+# Portions created by Matt Masson are Copyright (C) 2000 Matt Masson.
+# All Rights Reserved.
 #
-# Contributors : Gavin Shelley <bugzilla@chimpychompy.org>
+# Contributors : Matt Masson <matthew@zeroknowledge.com>
+#                Gavin Shelley <bugzilla@chimpychompy.org>
 #                Frédéric Buclin <LpSolit@gmail.com>
-#
-
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
 use Bugzilla::Util;
 use Bugzilla::Error;
 use Bugzilla::Milestone;
-use Bugzilla::Bug;
 use Bugzilla::Token;
 
 my $cgi = Bugzilla->cgi;
 my $dbh = Bugzilla->dbh;
 my $template = Bugzilla->template;
 my $vars = {};
+# There is only one section about milestones in the documentation,
+# so all actions point to the same page.
+$vars->{'doc_section'} = 'milestones.html';
 
 #
 # Preliminary checks:
 #
 
 my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $whoid = $user->id;
 
 print $cgi->header();
 
@@ -86,16 +90,11 @@
 
     $vars->{'showbugcounts'} = $showbugcounts;
     $vars->{'product'} = $product;
-    $template->process("admin/milestones/list.html.tmpl",
-                       $vars)
+    $template->process("admin/milestones/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
-
-
-
 #
 # action='add' -> present form for parameters for new milestone
 #
@@ -105,62 +104,30 @@
 if ($action eq 'add') {
     $vars->{'token'} = issue_session_token('add_milestone');
     $vars->{'product'} = $product;
-    $template->process("admin/milestones/create.html.tmpl",
-                       $vars)
+    $template->process("admin/milestones/create.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
-
-
 #
 # action='new' -> add milestone entered in the 'action=add' screen
 #
 
 if ($action eq 'new') {
     check_token_data($token, 'add_milestone');
-    $milestone_name || ThrowUserError('milestone_blank_name');
-
-    if (length($milestone_name) > 20) {
-        ThrowUserError('milestone_name_too_long',
-                       {'name' => $milestone_name});
-    }
-
-    $sortkey = Bugzilla::Milestone::check_sort_key($milestone_name,
-                                                   $sortkey);
-
-    my $milestone = new Bugzilla::Milestone(
-        { product => $product, name => $milestone_name });
-
-    if ($milestone) {
-        ThrowUserError('milestone_already_exists',
-                       {'name' => $milestone->name,
-                        'product' => $product->name});
-    }
-
-    # Add the new milestone
-    trick_taint($milestone_name);
-    $dbh->do('INSERT INTO milestones ( value, product_id, sortkey )
-              VALUES ( ?, ?, ? )',
-             undef, $milestone_name, $product->id, $sortkey);
-
-    $milestone = new Bugzilla::Milestone(
-        { product => $product, name => $milestone_name });
+    my $milestone = Bugzilla::Milestone->create({ name    => $milestone_name,
+                                                  product => $product,
+                                                  sortkey => $sortkey });
     delete_token($token);
 
+    $vars->{'message'} = 'milestone_created';
     $vars->{'milestone'} = $milestone;
     $vars->{'product'} = $product;
-    $template->process("admin/milestones/created.html.tmpl",
-                       $vars)
+    $template->process("admin/milestones/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
-
-
-
 #
 # action='del' -> ask if user really wants to delete
 #
@@ -168,15 +135,15 @@
 #
 
 if ($action eq 'del') {
-    my $milestone = Bugzilla::Milestone::check_milestone($product,
-                                                         $milestone_name);
+    my $milestone = Bugzilla::Milestone->check({ product => $product,
+                                                 name    => $milestone_name });
     
     $vars->{'milestone'} = $milestone;
     $vars->{'product'} = $product;
 
     # The default milestone cannot be deleted.
     if ($product->default_milestone eq $milestone->name) {
-        ThrowUserError("milestone_is_default", $vars);
+        ThrowUserError("milestone_is_default", { milestone => $milestone });
     }
     $vars->{'token'} = issue_session_token('delete_milestone');
 
@@ -185,58 +152,27 @@
     exit;
 }
 
-
-
 #
 # action='delete' -> really delete the milestone
 #
 
 if ($action eq 'delete') {
     check_token_data($token, 'delete_milestone');
-    my $milestone =
-        Bugzilla::Milestone::check_milestone($product,
-                                             $milestone_name);
-    $vars->{'milestone'} = $milestone;
-    $vars->{'product'} = $product;
-
-    # The default milestone cannot be deleted.
-    if ($milestone->name eq $product->default_milestone) {
-        ThrowUserError("milestone_is_default", $vars);
-    }
-
-    if ($milestone->bug_count) {
-        # We don't want to delete bugs when deleting a milestone.
-        # Bugs concerned are reassigned to the default milestone.
-        my $bug_ids =
-          $dbh->selectcol_arrayref("SELECT bug_id FROM bugs
-                                    WHERE product_id = ? AND target_milestone = ?",
-                                    undef, ($product->id, $milestone->name));
-        my $timestamp = $dbh->selectrow_array("SELECT NOW()");
-        foreach my $bug_id (@$bug_ids) {
-            $dbh->do("UPDATE bugs SET target_milestone = ?,
-                      delta_ts = ? WHERE bug_id = ?",
-                      undef, ($product->default_milestone, $timestamp,
-                              $bug_id));
-            # We have to update the 'bugs_activity' table too.
-            LogActivityEntry($bug_id, 'target_milestone',
-                             $milestone->name,
-                             $product->default_milestone,
-                             $whoid, $timestamp);
-        }
-    }
-
-    $dbh->do("DELETE FROM milestones WHERE product_id = ? AND value = ?",
-             undef, ($product->id, $milestone->name));
-
+    my $milestone = Bugzilla::Milestone->check({ product => $product,
+                                                 name    => $milestone_name });
+    $milestone->remove_from_db;
     delete_token($token);
 
-    $template->process("admin/milestones/deleted.html.tmpl", $vars)
+    $vars->{'message'} = 'milestone_deleted';
+    $vars->{'milestone'} = $milestone;
+    $vars->{'product'} = $product;
+    $vars->{'no_edit_milestone_link'} = 1;
+
+    $template->process("admin/milestones/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
     exit;
 }
 
-
-
 #
 # action='edit' -> present the edit milestone form
 #
@@ -245,23 +181,18 @@
 
 if ($action eq 'edit') {
 
-    my $milestone =
-        Bugzilla::Milestone::check_milestone($product,
-                                             $milestone_name);
+    my $milestone = Bugzilla::Milestone->check({ product => $product,
+                                                 name    => $milestone_name });
 
     $vars->{'milestone'} = $milestone;
     $vars->{'product'} = $product;
     $vars->{'token'} = issue_session_token('edit_milestone');
 
-    $template->process("admin/milestones/edit.html.tmpl",
-                       $vars)
+    $template->process("admin/milestones/edit.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
-
-
 #
 # action='update' -> update the milestone
 #
@@ -269,95 +200,24 @@
 if ($action eq 'update') {
     check_token_data($token, 'edit_milestone');
     my $milestone_old_name = trim($cgi->param('milestoneold') || '');
-    my $milestone_old =
-        Bugzilla::Milestone::check_milestone($product,
-                                             $milestone_old_name);
+    my $milestone = Bugzilla::Milestone->check({ product => $product,
+                                                 name    => $milestone_old_name });
 
-    if (length($milestone_name) > 20) {
-        ThrowUserError('milestone_name_too_long',
-                       {'name' => $milestone_name});
-    }
+    $milestone->set_name($milestone_name);
+    $milestone->set_sortkey($sortkey);
+    my $changes = $milestone->update();
 
-    $dbh->bz_lock_tables('bugs WRITE',
-                         'milestones WRITE',
-                         'products WRITE');
-
-    if ($sortkey ne $milestone_old->sortkey) {
-        $sortkey = Bugzilla::Milestone::check_sort_key($milestone_name,
-                                                       $sortkey);
-
-        $dbh->do('UPDATE milestones SET sortkey = ?
-                  WHERE product_id = ?
-                  AND value = ?',
-                 undef,
-                 $sortkey,
-                 $product->id,
-                 $milestone_old->name);
-
-        $vars->{'updated_sortkey'} = 1;
-    }
-
-    if ($milestone_name ne $milestone_old->name) {
-        unless ($milestone_name) {
-            ThrowUserError('milestone_blank_name');
-        }
-        my $milestone = new Bugzilla::Milestone(
-            { product => $product, name => $milestone_name });
-        if ($milestone) {
-            ThrowUserError('milestone_already_exists',
-                           {'name' => $milestone->name,
-                            'product' => $product->name});
-        }
-
-        trick_taint($milestone_name);
-
-        $dbh->do('UPDATE bugs
-                  SET target_milestone = ?
-                  WHERE target_milestone = ?
-                  AND product_id = ?',
-                 undef,
-                 $milestone_name,
-                 $milestone_old->name,
-                 $product->id);
-
-        $dbh->do("UPDATE milestones
-                  SET value = ?
-                  WHERE product_id = ?
-                  AND value = ?",
-                 undef,
-                 $milestone_name,
-                 $product->id,
-                 $milestone_old->name);
-
-        $dbh->do("UPDATE products
-                  SET defaultmilestone = ?
-                  WHERE id = ?
-                  AND defaultmilestone = ?",
-                 undef,
-                 $milestone_name,
-                 $product->id,
-                 $milestone_old->name);
-
-        $vars->{'updated_name'} = 1;
-    }
-
-    $dbh->bz_unlock_tables();
-
-    my $milestone =
-        Bugzilla::Milestone::check_milestone($product,
-                                             $milestone_name);
     delete_token($token);
 
+    $vars->{'message'} = 'milestone_updated';
     $vars->{'milestone'} = $milestone;
     $vars->{'product'} = $product;
-    $template->process("admin/milestones/updated.html.tmpl",
-                       $vars)
+    $vars->{'changes'} = $changes;
+    $template->process("admin/milestones/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
-
     exit;
 }
 
-
 #
 # No valid action found
 #
diff --git a/BugsSite/editparams.cgi b/BugsSite/editparams.cgi
index b4100a0..2fd30e9 100755
--- a/BugsSite/editparams.cgi
+++ b/BugsSite/editparams.cgi
@@ -23,17 +23,19 @@
 #                 Frédéric Buclin <LpSolit@gmail.com>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
 use Bugzilla::Config qw(:admin);
 use Bugzilla::Config::Common;
+use Bugzilla::Hook;
 use Bugzilla::Util;
 use Bugzilla::Error;
 use Bugzilla::Token;
 use Bugzilla::User;
 use Bugzilla::User::Setting;
+use Bugzilla::Status;
 
 my $user = Bugzilla->login(LOGIN_REQUIRED);
 my $cgi = Bugzilla->cgi;
@@ -55,13 +57,15 @@
 
 my $current_module;
 my @panels = ();
-foreach my $panel (Bugzilla::Config::param_panels()) {
-    eval("require Bugzilla::Config::$panel") || die $@;
-    my @module_param_list = "Bugzilla::Config::${panel}"->get_param_list(1);
+my $param_panels = Bugzilla::Config::param_panels();
+foreach my $panel (keys %$param_panels) {
+    my $module = $param_panels->{$panel};
+    eval("require $module") || die $@;
+    my @module_param_list = "$module"->get_param_list();
     my $item = { name => lc($panel),
                  current => ($current_panel eq lc($panel)) ? 1 : 0,
                  param_list => \@module_param_list,
-                 sortkey => eval "\$Bugzilla::Config::${panel}::sortkey;"
+                 sortkey => eval "\$${module}::sortkey;"
                };
     push(@panels, $item);
     $current_module = $panel if ($current_panel eq lc($panel));
@@ -72,9 +76,8 @@
 if ($action eq 'save' && $current_module) {
     check_token_data($token, 'edit_parameters');
     my @changes = ();
-    my @module_param_list = "Bugzilla::Config::${current_module}"->get_param_list(1);
+    my @module_param_list = "$param_panels->{$current_module}"->get_param_list();
 
-    my $update_lang_user_pref = 0;
     foreach my $i (@module_param_list) {
         my $name = $i->{'name'};
         my $value = $cgi->param($name);
@@ -134,21 +137,11 @@
             if (($name eq "shutdownhtml") && ($value ne "")) {
                 $vars->{'shutdown_is_active'} = 1;
             }
-            if ($name eq 'languages') {
-                $update_lang_user_pref = 1;
+            if ($name eq 'duplicate_or_move_bug_status') {
+                Bugzilla::Status::add_missing_bug_status_transitions($value);
             }
         }
     }
-    if ($update_lang_user_pref) {
-        # We have to update the list of languages users can choose.
-        # If some users have selected a language which is no longer available,
-        # then we delete it (the user pref is reset to the default one).
-        my @languages = split(/[\s,]+/, Bugzilla->params->{'languages'});
-        map {trick_taint($_)} @languages;
-        my $lang = Bugzilla->params->{'defaultlanguage'};
-        trick_taint($lang);
-        add_setting('lang', \@languages, $lang, undef, 1);
-    }
 
     $vars->{'message'} = 'parameters_updated';
     $vars->{'param_changed'} = \@changes;
diff --git a/BugsSite/editproducts.cgi b/BugsSite/editproducts.cgi
index 9be85de..cb451b0 100755
--- a/BugsSite/editproducts.cgi
+++ b/BugsSite/editproducts.cgi
@@ -26,13 +26,10 @@
 #               Frédéric Buclin <LpSolit@gmail.com>
 #               Greg Hendricks <ghendricks@novell.com>
 #               Lance Larsh <lance.larsh@oracle.com>
-#
-# Direct any questions on this source code to
-#
-# Holger Schurig <holgerschurig@nikocity.de>
+#               Elliotte Martin <elliotte.martin@yahoo.com>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -48,6 +45,7 @@
 use Bugzilla::User;
 use Bugzilla::Field;
 use Bugzilla::Token;
+use Bugzilla::Status;
 
 #
 # Preliminary checks:
@@ -60,6 +58,9 @@
 my $cgi = Bugzilla->cgi;
 my $template = Bugzilla->template;
 my $vars = {};
+# Remove this as soon as the documentation about products has been
+# improved and each action has its own section.
+$vars->{'doc_section'} = 'products.html';
 
 print $cgi->header();
 
@@ -87,8 +88,9 @@
     && !$classification_name
     && !$product_name)
 {
-    $vars->{'classifications'} = $user->get_selectable_classifications;
-    
+    $vars->{'classifications'} = $user->in_group('editcomponents') ?
+      [Bugzilla::Classification::get_all_classifications] : $user->get_selectable_classifications;
+
     $template->process("admin/products/list-classifications.html.tmpl", $vars)
         || ThrowTemplateError($template->error());
     exit;
@@ -335,9 +337,13 @@
     }
     delete_token($token);
 
+    $vars->{'message'} = 'product_created';
     $vars->{'product'} = $product;
+    $vars->{'classification'} = new Bugzilla::Classification($product->classification_id)
+      if Bugzilla->params->{'useclassification'};
+    $vars->{'token'} = issue_session_token('edit_product');
 
-    $template->process("admin/products/created.html.tmpl", $vars)
+    $template->process("admin/products/edit.html.tmpl", $vars)
         || ThrowTemplateError($template->error());
     exit;
 }
@@ -364,7 +370,9 @@
 
     $vars->{'product'} = $product;
     $vars->{'token'} = issue_session_token('delete_product');
-
+    
+    Bugzilla::Hook::process("product-confirm_delete", { vars => $vars });
+    
     $template->process("admin/products/confirm-delete.html.tmpl", $vars)
         || ThrowTemplateError($template->error());
     exit;
@@ -378,8 +386,6 @@
     my $product = $user->check_can_admin_product($product_name);
     check_token_data($token, 'delete_product');
 
-    $vars->{'product'} = $product;
-
     if (Bugzilla->params->{'useclassification'}) {
         my $classification = 
             Bugzilla::Classification::check_classification($classification_name);
@@ -406,10 +412,7 @@
         }
     }
 
-    $dbh->bz_lock_tables('products WRITE', 'components WRITE',
-                         'versions WRITE', 'milestones WRITE',
-                         'group_control_map WRITE', 'component_cc WRITE',
-                         'flaginclusions WRITE', 'flagexclusions WRITE');
+    $dbh->bz_start_transaction();
 
     my $comp_ids = $dbh->selectcol_arrayref('SELECT id FROM components
                                              WHERE product_id = ?',
@@ -439,12 +442,38 @@
     $dbh->do("DELETE FROM products WHERE id = ?",
              undef, $product->id);
 
-    $dbh->bz_unlock_tables();
+    $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};
 
     delete_token($token);
 
-    $template->process("admin/products/deleted.html.tmpl", $vars)
-        || ThrowTemplateError($template->error());
+    $vars->{'message'} = 'product_deleted';
+    $vars->{'product'} = $product;
+    $vars->{'no_edit_product_link'} = 1;
+
+    if (Bugzilla->params->{'useclassification'}) {
+        $vars->{'classifications'} = $user->in_group('editcomponents') ?
+          [Bugzilla::Classification::get_all_classifications] : $user->get_selectable_classifications;
+
+        $template->process("admin/products/list-classifications.html.tmpl", $vars)
+          || ThrowTemplateError($template->error());
+    }
+    else {
+        my $products = $user->get_selectable_products;
+        # If the user has editcomponents privs for some products only,
+        # we have to restrict the list of products to display.
+        unless ($user->in_group('editcomponents')) {
+            $products = $user->get_products_by_permission('editcomponents');
+        }
+        $vars->{'products'} = $products;
+
+        $template->process("admin/products/list.html.tmpl", $vars)
+          || ThrowTemplateError($template->error());
+    }
     exit;
 }
 
@@ -474,30 +503,11 @@
         }
         $vars->{'classification'} = $classification;
     }
-    my $group_controls = $product->group_controls;
-        
-    # Convert Group Controls(membercontrol and othercontrol) from 
-    # integer to string to display Membercontrol/Othercontrol names
-    # at the template. <gabriel@async.com.br>
-    my $constants = {
-        (CONTROLMAPNA) => 'NA',
-        (CONTROLMAPSHOWN) => 'Shown',
-        (CONTROLMAPDEFAULT) => 'Default',
-        (CONTROLMAPMANDATORY) => 'Mandatory'};
-
-    foreach my $group (keys(%$group_controls)) {
-        foreach my $control ('membercontrol', 'othercontrol') {
-            $group_controls->{$group}->{$control} = 
-                $constants->{$group_controls->{$group}->{$control}};
-        }
-    }
-    $vars->{'group_controls'} = $group_controls;
     $vars->{'product'} = $product;
     $vars->{'token'} = issue_session_token('edit_product');
 
     $template->process("admin/products/edit.html.tmpl", $vars)
         || ThrowTemplateError($template->error());
-
     exit;
 }
 
@@ -537,20 +547,28 @@
                    {'Slice' => {}}, $product->id);
         }
 
+#
+# return the mandatory groups which need to have bug entries added to the bug_group_map
+# and the corresponding bug count
+#
         my $mandatory_groups;
         if (@now_mandatory) {
             $mandatory_groups = $dbh->selectall_arrayref(
-                    'SELECT groups.name, COUNT(bugs.bug_id) AS count
-                       FROM bugs
-                  LEFT JOIN bug_group_map
-                         ON bug_group_map.bug_id = bugs.bug_id
-                 INNER JOIN groups
-                         ON bug_group_map.group_id = groups.id
-                      WHERE groups.id IN (' . join(', ', @now_mandatory) . ')
-                        AND bugs.product_id = ?
-                        AND bug_group_map.bug_id IS NULL ' .
-                       $dbh->sql_group_by('groups.name'),
+                    'SELECT groups.name,
+                           (SELECT COUNT(bugs.bug_id)
+                              FROM bugs
+                             WHERE bugs.product_id = ?
+                               AND bugs.bug_id NOT IN
+                                (SELECT bug_group_map.bug_id FROM bug_group_map
+                                  WHERE bug_group_map.group_id = groups.id))
+                           AS count
+                      FROM groups
+                     WHERE groups.id IN (' . join(', ', @now_mandatory) . ')
+                     ORDER BY groups.name',
                    {'Slice' => {}}, $product->id);
+            # remove zero counts
+            @$mandatory_groups = grep { $_->{count} } @$mandatory_groups;
+
         }
         if (($na_groups && scalar(@$na_groups))
             || ($mandatory_groups && scalar(@$mandatory_groups)))
@@ -586,12 +604,7 @@
                             {groupname => $groupname});
         }
     }
-    $dbh->bz_lock_tables('groups READ',
-                         'group_control_map WRITE',
-                         'bugs WRITE',
-                         'bugs_activity WRITE',
-                         'bug_group_map WRITE',
-                         'fielddefs READ');
+    $dbh->bz_start_transaction();
 
     my $sth_Insert = $dbh->prepare('INSERT INTO group_control_map
                                     (group_id, product_id, entry, membercontrol,
@@ -770,7 +783,7 @@
 
         push(@added_mandatory, \%group);
     }
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     delete_token($token);
 
@@ -846,7 +859,7 @@
                        {votestoconfirm => $stored_votestoconfirm});
     }
 
-    $dbh->bz_lock_tables('products WRITE', 'milestones READ');
+    $dbh->bz_start_transaction();
 
     my $testproduct = 
         new Bugzilla::Product({name => $product_name});
@@ -916,7 +929,7 @@
                  undef, ($product_name, $product_old->id));
     }
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     my $product = new Bugzilla::Product({name => $product_name});
 
@@ -939,10 +952,7 @@
                 my ($who, $id) = (@$vote);
                 # If some votes are removed, RemoveVotes() returns a list
                 # of messages to send to voters.
-                my $msgs =
-                    RemoveVotes($id, $who, "The rules for voting on this product " .
-                                           "has changed;\nyou had too many votes " .
-                                           "for a single bug.");
+                my $msgs = RemoveVotes($id, $who, 'votes_too_many_per_bug');
                 foreach my $msg (@$msgs) {
                     MessageToMTA($msg);
                 }
@@ -991,11 +1001,7 @@
                 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, "The rules for voting on this " .
-                                                   "product has changed; you had " .
-                                                   "too many\ntotal votes, so all " .
-                                                   "votes have been removed.");
+                    my $msgs = RemoveVotes($bug_id, $who, 'votes_too_many_per_user');
                     foreach my $msg (@$msgs) {
                         MessageToMTA($msg);
                     }
diff --git a/BugsSite/editsettings.cgi b/BugsSite/editsettings.cgi
index a4a8571..d375a3d 100755
--- a/BugsSite/editsettings.cgi
+++ b/BugsSite/editsettings.cgi
@@ -14,10 +14,10 @@
 # The Original Code is the Bugzilla Bug Tracking System.
 #
 # Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
-#
+#                 Frédéric Buclin <LpSolit@gmail.com>
 
 use strict;
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -27,51 +27,10 @@
 use Bugzilla::Token;
 
 my $template = Bugzilla->template;
-local our $vars = {};
-
-###############################
-###  Subroutine Definitions ###
-###############################
-
-sub LoadSettings {
-
-    $vars->{'settings'} = Bugzilla::User::Setting::get_defaults();
-
-    my @setting_list = keys %{$vars->{'settings'}};
-    $vars->{'setting_names'} = \@setting_list;
-}
-
-sub SaveSettings{
-
-    my $cgi = Bugzilla->cgi;
-
-    $vars->{'settings'} = Bugzilla::User::Setting::get_defaults();
-    my @setting_list = keys %{$vars->{'settings'}};
-
-    foreach my $name (@setting_list) {
-        my $changed = 0;
-        my $old_enabled = $vars->{'settings'}->{$name}->{'is_enabled'};
-        my $old_value   = $vars->{'settings'}->{$name}->{'default_value'};
-        my $enabled = defined $cgi->param("${name}-enabled") || 0;
-        my $value = $cgi->param("${name}");
-        my $setting = new Bugzilla::User::Setting($name);
-
-        $setting->validate_value($value);
-
-        if ( ($old_enabled != $enabled) ||
-             ($old_value ne $value) ) {
-            Bugzilla::User::Setting::set_default($name, $value, $enabled);
-        }
-    }
-}
-
-###################
-###  Live code  ###
-###################
-
 my $user = Bugzilla->login(LOGIN_REQUIRED);
-
 my $cgi = Bugzilla->cgi;
+my $vars = {};
+
 print $cgi->header;
 
 $user->in_group('tweakparams')
@@ -79,32 +38,36 @@
                                      action => "modify",
                                      object => "settings"});
 
-my $action  = trim($cgi->param('action')  || 'load');
-my $token   = $cgi->param('token');
+my $action = trim($cgi->param('action') || '');
+my $token = $cgi->param('token');
 
 if ($action eq 'update') {
     check_token_data($token, 'edit_settings');
-    SaveSettings();
+    my $settings = Bugzilla::User::Setting::get_defaults();
+    my $changed = 0;
+
+    foreach my $name (keys %$settings) {
+        my $old_enabled = $settings->{$name}->{'is_enabled'};
+        my $old_value = $settings->{$name}->{'default_value'};
+        my $enabled = defined $cgi->param("${name}-enabled") || 0;
+        my $value = $cgi->param("${name}");
+        my $setting = new Bugzilla::User::Setting($name);
+
+        $setting->validate_value($value);
+
+        if ($old_enabled != $enabled || $old_value ne $value) {
+            Bugzilla::User::Setting::set_default($name, $value, $enabled);
+            $changed = 1;
+        }
+    }
+    $vars->{'message'} = 'default_settings_updated';
+    $vars->{'changes_saved'} = $changed;
     delete_token($token);
-    $vars->{'changes_saved'} = 1;
-
-    $template->process("admin/settings/updated.html.tmpl", $vars)
-      || ThrowTemplateError($template->error());
-
-    exit;
 }
 
-if ($action eq 'load') {
-    LoadSettings();
-    $vars->{'token'} = issue_session_token('edit_settings');
+# Don't use $settings as defaults may have changed.
+$vars->{'settings'} = Bugzilla::User::Setting::get_defaults();
+$vars->{'token'} = issue_session_token('edit_settings');
 
-    $template->process("admin/settings/edit.html.tmpl", $vars)
-      || ThrowTemplateError($template->error());
-
-    exit;
-}
-
-#
-# No valid action found
-#
-ThrowUserError('no_valid_action', {'field' => "settings"});
+$template->process("admin/settings/edit.html.tmpl", $vars)
+  || ThrowTemplateError($template->error());
diff --git a/BugsSite/editusers.cgi b/BugsSite/editusers.cgi
index 0aca20d..b93f1c0 100755
--- a/BugsSite/editusers.cgi
+++ b/BugsSite/editusers.cgi
@@ -21,7 +21,7 @@
 #                 Gavin Shelley  <bugzilla@chimpychompy.org>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -170,7 +170,7 @@
 
     }
 
-    if ($matchtype eq 'exact' && scalar(@{$vars->{'users'}}) == 1) {
+    if ($matchtype && $matchtype eq 'exact' && scalar(@{$vars->{'users'}}) == 1) {
         my $match_user_id = $vars->{'users'}[0]->{'userid'};
         my $match_user = check_user($match_user_id);
         edit_processing($match_user);
@@ -227,15 +227,7 @@
     $otherUserID = $otherUser->id;
 
     # Lock tables during the check+update session.
-    $dbh->bz_lock_tables('profiles WRITE',
-                         'profiles_activity WRITE',
-                         'fielddefs READ',
-                         'tokens WRITE',
-                         'logincookies WRITE',
-                         'groups READ',
-                         'user_group_map WRITE',
-                         'group_group_map READ',
-                         'group_group_map AS ggm READ');
+    $dbh->bz_start_transaction();
  
     $editusers || $user->can_see_user($otherUser)
         || ThrowUserError('auth_failure', {reason => "not_visible",
@@ -282,15 +274,16 @@
     # silently.
     # XXX: checking for existence of each user_group_map entry
     #      would allow to display a friendlier error message on page reloads.
+    userDataToVars($otherUserID);
+    my $permissions = $vars->{'permissions'};
     foreach (@{$user->bless_groups()}) {
         my $id = $$_{'id'};
         my $name = $$_{'name'};
 
         # Change memberships.
-        my $oldgroupid = $cgi->param("oldgroup_$id") || '0';
-        my $groupid    = $cgi->param("group_$id")    || '0';
-        if ($groupid ne $oldgroupid) {
-            if ($groupid eq '0') {
+        my $groupid = $cgi->param("group_$id") || 0;
+        if ($groupid != $permissions->{$id}->{'directmember'}) {
+            if (!$groupid) {
                 $sth_remove_mapping->execute(
                     $otherUserID, $id, 0, GRANT_DIRECT);
                 push(@groupsRemovedFrom, $name);
@@ -304,10 +297,9 @@
         # Only members of the editusers group may change bless grants.
         # Skip silently if this is not the case.
         if ($editusers) {
-            my $oldgroupid = $cgi->param("oldbless_$id") || '0';
-            my $groupid    = $cgi->param("bless_$id")    || '0';
-            if ($groupid ne $oldgroupid) {
-                if ($groupid eq '0') {
+            my $groupid = $cgi->param("bless_$id") || 0;
+            if ($groupid != $permissions->{$id}->{'directbless'}) {
+                if (!$groupid) {
                     $sth_remove_mapping->execute(
                         $otherUserID, $id, 1, GRANT_DIRECT);
                     push(@groupsDeniedRightsToBless, $name);
@@ -335,7 +327,7 @@
     }
     # XXX: should create profiles_activity entries for blesser changes.
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     # XXX: userDataToVars may be off when editing ourselves.
     userDataToVars($otherUserID);
@@ -366,6 +358,9 @@
     $vars->{'otheruser'}      = $otherUser;
 
     # Find other cross references.
+    $vars->{'attachments'} = $dbh->selectrow_array(
+        'SELECT COUNT(*) FROM attachments WHERE submitter_id = ?',
+        undef, $otherUserID);
     $vars->{'assignee_or_qa'} = $dbh->selectrow_array(
         qq{SELECT COUNT(*)
            FROM bugs
@@ -380,6 +375,9 @@
     $vars->{'bugs_activity'} = $dbh->selectrow_array(
         'SELECT COUNT(*) FROM bugs_activity WHERE who = ?',
         undef, $otherUserID);
+    $vars->{'component_cc'} = $dbh->selectrow_array(
+        'SELECT COUNT(*) FROM component_cc WHERE user_id = ?',
+        undef, $otherUserID);
     $vars->{'email_setting'} = $dbh->selectrow_array(
         'SELECT COUNT(*) FROM email_setting WHERE user_id = ?',
         undef, $otherUserID);
@@ -410,6 +408,9 @@
     $vars->{'profiles_activity'} = $dbh->selectrow_array(
         'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?',
         undef, ($otherUserID, $otherUserID));
+    $vars->{'quips'} = $dbh->selectrow_array(
+        'SELECT COUNT(*) FROM quips WHERE userid = ?',
+        undef, $otherUserID);
     $vars->{'series'} = $dbh->selectrow_array(
         'SELECT COUNT(*) FROM series WHERE creator = ?',
         undef, $otherUserID);
@@ -451,33 +452,7 @@
     # XXX: if there was some change on these tables after the deletion
     #      confirmation checks, we may do something here we haven't warned
     #      about.
-    $dbh->bz_lock_tables('bugs WRITE',
-                         'bugs_activity WRITE',
-                         'attachments READ',
-                         'fielddefs READ',
-                         'products READ',
-                         'components READ',
-                         'logincookies WRITE',
-                         'profiles WRITE',
-                         'profiles_activity WRITE',
-                         'email_setting WRITE',
-                         'profile_setting WRITE',
-                         'bug_group_map READ',
-                         'user_group_map WRITE',
-                         'flags WRITE',
-                         'flagtypes READ',
-                         'cc WRITE',
-                         'namedqueries WRITE',
-                         'namedqueries_link_in_footer WRITE',
-                         'namedquery_group_map WRITE',
-                         'tokens WRITE',
-                         'votes WRITE',
-                         'watch WRITE',
-                         'series WRITE',
-                         'series_data WRITE',
-                         'whine_schedules WRITE',
-                         'whine_queries WRITE',
-                         'whine_events WRITE');
+    $dbh->bz_start_transaction();
 
     Bugzilla->params->{'allowuserdeletion'}
         || ThrowUserError('users_deletion_disabled');
@@ -518,14 +493,14 @@
 
     foreach (@$buglist) {
         my ($bug_id, $attach_id) = @$_;
-        my @old_summaries = Bugzilla::Flag::snapshot($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);
         }
         else {
             $sth_flagupdate_bug->execute($bug_id, $otherUserID);
         }
-        my @new_summaries = Bugzilla::Flag::snapshot($bug_id, $attach_id);
+        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,
@@ -548,6 +523,7 @@
              $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);
@@ -654,7 +630,7 @@
     # Finally, remove the user account itself.
     $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID);
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
     delete_token($token);
 
     $vars->{'message'} = 'account_deleted';
diff --git a/BugsSite/editvalues.cgi b/BugsSite/editvalues.cgi
index fe1ad54..a7a5204 100755
--- a/BugsSite/editvalues.cgi
+++ b/BugsSite/editvalues.cgi
@@ -19,7 +19,7 @@
 # with some cleanup.
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Util;
@@ -27,18 +27,22 @@
 use Bugzilla::Constants;
 use Bugzilla::Config qw(:admin);
 use Bugzilla::Token;
+use Bugzilla::Field;
+use Bugzilla::Bug;
+use Bugzilla::Status;
 
 # 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.
-# Admins may add bug_status to this list, but they do so at their own risk.
 our @valid_fields = ('op_sys', 'rep_platform', 'priority', 'bug_severity',
-                     'resolution');
+                     'bug_status', 'resolution');
 
 # 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);
 
@@ -63,13 +67,13 @@
     # 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) = @_;
-    FieldMustExist($field);
-    trick_taint($field);
     # Value is safe because it's being passed only to a SELECT
     # statement via a placeholder.
     trick_taint($value);
@@ -106,7 +110,11 @@
 my $dbh      = Bugzilla->dbh;
 my $cgi      = Bugzilla->cgi;
 my $template = Bugzilla->template;
-my $vars = {};
+local our $vars = {};
+
+# Replace this entry by separate entries in templates when
+# the documentation about legal values becomes bigger.
+$vars->{'doc_section'} = 'edit-values.html';
 
 print $cgi->header();
 
@@ -126,7 +134,7 @@
 
 # Gives the name of the parameter associated with the field
 # and representing its default value.
-my %defaults;
+local our %defaults;
 $defaults{'op_sys'} = 'defaultopsys';
 $defaults{'rep_platform'} = 'defaultplatform';
 $defaults{'priority'} = 'defaultpriority';
@@ -134,7 +142,8 @@
 
 # Alternatively, a list of non-editable values can be specified.
 # In this case, only the sortkey can be altered.
-my %static;
+local our %static;
+$static{'bug_status'} = ['UNCONFIRMED', Bugzilla->params->{'duplicate_or_move_bug_status'}];
 $static{'resolution'} = ['', 'FIXED', 'MOVED', 'DUPLICATE'];
 $static{$_->name} = ['---'] foreach (@custom_fields);
 
@@ -155,39 +164,39 @@
     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;
+}
 
 #
 # action='' -> Show nice list of values.
 #
-unless ($action) {
-    FieldMustExist($field);
-    # Now we know the $field is valid.
-    trick_taint($field);
-
-    my $fieldvalues = 
-        $dbh->selectall_arrayref("SELECT value AS name, sortkey"
-                               . "  FROM $field ORDER BY sortkey, value",
-                                 {Slice =>{}});
-    $vars->{'field'} = $field;
-    $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;
-}
-
+display_field_values() unless $action;
 
 #
 # action='add' -> show form for adding new field value.
 # (next action will be 'new')
 #
 if ($action eq 'add') {
-    FieldMustExist($field);
-
     $vars->{'value'} = $value;
-    $vars->{'field'} = $field;
     $vars->{'token'} = issue_session_token('add_field_value');
     $template->process("admin/fieldvalues/create.html.tmpl",
                        $vars)
@@ -202,8 +211,6 @@
 #
 if ($action eq 'new') {
     check_token_data($token, 'add_field_value');
-    FieldMustExist($field);
-    trick_taint($field);
 
     # Cleanups and validity checks
     $value || ThrowUserError('fieldvalue_undefined');
@@ -221,26 +228,38 @@
     }
     if (ValueExists($field, $value)) {
         ThrowUserError('fieldvalue_already_exists',
-                       {'field' => $field,
+                       {'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.
-    my $sth = $dbh->prepare("INSERT INTO $field ( value, sortkey )
-                             VALUES ( ?, ? )");
-    $sth->execute($value, $sortkey);
+    $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();
+    }
 
     delete_token($token);
 
+    $vars->{'message'} = 'field_value_created';
     $vars->{'value'} = $value;
-    $vars->{'field'} = $field;
-    $template->process("admin/fieldvalues/created.html.tmpl",
-                       $vars)
-      || ThrowTemplateError($template->error());
-
-    exit;
+    display_field_values();
 }
 
 
@@ -250,18 +269,24 @@
 #
 if ($action eq 'del') {
     ValueMustExist($field, $value);
-    trick_taint($field);
     trick_taint($value);
 
     # See if any bugs are still using this value.
-    $vars->{'bug_count'} = 
-        $dbh->selectrow_array("SELECT COUNT(*) FROM bugs WHERE $field = ?",
-                              undef, $value) || 0;
-    $vars->{'value_count'} = 
+    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->{'field'} = $field;
     $vars->{'param_name'} = $defaults{$field};
 
     # If the value cannot be deleted, throw an error.
@@ -286,7 +311,6 @@
     ValueMustExist($field, $value);
 
     $vars->{'value'} = $value;
-    $vars->{'field'} = $field;
     $vars->{'param_name'} = $defaults{$field};
 
     if (defined $defaults{$field}
@@ -299,32 +323,48 @@
         ThrowUserError('fieldvalue_not_deletable', $vars);
     }
 
-    trick_taint($field);
     trick_taint($value);
 
-    $dbh->bz_lock_tables('bugs READ', "$field WRITE");
+    $dbh->bz_start_transaction();
 
     # Check if there are any bugs that still have this value.
-    my $bug_ids = $dbh->selectcol_arrayref(
-        "SELECT bug_id FROM bugs WHERE $field = ?", undef, $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 (scalar(@$bug_ids)) {
+
+    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 => scalar(@$bug_ids) });
+                         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_unlock_tables();
+    $dbh->bz_commit_transaction();
     delete_token($token);
 
-    $template->process("admin/fieldvalues/deleted.html.tmpl",
-                       $vars)
-      || ThrowTemplateError($template->error());
-    exit;
+    $vars->{'message'} = 'field_value_deleted';
+    $vars->{'no_edit_link'} = 1;
+    display_field_values();
 }
 
 
@@ -334,16 +374,18 @@
 #
 if ($action eq 'edit') {
     ValueMustExist($field, $value);
-    trick_taint($field);
     trick_taint($value);
 
     $vars->{'sortkey'} = $dbh->selectrow_array(
         "SELECT sortkey FROM $field WHERE value = ?", undef, $value) || 0;
 
     $vars->{'value'} = $value;
-    $vars->{'field'} = $field;
     $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());
@@ -361,12 +403,9 @@
     my $sortkeyold = trim($cgi->param('sortkeyold') || '0');
 
     ValueMustExist($field, $valueold);
-    trick_taint($field);
     trick_taint($valueold);
 
     $vars->{'value'} = $value;
-    $vars->{'field'} = $field;
-
     # If the value cannot be renamed, throw an error.
     if (lsearch($static{$field}, $valueold) >= 0 && $value ne $valueold) {
         $vars->{'old_value'} = $valueold;
@@ -377,7 +416,7 @@
         ThrowUserError('fieldvalue_name_too_long', $vars);
     }
 
-    $dbh->bz_lock_tables('bugs WRITE', "$field WRITE");
+    $dbh->bz_start_transaction();
 
     # Need to store because detaint_natural() will delete this if
     # invalid
@@ -406,10 +445,22 @@
         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);
 
-        $dbh->do("UPDATE bugs SET $field = ? WHERE $field = ?",
-                 undef, $value, $valueold);
+        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);
@@ -417,7 +468,7 @@
         $vars->{'updated_value'} = 1;
     }
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     # If the old value was the default value for the field,
     # update data/params accordingly.
@@ -433,11 +484,8 @@
     }
     delete_token($token);
 
-    $template->process("admin/fieldvalues/updated.html.tmpl",
-                       $vars)
-      || ThrowTemplateError($template->error());
-
-    exit;
+    $vars->{'message'} = 'field_value_updated';
+    display_field_values();
 }
 
 
diff --git a/BugsSite/editversions.cgi b/BugsSite/editversions.cgi
index 7bda621..85f4f8c 100755
--- a/BugsSite/editversions.cgi
+++ b/BugsSite/editversions.cgi
@@ -22,14 +22,9 @@
 #                 Terry Weissman <terry@mozilla.org>
 #                 Gavin Shelley <bugzilla@chimpychompy.org>
 #                 Frédéric Buclin <LpSolit@gmail.com>
-#
-#
-# Direct any questions on this source code to
-#
-# Holger Schurig <holgerschurig@nikocity.de>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -42,6 +37,9 @@
 my $dbh = Bugzilla->dbh;
 my $template = Bugzilla->template;
 my $vars = {};
+# There is only one section about versions in the documentation,
+# so all actions point to the same page.
+$vars->{'doc_section'} = 'versions.html';
 
 #
 # Preliminary checks:
@@ -100,9 +98,6 @@
     exit;
 }
 
-
-
-
 #
 # action='add' -> present form for parameters for new version
 #
@@ -118,8 +113,6 @@
     exit;
 }
 
-
-
 #
 # action='new' -> add version entered in the 'action=add' screen
 #
@@ -129,17 +122,15 @@
     my $version = Bugzilla::Version::create($version_name, $product);
     delete_token($token);
 
+    $vars->{'message'} = 'version_created';
     $vars->{'version'} = $version;
     $vars->{'product'} = $product;
-    $template->process("admin/versions/created.html.tmpl", $vars)
+    $template->process("admin/versions/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
 
     exit;
 }
 
-
-
-
 #
 # action='del' -> ask if user really wants to delete
 #
@@ -147,9 +138,8 @@
 #
 
 if ($action eq 'del') {
-
-    my $version = Bugzilla::Version::check_version($product, $version_name);
-
+    my $version = Bugzilla::Version->check({ product => $product,
+                                             name    => $version_name });
     $vars->{'version'} = $version;
     $vars->{'product'} = $product;
     $vars->{'token'} = issue_session_token('delete_version');
@@ -159,29 +149,28 @@
     exit;
 }
 
-
-
 #
 # action='delete' -> really delete the version
 #
 
 if ($action eq 'delete') {
     check_token_data($token, 'delete_version');
-    my $version = Bugzilla::Version::check_version($product, $version_name);
+    my $version = Bugzilla::Version->check({ product => $product, 
+                                             name    => $version_name });
     $version->remove_from_db;
     delete_token($token);
 
+    $vars->{'message'} = 'version_deleted';
     $vars->{'version'} = $version;
     $vars->{'product'} = $product;
+    $vars->{'no_edit_version_link'} = 1;
 
-    $template->process("admin/versions/deleted.html.tmpl", $vars)
+    $template->process("admin/versions/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
 
     exit;
 }
 
-
-
 #
 # action='edit' -> present the edit version form
 #
@@ -189,9 +178,8 @@
 #
 
 if ($action eq 'edit') {
-
-    my $version = Bugzilla::Version::check_version($product, $version_name);
-
+    my $version = Bugzilla::Version->check({ product => $product,
+                                             name    => $version_name });
     $vars->{'version'} = $version;
     $vars->{'product'} = $product;
     $vars->{'token'} = issue_session_token('edit_version');
@@ -202,8 +190,6 @@
     exit;
 }
 
-
-
 #
 # action='update' -> update the version
 #
@@ -211,26 +197,25 @@
 if ($action eq 'update') {
     check_token_data($token, 'edit_version');
     my $version_old_name = trim($cgi->param('versionold') || '');
-    my $version =
-        Bugzilla::Version::check_version($product, $version_old_name);
+    my $version = Bugzilla::Version->check({ product => $product,
+                                             name   => $version_old_name });
 
-    $dbh->bz_lock_tables('bugs WRITE', 'versions WRITE');
+    $dbh->bz_start_transaction();
 
     $vars->{'updated'} = $version->update($version_name, $product);
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
     delete_token($token);
 
+    $vars->{'message'} = 'version_updated';
     $vars->{'version'} = $version;
     $vars->{'product'} = $product;
-    $template->process("admin/versions/updated.html.tmpl", $vars)
+    $template->process("admin/versions/list.html.tmpl", $vars)
       || ThrowTemplateError($template->error());
 
     exit;
 }
 
-
-
 #
 # No valid action found
 #
diff --git a/BugsSite/editwhines.cgi b/BugsSite/editwhines.cgi
index ba39b54..33b7860 100755
--- a/BugsSite/editwhines.cgi
+++ b/BugsSite/editwhines.cgi
@@ -27,7 +27,7 @@
 
 use strict;
 
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -238,28 +238,14 @@
                     # get an id for the mailto address
                     if ($can_mail_others && $mailto) {
                         if ($mailto_type == MAILTO_USER) {
-                            # detaint
-                            my $emailregexp = Bugzilla->params->{'emailregexp'};
-                            if ($mailto =~ /($emailregexp)/) {
-                                $mailto_id = login_to_id($1);
-                            }
-                            else {
-                                ThrowUserError("illegal_email_address", 
-                                               { addr => $mailto });
-                            }
+                            # The user login has already been validated.
+                            $mailto_id = login_to_id($mailto);
                         }
                         elsif ($mailto_type == MAILTO_GROUP) {
-                            # detaint the group parameter
-                            if ($mailto =~ /^([0-9a-z_\-\.]+)$/i) {
-                                $mailto_id = Bugzilla::Group::ValidateGroupName(
-                                                 $1, ($user)) || 
-                                             ThrowUserError(
-                                                 'invalid_group_name', 
-                                                 { name => $1 });
-                            } else {
-                                ThrowUserError('invalid_group_name',
-                                               { name => $mailto });
-                            }
+                            # The group name is used in a placeholder.
+                            trick_taint($mailto);
+                            $mailto_id = Bugzilla::Group::ValidateGroupName($mailto, ($user))
+                                           || ThrowUserError('invalid_group_name', { name => $mailto });
                         }
                         else {
                             # bad value, so it will just mail to the whine
diff --git a/BugsSite/editworkflow.cgi b/BugsSite/editworkflow.cgi
new file mode 100755
index 0000000..7e51798
--- /dev/null
+++ b/BugsSite/editworkflow.cgi
@@ -0,0 +1,151 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (C) 2007
+# Frédéric Buclin. All Rights Reserved.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Token;
+use Bugzilla::Status;
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+print $cgi->header();
+
+$user->in_group('admin')
+  || ThrowUserError('auth_failure', {group  => 'admin',
+                                     action => 'modify',
+                                     object => 'workflow'});
+
+my $action = $cgi->param('action') || 'edit';
+my $token = $cgi->param('token');
+
+sub get_workflow {
+    my $dbh = Bugzilla->dbh;
+    my $workflow = $dbh->selectall_arrayref('SELECT old_status, new_status, require_comment
+                                             FROM status_workflow');
+    my %workflow;
+    foreach my $row (@$workflow) {
+        my ($old, $new, $type) = @$row;
+        $workflow{$old || 0}{$new} = $type;
+    }
+    return \%workflow;
+}
+
+sub load_template {
+    my ($filename, $message) = @_;
+    my $template = Bugzilla->template;
+    my $vars = {};
+
+    $vars->{'statuses'} = [Bugzilla::Status->get_all];
+    $vars->{'workflow'} = get_workflow();
+    $vars->{'token'} = issue_session_token("workflow_$filename");
+    $vars->{'message'} = $message;
+
+    $template->process("admin/workflow/$filename.html.tmpl", $vars)
+      || ThrowTemplateError($template->error());
+    exit;
+}
+
+if ($action eq 'edit') {
+    load_template('edit');
+}
+elsif ($action eq 'update') {
+    check_token_data($token, 'workflow_edit');
+    my $statuses = [Bugzilla::Status->get_all];
+    my $workflow = get_workflow();
+
+    my $sth_insert = $dbh->prepare('INSERT INTO status_workflow (old_status, new_status)
+                                    VALUES (?, ?)');
+    my $sth_delete = $dbh->prepare('DELETE FROM status_workflow
+                                    WHERE old_status = ? AND new_status = ?');
+    my $sth_delnul = $dbh->prepare('DELETE FROM status_workflow
+                                    WHERE old_status IS NULL AND new_status = ?');
+
+    # Part 1: Initial bug statuses.
+    foreach my $new (@$statuses) {
+        if ($new->is_open && $cgi->param('w_0_' . $new->id)) {
+            $sth_insert->execute(undef, $new->id)
+              unless defined $workflow->{0}->{$new->id};
+        }
+        else {
+            $sth_delnul->execute($new->id);
+        }
+    }
+
+    # Part 2: Bug status changes.
+    foreach my $old (@$statuses) {
+        foreach my $new (@$statuses) {
+            next if $old->id == $new->id;
+
+            # All transitions to 'duplicate_or_move_bug_status' must be valid.
+            if ($cgi->param('w_' . $old->id . '_' . $new->id)
+                || ($new->name eq Bugzilla->params->{'duplicate_or_move_bug_status'}))
+            {
+                $sth_insert->execute($old->id, $new->id)
+                  unless defined $workflow->{$old->id}->{$new->id};
+            }
+            else {
+                $sth_delete->execute($old->id, $new->id);
+            }
+        }
+    }
+    delete_token($token);
+    load_template('edit', 'workflow_updated');
+}
+elsif ($action eq 'edit_comment') {
+    load_template('comment');
+}
+elsif ($action eq 'update_comment') {
+    check_token_data($token, 'workflow_comment');
+    my $workflow = get_workflow();
+
+    my $sth_update = $dbh->prepare('UPDATE status_workflow SET require_comment = ?
+                                    WHERE old_status = ? AND new_status = ?');
+    my $sth_updnul = $dbh->prepare('UPDATE status_workflow SET require_comment = ?
+                                    WHERE old_status IS NULL AND new_status = ?');
+
+    foreach my $old (keys %$workflow) {
+        # Hashes cannot have undef as a key, so we use 0. But the DB
+        # must store undef, for referential integrity.
+        my $old_id_for_db = $old || undef;
+        foreach my $new (keys %{$workflow->{$old}}) {
+            my $comment_required = $cgi->param("c_${old}_$new") ? 1 : 0;
+            next if ($workflow->{$old}->{$new} == $comment_required);
+            if ($old_id_for_db) {
+                $sth_update->execute($comment_required, $old_id_for_db, $new);
+            }
+            else {
+                $sth_updnul->execute($comment_required, $new);
+            }
+        }
+    }
+    delete_token($token);
+    load_template('comment', 'workflow_updated');
+}
+else {
+    ThrowCodeError("action_unrecognized", {action => $action});
+}
diff --git a/BugsSite/email_in.pl b/BugsSite/email_in.pl
index 21d38ce..08e1db7 100755
--- a/BugsSite/email_in.pl
+++ b/BugsSite/email_in.pl
@@ -29,6 +29,8 @@
     chdir(File::Basename::dirname($0)); 
 }
 
+use lib qw(. lib);
+
 use Data::Dumper;
 use Email::Address;
 use Email::Reply qw(reply);
@@ -36,7 +38,7 @@
 use Email::MIME::Attachment::Stripper;
 use Getopt::Long qw(:config bundling);
 use Pod::Usage;
-use Encode qw(encode decode);
+use Encode;
 
 use Bugzilla;
 use Bugzilla::Bug qw(ValidateBugID);
@@ -45,6 +47,7 @@
 use Bugzilla::Mailer;
 use Bugzilla::User;
 use Bugzilla::Util;
+use Bugzilla::Token;
 
 #############
 # Constants #
@@ -54,45 +57,6 @@
 # in a message. RFC-compliant mailers use this.
 use constant SIGNATURE_DELIMITER => '-- ';
 
-# These fields must all be defined or post_bug complains. They don't have
-# to have values--they just have to be defined. There's not yet any
-# way to require custom fields have values, for enter_bug, so we don't
-# have to worry about those yet.
-use constant REQUIRED_ENTRY_FIELDS => qw(
-    reporter
-    short_desc
-    product
-    component
-    version
-
-    assigned_to
-    rep_platform
-    op_sys
-    priority
-    bug_severity
-    bug_file_loc
-);
-
-# Fields that must be defined during process_bug. They *do* have to
-# have values. The script will grab their values from the current
-# bug object, if they're not specified.
-use constant REQUIRED_PROCESS_FIELDS => qw(
-    dependson
-    blocked
-    version
-    product
-    target_milestone
-    rep_platform
-    op_sys
-    priority
-    bug_severity
-    bug_file_loc
-    component
-    short_desc
-    reporter_accessible
-    cclist_accessible
-);
-
 # $input_email is a global so that it can be used in die_handler.
 our ($input_email, %switch);
 
@@ -124,7 +88,7 @@
     debug_print("Body:\n" . $body, 3);
 
     $body = remove_leading_blank_lines($body);
-    my @body_lines = split("\n", $body);
+    my @body_lines = split(/\r?\n/s, $body);
 
     # If there are fields specified.
     if ($body =~ /^\s*@/s) {
@@ -143,6 +107,16 @@
             
             if ($line =~ /^@(\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 {
@@ -178,15 +152,6 @@
 
     debug_print('Posting a new bug...');
 
-    $fields{'rep_platform'} ||= Bugzilla->params->{'defaultplatform'};
-    $fields{'op_sys'}   ||= Bugzilla->params->{'defaultopsys'};
-    $fields{'priority'} ||= Bugzilla->params->{'defaultpriority'};
-    $fields{'bug_severity'} ||= Bugzilla->params->{'defaultseverity'};
-
-    foreach my $field (REQUIRED_ENTRY_FIELDS) {
-        $fields{$field} ||= '';
-    }
-
     my $cgi = Bugzilla->cgi;
     foreach my $field (keys %fields) {
         $cgi->param(-name => $field, -value => $fields{$field});
@@ -211,36 +176,21 @@
     ValidateBugID($bug_id);
     my $bug = new Bugzilla::Bug($bug_id);
 
-    if ($fields{'assigned_to'}) {
-        $fields{'knob'} = 'reassign';
+    if ($fields{'bug_status'}) {
+        $fields{'knob'} = $fields{'bug_status'};
     }
-    if (my $status = $fields{'bug_status'}) {
-        $fields{'knob'} = 'confirm' if $status =~ /NEW/i;
-        $fields{'knob'} = 'accept'  if $status =~ /ASSIGNED/i;
-        $fields{'knob'} = 'clearresolution' if $status =~ /REOPENED/i;
-        $fields{'knob'} = 'verify'  if $status =~ /VERIFIED/i;
-        $fields{'knob'} = 'close'   if $status =~ /CLOSED/i;
+    # If no status is given, then we only want to change the resolution.
+    elsif ($fields{'resolution'}) {
+        $fields{'knob'} = 'change_resolution';
+        $fields{'resolution_knob_change_resolution'} = $fields{'resolution'};
     }
     if ($fields{'dup_id'}) {
         $fields{'knob'} = 'duplicate';
     }
-    if ($fields{'resolution'}) {
-        $fields{'knob'} = 'resolve';
-    }
 
-    # Make sure we don't get prompted if we have to change the default
-    # groups.
-    if ($fields{'product'}) {
-        $fields{'addtonewgroup'} = 0;
-    }
-
-    foreach my $field (REQUIRED_PROCESS_FIELDS) {
-        my $value = $bug->$field;
-        if (ref $value) {
-            $value = join(',', @$value);
-        }
-        $fields{$field} ||= $value;
-    }
+    # Move @cc to @newcc as @cc is used by process_bug.cgi to remove
+    # users from the CC list when @removecc is set.
+    $fields{'newcc'} = delete $fields{'cc'} if $fields{'cc'};
 
     # Make it possible to remove CCs.
     if ($fields{'removecc'}) {
@@ -253,6 +203,7 @@
         $cgi->param(-name => $field, -value => $fields{$field});
     }
     $cgi->param('longdesclength', scalar $bug->longdescs);
+    $cgi->param('token', issue_hash_token([$bug->id, $bug->delta_ts]));
 
     require 'process_bug.cgi';
 }
@@ -297,15 +248,16 @@
     foreach my $part (@parts) {
         my $ct = $part->content_type || 'text/plain';
         my $charset = 'iso-8859-1';
-        if ($ct =~ /charset=([^;]+)/) {
+        # The charset may be quoted.
+        if ($ct =~ /charset="?([^;"]+)/) {
             $charset= $1;
         }
         debug_print("Part Content-Type: $ct", 2);
         debug_print("Part Character Encoding: $charset", 2);
         if (!$ct || $ct =~ /^text\/plain/i) {
             $body = $part->body;
-            if (Bugzilla->params->{'utf8'}) {
-                $body = encode('UTF-8', decode($charset, $body));
+            if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($body)) {
+                $body = Encode::decode($charset, $body);
             }
             last;
         }
@@ -337,6 +289,8 @@
     $var =~ s/\&gt;/>/g;
     $var =~ s/\&quot;/\"/g;
     $var =~ s/&#64;/@/g;
+    # Also remove undesired newlines and consecutive spaces.
+    $var =~ s/[\n\s]+/ /gms;
     return $var;
 }
 
@@ -359,10 +313,12 @@
        $msg =~ s/at .+ line.*$//ms;
        $msg =~ s/^Compilation failed in require.+$//ms;
        $msg = html_strip($msg);
-       my $reply = reply(to => $input_email, top_post => 1, body => "$msg\n");
+       my $from = Bugzilla->params->{'mailfrom'};
+       my $reply = reply(to => $input_email, from => $from, top_post => 1, 
+                         body => "$msg\n");
        MessageToMTA($reply->as_string);
     }
-    print STDERR $msg;
+    print STDERR "$msg\n";
     # We exit with a successful value, because we don't want the MTA
     # to *also* send a failure notice.
     exit;
@@ -388,6 +344,11 @@
 my $mail_fields = parse_mail($mail_text);
 
 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 });
 
@@ -512,9 +473,9 @@
 
 =head3 Adding/Removing CCs
 
-You can't just add CCs to a bug by using the C<@cc> parameter like you
-can when you're filing a bug. To add CCs, you can specify them in a
-comma-separated list in C<@newcc>.
+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>.
 
diff --git a/BugsSite/enter_bug.cgi b/BugsSite/enter_bug.cgi
index 53262b3..f9fdd60 100755
--- a/BugsSite/enter_bug.cgi
+++ b/BugsSite/enter_bug.cgi
@@ -35,7 +35,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -49,6 +49,7 @@
 use Bugzilla::Keyword;
 use Bugzilla::Token;
 use Bugzilla::Field;
+use Bugzilla::Status;
 
 my $user = Bugzilla->login(LOGIN_REQUIRED);
 
@@ -60,6 +61,9 @@
 my $template = Bugzilla->template;
 my $vars = {};
 
+# All pages point to the same part of the documentation.
+$vars->{'doc_section'} = 'bugreports.html';
+
 my $product_name = trim($cgi->param('product') || '');
 # Will contain the product object the bug is created in.
 my $product;
@@ -257,49 +261,69 @@
             /\(.*OSF.*\)/ && do {push @os, "OSF/1";};
             /\(.*Linux.*\)/ && do {push @os, "Linux";};
             /\(.*Solaris.*\)/ && do {push @os, "Solaris";};
-            /\(.*SunOS 5.*\)/ && do {push @os, "Solaris";};
-            /\(.*SunOS.*sun4u.*\)/ && do {push @os, "Solaris";};
-            /\(.*SunOS.*\)/ && do {push @os, "SunOS";};
+            /\(.*SunOS.*\)/ && do {
+              /\(.*SunOS 5.11.*\)/ && do {push @os, ("OpenSolaris", "Opensolaris", "Solaris 11");};
+              /\(.*SunOS 5.10.*\)/ && do {push @os, "Solaris 10";};
+              /\(.*SunOS 5.9.*\)/ && do {push @os, "Solaris 9";};
+              /\(.*SunOS 5.8.*\)/ && do {push @os, "Solaris 8";};
+              /\(.*SunOS 5.7.*\)/ && do {push @os, "Solaris 7";};
+              /\(.*SunOS 5.6.*\)/ && do {push @os, "Solaris 6";};
+              /\(.*SunOS 5.5.*\)/ && do {push @os, "Solaris 5";};
+              /\(.*SunOS 5.*\)/ && do {push @os, "Solaris";};
+              /\(.*SunOS.*sun4u.*\)/ && do {push @os, "Solaris";};
+              /\(.*SunOS.*i86pc.*\)/ && do {push @os, "Solaris";};
+              /\(.*SunOS.*\)/ && do {push @os, "SunOS";};
+            };
             /\(.*HP-?UX.*\)/ && do {push @os, "HP-UX";};
-            /\(.*BSD\/(?:OS|386).*\)/ && do {push @os, "BSDI";};
-            /\(.*FreeBSD.*\)/ && do {push @os, "FreeBSD";};
-            /\(.*OpenBSD.*\)/ && do {push @os, "OpenBSD";};
-            /\(.*NetBSD.*\)/ && do {push @os, "NetBSD";};
+            /\(.*BSD.*\)/ && do {
+              /\(.*BSD\/(?:OS|386).*\)/ && do {push @os, "BSDI";};
+              /\(.*FreeBSD.*\)/ && do {push @os, "FreeBSD";};
+              /\(.*OpenBSD.*\)/ && do {push @os, "OpenBSD";};
+              /\(.*NetBSD.*\)/ && do {push @os, "NetBSD";};
+            };
             /\(.*BeOS.*\)/ && do {push @os, "BeOS";};
             /\(.*AIX.*\)/ && do {push @os, "AIX";};
             /\(.*OS\/2.*\)/ && do {push @os, "OS/2";};
             /\(.*QNX.*\)/ && do {push @os, "Neutrino";};
             /\(.*VMS.*\)/ && do {push @os, "OpenVMS";};
-            /\(.*Windows XP.*\)/ && do {push @os, "Windows XP";};
-            /\(.*Windows NT 6\.0.*\)/ && do {push @os, "Windows Vista";};
-            /\(.*Windows NT 5\.2.*\)/ && do {push @os, "Windows Server 2003";};
-            /\(.*Windows NT 5\.1.*\)/ && do {push @os, "Windows XP";};
-            /\(.*Windows 2000.*\)/ && do {push @os, "Windows 2000";};
-            /\(.*Windows NT 5.*\)/ && do {push @os, "Windows 2000";};
-            /\(.*Win.*9[8x].*4\.9.*\)/ && do {push @os, "Windows ME";};
-            /\(.*Win(?:dows |)M[Ee].*\)/ && do {push @os, "Windows ME";};
-            /\(.*Win(?:dows |)98.*\)/ && do {push @os, "Windows 98";};
-            /\(.*Win(?:dows |)95.*\)/ && do {push @os, "Windows 95";};
-            /\(.*Win(?:dows |)16.*\)/ && do {push @os, "Windows 3.1";};
-            /\(.*Win(?:dows[ -]|)NT.*\)/ && do {push @os, "Windows NT";};
-            /\(.*Windows.*NT.*\)/ && do {push @os, "Windows NT";};
+            /\(.*Win.*\)/ && do {
+              /\(.*Windows XP.*\)/ && do {push @os, "Windows XP";};
+              /\(.*Windows NT 6\.0.*\)/ && do {push @os, "Windows Vista";};
+              /\(.*Windows NT 5\.2.*\)/ && do {push @os, "Windows Server 2003";};
+              /\(.*Windows NT 5\.1.*\)/ && do {push @os, "Windows XP";};
+              /\(.*Windows 2000.*\)/ && do {push @os, "Windows 2000";};
+              /\(.*Windows NT 5.*\)/ && do {push @os, "Windows 2000";};
+              /\(.*Win.*9[8x].*4\.9.*\)/ && do {push @os, "Windows ME";};
+              /\(.*Win(?:dows |)M[Ee].*\)/ && do {push @os, "Windows ME";};
+              /\(.*Win(?:dows |)98.*\)/ && do {push @os, "Windows 98";};
+              /\(.*Win(?:dows |)95.*\)/ && do {push @os, "Windows 95";};
+              /\(.*Win(?:dows |)16.*\)/ && do {push @os, "Windows 3.1";};
+              /\(.*Win(?:dows[ -]|)NT.*\)/ && do {push @os, "Windows NT";};
+              /\(.*Windows.*NT.*\)/ && do {push @os, "Windows NT";};
+            };
+            /\(.*Mac OS X.*\)/ && do {
+              /\(.*Intel.*Mac OS X 10.5.*\)/ && do {push @os, "Mac OS X 10.5";};
+              /\(.*Intel.*Mac OS X.*\)/ && do {push @os, "Mac OS X 10.4";};
+              /\(.*Mac OS X.*\)/ && do {push @os, ("Mac OS X 10.3", "Mac OS X 10.0", "Mac OS X");};
+            };
             /\(.*32bit.*\)/ && do {push @os, "Windows 95";};
             /\(.*16bit.*\)/ && do {push @os, "Windows 3.1";};
-            /\(.*Mac OS 9.*\)/ && do {push @os, "Mac System 9.x";};
-            /\(.*Mac OS 8\.6.*\)/ && do {push @os, "Mac System 8.6";};
-            /\(.*Mac OS 8\.5.*\)/ && do {push @os, "Mac System 8.5";};
-        # Bugzilla doesn't have an entry for 8.1
-            /\(.*Mac OS 8\.1.*\)/ && do {push @os, "Mac System 8.0";};
-            /\(.*Mac OS 8\.0.*\)/ && do {push @os, "Mac System 8.0";};
-            /\(.*Mac OS 8[^.].*\)/ && do {push @os, "Mac System 8.0";};
-            /\(.*Mac OS 8.*\)/ && do {push @os, "Mac System 8.6";};
-            /\(.*Intel.*Mac OS X.*\)/ && do {push @os, "Mac OS X 10.4";};
-            /\(.*Mac OS X.*\)/ && do {push @os, ("Mac OS X 10.3", "Mac OS X 10.0");};
-            /\(.*Darwin.*\)/ && do {push @os, "Mac OS X 10.0";};
+            /\(.*Mac OS \d.*\)/ && do {
+              /\(.*Mac OS 9.*\)/ && do {push @os, ("Mac System 9.x", "Mac System 9.0");};
+              /\(.*Mac OS 8\.6.*\)/ && do {push @os, ("Mac System 8.6", "Mac System 8.5");};
+              /\(.*Mac OS 8\.5.*\)/ && do {push @os, "Mac System 8.5";};
+              /\(.*Mac OS 8\.1.*\)/ && do {push @os, ("Mac System 8.1", "Mac System 8.0");};
+              /\(.*Mac OS 8\.0.*\)/ && do {push @os, "Mac System 8.0";};
+              /\(.*Mac OS 8[^.].*\)/ && do {push @os, "Mac System 8.0";};
+              /\(.*Mac OS 8.*\)/ && do {push @os, "Mac System 8.6";};
+            };
+            /\(.*Darwin.*\)/ && do {push @os, ("Mac OS X 10.0", "Mac OS X");};
         # Silly
-            /\(.*Mac.*PowerPC.*\)/ && do {push @os, "Mac System 9.x";};
-            /\(.*Mac.*PPC.*\)/ && do {push @os, "Mac System 9.x";};
-            /\(.*Mac.*68k.*\)/ && do {push @os, "Mac System 8.0";};
+            /\(.*Mac.*\)/ && do {
+              /\(.*Mac.*PowerPC.*\)/ && do {push @os, "Mac System 9.x";};
+              /\(.*Mac.*PPC.*\)/ && do {push @os, "Mac System 9.x";};
+              /\(.*Mac.*68k.*\)/ && do {push @os, "Mac System 8.0";};
+            };
         # Evil
             /Amiga/i && do {push @os, "Other";};
             /WinMosaic/ && do {push @os, "Windows 95";};
@@ -359,8 +383,7 @@
 $vars->{'token'}             = issue_session_token('createbug:');
 
 
-my @enter_bug_fields = Bugzilla->get_fields({ custom => 1, obsolete => 0, 
-                                              enter_bug => 1 });
+my @enter_bug_fields = grep { $_->enter_bug } Bugzilla->active_custom_fields;
 foreach my $field (@enter_bug_fields) {
     $vars->{$field->name} = formvalue($field->name);
 }
@@ -368,49 +391,46 @@
 if ($cloned_bug_id) {
 
     $default{'component_'}    = $cloned_bug->component;
-    $default{'priority'}      = $cloned_bug->{'priority'};
-    $default{'bug_severity'}  = $cloned_bug->{'bug_severity'};
-    $default{'rep_platform'}  = $cloned_bug->{'rep_platform'};
-    $default{'op_sys'}        = $cloned_bug->{'op_sys'};
+    $default{'priority'}      = $cloned_bug->priority;
+    $default{'bug_severity'}  = $cloned_bug->bug_severity;
+    $default{'rep_platform'}  = $cloned_bug->rep_platform;
+    $default{'op_sys'}        = $cloned_bug->op_sys;
 
-    $vars->{'short_desc'}     = $cloned_bug->{'short_desc'};
-    $vars->{'bug_file_loc'}   = $cloned_bug->{'bug_file_loc'};
+    $vars->{'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->{'deadline'}       = $cloned_bug->{'deadline'};
+    $vars->{'deadline'}       = $cloned_bug->deadline;
 
     if (defined $cloned_bug->cc) {
-        $vars->{'cc'}         = join (" ", @{$cloned_bug->cc});
+        $vars->{'cc'}         = join (", ", @{$cloned_bug->cc});
     } else {
         $vars->{'cc'}         = formvalue('cc');
     }
 
     foreach my $field (@enter_bug_fields) {
-        $vars->{$field->name} = $cloned_bug->{$field->name};
+        my $field_name = $field->name;
+        $vars->{$field_name} = $cloned_bug->$field_name;
     }
 
-# We need to ensure that we respect the 'insider' status of
-# the first comment, if it has one. Either way, make a note
-# that this bug was cloned from another bug.
-
-    $cloned_bug->longdescs();
-    my $isprivate             = $cloned_bug->{'longdescs'}->[0]->{'isprivate'};
+    # 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'};
 
     $vars->{'comment'}        = "";
     $vars->{'commentprivacy'} = 0;
 
-    if ( !($isprivate) ||
-         ( ( Bugzilla->params->{"insidergroup"} ) && 
-           ( Bugzilla->user->in_group(Bugzilla->params->{"insidergroup"}) ) ) 
-       ) {
-        $vars->{'comment'}        = $cloned_bug->{'longdescs'}->[0]->{'body'};
+    if (!$isprivate || Bugzilla->user->is_insider) {
+        $vars->{'comment'}        = $bug_desc->[0]->{'body'};
         $vars->{'commentprivacy'} = $isprivate;
     }
 
-# Ensure that the groupset information is set up for later use.
-    $cloned_bug->groups();
-
 } # end of cloned bug entry form
 
 else {
@@ -453,7 +473,7 @@
 
 if ( ($cloned_bug_id) &&
      ($product->name eq $cloned_bug->product ) ) {
-    $default{'version'} = $cloned_bug->{'version'};
+    $default{'version'} = $cloned_bug->version;
 } elsif (formvalue('version')) {
     $default{'version'} = formvalue('version');
 } elsif (defined $cgi->cookie("VERSION-" . $product->name) &&
@@ -474,39 +494,38 @@
 }
 
 # Construct the list of allowable statuses.
-#
-# * If the product requires votes to confirm:
-#   users with privs   : NEW + ASSI + UNCO
-#   users with no privs: UNCO
-#
-# * If the product doesn't require votes to confirm:
-#   users with privs   : NEW + ASSI
-#   users with no privs: NEW (as these users cannot reassign
-#                             bugs to them, it doesn't make sense
-#                             to let them mark bugs as ASSIGNED)
+my $initial_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;
 
-my @status;
-if ($has_editbugs || $has_canconfirm) {
-    @status = ('NEW', 'ASSIGNED');
-}
-elsif (!$product->votes_to_confirm) {
-    @status = ('NEW');
-}
-if ($product->votes_to_confirm) {
-    push(@status, 'UNCONFIRMED');
+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');
+
+# 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);
 }
 
-$vars->{'bug_status'} = \@status; 
+$vars->{'bug_status'} = \@status;
 
 # Get the default from a template value if it is legitimate.
-# Otherwise, set the default to the first item on the list.
+# 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)) {
     $default{'bug_status'} = formvalue('bug_status');
-} else {
+} elsif (scalar @status == 1) {
     $default{'bug_status'} = $status[0];
 }
- 
+else {
+    $default{'bug_status'} = ($status[0] ne 'UNCONFIRMED') ? $status[0] : $status[1];
+}
+
 my $grouplist = $dbh->selectall_arrayref(
                   q{SELECT DISTINCT groups.id, groups.name, groups.description,
                                     membercontrol, othercontrol
@@ -540,9 +559,9 @@
     #
     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'};
+        foreach my $i (0..(@{$cloned_bug->groups} - 1) ) {
+            if ($cloned_bug->groups->[$i]->{'bit'} == $id) {
+                $check = $cloned_bug->groups->[$i]->{'ison'};
             }
         }
     }
diff --git a/BugsSite/extensions/example/code/bug-end_of_update.pl b/BugsSite/extensions/example/code/bug-end_of_update.pl
new file mode 100644
index 0000000..0365635
--- /dev/null
+++ b/BugsSite/extensions/example/code/bug-end_of_update.pl
@@ -0,0 +1,56 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, 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/BugsSite/extensions/example/code/buglist-columns.pl b/BugsSite/extensions/example/code/buglist-columns.pl
new file mode 100644
index 0000000..4487b2d
--- /dev/null
+++ b/BugsSite/extensions/example/code/buglist-columns.pl
@@ -0,0 +1,26 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, 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/BugsSite/extensions/example/code/colchange-columns.pl b/BugsSite/extensions/example/code/colchange-columns.pl
new file mode 100644
index 0000000..6174d39
--- /dev/null
+++ b/BugsSite/extensions/example/code/colchange-columns.pl
@@ -0,0 +1,27 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, 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/BugsSite/extensions/example/code/config.pl b/BugsSite/extensions/example/code/config.pl
new file mode 100644
index 0000000..1da490c
--- /dev/null
+++ b/BugsSite/extensions/example/code/config.pl
@@ -0,0 +1,26 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Example Plugin.
+#
+# The Initial Developer of the Original Code is Canonical Ltd.
+# Portions created by Canonical Ltd. are Copyright (C) 2008 
+# Canonical Ltd. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+#                 Bradley Baetz <bbaetz@acm.org>
+
+use strict;
+use warnings;
+use Bugzilla;
+my $config = Bugzilla->hook_args->{config};
+$config->{Example} = "extensions::example::lib::ConfigExample";
diff --git a/BugsSite/extensions/example/code/flag-end_of_update.pl b/BugsSite/extensions/example/code/flag-end_of_update.pl
new file mode 100644
index 0000000..6371bd1
--- /dev/null
+++ b/BugsSite/extensions/example/code/flag-end_of_update.pl
@@ -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 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/BugsSite/extensions/example/code/install-before_final_checks.pl b/BugsSite/extensions/example/code/install-before_final_checks.pl
new file mode 100644
index 0000000..ef1bee2
--- /dev/null
+++ b/BugsSite/extensions/example/code/install-before_final_checks.pl
@@ -0,0 +1,28 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla 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/BugsSite/extensions/example/code/product-confirm_delete.pl b/BugsSite/extensions/example/code/product-confirm_delete.pl
new file mode 100644
index 0000000..1f4c374
--- /dev/null
+++ b/BugsSite/extensions/example/code/product-confirm_delete.pl
@@ -0,0 +1,26 @@
+#!/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/BugsSite/extensions/example/code/webservice-error_codes.pl b/BugsSite/extensions/example/code/webservice-error_codes.pl
new file mode 100644
index 0000000..94c4c52
--- /dev/null
+++ b/BugsSite/extensions/example/code/webservice-error_codes.pl
@@ -0,0 +1,25 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the 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>
+
+use strict;
+use warnings;
+use Bugzilla;
+my $error_map = Bugzilla->hook_args->{error_map};
+$error_map->{'example_my_error'} = 10001;
diff --git a/BugsSite/extensions/example/code/webservice.pl b/BugsSite/extensions/example/code/webservice.pl
new file mode 100644
index 0000000..ff503be
--- /dev/null
+++ b/BugsSite/extensions/example/code/webservice.pl
@@ -0,0 +1,25 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by Everything Solved, Inc. are Copyright (C) 2007 
+# Everything Solved, Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use warnings;
+use Bugzilla;
+my $dispatch = Bugzilla->hook_args->{dispatch};
+$dispatch->{Example} = "extensions::example::lib::WSExample";
diff --git a/BugsSite/extensions/example/disabled b/BugsSite/extensions/example/disabled
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/BugsSite/extensions/example/disabled
diff --git a/BugsSite/extensions/example/info.pl b/BugsSite/extensions/example/info.pl
new file mode 100644
index 0000000..b4620ee
--- /dev/null
+++ b/BugsSite/extensions/example/info.pl
@@ -0,0 +1,41 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Example 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/BugsSite/extensions/example/lib/ConfigExample.pm b/BugsSite/extensions/example/lib/ConfigExample.pm
new file mode 100644
index 0000000..5ee612d
--- /dev/null
+++ b/BugsSite/extensions/example/lib/ConfigExample.pm
@@ -0,0 +1,41 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Example Plugin.
+#
+# The Initial Developer of the Original Code is Canonical Ltd.
+# Portions created by Canonical Ltd. are Copyright (C) 2008
+# Canonical Ltd. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+#                 Bradley Baetz <bbaetz@acm.org>
+
+package extensions::example::lib::ConfigExample;
+use strict;
+use warnings;
+
+use Bugzilla::Config::Common;
+
+sub get_param_list {
+    my ($class) = @_;
+
+    my @param_list = (
+    {
+        name => 'example_string',
+        type => 't',
+        default => 'EXAMPLE',
+    },
+    );
+    return @param_list;
+}
+
+1;
diff --git a/BugsSite/extensions/example/lib/WSExample.pm b/BugsSite/extensions/example/lib/WSExample.pm
new file mode 100644
index 0000000..1468672
--- /dev/null
+++ b/BugsSite/extensions/example/lib/WSExample.pm
@@ -0,0 +1,32 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by Everything Solved, Inc. are Copyright (C) 2007 
+# Everything Solved, Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package extensions::example::lib::WSExample;
+use strict;
+use warnings;
+use base qw(Bugzilla::WebService);
+use Bugzilla::Error;
+
+# This can be called as Example.hello() from XML-RPC.
+sub hello { return 'Hello!'; }
+
+sub throw_an_error { ThrowUserError('example_my_error') }
+
+1;
diff --git a/BugsSite/extensions/example/template/en/default/admin/params/example.html.tmpl b/BugsSite/extensions/example/template/en/default/admin/params/example.html.tmpl
new file mode 100644
index 0000000..e2bb5f3
--- /dev/null
+++ b/BugsSite/extensions/example/template/en/default/admin/params/example.html.tmpl
@@ -0,0 +1,28 @@
+[%#
+  # The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Example Plugin.
+  #
+  # The Initial Developer of the Original Code is Canonical Ltd.
+  # Portions created by Canonical Ltd. are Copyright (C) 2008
+  # Canonical Ltd. All Rights Reserved.
+  #
+  # Contributor(s): Bradley Baetz <bbaetz@acm.org>
+  #%]
+[%
+    title = "Example Extension"
+    desc = "Configure example extension"
+%]
+
+[% param_descs = {
+  example_string => "Example string",
+}
+%]
diff --git a/BugsSite/extensions/example/template/en/global/user-error-errors.html.tmpl b/BugsSite/extensions/example/template/en/global/user-error-errors.html.tmpl
new file mode 100644
index 0000000..df5a203
--- /dev/null
+++ b/BugsSite/extensions/example/template/en/global/user-error-errors.html.tmpl
@@ -0,0 +1,12 @@
+[%# Note that error messages should generally be indented four spaces, like
+  # below, because when Bugzilla translates an error message into plain
+  # text, it takes four spaces off the beginning of the lines. 
+  #
+  # Note also that I prefixed my error name with "example", the name of my
+  # extension, so that I wouldn't conflict with other error names in
+  # Bugzilla or other extensions.
+  #%]
+[% IF error == "example_my_error" %]
+   [% title = "Example Error Title" %]
+   This is the error message! It contains <em>some html</em>.
+[% END %]
diff --git a/BugsSite/images/favicon.ico b/BugsSite/images/favicon.ico
new file mode 100644
index 0000000..c8ade39
--- /dev/null
+++ b/BugsSite/images/favicon.ico
Binary files differ
diff --git a/BugsSite/importxml.pl b/BugsSite/importxml.pl
index 6e6f4a6..24eff8f 100755
--- a/BugsSite/importxml.pl
+++ b/BugsSite/importxml.pl
@@ -23,6 +23,7 @@
 #                 Vance Baarda <vrb@novell.com>
 #                 Guzman Braso <gbn@hqso.net>
 #                 Erik Purins <epurins@day1studios.com>
+#                 Frédéric Buclin <LpSolit@gmail.com>
 
 # This script reads in xml bug data from standard input and inserts
 # a new bug into bugzilla. Everything before the beginning <?xml line
@@ -53,22 +54,16 @@
 #
 #####################################################################
 
-# figure out which path this script lives in. Set the current path to
-# this and add it to @INC so this will work when run as part of mail
-# alias by the mailer daemon
-# since "use lib" is run at compile time, we need to enclose the
-# $::path declaration in a BEGIN block so that it is executed before
-# the rest of the file is compiled.
+use File::Basename qw(dirname);
+# MTAs may call this script from any directory, but it should always
+# run from this one so that it can find its modules.
 BEGIN {
-    $::path = $0;
-    $::path =~ m#(.*)/[^/]+#;
-    $::path = $1;
-    $::path ||= '.';    # $0 is empty at compile time.  This line will
-                        # have no effect on this script at runtime.
+    require File::Basename;
+    my $dir = $0; $dir =~ /(.*)/; $dir = $1; # trick taint
+    chdir(File::Basename::dirname($dir));
 }
 
-chdir $::path;
-use lib ($::path);
+use lib qw(. lib);
 # Data dumber is used for debugging, I got tired of copying it back in 
 # and then removing it. 
 #use Data::Dumper;
@@ -88,6 +83,7 @@
 use Bugzilla::Constants;
 use Bugzilla::Keyword;
 use Bugzilla::Field;
+use Bugzilla::Status;
 
 use MIME::Base64;
 use MIME::Parser;
@@ -285,6 +281,14 @@
     return $err;
 }
 
+# Converts and returns the input data as an array.
+sub _to_array {
+    my $value = shift;
+
+    $value = [$value] if !ref($value);
+    return @$value;
+}
+
 ###############################################################################
 # XML Handlers                                                                #
 ###############################################################################
@@ -322,19 +326,17 @@
     Error( "no urlbase set", "REOPEN", $exporter ) unless ($urlbase);
     my $def_product =
         new Bugzilla::Product( { name => $params->{"moved-default-product"} } )
-        || Error("Cannot import these bugs because an invalid default 
-                  product was defined for the target db."
-                  . $params->{"maintainer"} . " needs to fix the definitions of
-                  moved-default-product. \n", "REOPEN", $exporter);
+        || 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("Cannot import these bugs because an invalid default 
-              component was defined for the target db."
-              . $params->{"maintainer"} . " needs to fix the definitions of
-              moved-default-component.\n", "REOPEN", $exporter);
+    || 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);
 }
     
 
@@ -366,6 +368,7 @@
     $attachment{'isobsolete'} = $attach->{'att'}->{'isobsolete'} || 0;
     $attachment{'isprivate'}  = $attach->{'att'}->{'isprivate'} || 0;
     $attachment{'filename'}   = $attach->field('filename') || "file";
+    $attachment{'attacher'}   = $attach->field('attacher');
     # Attachment data is not exported in versions 2.20 and older.
     if (defined $attach->first_child('data') &&
             defined $attach->first_child('data')->{'att'}->{'encoding'}) {
@@ -379,8 +382,13 @@
         elsif ($encoding =~ /filename/) {
             # read the attachment file
             Error("attach_path is required", undef) unless ($attach_path);
-            my $attach_filename = $attach_path . "/" . $attach->field('data');
-            open(ATTACH_FH, $attach_filename) or
+            
+            my $filename = $attach->field('data');
+            # Remove any leading path data from the filename
+            $filename =~ s/(.*\/|.*\\)//gs;
+            
+            my $attach_filename = $attach_path . "/" . $filename;
+            open(ATTACH_FH, "<", $attach_filename) or
                 Error("cannot open $attach_filename", undef);
             $attachment{'data'} = do { local $/; <ATTACH_FH> };
             close ATTACH_FH;
@@ -464,9 +472,19 @@
    # append it to the log, which will go into the comments when we are done.
     foreach my $bugchild ( $bug->children() ) {
         Debug( "Parsing field: " . $bugchild->name, DEBUG_LEVEL );
+
+        # Skip the token if one is included. We don't want it included in
+        # the comments, and it is not used by the importer.
+        next if $bugchild->name eq 'token';
+
         if ( defined $all_fields{ $bugchild->name } ) {
-              $bug_fields{ $bugchild->name } =
-                  join( " ", $bug->children_text( $bugchild->name ) );
+            my @values = $bug->children_text($bugchild->name);
+            if (scalar @values > 1) {
+                $bug_fields{$bugchild->name} = \@values;
+            }
+            else {
+                $bug_fields{$bugchild->name} = $values[0];
+            }
         }
         else {
             $err .= "Unknown bug field \"" . $bugchild->name . "\"";
@@ -562,10 +580,12 @@
     $comments .= "This bug was previously known as _bug_ $bug_fields{'bug_id'} at ";
     $comments .= $urlbase . "show_bug.cgi?id=" . $bug_fields{'bug_id'} . "\n";
     if ( defined $bug_fields{'dependson'} ) {
-        $comments .= "This bug depended on bug(s) $bug_fields{'dependson'}.\n";
+        $comments .= "This bug depended on bug(s) " .
+                     join(' ', _to_array($bug_fields{'dependson'})) . ".\n";
     }
     if ( defined $bug_fields{'blocked'} ) {
-        $comments .= "This bug blocked bug(s) $bug_fields{'blocked'}.\n";
+        $comments .= "This bug blocked bug(s) " .
+                     join(' ', _to_array($bug_fields{'blocked'})) . ".\n";
     }
 
     # Now we process each of the fields in turn and make sure they contain
@@ -577,7 +597,7 @@
 
     # Each of these fields we will check for newlines and shove onto the array
     foreach my $field (qw(status_whiteboard bug_file_loc short_desc)) {
-        if (( defined $bug_fields{$field} ) && ( $bug_fields{$field} )) {
+        if ($bug_fields{$field}) {
             $bug_fields{$field} = clean_text( $bug_fields{$field} );
             push( @query,  $field );
             push( @values, $bug_fields{$field} );
@@ -916,7 +936,30 @@
         $err .= "This bug was marked DUPLICATE in the database ";
         $err .= "it was moved from.\n    Changing resolution to \"MOVED\"\n";
     } 
-    
+
+    # If there is at least 1 initial bug status different from UNCO, use it,
+    # else use the open bug status with the lowest sortkey (different from UNCO).
+    my @bug_statuses = @{Bugzilla::Status->can_change_to()};
+    @bug_statuses = grep { $_->name ne 'UNCONFIRMED' } @bug_statuses;
+
+    my $initial_status;
+    if (scalar(@bug_statuses)) {
+        $initial_status = $bug_statuses[0]->name;
+    }
+    else {
+        @bug_statuses = @{Bugzilla::Status->get_all()};
+        # Exclude UNCO and inactive bug statuses.
+        @bug_statuses = grep { $_->is_active && $_->name ne 'UNCONFIRMED'} @bug_statuses;
+        my @open_statuses = grep { $_->is_open } @bug_statuses;
+        if (scalar(@open_statuses)) {
+            $initial_status = $open_statuses[0]->name;
+        }
+        else {
+            # There is NO other open bug statuses outside UNCO???
+            Error("no open bug statuses available.");
+        }
+    }
+
     if($has_status){
         if($valid_status){
             if($is_open){
@@ -927,7 +970,7 @@
                 }
                 if($changed_owner){
                     if($everconfirmed){  
-                        $status = "NEW";
+                        $status = $initial_status;
                     }
                     else{
                         $status = "UNCONFIRMED";
@@ -941,9 +984,9 @@
                 if($everconfirmed){
                     if($status eq "UNCONFIRMED"){
                         $err .= "Bug Status was UNCONFIRMED but everconfirmed was true\n";
-                        $err .= "   Setting status to NEW\n";
+                        $err .= "   Setting status to $initial_status\n";
                         $err .= "Resetting votes to 0\n" if ( $bug_fields{'votes'} );
-                        $status = "NEW";
+                        $status = $initial_status;
                     }
                 }
                 else{ # $everconfirmed is false
@@ -958,8 +1001,8 @@
                if(!$has_res){
                    $err .= "Missing Resolution. Setting status to ";
                    if($everconfirmed){
-                       $status = "NEW";
-                       $err .= "NEW\n";
+                       $status = $initial_status;
+                       $err .= "$initial_status\n";
                    }
                    else{
                        $status = "UNCONFIRMED";
@@ -975,7 +1018,7 @@
         }
         else{ # $valid_status is false
             if($everconfirmed){  
-                $status = "NEW";
+                $status = $initial_status;
             }
             else{
                 $status = "UNCONFIRMED";
@@ -989,7 +1032,7 @@
     }
     else{ #has_status is false
         if($everconfirmed){  
-            $status = "NEW";
+            $status = $initial_status;
         }
         else{
             $status = "UNCONFIRMED";
@@ -1008,21 +1051,47 @@
     push( @query,  "bug_status" );
     push( @values, $status );
 
-    # Custom fields
-    foreach my $custom_field (Bugzilla->custom_field_names) {
-        next unless defined($bug_fields{$custom_field});
-        my $field = new Bugzilla::Field({name => $custom_field});
+    # Custom fields - Multi-select fields have their own table.
+    my %multi_select_fields;
+    foreach my $field (Bugzilla->active_custom_fields) {
+        my $custom_field = $field->name;
+        my $value = $bug_fields{$custom_field};
+        next unless defined $value;
         if ($field->type == FIELD_TYPE_FREETEXT) {
             push(@query, $custom_field);
-            push(@values, clean_text($bug_fields{$custom_field}));
+            push(@values, clean_text($value));
+        } elsif ($field->type == FIELD_TYPE_TEXTAREA) {
+            push(@query, $custom_field);
+            push(@values, $value);
         } elsif ($field->type == FIELD_TYPE_SINGLE_SELECT) {
-            my $is_well_formed = check_field($custom_field, scalar $bug_fields{$custom_field},
-                                             undef, ERR_LEVEL);
+            my $is_well_formed = check_field($custom_field, $value, undef, ERR_LEVEL);
             if ($is_well_formed) {
                 push(@query, $custom_field);
-                push(@values, $bug_fields{$custom_field});
+                push(@values, $value);
             } else {
-                $err .= "Skipping illegal value \"$bug_fields{$custom_field}\" in $custom_field.\n" ;
+                $err .= "Skipping illegal value \"$value\" in $custom_field.\n" ;
+            }
+        } elsif ($field->type == FIELD_TYPE_MULTI_SELECT) {
+            my @legal_values;
+            foreach my $item (_to_array($value)) {
+                my $is_well_formed = check_field($custom_field, $item, undef, ERR_LEVEL);
+                if ($is_well_formed) {
+                    push(@legal_values, $item);
+                } else {
+                    $err .= "Skipping illegal value \"$item\" in $custom_field.\n" ;
+                }
+            }
+            if (scalar @legal_values) {
+                $multi_select_fields{$custom_field} = \@legal_values;
+            }
+        } elsif ($field->type == FIELD_TYPE_DATETIME) {
+            eval { $value = Bugzilla::Bug->_check_datetime_field($value); };
+            if ($@) {
+                $err .= "Skipping illegal value \"$value\" in $custom_field.\n" ;
+            }
+            else {
+                push(@query, $custom_field);
+                push(@values, $value);
             }
         } else {
             $err .= "Type of custom field $custom_field is an unhandled FIELD_TYPE: " .
@@ -1063,7 +1132,7 @@
     if ( defined $bug_fields{'cc'} ) {
         my %ccseen;
         my $sth_cc = $dbh->prepare("INSERT INTO cc (bug_id, who) VALUES (?,?)");
-        foreach my $person ( split( /[\s,]+/, $bug_fields{'cc'} ) ) {
+        foreach my $person (_to_array($bug_fields{'cc'})) {
             next unless $person;
             my $uid;
             if ($uid = login_to_id($person)) {
@@ -1108,6 +1177,16 @@
             undef, $keywordstring, $id )
     }
 
+    # Insert values of custom multi-select fields. They have already
+    # been validated.
+    foreach my $custom_field (keys %multi_select_fields) {
+        my $sth = $dbh->prepare("INSERT INTO bug_$custom_field
+                                 (bug_id, value) VALUES (?, ?)");
+        foreach my $value (@{$multi_select_fields{$custom_field}}) {
+            $sth->execute($id, $value);
+        }
+    }
+
     # Parse bug flags
     foreach my $bflag ( $bug->children('flag')) {
         next unless ( defined($bflag) );
@@ -1131,13 +1210,16 @@
             $err .= "   Marking attachment public\n";
             $att->{'isprivate'} = 0;
         }
+
+        my $attacher_id = $att->{'attacher'} ? login_to_id($att->{'attacher'}) : undef;
+
         $dbh->do("INSERT INTO attachments 
-                 (bug_id, creation_ts, filename, description, mimetype, 
-                 ispatch, isprivate, isobsolete, submitter_id) 
-                 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
-            undef, $id, $att->{'date'}, $att->{'filename'},
+                 (bug_id, creation_ts, modification_time, filename, description,
+                 mimetype, ispatch, isprivate, isobsolete, submitter_id) 
+                 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+            undef, $id, $att->{'date'}, $att->{'date'}, $att->{'filename'},
             $att->{'desc'}, $att->{'ctype'}, $att->{'ispatch'},
-            $att->{'isprivate'}, $att->{'isobsolete'}, $exporterid);
+            $att->{'isprivate'}, $att->{'isobsolete'}, $attacher_id || $exporterid);
         my $att_id   = $dbh->bz_last_key( 'attachments', 'attach_id' );
         my $att_data = $att->{'data'};
         my $sth = $dbh->prepare("INSERT INTO attach_data (id, thedata) 
@@ -1145,7 +1227,18 @@
         trick_taint($att_data);
         $sth->bind_param( 1, $att_data, $dbh->BLOB_TYPE );
         $sth->execute();
+
         $comments .= "Imported an attachment (id=$att_id)\n";
+        if (!$attacher_id) {
+            if ($att->{'attacher'}) {
+                $err .= "The original submitter of attachment $att_id was\n   ";
+                $err .= $att->{'attacher'} . ", but he doesn't have an account here.\n";
+            }
+            else {
+                $err .= "The original submitter of attachment $att_id is unknown.\n";
+            }
+            $err .= "   Reassigning to the person who moved it here: $exporter_login.\n";
+        }
 
         # Process attachment flags
         foreach my $aflag (@{ $att->{'flags'} }) {
@@ -1176,6 +1269,7 @@
                      VALUES (?,?,?,?,?,?)", undef,
         $id, $exporterid, $timestamp, $worktime, $private, $long_description
     );
+    Bugzilla::Bug->new($id)->_sync_fulltext('new_bug');
 
     # Add this bug to each group of which its product is a member.
     my $sth_group = $dbh->prepare("INSERT INTO bug_group_map (bug_id, group_id) 
@@ -1263,7 +1357,7 @@
  Options:
        -? --help        brief help message
        -v --verbose     print error and debug information. 
-                        Mulltiple -v increases verbosity
+                        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.)
diff --git a/BugsSite/index.cgi b/BugsSite/index.cgi
index 7d1525b..89880d1 100755
--- a/BugsSite/index.cgi
+++ b/BugsSite/index.cgi
@@ -29,7 +29,7 @@
 use strict;
 
 # Include the Bugzilla CGI and general utility library.
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -46,7 +46,9 @@
 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 (Bugzilla->params->{'sslbase'} ne '' and Bugzilla->params->{'ssl'} ne 'never') {
+if ($cgi->protocol ne 'https' && Bugzilla->params->{'sslbase'} ne ''
+    && Bugzilla->params->{'ssl'} ne 'never')
+{
     $cgi->require_https(Bugzilla->params->{'sslbase'});
 }
 
diff --git a/BugsSite/install-module.pl b/BugsSite/install-module.pl
new file mode 100755
index 0000000..ca99ac2
--- /dev/null
+++ b/BugsSite/install-module.pl
@@ -0,0 +1,161 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Everything Solved, Inc.
+# Portions created by Everything Solved are Copyright (C) 2007
+# Everything Solved, Inc. All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+use strict;
+use warnings;
+
+# Have to abs_path('.') or calls to Bugzilla modules won't work once
+# CPAN has chdir'ed around. We do all of this in this funny order to
+# make sure that we use the lib/ modules instead of the base Perl modules,
+# in case the lib/ modules are newer.
+use Cwd qw(abs_path);
+use lib abs_path('.');
+use Bugzilla::Constants;
+use lib abs_path(bz_locations()->{ext_libpath});
+
+use Bugzilla::Install::CPAN;
+
+use Bugzilla::Constants;
+use Bugzilla::Install::Requirements;
+
+use Data::Dumper;
+use Getopt::Long;
+use Pod::Usage;
+
+our %switch;
+
+GetOptions(\%switch, 'all|a', 'upgrade-all|u', 'show-config|s', 'global|g',
+                     '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";
+    exit;
+}
+
+pod2usage({ -verbose => 0 }) if (!%switch && !@ARGV);
+
+set_cpan_config($switch{'global'});
+
+if ($switch{'show-config'}) {
+  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";
+}
+
+if ($switch{'all'} || $switch{'upgrade-all'}) {
+    my @modules;
+    if ($switch{'upgrade-all'}) {
+        @modules = (@{REQUIRED_MODULES()}, @{OPTIONAL_MODULES()});
+        push(@modules, DB_MODULE->{$_}->{dbd}) foreach (keys %{DB_MODULE()});
+    }
+    else {
+        # This is the only time we need a Bugzilla-related module, so
+        # we require them down here. Otherwise this script can be run from
+        # any directory, even outside of Bugzilla itself.
+        my $reqs = check_requirements(0);
+        @modules = (@{$reqs->{missing}}, @{$reqs->{optional}});
+        my $dbs = DB_MODULE;
+        foreach my $db (keys %$dbs) {
+            push(@modules, $dbs->{$db}->{dbd})
+                if !have_vers($dbs->{$db}->{dbd}, 0);
+        }
+    }
+    foreach my $module (@modules) {
+        my $cpan_name = $module->{module};
+        # --all shouldn't include mod_perl2, because it can have some complex
+        # configuration, and really should be installed on its own.
+        next if $cpan_name eq 'mod_perl2';
+        next if $cpan_name eq 'DBD::Oracle' and !$ENV{ORACLE_HOME};
+        install_module($cpan_name, $can_notest);
+    }
+}
+
+foreach my $module (@ARGV) {
+    install_module($module, $can_notest);
+}
+
+__END__
+
+=head1 NAME
+
+install-module.pl - Installs or upgrades modules from CPAN.
+This script does not run on Windows.
+
+=head1 SYNOPSIS
+
+  ./install-module.pl Module::Name [--global]
+  ./install-module.pl --all [--global]
+  ./install-module.pl --all-upgrade [--global]
+  ./install-module.pl --show-config
+
+  Do "./install-module.pl --help" for more information.
+
+=head1 OPTIONS
+
+=over
+
+=item B<Module::Name>
+
+The name of a module that you want to install from CPAN. This is the
+same thing that you'd give to the C<install> command in the CPAN shell.
+
+You can specify multiple module names separated by a space to install
+multiple modules.
+
+=item B<--global>
+
+This makes install-module install modules globally for all applications,
+instead of just for Bugzilla.
+
+On most systems, you have to be root for C<--global> to work.
+
+=item B<--all>
+
+This will make install-module do its best to install every required
+and optional module that is not installed that Bugzilla can use.
+
+Some modules may fail to install. You can run checksetup.pl to see
+which installed properly.
+
+=item B<--upgrade-all>
+
+This is like C<--all>, except it forcibly installs the very latest
+version of every Bugzilla prerequisite, whether or not you already
+have them installed.
+
+=item B<--show-config>
+
+Prints out the CPAN configuration in raw Perl format. Useful for debugging.
+
+=item B<--help>
+
+Shows this help.
+
+=back
diff --git a/BugsSite/js/duplicates.js b/BugsSite/js/duplicates.js
deleted file mode 100644
index ccad539..0000000
--- a/BugsSite/js/duplicates.js
+++ /dev/null
@@ -1,153 +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>
- */
-
-// When the XUL window finishes loading, load the RDF data into it.
-window.addEventListener('load', loadData, false);
-
-// The base URL of this Bugzilla installation; derived from the page's URL.
-var gBaseURL = window.location.href.replace(/(jar:)?(.*?)duplicates\.(jar!|xul).*/, "$2");
-
-function loadData()
-{
-  // Loads the duplicates data as an RDF data source, attaches it to the tree,
-  // and rebuilds the tree to display the data.
-
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-  
-  // Get the RDF service so we can use it to load the data source.
-  var rdfService = 
-    Components
-      .classes["@mozilla.org/rdf/rdf-service;1"]
-        .getService(Components.interfaces.nsIRDFService);
-
-  // When a bug report loads in the content iframe, a 'load' event bubbles up
-  // to the browser window, which calls this load handler again, which reloads
-  // the RDF data, which causes the tree to lose the selection.  To prevent
-  // this, we have to remove this handler.
-  window.removeEventListener('load', loadData, false);
-
-  // The URL of the RDF file; by default for performance a static file 
-  // generated by collectstats.pl, but a call to duplicates.cgi if the page's 
-  // URL contains parameters (so we can dynamically generate the RDF data 
-  // based on those parameters).
-  var dataURL = gBaseURL + "data/duplicates.rdf";
-  if (window.location.href.search(/duplicates\.xul\?.+/) != -1)
-    dataURL = window.location.href.replace(/(duplicates\.jar!\/)?duplicates\.xul\?/, "duplicates.cgi?ctype=rdf&");
-  
-  // Get the data source and add it to the XUL tree's database to populate
-  // the tree with the data.
-  var dataSource = rdfService.GetDataSource(dataURL);
-  
-  // If we're using the static file, add an observer that detects failed loads
-  // (in case this installation isn't generating the file nightly) and redirects
-  // to the CGI version when loading of the static version fails.
-  if (window.location.href.search(/duplicates\.xul\?.+/) == -1)
-  {
-    var sink = dataSource.QueryInterface(Components.interfaces.nsIRDFXMLSink);
-    sink.addXMLSinkObserver(StaticDataSourceObserver);
-  }
-  
-  // Add the data source to the tree, set the tree's "ref" attribute
-  // to the base URL of the data source, and rebuild the tree.
-  var resultsTree = document.getElementById('results-tree');
-  resultsTree.database.AddDataSource(dataSource);
-  resultsTree.setAttribute('ref', gBaseURL + "data/duplicates.rdf");
-  resultsTree.builder.rebuild();
-}
-
-function getBugURI()
-{
-  var tree = document.getElementById('results-tree');
-  var index = tree.currentIndex;
-
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-  var builder = tree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
-  var resource = builder.getResourceAtIndex(index);
-
-  return resource.Value;
-}
-
-function loadBugInWindow()
-{
-  // Loads the selected bug in the browser window, replacing the duplicates report
-  // with the bug report.
-
-  var bugURI = getBugURI();
-  window.location = bugURI;
-}
-
-function loadBugInPane()
-{
-  // Loads the selected bug in the iframe-based content pane that is part of 
-  // this XUL document.
-
-  var splitter = document.getElementById('report-content-splitter');
-  var state = splitter.getAttribute('state');
-  if (state != "collapsed")
-  {
-    var bugURI = getBugURI();
-    var browser = document.getElementById('content-browser');
-    browser.setAttribute('src', bugURI);
-  }
-}
-
-var StaticDataSourceObserver = {
-  onBeginLoad: function(aSink) { } , 
-  onInterrupt: function(aSink) { } , 
-  onResume: function(aSink) { } , 
-  onEndLoad: function(aSink)
-  {
-    // Removes the observer from the data source so it doesn't stay around
-    // when duplicates.xul is reloaded from scratch.
-    
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    
-    aSink.removeXMLSinkObserver(StaticDataSourceObserver);
-  } , 
-  onError: function(aSink, aStatus, aErrorMsg)
-  {
-    // Tries the dynamic data source since the static one failed to load.
-    
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    
-    // Get the RDF service so we can use it to load the data source.
-    var rdfService = 
-      Components
-        .classes["@mozilla.org/rdf/rdf-service;1"]
-          .getService(Components.interfaces.nsIRDFService);
-    
-    // Remove the observer from the data source so it doesn't stay around
-    // when duplicates.xul is reloaded from scratch.
-    aSink.removeXMLSinkObserver(StaticDataSourceObserver);
-    
-    // Remove the static data source from the tree.
-    var oldDataSource = aSink.QueryInterface(Components.interfaces.nsIRDFDataSource);
-    var resultsTree = document.getElementById('results-tree');
-    resultsTree.database.RemoveDataSource(oldDataSource);
-
-    // Munge the URL to point to the CGI and load the data source.
-    var dataURL = gBaseURL + "duplicates.cgi?ctype=rdf";
-    newDataSource = rdfService.GetDataSource(dataURL);
-    
-    // Add the data source to the tree and rebuild the tree with the new data.
-    resultsTree.database.AddDataSource(newDataSource);
-    resultsTree.builder.rebuild();
-  }
-};
diff --git a/BugsSite/js/field.js b/BugsSite/js/field.js
new file mode 100644
index 0000000..a1b6245
--- /dev/null
+++ b/BugsSite/js/field.js
@@ -0,0 +1,339 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Everything Solved, Inc.
+ * Portions created by Everything Solved are Copyright (C) 2007 Everything
+ * Solved, Inc. All Rights Reserved.
+ *
+ * Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ *                 Guy Pyrzak <guy.pyrzak@gmail.com>
+ */
+
+/* This library assumes that the needed YUI libraries have been loaded 
+   already. */
+
+function createCalendar(name) {
+    var cal = new YAHOO.widget.Calendar('calendar_' + name, 
+                                        'con_calendar_' + name);
+    YAHOO.bugzilla['calendar_' + name] = cal;
+    var field = document.getElementById(name);
+    cal.selectEvent.subscribe(setFieldFromCalendar, field, false);
+    updateCalendarFromField(field);
+    cal.render();
+}
+
+/* The onclick handlers for the button that shows the calendar. */
+function showCalendar(field_name) {
+    var calendar  = YAHOO.bugzilla["calendar_" + field_name];
+    var field     = document.getElementById(field_name);
+    var button    = document.getElementById('button_calendar_' + field_name);
+
+    bz_overlayBelow(calendar.oDomContainer, field);
+    calendar.show();
+    button.onclick = function() { hideCalendar(field_name); };
+
+    // Because of the way removeListener works, this has to be a function
+    // attached directly to this calendar.
+    calendar.bz_myBodyCloser = function(event) {
+        var container = this.oDomContainer;
+        var target    = YAHOO.util.Event.getTarget(event);
+        if (target != container && target != button
+            && !YAHOO.util.Dom.isAncestor(container, target))
+        {
+            hideCalendar(field_name);
+        }
+    };
+
+    // If somebody clicks outside the calendar, hide it.
+    YAHOO.util.Event.addListener(document.body, 'click', 
+                                 calendar.bz_myBodyCloser, calendar, true);
+
+    // Make Esc close the calendar.
+    calendar.bz_escCal = function (event) {
+        var key = YAHOO.util.Event.getCharCode(event);
+        if (key == 27) {
+            hideCalendar(field_name);
+        }
+    };
+    YAHOO.util.Event.addListener(document.body, 'keydown', calendar.bz_escCal);
+}
+
+function hideCalendar(field_name) {
+    var cal = YAHOO.bugzilla["calendar_" + field_name];
+    cal.hide();
+    var button = document.getElementById('button_calendar_' + field_name);
+    button.onclick = function() { showCalendar(field_name); };
+    YAHOO.util.Event.removeListener(document.body, 'click',
+                                    cal.bz_myBodyCloser);
+    YAHOO.util.Event.removeListener(document.body, 'keydown', cal.bz_escCal);
+}
+
+/* This is the selectEvent for our Calendar objects on our custom 
+ * DateTime fields.
+ */
+function setFieldFromCalendar(type, args, date_field) {
+    var dates = args[0];
+    var setDate = dates[0];
+
+    // We can't just write the date straight into the field, because there 
+    // might already be a time there.
+    var timeRe = /\b(\d{1,2}):(\d\d)(?::(\d\d))?/;
+    var currentTime = timeRe.exec(date_field.value);
+    var d = new Date(setDate[0], setDate[1] - 1, setDate[2]);
+    if (currentTime) {
+        d.setHours(currentTime[1], currentTime[2]);
+        if (currentTime[3]) {
+            d.setSeconds(currentTime[3]);
+        }
+    }
+
+    var year = d.getFullYear();
+    // JavaScript's "Date" represents January as 0 and December as 11.
+    var month = d.getMonth() + 1;
+    if (month < 10) month = '0' + String(month);
+    var day = d.getDate();
+    if (day < 10) day = '0' + String(day);
+    var dateStr = year + '-' + month  + '-' + day;
+
+    if (currentTime) {
+        var minutes = d.getMinutes();
+        if (minutes < 10) minutes = '0' + String(minutes);
+        var seconds = d.getSeconds();
+        if (seconds > 0 && seconds < 10) {
+            seconds = '0' + String(seconds);
+        }
+
+        dateStr = dateStr + ' ' + d.getHours() + ':' + minutes;
+        if (seconds) dateStr = dateStr + ':' + seconds;
+    }
+
+    date_field.value = dateStr;
+    hideCalendar(date_field.id);
+}
+
+/* Sets the calendar based on the current field value. 
+ */ 
+function updateCalendarFromField(date_field) {
+    var dateRe = /(\d\d\d\d)-(\d\d?)-(\d\d?)/;
+    var pieces = dateRe.exec(date_field.value);
+    if (pieces) {
+        var cal = YAHOO.bugzilla["calendar_" + date_field.id];
+        cal.select(new Date(pieces[1], pieces[2] - 1, pieces[3]));
+        var selectedArray = cal.getSelectedDates();
+        var selected = selectedArray[0];
+        cal.cfg.setProperty("pagedate", (selected.getMonth() + 1) + '/' 
+                                        + selected.getFullYear());
+        cal.render();
+    }
+}
+
+
+/* Hide input fields and show the text with (edit) next to it */  
+function hideEditableField( container, input, action, field_id, original_value ) {
+    YAHOO.util.Dom.setStyle(container, 'display', 'inline');
+    YAHOO.util.Dom.setStyle(input, 'display', 'none');
+    YAHOO.util.Event.addListener(action, 'click', showEditableField,
+                                 new Array(container, input));
+    if(field_id != ""){
+        YAHOO.util.Event.addListener(window, 'load', checkForChangedFieldValues,
+                        new Array(container, input, field_id, original_value));
+    }
+}
+
+/* showEditableField (e, ContainerInputArray)
+ * Function hides the (edit) link and the text and displays the input
+ *
+ * var e: the event
+ * var ContainerInputArray: An array containing the (edit) and text area and the input being displayed
+ * var ContainerInputArray[0]: the conainer that will be hidden usually shows the (edit) text
+ * var ContainerInputArray[1]: the input area and label that will be displayed
+ *
+ */
+function showEditableField (e, ContainerInputArray) {
+    var inputs = new Array();
+    var inputArea = YAHOO.util.Dom.get(ContainerInputArray[1]);    
+    if ( ! inputArea ){
+        YAHOO.util.Event.preventDefault(e);
+        return;
+    }
+    YAHOO.util.Dom.setStyle(ContainerInputArray[0], 'display', 'none');
+    YAHOO.util.Dom.setStyle(inputArea, 'display', 'inline');
+    if ( inputArea.tagName.toLowerCase() == "input" ) {
+        inputs.push(inputArea);
+    } else {
+        inputs = inputArea.getElementsByTagName('input');
+    }
+    if ( inputs.length > 0 ) {
+        // focus on the first field, this makes it easier to edit
+        inputs[0].focus();
+        inputs[0].select();
+    }
+    YAHOO.util.Event.preventDefault(e);
+}
+
+
+/* checkForChangedFieldValues(e, array )
+ * Function checks if after the autocomplete by the browser if the values match the originals.
+ *   If they don't match then hide the text and show the input so users don't get confused.
+ *
+ * var e: the event
+ * var ContainerInputArray: An array containing the (edit) and text area and the input being displayed
+ * var ContainerInputArray[0]: the conainer that will be hidden usually shows the (edit) text
+ * var ContainerInputArray[1]: the input area and label that will be displayed
+ * var ContainerInputArray[2]: the field that is on the page, might get changed by browser autocomplete 
+ * var ContainerInputArray[3]: the original value from the page loading.
+ *
+ */  
+function checkForChangedFieldValues(e, ContainerInputArray ) {
+    var el = document.getElementById(ContainerInputArray[2]);
+    var unhide = false;
+    if ( el ) {
+        if ( el.value != ContainerInputArray[3] ||
+            ( el.value == "" && el.id != "alias") ) {
+            unhide = true;
+        }
+        else {
+            var set_default = document.getElementById("set_default_" +
+                                                      ContainerInputArray[2]);
+            if ( set_default ) {
+                if(set_default.checked){
+                    unhide = true;
+                }              
+            }
+        }
+    }
+    if(unhide){
+        YAHOO.util.Dom.setStyle(ContainerInputArray[0], 'display', 'none');
+        YAHOO.util.Dom.setStyle(ContainerInputArray[1], 'display', 'inline');
+    }
+
+}
+
+function hideAliasAndSummary(short_desc_value, alias_value) {
+    // check the short desc field
+    hideEditableField( 'summary_alias_container','summary_alias_input',
+                       'editme_action','short_desc', short_desc_value);  
+    // check that the alias hasn't changed
+    var bz_alias_check_array = new Array('summary_alias_container',
+                                     'summary_alias_input', 'alias', alias_value);
+    YAHOO.util.Event.addListener( window, 'load', checkForChangedFieldValues,
+                                 bz_alias_check_array);
+}
+
+function showPeopleOnChange( field_id_list ) {
+    for(var i = 0; i < field_id_list.length; i++) {
+        YAHOO.util.Event.addListener( field_id_list[i],'change', showEditableField,
+                                      new Array('bz_qa_contact_edit_container',
+                                                'bz_qa_contact_input'));
+        YAHOO.util.Event.addListener( field_id_list[i],'change',showEditableField,
+                                      new Array('bz_assignee_edit_container',
+                                                'bz_assignee_input'));
+    }
+}
+
+function assignToDefaultOnChange(field_id_list) {
+    showPeopleOnChange( field_id_list );
+    for(var i = 0; i < field_id_list.length; i++) {
+        YAHOO.util.Event.addListener( field_id_list[i],'change', setDefaultCheckbox,
+                                      'set_default_assignee');
+        YAHOO.util.Event.addListener( field_id_list[i],'change',setDefaultCheckbox,
+                                      'set_default_qa_contact');    
+    }
+}
+
+function initDefaultCheckbox(field_id){
+    YAHOO.util.Event.addListener( 'set_default_' + field_id,'change', boldOnChange,
+                                  'set_default_' + field_id);
+    YAHOO.util.Event.addListener( window,'load', checkForChangedFieldValues,
+                                  new Array( 'bz_' + field_id + '_edit_container',
+                                             'bz_' + field_id + '_input',
+                                             'set_default_' + field_id ,'1'));
+    
+    YAHOO.util.Event.addListener( window, 'load', boldOnChange,
+                                 'set_default_' + field_id ); 
+}
+
+function showHideStatusItems(e, dupArrayInfo) {
+    var el = document.getElementById('bug_status');
+    // finish doing stuff based on the selection.
+    if ( el ) {
+        showDuplicateItem(el);
+        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');  
+        }
+        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');
+        }
+    }
+}
+
+function showDuplicateItem(e) {
+    var resolution = document.getElementById('resolution');
+    var bug_status = document.getElementById('bug_status');
+    var dup_id = document.getElementById('dup_id');
+    if (resolution) {
+        if (resolution.value == 'DUPLICATE' && bz_isValueInArray( close_status_array, bug_status.value) ) {
+            // hide resolution show duplicate
+            YAHOO.util.Dom.setStyle('duplicate_settings', 'display', 'inline');
+            YAHOO.util.Dom.setStyle('dup_id_discoverable', 'display', 'none');
+            dup_id.focus();
+            dup_id.select();
+        }
+        else {
+            YAHOO.util.Dom.setStyle('duplicate_settings', 'display', 'none');
+            YAHOO.util.Dom.setStyle('dup_id_discoverable', 'display', 'block');
+            dup_id.blur();
+        }
+    }
+    YAHOO.util.Event.preventDefault(e); //prevents the hyperlink from going to the url in the href.
+}
+
+function setResolutionToDuplicate(e, duplicate_or_move_bug_status) {
+    var status = document.getElementById('bug_status');
+    var resolution = document.getElementById('resolution');
+    YAHOO.util.Dom.setStyle('dup_id_discoverable', 'display', 'none');
+    status.value = duplicate_or_move_bug_status;
+    resolution.value = "DUPLICATE";
+    showHideStatusItems("", ["",""]);
+    YAHOO.util.Event.preventDefault(e);
+}
+
+function setDefaultCheckbox(e, field_id ) { 
+    var el = document.getElementById(field_id);
+    var elLabel = document.getElementById(field_id + "_label");
+    if( el && elLabel ) {
+        el.checked = "true";
+        YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'bold');
+    }
+}
+
+function boldOnChange(e, field_id){
+    var el = document.getElementById(field_id);
+    var elLabel = document.getElementById(field_id + "_label");
+    if( el && elLabel ) {
+        if( el.checked ){
+            YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'bold');
+        }
+        else{
+            YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'normal');
+        }
+    }
+}
diff --git a/BugsSite/js/help.js b/BugsSite/js/help.js
new file mode 100644
index 0000000..938a73a
--- /dev/null
+++ b/BugsSite/js/help.js
@@ -0,0 +1,108 @@
+/* ***** 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/BugsSite/js/params.js b/BugsSite/js/params.js
new file mode 100644
index 0000000..4537407
--- /dev/null
+++ b/BugsSite/js/params.js
@@ -0,0 +1,61 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * The Initial Developer of the Original Code is Marc Schumann.
+ * Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
+ * All rights reserved.
+ *
+ * Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ */
+
+function sortedList_moveItem(paramName, direction, separator) {
+    var select = document.getElementById('select_' + paramName);
+    var inputField = document.getElementById('input_' + paramName);
+    var currentIndex = select.selectedIndex;
+    var newIndex = currentIndex + direction;
+    var optionCurrentIndex;
+    var optionNewIndex;
+
+    /* Return if no selection */
+    if (currentIndex < 0) return;
+    /* Return if trying to move upward out of list */
+    if (newIndex < 0) return;
+    /* Return if trying to move downward out of list */
+    if (newIndex >= select.length) return;
+
+    /* Move selection */
+    optionNewIndex = select.options[newIndex];
+    optionCurrentIndex = select.options[currentIndex];
+    /* Because some browsers don't accept the same option object twice in a
+     * selection list, we need to put a blank option here first */
+    select.options[newIndex] = new Option();
+    select.options[currentIndex] = optionNewIndex;
+    select.options[newIndex] = optionCurrentIndex;
+    select.selectedIndex = newIndex;
+    populateInputField(select, inputField, separator);
+}
+
+function populateInputField(select, inputField, separator) {
+    var i;
+    var stringRepresentation = '';
+
+    for (i = 0; i < select.length; i++) {
+        if (select.options[i].value == separator) {
+            break;
+        }
+        if (stringRepresentation != '') {
+            stringRepresentation += ',';
+        }
+        stringRepresentation += select.options[i].value;
+    }
+    inputField.value = stringRepresentation;
+}
diff --git a/BugsSite/js/productform.js b/BugsSite/js/productform.js
index 7cf07d7..f9b420c 100644
--- a/BugsSite/js/productform.js
+++ b/BugsSite/js/productform.js
@@ -18,139 +18,164 @@
  * Contributor(s): Christian Reis <kiko@async.com.br>
  */
 
-/* this file contains functions to update form controls based on a
- * collection of javascript arrays containing strings */
+// Functions to update form select elements based on a
+// collection of javascript arrays containing strings.
 
-/* selectClassification reads the selection from f.classification and updates
- * f.product accordingly
- *     - f: a form containing classification, product, component, varsion and
- *       target_milestone select boxes.
- * globals (3vil!):
- *     - prods, indexed by classification name
- *     - first_load: boolean, specifying if it is the first time we load
- *       the query page.
- *     - last_sel: saves our last selection list so we know what has
- *       changed, and optimize for additions.
+/**
+ * Reads the selected classifications and updates product, component,
+ * version and milestone lists accordingly.
+ *
+ * @param  classfield Select element that contains classifications.
+ * @param  product    Select element that contains products.
+ * @param  component  Select element that contains components. Can be null if
+ *                    there is no such element to update.
+ * @param  version    Select element that contains versions. Can be null if
+ *                    there is no such element to update.
+ * @param  milestone  Select element that contains milestones. Can be null if
+ *                    there is no such element to update.
+ *
+ * @global prods      Array of products indexed by classification name.
+ * @global first_load Boolean; true if this is the first time this page loads
+ *                    or false if not.
+ * @global last_sel   Array that contains last list of products so we know what
+ *                    has changed, and optimize for additions.
  */
 function selectClassification(classfield, product, component, version, milestone) {
-    /* this is to avoid handling events that occur before the form
-     * itself is ready, which could happen in buggy browsers.
-     */
-    if (!classfield) {
+    // This is to avoid handling events that occur before the form
+    // itself is ready, which could happen in buggy browsers.
+    if (!classfield)
         return;
-    }
 
-    /* if this is the first load and nothing is selected, no need to
-     * merge and sort all components; perl gives it to us sorted.
-     */
+    // If this is the first load and nothing is selected, no need to
+    // merge and sort all lists; they are created sorted.
     if ((first_load) && (classfield.selectedIndex == -1)) {
         first_load = false;
         return;
     }
     
-    /* don't reset first_load as done in selectProduct.  That's because we
-       want selectProduct to handle the first_load attribute
-    */
+    // Don't reset first_load as done in selectProduct. That's because we
+    // want selectProduct to handle the first_load attribute.
 
-    /* - sel keeps the array of classifications we are selected. 
-     * - merging says if it is a full list or just a list of classifications 
-     *   that were added to the current selection.
-     */
-    var merging = false;
+    // Stores classifications that are selected.
     var sel = Array();
 
-    /* if nothing selected, pick all */
+    // True if sel array has a full list or false if sel contains only
+    // new classifications that are to be merged to the current list.
+    var merging = false;
+
+    // If nothing selected, pick all.
     var findall = classfield.selectedIndex == -1;
     sel = get_selection(classfield, findall, false);
     if (!findall) {
-        /* save sel for the next invocation of selectClassification() */
+        // Save sel for the next invocation of selectClassification().
         var tmp = sel;
     
-        /* this is an optimization: if we have just added classifications to an
-         * existing selection, no need to clear the form controls and add 
-         * everybody again; just merge the new ones with the existing 
-         * options.
-	 */
+        // This is an optimization: if we have just added classifications to an
+        // existing selection, no need to clear the form elements and add
+        // everything again; just merge the new ones with the existing
+        // options.
         if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
             sel = fake_diff_array(sel, last_sel);
             merging = true;
         }
         last_sel = tmp;
     }
-    /* save original options selected */
-    var saved_prods = get_selection(product, false, true);
 
-    /* do the actual fill/update, reselect originally selected options */
-    updateSelect(prods, sel, product, merging);
+    // Save original options selected.
+    var saved_prods = get_selection(product, false, true, null);
+
+    // Do the actual fill/update, reselect originally selected options.
+    updateSelect(prods, sel, product, merging, null);
     restoreSelection(product, saved_prods);
-    selectProduct(product, component, version, milestone);
+    selectProduct(product, component, version, milestone, null);
 }
 
-
-/* selectProduct reads the selection from the product control and
- * updates version, component and milestone controls accordingly.
- * 
- *     - product, component, version and milestone: form controls
+/**
+ * Reads the selected products and updates component, version and milestone
+ * lists accordingly.
  *
- * globals (3vil!):
- *     - cpts, vers, tms: array of arrays, indexed by product name. the
- *       subarrays contain a list of names to be fed to the respective
- *       selectboxes. For bugzilla, these are generated with perl code
- *       at page start.
- *     - first_load: boolean, specifying if it is the first time we load
- *       the query page.
- *     - last_sel: saves our last selection list so we know what has
- *       changed, and optimize for additions. 
+ * @param  product    Select element that contains products.
+ * @param  component  Select element that contains components. Can be null if
+ *                    there is no such element to update.
+ * @param  version    Select element that contains versions. Can be null if
+ *                    there is no such element to update.
+ * @param  milestone  Select element that contains milestones. Can be null if
+ *                    there is no such element to update.
+ * @param  anyval     Value to use for a special "Any" list item. Can be null
+ *                    to not use any. If used must and will be first item in
+ *                    the select element.
+ *
+ * @global cpts       Array of arrays, indexed by product name. The subarrays
+ *                    contain a list of components to be fed to the respective
+ *                    select element.
+ * @global vers       Array of arrays, indexed by product name. The subarrays
+ *                    contain a list of versions to be fed to the respective
+ *                    select element.
+ * @global tms        Array of arrays, indexed by product name. The subarrays
+ *                    contain a list of milestones to be fed to the respective
+ *                    select element.
+ * @global first_load Boolean; true if this is the first time this page loads
+ *                    or false if not.
+ * @global last_sel   Array that contains last list of products so we know what
+ *                    has changed, and optimize for additions.
  */
-function selectProduct(product, component, version, milestone) {
-
-    if (!product) {
-        /* this is to avoid handling events that occur before the form
-         * itself is ready, which could happen in buggy browsers. */
+function selectProduct(product, component, version, milestone, anyval) {
+    // This is to avoid handling events that occur before the form
+    // itself is ready, which could happen in buggy browsers.
+    if (!product)
         return;
-    }
 
-    /* if this is the first load and nothing is selected, no need to
-     * merge and sort all components; perl gives it to us sorted. */
+    // Do nothing if no products are defined. This is to avoid the
+    // "a has no properties" error from merge_arrays function.
+    if (product.length == (anyval != null ? 1 : 0))
+        return;
+
+    // If this is the first load and nothing is selected, no need to
+    // merge and sort all lists; they are created sorted.
     if ((first_load) && (product.selectedIndex == -1)) {
         first_load = false;
         return;
     }
 
-    /* turn first_load off. this is tricky, since it seems to be
-     * redundant with the above clause. It's not: if when we first load
-     * the page there is _one_ element selected, it won't fall into that
-     * clause, and first_load will remain 1. Then, if we unselect that
-     * item, selectProduct will be called but the clause will be valid
-     * (since selectedIndex == -1), and we will return - incorrectly -
-     * without merge/sorting. */
+    // Turn first_load off. This is tricky, since it seems to be
+    // redundant with the above clause. It's not: if when we first load
+    // the page there is _one_ element selected, it won't fall into that
+    // clause, and first_load will remain 1. Then, if we unselect that
+    // item, selectProduct will be called but the clause will be valid
+    // (since selectedIndex == -1), and we will return - incorrectly -
+    // without merge/sorting.
     first_load = false;
 
-    /* - sel keeps the array of products we are selected.
-     * - merging says if it is a full list or just a list of products that
-     *   were added to the current selection. */
-    var merging = false;
+    // Stores products that are selected.
     var sel = Array();
 
-    /* if nothing selected, pick all */
-    var findall = product.selectedIndex == -1;
+    // True if sel array has a full list or false if sel contains only
+    // new products that are to be merged to the current list.
+    var merging = false;
+
+    // If nothing is selected, or the special "Any" option is selected
+    // which represents all products, then pick all products so we show
+    // all components.
+    var findall = (product.selectedIndex == -1
+                   || (anyval != null && product.options[0].selected));
+
     if (useclassification) {
-        /* update index based on the complete product array */
-        sel = get_selection(product, findall, true);
-        for (var i=0; i<sel.length; i++) {
+        // Update index based on the complete product array.
+        sel = get_selection(product, findall, true, anyval);
+        for (var i=0; i<sel.length; i++)
            sel[i] = prods[sel[i]];
-        }
-    } else {
-        sel = get_selection(product, findall, false);
+    }
+    else {
+        sel = get_selection(product, findall, false, anyval);
     }
     if (!findall) {
-        /* save sel for the next invocation of selectProduct() */
+        // Save sel for the next invocation of selectProduct().
         var tmp = sel;
 
-        /* this is an optimization: if we have just added products to an
-         *  existing selection, no need to clear the form controls and add
-         *  everybody again; just merge the new ones with the existing
-         *  options. */
+        // This is an optimization: if we have just added products to an
+        // existing selection, no need to clear the form controls and add
+        // everybody again; just merge the new ones with the existing
+        // options.
         if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
             sel = fake_diff_array(sel, last_sel);
             merging = true;
@@ -158,167 +183,180 @@
         last_sel = tmp;
     }
 
-    /* do the actual fill/update */
+    // Do the actual fill/update.
     if (component) {
-        var saved_cpts = get_selection(component, false, true);
-        updateSelect(cpts, sel, component, merging);
+        var saved_cpts = get_selection(component, false, true, null);
+        updateSelect(cpts, sel, component, merging, anyval);
         restoreSelection(component, saved_cpts);
     }
 
     if (version) {
-        var saved_vers = get_selection(version, false, true);
-        updateSelect(vers, sel, version, merging);
+        var saved_vers = get_selection(version, false, true, null);
+        updateSelect(vers, sel, version, merging, anyval);
         restoreSelection(version, saved_vers);
     }
 
     if (milestone) {
-        var saved_tms = get_selection(milestone, false, true);
-        updateSelect(tms, sel, milestone, merging);
+        var saved_tms = get_selection(milestone, false, true, null);
+        updateSelect(tms, sel, milestone, merging, anyval);
         restoreSelection(milestone, saved_tms);
     }
 }
 
-
-/* updateSelect(array, sel, target, merging)
+/**
+ * Adds to the target select element all elements from array that
+ * correspond to the selected items.
  *
- * Adds to the target select object all elements in array that
- * correspond to the elements selected in source.
- * - array should be a array of arrays, indexed by number. the
- *   array should contain the elements that correspond to that
- *   product.
- * - sel is a list of selected items, either whole or a diff
- *   depending on merging.
- * - target should be the target select object.
- * - merging (boolean) determines if we are mergine in a diff or
- *   substituting the whole selection. a diff is used to optimize adding
- *   selections.
+ * @param array   An array of arrays, indexed by number. The array should
+ *                contain elements for each selection.
+ * @param sel     A list of selected items, either whole or a diff depending
+ *                on merging parameter.
+ * @param target  Select element that is to be updated.
+ * @param merging Boolean that determines if we are merging in a diff or
+ *                substituting the whole selection. A diff is used to optimize
+ *                adding selections.
+ * @param anyval  Name of special "Any" value to add. Can be null if not used.
+ * @return        Boolean; true if target contains options or false if target
+ *                is empty.
  *
- * Example (compsel is a select form control)
+ * Example (compsel is a select form element):
  *
  *     var components = Array();
  *     components[1] = [ 'ComponentA', 'ComponentB' ];
  *     components[2] = [ 'ComponentC', 'ComponentD' ];
  *     source = [ 2 ];
- *     updateSelect(components, source, compsel, 0, 0);
+ *     updateSelect(components, source, compsel, false, null);
  *
- * would clear compsel and add 'ComponentC' and 'ComponentD' to it.
- *
+ * This would clear compsel and add 'ComponentC' and 'ComponentD' to it.
  */
-
-function updateSelect(array, sel, target, merging) {
-
+function updateSelect(array, sel, target, merging, anyval) {
     var i, item;
 
-    /* If we have no versions/components/milestones */
+    // If we have no versions/components/milestones.
     if (array.length < 1) {
         target.options.length = 0;
         return false;
     }
 
     if (merging) {
-        /* array merging/sorting in the case of multiple selections */
-        /* merge in the current options with the first selection */
+        // Array merging/sorting in the case of multiple selections
+        // merge in the current options with the first selection.
         item = merge_arrays(array[sel[0]], target.options, 1);
 
-        /* merge the rest of the selection with the results */
-        for (i = 1 ; i < sel.length ; i++) {
+        // Merge the rest of the selection with the results.
+        for (i = 1 ; i < sel.length ; i++)
             item = merge_arrays(array[sel[i]], item, 0);
-        }
-    } else if ( sel.length > 1 ) {
-        /* here we micro-optimize for two arrays to avoid merging with a
-         * null array */
+    }
+    else if (sel.length > 1) {
+        // Here we micro-optimize for two arrays to avoid merging with a
+        // null array.
         item = merge_arrays(array[sel[0]],array[sel[1]], 0);
 
-        /* merge the arrays. not very good for multiple selections. */
-        for (i = 2; i < sel.length; i++) {
+        // Merge the arrays. Not very good for multiple selections.
+        for (i = 2; i < sel.length; i++)
             item = merge_arrays(item, array[sel[i]], 0);
-        }
-    } else { /* single item in selection, just get me the list */
+    }
+    else {
+        // Single item in selection, just get me the list.
         item = array[sel[0]];
     }
 
-    /* clear select */
+    // Clear current selection.
     target.options.length = 0;
 
-    /* load elements of list into select */
-    for (i = 0; i < item.length; i++) {
-        target.options[i] = new Option(item[i], item[i]);
-    }
+    // Add special "Any" value back to the list.
+    if (anyval != null)
+        target.options[0] = new Option(anyval, "");
+
+    // Load elements of list into select element.
+    for (i = 0; i < item.length; i++)
+        target.options[target.options.length] = new Option(item[i], item[i]);
+
     return true;
 }
 
-
-/* Selects items in control that have index defined in sel
- *    - control: SELECT control to be restored
- *    - selnames: array of indexes in select form control */
+/**
+ * Selects items in select element that are defined to be selected.
+ *
+ * @param control  Select element of which selected options are to be restored.
+ * @param selnames Array of option names to select.
+ */
 function restoreSelection(control, selnames) {
-    /* right. this sucks. but I see no way to avoid going through the
-     * list and comparing to the contents of the control. */
-    for (var j=0; j < selnames.length; j++) {
-        for (var i=0; i < control.options.length; i++) {
-            if (control.options[i].value == selnames[j]) {
+    // Right. This sucks but I see no way to avoid going through the
+    // list and comparing to the contents of the control.
+    for (var j = 0; j < selnames.length; j++)
+        for (var i = 0; i < control.options.length; i++)
+            if (control.options[i].value == selnames[j])
                 control.options[i].selected = true;
-            }
-        }
-    }
 }
 
-
-/* Returns elements in a that are not in b.
+/**
+ * Returns elements in a that are not in b.
  * NOT A REAL DIFF: does not check the reverse.
- *    - a,b: arrays of values to be compare. */
+ *
+ * @param  a First array to compare.
+ * @param  b Second array to compare.
+ * @return   Array of elements in a but not in b.
+ */
 function fake_diff_array(a, b) {
     var newsel = new Array();
     var found = false;
 
-    /* do a boring array diff to see who's new */
+    // Do a boring array diff to see who's new.
     for (var ia in a) {
-        for (var ib in b) {
-            if (a[ia] == b[ib]) {
+        for (var ib in b)
+            if (a[ia] == b[ib])
                 found = true;
-            }
-        }
-        if (!found) {
+
+        if (!found)
             newsel[newsel.length] = a[ia];
-        }
+
         found = false;
     }
+
     return newsel;
 }
 
-/* takes two arrays and sorts them by string, returning a new, sorted
- * array. the merge removes dupes, too.
- *    - a, b: arrays to be merge.
- *    - b_is_select: if true, then b is actually an optionitem and as
- *      such we need to use item.value on it. */
+/**
+ * Takes two arrays and sorts them by string, returning a new, sorted
+ * array. The merge removes dupes, too.
+ *
+ * @param  a           First array to merge.
+ * @param  b           Second array or an optionitem element to merge.
+ * @param  b_is_select Boolean; true if b is an optionitem element (need to
+ *                     access its value by item.value) or false if b is a
+ *                     an array.
+ * @return             Merged and sorted array.
+ */
 function merge_arrays(a, b, b_is_select) {
     var pos_a = 0;
     var pos_b = 0;
     var ret = new Array();
     var bitem, aitem;
 
-    /* iterate through both arrays and add the larger item to the return
-     * list. remove dupes, too. Use toLowerCase to provide
-     * case-insensitivity. */
+    // Iterate through both arrays and add the larger item to the return
+    // list. Remove dupes, too. Use toLowerCase to provide
+    // case-insensitivity.
     while ((pos_a < a.length) && (pos_b < b.length)) {
-        if (b_is_select) {
-            bitem = b[pos_b].value;
-        } else {
-            bitem = b[pos_b];
-        }
         aitem = a[pos_a];
+        if (b_is_select)
+            bitem = b[pos_b].value;
+        else
+            bitem = b[pos_b];
 
-        /* smaller item in list a */
+        // Smaller item in list a.
         if (aitem.toLowerCase() < bitem.toLowerCase()) {
             ret[ret.length] = aitem;
             pos_a++;
-        } else {
-            /* smaller item in list b */
+        }
+        else {
+            // Smaller item in list b.
             if (aitem.toLowerCase() > bitem.toLowerCase()) {
                 ret[ret.length] = bitem;
                 pos_b++;
-            } else {
-                /* list contents are equal, inc both counters. */
+            }
+            else {
+                // List contents are equal, include both counters.
                 ret[ret.length] = aitem;
                 pos_a++;
                 pos_b++;
@@ -326,44 +364,45 @@
         }
     }
 
-    /* catch leftovers here. these sections are ugly code-copying. */
-    if (pos_a < a.length) {
-        for (; pos_a < a.length ; pos_a++) {
+    // Catch leftovers here. These sections are ugly code-copying.
+    if (pos_a < a.length)
+        for (; pos_a < a.length ; pos_a++)
             ret[ret.length] = a[pos_a];
-        }
-    }
 
     if (pos_b < b.length) {
         for (; pos_b < b.length; pos_b++) {
-            if (b_is_select) {
+            if (b_is_select)
                 bitem = b[pos_b].value;
-            } else {
+            else
                 bitem = b[pos_b];
-            }
             ret[ret.length] = bitem;
         }
     }
+
     return ret;
 }
 
-/* Returns an array of indexes or values from a select form control.
- *    - control: select control from which to find selections
- *    - findall: boolean, store all options when true or just the selected
- *      indexes
- *    - want_values: boolean; we store values when true and indexes when
- *      false */
-function get_selection(control, findall, want_values) {
+/**
+ * Returns an array of indexes or values of options in a select form element.
+ *
+ * @param  control     Select form element from which to find selections.
+ * @param  findall     Boolean; true to return all options or false to return
+ *                     only selected options.
+ * @param  want_values Boolean; true to return values and false to return
+ *                     indexes.
+ * @param  anyval      Name of a special "Any" value that should be skipped. Can
+ *                     be null if not used.
+ * @return             Array of all or selected indexes or values.
+ */
+function get_selection(control, findall, want_values, anyval) {
     var ret = new Array();
 
-    if ((!findall) && (control.selectedIndex == -1)) {
+    if ((!findall) && (control.selectedIndex == -1))
         return ret;
-    }
 
-    for (var i=0; i<control.length; i++) {
-        if (findall || control.options[i].selected) {
+    for (var i = (anyval != null ? 1 : 0); i < control.length; i++)
+        if (findall || control.options[i].selected)
             ret[ret.length] = want_values ? control.options[i].value : i;
-        }
-    }
+
     return ret;
 }
-
diff --git a/BugsSite/js/util.js b/BugsSite/js/util.js
new file mode 100644
index 0000000..ce7ea4c
--- /dev/null
+++ b/BugsSite/js/util.js
@@ -0,0 +1,156 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Cross Platform JavaScript Utility Library.
+ *
+ * The Initial Developer of the Original Code is
+ * Everything Solved.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Max Kanat-Alexander <mkanat@bugzilla.org>
+ *   Christopher A. Aillon <christopher@aillon.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Locate where an element is on the page, x-wise.
+ *
+ * @param  obj Element of which location to return.
+ * @return     Current position of the element relative to the left of the
+ *             page window. Measured in pixels.
+ */
+function bz_findPosX(obj)
+{
+    var curleft = 0;
+
+    if (obj.offsetParent) {
+        while (obj) {
+            curleft += obj.offsetLeft;
+            obj = obj.offsetParent;
+        }
+    }
+    else if (obj.x) {
+        curleft += obj.x;
+    }
+
+    return curleft;
+}
+
+/**
+ * Locate where an element is on the page, y-wise.
+ *
+ * @param  obj Element of which location to return.
+ * @return     Current position of the element relative to the top of the
+ *             page window. Measured in pixels.
+ */
+function bz_findPosY(obj)
+{
+    var curtop = 0;
+
+    if (obj.offsetParent) {
+        while (obj) {
+            curtop += obj.offsetTop;
+            obj = obj.offsetParent;
+        }
+    }
+    else if (obj.y) {
+        curtop += obj.y;
+    }
+
+    return curtop;
+}
+
+/**
+ * Get the full height of an element, even if it's larger than the browser
+ * window.
+ *
+ * @param  fromObj Element of which height to return.
+ * @return         Current height of the element. Measured in pixels.
+ */
+function bz_getFullHeight(fromObj)
+{
+    var scrollY;
+
+    // All but Mac IE
+    if (fromObj.scrollHeight > fromObj.offsetHeight) {
+        scrollY = fromObj.scrollHeight;
+    // Mac IE
+    }  else {
+        scrollY = fromObj.offsetHeight;
+    }
+
+    return scrollY;
+}
+
+/**
+ * Get the full width of an element, even if it's larger than the browser
+ * window.
+ *
+ * @param  fromObj Element of which width to return.
+ * @return         Current width of the element. Measured in pixels.
+ */
+function bz_getFullWidth(fromObj)
+{
+    var scrollX;
+
+    // All but Mac IE
+    if (fromObj.scrollWidth > fromObj.offsetWidth) {
+        scrollX = fromObj.scrollWidth;
+    // Mac IE
+    }  else {
+        scrollX = fromObj.offsetWidth;
+    }
+
+    return scrollX;
+}
+
+/**
+ * Causes a block to appear directly underneath another block,
+ * overlaying anything below it.
+ * 
+ * @param item   The block that you want to move.
+ * @param parent The block that it goes on top of.
+ * @return nothing
+ */
+function bz_overlayBelow(item, parent) {
+    var elemY = bz_findPosY(parent);
+    var elemX = bz_findPosX(parent);
+    var elemH = parent.offsetHeight;
+
+    item.style.position = 'absolute';
+    item.style.left = elemX + "px";
+    item.style.top = elemY + elemH + 1 + "px";
+}
+
+/**
+ * Checks if a specified value is in the specified array.
+ *
+ * @param  aArray Array to search for the value.
+ * @param  aValue Value to search from the array.
+ * @return        Boolean; true if value is found in the array and false if not.
+ */
+function bz_isValueInArray(aArray, aValue)
+{
+  var run = 0;
+  var len = aArray.length;
+
+  for ( ; run < len; run++) {
+    if (aArray[run] == aValue) {
+      return true;
+    }
+  }
+
+  return false;
+}
diff --git a/BugsSite/js/yui/calendar.js b/BugsSite/js/yui/calendar.js
new file mode 100644
index 0000000..a8eff37
--- /dev/null
+++ b/BugsSite/js/yui/calendar.js
@@ -0,0 +1,16 @@
+/*
+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("&#160;");}}};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("&#160;");}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+" >&#160;</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+" >&#160;</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>&#160;</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>&#160;</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="&#160;";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/BugsSite/js/yui/yahoo-dom-event.js b/BugsSite/js/yui/yahoo-dom-event.js
new file mode 100644
index 0000000..d089967
--- /dev/null
+++ b/BugsSite/js/yui/yahoo-dom-event.js
@@ -0,0 +1,10 @@
+/*
+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/BugsSite/lib/README b/BugsSite/lib/README
new file mode 100644
index 0000000..5778a9a
--- /dev/null
+++ b/BugsSite/lib/README
@@ -0,0 +1,4 @@
+This directory contains the Perl modules that Bugzilla requires to run.
+
+If you would rather have Bugzilla use the Perl modules installed on your
+system, you can delete everything in this directory.
diff --git a/BugsSite/long_list.cgi b/BugsSite/long_list.cgi
index c02c8ded..7e1f695 100755
--- a/BugsSite/long_list.cgi
+++ b/BugsSite/long_list.cgi
@@ -22,7 +22,7 @@
 #                 Gervase Markham <gerv@gerv.net>
 
 use strict;
-use lib qw(.);
+use lib qw(. lib);
 use Bugzilla;
 
 my $cgi = Bugzilla->cgi;
diff --git a/BugsSite/mod_perl.pl b/BugsSite/mod_perl.pl
index bd6ef10..1e5c7fc 100644
--- a/BugsSite/mod_perl.pl
+++ b/BugsSite/mod_perl.pl
@@ -53,6 +53,8 @@
 # Set up the configuration for the web server
 my $server = Apache2::ServerUtil->server;
 my $conf = <<EOT;
+# Make sure each httpd child receives a different random seed (bug 476622)
+PerlChildInitHandler "sub { srand(); }"
 <Directory "$cgi_path">
     AddHandler perl-script .cgi
     # No need to PerlModule these because they're already defined in mod_perl.pl
@@ -92,6 +94,7 @@
     # $0 is broken under mod_perl before 2.0.2, so we have to set it
     # here explicitly or init_page's shutdownhtml code won't work right.
     $0 = $ENV{'SCRIPT_FILENAME'};
+
     Bugzilla::init_page();
     return $class->SUPER::handler(@_);
 }
@@ -104,6 +107,7 @@
 sub handler {
     my $r = shift;
 
+    Bugzilla::_cleanup();
     # Sometimes mod_perl doesn't properly call DESTROY on all
     # the objects in pnotes()
     foreach my $key (keys %{$r->pnotes}) {
diff --git a/BugsSite/page.cgi b/BugsSite/page.cgi
index 43a8265..290a4ac 100755
--- a/BugsSite/page.cgi
+++ b/BugsSite/page.cgi
@@ -30,7 +30,7 @@
 
 use strict;
 
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Error;
diff --git a/BugsSite/post_bug.cgi b/BugsSite/post_bug.cgi
index 8231748..a9910e8 100755
--- a/BugsSite/post_bug.cgi
+++ b/BugsSite/post_bug.cgi
@@ -25,7 +25,7 @@
 #                 Marc Schumann <wurblzap@gmail.com>
 
 use strict;
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Attachment;
@@ -36,6 +36,7 @@
 use Bugzilla::Bug;
 use Bugzilla::User;
 use Bugzilla::Field;
+use Bugzilla::Hook;
 use Bugzilla::Product;
 use Bugzilla::Component;
 use Bugzilla::Keyword;
@@ -53,6 +54,9 @@
 # Main Script
 ######################################################################
 
+# redirect to enter_bug if no field is passed.
+print $cgi->redirect(correct_urlbase() . 'enter_bug.cgi') unless $cgi->param();
+
 # Detect if the user already used the same form to submit a bug
 my $token = trim($cgi->param('token'));
 if ($token) {
@@ -62,7 +66,7 @@
               && ($old_bug_id =~ "^createbug:"))
     {
         # The token is invalid.
-        ThrowUserError('token_inexistent');
+        ThrowUserError('token_does_not_exist');
     }
 
     $old_bug_id =~ s/^createbug://;
@@ -120,8 +124,8 @@
     || ThrowTemplateError($template->error());
 
 # Include custom fields editable on bug creation.
-my @custom_bug_fields = Bugzilla->get_fields(
-    { custom => 1, obsolete => 0, enter_bug => 1 });
+my @custom_bug_fields = grep {$_->type != FIELD_TYPE_MULTI_SELECT && $_->enter_bug}
+                             Bugzilla->active_custom_fields;
 
 # Undefined custom fields are ignored to ensure they will get their default
 # value (e.g. "---" for custom single select fields).
@@ -163,6 +167,13 @@
 $bug_params{'groups'}      = \@selected_groups;
 $bug_params{'comment'}     = $comment;
 
+my @multi_selects = grep {$_->type == FIELD_TYPE_MULTI_SELECT && $_->enter_bug}
+                         Bugzilla->active_custom_fields;
+
+foreach my $field (@multi_selects) {
+    $bug_params{$field->name} = [$cgi->param($field->name)];
+}
+
 my $bug = Bugzilla::Bug->create(\%bug_params);
 
 # Get the bug ID back.
@@ -183,15 +194,15 @@
 # Add an attachment if requested.
 if (defined($cgi->upload('data')) || $cgi->param('attachurl')) {
     $cgi->param('isprivate', $cgi->param('commentprivacy'));
-    my $attach_id = Bugzilla::Attachment->insert_attachment_for_bug(!THROW_ERROR,
-                                                  $bug, $user, $timestamp, \$vars);
+    my $attachment = Bugzilla::Attachment->insert_attachment_for_bug(!THROW_ERROR,
+                                                  $bug, $user, $timestamp, $vars);
 
-    if ($attach_id) {
+    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=$attach_id)\n" .
-                          $cgi->param('description') . "\n";
+        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.
@@ -219,8 +230,8 @@
 my $error_mode_cache = Bugzilla->error_mode;
 Bugzilla->error_mode(ERROR_MODE_DIE);
 eval {
-    Bugzilla::Flag::validate($cgi, $id, undef, SKIP_REQUESTEE_ON_ERROR);
-    Bugzilla::Flag::process($bug, undef, $timestamp, $cgi);
+    Bugzilla::Flag::validate($id, undef, SKIP_REQUESTEE_ON_ERROR);
+    Bugzilla::Flag->process($bug, undef, $timestamp, $vars);
 };
 Bugzilla->error_mode($error_mode_cache);
 if ($@) {
@@ -234,6 +245,8 @@
 $vars->{'id'} = $id;
 $vars->{'bug'} = $bug;
 
+Bugzilla::Hook::process("post_bug-after_creation", { vars => $vars });
+
 ThrowCodeError("bug_error", { bug => $bug }) if $bug->error;
 
 $vars->{'sentmail'} = [];
diff --git a/BugsSite/process_bug.cgi b/BugsSite/process_bug.cgi
index 0164657..f21b172 100755
--- a/BugsSite/process_bug.cgi
+++ b/BugsSite/process_bug.cgi
@@ -27,6 +27,7 @@
 #                 Frédéric Buclin <LpSolit@gmail.com>
 #                 Lance Larsh <lance.larsh@oracle.com>
 #                 Akamai Technologies <bugzilla-dev@akamai.com>
+#                 Max Kanat-Alexander <mkanat@bugzilla.org>
 
 # Implementation notes for this file:
 #
@@ -42,7 +43,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -57,63 +58,23 @@
 use Bugzilla::Component;
 use Bugzilla::Keyword;
 use Bugzilla::Flag;
+use Bugzilla::Status;
+use Bugzilla::Token;
+
+use Storable qw(dclone);
 
 my $user = Bugzilla->login(LOGIN_REQUIRED);
-local our $whoid = $user->id;
-my $grouplist = $user->groups_as_string;
 
 my $cgi = Bugzilla->cgi;
 my $dbh = Bugzilla->dbh;
 my $template = Bugzilla->template;
-local our $vars = {};
+my $vars = {};
 $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
 
-my @editable_bug_fields = editable_bug_fields();
-
-my $requiremilestone = 0;
-local our $PrivilegesRequired = 0;
-
 ######################################################################
 # Subroutines
 ######################################################################
 
-sub BugInGroupId {
-    my ($bug_id, $group_id) = @_;
-    detaint_natural($bug_id);
-    detaint_natural($group_id);
-    my ($in_group) = Bugzilla->dbh->selectrow_array(
-        "SELECT CASE WHEN bug_id != 0 THEN 1 ELSE 0 END
-           FROM bug_group_map
-          WHERE bug_id = ? AND group_id = ?", undef, ($bug_id, $group_id));
-    return $in_group;
-}
-
-# This function checks if there are any default groups defined.
-# If so, then groups may have to be changed when bugs move from
-# one bug to another.
-sub AnyDefaultGroups {
-    my $product_id = shift;
-    my $dbh = Bugzilla->dbh;
-    my $grouplist = Bugzilla->user->groups_as_string;
-
-    my $and_product = $product_id ? ' AND product_id = ? ' : '';
-    my @args = (CONTROLMAPDEFAULT);
-    unshift(@args, $product_id) if $product_id;
-
-    my $any_default =
-        $dbh->selectrow_array("SELECT 1
-                                 FROM group_control_map
-                           INNER JOIN groups
-                                   ON groups.id = group_control_map.group_id
-                                WHERE isactive != 0
-                                 $and_product
-                                  AND membercontrol = ?
-                                  AND group_id IN ($grouplist) " .
-                                 $dbh->sql_limit(1),
-                                undef, @args);
-    return $any_default;
-}
-
 # Used to send email when an update is done.
 sub send_results {
     my ($bug_id, $vars) = @_;
@@ -128,17 +89,28 @@
     $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
+    # whose name starts with "defined_" and then the field name--it's used
+    # to know when we did things like empty a multi-select or deselect
+    # a checkbox.
+    my ($field, $check_defined) = @_;
+    my $cgi = Bugzilla->cgi;
+    if ( defined $cgi->param($field) 
+         || ($check_defined && defined $cgi->param("defined_$field")) )
+    {
+        return 1;
+    }
+    return 0;
+}
+
 ######################################################################
 # Begin Data/Security Validation
 ######################################################################
 
-# Create a list of IDs of all bugs being modified in this request.
-# This list will either consist of a single bug number from the "id"
-# form/URL field or a series of numbers from multiple form/URL fields
-# named "id_x" where "x" is the bug number.
-# For each bug being modified, make sure its ID is a valid bug number 
-# representing an existing bug that the user is authorized to access.
-my @idlist;
+# 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);
@@ -146,94 +118,37 @@
   # 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 @idlist, $id;
+  push(@bug_objects, new Bugzilla::Bug($id));
 } else {
+    my @ids;
     foreach my $i ($cgi->param()) {
         if ($i =~ /^id_([1-9][0-9]*)/) {
             my $id = $1;
             ValidateBugID($id);
-            push @idlist, $id;
+            push(@ids, $id);
         }
     }
+    @bug_objects = @{Bugzilla::Bug->new_from_list(\@ids)};
 }
 
 # Make sure there are bugs to process.
-scalar(@idlist) || ThrowUserError("no_bugs_chosen");
+scalar(@bug_objects) || ThrowUserError("no_bugs_chosen", {action => 'modify'});
 
-# Build a bug object using $cgi->param('id') as ID.
-# If there are more than one bug changed at once, the bug object will be
-# empty, which doesn't matter.
-my $bug = new Bugzilla::Bug(scalar $cgi->param('id'));
+my $first_bug = $bug_objects[0]; # Used when we're only updating a single bug.
 
-# Make sure form param 'dontchange' is defined so it can be compared to easily.
-$cgi->param('dontchange','') unless defined $cgi->param('dontchange');
-
-# Make sure the 'knob' param is defined; else set it to 'none'.
-$cgi->param('knob', 'none') unless defined $cgi->param('knob');
-
-# Validate all timetracking fields
-foreach my $field ("estimated_time", "work_time", "remaining_time") {
-    if (defined $cgi->param($field)) {
-        my $er_time = trim($cgi->param($field));
-        if ($er_time ne $cgi->param('dontchange')) {
-            Bugzilla::Bug::ValidateTime($er_time, $field);
+# Delete any parameter set to 'dontchange'.
+if (defined $cgi->param('dontchange')) {
+    foreach my $name ($cgi->param) {
+        next if $name eq 'dontchange'; # But don't delete dontchange itself!
+        # Skip ones we've already deleted (such as "defined_$name").
+        next if !defined $cgi->param($name);
+        if ($cgi->param($name) eq $cgi->param('dontchange')) {
+            $cgi->delete($name);
+            $cgi->delete("defined_$name");
         }
     }
 }
 
-if (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
-    my $wk_time = $cgi->param('work_time');
-    if ($cgi->param('comment') =~ /^\s*$/ && $wk_time && $wk_time != 0) {
-        ThrowUserError('comment_required');
-    }
-}
-
-ValidateComment(scalar $cgi->param('comment'));
-
-# If the bug(s) being modified have dependencies, validate them
-# and rebuild the list with the validated values.  This is important
-# because there are situations where validation changes the value
-# instead of throwing an error, f.e. when one or more of the values
-# is a bug alias that gets converted to its corresponding bug ID
-# during validation.
-foreach my $field ("dependson", "blocked") {
-    if ($cgi->param('id')) {
-        my @old = @{$bug->$field};
-        my @new;
-        foreach my $id (split(/[\s,]+/, $cgi->param($field))) {
-            next unless $id;
-            ValidateBugID($id, $field);
-            push @new, $id;
-        }
-        $cgi->param($field, join(",", @new));
-        my ($removed, $added) = diff_arrays(\@old, \@new);
-        foreach my $id (@$added , @$removed) {
-            # ValidateBugID is called without $field here so that it will
-            # throw an error if any of the changed bugs are not visible.
-            ValidateBugID($id);
-            if (Bugzilla->params->{"strict_isolation"}) {
-                my $deltabug = new Bugzilla::Bug($id);
-                if (!$user->can_edit_product($deltabug->{'product_id'})) {
-                    $vars->{'field'} = $field;
-                    ThrowUserError("illegal_change_deps", $vars);
-                }
-            }
-        }
-        if ((@$added || @$removed)
-            && !$bug->check_can_change_field($field, 0, 1, \$PrivilegesRequired))
-        {
-            $vars->{'privs'} = $PrivilegesRequired;
-            $vars->{'field'} = $field;
-            ThrowUserError("illegal_change", $vars);
-        }
-    } else {
-        # Bugzilla does not support mass-change of dependencies so they
-        # are not validated.  To prevent a URL-hacking risk, the dependencies
-        # are deleted for mass-changes.
-        $cgi->delete($field);
-    }
-}
-
 # do a match on the fields if applicable
 
 # The order of these function calls is important, as Flag::validate
@@ -249,764 +164,235 @@
 
 # Validate flags in all cases. validate() should not detect any
 # reference to flags if $cgi->param('id') is undefined.
-Bugzilla::Flag::validate($cgi, $cgi->param('id'));
+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)
+{
+    ($vars->{'operations'}) =
+        Bugzilla::Bug::GetBugActivity($first_bug->id, undef,
+                                      scalar $cgi->param('delta_ts'));
+
+    $vars->{'title_tag'} = "mid_air";
+    
+    ThrowCodeError('undefined_field', { field => 'longdesclength' })
+        if !defined $cgi->param('longdesclength');
+
+    $vars->{'start_at'} = $cgi->param('longdesclength');
+    # Always sort midair collision comments oldest to newest,
+    # regardless of the user's personal preference.
+    $vars->{'comments'} = 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;
+}
+
+# We couldn't do this check earlier as we first had to validate bug IDs
+# and display the mid-air collision page if delta_ts changed.
+# If we do a mass-change, we use session tokens.
+my $token = $cgi->param('token');
+
+if ($cgi->param('id')) {
+    check_hash_token($token, [$first_bug->id, $first_bug->delta_ts]);
+}
+else {
+    check_token_data($token, 'buglist_mass_change', 'query.cgi');
+}
 
 ######################################################################
 # End Data/Security Validation
 ######################################################################
 
-print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_EMAIL;
 $vars->{'title_tag'} = "bug_processed";
 
-# Set the title if we can see a mid-air coming. This test may have false
-# negatives, but never false positives, and should catch the majority of cases.
-# It only works at all in the single bug case.
-if (defined $cgi->param('id')) {
-    my $delta_ts = $dbh->selectrow_array(
-        q{SELECT delta_ts FROM bugs WHERE bug_id = ?},
-        undef, $cgi->param('id'));
-    
-    if (defined $cgi->param('delta_ts') && $cgi->param('delta_ts') ne $delta_ts)
-    {
-        $vars->{'title_tag'} = "mid_air";
-    }
-}
-
 # Set up the vars for navigational <link> elements
 my @bug_list;
-if ($cgi->cookie("BUGLIST") && defined $cgi->param('id')) {
+if ($cgi->cookie("BUGLIST")) {
     @bug_list = split(/:/, $cgi->cookie("BUGLIST"));
     $vars->{'bug_list'} = \@bug_list;
 }
 
-# This function checks if there is a comment required for a specific
-# function and tests, if the comment was given.
-# If comments are required for functions is defined by params.
-#
-sub CheckonComment {
-    my ($function) = (@_);
-    my $cgi = Bugzilla->cgi;
-    
-    # Param is 1 if comment should be added !
-    my $ret = Bugzilla->params->{ "commenton" . $function };
-
-    # Allow without comment in case of undefined Params.
-    $ret = 0 unless ( defined( $ret ));
-
-    if( $ret ) {
-        if (!defined $cgi->param('comment')
-            || $cgi->param('comment') =~ /^\s*$/) {
-            # No comment - sorry, action not allowed !
-            ThrowUserError("comment_required");
-        } else {
-            $ret = 0;
-        }
-    }
-    return( ! $ret ); # Return val has to be inverted
-}
-
-# Figure out whether or not the user is trying to change the product
-# (either the "product" variable is not set to "don't change" or the
-# user is changing a single bug and has changed the bug's product),
-# and make the user verify the version, component, target milestone,
-# and bug groups if so.
-my $oldproduct = '';
+my ($action, $next_bug);
 if (defined $cgi->param('id')) {
-    $oldproduct = $dbh->selectrow_array(
-        q{SELECT name FROM products INNER JOIN bugs
-        ON products.id = bugs.product_id WHERE bug_id = ?},
-        undef, $cgi->param('id'));
-}
+    $action = Bugzilla->user->settings->{'post_bug_submit_action'}->{'value'};
 
-# At this point, the product must be defined, even if set to "dontchange".
-defined($cgi->param('product'))
-  || ThrowCodeError('undefined_field', { field => 'product' });
-
-if (((defined $cgi->param('id') && $cgi->param('product') ne $oldproduct) 
-     || (!$cgi->param('id')
-         && $cgi->param('product') ne $cgi->param('dontchange')))
-    && CheckonComment( "reassignbycomponent" ))
-{
-    # Check to make sure they actually have the right to change the product
-    if (!$bug->check_can_change_field('product', $oldproduct, $cgi->param('product'),
-                                      \$PrivilegesRequired))
-    {
-        $vars->{'oldvalue'} = $oldproduct;
-        $vars->{'newvalue'} = $cgi->param('product');
-        $vars->{'field'} = 'product';
-        $vars->{'privs'} = $PrivilegesRequired;
-        ThrowUserError("illegal_change", $vars);
-    }
-
-    my $prod = $cgi->param('product');
-    my $prod_obj = new Bugzilla::Product({name => $prod});
-    trick_taint($prod);
-
-    # If at least one bug does not belong to the product we are
-    # moving to, we have to check whether or not the user is
-    # allowed to enter bugs into that product.
-    # Note that this check must be done early to avoid the leakage
-    # of component, version and target milestone names.
-    my $check_can_enter =
-        $dbh->selectrow_array("SELECT 1 FROM bugs
-                               INNER JOIN products
-                               ON bugs.product_id = products.id
-                               WHERE products.name != ?
-                               AND bugs.bug_id IN
-                               (" . join(',', @idlist) . ") " .
-                               $dbh->sql_limit(1),
-                               undef, $prod);
-
-    if ($check_can_enter) { $user->can_enter_product($prod, 1) }
-
-    # note that when this script is called from buglist.cgi (rather
-    # than show_bug.cgi), it's possible that the product will be changed
-    # but that the version and/or component will be set to 
-    # "--dont_change--" but still happen to be correct.  in this case,
-    # the if statement will incorrectly trigger anyway.  this is a 
-    # pretty weird case, and not terribly unreasonable behavior, but 
-    # worthy of a comment, perhaps.
-    #
-    my @version_names = map($_->name, @{$prod_obj->versions});
-    my @component_names = map($_->name, @{$prod_obj->components});
-    my $vok = 0;
-    if (defined $cgi->param('version')) {
-        $vok = lsearch(\@version_names, $cgi->param('version')) >= 0;
-    }
-    my $cok = 0;
-    if (defined $cgi->param('component')) {
-        $cok = lsearch(\@component_names, $cgi->param('component')) >= 0;
-    }
-
-    my $mok = 1;   # so it won't affect the 'if' statement if milestones aren't used
-    my @milestone_names = ();
-    if ( Bugzilla->params->{"usetargetmilestone"} ) {
-       @milestone_names = map($_->name, @{$prod_obj->milestones});
-       $mok = 0;
-       if (defined $cgi->param('target_milestone')) {
-           $mok = lsearch(\@milestone_names, $cgi->param('target_milestone')) >= 0;
-       }
-    }
-
-    # We cannot be sure if the component is the same by only checking $cok; the
-    # current component name could exist in the new product. So always display
-    # the form and use the confirm_product_change param to check if that was
-    # shown. Also show the verification form if the product-specific fields
-    # somehow still need to be verified, or if we need to verify whether or not
-    # to add the bugs to their new product's group.
-    my $has_default_groups = AnyDefaultGroups($prod_obj->id);
-
-    if (!$vok || !$cok || !$mok || !defined $cgi->param('confirm_product_change')
-        || ($has_default_groups && !defined $cgi->param('addtonewgroup'))) {
-
-        if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
-            if (!$vok) {
-                ThrowUserError('version_not_valid', {
-                    version => $cgi->param('version'),
-                    product => $cgi->param('product')});
-            }
-            if (!$cok) {
-                ThrowUserError('component_not_valid', {
-                    product => $cgi->param('product'),
-                    name    => $cgi->param('component')});
-            }
-            if (!$mok) {
-                ThrowUserError('milestone_not_valid', {
-                    product   => $cgi->param('product'),
-                    milestone => $cgi->param('target_milestone')});
-            }
-        }
-        
-        if (!$vok || !$cok || !$mok
-            || !defined $cgi->param('confirm_product_change'))
-        {
-            $vars->{'verify_fields'} = 1;
-            my %defaults;
-            # We set the defaults to these fields to the old value,
-            # if it's a valid option, otherwise we use the default where
-            # that's appropriate
-            $vars->{'versions'} = \@version_names;
-            if ($vok) {
-                $defaults{'version'} = $cgi->param('version');
-            }
-            elsif (scalar(@version_names) == 1) {
-                $defaults{'version'} = $version_names[0];
-            }
-
-            $vars->{'components'} = \@component_names;
-            if ($cok) {
-                $defaults{'component'} = $cgi->param('component');
-            }
-            elsif (scalar(@component_names) == 1) {
-                $defaults{'component'} = $component_names[0];
-            }
-
-            if (Bugzilla->params->{"usetargetmilestone"}) {
-                $vars->{'use_target_milestone'} = 1;
-                $vars->{'milestones'} = \@milestone_names;
-                if ($mok) {
-                    $defaults{'target_milestone'} = $cgi->param('target_milestone');
-                } else {
-                    $defaults{'target_milestone'} = $dbh->selectrow_array(
-                        q{SELECT defaultmilestone FROM products 
-                        WHERE name = ?}, undef, $prod);
-                }
-            }
-            else {
-                $vars->{'use_target_milestone'} = 0;
-            }
-            $vars->{'defaults'} = \%defaults;
-        }
-        else {
-            $vars->{'verify_fields'} = 0;
-        }
-
-        $vars->{'verify_bug_group'} = ($has_default_groups
-                                       && !defined $cgi->param('addtonewgroup'));
-
-        # Get the ID of groups which are no longer valid in the new product.
-        # If the bug was restricted to some group which still exists in the new
-        # product, leave it alone, independently of your privileges.
-        my $gids =
-          $dbh->selectcol_arrayref("SELECT bgm.group_id
-                                      FROM bug_group_map AS bgm
-                                     WHERE bgm.bug_id IN (" . join(', ', @idlist) . ")
-                                       AND bgm.group_id NOT IN
-                                           (SELECT gcm.group_id
-                                              FROM group_control_map AS gcm
-                                             WHERE gcm.product_id = ?
-                                               AND gcm.membercontrol IN (?, ?, ?))",
-                                     undef, ($prod_obj->id, CONTROLMAPSHOWN,
-                                             CONTROLMAPDEFAULT, CONTROLMAPMANDATORY));
-
-        $vars->{'old_groups'} = Bugzilla::Group->new_from_list($gids);
-
-        $template->process("bug/process/verify-new-product.html.tmpl", $vars)
-          || ThrowTemplateError($template->error());
-        exit;
-    }
-}
-
-# At this point, the component must be defined, even if set to "dontchange".
-defined($cgi->param('component'))
-  || ThrowCodeError('undefined_field', { field => 'component' });
-
-# Confirm that the reporter of the current bug can access the bug we are duping to.
-sub DuplicateUserConfirm {
-    my $cgi = Bugzilla->cgi;
-    my $dbh = Bugzilla->dbh;
-    my $template = Bugzilla->template;
-
-    # if we've already been through here, then exit
-    if (defined $cgi->param('confirm_add_duplicate')) {
-        return;
-    }
-
-    # Remember that we validated both these ids earlier, so we know
-    # they are both valid bug ids
-    my $dupe = $cgi->param('id');
-    my $original = $cgi->param('dup_id');
-    
-    my $reporter = $dbh->selectrow_array(
-        q{SELECT reporter FROM bugs WHERE bug_id = ?}, undef, $dupe);
-    my $rep_user = Bugzilla::User->new($reporter);
-
-    if ($rep_user->can_see_bug($original)) {
-        $cgi->param('confirm_add_duplicate', '1');
-        return;
-    }
-    elsif (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
-        # The email interface defaults to the safe alternative, which is
-        # not CC'ing the user.
-        $cgi->param('confirm_add_duplicate', 0);
-        return;
-    }
-
-    $vars->{'cclist_accessible'} = $dbh->selectrow_array(
-        q{SELECT cclist_accessible FROM bugs WHERE bug_id = ?},
-        undef, $original);
-    
-    # Once in this part of the subroutine, the user has not been auto-validated
-    # and the duper has not chosen whether or not to add to CC list, so let's
-    # ask the duper what he/she wants to do.
-    
-    $vars->{'original_bug_id'} = $original;
-    $vars->{'duplicate_bug_id'} = $dupe;
-    
-    # Confirm whether or not to add the reporter to the cc: list
-    # of the original bug (the one this bug is being duped against).
-    print Bugzilla->cgi->header();
-    $template->process("bug/process/confirm-duplicate.html.tmpl", $vars)
-      || ThrowTemplateError($template->error());
-    exit;
-}
-
-if (defined $cgi->param('id')) {
-    # since this means that we were called from show_bug.cgi, now is a good
-    # time to do a whole bunch of error checking that can't easily happen when
-    # we've been called from buglist.cgi, because buglist.cgi only tweaks
-    # values that have been changed instead of submitting all the new values.
-    # (XXX those error checks need to happen too, but implementing them 
-    # is more work in the current architecture of this script...)
-    my $prod_obj = Bugzilla::Product::check_product($cgi->param('product'));
-    check_field('component', scalar $cgi->param('component'), 
-                [map($_->name, @{$prod_obj->components})]);
-    check_field('version', scalar $cgi->param('version'),
-                [map($_->name, @{$prod_obj->versions})]);
-    if ( Bugzilla->params->{"usetargetmilestone"} ) {
-        check_field('target_milestone', scalar $cgi->param('target_milestone'), 
-                    [map($_->name, @{$prod_obj->milestones})]);
-    }
-    check_field('rep_platform', scalar $cgi->param('rep_platform'));
-    check_field('op_sys',       scalar $cgi->param('op_sys'));
-    check_field('priority',     scalar $cgi->param('priority'));
-    check_field('bug_severity', scalar $cgi->param('bug_severity'));
-
-    # Those fields only have to exist. We don't validate their value here.
-    foreach my $field_name ('bug_file_loc', 'short_desc', 'longdesclength') {
-        defined($cgi->param($field_name))
-          || ThrowCodeError('undefined_field', { field => $field_name });
-    }
-    $cgi->param('short_desc', clean_text($cgi->param('short_desc')));
-
-    if (trim($cgi->param('short_desc')) eq "") {
-        ThrowUserError("require_summary");
-    }
-}
-
-my $action = trim($cgi->param('action') || '');
-
-if ($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'});
-
-    # Moved bugs are marked as RESOLVED MOVED.
-    my $sth = $dbh->prepare("UPDATE bugs
-                                SET bug_status = 'RESOLVED',
-                                    resolution = 'MOVED',
-                                    delta_ts = ?
-                              WHERE bug_id = ?");
-    # Bugs cannot be a dupe and moved at the same time.
-    my $sth2 = $dbh->prepare("DELETE FROM duplicates WHERE dupe = ?");
-
-    my $comment = "";
-    if (defined $cgi->param('comment') && $cgi->param('comment') !~ /^\s*$/) {
-        $comment = $cgi->param('comment');
-    }
-
-    $dbh->bz_lock_tables('bugs WRITE', 'bugs_activity WRITE', 'duplicates WRITE',
-                         'longdescs WRITE', 'profiles READ', 'groups READ',
-                         'bug_group_map READ', 'group_group_map READ',
-                         'user_group_map READ', 'classifications READ',
-                         'products READ', 'components READ', 'votes READ',
-                         'cc READ', 'fielddefs READ');
-
-    my $timestamp = $dbh->selectrow_array("SELECT NOW()");
-    my @bugs;
-    # First update all moved bugs.
-    foreach my $id (@idlist) {
-        my $bug = new Bugzilla::Bug($id);
-        push(@bugs, $bug);
-
-        $sth->execute($timestamp, $id);
-        $sth2->execute($id);
-
-        AppendComment($id, $whoid, $comment, 0, $timestamp, 0, CMT_MOVED_TO, $user->login);
-
-        if ($bug->bug_status ne 'RESOLVED') {
-            LogActivityEntry($id, 'bug_status', $bug->bug_status,
-                             'RESOLVED', $whoid, $timestamp);
-        }
-        if ($bug->resolution ne 'MOVED') {
-            LogActivityEntry($id, 'resolution', $bug->resolution,
-                             'MOVED', $whoid, $timestamp);
+    if ($action eq 'next_bug') {
+        my $cur = lsearch(\@bug_list, $cgi->param('id'));
+        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};
         }
     }
-    $dbh->bz_unlock_tables();
-
-    # Now send emails.
-    foreach my $id (@idlist) {
-        $vars->{'mailrecipients'} = { 'changer' => $user->login };
-        $vars->{'id'} = $id;
-        $vars->{'type'} = "move";
-        send_results($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(', ', @idlist) . "\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;
-}
-
-
-$::query = "UPDATE bugs SET";
-$::comma = "";
-local our @values;
-umask(0);
-
-sub _remove_remaining_time {
-    my $cgi = Bugzilla->cgi;
-    if (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
-        if ( defined $cgi->param('remaining_time') 
-             && $cgi->param('remaining_time') > 0 )
-        {
-            $cgi->param('remaining_time', 0);
-            $vars->{'message'} = "remaining_time_zeroed";
-        }
-    }
+    # Include both action = 'same_bug' and 'nothing'.
     else {
-        DoComma();
-        $::query .= "remaining_time = 0";
+        $vars->{'bug'} = {bug_id => $cgi->param('id')};
+    }
+}
+else {
+    # param('id') is not defined when changing multiple bugs at once.
+    $action = 'nothing';
+}
+
+# For each bug, we have to check if the user can edit the bug the product
+# is currently in, before we allow them to change anything.
+foreach my $bug (@bug_objects) {
+    if (!Bugzilla->user->can_edit_product($bug->product_obj->id) ) {
+        ThrowUserError("product_edit_denied",
+                      { product => $bug->product });
     }
 }
 
-sub DoComma {
-    $::query .= "$::comma\n    ";
-    $::comma = ",";
-}
-
-# $everconfirmed is used by ChangeStatus() to determine whether we are
-# confirming the bug or not.
-local our $everconfirmed;
-sub DoConfirm {
-    my $bug = shift;
-    if ($bug->check_can_change_field("canconfirm", 0, 1, 
-                                     \$PrivilegesRequired)) 
-    {
-        DoComma();
-        $::query .= "everconfirmed = 1";
-        $everconfirmed = 1;
+# 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;
     }
 }
-
-sub ChangeStatus {
-    my ($str) = (@_);
-    my $cgi = Bugzilla->cgi;
-    my $dbh = Bugzilla->dbh;
-
-    if (!$cgi->param('dontchange')
-        || $str ne $cgi->param('dontchange')) {
-        DoComma();
-        if ($cgi->param('knob') eq 'reopen') {
-            # When reopening, we need to check whether the bug was ever
-            # confirmed or not
-            $::query .= "bug_status = CASE WHEN everconfirmed = 1 THEN " .
-                        $dbh->quote($str) . " ELSE 'UNCONFIRMED' END";
-        } elsif (is_open_state($str)) {
-            # Note that we cannot combine this with the above branch - here we
-            # need to check if bugs.bug_status is open, (since we don't want to
-            # reopen closed bugs when reassigning), while above the whole point
-            # is to reopen a closed bug.
-            # Currently, the UI doesn't permit a user to reassign a closed bug
-            # from the single bug page (only during a mass change), but they
-            # could still hack the submit, so don't restrict this extended
-            # check to the mass change page for safety/sanity/consistency
-            # purposes.
-
-            # The logic for this block is:
-            # If the new state is open:
-            #   If the old state was open
-            #     If the bug was confirmed
-            #       - move it to the new state
-            #     Else
-            #       - Set the state to unconfirmed
-            #   Else
-            #     - leave it as it was
-
-            # This is valid only because 'reopen' is the only thing which moves
-            # from closed to open, and it's handled above
-            # This also relies on the fact that confirming and accepting have
-            # already called DoConfirm before this is called
-
-            my @open_state = map($dbh->quote($_), BUG_STATE_OPEN);
-            my $open_state = join(", ", @open_state);
-
-            # If we are changing everconfirmed to 1, we have to take this change
-            # into account and the new bug status is given by $str.
-            my $cond = $dbh->quote($str);
-            # If we are not setting everconfirmed, the new bug status depends on
-            # the actual value of everconfirmed, which is bug-specific.
-            unless ($everconfirmed) {
-                $cond = "(CASE WHEN everconfirmed = 1 THEN " . $cond .
-                        " ELSE 'UNCONFIRMED' END)";
+        
+# 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);
             }
-            $::query .= "bug_status = CASE WHEN bug_status IN($open_state) THEN " .
-                                      $cond . " ELSE bug_status END";
-        } else {
-            $::query .= "bug_status = ?";
-            push(@values, $str);
-        }
-        # If bugs are reassigned and their status is "UNCONFIRMED", they
-        # should keep this status instead of "NEW" as suggested here.
-        # This point is checked for each bug later in the code.
-        $cgi->param('bug_status', $str);
-    }
-}
-
-sub ChangeResolution {
-    my ($bug, $str) = (@_);
-    my $dbh = Bugzilla->dbh;
-    my $cgi = Bugzilla->cgi;
-
-    if (!$cgi->param('dontchange')
-        || $str ne $cgi->param('dontchange'))
-    {
-        # Make sure the user is allowed to change the resolution.
-        # If the user is changing several bugs at once using the UI,
-        # then he has enough privs to do so. In the case he is hacking
-        # the URL, we don't care if he reads --UNKNOWN-- as a resolution
-        # in the error message.
-        my $old_resolution = '-- UNKNOWN --';
-        my $bug_id = $cgi->param('id');
-        if ($bug_id) {
-            $old_resolution =
-                $dbh->selectrow_array('SELECT resolution FROM bugs WHERE bug_id = ?',
-                                       undef, $bug_id);
-        }
-        unless ($bug->check_can_change_field('resolution', $old_resolution, $str,
-                                             \$PrivilegesRequired))
-        {
-            $vars->{'oldvalue'} = $old_resolution;
-            $vars->{'newvalue'} = $str;
-            $vars->{'field'} = 'resolution';
-            $vars->{'privs'} = $PrivilegesRequired;
-            ThrowUserError("illegal_change", $vars);
-        }
-
-        DoComma();
-        $::query .= "resolution = ?";
-        trick_taint($str);
-        push(@values, $str);
-        # We define this variable here so that customized installations
-        # may set rules based on the resolution in Bug::check_can_change_field().
-        $cgi->param('resolution', $str);
-    }
-}
-
-# Changing this so that it will process groups from checkboxes instead of
-# select lists.  This means that instead of looking for the bit-X values in
-# the form, we need to loop through all the bug groups this user has access
-# to, and for each one, see if it's selected.
-# If the form element isn't present, or the user isn't in the group, leave
-# it as-is
-
-my @groupAdd = ();
-my @groupDel = ();
-
-my $groups = $dbh->selectall_arrayref(
-    qq{SELECT groups.id, isactive FROM groups
-        WHERE id IN($grouplist) AND isbuggroup = 1});
-foreach my $group (@$groups) {
-    my ($b, $isactive) = @$group;
-    # The multiple change page may not show all groups a bug is in
-    # (eg product groups when listing more than one product)
-    # Only consider groups which were present on the form. We can't do this
-    # for single bug changes because non-checked checkboxes aren't present.
-    # All the checkboxes should be shown in that case, though, so it isn't
-    # an issue there
-    #
-    # For bug updates that come from email_in.pl there will not be any
-    # bit-X fields defined unless the user is explicitly changing the
-    # state of the specified group (by including lines such as @bit-XX = 1
-    # or @bit-XX = 0 in the body of the email).  Therefore if we are in
-    # USAGE_MODE_EMAIL we will only change the group setting if bit-XX
-    # is defined.
-     if ((defined $cgi->param('id') && Bugzilla->usage_mode != USAGE_MODE_EMAIL)
-         || defined $cgi->param("bit-$b"))
-     {
-        if (!$cgi->param("bit-$b")) {
-            push(@groupDel, $b);
-        } elsif ($cgi->param("bit-$b") == 1 && $isactive) {
-            push(@groupAdd, $b);
-        }
-    }
-}
-
-foreach my $field ("rep_platform", "priority", "bug_severity",
-                   "bug_file_loc", "short_desc", "version", "op_sys",
-                   "target_milestone", "status_whiteboard") {
-    if (defined $cgi->param($field)) {
-        if (!$cgi->param('dontchange')
-            || $cgi->param($field) ne $cgi->param('dontchange')) {
-            DoComma();
-            $::query .= "$field = ?";
-            my $value = trim($cgi->param($field));
-            trick_taint($value);
-            push(@values, $value);
-        }
-    }
-}
-
-# Add custom fields data to the query that will update the database.
-foreach my $field (Bugzilla->get_fields({custom => 1, obsolete => 0})) {
-    my $fname = $field->name;
-    if (defined $cgi->param($fname)
-        && (!$cgi->param('dontchange')
-            || $cgi->param($fname) ne $cgi->param('dontchange')))
-    {
-        DoComma();
-        $::query .= "$fname = ?";
-        my $value = $cgi->param($fname);
-        check_field($fname, $value) if ($field->type == FIELD_TYPE_SINGLE_SELECT);
-        trick_taint($value);
-        push(@values, $value);
-    }
-}
-
-my $product;
-my $prod_changed = 0;
-my @newprod_ids;
-if ($cgi->param('product') ne $cgi->param('dontchange')) {
-    $product = Bugzilla::Product::check_product(scalar $cgi->param('product'));
-
-    DoComma();
-    $::query .= "product_id = ?";
-    push(@values, $product->id);
-    @newprod_ids = ($product->id);
-    # If the bug remains in the same product, leave $prod_changed set to 0.
-    # Even with 'strict_isolation' turned on, we ignore users who already
-    # play a role for the bug; else you would never be able to edit it.
-    # If you want to move the bug to another product, then you first have to
-    # remove these users from the bug.
-    unless (defined $cgi->param('id') && $bug->product_id == $product->id) {
-        $prod_changed = 1;
-    }
-} else {
-    @newprod_ids = @{$dbh->selectcol_arrayref("SELECT DISTINCT product_id
-                                               FROM bugs 
-                                               WHERE bug_id IN (" .
-                                                   join(',', @idlist) . 
-                                               ")")};
-    if (scalar(@newprod_ids) == 1) {
-        $product = new Bugzilla::Product($newprod_ids[0]);
-    }
-}
-
-my $component;
-if ($cgi->param('component') ne $cgi->param('dontchange')) {
-    if (scalar(@newprod_ids) > 1) {
-        ThrowUserError("no_component_change_for_multiple_products");
-    }
-    $component =
-        Bugzilla::Component::check_component($product, scalar $cgi->param('component'));
-
-    # This parameter is required later when checking fields the user can change.
-    $cgi->param('component_id', $component->id);
-    DoComma();
-    $::query .= "component_id = ?";
-    push(@values, $component->id);
-}
-
-# If this installation uses bug aliases, and the user is changing the alias,
-# add this change to the query.
-if (Bugzilla->params->{"usebugaliases"} && defined $cgi->param('alias')) {
-    my $alias = trim($cgi->param('alias'));
-    
-    # Since aliases are unique (like bug numbers), they can only be changed
-    # for one bug at a time, so ignore the alias change unless only a single
-    # bug is being changed.
-    if (scalar(@idlist) == 1) {
-        # Add the alias change to the query.  If the field contains the blank 
-        # value, make the field be NULL to indicate that the bug has no alias.
-        # Otherwise, if the field contains a value, update the record 
-        # with that value.
-        DoComma();
-        if ($alias ne "") {
-            ValidateBugAlias($alias, $idlist[0]);
-            $::query .= "alias = ?";
-            push(@values, $alias);
-        } else {
-            $::query .= "alias = NULL";
-        }
-    }
-}
-
-# If the user is submitting changes from show_bug.cgi for a single bug,
-# and that bug is restricted to a group, process the checkboxes that
-# allowed the user to set whether or not the reporter
-# and cc list can see the bug even if they are not members of all groups 
-# to which the bug is restricted.
-if (defined $cgi->param('id')) {
-    my ($havegroup) = $dbh->selectrow_array(
-        q{SELECT group_id FROM bug_group_map WHERE bug_id = ?},
-        undef, $cgi->param('id'));
-    if ( $havegroup ) {
-        foreach my $field ('reporter_accessible', 'cclist_accessible') {
-            if ($bug->check_can_change_field($field, 0, 1, \$PrivilegesRequired)) {
-                DoComma();
-                $cgi->param($field, $cgi->param($field) ? '1' : '0');
-                $::query .= " $field = ?";
-                push(@values, $cgi->param($field));
-            }
-            else {
-                $cgi->delete($field);
+            # "== 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 ( defined $cgi->param('id') &&
-     (Bugzilla->params->{"insidergroup"} 
-      && Bugzilla->user->in_group(Bugzilla->params->{"insidergroup"})) ) 
+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 $sth = $dbh->prepare('UPDATE longdescs SET isprivate = ?
-                             WHERE bug_id = ? AND bug_when = ?');
-
-    foreach my $field ($cgi->param()) {
-        if ($field =~ /when-([0-9]+)/) {
-            my $sequence = $1;
-            my $private = $cgi->param("isprivate-$sequence") ? 1 : 0 ;
-            if ($private != $cgi->param("oisprivate-$sequence")) {
-                my $field_data = $cgi->param("$field");
-                # Make sure a valid date is given.
-                $field_data = format_time($field_data, '%Y-%m-%d %T');
-                $sth->execute($private, $cgi->param('id'), $field_data);
-            }
-        }
-
+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;
     }
 }
 
-my $duplicate;
+# 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);
+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;
 
-# 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 hashes of ID's involved
-# in this CC change.  Then those hashes can be used later on for the actual change.
-my (%cc_add, %cc_remove);
+my %methods = (
+    bug_severity => 'set_severity',
+    rep_platform => 'set_platform',
+    short_desc   => 'set_summary',
+    bug_file_loc => 'set_url',
+);
+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)]);
+        }
+    }
+}
+
+# Certain changes can only happen on individual bugs, never on mass-changes.
+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"));
+    }
+}
+
+# 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) = "";
@@ -1025,1164 +411,224 @@
         }
     }
 
-    if ($cc_add) {
-        $cc_add =~ s/[\s,]+/ /g; # Change all delimiters to a single space
-        foreach my $person ( split(" ", $cc_add) ) {
-            my $pid = login_to_id($person, THROW_ERROR);
-            $cc_add{$pid} = $person;
-        }
-    }
-    if ($cgi->param('addselfcc')) {
-        $cc_add{$whoid} = $user->login;
-    }
-    if ($cc_remove) {
-        $cc_remove =~ s/[\s,]+/ /g; # Change all delimiters to a single space
-        foreach my $person ( split(" ", $cc_remove) ) {
-            my $pid = login_to_id($person, THROW_ERROR);
-            $cc_remove{$pid} = $person;
-        }
+    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;
+}
+
+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();
     }
 }
 
-# Store the new assignee and QA contact IDs (if any). This is the
-# only way to keep these informations when bugs are reassigned by
-# component as $cgi->param('assigned_to') and $cgi->param('qa_contact')
-# are not the right fields to look at.
-# If the assignee or qacontact is changed, the new one is checked when
-# changed information is validated.  If not, then the unchanged assignee
-# or qacontact may have to be validated later.
+my $move_action = $cgi->param('action') || '';
+if ($move_action eq Bugzilla->params->{'move-button-text'}) {
+    Bugzilla->params->{'move-enabled'} || ThrowUserError("move_bugs_disabled");
 
-my $assignee;
-my $qacontact;
-my $qacontact_checked = 0;
-my $assignee_checked = 0;
+    $user->is_mover || ThrowUserError("auth_failure", {action => 'move',
+                                                       object => 'bugs'});
 
-my %usercache = ();
+    $dbh->bz_start_transaction();
 
-if (defined $cgi->param('qa_contact')
-    && $cgi->param('knob') ne "reassignbycomponent")
-{
-    my $name = trim($cgi->param('qa_contact'));
-    # The QA contact cannot be deleted from show_bug.cgi for a single bug!
-    if ($name ne $cgi->param('dontchange')) {
-        $qacontact = login_to_id($name, THROW_ERROR) if ($name ne "");
-        if ($qacontact && Bugzilla->params->{"strict_isolation"}
-            && !(defined $cgi->param('id') && $bug->qa_contact
-                 && $qacontact == $bug->qa_contact->id))
-        {
-                $usercache{$qacontact} ||= Bugzilla::User->new($qacontact);
-                my $qa_user = $usercache{$qacontact};
-                foreach my $product_id (@newprod_ids) {
-                    if (!$qa_user->can_edit_product($product_id)) {
-                        my $product_name = Bugzilla::Product->new($product_id)->name;
-                        ThrowUserError('invalid_user_group',
-                                          {'users'   => $qa_user->login,
-                                           'product' => $product_name,
-                                           'bug_id' => (scalar(@idlist) > 1)
-                                                         ? undef : $idlist[0]
-                                          });
-                    }
-                }
-        }
-        $qacontact_checked = 1;
-        DoComma();
-        if($qacontact) {
-            $::query .= "qa_contact = ?";
-            push(@values, $qacontact);
-        }
-        else {
-            $::query .= "qa_contact = NULL";
-        }
+    # 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'));
     }
 }
 
-SWITCH: for ($cgi->param('knob')) {
-    /^none$/ && do {
-        last SWITCH;
-    };
-    /^confirm$/ && CheckonComment( "confirm" ) && do {
-        DoConfirm($bug);
-        ChangeStatus('NEW');
-        last SWITCH;
-    };
-    /^accept$/ && CheckonComment( "accept" ) && do {
-        DoConfirm($bug);
-        ChangeStatus('ASSIGNED');
-        if (Bugzilla->params->{"usetargetmilestone"} 
-            && Bugzilla->params->{"musthavemilestoneonaccept"}) 
-        {
-            $requiremilestone = 1;
-        }
-        last SWITCH;
-    };
-    /^clearresolution$/ && CheckonComment( "clearresolution" ) && do {
-        ChangeResolution($bug, '');
-        last SWITCH;
-    };
-    /^(resolve|change_resolution)$/ && CheckonComment( "resolve" ) && do {
-        # Check here, because it's the only place we require the resolution
-        check_field('resolution', scalar $cgi->param('resolution'),
-                    Bugzilla::Bug->settable_resolutions);
+##############################
+# Do Actual Database Updates #
+##############################
+foreach my $bug (@bug_objects) {
+    $dbh->bz_start_transaction();
 
-        # don't resolve as fixed while still unresolved blocking bugs
-        if (Bugzilla->params->{"noresolveonopenblockers"}
-            && $cgi->param('resolution') eq 'FIXED')
-        {
-            my @dependencies = Bugzilla::Bug::CountOpenDependencies(@idlist);
-            if (scalar @dependencies > 0) {
-                ThrowUserError("still_unresolved_bugs",
-                               { dependencies     => \@dependencies,
-                                 dependency_count => scalar @dependencies });
-            }
-        }
+    my $timestamp = $dbh->selectrow_array(q{SELECT NOW()});
+    my $changes = $bug->update($timestamp);
 
-        if ($cgi->param('knob') eq 'resolve') {
-            # RESOLVED bugs should have no time remaining;
-            # more time can be added for the VERIFY step, if needed.
-            _remove_remaining_time();
-
-            ChangeStatus('RESOLVED');
-        }
-        else {
-            # You cannot use change_resolution if there is at least
-            # one open bug.
-            my $open_states = join(',', map {$dbh->quote($_)} BUG_STATE_OPEN);
-            my $idlist = join(',', @idlist);
-            my $is_open =
-              $dbh->selectrow_array("SELECT 1 FROM bugs WHERE bug_id IN ($idlist)
-                                     AND bug_status IN ($open_states)");
-
-            ThrowUserError('resolution_not_allowed') if $is_open;
-        }
-
-        ChangeResolution($bug, $cgi->param('resolution'));
-        last SWITCH;
-    };
-    /^reassign$/ && CheckonComment( "reassign" ) && do {
-        if ($cgi->param('andconfirm')) {
-            DoConfirm($bug);
-        }
-        ChangeStatus('NEW');
-        DoComma();
-        if (defined $cgi->param('assigned_to')
-            && trim($cgi->param('assigned_to')) ne "") { 
-            $assignee = login_to_id(trim($cgi->param('assigned_to')), THROW_ERROR);
-            if (Bugzilla->params->{"strict_isolation"}) {
-                $usercache{$assignee} ||= Bugzilla::User->new($assignee);
-                my $assign_user = $usercache{$assignee};
-                foreach my $product_id (@newprod_ids) {
-                    if (!$assign_user->can_edit_product($product_id)) {
-                        my $product_name = Bugzilla::Product->new($product_id)->name;
-                        ThrowUserError('invalid_user_group',
-                                          {'users'   => $assign_user->login,
-                                           'product' => $product_name,
-                                           'bug_id' => (scalar(@idlist) > 1)
-                                                         ? undef : $idlist[0]
-                                          });
-                    }
-                }
-            }
-        } else {
-            ThrowUserError("reassign_to_empty");
-        }
-        $::query .= "assigned_to = ?";
-        push(@values, $assignee);
-        $assignee_checked = 1;
-        last SWITCH;
-    };
-    /^reassignbycomponent$/  && CheckonComment( "reassignbycomponent" ) && do {
-        if ($cgi->param('compconfirm')) {
-            DoConfirm($bug);
-        }
-        ChangeStatus('NEW');
-        last SWITCH;
-    };
-    /^reopen$/  && CheckonComment( "reopen" ) && do {
-        ChangeStatus('REOPENED');
-        ChangeResolution($bug, '');
-        last SWITCH;
-    };
-    /^verify$/ && CheckonComment( "verify" ) && do {
-        ChangeStatus('VERIFIED');
-        last SWITCH;
-    };
-    /^close$/ && CheckonComment( "close" ) && do {
-        # CLOSED bugs should have no time remaining.
-        _remove_remaining_time();
-
-        ChangeStatus('CLOSED');
-        last SWITCH;
-    };
-    /^duplicate$/ && CheckonComment( "duplicate" ) && do {
-        # You cannot mark bugs as duplicates when changing
-        # several bugs at once.
-        unless (defined $cgi->param('id')) {
-            ThrowUserError('dupe_not_allowed');
-        }
-
-        # Make sure we can change the original bug (issue A on bug 96085)
-        defined($cgi->param('dup_id'))
-          || ThrowCodeError('undefined_field', { field => 'dup_id' });
-
-        $duplicate = $cgi->param('dup_id');
-        ValidateBugID($duplicate, 'dup_id');
-        $cgi->param('dup_id', $duplicate);
-
-        # Make sure a loop isn't created when marking this bug
-        # as duplicate.
-        my %dupes;
-        my $dupe_of = $duplicate;
-        my $sth = $dbh->prepare('SELECT dupe_of FROM duplicates
-                                 WHERE dupe = ?');
-
-        while ($dupe_of) {
-            if ($dupe_of == $cgi->param('id')) {
-                ThrowUserError('dupe_loop_detected', { bug_id  => $cgi->param('id'),
-                                                       dupe_of => $duplicate });
-            }
-            # If $dupes{$dupe_of} 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{"$dupe_of"};
-            $dupes{"$dupe_of"} = 1;
-            $sth->execute($dupe_of);
-            $dupe_of = $sth->fetchrow_array;
-        }
-
-        # Also, let's see if the reporter has authorization to see
-        # the bug to which we are duping. If not we need to prompt.
-        DuplicateUserConfirm();
-
-        # DUPLICATE bugs should have no time remaining.
-        _remove_remaining_time();
-
-        ChangeStatus('RESOLVED');
-        ChangeResolution($bug, 'DUPLICATE');
-        last SWITCH;
-    };
-
-    ThrowCodeError("unknown_action", { action => $cgi->param('knob') });
-}
-
-my @keywordlist;
-my %keywordseen;
-
-if (defined $cgi->param('keywords')) {
-    foreach my $keyword (split(/[\s,]+/, $cgi->param('keywords'))) {
-        if ($keyword eq '') {
-            next;
-        }
-        my $keyword_obj = new Bugzilla::Keyword({name => $keyword});
-        if (!$keyword_obj) {
-            ThrowUserError("unknown_keyword",
-                           { keyword => $keyword });
-        }
-        if (!$keywordseen{$keyword_obj->id}) {
-            push(@keywordlist, $keyword_obj->id);
-            $keywordseen{$keyword_obj->id} = 1;
-        }
-    }
-}
-
-my $keywordaction = $cgi->param('keywordaction') || "makeexact";
-if (!grep($keywordaction eq $_, qw(add delete makeexact))) {
-    $keywordaction = "makeexact";
-}
-
-if ($::comma eq ""
-    && (! @groupAdd) && (! @groupDel)
-    && (!Bugzilla::Keyword::keyword_count() 
-        || (0 == @keywordlist && $keywordaction ne "makeexact"))
-    && defined $cgi->param('masscc') && ! $cgi->param('masscc')
-    ) {
-    if (!defined $cgi->param('comment') || $cgi->param('comment') =~ /^\s*$/) {
-        ThrowUserError("bugs_not_changed");
-    }
-}
-
-# Process data for Time Tracking fields
-if (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
-    foreach my $field ("estimated_time", "remaining_time") {
-        if (defined $cgi->param($field)) {
-            my $er_time = trim($cgi->param($field));
-            if ($er_time ne $cgi->param('dontchange')) {
-                DoComma();
-                $::query .= "$field = ?";
-                trick_taint($er_time);
-                push(@values, $er_time);
-            }
-        }
-    }
-
-    if (defined $cgi->param('deadline')) {
-        DoComma();
-        if ($cgi->param('deadline')) {
-            validate_date($cgi->param('deadline'))
-              || ThrowUserError('illegal_date', {date => $cgi->param('deadline'),
-                                                 format => 'YYYY-MM-DD'});
-            $::query .= "deadline = ?";
-            my $deadline = $cgi->param('deadline');
-            trick_taint($deadline);
-            push(@values, $deadline);
-        } else {
-            $::query .= "deadline = NULL";
-        }
-    }
-}
-
-my $basequery = $::query;
-
-local our $delta_ts;
-sub SnapShotBug {
-    my ($id) = (@_);
-    my $dbh = Bugzilla->dbh;
-    my @row = $dbh->selectrow_array(q{SELECT delta_ts, } .
-                join(',', editable_bug_fields()).q{ FROM bugs WHERE bug_id = ?},
-                undef, $id);
-    $delta_ts = shift @row;
-
-    return @row;
-}
-
-
-sub SnapShotDeps {
-    my ($bug_id, $target, $me) = (@_);
-    my $list = Bugzilla::Bug::EmitDependList($me, $target, $bug_id);
-    return join(',', @$list);
-}
-
-
-my $timestamp;
-
-local our $bug_changed;
-sub LogDependencyActivity {
-    my ($i, $oldstr, $target, $me, $timestamp) = (@_);
-    my $dbh = Bugzilla->dbh;
-    my $newstr = SnapShotDeps($i, $target, $me);
-    if ($oldstr ne $newstr) {
-        # Figure out what's really different...
-        my ($removed, $added) = diff_strings($oldstr, $newstr);
-        LogActivityEntry($i,$target,$removed,$added,$whoid,$timestamp);
-        # update timestamp on target bug so midairs will be triggered
-        $dbh->do(q{UPDATE bugs SET delta_ts = ? WHERE bug_id = ?},
-                 undef, $timestamp, $i);
-        $bug_changed = 1;
-        return 1;
-    }
-    return 0;
-}
-
-if (Bugzilla->params->{"strict_isolation"}) {
-    my @blocked_cc = ();
-    foreach my $pid (keys %cc_add) {
-        $usercache{$pid} ||= Bugzilla::User->new($pid);
-        my $cc_user = $usercache{$pid};
-        foreach my $product_id (@newprod_ids) {
-            if (!$cc_user->can_edit_product($product_id)) {
-                push (@blocked_cc, $cc_user->login);
-                last;
-            }
-        }
-    }
-    if (scalar(@blocked_cc)) {
-        ThrowUserError("invalid_user_group", 
-            {'users' => \@blocked_cc,
-             'bug_id' => (scalar(@idlist) > 1) ? undef : $idlist[0]});
-    }
-}
-
-if ($prod_changed && Bugzilla->params->{"strict_isolation"}) {
-    my $sth_cc = $dbh->prepare("SELECT who
-                                FROM cc
-                                WHERE bug_id = ?");
-    my $sth_bug = $dbh->prepare("SELECT assigned_to, qa_contact
-                                 FROM bugs
-                                 WHERE bug_id = ?");
-
-    foreach my $id (@idlist) {
-        $sth_cc->execute($id);
-        my @blocked_cc = ();
-        while (my ($pid) = $sth_cc->fetchrow_array) {
-            # Ignore deleted accounts. They will never get notification.
-            $usercache{$pid} ||= Bugzilla::User->new($pid) || next;
-            my $cc_user = $usercache{$pid};
-            if (!$cc_user->can_edit_product($product->id)) {
-                push (@blocked_cc, $cc_user->login);
-            }
-        }
-        if (scalar(@blocked_cc)) {
-            ThrowUserError('invalid_user_group',
-                              {'users'   => \@blocked_cc,
-                               'bug_id' => $id,
-                               'product' => $product->name});
-        }
-        $sth_bug->execute($id);
-        my ($assignee, $qacontact) = $sth_bug->fetchrow_array;
-        if (!$assignee_checked) {
-            $usercache{$assignee} ||= Bugzilla::User->new($assignee) || next;
-            my $assign_user = $usercache{$assignee};
-            if (!$assign_user->can_edit_product($product->id)) {
-                    ThrowUserError('invalid_user_group',
-                                      {'users'   => $assign_user->login,
-                                       'bug_id' => $id,
-                                       'product' => $product->name});
-            }
-        }
-        if (!$qacontact_checked && $qacontact) {
-            $usercache{$qacontact} ||= Bugzilla::User->new($qacontact) || next;
-            my $qa_user = $usercache{$qacontact};
-            if (!$qa_user->can_edit_product($product->id)) {
-                    ThrowUserError('invalid_user_group',
-                                      {'users'   => $qa_user->login,
-                                       'bug_id' => $id,
-                                       'product' => $product->name});
-            }
-        }
-    }
-}
-
-
-# This loop iterates once for each bug to be processed (i.e. all the
-# bugs selected when this script is called with multiple bugs selected
-# from buglist.cgi, or just the one bug when called from
-# show_bug.cgi).
-#
-foreach my $id (@idlist) {
-    my $query = $basequery;
-    my @bug_values = @values;
-    my $old_bug_obj = new Bugzilla::Bug($id);
-
-    if ($cgi->param('knob') eq 'reassignbycomponent') {
-        # We have to check whether the bug is moved to another product
-        # and/or component before reassigning. If $component is defined,
-        # use it; else use the product/component the bug is already in.
-        my $new_comp_id = $component ? $component->id : $old_bug_obj->{'component_id'};
-        $assignee = $dbh->selectrow_array('SELECT initialowner
-                                           FROM components
-                                           WHERE components.id = ?',
-                                           undef, $new_comp_id);
-        $query .= ", assigned_to = ?";
-        push(@bug_values, $assignee);
-        if (Bugzilla->params->{"useqacontact"}) {
-            $qacontact = $dbh->selectrow_array('SELECT initialqacontact
-                                                FROM components
-                                                WHERE components.id = ?',
-                                                undef, $new_comp_id);
-            if ($qacontact) {
-                $query .= ", qa_contact = ?";
-                push(@bug_values, $qacontact);
-            }
-            else {
-                $query .= ", qa_contact = NULL";
-            }
-        }
-
+    my %notify_deps;
+    if ($changes->{'bug_status'}) {
+        my ($old_status, $new_status) = @{ $changes->{'bug_status'} };
         
-
-        # And add in the Default CC for the Component.
-        my $comp_obj = $component || new Bugzilla::Component($new_comp_id);
-        my @new_init_cc = @{$comp_obj->initial_cc};
-        foreach my $cc (@new_init_cc) {
-            # NewCC must be defined or the code below won't insert
-            # any CCs.
-            $cgi->param('newcc') || $cgi->param('newcc', []);
-            $cc_add{$cc->id} = $cc->login;
+        # 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 %dependencychanged;
-    $bug_changed = 0;
-    my $write = "WRITE";        # Might want to make a param to control
-                                # whether we do LOW_PRIORITY ...
-    $dbh->bz_lock_tables("bugs $write", "bugs_activity $write", "cc $write",
-            "profiles READ", "dependencies $write", "votes $write",
-            "products READ", "components READ", "milestones READ",
-            "keywords $write", "longdescs $write", "fielddefs READ",
-            "bug_group_map $write", "flags $write", "duplicates $write",
-            "user_group_map READ", "group_group_map READ", "flagtypes READ",
-            "flaginclusions AS i READ", "flagexclusions AS e READ",
-            "keyworddefs READ", "groups READ", "attachments READ",
-            "group_control_map AS oldcontrolmap READ",
-            "group_control_map AS newcontrolmap READ",
-            "group_control_map READ", "email_setting READ", "classifications READ");
-
-    # It may sound crazy to set %formhash for each bug as $cgi->param()
-    # will not change, but %formhash is modified below and we prefer
-    # to set it again.
-    my $i = 0;
-    my @oldvalues = SnapShotBug($id);
-    my %oldhash;
-    my %formhash;
-    foreach my $col (@editable_bug_fields) {
-        # Consider NULL db entries to be equivalent to the empty string
-        $oldvalues[$i] = defined($oldvalues[$i]) ? $oldvalues[$i] : '';
-        # Convert the deadline taken from the DB into the YYYY-MM-DD format
-        # for consistency with the deadline provided by the user, if any.
-        # Else Bug::check_can_change_field() would see them as different
-        # in all cases.
-        if ($col eq 'deadline') {
-            $oldvalues[$i] = format_time($oldvalues[$i], "%Y-%m-%d");
-        }
-        $oldhash{$col} = $oldvalues[$i];
-        $formhash{$col} = $cgi->param($col) if defined $cgi->param($col);
-        $i++;
-    }
-    # If the user is reassigning bugs, we need to:
-    # - convert $newhash{'assigned_to'} and $newhash{'qa_contact'}
-    #   email addresses into their corresponding IDs;
-    # - update $newhash{'bug_status'} to its real state if the bug
-    #   is in the unconfirmed state.
-    $formhash{'qa_contact'} = $qacontact if Bugzilla->params->{'useqacontact'};
-    if ($cgi->param('knob') eq 'reassignbycomponent'
-        || $cgi->param('knob') eq 'reassign') {
-        $formhash{'assigned_to'} = $assignee;
-        if ($oldhash{'bug_status'} eq 'UNCONFIRMED') {
-            $formhash{'bug_status'} = $oldhash{'bug_status'};
-        }
-    }
-    # This hash is required by Bug::check_can_change_field().
-    my $cgi_hash = {
-        'dontchange' => scalar $cgi->param('dontchange'),
-        'knob'       => scalar $cgi->param('knob')
-    };
-    foreach my $col (@editable_bug_fields) {
-        # The 'resolution' field is checked by ChangeResolution(),
-        # i.e. only if we effectively use it.
-        next if ($col eq 'resolution');
-        if (exists $formhash{$col}
-            && !$old_bug_obj->check_can_change_field($col, $oldhash{$col}, $formhash{$col},
-                                                     \$PrivilegesRequired, $cgi_hash))
-        {
-            my $vars;
-            if ($col eq 'component_id') {
-                # Display the component name
-                $vars->{'oldvalue'} = $old_bug_obj->component;
-                $vars->{'newvalue'} = $cgi->param('component');
-                $vars->{'field'} = 'component';
-            } elsif ($col eq 'assigned_to' || $col eq 'qa_contact') {
-                # Display the assignee or QA contact email address
-                $vars->{'oldvalue'} = user_id_to_login($oldhash{$col});
-                $vars->{'newvalue'} = user_id_to_login($formhash{$col});
-                $vars->{'field'} = $col;
-            } else {
-                $vars->{'oldvalue'} = $oldhash{$col};
-                $vars->{'newvalue'} = $formhash{$col};
-                $vars->{'field'} = $col;
-            }
-            $vars->{'privs'} = $PrivilegesRequired;
-            ThrowUserError("illegal_change", $vars);
-        }
-    }
-    
-    # When editing multiple bugs, users can specify a list of keywords to delete
-    # from bugs.  If the list matches the current set of keywords on those bugs,
-    # Bug::check_can_change_field will fail to check permissions because it thinks
-    # the list hasn't changed. To fix that, we have to call Bug::check_can_change_field
-    # again with old!=new if the keyword action is "delete" and old=new.
-    if ($keywordaction eq "delete"
-        && defined $cgi->param('keywords')
-        && length(@keywordlist) > 0
-        && $cgi->param('keywords') eq $oldhash{keywords}
-        && !$old_bug_obj->check_can_change_field("keywords", "old is not", "equal to new",
-                                                 \$PrivilegesRequired))
-    {
-        $vars->{'oldvalue'} = $oldhash{keywords};
-        $vars->{'newvalue'} = "no keywords";
-        $vars->{'field'} = "keywords";
-        $vars->{'privs'} = $PrivilegesRequired;
-        ThrowUserError("illegal_change", $vars);
-    }
-
-    $oldhash{'product'} = $old_bug_obj->product;
-    if (!Bugzilla->user->can_edit_product($oldhash{'product_id'})) {
-        ThrowUserError("product_edit_denied",
-                      { product => $oldhash{'product'} });
-    }
-
-    if ($requiremilestone) {
-        # musthavemilestoneonaccept applies only if at least two
-        # target milestones are defined for the current product.
-        my $prod_obj = new Bugzilla::Product({'name' => $oldhash{'product'}});
-        my $nb_milestones = scalar(@{$prod_obj->milestones});
-        if ($nb_milestones > 1) {
-            my $value = $cgi->param('target_milestone');
-            if (!defined $value || $value eq $cgi->param('dontchange')) {
-                $value = $oldhash{'target_milestone'};
-            }
-            my $defaultmilestone =
-                $dbh->selectrow_array("SELECT defaultmilestone
-                                       FROM products WHERE id = ?",
-                                       undef, $oldhash{'product_id'});
-            # if musthavemilestoneonaccept == 1, then the target
-            # milestone must be different from the default one.
-            if ($value eq $defaultmilestone) {
-                ThrowUserError("milestone_required", { bug_id => $id });
-            }
-        }
-    }   
-    if (defined $cgi->param('delta_ts') && $cgi->param('delta_ts') ne $delta_ts)
-    {
-        ($vars->{'operations'}) =
-            Bugzilla::Bug::GetBugActivity($id, $cgi->param('delta_ts'));
-
-        $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($id, "oldest_to_newest");
-
-        $cgi->param('delta_ts', $delta_ts);
         
-        $vars->{'bug_id'} = $id;
-        
-        $dbh->bz_unlock_tables(UNLOCK_ABORT);
-        
-        # 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;
-    }
-
-    # Gather the dependency list, and make sure there are no circular refs
-    my %deps = Bugzilla::Bug::ValidateDependencies(scalar($cgi->param('dependson')),
-                                                   scalar($cgi->param('blocked')),
-                                                   $id);
-
-    #
-    # Start updating the relevant database entries
-    #
-
-    $timestamp = $dbh->selectrow_array(q{SELECT NOW()});
-
-    my $work_time;
-    if (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
-        $work_time = $cgi->param('work_time');
-        if ($work_time) {
-            # AppendComment (called below) can in theory raise an error,
-            # but because we've already validated work_time here it's
-            # safe to log the entry before adding the comment.
-            LogActivityEntry($id, "work_time", "", $work_time,
-                             $whoid, $timestamp);
+        # 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 ($cgi->param('comment') || $work_time || $duplicate) {
-        my $type = $duplicate ? CMT_DUPE_OF : CMT_NORMAL;
-
-        AppendComment($id, $whoid, scalar($cgi->param('comment')),
-                      scalar($cgi->param('commentprivacy')), $timestamp,
-                      $work_time, $type, $duplicate);
-        $bug_changed = 1;
-    }
-
-    if (Bugzilla::Keyword::keyword_count() 
-        && defined $cgi->param('keywords')) 
-    {
-        # There are three kinds of "keywordsaction": makeexact, add, delete.
-        # For makeexact, we delete everything, and then add our things.
-        # For add, we delete things we're adding (to make sure we don't
-        # end up having them twice), and then we add them.
-        # For delete, we just delete things on the list.
-        my $changed = 0;
-        if ($keywordaction eq "makeexact") {
-            $dbh->do(q{DELETE FROM keywords WHERE bug_id = ?},
-                     undef, $id);
-            $changed = 1;
-        }
-        my $sth_delete = $dbh->prepare(q{DELETE FROM keywords
-                                               WHERE bug_id = ?
-                                                 AND keywordid = ?});
-        my $sth_insert =
-            $dbh->prepare(q{INSERT INTO keywords (bug_id, keywordid)
-                                 VALUES (?, ?)});
-        foreach my $keyword (@keywordlist) {
-            if ($keywordaction ne "makeexact") {
-                $sth_delete->execute($id, $keyword);
-                $changed = 1;
-            }
-            if ($keywordaction ne "delete") {
-                $sth_insert->execute($id, $keyword);
-                $changed = 1;
-            }
-        }
-        if ($changed) {
-            my $list = $dbh->selectcol_arrayref(
-                q{SELECT keyworddefs.name
-                    FROM keyworddefs
-              INNER JOIN keywords 
-                      ON keyworddefs.id = keywords.keywordid
-                   WHERE keywords.bug_id = ?
-                ORDER BY keyworddefs.name},
-                undef, $id);
-            $dbh->do("UPDATE bugs SET keywords = ? WHERE bug_id = ?",
-                     undef, join(', ', @$list), $id);
-        }
-    }
-    $query .= " WHERE bug_id = ?";
-    push(@bug_values, $id);
-    
-    if ($::comma ne "") {
-        $dbh->do($query, undef, @bug_values);
-    }
-
-    # Check for duplicates if the bug is [re]open or its resolution is changed.
-    my $resolution = $dbh->selectrow_array(
-        q{SELECT resolution FROM bugs WHERE bug_id = ?}, undef, $id);
-    if ($resolution ne 'DUPLICATE') {
-        $dbh->do(q{DELETE FROM duplicates WHERE dupe = ?}, undef, $id);
-    }
-
-    my %groupsrequired = ();
-    my %groupsforbidden = ();
-    my $group_controls =
-        $dbh->selectall_arrayref(q{SELECT id, membercontrol
-                                     FROM groups
-                                LEFT JOIN group_control_map
-                                       ON id = group_id
-                                      AND product_id = ?
-                                    WHERE isactive != 0},
-        undef, $oldhash{'product_id'});
-    foreach my $group_control (@$group_controls) {
-        my ($group, $control) = @$group_control;
-        $control ||= 0;
-        unless ($control > CONTROLMAPNA)  {
-            $groupsforbidden{$group} = 1;
-        }
-        if ($control == CONTROLMAPMANDATORY) {
-            $groupsrequired{$group} = 1;
-        }
-    }
-
-    my @groupAddNames = ();
-    my @groupAddNamesAll = ();
-    my $sth = $dbh->prepare(q{INSERT INTO bug_group_map (bug_id, group_id)
-                                   VALUES (?, ?)});
-    foreach my $grouptoadd (@groupAdd, keys %groupsrequired) {
-        next if $groupsforbidden{$grouptoadd};
-        my $group_obj = new Bugzilla::Group($grouptoadd);
-        push(@groupAddNamesAll, $group_obj->name);
-        if (!BugInGroupId($id, $grouptoadd)) {
-            push(@groupAddNames, $group_obj->name);
-            $sth->execute($id, $grouptoadd);
-        }
-    }
-    my @groupDelNames = ();
-    my @groupDelNamesAll = ();
-    $sth = $dbh->prepare(q{DELETE FROM bug_group_map
-                                 WHERE bug_id = ? AND group_id = ?});
-    foreach my $grouptodel (@groupDel, keys %groupsforbidden) {
-        my $group_obj = new Bugzilla::Group($grouptodel);
-        push(@groupDelNamesAll, $group_obj->name);
-        next if $groupsrequired{$grouptodel};
-        if (BugInGroupId($id, $grouptodel)) {
-            push(@groupDelNames, $group_obj->name);
-        }
-        $sth->execute($id, $grouptodel);
-    }
-
-    my $groupDelNames = join(',', @groupDelNames);
-    my $groupAddNames = join(',', @groupAddNames);
-
-    if ($groupDelNames ne $groupAddNames) {
-        LogActivityEntry($id, "bug_group", $groupDelNames, $groupAddNames,
-                         $whoid, $timestamp); 
-        $bug_changed = 1;
-    }
-
-    my @ccRemoved = (); 
-    if (defined $cgi->param('newcc')
-        || defined $cgi->param('addselfcc')
-        || defined $cgi->param('removecc')
-        || defined $cgi->param('masscc')) {
-        # Get the current CC list for this bug
-        my %oncc;
-        my $cc_list = $dbh->selectcol_arrayref(
-            q{SELECT who FROM cc WHERE bug_id = ?}, undef, $id);
-        foreach my $who (@$cc_list) {
-            $oncc{$who} = 1;
-        }
-
-        my (@added, @removed) = ();
-
-        my $sth_insert = $dbh->prepare(q{INSERT INTO cc (bug_id, who)
-                                              VALUES (?, ?)});
-        foreach my $pid (keys %cc_add) {
-            # If this person isn't already on the cc list, add them
-            if (! $oncc{$pid}) {
-                $sth_insert->execute($id, $pid);
-                push (@added, $cc_add{$pid});
-                $oncc{$pid} = 1;
-            }
-        }
-        my $sth_delete = $dbh->prepare(q{DELETE FROM cc
-                                          WHERE bug_id = ? AND who = ?});
-        foreach my $pid (keys %cc_remove) {
-            # If the person is on the cc list, remove them
-            if ($oncc{$pid}) {
-                $sth_delete->execute($id, $pid);
-                push (@removed, $cc_remove{$pid});
-                $oncc{$pid} = 0;
-            }
-        }
-
-        # If any changes were found, record it in the activity log
-        if (scalar(@removed) || scalar(@added)) {
-            my $removed = join(", ", @removed);
-            my $added = join(", ", @added);
-            LogActivityEntry($id,"cc",$removed,$added,$whoid,$timestamp);
-            $bug_changed = 1;
-        }
-        @ccRemoved = @removed;
-    }
-
-    # We need to send mail for dependson/blocked bugs if the dependencies
-    # change or the status or resolution change. This var keeps track of that.
-    my $check_dep_bugs = 0;
-
-    foreach my $pair ("blocked/dependson", "dependson/blocked") {
-        my ($me, $target) = split("/", $pair);
-
-        my @oldlist = @{$dbh->selectcol_arrayref("SELECT $target FROM dependencies
-                                                  WHERE $me = ? ORDER BY $target",
-                                                  undef, $id)};
-
-        # Only bugs depending on the current one should get notification.
-        # Bugs blocking the current one never get notification, unless they
-        # are added or removed from the dependency list. This case is treated
-        # below.
-        @dependencychanged{@oldlist} = 1 if ($me eq 'dependson');
-
-        if (defined $cgi->param($target)) {
-            my %snapshot;
-            my @newlist = sort {$a <=> $b} @{$deps{$target}};
-
-            while (0 < @oldlist || 0 < @newlist) {
-                if (@oldlist == 0 || (@newlist > 0 &&
-                                      $oldlist[0] > $newlist[0])) {
-                    $snapshot{$newlist[0]} = SnapShotDeps($newlist[0], $me,
-                                                          $target);
-                    shift @newlist;
-                } elsif (@newlist == 0 || (@oldlist > 0 &&
-                                           $newlist[0] > $oldlist[0])) {
-                    $snapshot{$oldlist[0]} = SnapShotDeps($oldlist[0], $me,
-                                                          $target);
-                    shift @oldlist;
-                } else {
-                    if ($oldlist[0] != $newlist[0]) {
-                        ThrowCodeError('list_comparison_error');
-                    }
-                    shift @oldlist;
-                    shift @newlist;
-                }
-            }
-            my @keys = keys(%snapshot);
-            if (@keys) {
-                my $oldsnap = SnapShotDeps($id, $target, $me);
-                $dbh->do(qq{DELETE FROM dependencies WHERE $me = ?},
-                         undef, $id);
-                my $sth =
-                    $dbh->prepare(qq{INSERT INTO dependencies ($me, $target)
-                                          VALUES (?, ?)});
-                foreach my $i (@{$deps{$target}}) {
-                    $sth->execute($id, $i);
-                }
-                foreach my $k (@keys) {
-                    LogDependencyActivity($k, $snapshot{$k}, $me, $target, $timestamp);
-                }
-                LogDependencyActivity($id, $oldsnap, $target, $me, $timestamp);
-                $check_dep_bugs = 1;
-                # All bugs added or removed from the dependency list
-                # must be notified.
-                @dependencychanged{@keys} = 1;
-            }
-        }
-    }
-
-    # When a bug changes products and the old or new product is associated
-    # with a bug group, it may be necessary to remove the bug from the old
-    # group or add it to the new one.  There are a very specific series of
-    # conditions under which these activities take place, more information
-    # about which can be found in comments within the conditionals below.
-    # Check if the user has changed the product to which the bug belongs;
-    if ($cgi->param('product') ne $cgi->param('dontchange')
-        && $cgi->param('product') ne $oldhash{'product'})
-    {
-        # Depending on the "addtonewgroup" variable, groups with
-        # defaults will change.
-        #
-        # For each group, determine
-        # - The group id and if it is active
-        # - The control map value for the old product and this group
-        # - The control map value for the new product and this group
-        # - Is the user in this group?
-        # - Is the bug in this group?
-        my $groups = $dbh->selectall_arrayref(
-            qq{SELECT DISTINCT groups.id, isactive,
-                               oldcontrolmap.membercontrol,
-                               newcontrolmap.membercontrol,
-                      CASE WHEN groups.id IN ($grouplist) THEN 1 ELSE 0 END,
-                      CASE WHEN bug_group_map.group_id IS NOT NULL
-                                THEN 1 ELSE 0 END
-                 FROM groups
-            LEFT JOIN group_control_map AS oldcontrolmap
-                   ON oldcontrolmap.group_id = groups.id
-                  AND oldcontrolmap.product_id = ?
-            LEFT JOIN group_control_map AS newcontrolmap
-                   ON newcontrolmap.group_id = groups.id
-                  AND newcontrolmap.product_id = ?
-            LEFT JOIN bug_group_map
-                   ON bug_group_map.group_id = groups.id
-                  AND bug_group_map.bug_id = ?},
-            undef, $oldhash{'product_id'}, $product->id, $id);
-        my @groupstoremove = ();
-        my @groupstoadd = ();
-        my @defaultstoadd = ();
-        my @allgroups = ();
-        my $buginanydefault = 0;
-        foreach my $group (@$groups) {
-            my ($groupid, $isactive, $oldcontrol, $newcontrol,
-                   $useringroup, $bugingroup) = @$group;
-            # An undefined newcontrol is none.
-            $newcontrol = CONTROLMAPNA unless $newcontrol;
-            $oldcontrol = CONTROLMAPNA unless $oldcontrol;
-            push(@allgroups, $groupid);
-            if (($bugingroup) && ($isactive)
-                && ($oldcontrol == CONTROLMAPDEFAULT)) {
-                # Bug was in a default group.
-                $buginanydefault = 1;
-            }
-            if (($isactive) && (!$bugingroup)
-                && ($newcontrol == CONTROLMAPDEFAULT)
-                && ($useringroup)) {
-                push (@defaultstoadd, $groupid);
-            }
-            if (($bugingroup) && ($isactive) && ($newcontrol == CONTROLMAPNA)) {
-                # Group is no longer permitted.
-                push(@groupstoremove, $groupid);
-            }
-            if ((!$bugingroup) && ($isactive) 
-                && ($newcontrol == CONTROLMAPMANDATORY)) {
-                # Group is now required.
-                push(@groupstoadd, $groupid);
-            }
-        }
-        # If addtonewgroups = "yes", new default groups will be added.
-        # If addtonewgroups = "yesifinold", new default groups will be
-        # added only if the bug was in ANY of the old default groups.
-        # If addtonewgroups = "no", old default groups are left alone
-        # and no new default group will be added.
-        if (AnyDefaultGroups()
-            && (($cgi->param('addtonewgroup') eq 'yes')
-            || (($cgi->param('addtonewgroup') eq 'yesifinold')
-            && ($buginanydefault)))) {
-            push(@groupstoadd, @defaultstoadd);
-        }
-
-        # Now actually update the bug_group_map.
-        my @DefGroupsAdded = ();
-        my @DefGroupsRemoved = ();
-        my $sth_insert =
-            $dbh->prepare(q{INSERT INTO bug_group_map (bug_id, group_id)
-                                 VALUES (?, ?)});
-        my $sth_delete = $dbh->prepare(q{DELETE FROM bug_group_map
-                                               WHERE bug_id = ?
-                                                 AND group_id = ?});
-        foreach my $groupid (@allgroups) {
-            my $thisadd = grep( ($_ == $groupid), @groupstoadd);
-            my $thisdel = grep( ($_ == $groupid), @groupstoremove);
-            if ($thisadd) {
-                my $group_obj = new Bugzilla::Group($groupid);
-                push(@DefGroupsAdded, $group_obj->name);
-                $sth_insert->execute($id, $groupid);
-            } elsif ($thisdel) {
-                my $group_obj = new Bugzilla::Group($groupid);
-                push(@DefGroupsRemoved, $group_obj->name);
-                $sth_delete->execute($id, $groupid);
-            }
-        }
-        if ((@DefGroupsAdded) || (@DefGroupsRemoved)) {
-            LogActivityEntry($id, "bug_group",
-                join(', ', @DefGroupsRemoved),
-                join(', ', @DefGroupsAdded),
-                     $whoid, $timestamp); 
-        }
-    }
-  
-    # get a snapshot of the newly set values out of the database, 
-    # and then generate any necessary bug activity entries by seeing 
-    # what has changed since before we wrote out the new values.
-    #
-    my $new_bug_obj = new Bugzilla::Bug($id);
-    my @newvalues = SnapShotBug($id);
-    my %newhash;
-    $i = 0;
-    foreach my $col (@editable_bug_fields) {
-        # Consider NULL db entries to be equivalent to the empty string
-        $newvalues[$i] = defined($newvalues[$i]) ? $newvalues[$i] : '';
-        # Convert the deadline to the YYYY-MM-DD format.
-        if ($col eq 'deadline') {
-            $newvalues[$i] = format_time($newvalues[$i], "%Y-%m-%d");
-        }
-        $newhash{$col} = $newvalues[$i];
-        $i++;
-    }
-    # for passing to Bugzilla::BugMail to ensure that when someone is removed
-    # from one of these fields, they get notified of that fact (if desired)
-    #
-    my $origOwner = "";
-    my $origQaContact = "";
+    # 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;
-
-    foreach my $c (@editable_bug_fields) {
-        my $col = $c;           # We modify it, don't want to modify array
-                                # values in place.
-        my $old = shift @oldvalues;
-        my $new = shift @newvalues;
-        if ($old ne $new) {
-
-            # Products and components are now stored in the DB using ID's
-            # We need to translate this to English before logging it
-            if ($col eq 'product_id') {
-                $old = $old_bug_obj->product;
-                $new = $new_bug_obj->product;
-                $col = 'product';
-            }
-            if ($col eq 'component_id') {
-                $old = $old_bug_obj->component;
-                $new = $new_bug_obj->component;
-                $col = 'component';
-            }
-
-            # save off the old value for passing to Bugzilla::BugMail so
-            # the old assignee can be notified
-            #
-            if ($col eq 'assigned_to') {
-                $old = ($old) ? user_id_to_login($old) : "";
-                $new = ($new) ? user_id_to_login($new) : "";
-                $origOwner = $old;
-            }
-
-            # ditto for the old qa contact
-            #
-            if ($col eq 'qa_contact') {
-                $old = ($old) ? user_id_to_login($old) : "";
-                $new = ($new) ? user_id_to_login($new) : "";
-                $origQaContact = $old;
-            }
-
-            # If this is the keyword field, only record the changes, not everything.
-            if ($col eq 'keywords') {
-                ($old, $new) = diff_strings($old, $new);
-            }
-
-            if ($col eq '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($id, 0,
-                          "This bug has been moved to a different product");
-
-                CheckIfVotedConfirmed($id, $whoid);
-            }
-
-            if ($col eq 'bug_status' 
-                && is_open_state($old) ne is_open_state($new))
-            {
-                $check_dep_bugs = 1;
-            }
-
-            LogActivityEntry($id,$col,$old,$new,$whoid,$timestamp);
-            $bug_changed = 1;
-        }
+    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($new_bug_obj, undef, $timestamp, $cgi);
+    Bugzilla::Flag->process($bug, undef, $timestamp, $vars);
 
-    if ($bug_changed) {
-        $dbh->do(q{UPDATE bugs SET delta_ts = ? WHERE bug_id = ?},
-                 undef, $timestamp, $id);
-    }
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
+
+    ###############
+    # Send Emails #
+    ###############
 
     # Now is a good time to send email to voters.
     foreach my $msg (@$msgs) {
         MessageToMTA($msg);
     }
 
-    if ($duplicate) {
-        # If the bug was already marked as a duplicate, remove
-        # the existing entry.
-        $dbh->do('DELETE FROM duplicates WHERE dupe = ?',
-                  undef, $cgi->param('id'));
+    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 };
 
-        # Check to see if Reporter of this bug is reporter of Dupe 
-        my $reporter = $dbh->selectrow_array(
-            q{SELECT reporter FROM bugs WHERE bug_id = ?}, undef, $id);
-        my $isreporter = $dbh->selectrow_array(
-            q{SELECT reporter FROM bugs WHERE bug_id = ? AND reporter = ?},
-            undef, $duplicate, $reporter);
-        my $isoncc = $dbh->selectrow_array(q{SELECT who FROM cc
-                                           WHERE bug_id = ? AND who = ?},
-                                           undef, $duplicate, $reporter);
-        unless ($isreporter || $isoncc
-                || !$cgi->param('confirm_add_duplicate')) {
-            # The reporter is oblivious to the existence of the new bug and is permitted access
-            # ... add 'em to the cc (and record activity)
-            LogActivityEntry($duplicate,"cc","",user_id_to_login($reporter),
-                             $whoid,$timestamp);
-            $dbh->do(q{INSERT INTO cc (who, bug_id) VALUES (?, ?)},
-                     undef, $reporter, $duplicate);
-        }
-        # Bug 171639 - Duplicate notifications do not need to be private.
-        AppendComment($duplicate, $whoid, "", 0, $timestamp, 0,
-                      CMT_HAS_DUPE, scalar $cgi->param('id'));
-
-        $dbh->do(q{INSERT INTO duplicates VALUES (?, ?)}, undef,
-                 $duplicate, $cgi->param('id'));
-    }
-
-    # Now all changes to the DB have been made. It's time to email
-    # all concerned users, including the bug itself, but also the
-    # duplicated bug and dependent bugs, if any.
-
-    $vars->{'mailrecipients'} = { 'cc' => \@ccRemoved,
-                                  'owner' => $origOwner,
-                                  'qacontact' => $origQaContact,
-                                  'changer' => Bugzilla->user->login };
-
-    $vars->{'id'} = $id;
+    $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($id, $vars);
+    send_results($bug->id, $vars);
  
-    if ($duplicate) {
+    # 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'} = $duplicate;
+        $vars->{'id'} = $new_dup_id;
         $vars->{'type'} = "dupe";
         
         # Let the user know a duplication notation was added to the 
         # original bug.
-        send_results($duplicate, $vars);
+        send_results($new_dup_id, $vars);
     }
 
-    if ($check_dep_bugs) {
-        foreach my $k (keys(%dependencychanged)) {
-            $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login }; 
-            $vars->{'id'} = $k;
-            $vars->{'type'} = "dep";
+    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($k, $vars);
-        }
+        # 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);
     }
 }
 
@@ -2193,22 +639,10 @@
     $vars->{'patchviewerinstalled'} = 1;
 };
 
-if (defined $cgi->param('id')) {
-    $action = Bugzilla->user->settings->{'post_bug_submit_action'}->{'value'};
-} else {
-    # param('id') is not defined when changing multiple bugs
-    $action = 'nothing';
-}
-
 if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
     # Do nothing.
 }
 elsif ($action eq 'next_bug') {
-    my $next_bug;
-    my $cur = lsearch(\@bug_list, $cgi->param("id"));
-    if ($cur >= 0 && $cur < $#bug_list) {
-        $next_bug = $bug_list[$cur + 1];
-    }
     if ($next_bug) {
         if (detaint_natural($next_bug) && Bugzilla->user->can_see_bug($next_bug)) {
             my $bug = new Bugzilla::Bug($next_bug);
@@ -2241,9 +675,6 @@
 
 # End the response page.
 unless (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
-    # The user pref is 'Do nothing', so all we need is the current bug ID.
-    $vars->{'bug'} = {bug_id => scalar $cgi->param('id')};
-
     $template->process("bug/navigate.html.tmpl", $vars)
         || ThrowTemplateError($template->error());
     $template->process("global/footer.html.tmpl", $vars)
diff --git a/BugsSite/productmenu.js b/BugsSite/productmenu.js
deleted file mode 100644
index 90bb181..0000000
--- a/BugsSite/productmenu.js
+++ /dev/null
@@ -1,256 +0,0 @@
-// Adds to the target select object all elements in array that
-// correspond to the elements selected in source.
-//     - array should be a array of arrays, indexed by product name. the
-//       array should contain the elements that correspont to that
-//       product. Example:
-//         var array = Array();
-//         array['ProductOne'] = [ 'ComponentA', 'ComponentB' ];
-//         updateSelect(array, source, target);
-//     - sel is a list of selected items, either whole or a diff
-//       depending on sel_is_diff.
-//     - sel_is_diff determines if we are sending in just a diff or the
-//       whole selection. a diff is used to optimize adding selections.
-//     - target should be the target select object.
-//     - single specifies if we selected a single item. if we did, no
-//       need to merge.
-
-function updateSelect( array, sel, target, sel_is_diff, single, blank ) {
-
-    var i, j, comp;
-
-    // if single, even if it's a diff (happens when you have nothing
-    // selected and select one item alone), skip this.
-    if ( ! single ) {
-
-        // array merging/sorting in the case of multiple selections
-        if ( sel_is_diff ) {
-
-            // merge in the current options with the first selection
-            comp = merge_arrays( array[sel[0]], target.options, 1 );
-
-            // merge the rest of the selection with the results
-            for ( i = 1 ; i < sel.length ; i++ ) {
-                comp = merge_arrays( array[sel[i]], comp, 0 );
-            }
-        } else {
-            // here we micro-optimize for two arrays to avoid merging with a
-            // null array
-            comp = merge_arrays( array[sel[0]],array[sel[1]], 0 );
-
-            // merge the arrays. not very good for multiple selections.
-            for ( i = 2; i < sel.length; i++ ) {
-                comp = merge_arrays( comp, array[sel[i]], 0 );
-            }
-        }
-    } else {
-        // single item in selection, just get me the list
-        comp = array[sel[0]];
-    }
-
-    // save the selection in the target select so we can restore it later
-    var selections = new Array();
-    for ( i = 0; i < target.options.length; i++ )
-      if (target.options[i].selected) selections.push(target.options[i].value);
-
-    // clear select
-    target.options.length = 0;
-
-    // add empty "Any" value back to the list
-    if (blank) target.options[0] = new Option( blank, "" );
-
-    // load elements of list into select
-    for ( i = 0; i < comp.length; i++ ) {
-        target.options[target.options.length] = new Option( comp[i], comp[i] );
-    }
-
-    // restore the selection
-    for ( i=0 ; i<selections.length ; i++ )
-      for ( j=0 ; j<target.options.length ; j++ )
-        if (target.options[j].value == selections[i]) target.options[j].selected = true;
-
-}
-
-// Returns elements in a that are not in b.
-// NOT A REAL DIFF: does not check the reverse.
-//     - a,b: arrays of values to be compare.
-
-function fake_diff_array( a, b ) {
-    var newsel = new Array();
-
-    // do a boring array diff to see who's new
-        for ( var ia in a ) {
-            var found = 0;
-            for ( var ib in b ) {
-                if ( a[ia] == b[ib] ) {
-                    found = 1;
-                }
-            }
-            if ( ! found ) {
-                newsel[newsel.length] = a[ia];
-            }
-            found = 0;
-        }
-        return newsel;
-    }
-
-// takes two arrays and sorts them by string, returning a new, sorted
-// array. the merge removes dupes, too.
-//     - a, b: arrays to be merge.
-//     - b_is_select: if true, then b is actually an optionitem and as
-//       such we need to use item.value on it.
-
-    function merge_arrays( a, b, b_is_select ) {
-        var pos_a = 0;
-        var pos_b = 0;
-        var ret = new Array();
-        var bitem, aitem;
-
-    // iterate through both arrays and add the larger item to the return
-    // list. remove dupes, too. Use toLowerCase to provide
-    // case-insensitivity.
-
-        while ( ( pos_a < a.length ) && ( pos_b < b.length ) ) {
-
-            if ( b_is_select ) {
-                bitem = b[pos_b].value;
-            } else {
-                bitem = b[pos_b];
-            }
-            aitem = a[pos_a];
-
-        // smaller item in list a
-            if ( aitem.toLowerCase() < bitem.toLowerCase() ) {
-                ret[ret.length] = aitem;
-                pos_a++;
-            } else {
-            // smaller item in list b
-                if ( aitem.toLowerCase() > bitem.toLowerCase() ) {
-                    ret[ret.length] = bitem;
-                    pos_b++;
-                } else {
-                // list contents are equal, inc both counters.
-                    ret[ret.length] = aitem;
-                pos_a++;
-                pos_b++;
-            }
-        }
-        }
-
-    // catch leftovers here. these sections are ugly code-copying.
-        if ( pos_a < a.length ) {
-            for ( ; pos_a < a.length ; pos_a++ ) {
-                ret[ret.length] = a[pos_a];
-            }
-        }
-
-        if ( pos_b < b.length ) {
-            for ( ; pos_b < b.length; pos_b++ ) {
-                if ( b_is_select ) {
-                    bitem = b[pos_b].value;
-                } else {
-                    bitem = b[pos_b];
-                }
-                ret[ret.length] = bitem;
-            }
-        }
-        return ret;
-    }
-
-// selectProduct reads the selection from f[productfield] and updates
-// f.version, component and target_milestone accordingly.
-//     - f: a form containing product, component, varsion and
-//       target_milestone select boxes.
-// globals (3vil!):
-//     - cpts, vers, tms: array of arrays, indexed by product name. the
-//       subarrays contain a list of names to be fed to the respective
-//       selectboxes. For bugzilla, these are generated with perl code
-//       at page start.
-//     - usetms: this is a global boolean that is defined if the
-//       bugzilla installation has it turned on. generated in perl too.
-//     - first_load: boolean, specifying if it's the first time we load
-//       the query page.
-//     - last_sel: saves our last selection list so we know what has
-//       changed, and optimize for additions.
-
-function selectProduct( f , productfield, componentfield, blank ) {
-
-    // this is to avoid handling events that occur before the form
-    // itself is ready, which happens in buggy browsers.
-
-    if ( ( !f ) || ( ! f[productfield] ) ) {
-        return;
-    }
-
-    // Do nothing if no products are defined (this avoids the
-    // "a has no properties" error from merge_arrays function)
-    if (f[productfield].length == blank ? 1 : 0) {
-        return;
-    }
-
-    // if this is the first load and nothing is selected, no need to
-    // merge and sort all components; perl gives it to us sorted.
-
-    if ( ( first_load ) && ( f[productfield].selectedIndex == -1 ) ) {
-            first_load = 0;
-            return;
-    }
-
-    // turn first_load off. this is tricky, since it seems to be
-    // redundant with the above clause. It's not: if when we first load
-    // the page there is _one_ element selected, it won't fall into that
-    // clause, and first_load will remain 1. Then, if we unselect that
-    // item, selectProduct will be called but the clause will be valid
-    // (since selectedIndex == -1), and we will return - incorrectly -
-    // without merge/sorting.
-
-    first_load = 0;
-
-    // - sel keeps the array of products we are selected.
-    // - is_diff says if it's a full list or just a list of products that
-    //   were added to the current selection.
-    // - single indicates if a single item was selected
-    // - selectedIndex is the index of the first selected item
-    // - selectedValue is the value of the first selected item
-    var sel = Array();
-    var is_diff = 0;
-    var single;
-    var selectedIndex = f[productfield].selectedIndex;
-    var selectedValue = f[productfield].options[selectedIndex].value;
-
-    // If nothing is selected, or the selected item is the "blank" value
-    // at the top of the list which represents all products on drop-down menus,
-    // then pick all products so we show all components.
-    if ( selectedIndex == -1 || !cpts[selectedValue])
-    {
-        for ( var i = blank ? 1 : 0 ; i < f[productfield].length ; i++ ) {
-            sel[sel.length] = f[productfield].options[i].value;
-        }
-        // If there is only one product, then only one product can be selected
-        single = ( sel.length == 1 );
-    } else {
-
-        for ( i = blank ? 1 : 0 ; i < f[productfield].length ; i++ ) {
-            if ( f[productfield].options[i].selected ) {
-                sel[sel.length] = f[productfield].options[i].value;
-            }
-        }
-
-        single = ( sel.length == 1 );
-
-        // save last_sel before we kill it
-            var tmp = last_sel;
-        last_sel = sel;
-
-        // this is an optimization: if we've added components, no need
-        // to remerge them; just merge the new ones with the existing
-        // options.
-
-        if ( ( tmp ) && ( tmp.length < sel.length ) ) {
-            sel = fake_diff_array(sel, tmp);
-            is_diff = 1;
-        }
-    }
-
-    // do the actual fill/update
-    updateSelect( cpts, sel, f[componentfield], is_diff, single, blank );
-}
diff --git a/BugsSite/query.cgi b/BugsSite/query.cgi
index caf9da5..5998c31 100755
--- a/BugsSite/query.cgi
+++ b/BugsSite/query.cgi
@@ -26,7 +26,7 @@
 #                 Max Kanat-Alexander <mkanat@bugzilla.org>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Bug;
@@ -38,7 +38,7 @@
 use Bugzilla::Product;
 use Bugzilla::Keyword;
 use Bugzilla::Field;
-use Bugzilla::Install::Requirements;
+use Bugzilla::Install::Util qw(vers_cmp);
 
 my $cgi = Bugzilla->cgi;
 my $dbh = Bugzilla->dbh;
@@ -70,7 +70,7 @@
                 # If the query name contains invalid characters, don't import.
                 $name =~ /[<>&]/ && next;
                 trick_taint($name);
-                $dbh->bz_lock_tables('namedqueries WRITE');
+                $dbh->bz_start_transaction();
                 my $query = $dbh->selectrow_array(
                     "SELECT query FROM namedqueries " .
                      "WHERE userid = ? AND name = ?",
@@ -80,7 +80,7 @@
                             "(userid, name, query) VALUES " .
                             "(?, ?, ?)", undef, ($userid, $name, $value));
                 }
-                $dbh->bz_unlock_tables();
+                $dbh->bz_commit_transaction();
             }
             $cgi->remove_cookie($cookiename);
         }
@@ -129,6 +129,7 @@
                       "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",
@@ -188,6 +189,7 @@
 # don't have access to. Remove them from the list.
 my @selectable_products = sort {lc($a->name) cmp lc($b->name)} 
                                @{$user->get_selectable_products};
+Bugzilla::Product::preload(\@selectable_products);
 
 # Create the component, version and milestone lists.
 my %components;
@@ -259,10 +261,19 @@
 $vars->{'bug_severity'} = get_legal_field_values('bug_severity');
 
 # Boolean charts
-my @fields;
-push(@fields, { name => "noop", description => "---" });
-push(@fields, $dbh->bz_get_field_defs());
-@fields = sort {lc($a->{'description'}) cmp lc($b->{'description'})} @fields;
+my @fields = Bugzilla->get_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))
+    {
+        @fields = grep($_->name ne $tt_field, @fields);
+    }
+}
+
+@fields = sort {lc($a->description) cmp lc($b->description)} @fields;
+unshift(@fields, { name => "noop", description => "---" });
 $vars->{'fields'} = \@fields;
 
 # Creating new charts - if the cmd-add value is there, we define the field
diff --git a/BugsSite/quips.cgi b/BugsSite/quips.cgi
index bb0e5af..33b4e23 100755
--- a/BugsSite/quips.cgi
+++ b/BugsSite/quips.cgi
@@ -25,7 +25,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -88,6 +88,11 @@
 }
 
 if ($action eq 'approve') {
+    $user->in_group('admin')
+      || ThrowUserError("auth_failure", {group  => "admin",
+                                         action => "approve",
+                                         object => "quips"});
+ 
     # Read in the entire quip list
     my $quipsref = $dbh->selectall_arrayref("SELECT quipid, approved FROM quips");
     
@@ -100,11 +105,18 @@
     my @approved;
     my @unapproved;
     foreach my $quipid (keys %quips) {
-       my $form = $cgi->param('quipid_'.$quipid) ? 1 : 0;
-       if($quips{$quipid} ne $form) {
-           if($form) { push(@approved, $quipid); }
-           else { push(@unapproved, $quipid); }
-       }
+        # Must check for each quipid being defined for concurrency and
+        # automated usage where only one quipid might be defined.
+        my $quip = $cgi->param("quipid_$quipid") ? 1 : 0;
+        if(defined($cgi->param("defined_quipid_$quipid"))) {
+            if($quips{$quipid} != $quip) {
+                if($quip) { 
+                    push(@approved, $quipid); 
+                } else { 
+                    push(@unapproved, $quipid); 
+                }
+            }
+        }
     }
     $dbh->do("UPDATE quips SET approved = 1 WHERE quipid IN (" .
             join(",", @approved) . ")") if($#approved > -1);
diff --git a/BugsSite/relogin.cgi b/BugsSite/relogin.cgi
index 5aa1874..9d30d7c 100755
--- a/BugsSite/relogin.cgi
+++ b/BugsSite/relogin.cgi
@@ -23,7 +23,7 @@
 #                 A. Karl Kornel <karl@kornel.name>
 
 use strict;
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Mailer;
@@ -158,11 +158,11 @@
 
     # Go ahead and send out the message now
     my $message;
-    $template->process('email/sudo.txt.tmpl', 
-                       { reason => $reason },
-                       \$message);
+    my $mail_template = Bugzilla->template_inner($target_user->settings->{'lang'}->{'value'});
+    $mail_template->process('email/sudo.txt.tmpl', { reason => $reason }, \$message);
+    Bugzilla->template_inner("");
     MessageToMTA($message);
-        
+
     $vars->{'message'} = 'sudo_started';
     $vars->{'target'} = $target_user->login;
     $target = 'global/message.html.tmpl';
diff --git a/BugsSite/report.cgi b/BugsSite/report.cgi
index 10c5321..913f85d 100755
--- a/BugsSite/report.cgi
+++ b/BugsSite/report.cgi
@@ -22,7 +22,7 @@
 #                 <rdean@cambianetworks.com>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -316,6 +316,11 @@
     print Data::Dumper::Dumper(@image_data) . "\n\n</pre>";
 }
 
+# All formats point to the same section of the documentation.
+$vars->{'doc_section'} = 'reporting.html#reports';
+
+disable_utf8() if ($format->{'ctype'} =~ /^image\//);
+
 $template->process("$format->{'template'}", $vars)
   || ThrowTemplateError($template->error());
 
diff --git a/BugsSite/reports.cgi b/BugsSite/reports.cgi
index 2c9f8fa..6eb4496 100755
--- a/BugsSite/reports.cgi
+++ b/BugsSite/reports.cgi
@@ -37,12 +37,13 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
 use Bugzilla::Util;
 use Bugzilla::Error;
+use Bugzilla::Status;
 
 eval "use GD";
 $@ && ThrowCodeError("gd_not_installed");
@@ -75,7 +76,7 @@
       || ThrowCodeError('chart_dir_nonexistent',
                         {dir => $dir, graph_dir => $graph_dir});
 
-    my %default_sel = map { $_ => 1 } qw/UNCONFIRMED NEW ASSIGNED REOPENED/;
+    my %default_sel = map { $_ => 1 } BUG_STATE_OPEN;
 
     my @datasets;
     my @data = get_data($dir);
@@ -214,6 +215,7 @@
         if (/^#/) {
             if (/^# fields?: (.*)\s*$/) {
                 @fields = split /\||\r/, $1;
+                $data{$_} ||= [] foreach @fields;
                 unless ($fields[0] =~ /date/i) {
                     ThrowCodeError('chart_datafile_corrupt', {'file' => $data_file});
                 }
diff --git a/BugsSite/request.cgi b/BugsSite/request.cgi
index 8d51434..cad1f6f 100755
--- a/BugsSite/request.cgi
+++ b/BugsSite/request.cgi
@@ -28,7 +28,7 @@
 # Make it harder for us to do dangerous things in Perl.
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Util;
@@ -42,6 +42,11 @@
 # Make sure the user is logged in.
 my $user = Bugzilla->login();
 my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+my $template = Bugzilla->template;
+my $action = $cgi->param('action') || '';
+
+print $cgi->header();
 
 ################################################################################
 # Main Body Execution
@@ -59,7 +64,30 @@
 
 Bugzilla::User::match_field($cgi, $fields);
 
-queue();
+if ($action eq 'queue') {
+    queue();
+}
+else {
+    my $flagtypes = $dbh->selectcol_arrayref('SELECT DISTINCT(name) FROM flagtypes
+                                              ORDER BY name');
+    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 $comp (@{$prod->components}) {
+            $components{$comp->name} = 1;
+        }
+    }
+    $vars->{'components'} = [ sort { $a cmp $b } keys %components ];
+
+    $template->process('request/queue.html.tmpl', $vars)
+      || ThrowTemplateError($template->error());
+}
 exit;
 
 ################################################################################
@@ -186,8 +214,8 @@
         push(@criteria, "bugs.product_id = " . $product->id);
         push(@excluded_columns, 'product') unless $cgi->param('do_union');
         if (defined $cgi->param('component') && $cgi->param('component') ne "") {
-            my $component =
-                Bugzilla::Component::check_component($product, scalar $cgi->param('component'));
+            my $component = Bugzilla::Component->check({ product => $product,
+                                                         name => scalar $cgi->param('component') });
             push(@criteria, "bugs.component_id = " . $component->id);
             push(@excluded_columns, 'component') unless $cgi->param('do_union');
         }
@@ -288,8 +316,13 @@
     $vars->{'requests'} = \@requests;
     $vars->{'types'} = \@types;
 
-    # Return the appropriate HTTP response headers.
-    print $cgi->header();
+    my %components;
+    foreach my $prod (@{$vars->{'products'}}) {
+        foreach my $comp (@{$prod->components}) {
+            $components{$comp->name} = 1;
+        }
+    }
+    $vars->{'components'} = [ sort { $a cmp $b } keys %components ];
 
     # Generate and return the UI (HTML page) from the appropriate template.
     $template->process("request/queue.html.tmpl", $vars)
diff --git a/BugsSite/runtests.pl b/BugsSite/runtests.pl
index ad6898b..092d806 100755
--- a/BugsSite/runtests.pl
+++ b/BugsSite/runtests.pl
@@ -23,6 +23,7 @@
 # Make it harder for us to do dangerous things in Perl.
 use diagnostics;
 use strict;
+use lib qw(lib);
 
 use Test::Harness qw(&runtests $verbose);
 
diff --git a/BugsSite/sanitycheck.cgi b/BugsSite/sanitycheck.cgi
index 1722d2f..e326359 100755
--- a/BugsSite/sanitycheck.cgi
+++ b/BugsSite/sanitycheck.cgi
@@ -26,83 +26,86 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
+use Bugzilla::Bug;
 use Bugzilla::Constants;
 use Bugzilla::Util;
 use Bugzilla::Error;
-use Bugzilla::User;
+use Bugzilla::Status;
 
 ###########################################################################
 # General subs
 ###########################################################################
 
+sub get_string {
+    my ($san_tag, $vars) = @_;
+    $vars->{'san_tag'} = $san_tag;
+    return get_text('sanitycheck', $vars);
+}
+
 sub Status {
-    my ($str) = (@_);
-    print "$str <p>\n";
-}
+    my ($san_tag, $vars, $alert) = @_;
+    my $cgi = Bugzilla->cgi;
+    return if (!$alert && Bugzilla->usage_mode == USAGE_MODE_CMDLINE && !$cgi->param('verbose'));
 
-sub Alert {
-    my ($str) = (@_);
-    Status("<font color=\"red\">$str</font>");
-}
-
-sub BugLink {
-    my ($id) = (@_);
-    return "<a href=\"show_bug.cgi?id=$id\">$id</a>";
-}
-
-#
-# Parameter is a list of bug ids.
-#
-# Return is a string containing a list of all the bugids, as hrefs,
-# followed by a link to them all as a buglist
-sub BugListLinks {
-    my @bugs = @_;
-
-    # Historically, GetBugLink() wasn't used here. I'm guessing this
-    # was because it didn't exist or is too performance heavy, or just
-    # plain unnecessary
-    my @bug_links = map(BugLink($_), @bugs);
-
-    return join(', ',@bug_links) . " <a href=\"buglist.cgi?bug_id=" .
-        join(',',@bugs) . "\">(as buglist)</a>";
+    if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+        my $output = $cgi->param('output') || '';
+        my $linebreak = $alert ? "\nALERT: " : "\n";
+        $cgi->param('error_found', 1) if $alert;
+        $cgi->param('output', $output . $linebreak . get_string($san_tag, $vars));
+    }
+    else {
+        my $start_tag = $alert ? '<p class="alert">' : '<p>';
+        print $start_tag . get_string($san_tag, $vars) . "</p>\n";
+    }
 }
 
 ###########################################################################
 # Start
 ###########################################################################
 
-Bugzilla->login(LOGIN_REQUIRED);
+my $user = Bugzilla->login(LOGIN_REQUIRED);
 
 my $cgi = Bugzilla->cgi;
 my $dbh = Bugzilla->dbh;
-my $template = Bugzilla->template;
-my $user = Bugzilla->user;
+# If the result of the sanity check is sent per email, then we have to
+# take the user prefs into account rather than querying the web browser.
+my $template;
+if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+    $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+}
+else {
+    $template = Bugzilla->template;
+}
+my $vars = {};
+
+print $cgi->header() unless Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
 
 # Make sure the user is authorized to access sanitycheck.cgi.
 # As this script can now alter the group_control_map table, we no longer
 # let users with editbugs privs run it anymore.
 $user->in_group("editcomponents")
-  || ($user->in_group('editkeywords') && defined $cgi->param('rebuildkeywordcache'))
+  || ($user->in_group('editkeywords') && $cgi->param('rebuildkeywordcache'))
   || ThrowUserError("auth_failure", {group  => "editcomponents",
                                      action => "run",
                                      object => "sanity_check"});
 
-print $cgi->header();
-
-my @row;
-
-$template->put_header("Sanity Check");
+unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+    $template->process('admin/sanitycheck/list.html.tmpl', $vars)
+      || ThrowTemplateError($template->error());
+}
 
 ###########################################################################
 # Users with 'editkeywords' privs only can only check keywords.
 ###########################################################################
 unless ($user->in_group('editcomponents')) {
     check_votes_or_keywords('keywords');
-    Status("Sanity check completed.");
-    $template->put_footer();
+    Status('checks_completed');
+
+    $template->process('global/footer.html.tmpl', $vars)
+        || ThrowTemplateError($template->error());
     exit;
 }
 
@@ -110,9 +113,9 @@
 # Fix vote cache
 ###########################################################################
 
-if (defined $cgi->param('rebuildvotecache')) {
-    Status("OK, now rebuilding vote cache.");
-    $dbh->bz_lock_tables('bugs WRITE', 'votes READ');
+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 = ? 
@@ -123,17 +126,16 @@
     while (my ($id, $v) = $sth->fetchrow_array) {
         $sth_update->execute($v, $id);
     }
-    $dbh->bz_unlock_tables();
-    Status("Vote cache has been rebuilt.");
+    $dbh->bz_commit_transaction();
+    Status('vote_cache_rebuild_end');
 }
 
 ###########################################################################
 # Create missing group_control_map entries
 ###########################################################################
 
-if (defined $cgi->param('createmissinggroupcontrolmapentries')) {
-    Status(qq{OK, now creating <code>SHOWN</code> member control entries
-              for product/group combinations lacking one.});
+if ($cgi->param('createmissinggroupcontrolmapentries')) {
+    Status('group_control_map_entries_creation');
 
     my $na    = CONTROLMAPNA;
     my $shown = CONTROLMAPSHOWN;
@@ -180,29 +182,26 @@
 
         $counter++;
         if (defined($currentmembercontrol)) {
-            Status(qq{Updating <code>NA/<em>xxx</em></code> group control
-                      setting for group <em>$group_name</em> to
-                      <code>SHOWN/<em>xxx</em></code> in product
-                      <em>$product_name</em>.});
+            Status('group_control_map_entries_update',
+                   {group_name => $group_name, product_name => $product_name});
             $updatesth->execute($group_id, $product_id);
         }
         else {
-            Status(qq{Generating <code>SHOWN/NA</code> group control setting
-                      for group <em>$group_name</em> in product
-                      <em>$product_name</em>.});
+            Status('group_control_map_entries_generation',
+                   {group_name => $group_name, product_name => $product_name});
             $insertsth->execute($group_id, $product_id);
         }
     }
 
-    Status("Repaired $counter defective group control settings.");
+    Status('group_control_map_entries_repaired', {counter => $counter});
 }
 
 ###########################################################################
 # Fix missing creation date
 ###########################################################################
 
-if (defined $cgi->param('repair_creation_date')) {
-    Status("OK, now fixing missing bug creation dates");
+if ($cgi->param('repair_creation_date')) {
+    Status('bug_creation_date_start');
 
     my $bug_ids = $dbh->selectcol_arrayref('SELECT bug_id FROM bugs
                                             WHERE creation_ts IS NULL');
@@ -211,7 +210,7 @@
                                         WHERE bug_id = ?');
 
     # All bugs have an entry in the 'longdescs' table when they are created,
-    # even if 'commentoncreate' is turned off.
+    # even if no comment is required.
     my $sth_getDate = $dbh->prepare('SELECT MIN(bug_when) FROM longdescs
                                      WHERE bug_id = ?');
 
@@ -220,19 +219,39 @@
         my $date = $sth_getDate->fetchrow_array;
         $sth_UpdateDate->execute($date, $bugid);
     }
-    Status(scalar(@$bug_ids) . " bugs have been fixed.");
+    Status('bug_creation_date_fixed', {bug_count => scalar(@$bug_ids)});
+}
+
+###########################################################################
+# Fix entries in Bugs full_text
+###########################################################################
+
+if ($cgi->param('repair_bugs_fulltext')) {
+    Status('bugs_fulltext_start');
+
+    my $bug_ids = $dbh->selectcol_arrayref('SELECT bugs.bug_id
+                                            FROM bugs
+                                            LEFT JOIN bugs_fulltext
+                                            ON bugs_fulltext.bug_id = bugs.bug_id
+                                            WHERE bugs_fulltext.bug_id IS NULL');
+
+   foreach my $bugid (@$bug_ids) {
+       Bugzilla::Bug->new($bugid)->_sync_fulltext('new_bug');
+   }
+
+   Status('bugs_fulltext_fixed', {bug_count => scalar(@$bug_ids)});
 }
 
 ###########################################################################
 # Send unsent mail
 ###########################################################################
 
-if (defined $cgi->param('rescanallBugMail')) {
+if ($cgi->param('rescanallBugMail')) {
     require Bugzilla::BugMail;
 
-    Status("OK, now attempting to send unsent mail");
+    Status('send_bugmail_start');
     my $time = $dbh->sql_interval(30, 'MINUTE');
-    
+
     my $list = $dbh->selectcol_arrayref(qq{
                                         SELECT bug_id
                                           FROM bugs 
@@ -240,10 +259,9 @@
                                                 OR lastdiffed < delta_ts)
                                            AND delta_ts < now() - $time
                                       ORDER BY bug_id});
-         
-    Status(scalar(@$list) . ' bugs found with possibly unsent mail.');
 
-    my $vars = {};
+    Status('send_bugmail_status', {bug_count => scalar(@$list)});
+
     # We cannot simply look at the bugs_activity table to find who did the
     # last change in a given bug, as e.g. adding a comment doesn't add any
     # entry to this table. And some other changes may be private
@@ -257,11 +275,12 @@
         Bugzilla::BugMail::Send($bugid, $vars);
     }
 
-    if (scalar(@$list) > 0) {
-        Status("Unsent mail has been sent.");
-    }
+    Status('send_bugmail_end') if scalar(@$list);
 
-    $template->put_footer();
+    unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+        $template->process('global/footer.html.tmpl', $vars)
+          || ThrowTemplateError($template->error());
+    }
     exit;
 }
 
@@ -269,20 +288,22 @@
 # Remove all references to deleted bugs
 ###########################################################################
 
-if (defined $cgi->param('remove_invalid_bug_references')) {
-    Status("OK, now removing all references to deleted bugs.");
+if ($cgi->param('remove_invalid_bug_references')) {
+    Status('bug_reference_deletion_start');
 
-    $dbh->bz_lock_tables('attachments WRITE', 'bug_group_map WRITE',
-                         'bugs_activity WRITE', 'cc WRITE',
-                         'dependencies WRITE', 'duplicates WRITE',
-                         'flags WRITE', 'keywords WRITE',
-                         'longdescs WRITE', 'votes WRITE', 'bugs READ');
+    $dbh->bz_start_transaction();
 
-    foreach my $pair ('attachments/', 'bug_group_map/', 'bugs_activity/', 'cc/',
+    # 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/') {
-
+                      'flags/', 'keywords/', 'longdescs/', 'votes/',
+                      @addl_fields)
+    {
         my ($table, $field) = split('/', $pair);
         $field ||= "bug_id";
 
@@ -296,18 +317,18 @@
         }
     }
 
-    $dbh->bz_unlock_tables();
-    Status("All references to deleted bugs have been removed.");
+    $dbh->bz_commit_transaction();
+    Status('bug_reference_deletion_end');
 }
 
 ###########################################################################
 # Remove all references to deleted attachments
 ###########################################################################
 
-if (defined $cgi->param('remove_invalid_attach_references')) {
-    Status("OK, now removing all references to deleted attachments.");
+if ($cgi->param('remove_invalid_attach_references')) {
+    Status('attachment_reference_deletion_start');
 
-    $dbh->bz_lock_tables('attachments WRITE', 'attach_data WRITE');
+    $dbh->bz_start_transaction();
 
     my $attach_ids =
         $dbh->selectcol_arrayref('SELECT attach_data.id
@@ -321,11 +342,41 @@
                  join(',', @$attach_ids) . ')');
     }
 
-    $dbh->bz_unlock_tables();
-    Status("All references to deleted attachments have been removed.");
+    $dbh->bz_commit_transaction();
+    Status('attachment_reference_deletion_end');
 }
 
-print "OK, now running sanity checks.<p>\n";
+###########################################################################
+# Remove all references to deleted users or groups from whines
+###########################################################################
+
+if ($cgi->param('remove_old_whine_targets')) {
+    Status('whines_obsolete_target_deletion_start');
+
+    $dbh->bz_start_transaction();
+
+    foreach my $target (['groups', 'id', MAILTO_GROUP],
+                        ['profiles', 'userid', MAILTO_USER])
+    {
+        my ($table, $col, $type) = @$target;
+        my $old_ids =
+          $dbh->selectcol_arrayref("SELECT DISTINCT mailto
+                                      FROM whine_schedules
+                                 LEFT JOIN $table
+                                        ON $table.$col = whine_schedules.mailto
+                                     WHERE mailto_type = $type AND $table.$col IS NULL");
+
+        if (scalar(@$old_ids)) {
+            $dbh->do("DELETE FROM whine_schedules
+                       WHERE mailto_type = $type AND mailto IN (" .
+                       join(',', @$old_ids) . ")");
+        }
+    }
+    $dbh->bz_commit_transaction();
+    Status('whines_obsolete_target_deletion_end');
+}
+
+Status('checks_start');
 
 ###########################################################################
 # Perform referential (cross) checks
@@ -354,7 +405,7 @@
     my $field = shift @_;
     my $dbh = Bugzilla->dbh;
 
-    Status("Checking references to $table.$field");
+    Status('cross_check_to', {table => $table, field => $field});
 
     while (@_) {
         my $ref = shift @_;
@@ -363,8 +414,8 @@
         $exceptions ||= [];
         my %exceptions = map { $_ => 1 } @$exceptions;
 
-        Status("... from $refertable.$referfield");
-       
+        Status('cross_check_from', {table => $refertable, field => $referfield});
+
         my $query = qq{SELECT DISTINCT $refertable.$referfield} .
             ($keyname ? qq{, $refertable.$keyname } : q{}) .
                      qq{ FROM $refertable
@@ -372,7 +423,7 @@
                            ON $refertable.$referfield = $table.$field
                         WHERE $table.$field IS NULL
                           AND $refertable.$referfield IS NOT NULL};
-         
+
         my $sth = $dbh->prepare($query);
         $sth->execute;
 
@@ -380,26 +431,18 @@
 
         while (my ($value, $key) = $sth->fetchrow_array) {
             next if $exceptions{$value};
-            my $alert = "Bad value &quot;$value&quot; found in $refertable.$referfield";
-            if ($keyname) {
-                if ($keyname eq 'bug_id') {
-                    $alert .= ' (bug ' . BugLink($key) . ')';
-                } else {
-                    $alert .= " ($keyname == '$key')";
-                }
-            }
-            Alert($alert);
+            Status('cross_check_alert', {value => $value, table => $refertable,
+                                         field => $referfield, keyname => $keyname,
+                                         key => $key}, 'alert');
             $has_bad_references = 1;
         }
         # References to non existent bugs can be safely removed, bug 288461
         if ($table eq 'bugs' && $has_bad_references) {
-            print qq{<a href="sanitycheck.cgi?remove_invalid_bug_references=1">
-                     Remove invalid references to non existent bugs.</a><p>\n};
+            Status('cross_check_bug_has_references');
         }
         # References to non existent attachments can be safely removed.
         if ($table eq 'attachments' && $has_bad_references) {
-            print qq{<a href="sanitycheck.cgi?remove_invalid_attach_references=1">
-                     Remove invalid references to non existent attachments.</a><p>\n};
+            Status('cross_check_attachment_has_references');
         }
     }
 }
@@ -415,11 +458,18 @@
            ['profiles_activity', 'fieldid']);
 
 CrossCheck("flagtypes", "id",
-           ["flags", "type_id"]);
+           ["flags", "type_id"],
+           ["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"],
+           ["bugs_fulltext", "bug_id"],
            ["attachments", "bug_id"],
            ["cc", "bug_id"],
            ["longdescs", "bug_id"],
@@ -429,7 +479,8 @@
            ["votes", "bug_id"],
            ["keywords", "bug_id"],
            ["duplicates", "dupe_of", "dupe"],
-           ["duplicates", "dupe", "dupe_of"]);
+           ["duplicates", "dupe", "dupe_of"],
+           @addl_fields);
 
 CrossCheck("groups", "id",
            ["bug_group_map", "group_id"],
@@ -438,7 +489,9 @@
            ["group_group_map", "member_id"],
            ["group_control_map", "group_id"],
            ["namedquery_group_map", "group_id"],
-           ["user_group_map", "group_id"]);
+           ["user_group_map", "group_id"],
+           ["flagtypes", "grant_group_id"],
+           ["flagtypes", "request_group_id"]);
 
 CrossCheck("namedqueries", "id",
            ["namedqueries_link_in_footer", "namedquery_id"],
@@ -484,7 +537,9 @@
            ["flagexclusions", "product_id", "type_id"]);
 
 CrossCheck("components", "id",
-           ["component_cc", "component_id"]);
+           ["component_cc", "component_id"],
+           ["flagexclusions", "component_id", "type_id"],
+           ["flaginclusions", "component_id", "type_id"]);
 
 # Check the former enum types -mkanat@bugzilla.org
 CrossCheck("bug_status", "value",
@@ -509,14 +564,21 @@
            ['series_data', 'series_id']);
 
 CrossCheck('series_categories', 'id',
-           ['series', 'category']);
+           ['series', 'category'],
+           ["category_group_map", "category_id"],
+           ["series", "subcategory"]);
 
 CrossCheck('whine_events', 'id',
            ['whine_queries', 'eventid'],
            ['whine_schedules', 'eventid']);
 
 CrossCheck('attachments', 'attach_id',
-           ['attach_data', 'id']);
+           ['attach_data', 'id'],
+           ['bugs_activity', 'attach_id']);
+
+CrossCheck('bug_status', 'id',
+           ['status_workflow', 'old_status'],
+           ['status_workflow', 'new_status']);
 
 ###########################################################################
 # Perform double field referential (cross) checks
@@ -541,14 +603,16 @@
     my $field1 = shift @_;
     my $field2 = shift @_;
     my $dbh = Bugzilla->dbh;
- 
-    Status("Checking references to $table.$field1 / $table.$field2");
- 
+
+    Status('double_cross_check_to',
+           {table => $table, field1 => $field1, field2 => $field2});
+
     while (@_) {
         my $ref = shift @_;
         my ($refertable, $referfield1, $referfield2, $keyname) = @$ref;
- 
-        Status("... from $refertable.$referfield1 / $refertable.$referfield2");
+
+        Status('double_cross_check_from',
+               {table => $refertable, field1 => $referfield1, field2 =>$referfield2});
 
         my $d_cross_check = $dbh->selectall_arrayref(qq{
                         SELECT DISTINCT $refertable.$referfield1, 
@@ -565,17 +629,11 @@
 
         foreach my $check (@$d_cross_check) {
             my ($value1, $value2, $key) = @$check;
-            my $alert = "Bad values &quot;$value1&quot;, &quot;$value2&quot; found in " .
-                "$refertable.$referfield1 / $refertable.$referfield2";
-            if ($keyname) {
-                if ($keyname eq 'bug_id') {
-                   $alert .= ' (bug ' . BugLink($key) . ')';
-                }
-                else {
-                    $alert .= " ($keyname == '$key')";
-                }
-            }
-            Alert($alert);
+            Status('double_cross_check_alert',
+                   {value1 => $value1, value2 => $value2,
+                    table => $refertable,
+                    field1 => $referfield1, field2 => $referfield2,
+                    keyname => $keyname, key => $key}, 'alert');
         }
     }
 }
@@ -599,26 +657,21 @@
 ###########################################################################
 # Perform login checks
 ###########################################################################
- 
-Status("Checking profile logins");
+
+Status('profile_login_start');
 
 my $sth = $dbh->prepare(q{SELECT userid, login_name FROM profiles});
 $sth->execute;
 
 while (my ($id, $email) = $sth->fetchrow_array) {
     validate_email_syntax($email)
-      || Alert "Bad profile email address, id=$id,  &lt;$email&gt;.";
+      || Status('profile_login_alert', {id => $id, email => $email}, 'alert');
 }
 
 ###########################################################################
 # Perform vote/keyword cache checks
 ###########################################################################
 
-sub AlertBadVoteCache {
-    my ($id) = (@_);
-    Alert("Bad vote cache for bug " . BugLink($id));
-}
-
 check_votes_or_keywords();
 
 sub check_votes_or_keywords {
@@ -651,7 +704,7 @@
 sub _check_votes {
     my $votes = shift;
 
-    Status("Checking cached vote counts");
+    Status('vote_count_start');
     my $dbh = Bugzilla->dbh;
     my $sth = $dbh->prepare(q{SELECT bug_id, SUM(vote_count)
                                 FROM votes }.
@@ -662,29 +715,27 @@
 
     while (my ($id, $v) = $sth->fetchrow_array) {
         if ($v <= 0) {
-            Alert("Bad vote sum for bug $id");
+            Status('vote_count_alert', {id => $id}, 'alert');
         } else {
             if (!defined $votes->{$id} || $votes->{$id} != $v) {
-                AlertBadVoteCache($id);
+                Status('vote_cache_alert', {id => $id}, 'alert');
                 $offer_votecache_rebuild = 1;
             }
             delete $votes->{$id};
         }
     }
     foreach my $id (keys %$votes) {
-        AlertBadVoteCache($id);
+        Status('vote_cache_alert', {id => $id}, 'alert');
         $offer_votecache_rebuild = 1;
     }
 
-    if ($offer_votecache_rebuild) {
-        print qq{<a href="sanitycheck.cgi?rebuildvotecache=1">Click here to rebuild the vote cache</a><p>\n};
-    }
+    Status('vote_cache_rebuild_fix') if $offer_votecache_rebuild;
 }
 
 sub _check_keywords {
     my $keyword = shift;
 
-    Status("Checking keywords table");
+    Status('keyword_check_start');
     my $dbh = Bugzilla->dbh;
     my $cgi = Bugzilla->cgi;
 
@@ -695,11 +746,11 @@
     foreach (@$keywords) {
         my ($id, $name) = @$_;
         if ($keywordids{$id}) {
-            Alert("Duplicate entry in keyworddefs for id $id");
+            Status('keyword_check_alert', {id => $id}, 'alert');
         }
         $keywordids{$id} = 1;
         if ($name =~ /[\s,]/) {
-            Alert("Bogus name in keyworddefs for id $id");
+            Status('keyword_check_invalid_name', {id => $id}, 'alert');
         }
     }
 
@@ -711,19 +762,19 @@
     my $lastk;
     while (my ($id, $k) = $sth->fetchrow_array) {
         if (!$keywordids{$k}) {
-            Alert("Bogus keywordids $k found in keywords table");
+            Status('keyword_check_invalid_id', {id => $k}, 'alert');
         }
         if (defined $lastid && $id eq $lastid && $k eq $lastk) {
-            Alert("Duplicate keyword ids found in bug " . BugLink($id));
+            Status('keyword_check_duplicated_ids', {id => $id}, 'alert');
         }
         $lastid = $id;
         $lastk = $k;
     }
 
-    Status("Checking cached keywords");
+    Status('keyword_cache_start');
 
-    if (defined $cgi->param('rebuildkeywordcache')) {
-        $dbh->bz_lock_tables('bugs write', 'keywords read', 'keyworddefs read');
+    if ($cgi->param('rebuildkeywordcache')) {
+        $dbh->bz_start_transaction();
     }
 
     my $query = q{SELECT keywords.bug_id, keyworddefs.name
@@ -768,15 +819,13 @@
     }
     if (@badbugs) {
         @badbugs = sort {$a <=> $b} @badbugs;
-        Alert(scalar(@badbugs) . " bug(s) found with incorrect keyword cache: " .
-              BugListLinks(@badbugs));
 
-        my $sth_update = $dbh->prepare(q{UPDATE bugs
-                                            SET keywords = ?
-                                          WHERE bug_id = ?});
+        if ($cgi->param('rebuildkeywordcache')) {
+            my $sth_update = $dbh->prepare(q{UPDATE bugs
+                                                SET keywords = ?
+                                              WHERE bug_id = ?});
 
-        if (defined $cgi->param('rebuildkeywordcache')) {
-            Status("OK, now fixing keyword cache.");
+            Status('keyword_cache_fixing');
             foreach my $b (@badbugs) {
                 my $k = '';
                 if (exists($realk{$b})) {
@@ -784,14 +833,15 @@
                 }
                 $sth_update->execute($k, $b);
             }
-            Status("Keyword cache fixed.");
+            Status('keyword_cache_fixed');
         } else {
-            print qq{<a href="sanitycheck.cgi?rebuildkeywordcache=1">Click here to rebuild the keyword cache</a><p>\n};
+            Status('keyword_cache_alert', {badbugs => \@badbugs}, 'alert');
+            Status('keyword_cache_rebuild');
         }
     }
 
-    if (defined $cgi->param('rebuildkeywordcache')) {
-        $dbh->bz_unlock_tables();
+    if ($cgi->param('rebuildkeywordcache')) {
+        $dbh->bz_commit_transaction();
     }
 }
 
@@ -799,7 +849,7 @@
 # Check for flags being in incorrect products and components
 ###########################################################################
 
-Status('Checking for flags being in the wrong product/component');
+Status('flag_check_start');
 
 my $invalid_flags = $dbh->selectall_arrayref(
        'SELECT DISTINCT flags.id, flags.bug_id, flags.attach_id
@@ -828,22 +878,20 @@
 
 if (scalar(@invalid_flags)) {
     if ($cgi->param('remove_invalid_flags')) {
-        Status("OK, now deleting invalid flags.");
+        Status('flag_deletion_start');
         my @flag_ids = map {$_->[0]} @invalid_flags;
-        $dbh->bz_lock_tables('flags WRITE');
         # Silently delete these flags, with no notification to requesters/setters.
         $dbh->do('DELETE FROM flags WHERE id IN (' . join(',', @flag_ids) .')');
-        $dbh->bz_unlock_tables();
-        Status("Invalid flags deleted.");
+        Status('flag_deletion_end');
     }
     else {
         foreach my $flag (@$invalid_flags) {
             my ($flag_id, $bug_id, $attach_id) = @$flag;
-            Alert("Invalid flag $flag_id for " .
-                  ($attach_id ? "attachment $attach_id in bug " : "bug ") . BugLink($bug_id));
+            Status('flag_alert',
+                   {flag_id => $flag_id, attach_id => $attach_id, bug_id => $bug_id},
+                   'alert');
         }
-        print qq{<a href="sanitycheck.cgi?remove_invalid_flags=1">Click
-                 here to delete invalid flags</a><p>\n};
+        Status('flag_fix');
     }
 }
 
@@ -860,57 +908,64 @@
                                             ORDER BY bugs.bug_id});
 
     if (scalar(@$badbugs)) {
-        Alert("$errortext: " . BugListLinks(@$badbugs));
+        Status('bug_check_alert',
+               {errortext => get_string($errortext), badbugs => $badbugs},
+               'alert');
+
         if ($repairparam) {
-            $repairtext ||= 'Repair these bugs';
-            print qq{<a href="sanitycheck.cgi?$repairparam=1">$repairtext</a>.},
-                  '<p>';
+            $repairtext ||= 'repair_bugs';
+            Status('bug_check_repair',
+                   {param => $repairparam, text => get_string($repairtext)});
         }
     }
 }
 
-Status("Checking for bugs with no creation date (which makes them invisible)");
+Status('bug_check_creation_date');
 
-BugCheck("bugs WHERE creation_ts IS NULL", "Bugs with no creation date",
-         "repair_creation_date", "Repair missing creation date for these bugs");
+BugCheck("bugs WHERE creation_ts IS NULL", 'bug_check_creation_date_error_text',
+         'repair_creation_date', 'bug_check_creation_date_repair_text');
 
-Status("Checking resolution/duplicates");
+Status('bug_check_bugs_fulltext');
+
+BugCheck("bugs LEFT JOIN bugs_fulltext ON bugs_fulltext.bug_id = bugs.bug_id " .
+         "WHERE bugs_fulltext.bug_id IS NULL", 'bug_check_bugs_fulltext_error_text',
+         'repair_bugs_fulltext', 'bug_check_bugs_fulltext_repair_text');
+
+Status('bug_check_res_dupl');
 
 BugCheck("bugs INNER JOIN duplicates ON bugs.bug_id = duplicates.dupe " .
-         "WHERE bugs.resolution != 'DUPLICATE'",
-         "Bug(s) found on duplicates table that are not marked duplicate");
+         "WHERE bugs.resolution != 'DUPLICATE'", 'bug_check_res_dupl_error_text');
 
 BugCheck("bugs LEFT JOIN duplicates ON bugs.bug_id = duplicates.dupe WHERE " .
          "bugs.resolution = 'DUPLICATE' AND " .
-         "duplicates.dupe IS NULL",
-         "Bug(s) found marked resolved duplicate and not on duplicates table");
+         "duplicates.dupe IS NULL", 'bug_check_res_dupl_error_text2');
 
-Status("Checking statuses/resolutions");
+Status('bug_check_status_res');
 
 my @open_states = map($dbh->quote($_), BUG_STATE_OPEN);
 my $open_states = join(', ', @open_states);
 
 BugCheck("bugs WHERE bug_status IN ($open_states) AND resolution != ''",
-         "Bugs with open status and a resolution");
+         'bug_check_status_res_error_text');
 BugCheck("bugs WHERE bug_status NOT IN ($open_states) AND resolution = ''",
-         "Bugs with non-open status and no resolution");
+         'bug_check_status_res_error_text2');
 
-Status("Checking statuses/everconfirmed");
+Status('bug_check_status_everconfirmed');
 
 BugCheck("bugs WHERE bug_status = 'UNCONFIRMED' AND everconfirmed = 1",
-         "Bugs that are UNCONFIRMED but have everconfirmed set");
-# The below list of resolutions is hard-coded because we don't know if future
-# resolutions will be confirmed, unconfirmed or maybeconfirmed.  I suspect
-# they will be maybeconfirmed, e.g. ASLEEP and REMIND.  This hardcoding should
-# disappear when we have customized statuses.
-BugCheck("bugs WHERE bug_status IN ('NEW', 'ASSIGNED', 'REOPENED') AND everconfirmed = 0",
-         "Bugs with confirmed status but don't have everconfirmed set"); 
+         'bug_check_status_everconfirmed_error_text');
 
-Status("Checking votes/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",
-         "Bugs that have enough votes to be confirmed but haven't been");
+         'bug_check_votes_everconfirmed_error_text');
 
 ###########################################################################
 # Control Values
@@ -918,7 +973,7 @@
 
 # Checks for values that are invalid OR
 # not among the 9 valid combinations
-Status("Checking for bad values in group_control_map");
+Status('bug_check_control_values');
 my $groups = join(", ", (CONTROLMAPNA, CONTROLMAPSHOWN, CONTROLMAPDEFAULT,
 CONTROLMAPMANDATORY));
 my $query = qq{
@@ -930,13 +985,11 @@
              AND (membercontrol != } . CONTROLMAPSHOWN . q{)
              AND ((membercontrol != } . CONTROLMAPDEFAULT . q{)
                   OR (othercontrol = } . CONTROLMAPSHOWN . q{)))};
-                  
-my $c = $dbh->selectrow_array($query);
-if ($c) {
-    Alert("Found $c bad group_control_map entries");
-}
 
-Status("Checking for bugs with groups violating their product's group controls");
+my $entries = $dbh->selectrow_array($query);
+Status('bug_check_control_values_alert', {entries => $entries}, 'alert') if $entries;
+
+Status('bug_check_control_values_violation');
 BugCheck("bugs
          INNER JOIN bug_group_map
             ON bugs.bug_id = bug_group_map.bug_id
@@ -945,10 +998,9 @@
            AND bug_group_map.group_id = group_control_map.group_id
          WHERE ((group_control_map.membercontrol = " . CONTROLMAPNA . ")
          OR (group_control_map.membercontrol IS NULL))",
-         'Have groups not permitted for their products',
+         'bug_check_control_values_error_text',
          'createmissinggroupcontrolmapentries',
-         'Permit the missing groups for the affected products
-          (set member control to <code>SHOWN</code>)');
+         'bug_check_control_values_repair_text');
 
 BugCheck("bugs
          INNER JOIN group_control_map
@@ -961,14 +1013,13 @@
          WHERE group_control_map.membercontrol = " . CONTROLMAPMANDATORY . "
            AND bug_group_map.group_id IS NULL
            AND groups.isactive != 0",
-         "Are missing groups required for their products");
-
+         'bug_check_control_values_error_text2');
 
 ###########################################################################
 # Unsent mail
 ###########################################################################
 
-Status("Checking for unsent mail");
+Status('unsent_bugmail_check');
 
 my $time = $dbh->sql_interval(30, 'MINUTE');
 my $badbugs = $dbh->selectcol_arrayref(qq{
@@ -980,15 +1031,41 @@
 
 
 if (scalar(@$badbugs > 0)) {
-    Alert("Bugs that have changes but no mail sent for at least half an hour: " .
-          BugListLinks(@$badbugs));
-
-    print qq{<a href="sanitycheck.cgi?rescanallBugMail=1">Send these mails</a>.<p>\n};
+    Status('unsent_bugmail_alert', {badbugs => $badbugs}, 'alert');
+    Status('unsent_bugmail_fix');
 }
 
 ###########################################################################
+# Whines
+###########################################################################
+
+Status('whines_obsolete_target_start');
+
+my $display_repair_whines_link = 0;
+foreach my $target (['groups', 'id', MAILTO_GROUP],
+                    ['profiles', 'userid', MAILTO_USER])
+{
+    my ($table, $col, $type) = @$target;
+    my $old = $dbh->selectall_arrayref("SELECT whine_schedules.id, mailto
+                                          FROM whine_schedules
+                                     LEFT JOIN $table
+                                            ON $table.$col = whine_schedules.mailto
+                                         WHERE mailto_type = $type AND $table.$col IS NULL");
+
+    if (scalar(@$old)) {
+        Status('whines_obsolete_target_alert', {schedules => $old, type => $type}, 'alert');
+        $display_repair_whines_link = 1;
+    }
+}
+Status('whines_obsolete_target_fix') if $display_repair_whines_link;
+
+###########################################################################
 # End
 ###########################################################################
 
-Status("Sanity check completed.");
-$template->put_footer();
+Status('checks_completed');
+
+unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+    $template->process('global/footer.html.tmpl', $vars)
+      || ThrowTemplateError($template->error());
+}
diff --git a/BugsSite/sanitycheck.pl b/BugsSite/sanitycheck.pl
new file mode 100755
index 0000000..2ef0eea
--- /dev/null
+++ b/BugsSite/sanitycheck.pl
@@ -0,0 +1,116 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by Frédéric Buclin are Copyright (C) 2007
+# Frédéric Buclin. All Rights Reserved.
+#
+# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::User;
+use Bugzilla::Mailer;
+
+use Getopt::Long;
+use Pod::Usage;
+
+my $verbose = 0; # Return all comments if true, else errors only.
+my $login = '';  # Login name of the user which is used to call sanitycheck.cgi.
+my $help = 0;    # Has user asked for help on this script?
+
+my $result = GetOptions('verbose'  => \$verbose,
+                        'login=s'  => \$login,
+                        'help|h|?' => \$help);
+
+pod2usage({-verbose => 1, -exitval => 1}) if $help;
+
+Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
+
+# Be sure a login name if given.
+$login || ThrowUserError('invalid_username');
+
+my $user = new Bugzilla::User({ name => $login })
+  || ThrowUserError('invalid_username', { name => $login });
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+
+# Authenticate using this user account.
+Bugzilla->set_user($user);
+
+# Pass this param to sanitycheck.cgi.
+$cgi->param('verbose', $verbose);
+
+require 'sanitycheck.cgi';
+
+# Now it's time to send an email to the user if there is something to notify.
+if ($cgi->param('output')) {
+    my $message;
+    my $vars = {};
+    $vars->{'addressee'} = $user->email;
+    $vars->{'output'} = $cgi->param('output');
+    $vars->{'error_found'} = $cgi->param('error_found') ? 1 : 0;
+
+    $template->process('email/sanitycheck.txt.tmpl', $vars, \$message)
+      || ThrowTemplateError($template->error());
+
+    MessageToMTA($message);
+}
+
+
+__END__
+
+=head1 NAME
+
+sanitycheck.pl - Perl script to perform a sanity check at the command line
+
+=head1 SYNOPSIS
+
+ ./sanitycheck.pl [--help]
+ ./sanitycheck.pl [--verbose] --login <user@domain.com>
+
+=head1 OPTIONS
+
+=over
+
+=item B<--help>
+
+Displays this help text
+
+=item B<--verbose>
+
+Causes this script to be more verbose in its output. Without this option,
+the script will return only errors. With the option, the script will append
+all output to the email.
+
+=item B<--login>
+
+This should be passed the email address of a user that is capable of
+running the Sanity Check process, a user with the editcomponents priv. This
+user will receive an email with the results of the script run.
+
+=back
+
+=head1 DESCRIPTION
+
+This script provides a way of running a 'Sanity Check' on the database
+via either a CLI or cron. It is equivalent to calling sanitycheck.cgi
+via a web broswer.
diff --git a/BugsSite/search_plugin.cgi b/BugsSite/search_plugin.cgi
index e3384fc..5048f7c 100755
--- a/BugsSite/search_plugin.cgi
+++ b/BugsSite/search_plugin.cgi
@@ -16,7 +16,7 @@
 # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Error;
diff --git a/BugsSite/show_activity.cgi b/BugsSite/show_activity.cgi
index 1eff56f..d2570f8 100755
--- a/BugsSite/show_activity.cgi
+++ b/BugsSite/show_activity.cgi
@@ -24,7 +24,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Error;
@@ -53,7 +53,7 @@
 ($vars->{'operations'}, $vars->{'incomplete_data'}) = 
     Bugzilla::Bug::GetBugActivity($bug_id);
 
-$vars->{'bug_id'} = $bug_id;
+$vars->{'bug'} = new Bugzilla::Bug($bug_id);
 
 print $cgi->header();
 
diff --git a/BugsSite/show_bug.cgi b/BugsSite/show_bug.cgi
index bc6faa8..2655e5f 100755
--- a/BugsSite/show_bug.cgi
+++ b/BugsSite/show_bug.cgi
@@ -22,7 +22,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -99,7 +99,7 @@
 $vars->{'marks'} = \%marks;
 $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
 
-my @bugids = map {$_->bug_id} @bugs;
+my @bugids = map {$_->bug_id} grep {!$_->error} @bugs;
 $vars->{'bugids'} = join(", ", @bugids);
 
 # Next bug in list (if there is one)
@@ -115,7 +115,7 @@
 # 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', 
-                 'attachment', 'attachmentdata');
+                 'attachment', 'attachmentdata', 'token');
 my %displayfields;
 
 if ($cgi->param("field")) {
diff --git a/BugsSite/showattachment.cgi b/BugsSite/showattachment.cgi
index f535d5c..e90a015 100755
--- a/BugsSite/showattachment.cgi
+++ b/BugsSite/showattachment.cgi
@@ -23,7 +23,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Util;
diff --git a/BugsSite/showdependencygraph.cgi b/BugsSite/showdependencygraph.cgi
index 0d74304..f9eb4ed 100755
--- a/BugsSite/showdependencygraph.cgi
+++ b/BugsSite/showdependencygraph.cgi
@@ -23,7 +23,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use File::Temp;
 
@@ -32,6 +32,7 @@
 use Bugzilla::Util;
 use Bugzilla::Error;
 use Bugzilla::Bug;
+use Bugzilla::Status;
 
 Bugzilla->login();
 
@@ -71,7 +72,7 @@
             # Pick up bugid from the mapdata label field. Getting the title from
             # bugtitle hash instead of mapdata allows us to get the summary even
             # when showsummary is off, and also gives us status and resolution.
-            my $bugtitle = value_quote($bugtitles{$bugid});
+            my $bugtitle = html_quote(clean_text($bugtitles{$bugid}));
             $map .= qq{<area alt="bug $bugid" name="bug$bugid" shape="rect" } .
                     qq{title="$bugtitle" href="$url" } .
                     qq{coords="$leftx,$topy,$rightx,$bottomy">\n};
@@ -95,18 +96,19 @@
 }
 
 # The list of valid directions. Some are not proposed in the dropdrown
-# menu despite they are valid ones.
+# menu despite the fact that they are valid.
 my @valid_rankdirs = ('LR', 'RL', 'TB', 'BT');
 
-my $rankdir = $cgi->param('rankdir') || "LR";
+my $rankdir = $cgi->param('rankdir') || 'TB';
 # Make sure the submitted 'rankdir' value is valid.
 if (lsearch(\@valid_rankdirs, $rankdir) < 0) {
-    $rankdir = 'LR';
+    $rankdir = 'TB';
 }
 
+my $display = $cgi->param('display') || 'tree';
 my $webdotdir = bz_locations()->{'webdotdir'};
 
-if (!defined $cgi->param('id') && !defined $cgi->param('doall')) {
+if (!defined $cgi->param('id') && $display ne 'doall') {
     ThrowCodeError("missing_bug_id");
 }
 
@@ -123,7 +125,7 @@
 
 my %baselist;
 
-if ($cgi->param('doall')) {
+if ($display eq 'doall') {
     my $dependencies = $dbh->selectall_arrayref(
                            "SELECT blocked, dependson FROM dependencies");
 
@@ -133,29 +135,48 @@
     }
 } else {
     foreach my $i (split('[\s,]+', $cgi->param('id'))) {
-        $i = trim($i);
         ValidateBugID($i);
         $baselist{$i} = 1;
     }
 
     my @stack = keys(%baselist);
-    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;
-            }
+    if ($display eq 'web') {
+        my $sth = $dbh->prepare(q{SELECT blocked, dependson
+                                    FROM dependencies
+                                   WHERE blocked = ? OR 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);
+            }
         }
     }
 
@@ -224,8 +245,10 @@
 my $webdotbase = Bugzilla->params->{'webdotbase'};
 
 if ($webdotbase =~ /^https?:/) {
-     # Remote dot server
-     my $url = perform_substs($webdotbase) . $filename;
+     # Remote dot server. We don't hardcode 'urlbase' here in case
+     # 'sslbase' is in use.
+     $webdotbase =~ s/%([a-z]*)%/Bugzilla->params->{$1}/eg;
+     my $url = $webdotbase . $filename;
      $vars->{'image_url'} = $url . ".gif";
      $vars->{'map_url'} = $url . ".map";
 } else {
@@ -292,7 +315,7 @@
 my @bugs = grep(detaint_natural($_), split(/[\s,]+/, $cgi->param('id')));
 $vars->{'bug_id'} = join(', ', @bugs);
 $vars->{'multiple_bugs'} = ($cgi->param('id') =~ /[ ,]/);
-$vars->{'doall'} = $cgi->param('doall');
+$vars->{'display'} = $display;
 $vars->{'rankdir'} = $rankdir;
 $vars->{'showsummary'} = $cgi->param('showsummary');
 
diff --git a/BugsSite/showdependencytree.cgi b/BugsSite/showdependencytree.cgi
index 1010adc..80e6771 100755
--- a/BugsSite/showdependencytree.cgi
+++ b/BugsSite/showdependencytree.cgi
@@ -26,7 +26,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Error;
@@ -49,7 +49,7 @@
 
 # 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('invalid_bug_id_or_alias');
+my $id = $cgi->param('id') || ThrowUserError('improper_bug_id_field_value');
 ValidateBugID($id);
 my $current_bug = new Bugzilla::Bug($id);
 
@@ -131,7 +131,7 @@
         if (!$bugs->{$dep_id}->{'error'}
             && Bugzilla->user->can_see_bug($dep_id)
             && (!$maxdepth || $depth <= $maxdepth) 
-            && ($bugs->{$dep_id}->{'isopened'} || !$hide_resolved))
+            && ($bugs->{$dep_id}->isopened || !$hide_resolved))
         {
             # Due to AUTOLOAD in Bug.pm, we cannot add 'dependencies'
             # as a bug object attribute from here.
diff --git a/BugsSite/sidebar.cgi b/BugsSite/sidebar.cgi
index 5619d6b..35c4e64 100755
--- a/BugsSite/sidebar.cgi
+++ b/BugsSite/sidebar.cgi
@@ -17,7 +17,7 @@
 
 use strict;
 
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Error;
diff --git a/BugsSite/skins/.cvsignore b/BugsSite/skins/.cvsignore
index b98d8c9..0d82d79 100644
--- a/BugsSite/skins/.cvsignore
+++ b/BugsSite/skins/.cvsignore
@@ -1,2 +1 @@
 custom
-contrib
diff --git a/BugsSite/skins/contrib/Dusk/.cvsignore b/BugsSite/skins/contrib/Dusk/.cvsignore
new file mode 100644
index 0000000..8e2561a
--- /dev/null
+++ b/BugsSite/skins/contrib/Dusk/.cvsignore
@@ -0,0 +1,17 @@
+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/BugsSite/skins/contrib/Dusk/buglist.css b/BugsSite/skins/contrib/Dusk/buglist.css
new file mode 100644
index 0000000..2e14368
--- /dev/null
+++ b/BugsSite/skins/contrib/Dusk/buglist.css
@@ -0,0 +1,24 @@
+/* The contents of this file are subject to the Mozilla Public
+  * License Version 1.1 (the "License"); you may not use this file
+  * except in compliance with the License. You may obtain a copy of
+  * the License at http://www.mozilla.org/MPL/
+  *
+  * Software distributed under the License is distributed on an "AS
+  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  * implied. See the License for the specific language governing
+  * rights and limitations under the License.
+  *
+  * The Original Code is the Bugzilla Bug Tracking System.
+  *
+  * The Initial Developer of the Original Code is Mike Schrag.
+  * Portions created by Marc Schumann are Copyright (c) 2007 Mike Schrag.
+  * All rights reserved.
+  *
+  * Contributor(s): Mike Schrag <mschrag@pobox.com>
+  *                 Byron Jones <bugzilla@glob.com.au>
+  *                 Marc Schumann <wurblzap@gmail.com>
+  */
+
+tr.bz_bugitem:hover {
+    background-color: #ccccff;
+}
diff --git a/BugsSite/skins/contrib/Dusk/global.css b/BugsSite/skins/contrib/Dusk/global.css
new file mode 100644
index 0000000..d6e8159
--- /dev/null
+++ b/BugsSite/skins/contrib/Dusk/global.css
@@ -0,0 +1,279 @@
+/* The contents of this file are subject to the Mozilla Public
+  * License Version 1.1 (the "License"); you may not use this file
+  * except in compliance with the License. You may obtain a copy of
+  * the License at http://www.mozilla.org/MPL/
+  *
+  * Software distributed under the License is distributed on an "AS
+  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  * implied. See the License for the specific language governing
+  * rights and limitations under the License.
+  *
+  * The Original Code is the Bugzilla Bug Tracking System.
+  *
+  * The Initial Developer of the Original Code is Mike Schrag.
+  * Portions created by Marc Schumann are Copyright (c) 2007 Mike Schrag.
+  * All rights reserved.
+  *
+  * Contributor(s): Mike Schrag <mschrag@pobox.com>
+  *                 Byron Jones <bugzilla@glob.com.au>
+  *                 Marc Schumann <wurblzap@gmail.com>
+  *                 Frédéric Buclin <LpSolit@gmail.com>
+  */
+
+body {
+    background: #c8c8c8;
+    font-family: Helvetica, Arial, Geneva;
+    padding-left: 1em;
+    padding-right: 1em;
+}
+
+/* page title */
+
+#titles {
+    -moz-border-radius-topleft: 5px;
+    -moz-border-radius-topright: 5px;
+}
+
+#header .links {
+    background-color: #929bb1;
+    color: #f1dbc7;
+    -moz-border-radius-bottomleft: 5px;
+    -moz-border-radius-bottomright: 5px;
+    border: none;
+}
+
+#header a {
+    color: white;
+}
+
+/* body */
+
+#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:visited {
+    color: #3d4a68;
+}
+
+a:link,
+a:visited {
+    text-decoration: none;
+}
+
+a:link:hover,
+a:visited:hover {
+    text-decoration: underline;
+}
+
+hr {
+  border-color: #969696;
+  border-style: dashed;
+  border-width: 1px;
+  margin-top: 10px;
+}
+
+/* edit */
+
+#bugzilla-body th {
+    font-weight: bold;
+    vertical-align: top;
+    white-space: nowrap;
+}
+
+#bug-form td {
+    padding-top: 2px;
+}
+
+/* attachments */
+
+#attachment-list {
+    border: 2px solid #c8c8ba;
+    font-size: 9pt;
+}
+
+#attachment-list th {
+    background-color: #e6e6d8;
+    border: none;
+    border-bottom: 1px solid #c8c8ba;
+    text-align: left;
+}
+
+#attachment-list th a {
+    color: #646456;
+}
+
+#attachment-list td {
+    border: none;
+}
+
+#attachment-list-actions td {
+    border-top: 1px solid #c8c8ba;
+}
+
+/* comments */
+
+#comments th {
+    font-size: 9pt;
+    font-weight: bold;
+    padding-top: 5px;
+    padding-right: 5px;
+    padding-bottom: 10px;
+    text-align: right;
+    vertical-align: top;
+    white-space: nowrap;
+}
+
+#comments td {
+    padding-top: 2px;
+}
+
+.reply-button a {
+    padding-left: 2px;
+    padding-right: 2px;
+}
+
+.bz_comment {
+    background-color: #e8e8e8;
+    margin: 1px 1px 10px 1px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: #c8c8ba;
+    padding: 5px;
+    font-size: 9pt;
+}
+
+.bz_first_comment {
+}
+
+.bz_comment_head,
+.bz_first_comment_head {
+    margin: 0; padding: 0;
+    background-color: transparent;
+    font-weight: bold;
+}
+
+.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;
+}
+
+/* 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,
+#footer #links-special {
+    margin-top: 2ex;
+}
+
+#footer .label {
+    font-weight: bold;
+    color: #dddddd;
+}
+
+#footer .links {
+    border-spacing: 30px;
+    padding-bottom: 2ex;
+}
+
+.separator {
+    color: #cccccc;
+}
+
+#footer li.form {
+    background-color: transparent;
+}
+
+/* tabs */
+
+.tabbed .tabbody {
+    background: #f8f8f8;
+    padding: 1em;
+    border-style: solid;
+    border-color: #000000;
+    border-width: 0 3px 3px 1px;
+}
+
+.tabs {
+    margin: 0;
+    padding: 0;
+    border-collapse: collapse;
+}
+
+.tabs td {
+    background: #c8c8c8;
+    border-width: 1px;
+}
+
+.tabs td.selected {
+    background: #f8f8f8;
+    border-width: 1px 3px 0 1px;
+}
+
+.tabs td.spacer {
+    background: transparent;
+    border-top: none;
+    border-left: none;
+    border-right: none;
+}
+
+/* other */
+
+.bz_row_odd {
+    background-color: #f0f0f0;
+}
+
+/* Rules specific for printing */
+@media print {
+    #header,
+    #footer,
+    .navigation {
+        display: none;
+    }
+
+    body {
+        background-image: none;
+        background-color: #ffffff;
+    }
+
+    #bugzilla-body {
+        border: none;
+        margin: 0;
+        padding: 0;
+    }
+}
diff --git a/BugsSite/skins/custom/global.css b/BugsSite/skins/custom/global.css
index 5dc04cb..97a07e0 100644
--- a/BugsSite/skins/custom/global.css
+++ b/BugsSite/skins/custom/global.css
@@ -8,6 +8,7 @@
 a img {
 	border: 0;
 }
+
 table {
 	border-collapse: collapse;
 }
@@ -21,35 +22,38 @@
 	border-bottom: 0px;
 }
 
-#banner {
-	height: 28px;
-	margin: 10px 0 5px 0;
-	padding: 10px 0 10px 0;
-	background-color: #aaa;
-	color: #fff;
-	-webkit-border-radius: 10px;
-	 -moz-border-radius: 10px;
-}
-
-#banner-name {
-	background-image: none;
-}
-
-#banner-name span {
-	font-size: 20px;
-	text-shadow: #000 1px 1px 5px;
-}
-
-#banner-version {
-	display: none;
-}
-
 #header {
+        padding-top: 5px;
+}
+
+#titles {
+        height: 32px;
+	-webkit-border-top-left-radius: 10px;
+	-webkit-border-top-right-radius: 10px;
+	-moz-border-top-left-radius: 10px;
+	-moz-border-top-right-radius: 10px;
+}
+
+#title {
+        text-align: center;
+        padding-left: 10px;
+        padding-right: 10px;
+        font-size: 20px;
+        text-shadow: #000 1px 1px 5px;
+}
+
+#bug_title {
+        text-align: center;
 	background-color: #ddd;
-	padding: 10px;
-	margin-bottom: 5px;
-	-webkit-border-radius: 10px;
-	-moz-border-radius: 10px;
+        padding: 5px;
+        border: 1px solid #aaa;
+}
+
+#header > .links {
+	-webkit-border-bottom-left-radius: 10px;
+	-webkit-border-bottom-right-radius: 10px;
+	-moz-border-botom-left-radius: 10px;
+	-moz-border-botom-right-radius: 10px;
 }
 
 #header h1, #header h2, #header h3 {
@@ -102,12 +106,16 @@
         font-weight: bold;
 }
 
+#attachment_table {
+        width: auto;
+}
+
 #summary_fields input {
   width: 50em;
 }
 
-#comment {
-  width: 60em;
+#comment, #description {
+  width: 98%;
 }
 
 span.bz_comment {
@@ -150,6 +158,15 @@
 
 }
 
+.bz_alias_short_desc_container {
+        -webkit-border-radius: 5px;
+        -moz-border-radius: 5px;     
+}
+
 .comment_rule {
         visibility: collapse;
 }
+
+.flag_select {
+        min-width: 40px;
+}
diff --git a/BugsSite/skins/standard/IE-fixes.css b/BugsSite/skins/standard/IE-fixes.css
index b0f3414..bfd525b 100644
--- a/BugsSite/skins/standard/IE-fixes.css
+++ b/BugsSite/skins/standard/IE-fixes.css
@@ -13,6 +13,11 @@
   * Contributor(s): Marc Schumann <wurblzap@gmail.com>
   */
 
+.bz_comment_text, .uneditable_textarea {
+     white-space: pre;
+     word-wrap: break-word;
+}
+
 #footer #useful-links li {
     padding-bottom: 0.8ex;
 }
diff --git a/BugsSite/skins/standard/admin.css b/BugsSite/skins/standard/admin.css
index 48bf3fe..184c496 100644
--- a/BugsSite/skins/standard/admin.css
+++ b/BugsSite/skins/standard/admin.css
@@ -29,6 +29,11 @@
     border-color: red;
 }
 
+.alert {
+    color: red;
+    background-color: inherit;
+}
+
 p.areyoureallyreallysure {
     color: red;
     font-size: 120%;
@@ -38,3 +43,73 @@
 tr.param_disabled {
     background-color: lightgrey;
 }
+
+td.admin_links {
+    width: 50%;
+    padding: 1em;
+    vertical-align: top;
+}
+
+td.admin_links dt {
+    margin-top: 1em;
+}
+
+td.admin_links dt.forbidden, td.admin_links dd.forbidden {
+    font-size: smaller;
+    font-style: italic;
+    color: #aaa;
+}
+
+td.admin_links dt.forbidden a, td.admin_links dd.forbidden a {
+    text-decoration: none;
+    color: inherit;
+    cursor: default;
+}
+
+.col-header {
+    width: 8em;
+}
+
+.checkbox-cell {
+    border: 1px black solid;
+}
+
+/* Grey-green color */
+.open-status {
+    color: #286;
+}
+
+/* Brown-red color */
+.closed-status {
+    color: #a63;
+}
+
+/* Dark green color */
+.checked {
+    background-color: #5b4;
+}
+
+/* Dark red color */
+td.forbidden {
+    background-color: #811;
+}
+
+/* Light green color */
+td.set {
+    background-color: #efe;
+}
+
+/* Light red color */
+td.unset {
+    background-color: #fee;
+}
+
+tr.highlight:hover {
+    background-color: yellow;
+}
+
+th.title {
+    font-size: larger;
+    text-align: center;
+    vertical-align: middle;
+}
diff --git a/BugsSite/skins/standard/buglist.css b/BugsSite/skins/standard/buglist.css
index dc3db0e..71206fc 100644
--- a/BugsSite/skins/standard/buglist.css
+++ b/BugsSite/skins/standard/buglist.css
@@ -60,3 +60,7 @@
 
 tr.bz_secure_mode_manual td.first-child {
 }
+
+#commit, #action {
+  margin-top: .25em;
+}
diff --git a/BugsSite/skins/standard/editusers.css b/BugsSite/skins/standard/editusers.css
index 55eb5c3..770d602 100644
--- a/BugsSite/skins/standard/editusers.css
+++ b/BugsSite/skins/standard/editusers.css
@@ -51,6 +51,20 @@
     white-space: nowrap;
 }
 
+table#user_responsibilities th {
+    text-align: center;
+    padding: 0 1em 1em;
+}
+
+table#user_responsibilities th.product {
+    text-align: left;
+    padding: 1em 0 0;
+}
+
+table#user_responsibilities td.center {
+    text-align: center;
+}
+
 .missing {
     color: red;
     border-color: inherit;
diff --git a/BugsSite/skins/standard/global.css b/BugsSite/skins/standard/global.css
index e336f67..aca0ab5 100644
--- a/BugsSite/skins/standard/global.css
+++ b/BugsSite/skins/standard/global.css
@@ -28,6 +28,14 @@
         color: #000;
         background: #fff url("global/body-back.gif") repeat-x;
     }
+    body, td, th, input {
+        font-family: Verdana, sans-serif;
+        font-size: small;
+    }
+    /* monospace is much smaller than Verdana by default, so we make it a bit bigger. */
+    pre, code, kbd {
+        font-size: medium;
+    }
 /* global (end) */
 
 /* header (begin) */
@@ -41,11 +49,6 @@
         display: inline;
     }
 
-    #header .btn,
-    #header .txt {
-        font-size: 80%;
-    }
-
     #header .links {
         font-size: 85%;
         border-left: 1px solid silver;
@@ -115,6 +118,7 @@
 
 /* footer (begin) */
     #footer {
+        clear: both;
         margin-top: 5px;
         width: 100%;
         background: #edf2f2;
@@ -138,7 +142,6 @@
     }
 
     #footer #links-actions,
-    #footer #links-edit,
     #footer #links-saved,
     #footer #links-special {
         display: table-row;
@@ -244,7 +247,26 @@
     margin-bottom: 2em;
 }
 
-.bz_comment_head {
+/* The rules for these classes make international text wrap correctly,
+   even for languages like Japanese that have no spaces. */
+.bz_comment_text, .uneditable_textarea {
+     font-family: monospace;
+    /* Note that these must all be on separate lines or they stop
+       working in Konqueror. */
+     white-space: pre-wrap;      /* CSS 3 & 2.1 */
+     white-space: -moz-pre-wrap; /* Gecko */
+     white-space: -pre-wrap;     /* Opera 4-6 */
+     white-space: -o-pre-wrap;   /* Opera 7 */
+}
+
+.bz_comment_text {
+     width: 50em;
+}
+
+.bz_first_comment {
+}
+
+.bz_comment_head, .bz_first_comment_head {
     background-color: #e0e0e0;
 }
 .bz_comment_hilite pre {
@@ -255,11 +277,13 @@
 
 span.quote {
     color: #65379c;
+    /* Make quoted text not wrap. */
+    white-space: pre;
 }
 
 table#flags th,
 table#flags td {
-    vertical-align: baseline;
+    vertical-align: middle;
     text-align: left;
 }
 
@@ -267,6 +291,10 @@
     min-width: 3em;
 }
 
+#error_msg {
+    font-size: x-large;
+}
+
 .throw_error {
     background-color: #ff0000;
     color: black;
@@ -285,19 +313,23 @@
     border-top: none;
 }
 
-/* Style of the attachment table */
+#admin_table th {
+    white-space: normal !important;
+}
+
+/* Style of the attachment table and time tracking table */
 #attachment_table {
     border-collapse: collapse;
     width: 40em;
     border: 1px solid #333333;
 }
 
-#attachment_table th, .bz_attach_footer {
+#attachment_table th, .bz_attach_footer, .bz_time_tracking_table th {
     background-color: #E0E0E0;
     color: black;
 }
 
-#attachment_table td {
+#attachment_table td, .bz_time_tracking_table th, .bz_time_tracking_table td {
     border: 1px solid #333333;
 }
 
@@ -314,6 +346,28 @@
     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;
 }
@@ -326,6 +380,10 @@
     padding: 0.5em 1em;
 }
 
+.collapsed {
+    display: none;
+}
+
 /* Rules specific for printing */
 @media print {
     #header, #footer {
@@ -343,6 +401,34 @@
     vertical-align: top;
     font-weight: bold;
 }
-.field_value {
+.field_value, form#Create th, form#Create td {
     vertical-align: top;
 }
+
+.calendar_button {
+    background: transparent url("global/calendar.png") no-repeat;
+    width: 20px;
+    height: 20px;
+    vertical-align: middle;
+}
+.calendar_button span { display: none }
+/* These classes are set by YUI. */
+.yui-calcontainer {
+    display: none; 
+    background-color: white; 
+    padding: 10px;
+    border: 1px solid #404D6C;
+}
+
+form#Create th {
+    text-align: right;
+}
+
+form#Create .comment {
+    vertical-align: top;
+    overflow: auto;
+    color: green;
+    margin: 0 0.5em;
+    padding: 0.3em;
+    height: 8ex;
+}
diff --git a/BugsSite/skins/standard/global/calendar.png b/BugsSite/skins/standard/global/calendar.png
new file mode 100644
index 0000000..0eb9ca7
--- /dev/null
+++ b/BugsSite/skins/standard/global/calendar.png
Binary files differ
diff --git a/BugsSite/skins/standard/help.css b/BugsSite/skins/standard/help.css
new file mode 100644
index 0000000..bc888ca
--- /dev/null
+++ b/BugsSite/skins/standard/help.css
@@ -0,0 +1,41 @@
+/* ***** 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/BugsSite/skins/standard/params.css b/BugsSite/skins/standard/params.css
index 8d34d0e..4eec752 100644
--- a/BugsSite/skins/standard/params.css
+++ b/BugsSite/skins/standard/params.css
@@ -48,6 +48,12 @@
   margin-bottom: 1.5em;
 }
 
+.sortlist_separator {
+    font-weight: bold;
+    font-size: 80%;
+    background-color: #dddddd;
+}
+
 .contribute {
     border: 1px dotted black;
     padding: .5em;
diff --git a/BugsSite/skins/standard/show_bug.css b/BugsSite/skins/standard/show_bug.css
new file mode 100644
index 0000000..52d99a8
--- /dev/null
+++ b/BugsSite/skins/standard/show_bug.css
@@ -0,0 +1,75 @@
+.bz_alias_short_desc_container {
+    margin: 8px 0; 
+    padding: 0.3em; 
+    background-color: rgb(208, 208, 208); 
+    -moz-border-radius: 0.5em; 
+    font-size: 125%; 
+    font-weight: bold;
+}
+
+.bz_column_spacer {
+    width: 2em;
+}
+
+.bz_default_hidden {
+    display: none;
+}
+
+.related_actions {
+    font-size: 0.85em; 
+    float: right;
+    list-style-type: none;
+    white-space: nowrap;
+    margin: 0;
+    padding: 0;
+}
+
+.related_actions li {
+    display: inline;
+}
+
+.bz_show_bug_column {
+  vertical-align: top;
+}
+
+.bz_section_spacer {
+    height: 1em;
+}
+
+#duplicate_settings, #votes_container {
+    white-space: nowrap;
+    
+}
+
+.bz_time_tracking_table {
+    border-collapse: collapse;
+}
+
+.bz_time_tracking_table th {
+    text-align: center;
+}
+
+.bz_time_tracking_table td {
+    text-align: center;
+}
+
+.bz_time_tracking_table th, 
+.bz_time_tracking_table td {
+    padding: 4px;
+}
+
+.bz_time_tracking_table .bz_summarize_time {
+    text-align: right;
+}
+
+#summary tr td { 
+    vertical-align:top;
+}
+
+#status { 
+    margin-bottom: 3ex;
+}
+
+#knob-buttons {
+    float: right;
+}
diff --git a/BugsSite/skins/standard/yui/calendar.css b/BugsSite/skins/standard/yui/calendar.css
new file mode 100644
index 0000000..80886d5
--- /dev/null
+++ b/BugsSite/skins/standard/yui/calendar.css
@@ -0,0 +1,7 @@
+/*
+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/BugsSite/skins/standard/yui/sprite.png b/BugsSite/skins/standard/yui/sprite.png
new file mode 100644
index 0000000..afd65e0
--- /dev/null
+++ b/BugsSite/skins/standard/yui/sprite.png
Binary files differ
diff --git a/BugsSite/summarize_time.cgi b/BugsSite/summarize_time.cgi
index 26cc047..071f89a 100755
--- a/BugsSite/summarize_time.cgi
+++ b/BugsSite/summarize_time.cgi
@@ -15,23 +15,19 @@
 #
 # Contributor(s): Christian Reis <kiko@async.com.br>
 #                 Shane H. W. Travis <travis@sedsystems.ca>
-#
+#                 Frédéric Buclin <LpSolit@gmail.com>
+
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Date::Parse;         # strptime
-use Date::Format;        # strftime
 
 use Bugzilla;
 use Bugzilla::Constants; # LOGIN_*
 use Bugzilla::Bug;       # EmitDependList
 use Bugzilla::Util;      # trim
 use Bugzilla::Error;
-use Bugzilla::User;      # Bugzilla->user->in_group
-
-my $template = Bugzilla->template;
-my $vars = {};
 
 #
 # Date handling
@@ -98,30 +94,6 @@
     return ($year, $month, $day);
 }
 
-sub check_dates {
-    my ($start_date, $end_date) = @_;
-    if ($start_date) {
-        if (!str2time($start_date)) {
-            ThrowUserError("illegal_date", {'date' => $start_date});
-        }
-        # This code may strike you as funny. It's actually a workaround
-        # for an "issue" in str2time. If you enter the date 2004-06-31,
-        # even though it's a bogus date (there *are* only 30 days in
-        # June), it will parse and return 2004-07-01. To make this
-        # less painful to the end-user, I do the "normalization" here,
-        # but it might be "surprising" and warrant a warning in the end.
-        $start_date = time2str("%Y-%m-%d", str2time($start_date));
-    } 
-    if ($end_date) {
-        if (!str2time($end_date)) {
-            ThrowUserError("illegal_date", {'date' => $end_date});
-        }
-        # see related comment above.
-        $end_date = time2str("%Y-%m-%d", str2time($end_date));
-    }
-    return ($start_date, $end_date);
-}
-
 sub split_by_month {
     # Takes start and end dates and splits them into a list of
     # monthly-spaced 2-lists of dates.
@@ -175,34 +147,6 @@
     return @months;
 }
 
-sub include_tt_details {
-    my ($res, $bugids, $start_date, $end_date) = @_;
-
-
-    my $dbh = Bugzilla->dbh;
-    my ($date_bits, $date_values) = sqlize_dates($start_date, $end_date);
-    my $buglist = join ", ", @{$bugids};
-
-    my $q = qq{SELECT bugs.bug_id, profiles.login_name, bugs.deadline,
-                      bugs.estimated_time, bugs.remaining_time
-               FROM   longdescs
-               INNER JOIN bugs
-                  ON longdescs.bug_id = bugs.bug_id
-               INNER JOIN profiles
-                  ON longdescs.who = profiles.userid
-               WHERE  longdescs.bug_id in ($buglist) $date_bits};
-
-    my %res = %{$res};
-    my $sth = $dbh->prepare($q);
-    $sth->execute(@{$date_values});
-    while (my $row = $sth->fetch) {
-        $res{$row->[0]}{"deadline"} = $row->[2];
-        $res{$row->[0]}{"estimated_time"} = $row->[3];
-        $res{$row->[0]}{"remaining_time"} = $row->[4];
-    }
-    return \%res;
-}
-
 sub sqlize_dates {
     my ($start_date, $end_date) = @_;
     my $date_bits = "";
@@ -226,172 +170,66 @@
     return ($date_bits, \@date_values);
 }
 
-#
-# Dependencies
-#
-
-sub get_blocker_ids_unique {
-    my $bug_id = shift;
-    my @ret = ($bug_id);
-    get_blocker_ids_deep($bug_id, \@ret);
-    my %unique;
-    foreach my $blocker (@ret) {
-        $unique{$blocker} = $blocker
-    }
-    return keys %unique;
-}
-
-sub get_blocker_ids_deep {
-    my ($bug_id, $ret) = @_;
+# Return all blockers of the current bug, recursively.
+sub get_blocker_ids {
+    my ($bug_id, $unique) = @_;
+    $unique ||= {$bug_id => 1};
     my $deps = Bugzilla::Bug::EmitDependList("blocked", "dependson", $bug_id);
-    push @{$ret}, @$deps;
-    foreach $bug_id (@$deps) {
-        get_blocker_ids_deep($bug_id, $ret);
+    my @unseen = grep { !$unique->{$_}++ } @$deps;
+    foreach $bug_id (@unseen) {
+        get_blocker_ids($bug_id, $unique);
     }
+    return keys %$unique;
 }
 
-#
-# Queries and data structure assembly
-#
-
-sub query_work_by_buglist {
-    my ($bugids, $start_date, $end_date) = @_;
+# Return a hashref whose key is chosen by the user (bug ID or commenter)
+# and value is a hash of the form {bug ID, commenter, time spent}.
+# So you can either view it as the time spent by commenters on each bug
+# or the time spent in bugs by each commenter.
+sub get_list {
+    my ($bugids, $start_date, $end_date, $keyname) = @_;
     my $dbh = Bugzilla->dbh;
 
     my ($date_bits, $date_values) = sqlize_dates($start_date, $end_date);
+    my $buglist = join(", ", @$bugids);
 
-    # $bugids is guaranteed to be non-empty because at least one bug is
-    # always provided to this page.
-    my $buglist = join ", ", @{$bugids};
-
-    # Returns the total time worked on each bug *per developer*, with
-    # bug descriptions and developer address
-    my $q = qq{SELECT sum(longdescs.work_time) as total_time,
-                      profiles.login_name, 
-                      longdescs.bug_id,
-                      bugs.short_desc,
-                      bugs.bug_status
-               FROM   longdescs
-               INNER JOIN profiles
+    # Returns the total time worked on each bug *per developer*.
+    my $data = $dbh->selectall_arrayref(
+            qq{SELECT SUM(work_time) AS total_time, login_name, longdescs.bug_id
+                 FROM longdescs
+           INNER JOIN profiles
                    ON longdescs.who = profiles.userid
-               INNER JOIN bugs
+           INNER JOIN bugs
                    ON bugs.bug_id = longdescs.bug_id
-               WHERE  longdescs.bug_id IN ($buglist)
-                      $date_bits } .
-            $dbh->sql_group_by('longdescs.bug_id, profiles.login_name',
-                'bugs.short_desc, bugs.bug_status, longdescs.bug_when') . qq{
-               ORDER BY longdescs.bug_when};
-    my $sth = $dbh->prepare($q);
-    $sth->execute(@{$date_values});
-    return $sth;
+                WHERE longdescs.bug_id IN ($buglist) $date_bits } .
+            $dbh->sql_group_by('longdescs.bug_id, login_name', 'longdescs.bug_when') .
+           qq{ HAVING SUM(work_time) > 0}, {Slice => {}}, @$date_values);
+
+    my %list;
+    # What this loop does is to push data having the same key in an array.
+    push(@{$list{ $_->{$keyname} }}, $_) foreach @$data;
+    return \%list;
 }
 
-sub get_work_by_owners {
-    my $sth = query_work_by_buglist(@_);
-    my %res;
-    while (my $row = $sth->fetch) {
-        # XXX: Why do we need to check if the total time is positive
-        # instead of using SQL to do that?  Simply because MySQL 3.x's
-        # GROUP BY doesn't work correctly with aggregates. This is
-        # really annoying, but I've spent a long time trying to wrestle
-        # with it and it just doesn't seem to work. Should work OK in
-        # 4.x, though.
-        if ($row->[0] > 0) {
-            my $login_name = $row->[1];
-            push @{$res{$login_name}}, { total_time => $row->[0],
-                                         bug_id     => $row->[2],
-                                         short_desc => $row->[3],
-                                         bug_status => $row->[4] };
-        }
-    }
-    return \%res;
-}
-
-sub get_work_by_bugs {
-    my $sth = query_work_by_buglist(@_);
-    my %res;
-    while (my $row = $sth->fetch) {
-        # Perl doesn't let me use arrays as keys :-(
-        # merge in ID, status and summary
-        my $bug = join ";", ($row->[2], $row->[4], $row->[3]);
-        # XXX: see comment in get_work_by_owners
-        if ($row->[0] > 0) {
-            push @{$res{$bug}}, { total_time => $row->[0],
-                                  login_name => $row->[1], };
-        }
-    }
-    return \%res;
-}
-
+# Return bugs which had no activity (a.k.a work_time = 0) during the given time range.
 sub get_inactive_bugs {
     my ($bugids, $start_date, $end_date) = @_;
     my $dbh = Bugzilla->dbh;
     my ($date_bits, $date_values) = sqlize_dates($start_date, $end_date);
-    my $buglist = join ", ", @{$bugids};
+    my $buglist = join(", ", @$bugids);
 
-    my %res;
-    # This sucks. I need to make sure that even bugs that *don't* show
-    # up in the longdescs query (because no comments were filed during
-    # the specified period) but *are* dependent on the parent bug show
-    # up in the results if they have no work done; that's why I prefill
-    # them in %res here and then remove them below.
-    my $q = qq{SELECT DISTINCT bugs.bug_id, bugs.short_desc ,
-                               bugs.bug_status
-               FROM   longdescs
-               INNER JOIN bugs
-                    ON longdescs.bug_id = bugs.bug_id
-               WHERE  longdescs.bug_id in ($buglist)};
-    my $sth = $dbh->prepare($q);
-    $sth->execute();
-    while (my $row = $sth->fetch) {
-        $res{$row->[0]} = [$row->[1], $row->[2]];
-    }
+    my $bugs = $dbh->selectcol_arrayref(
+        "SELECT bug_id
+           FROM bugs
+          WHERE bugs.bug_id IN ($buglist)
+            AND NOT EXISTS (
+                SELECT 1
+                  FROM longdescs
+                 WHERE bugs.bug_id = longdescs.bug_id
+                   AND work_time > 0 $date_bits)",
+         undef, @$date_values);
 
-    # Returns the total time worked on each bug, with description. This
-    # query differs a bit from one in the query_work_by_buglist and I
-    # avoided complicating that one just to make it more general.
-    $q = qq{SELECT sum(longdescs.work_time) as total_time,
-                   longdescs.bug_id,
-                   bugs.short_desc,
-                   bugs.bug_status
-            FROM   longdescs
-            INNER JOIN bugs
-                ON bugs.bug_id = longdescs.bug_id 
-            WHERE  longdescs.bug_id IN ($buglist)
-                   $date_bits } .
-         $dbh->sql_group_by('longdescs.bug_id',
-                            'bugs.short_desc, bugs.bug_status,
-                             longdescs.bug_when') . qq{
-            ORDER BY longdescs.bug_when};
-    $sth = $dbh->prepare($q);
-    $sth->execute(@{$date_values});
-    while (my $row = $sth->fetch) {
-        # XXX: see comment in get_work_by_owners
-        if ($row->[0] == 0) {
-            $res{$row->[1]} = [$row->[2], $row->[3]];
-        } else {
-            delete $res{$row->[1]};
-        }
-    }
-    return \%res;
-}
-
-#
-# Misc
-#
-
-sub sort_bug_keys {
-    # XXX a hack is the mother of all evils. The fact that we store keys
-    # joined by semi-colons in the workdata-by-bug structure forces us to
-    # write this evil comparison function to ensure we can process the
-    # data timely -- just pushing it through a numerical sort makes TT
-    # hang while generating output :-(
-    my $list = shift;
-    my @a;
-    my @b;
-    return sort { @a = split(";", $a); 
-                  @b = split(";", $b); 
-                  $a[0] <=> $b[0] } @{$list};
+    return $bugs;
 }
 
 #
@@ -401,18 +239,20 @@
 Bugzilla->login(LOGIN_REQUIRED);
 
 my $cgi = Bugzilla->cgi;
+my $user = Bugzilla->user;
+my $template = Bugzilla->template;
+my $vars = {};
 
 Bugzilla->switch_to_shadow_db();
 
-Bugzilla->user->in_group(Bugzilla->params->{"timetrackinggroup"})
+$user->in_group(Bugzilla->params->{"timetrackinggroup"})
     || ThrowUserError("auth_failure", {group  => "time-tracking",
                                        action => "access",
                                        object => "timetracking_summaries"});
 
 my @ids = split(",", $cgi->param('id'));
 map { ValidateBugID($_) } @ids;
-@ids = map { detaint_natural($_) && $_ } @ids;
-@ids = grep { Bugzilla->user->can_see_bug($_) } @ids;
+scalar(@ids) || ThrowUserError('no_bugs_chosen', {action => 'view'});
 
 my $group_by = $cgi->param('group_by') || "number";
 my $monthly = $cgi->param('monthly');
@@ -423,7 +263,7 @@
 my $ctype = scalar($cgi->param("ctype"));
 
 my ($start_date, $end_date);
-if ($do_report && @ids) {
+if ($do_report) {
     my @bugs = @ids;
 
     # Dependency mode requires a single bug and grabs dependents.
@@ -432,8 +272,8 @@
             ThrowCodeError("bad_arg", { argument=>"id",
                                         function=>"summarize_time"});
         }
-        @bugs = get_blocker_ids_unique($bugs[0]);
-        @bugs = grep { Bugzilla->user->can_see_bug($_) } @bugs;
+        @bugs = get_blocker_ids($bugs[0]);
+        @bugs = grep { $user->can_see_bug($_) } @bugs;
     }
 
     $start_date = trim $cgi->param('start_date');
@@ -445,16 +285,13 @@
         $vars->{'warn_swap_dates'} = 1;
         ($start_date, $end_date) = ($end_date, $start_date);
     }
-    ($start_date, $end_date) = check_dates($start_date, $end_date);
-
-    if ($detailed) {
-        my %detail_data;
-        my $res = include_tt_details(\%detail_data, \@bugs, $start_date, $end_date);
-
-        $vars->{'detail_data'} = $res;
+    foreach my $date ($start_date, $end_date) {
+        next unless $date;
+        validate_date($date)
+          || ThrowUserError('illegal_date', {date => $date, format => 'YYYY-MM-DD'});
     }
-  
-    # Store dates ia session cookie the dates so re-visiting the page
+
+    # Store dates in a session cookie so re-visiting the page
     # for other bugs keeps them around.
     $cgi->send_cookie(-name => 'time-summary-dates',
                       -value => join ";", ($start_date, $end_date));
@@ -475,38 +312,35 @@
         # start/end_date aren't provided -- and clock skews will make
         # this evident!
         @parts = split_by_month($start_date, 
-                                $end_date || time2str("%Y-%m-%d", time()));
+                                $end_date || format_time(scalar localtime(time()), '%Y-%m-%d'));
     } else {
         @parts = ([$start_date, $end_date]);
     }
 
-    my %empty_hash;
-    # For each of the separate divisions, grab the relevant summaries 
+    # For each of the separate divisions, grab the relevant data.
+    my $keyname = ($group_by eq 'owner') ? 'login_name' : 'bug_id';
     foreach my $part (@parts) {
-        my ($sub_start, $sub_end) = @{$part};
-        if (@bugs) {
-            if ($group_by eq "owner") {
-                $part_data = get_work_by_owners(\@bugs, $sub_start, $sub_end);
-            } else {
-                $part_data = get_work_by_bugs(\@bugs, $sub_start, $sub_end);
-            }
-        } else {
-            # $part_data must be a reference to a hash
-            $part_data = \%empty_hash; 
-        }
-        push @part_list, $part_data;
+        my ($sub_start, $sub_end) = @$part;
+        $part_data = get_list(\@bugs, $sub_start, $sub_end, $keyname);
+        push(@part_list, $part_data);
     }
 
-    if ($inactive && @bugs) {
+    # Do we want to see inactive bugs?
+    if ($inactive) {
         $vars->{'null'} = get_inactive_bugs(\@bugs, $start_date, $end_date);
     } else {
-        $vars->{'null'} = \%empty_hash;
+        $vars->{'null'} = {};
     }
 
+    # Convert bug IDs to bug objects.
+    @bugs = map {new Bugzilla::Bug($_)} @bugs;
+
     $vars->{'part_list'} = \@part_list;
     $vars->{'parts'} = \@parts;
-
-} elsif ($cgi->cookie("time-summary-dates")) {
+    # We pass the list of bugs as a hashref.
+    $vars->{'bugs'} = {map { $_->id => $_ } @bugs};
+}
+elsif ($cgi->cookie("time-summary-dates")) {
     ($start_date, $end_date) = split ";", $cgi->cookie('time-summary-dates');
 }
 
@@ -519,8 +353,6 @@
 $vars->{'inactive'} = $inactive;
 $vars->{'do_report'} = $do_report;
 $vars->{'do_depends'} = $do_depends;
-$vars->{'check_time'} = \&check_time;
-$vars->{'sort_bug_keys'} = \&sort_bug_keys;
 
 my $format = $template->get_format("bug/summarize-time", undef, $ctype);
 
diff --git a/BugsSite/t/001compile.t b/BugsSite/t/001compile.t
index 7d0bc01..78fc6a6 100644
--- a/BugsSite/t/001compile.t
+++ b/BugsSite/t/001compile.t
@@ -36,11 +36,11 @@
 use DBI;
 my @DBI_drivers = DBI->available_drivers;
 
-# Bugzilla requires Perl 5.8.0 now.  Checksetup will tell you this if you run it, but
+# 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 won't
-# pass and mistakenly let people think Bugzilla works on any perl below 5.8.
-require 5.008;
+# 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.
@@ -91,13 +91,23 @@
     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 {
+        }
+        else {
             ok(1,$file);
         }
-    } else {
+    }
+    else {
         ok(0,$file." --ERROR");
         print $fh $loginfo;
     }
diff --git a/BugsSite/t/004template.t b/BugsSite/t/004template.t
index 034031c..31ce792 100644
--- a/BugsSite/t/004template.t
+++ b/BugsSite/t/004template.t
@@ -39,7 +39,7 @@
 use File::Spec;
 use Template;
 use Test::More tests => ( scalar(@referenced_files) * scalar(@languages)
-                        + $num_actual_files * 2 );
+                        + $num_actual_files );
 
 # Capture the TESTOUT from Test::More or Test::Builder for printing errors.
 # This will handle verbosity for us automatically.
@@ -69,12 +69,12 @@
 foreach my $lang (@languages) {
     foreach my $file (@referenced_files) {
         my @path = map(File::Spec->catfile($_, $file),
-                       split(':', $include_path{$lang}));
+                       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);
+            print $fh "Looked in:\n  " . join("\n  ", @path) . "\n";
         }
     }
 }
@@ -132,20 +132,6 @@
             ok(1, "$path doesn't exist, skipping test");
         }
     }
-
-    # check to see that all templates have a version string:
-
-    foreach my $file (@{$actual_files{$include_path}}) {
-        my $path = File::Spec->catfile($include_path, $file);
-        open(TMPL, $path);
-        my $firstline = <TMPL>;
-        if ($firstline =~ /\d+\.\d+\@[\w\.-]+/) {
-            ok(1,"$file has a version string");
-        } else {
-            ok(0,"$file does not have a version string --ERROR");
-        }
-        close(TMPL);
-    }
 }
 
 exit 0;
diff --git a/BugsSite/t/006spellcheck.t b/BugsSite/t/006spellcheck.t
index 0110aa1..fe631e3 100644
--- a/BugsSite/t/006spellcheck.t
+++ b/BugsSite/t/006spellcheck.t
@@ -31,12 +31,18 @@
 #add the words to check here:
 @evilwords = qw(
 anyways
+appearence
 arbitary
+cancelled
+critera
 databasa
 dependan
 existance
 existant
 paramater
+refered
+repsentation
+suported
 varsion
 );
 
diff --git a/BugsSite/t/007util.t b/BugsSite/t/007util.t
index 5f2c998d..18d58148 100644
--- a/BugsSite/t/007util.t
+++ b/BugsSite/t/007util.t
@@ -28,7 +28,7 @@
 use Support::Files;
 
 BEGIN { 
-        use Test::More tests => 13;
+        use Test::More tests => 12;
         use_ok(Bugzilla);
         use_ok(Bugzilla::Util);
 }
@@ -48,9 +48,6 @@
 #url_quote():
 is(url_quote("<lala&>gaa\"'[]{\\"),"%3Clala%26%3Egaa%22%27%5B%5D%7B%5C",'url_quote');
 
-#value_quote():
-is(value_quote("<lal\na&>g\naa\"'[\n]{\\"),"&lt;lal&#013;a&amp;&gt;g&#013;aa&quot;'[&#013;]{\\",'value_quote');
-
 #lsearch():
 my @list = ('apple','pear','plum','<"\\%');
 is(lsearch(\@list,'pear'),1,'lsearch 1');
diff --git a/BugsSite/t/008filter.t b/BugsSite/t/008filter.t
index d405346..9a53ced 100644
--- a/BugsSite/t/008filter.t
+++ b/BugsSite/t/008filter.t
@@ -176,7 +176,7 @@
     return 1 if $directive =~ /^(IF|END|UNLESS|FOREACH|PROCESS|INCLUDE|
                                  BLOCK|USE|ELSE|NEXT|LAST|DEFAULT|FLUSH|
                                  ELSIF|SET|SWITCH|CASE|WHILE|RETURN|STOP|
-                                 TRY|CATCH|FINAL|THROW|CLEAR|MACRO)/x;
+                                 TRY|CATCH|FINAL|THROW|CLEAR|MACRO|FILTER)/x;
 
     # ? :
     if ($directive =~ /.+\?(.+):(.+)/) {
@@ -190,7 +190,7 @@
     return 1 if $directive =~ /^[0-9]+$/;
 
     # Simple assignments
-    return 1 if $directive =~ /^[\w\.\$]+\s+=\s+/;
+    return 1 if $directive =~ /^[\w\.\$\{\}]+\s+=\s+/;
 
     # Conditional literals with either sort of quotes 
     # There must be no $ in the string for it to be a literal
@@ -211,7 +211,7 @@
     return 1 if $directive =~ /^(time2str|url)\(/;
 
     # Safe Template Toolkit virtual methods
-    return 1 if $directive =~ /\.(length$|size$|push\()/;
+    return 1 if $directive =~ /\.(length$|size$|push\(|delete\()/;
 
     # Special Template Toolkit loop variable
     return 1 if $directive =~ /^loop\.(index|count)$/;
diff --git a/BugsSite/t/Support/Files.pm b/BugsSite/t/Support/Files.pm
index 8fc3345..dc60687 100644
--- a/BugsSite/t/Support/Files.pm
+++ b/BugsSite/t/Support/Files.pm
@@ -32,6 +32,7 @@
 %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']
 );
diff --git a/BugsSite/template/en/custom/attachment/content-types.html.tmpl b/BugsSite/template/en/custom/attachment/content-types.html.tmpl
index fa14841..297ba89 100644
--- a/BugsSite/template/en/custom/attachment/content-types.html.tmpl
+++ b/BugsSite/template/en/custom/attachment/content-types.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/custom/attachment/create.html.tmpl b/BugsSite/template/en/custom/attachment/create.html.tmpl
index c8750b9..6bf7c74 100644
--- a/BugsSite/template/en/custom/attachment/create.html.tmpl
+++ b/BugsSite/template/en/custom/attachment/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,7 +21,7 @@
   #                 Marc Schumann <wurblzap@gmail.com>
   #%]
 
-[% PROCESS global/variables.none.tmpl %]
+[% PROCESS "global/field-descs.none.tmpl" %]
 
 [%# Define strings that will serve as the title and header of this page %]
 [% title = BLOCK %]Create New Attachment for [% terms.Bug %] #[% bug.bug_id %][% END %]
@@ -37,11 +36,13 @@
   onload="setContentTypeDisabledState(document.entryform);"
   style_urls = [ 'skins/standard/create_attachment.css' ]
   javascript_urls = [ "js/attachment.js" ]
+  doc_section = "attachments.html"
 %]
 
 <form name="entryform" method="post" action="attachment.cgi" enctype="multipart/form-data">
   <input type="hidden" name="bugid" value="[% bug.bug_id %]">
   <input type="hidden" name="action" value="insert">
+  <input type="hidden" name="token" value="[% token FILTER html %]">
 
   <table class="attachment_entry">
     [% PROCESS attachment/createformcontents.html.tmpl %]
@@ -74,6 +75,21 @@
               check the box below.</em><br>
           <input type="checkbox" id="takebug" name="takebug" value="1">
           <label for="takebug">take [% terms.bug %]</label>
+          [% bug_statuses = [] %]
+          [% FOREACH bug_status = bug.status.can_change_to %]
+            [% NEXT IF bug_status.name == "UNCONFIRMED" && !bug.product_obj.votes_to_confirm %]
+            [% 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>
+              [% 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>
+              [% END %]
+            </select>
+          [% END %]
         </td>
       </tr>
     [% END %]
diff --git a/BugsSite/template/en/custom/attachment/created.html.tmpl b/BugsSite/template/en/custom/attachment/created.html.tmpl
index 544f4a4..049d44e 100644
--- a/BugsSite/template/en/custom/attachment/created.html.tmpl
+++ b/BugsSite/template/en/custom/attachment/created.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -20,27 +19,42 @@
   #%]
 
 [%# INTERFACE:
-  # bugid: integer. ID of the bug we just attached an attachment to.
-  # attachid: integer. ID of the attachment just created.
-  # description: string. Description of the attachment just created.
-  # contenttype: string. The Content Type we attached it as.
+  # attachment: object of the attachment just created.
   # contenttypemethod: string. How we got the content type of the attachment.
   #  Possible values: autodetect, list, manual.
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
+[% bug = bugs.0 %]
+[% 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 = "Changes Submitted"
+  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>
   <dt>
-    <a title="[% description FILTER html %]" href="attachment.cgi?id=[% attachid %]&amp;action=review">Attachment #[% attachid %]</a>
-    to [% "$terms.bug $bugid" FILTER bug_link(bugid) %] created
+[%# if WEBKIT_CHANGES %]
+    <a title="[% attachment.description FILTER html %]"
+       href="attachment.cgi?id=[% attachment.id %]&amp;action=review">Attachment #[% attachment.id %]</a>
+[%# endif // WEBKIT_CHANGES %]
+    to [% "$terms.bug $attachment.bug_id" FILTER bug_link(attachment.bug_id) FILTER none %] created
   </dt>
   <dd>
-    [% PROCESS "bug/process/bugmail.html.tmpl" mailing_bugid = bugid %]
+    [% PROCESS "bug/process/bugmail.html.tmpl" mailing_bugid = attachment.bug_id %]
     [% IF convertedbmp %]
       <p>
         <b>Note:</b> [% terms.Bugzilla %] automatically converted your BMP image file to a
@@ -50,9 +64,11 @@
     [% IF contenttypemethod == 'autodetect' %]
       <p>
         <b>Note:</b> [% terms.Bugzilla %] automatically detected the content type
-        <em>[% contenttype %]</em> for this attachment.  If this is
-        incorrect, correct the value by
-        editing the attachment's <a href="attachment.cgi?id=[% attachid %]&amp;action=review">details</a>.
+        <em>[% attachment.contenttype FILTER html %]</em> for this attachment.  If this is
+        incorrect, correct the value by editing the attachment's
+[%# if WEBKIT_CHANGES %]
+        <a href="attachment.cgi?id=[% attachment.id %]&amp;action=review">details</a>.
+[%# endif // WEBKIT_CHANGES %]
       </p>
     [% END %]
 
@@ -62,8 +78,8 @@
 </dl>
 
 <p>
-<a href="attachment.cgi?bugid=[% bugid %]&amp;action=enter">Create
- Another Attachment to [% terms.Bug %] #[% bugid %]</a>
+<a href="attachment.cgi?bugid=[% attachment.bug_id %]&amp;action=enter">Create
+ Another Attachment to [% terms.Bug %] [%+ attachment.bug_id %]</a>
 </p>
 
-[% PROCESS global/footer.html.tmpl %]
+[% PROCESS bug/show.html.tmpl %]
diff --git a/BugsSite/template/en/custom/attachment/edit.html.tmpl b/BugsSite/template/en/custom/attachment/edit.html.tmpl
index 0b6ff47..c6b5ab4 100644
--- a/BugsSite/template/en/custom/attachment/edit.html.tmpl
+++ b/BugsSite/template/en/custom/attachment/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -36,15 +35,12 @@
   title = title
   header = header
   subheader = subheader
-  style = "
-    table.attachment_info th { text-align: right; vertical-align: top; }
-    table.attachment_info td { text-align: left; vertical-align: top; }
-    #noview { text-align: left; vertical-align: middle; }
-
-    table#flags th, table#flags td { font-size: small; vertical-align: baseline; text-align: left; }
-  "
+  doc_section = "attachments.html"
 %]
 
+[%# 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';
@@ -56,37 +52,7 @@
 // endif WEBKIT_CHANGES
   function editAsComment()
     {
-      // Get the content of the document as a string.
-      var viewFrame = document.getElementById('viewFrame');
-      var aSerializer = new XMLSerializer();
-      var contentDocument = viewFrame.contentDocument;
-      var theContent = aSerializer.serializeToString(contentDocument);
-
-      // If this is a plaintext document, remove cruft that Mozilla adds
-      // because it treats it as an HTML document with a big PRE section.
-      // http://bugzilla.mozilla.org/show_bug.cgi?id=86012
-      var contentType = '[% attachment.contenttype FILTER js %]';
-      if ( contentType == 'text/plain' )
-        {
-          theContent = theContent.replace( /^<html><head\/?><body><pre>/i , "" );
-          theContent = theContent.replace( /<\/pre><\/body><\/html>$/i , "" );
-          theContent = theContent.replace( /&lt;/gi , "<" );
-          theContent = theContent.replace( /&gt;/gi , ">" );
-          theContent = theContent.replace( /&amp;/gi , "&" );
-        }
-
-      // Add mail-style quote indicators (>) to the beginning of each line.
-      // ".*\n" matches lines that end with a newline, while ".+" matches
-      // the rare situation in which the last line of a file does not end
-      // with a newline.
-      theContent = theContent.replace( /(.*\n|.+)/g , ">$1" );
-
       switchToMode('edit');
-
-      // Copy the contents of the diff into the textarea
-      var editFrame = document.getElementById('editFrame');
-      editFrame.value = theContent + "\n\n";
-
       has_edited = 1;
     }
   function undoEditAsComment()
@@ -229,6 +195,10 @@
   <input type="hidden" name="id" value="[% attachment.id %]">
   <input type="hidden" name="action" value="update">
   <input type="hidden" name="contenttypemethod" value="manual">
+  <input type="hidden" name="delta_ts" value="[% attachment.modification_time FILTER html %]">
+  [% IF user.id %]
+    <input type="hidden" name="token" value="[% issue_hash_token([attachment.id, attachment.modification_time]) FILTER html %]">
+  [% END %]
 
   <table class="attachment_info" width="100%">
 
@@ -267,11 +237,11 @@
                    value="[% attachment.contenttype FILTER html %]"><br>
 
           <input type="checkbox" id="ispatch" name="ispatch" value="1"
-                 [% 'checked="checked"' IF attachment.ispatch %]>
+                 [%+ '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 %]>
+                 [%+ '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"
@@ -279,6 +249,7 @@
             <label for="isprivate">private</label><br>
           [% END %]
           <br>
+        </small>
 
         [% IF flag_types.size > 0 %]
           [% PROCESS "flag/list.html.tmpl" bug_id = attachment.bug_id
@@ -286,8 +257,8 @@
         [% END %]
 
         <div id="smallCommentFrame">
-          <b><label for="comment">Comment</label> (on the
-          [%+ terms.bug %]):</b><br>
+          <b><small><label for="comment">Comment</label> (on the
+          [%+ terms.bug %]):</small></b><br>
             [% INCLUDE global/textarea.html.tmpl
               id      = 'comment'
               name    = 'comment'
@@ -313,47 +284,10 @@
               && attachment.datasize > 0 %]
           | <a href="attachment.cgi?id=[% attachment.id %]&amp;action=delete">Delete</a>
         [% END %]
-        </small>
       </td>
 
       [% IF !attachment.datasize %]
         <td width="75%"><b>The content of this attachment has been deleted.</b></td>
-      [% ELSIF isviewable %]
-        <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'
-          %]
-          <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>
       [% ELSIF attachment.isurl %]
         <td width="75%">
           <a href="[% attachment.data FILTER html %]">
@@ -366,6 +300,55 @@
             [% 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>
diff --git a/BugsSite/template/en/custom/attachment/list.html.tmpl b/BugsSite/template/en/custom/attachment/list.html.tmpl
index 17a17c9..be98425 100644
--- a/BugsSite/template/en/custom/attachment/list.html.tmpl
+++ b/BugsSite/template/en/custom/attachment/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -126,7 +125,7 @@
         <td valign="top">
 [%# if WEBKIT_CHANGES %]
           [% IF attachment.ispatch %]
-          <a href="attachment.cgi?id=[% attachment.id %]&amp;action=review">Review Patch</a>
+          <a href="attachment.cgi?id=[% attachment.id %]&amp;action=review">Review Patch</a> |
           [% END %]
 [%# endif // WEBKIT_CHANGES %]
           <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">Details</a>
@@ -149,9 +148,11 @@
       [% 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);">Hide Obsolete</a> ([% obsolete_attachments %])
           [% END %]
-          <a href="attachment.cgi?bugid=[% bugid %]&amp;action=viewall">View All</a>
+          [% IF Param("allow_attachment_display") %]
+            <a href="attachment.cgi?bugid=[% bugid %]&amp;action=viewall">View All</a>
+          [% END %]
         </span>
       [% END %]
       <a href="attachment.cgi?bugid=[% bugid %]&amp;action=enter">Add an attachment</a>
diff --git a/BugsSite/template/en/custom/attachment/review.html.tmpl b/BugsSite/template/en/custom/attachment/review.html.tmpl
index 734aee2..b36a317 100644
--- a/BugsSite/template/en/custom/attachment/review.html.tmpl
+++ b/BugsSite/template/en/custom/attachment/review.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -24,9 +23,9 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-<title>Review patch #[% attachid %] for [% terms.Bug %] #[% bugid %]: [% bugsummary FILTER html %]</title>
+<title>Review patch #[% attachment.id %] for [% terms.Bug %] #[% attachment.bug_id %]: [% bugsummary FILTER html %]</title>
 <frameset rows="60%,40%">
-<frame id=viewFrame src="attachment.cgi?id=[% attachid %]&action=prettypatch">
-<frame src="attachment.cgi?id=[% attachid %]&action=reviewform">
+<frame id=viewFrame src="attachment.cgi?id=[% attachment.id %]&action=prettypatch">
+<frame src="attachment.cgi?id=[% attachment.id %]&action=reviewform">
 </frameset>
 
diff --git a/BugsSite/template/en/custom/attachment/reviewform.html.tmpl b/BugsSite/template/en/custom/attachment/reviewform.html.tmpl
index f01b0ef..cbfd1d8 100644
--- a/BugsSite/template/en/custom/attachment/reviewform.html.tmpl
+++ b/BugsSite/template/en/custom/attachment/reviewform.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -58,16 +57,20 @@
 </script>
 
 <form method="post" action="attachment.cgi" target="_top" onsubmit="omitUntouchedComment(); return true;">
-  <input type="hidden" name="id" value="[% attachid %]">
+  <input type="hidden" name="id" value="[% attachment.id %]">
   <input type="hidden" name="action" value="update">
   <input type="hidden" name="contenttypemethod" value="manual">
-  <input type="hidden" name="description" value="[% description FILTER html %]">
-  <input type="hidden" name="filename" value="[% filename FILTER html %]">
-  <input type="hidden" name="contenttypeentry" value="[% contenttype FILTER html %]"><br>
-  <input type="hidden" name="ispatch" [% IF ispatch %] value="1" [% ELSE %] value="0" [% END %] >
-  <input type="hidden" name="isobsolete" [% IF isobsolete %] value="1" [% ELSE %] value="0" [% END %] >
-  [% IF (Param("insidergroup") && UserInGroup(Param("insidergroup"))) %]
-    <input type="hidden" name="isprivate" [% IF isprivate %] value="1" [% ELSE %] value="0" [% END %] >
+  <input type="hidden" name="delta_ts" value="[% attachment.modification_time FILTER html %]">
+  [% IF user.id %]
+    <input type="hidden" name="token" value="[% issue_hash_token([attachment.id, attachment.modification_time]) FILTER html %]">
+  [% END %]
+  <input type="hidden" name="description" value="[% attachment.description FILTER html %]">
+  <input type="hidden" name="filename" value="[% attachment.filename FILTER html %]">
+  <input type="hidden" name="contenttypeentry" value="[% attachment.contenttype FILTER html %]"><br>
+  <input type="hidden" name="ispatch" [% IF attachment.ispatch %] value="1" [% ELSE %] value="0" [% END %] >
+  <input type="hidden" name="isobsolete" [% IF attachment.isobsolete %] value="1" [% ELSE %] value="0" [% END %] >
+  [% IF (Param("insidergroup") && user.in_group(Param("insidergroup"))) %]
+    <input type="hidden" name="isprivate" [% IF attachment.isprivate %] value="1" [% ELSE %] value="0" [% END %] >
   [% END %]
 
 <table style="width:100%; height:90%" cellpadding=0 cellspacing=0>
@@ -77,11 +80,11 @@
 </td>
 <td>
     [% IF flag_types.size > 0 %]
-      [% PROCESS "flag/list.html.tmpl" bug_id=bugid attach_id=attachid %]<br>
+      [% PROCESS "flag/list.html.tmpl" bug_id=attachment.bug_id attach_id=attachment.id %]<br>
     [% END %]
 </td>
 <td>
-Back to <a href="show_bug.cgi?id=[% bugid %]" target="_top">[% terms.Bug %] #[% bugid %]</a>
+Back to <a href="show_bug.cgi?id=[% attachment.bug_id %]" target="_top">[% terms.Bug %] #[% attachment.bug_id %]</a>
 </td>
 <td>
     [% IF ispatch %]
diff --git a/BugsSite/template/en/custom/bug/edit.html.tmpl b/BugsSite/template/en/custom/bug/edit.html.tmpl
index 1427f06..ab45206 100644
--- a/BugsSite/template/en/custom/bug/edit.html.tmpl
+++ b/BugsSite/template/en/custom/bug/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -21,6 +20,8 @@
   #                 Max Kanat-Alexander <mkanat@bugzilla.org>
   #                 Frédéric Buclin <LpSolit@gmail.com>
   #                 Olav Vitters <olav@bkor.dhs.org>
+  #                 Guy Pyrzak <guy.pyrzak@gmail.com>
+  #                 Elliotte Martin <emartin@everythingsolved.com>
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
@@ -31,36 +32,43 @@
 
   <script type="text/javascript">
   <!--
-
+  
   /* Outputs a link to call replyToComment(); used to reduce HTML output */
-  function addReplyLink(id) {
+  function addReplyLink(id, real_id) {
       /* XXX this should really be updated to use the DOM Core's
        * createElement, but finding a container isn't trivial.
        */
-      document.write('[<a href="#add_comment" onclick="replyToComment(' + 
-                     id + ');">reply<' + '/a>]');
+      [% IF user.settings.quote_replies.value != 'off' %]
+        document.write('[<a href="#add_comment" onclick="replyToComment(' + 
+                       id + ',' + real_id + ');">reply<' + '/a>]');
+      [% END %]
   }
 
   /* Adds the reply text to the `comment' textarea */
-  function replyToComment(id) {
-      /* 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/);
-
+  function replyToComment(id, real_id) {
+      var prefix = "(In reply to comment #" + id + ")\n";
       var replytext = "";
-      for (var i=0; i < text.length; i++) {
-          replytext += "> " + text[i] + "\n"; 
-      }
+      [% IF user.settings.quote_replies.value == 'quoted_reply' %]
+        /* pre id="comment_name_N" */
+        var text_elem = document.getElementById('comment_text_'+id);
+        var text = getText(text_elem);
 
-      replytext = "(In reply to comment #" + id + ")\n" + replytext + "\n";
+        /* make sure we split on all newlines -- IE or Moz use \r and \n
+         * respectively.
+         */
+        text = text.split(/\r|\n/);
+
+        for (var i=0; i < text.length; i++) {
+            replytext += "> " + text[i] + "\n"; 
+        }
+
+        replytext = prefix + replytext + "\n";
+      [% ELSIF user.settings.quote_replies.value == 'simple_reply' %]
+        replytext = prefix;
+      [% END %]
 
     [% IF Param("insidergroup") && user.in_group(Param("insidergroup")) %]
-      if (document.getElementById('isprivate-'+id).checked) {
+      if (document.getElementById('isprivate_' + real_id).checked) {
           document.getElementById('newcommentprivacy').checked = 'checked';
       }
     [% END %]
@@ -136,251 +144,80 @@
   <input type="hidden" name="delta_ts" value="[% bug.delta_ts %]">
   <input type="hidden" name="longdesclength" value="[% bug.longdescs.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 %]">
 
-  [%# That's the main table, which contains all editable fields. %]
+  [% PROCESS section_title %]
 [%# if WEBKIT_CHANGES %]
   <table id="bug_details">
 [%# endif // WEBKIT_CHANGES %]
     <tr>
-      <td valign="top">
-        <fieldset>
-          <legend>Details</legend>
-          <table>
-
-            [%# *** Summary *** %]
-            <tr>
-              <td align="right">
-                <label for="short_desc" accesskey="s"><b><u>S</u>ummary</b></label>:
-              </td>
-              [% PROCESS input inputname => "short_desc" size => "60" colspan => 2
-                               maxlength => 255 %]
-            </tr>
-
-            <tr>
-              <td colspan="3">
-                <table>
-                  <tr>
-                    [%# *** ID, product, component, status and resolution *** %]
-                    <td valign="top">[% PROCESS section_details1 %]</td>
-
-                    [%# *** Platform, OS, severity, priority, version and milestone *** %]
-                    <td valign="top">[% PROCESS section_details2 %]</td>
-                  </tr>
-                </table>
-              </td>
-            </tr>
-
-            <tr>
-              <td colspan="3"><hr size="1"></td>
-            </tr>
-
-            [%# *** URL Whiteboard Keywords *** %]
-
-            <tr>
-              <td align="right">
-                <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>
-              [% PROCESS input inputname => "bug_file_loc" size => "60" colspan => 2 %]
-            </tr>
-
-            [% IF Param('usestatuswhiteboard') %]
-              <tr>
-                <td align="right">
-                  <label for="status_whiteboard" accesskey="w"><b><u>W</u>hiteboard</b></label>:
-                </td>
-                [% PROCESS input inputname => "status_whiteboard" size => "60" colspan => 2 %]
-              </tr>
-            [% END %]
-
-            [% IF use_keywords %]
-              <tr>
-                <td align="right">
-                  <label for="keywords" accesskey="k">
-                    <b><a href="describekeywords.cgi"><u>K</u>eywords</a></b></label>:
-                </td>
-                [% PROCESS input inputname => "keywords" size => 60 colspan => 2
-                                 value => bug.keywords.join(', ') %]
-              </tr>
-            [% END %]
-
-            [%# *** Custom Fields *** %]
-
-            [% USE Bugzilla %]
-            [% fields = Bugzilla.get_fields({ obsolete => 0, custom => 1 }) %]
-            [% IF fields %]
-              [% FOREACH field = fields %]
-                <tr>
-                  [% PROCESS bug/field.html.tmpl value=bug.${field.name}
-                                                 editable = bug.check_can_change_field(field.name, 0, 1)
-                                                 value_span = 2 %]
-                </tr>
-              [% END %]
-            [% END %]
-
-            [%# *** Dependencies *** %]
-
-            <tr>
-[%# if WEBKIT_CHANGES %]
-              [% PROCESS dependencies accesskey = "d"
-                         dep = { title => "<u>D</u>epends&nbsp;on", fieldname => "dependson" } %]
-[%# endif // WEBKIT_CHANGES %]
-            </tr>
-
-            <tr>
-              [% PROCESS dependencies accesskey = "b"
-                         dep = { title => "<u>B</u>locks", fieldname => "blocked" } %]
-            </tr>
-
-            <tr>
-              <th>&nbsp;</th>
-
-              <td colspan="2">
-                <a href="showdependencytree.cgi?id=[% bug.bug_id %]&amp;hide_resolved=1">Show
-                   dependency tree</a>
-
-                [% IF Param('webdotbase') %]
-                  - <a href="showdependencygraph.cgi?id=[% bug.bug_id %]">Show
-                       dependency graph</a>
-                [% END %]
-              </td>
-            </tr>
-          </table>
-        </fieldset>
-      </td>
-
-      [%# Force the layout to be displayed now, before drawing the second column of the table.
-        # This should prevent bug 370739 when using Firefox 2. %]
-      <script type="text/javascript">
-        <!--
-        var v = document.body.offsetHeight;
-        //-->
-      </script>
-
-      <td valign="top">
-        [%# *** Reporter, owner and QA contact *** %]
-        <fieldset>
-          <legend>People</legend>
+      [%# 1st Column %]
+      <td id="bz_show_bug_column_1" class="bz_show_bug_column">     
+        <table>
+          [%# *** ID, product, component, status, resolution, Hardware, and  OS *** %]
+          [% PROCESS section_status %]
+          
+          [% PROCESS section_spacer %]
+          
+          [% PROCESS section_details1 %]
+          
+          [% PROCESS section_spacer %]
+          
+          [%# *** severity, priority, version and milestone *** %]
+          [% PROCESS section_details2 %]            
+          
+          [%# *** assigned to and qa contact *** %]
           [% PROCESS section_people %]
-        </fieldset>
+          
+          [% PROCESS section_spacer %]
+          
+          [% PROCESS section_url_keyword_whiteboard %]
+          
+          [% PROCESS section_spacer %]
+          
+          [%# *** Dependencies *** %]
+          [% PROCESS section_dependson_blocks %]
+          
+        </table>
+      </td>
+      <td>
+        <div class="bz_column_spacer">&nbsp;</div>
+      </td>
+      [%# 2nd Column %]
+      <td id="bz_show_bug_column_2" class="bz_show_bug_column">
+        <table cellpadding="3" cellspacing="1">
+        [%# *** Reported and modified dates *** %]
+         [% PROCESS section_dates %]
+         
+         [% PROCESS section_cclist %]
+         
+         [% PROCESS section_spacer %] 
+         
+         [% PROCESS section_customfields %]
+         
+         [% PROCESS section_spacer %]
+         
+         [% Hook.process("after_custom_fields") %]
+                
+         [% PROCESS section_flags %]
 
-        [%# *** Flags *** %]
-        [% show_bug_flags = 0 %]
-        [% FOREACH type = bug.flag_types %]
-          [% IF (type.flags && type.flags.size > 0) || (user.id && type.is_active) %]
-            [% show_bug_flags = 1 %]
-            [% LAST %]
-          [% END %]
-        [% END %]
-        [% IF show_bug_flags %]
-          <fieldset>
-            <legend>Flags</legend>
-            <table cellspacing="1" cellpadding="1">
-              <tr>
-                <td colspan="2" valign="top">
-                  [% 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>
-            </table>
-          </fieldset>
-        [% END %]
+        </table>
+      </td>
+    </tr>
+    <tr>
+      <td colspan="3">
+          <hr id="bz_top_half_spacer">
       </td>
     </tr>
   </table>
 
+  
+  [% PROCESS section_restrict_visibility %]
   [% IF user.in_group(Param('timetrackinggroup')) %]
     <br>
-    <table cellspacing="0" cellpadding="4" border="1">
-      <tr>
-        <th align="center" bgcolor="#cccccc">
-          <label for="estimated_time">Orig. Est.</label>
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          Current Est.
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          <label for="work_time">Hours Worked</label>
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          <label for="remaining_time">Hours Left</label>
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          %Complete
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          Gain
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          <label for="deadline">Deadline</label>
-        </th>
-      </tr>
-      <tr>
-        <td align="center">
-          <input name="estimated_time" id="estimated_time"
-                 value="[% PROCESS formattimeunit
-                                   time_unit=bug.estimated_time %]"
-                 size="6" maxlength="6">
-        </td>
-        <td align="center">
-          [% PROCESS formattimeunit
-                     time_unit=(bug.actual_time + bug.remaining_time) %]
-        </td>
-        <td align="center">
-          [% PROCESS formattimeunit time_unit=bug.actual_time %] +
-          <input name="work_time" id="work_time"
-                 value="0" size="3" maxlength="6"
-                 onchange="adjustRemainingTime();">
-        </td>
-        <td align="center">
-          <input name="remaining_time" id="remaining_time"
-                 value="[% PROCESS formattimeunit
-                                   time_unit=bug.remaining_time %]"
-                 size="6" maxlength="6" onchange="updateRemainingTime();">
-        </td>
-        <td align="center">
-          [% PROCESS calculatepercentage act=bug.actual_time
-                                         rem=bug.remaining_time %]
-        </td>
-        <td align="center">
-          [% PROCESS formattimeunit time_unit=bug.estimated_time - (bug.actual_time + bug.remaining_time) %]
-        </td>
-         <td align="center">
-           <input name="deadline" id="deadline" value="[% bug.deadline %]"
-                  size="10" maxlength="10"><br />
-           <small>(YYYY-MM-DD)</small>
-        </td>        
-      </tr>
-      <tr>
-        <td colspan="7" align="right">
-          <a href="summarize_time.cgi?id=[% bug.bug_id %]&amp;do_depends=1">
-          Summarize time (including time for [% terms.bugs %]
-          blocking this [% terms.bug %])</a>
-        </td>
-      </tr>
-    </table>
+    [% PROCESS section_timetracking %]
   [% END %]
+  
 
 [%# *** Attachments *** %]
 
@@ -397,8 +234,11 @@
   <br>
   <table cellpadding="1" cellspacing="1">
     <tr>
-      <td>
+      <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"
@@ -407,7 +247,6 @@
             <label for="newcommentprivacy">Private</label>
           [% END %]
           <br>
-          <a name="add_comment"></a>
           [% INCLUDE global/textarea.html.tmpl
                      name      = 'comment'
                      id        = 'comment'
@@ -415,208 +254,201 @@
                      maxrows   = 25
                      cols      = constants.COMMENT_COLS
           %]
-
-          [% IF NOT bug.cc || NOT bug.cc.contains(user.login) %]
-            [% has_role = bug.user.isreporter
-                          || bug.assigned_to.id == user.id
-                          || (Param('useqacontact')
-                              && bug.qa_contact
-                              && bug.qa_contact.id == user.id) %]
-
-            <br>
-            <input type="checkbox" id="addselfcc" name="addselfcc"
-              [% " checked=\"checked\""
-                   IF user.settings.state_addselfcc.value == 'always'
-                      || (!has_role
-                          && user.settings.state_addselfcc.value == 'cc_unless_role') %]>
-            <label for="addselfcc">Add [% user.identity FILTER html %] to CC list</label>
-          [% END %]
+          <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="show_bug.cgi?id=[% bug.bug_id %]&amp;GoAheadAndLogIn=1">log in</a>
+              <a href="[% IF Param('ssl') != 'never' %][% Param('sslbase') %][% END %]show_bug.cgi?id=[% bug.bug_id %]&amp;GoAheadAndLogIn=1">log in</a>
               before you can comment on or make changes to this [% terms.bug %].
             </p>
           </fieldset>
         [% END %]
-      </td>
-
-      <td valign="top">
-        <fieldset>
-          <legend>Related actions</legend>
-          <ul>
-            <li><a href="show_activity.cgi?id=[% bug.bug_id %]">View [% terms.Bug %] Activity</a></li>
-            <li><a href="show_bug.cgi?format=multiple&amp;id=[% bug.bug_id %]">Format For Printing</a></li>
-            <li><a href="show_bug.cgi?ctype=xml&amp;id=[% bug.bug_id %]">XML</a></li>
-            <li><a href="enter_bug.cgi?cloned_bug_id=[% bug.bug_id %]">Clone This [% terms.Bug %]</a></li>
-          </ul>
-
-          [% IF bug.use_votes %]
-            <br>
-            <b><a href="page.cgi?id=voting.html">Votes</a></b>: [% bug.votes %]
-            <ul>
-              [% IF bug.votes %]
-                <li><a href="votes.cgi?action=show_bug&amp;bug_id=[% bug.bug_id %]">Show
-                             votes for this [% terms.bug %]</a></li>
-              [% END %]
-              <li><a href="votes.cgi?action=show_user&amp;bug_id=[% bug.bug_id %]#vote_[% bug.bug_id %]">Vote
-                           for this [% terms.bug %]</a></li>
-            </ul>
-          [% END %]
-
-          [%# Links to more things users can do with this bug. %]
-          [% Hook.process("links") %]
-        </fieldset>
+        [%# *** Additional Comments *** %]
+        <hr>
+        <div id="comments">
+        [% PROCESS bug/comments.html.tmpl
+           comments = bug.longdescs
+           mode = user.id ? "edit" : "show"
+         %]
+        </div>
+        
       </td>
     </tr>
   </table>
-  <br>
-
-  [% IF bug.groups.size > 0 %]
-    [% inallgroups = 1 %]
-    [% inagroup = 0 %]
-    [% FOREACH group = bug.groups %]
-      [% SET inallgroups = 0 IF NOT group.ingroup %]
-      [% SET inagroup = 1 IF group.ison %]
-
-      [% IF NOT group.mandatory %]
-        [% IF NOT emitted_description %]
-          [% emitted_description = 1 %]
-          <br>
-          <b>Only users in all of the selected groups can view this [% terms.bug %]:</b>
-          <br>
-          <font size="-1">
-            (Unchecking all boxes makes this a more public [% terms.bug %].)
-          </font>
-          <br>
-          <br>
-        [% END %]
-
-      &nbsp;&nbsp;&nbsp;&nbsp;
-      <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 %]
-    [% END %]
-
-    [% IF NOT inallgroups %]
-      <b>
-        Only members of a group can change the visibility of [% terms.abug %] for
-        that group
-      </b>
-    <br>
-    [% END %]
-
-    [% IF inagroup %]
-      <p>
-        <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>
-      </p>
-
-      <p>
-        <input type="checkbox" value="1"
-               name="reporter_accessible" id="reporter_accessible"
-               [% " checked" IF bug.reporter_accessible %]
-               [% " disabled=\"disabled\"" UNLESS bug.check_can_change_field("reporter_accessible", 0, 1) %]>
-        <label for="reporter_accessible">Reporter</label>
-        <input type="checkbox" value="1"
-               name="cclist_accessible" id="cclist_accessible"
-               [% " checked" IF bug.cclist_accessible %]
-               [% " disabled=\"disabled\"" UNLESS bug.check_can_change_field("cclist_accessible", 0, 1) %]>
-        <label for="cclist_accessible">CC List</label>
-      </p>
-    [% END %]
-  [% END %]
-
-[% PROCESS bug/knob.html.tmpl IF user.id %]
-
-[%# *** Additional Comments *** %]
-
-<hr>
-
-<div id="comments">
-[% PROCESS bug/comments.html.tmpl
-   comments = bug.longdescs
-   mode = user.id ? "edit" : "show"
- %]
-</div>
-
 </form>
 
 [%############################################################################%]
+[%# Block for the Title (alias and short desc)                               #%]
+[%############################################################################%]
+
+[% BLOCK section_title %]
+  [%# That's the main table, which contains all editable fields. %]
+  <div class="bz_alias_short_desc_container">
+    
+     <a href="show_bug.cgi?id=[% bug.bug_id %]">
+        <b>[% terms.Bug %]&nbsp;[% bug.bug_id FILTER html %]</b></a> - 
+     <span id="summary_alias_container" class="bz_default_hidden"> 
+      [% IF Param("usebugaliases") %]
+        [% IF bug.alias != "" %]
+          (<span id="alias_nonedit_display">[% bug.alias FILTER html %]</span>) 
+        [% END %]
+      [% END %]
+      <span id="short_desc_nonedit_display">[% bug.short_desc FILTER html %]</span>
+      [% IF bug.check_can_change_field('short_desc', 0, 1) || 
+            bug.check_can_change_field('alias', 0, 1)  %]
+        <small class="editme">(<a href="#" id="editme_action">edit</a>)</small>
+      [% END %]
+     </span>
+  
+       
+    <div id="summary_alias_input">
+      <table id="summary"> 
+        [% IF Param("usebugaliases") %]
+          <tr>
+          [% IF bug.check_can_change_field('alias', 0, 1) %]      
+            <td>
+              <label 
+                for="alias" 
+                title="a name for the 
+                       [% terms.bug %] that can be used in place of its ID number, 
+                       [%%] e.g. when adding it to a list of dependencies"
+                >Alias</label>:</td><td>
+          [% ELSIF bug.alias %]
+            <td colspan="2">(
+          [% ELSE %]
+            <td colspan="2">
+          [% END %]
+          [% PROCESS input inputname => "alias" 
+                     size => "20" 
+                     maxlength => "20"  
+                     no_td => 1 
+                     %][% ")" IF NOT bug.check_can_change_field('alias', 0, 1) 
+                                  && bug.alias %]
+          </td>
+        </tr>
+        [% END %] 
+        [%# *** Summary *** %]
+        <tr>
+          <td>
+            <label accesskey="s" for="short_desc"><u>S</u>ummary</label>:
+          </td>
+          <td>
+            [% PROCESS input inputname => "short_desc" size => "80" colspan => 2
+                             maxlength => 255 spellcheck => "true" no_td => 1 %]
+          </td>
+        </tr>
+      </table>
+    </div>
+  </div>
+  <script type="text/javascript">
+    hideAliasAndSummary('[% bug.short_desc FILTER js %]', '[% bug.alias FILTER js %]');
+  </script>
+[% END %]
+
+[%############################################################################%]
 [%# Block for the first table in the "Details" section                       #%]
 [%############################################################################%]
 
 [% BLOCK section_details1 %]
-  <table cellspacing="1" cellpadding="1">
-    <tr>
-      <td align="right">
-        [% IF Param('useclassification') && bug.classification_id != 1 %]
-          <b>[[% bug.classification FILTER html %]]</b>
-        [% END %]
-        <b>[% terms.Bug %]#</b>:
-      </td>
-      <td>
-        <a href="[% Param('urlbase') %]show_bug.cgi?id=[% bug.bug_id %]">
-           [% bug.bug_id %]</a>
-      </td>
-    </tr>
 
-    [% IF Param("usebugaliases") %]
-      <tr>
-        <td align="right">
-          <label for="alias" title="a name for the [% terms.bug %] that can be used in place of its ID number, f.e. when adding it to a list of dependencies"><b>Alias</b></label>:
-        </td>
-        [% PROCESS input inputname => "alias" size => "20" maxlength => "20" %]
-      </tr>
-    [% END %]
-
+    [%#############%]
+    [%#  PRODUCT  #%]
+    [%#############%]
     <tr>
-      <td align="right">
+      <td class="field_label">
         <label for="product" accesskey="p"><b><u>P</u>roduct</b></label>:
       </td>
       [% PROCESS select selname => "product" %]
     </tr>
-
+    [%###############%]    
+    [%#  Component  #%]
+    [%###############%]
     <tr>
-      <td align="right">
-        <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 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" %]
     </tr>
-
     <tr>
-      <td align="right">
-        <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
+      <td class="field_label">
+[%# if WEBKIT_CHANGES %]
+        <label for="version"><b><u>V</u>ersion</b></label>:
+[%# endif // WEBKIT_CHANGES %]
       </td>
-      <td>[% status_descs.${bug.bug_status} FILTER html %]</td>
-    </tr>
 
+[%# if WEBKIT_CHANGES %]
+      [% PROCESS select selname => "version" accesskey => "v" %]
+[%# endif // WEBKIT_CHANGES %]
+    </tr>
+    [%############%]    
+    [%# PLATFORM #%]
+    [%############%]    
     <tr>
-      <td align="right">
-        <b><a href="page.cgi?id=fields.html#resolution">Resolution</a></b>:
+      <td class="field_label">
+        <label for="rep_platform" accesskey="h"><b>Platform</b></label>:
       </td>
       <td>
-        [% get_resolution(bug.resolution) FILTER html %]
-        [% IF bug.resolution == "DUPLICATE" %]
-          of [% terms.bug %] [%+ "${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
-        [% END %]
+       [% PROCESS select selname => "rep_platform" no_td=> 1 %]
+       [%+ PROCESS select selname => "op_sys" no_td=> 1 %]
+       <script type="text/javascript">
+         assignToDefaultOnChange(['product', 'component']);
+       </script>
       </td>
     </tr>
-  </table>
+
+
+
+[% END %]
+
+[%############################################################################%]
+[%# Block for the status section                                             #%]
+[%############################################################################%]
+
+[% BLOCK section_status %]
+  <tr>
+    <td class="field_label">
+      <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
+    </td>
+    <td id="bz_field_status">
+      <span id="static_bug_status">
+        [% get_status(bug.bug_status) FILTER html %]
+        [% IF bug.resolution %]
+          [%+ get_resolution(bug.resolution) FILTER html %]
+          [% IF bug.dup_id %]
+            of [% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
+          [% END %]
+        [% END %]
+        [% IF bug.user.canedit || bug.user.isreporter %]
+          (<a href="#add_comment" 
+              onclick="window.setTimeout(function() { document.getElementById('bug_status').focus(); }, 10)">edit</a>)
+        [% END %]
+      </span>
+    </td>
+  </tr>
 [% END %]
 
 [%############################################################################%]
@@ -624,59 +456,50 @@
 [%############################################################################%]
 
 [% BLOCK section_details2 %]
-  <table cellspacing="1" cellpadding="1">
-    <tr>
-      <td align="right">
-        <label for="rep_platform" accesskey="h"><b><u>H</u>ardware</b></label>:
-      </td>
-      [% PROCESS select selname => "rep_platform" %]
-    </tr>
 
+ [%###############################################################%]
+ [%# Importance (priority, severity and votes) #%]
+ [%###############################################################%]
     <tr>
-      <td align="right">
-        <label for="op_sys" accesskey="o"><b><u>O</u>S</b></label>:
-      </td>
-      [% PROCESS select selname => "op_sys" %]
-    </tr>
-
-    <tr>
-      <td align="right">
+      <td class="field_label">
+        <label for="priority" accesskey="i">
 [%# if WEBKIT_CHANGES %]
-        <label for="version"><b><u>V</u>ersion</b></label>:
+          <b><a href="page.cgi?id=fields.html#importance"><u>I</u>mportanc<u>e</u></a></b></label>:
 [%# endif // WEBKIT_CHANGES %]
       </td>
+      <td>
+        [% PROCESS select selname => "priority" no_td=>1 %] 
 [%# if WEBKIT_CHANGES %]
-      [% PROCESS select selname => "version" accesskey => "v" %]
+        [% PROCESS select selname => "bug_severity" no_td=>1 accesskey => "e" %]
 [%# endif // WEBKIT_CHANGES %]
-    </tr>
-
-    <tr>
-      <td align="right">
-        <label for="priority" accesskey="i"><b><a href="page.cgi?id=fields.html#priority">Pr<u>i</u>ority</a></b></label>:
+        [% IF bug.use_votes %]
+          <span id="votes_container">
+          [% IF bug.votes %] 
+            with 
+            <a href="votes.cgi?action=show_bug&amp;bug_id=[% bug.bug_id %]">
+              [% bug.votes %] 
+              [% IF bug.votes == 1 %]
+                vote
+              [% ELSE %]
+                votes
+              [% END %]</a> 
+          [% END %]    
+          (<a href="votes.cgi?action=show_user&amp;bug_id=
+                  [% bug.bug_id %]#vote_[% bug.bug_id %]">vote</a>)
+          </span>  
+        [% END %]
       </td>
-      [% PROCESS select selname => "priority" %]
-    </tr>
-
-    <tr>
-      <td align="right">
-[%# if WEBKIT_CHANGES %]
-        <label for="bug_severity"><b><a href="page.cgi?id=fields.html#bug_severity">S<u>e</u>verity</a></b></label>:
-[%# endif // WEBKIT_CHANGES %]
-      </td>
-[%# if WEBKIT_CHANGES %]
-      [% PROCESS select selname => "bug_severity" accesskey => "e" %]
-[%# endif // WEBKIT_CHANGES %]
     </tr>
 
     [% IF Param("usetargetmilestone") && bug.target_milestone %]
       <tr>
-        <td align="right">
+        <td class="field_label">
           <label for="target_milestone"><b>
             [% IF bug.milestoneurl %]
               <a href="[% bug.milestoneurl FILTER html %]">
             [% END %]
 [%# if WEBKIT_CHANGES %]
-            <u>T</u>arget Milestone[% "</a>" IF bug.milestoneurl %]
+            <u>T</u>arget&nbsp;Milestone[% "</a>" IF bug.milestoneurl %]
 [%# endif // WEBKIT_CHANGES %]
           [%%]</b></label>:
         </td>
@@ -685,7 +508,7 @@
 [%# endif // WEBKIT_CHANGES %]
       </tr>
     [% END %]
-  </table>
+  
 [% END %]
 
 [%############################################################################%]
@@ -693,81 +516,378 @@
 [%############################################################################%]
 
 [% BLOCK section_people %]
-  <table cellpadding="1" cellspacing="1">
-    <tr>
-      <td align="right">
-        <b>Reporter</b>:
-      </td>
-      <td>
-        <a href="mailto:[% bug.reporter.email FILTER html %]">
-          [% bug.reporter.identity FILTER html %]</a>
-      </td>
-    </tr>
 
     <tr>
-      <td align="right">
-        <b><a href="page.cgi?id=fields.html#assigned_to">Assigned&nbsp;To</a></b>:
+      <td class="field_label">
+        <b><a href="page.cgi?id=fields.html#assigned_to">Assigned To</a></b>:
       </td>
       <td>
-        <a href="mailto:[% bug.assigned_to.email FILTER html %]">
-           [% bug.assigned_to.identity FILTER html %]</a>
+        [% 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 %]
+              (<a href="#" id="bz_assignee_edit_action">edit</a>)
+            </span>
+          </div>
+          <div id="bz_assignee_input">
+            [% INCLUDE global/userselect.html.tmpl
+                 id => "assigned_to"
+                 name => "assigned_to"
+                 value => bug.assigned_to.login
+                 size => 30
+            %]
+            <br>
+            <input type="checkbox" id="set_default_assignee" name="set_default_assignee" value="1">
+            <label id="set_default_assignee_label" for="set_default_assignee">Reset Assignee to default</label>
+          </div>
+          <script type="text/javascript">
+           hideEditableField('bz_assignee_edit_container', 
+                             'bz_assignee_input', 
+                             'bz_assignee_edit_action', 
+                             'assigned_to', 
+                             '[% bug.assigned_to.login FILTER js %]' );
+           initDefaultCheckbox('assignee');                  
+          </script>
+        [% ELSE %]
+          [% INCLUDE user_identity user => bug.assigned_to %]
+        [% END %]
       </td>
     </tr>
 
     [% IF Param('useqacontact') %]
     <tr>
-      <td align="right">
+      <td class="field_label">
         <label for="qa_contact" accesskey="q"><b><u>Q</u>A Contact</b></label>:
       </td>
-      <td colspan="7">
+      <td>
+
         [% IF bug.check_can_change_field("qa_contact", 0, 1) %]
-          [% INCLUDE global/userselect.html.tmpl
-              id => "qa_contact"
-              name => "qa_contact"
-              value => bug.qa_contact.login
-              size => 30
-              emptyok => 1
-          %]
-        [% ELSE %]
-          <input type="hidden" name="qa_contact" id="qa_contact"
-                 value="[% bug.qa_contact.login FILTER html %]">
-          <a href="mailto:[% bug.qa_contact.email FILTER html %]">
-            [% IF bug.qa_contact.login && bug.qa_contact.login.length > 30 %]
-              <span title="[% bug.qa_contact.login FILTER html %]">
-                [% bug.qa_contact.identity FILTER truncate(30) FILTER html %]
-              </span>
-            [% ELSE %]
-              [% bug.qa_contact.identity FILTER html %]
+          [% 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>
+              (<a href="#" id="bz_qa_contact_edit_action">edit</a>)
+            </span>
+          </div>
+          [% END %]
+          <div id="bz_qa_contact_input">
+            [% INCLUDE global/userselect.html.tmpl
+                id => "qa_contact"
+                name => "qa_contact"
+                value => bug.qa_contact.login
+                size => 30
+                emptyok => 1
+            %]
+            <br>
+            <input type="checkbox" id="set_default_qa_contact" name="set_default_qa_contact" value="1">
+            <label for="set_default_qa_contact" id="set_default_qa_contact_label">Reset QA Contact to default</label>
+          </div>
+          <script type="text/javascript">
+            [% IF bug.qa_contact != "" %]
+              hideEditableField('bz_qa_contact_edit_container', 
+                                 'bz_qa_contact_input', 
+                                 'bz_qa_contact_edit_action', 
+                                 'qa_contact', 
+                                 '[% bug.qa_contact.login FILTER js %]');
             [% END %]
-          </a>
+            initDefaultCheckbox('qa_contact');
+          </script>
+        [% ELSE %]
+          [% INCLUDE user_identity user => bug.qa_contact %]
         [% END %]
       </td>
     </tr>
     [% END %]
+[% END %]
 
-    [% IF user.id %]
-      <tr>
-        <td align="right" valign="top">
-          <label for="newcc" accesskey="a"><b><u>A</u>dd&nbsp;CC</b></label>:
-        </td>
-        <td>
-           [% INCLUDE global/userselect.html.tmpl
-              id => "newcc"
-              name => "newcc"
-              value => ""
-              size => 30
-              multiple => 5
-            %]
-        </td>
-      </tr>
+[%############################################################################%]
+[%# 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>
+    <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)") %]
+           <a href="[% bug.bug_file_loc FILTER html %]" target="_blank"
+              title="[% bug.bug_file_loc FILTER html %]">
+             [% bug.bug_file_loc FILTER truncate(40) FILTER html %]</a>
+        [% ELSE %]
+          [% bug.bug_file_loc FILTER html %]
+        [% END %]
+        (<a href="#" id="bz_url_edit_action">edit</a>)</span>
+      [% END %]
+      <span id="bz_url_input_area">
+        [% url_output =  PROCESS input no_td=1 inputname => "bug_file_loc" size => "40" colspan => 2 %]
+        [% IF NOT bug.check_can_change_field("bug_file_loc", 0, 1)  %]
+          <a href="[% bug.bug_file_loc FILTER html %]">[% url_output FILTER none %]</a>
+        [% ELSE %]
+          [% url_output FILTER none %]
+        [% END %]
+      </span>
+      [% IF bug.check_can_change_field("bug_file_loc", 0, 1) %]
+        <script type="text/javascript">
+          hideEditableField('bz_url_edit_container', 
+                            'bz_url_input_area', 
+                            'bz_url_edit_action', 
+                            'bug_file_loc', 
+                            "[% bug.bug_file_loc FILTER js %]");
+        </script>
+      [% END %]
+    </td>
+  </tr>
+  
+  [% IF Param('usestatuswhiteboard') %]
+    <tr>
+      <td class="field_label">
+        <label for="status_whiteboard" accesskey="w"><b><u>W</u>hiteboard</b></label>:
+      </td>
+      [% PROCESS input inputname => "status_whiteboard" size => "40" colspan => 2 %]
+    </tr>
+  [% END %]
+  
+  [% IF use_keywords %]
+    <tr>
+      <td class="field_label">
+        <label for="keywords" accesskey="k">
+          <b><a href="describekeywords.cgi"><u>K</u>eywords</a></b></label>:
+      </td>
+      [% PROCESS input inputname => "keywords" size => 40 colspan => 2
+                       value => bug.keywords.join(', ') %]
+    </tr>
+  [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Depends On / Blocks                                              #%]
+[%############################################################################%]
+[% BLOCK section_dependson_blocks %]
+  <tr>
+[%# if WEBKIT_CHANGES %]
+    [% PROCESS dependencies accesskey = "d"
+               dep = { title => "<u>D</u>epends&nbsp;on", fieldname => "dependson" } %]
+[%# endif // WEBKIT_CHANGES %]
+  </tr>
+  
+  <tr>
+    [% PROCESS dependencies accesskey = "b"
+               dep = { title => "<u>B</u>locks", fieldname => "blocked" } %]
+  
+  <tr>
+    <th>&nbsp;</th>
+  
+    <td colspan="2" align="left" id="show_dependency_tree_or_graph">
+      Show dependency <a href="showdependencytree.cgi?id=[% bug.bug_id %]&amp;hide_resolved=1">tree</a>
+  
+      [% IF Param('webdotbase') %]
+        /&nbsp;<a href="showdependencygraph.cgi?id=[% bug.bug_id %]">graph</a>
+      [% END %]
+    </td>
+  </tr>
+[% END %]
+
+
+[%############################################################################%]
+[%# Block for Restricting Visibility                                         #%]
+[%############################################################################%]
+
+[% BLOCK section_restrict_visibility %]
+  [% RETURN UNLESS bug.groups.size %]
+
+  [% inallgroups = 1 %]
+  [% inagroup = 0 %]
+  [% emitted_description = 0 %]
+
+  [% FOREACH group = bug.groups %]
+    [% SET inallgroups = 0 IF NOT group.ingroup %]
+    [% SET inagroup = 1 IF group.ison %]
+
+    [% NEXT IF group.mandatory %]
+
+    [% IF NOT emitted_description %]
+      [% emitted_description = 1 %]
+      <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 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 emitted_description %]
+    [% IF NOT inallgroups %]
+      <b>Only members of a group can change the visibility of [% terms.abug %] for that group.</b>
+      <br>
+    [% END %]
+      </td>
+    </tr>
+    [% "</table>" IF NOT inagroup %]
+  [% END %]
+
+  [% IF inagroup %]
+    [% IF NOT emitted_description %]
+      [% emitted_description = 1 %]
+      <table>
+    [% END %]
     <tr>
-      [% IF bug.cc %]
-        <td align="right" valign="top">
-          <label for="cc"><b>CC</b></label>:
+      <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>
+        </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>
+        </div>
+      </td>
+    </tr>
+  </table>
+  [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Dates                                                          #%]
+[%############################################################################%]
+
+[% BLOCK section_dates %]
+  <tr>
+    <td class="field_label">
+      <b>Reported</b>:
+    </td>
+    <td>
+     [% bug.creation_ts FILTER time %] by [% INCLUDE user_identity user => bug.reporter %]
+    </td>
+  </tr>
+  
+  <tr>
+    <td class="field_label">
+      <b> Modified</b>:
+    </td>
+    <td>
+      [% bug.delta_ts FILTER time FILTER replace(':\d\d$', '') FILTER replace(':\d\d ', ' ')%] 
+      (<a href="show_activity.cgi?id=[% bug.bug_id %]">[%# terms.Bug %]History</a>)
+    </td>
+  
+  </tr>
+[% END %]
+
+[%############################################################################%]
+[%# Block for CC LIST                                                        #%]
+[%############################################################################%]
+[% BLOCK section_cclist %]
+  [% IF user.id %]
+    <tr>
+        <td class="field_label">
+          <label for="newcc" accesskey="a"><b>CC List</b>:</label>
         </td>
-        <td valign="top">
+      <td>
+        [% IF user.id %]
+          [% IF NOT bug.cc || NOT bug.cc.contains(user.login) %]
+            [% has_role = bug.user.isreporter
+                          || bug.assigned_to.id == user.id
+                          || (Param('useqacontact')
+                              && bug.qa_contact
+                              && bug.qa_contact.id == user.id) %]
+            <input type="checkbox" id="addselfcc" name="addselfcc"
+              [% " checked=\"checked\""
+                   IF user.settings.state_addselfcc.value == 'always'
+                      || (!has_role
+                          && user.settings.state_addselfcc.value == 'cc_unless_role') %]>
+            <label for="addselfcc">Add me to CC list</label>
+            <br> 
+          [% END %]
+        [% END %]
+        [% bug.cc.size || 0  FILTER html %] 
+        [% IF bug.cc.size == 1 %]
+          user
+        [% ELSE %]
+          users
+        [% END %]
+        [% IF user.id %]
+          [% IF bug.cc.contains( user.email ) %]
+            including you
+          [% END %]
+        [% END %]
+        <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>
@@ -779,47 +899,216 @@
             [%%]<label for="removecc">Remove selected CCs</label>
             <br>
           [% END %]
-        </td>
-      [% ELSE %]
-        <td colspan="2"><input type="hidden" name="cc" value=""></td>
-      [% END %]
+        [% END %]
+        </div>
+        <script type="text/javascript">
+          hideEditableField( 'cc_edit_area_showhide_container', 
+                             'cc_edit_area', 
+                             'cc_edit_area_showhide', 
+                             '', 
+                             '');  
+        </script>
+      </td>
     </tr>
-  </table>
+  [% END %]
 [% END %]
 
 [%############################################################################%]
+[%# Block for FLAGS                                                          #%]
+[%############################################################################%]
+
+[% BLOCK section_flags %]
+  [%# *** Flags *** %]
+  [% show_bug_flags = 0 %]
+  [% FOREACH type = bug.flag_types %]
+    [% IF (type.flags && type.flags.size > 0) || (user.id && type.is_active) %]
+      [% show_bug_flags = 1 %]
+      [% LAST %]
+    [% END %]
+  [% END %]
+  [% IF show_bug_flags %]
+    <tr>
+      <td class="field_label">
+        <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 %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Custom Fields                                                  #%]
+[%############################################################################%]
+
+[% BLOCK section_customfields %]
+[%# *** Custom Fields *** %]
+
+  [% USE Bugzilla %]
+  [% FOREACH field = Bugzilla.active_custom_fields %]
+    <tr>
+      [% PROCESS bug/field.html.tmpl value=bug.${field.name}
+                                     editable = bug.check_can_change_field(field.name, 0, 1)
+                                     value_span = 2 %]
+    </tr>
+  [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Section Spacer                                                 #%]
+[%############################################################################%]
+
+[% BLOCK section_spacer %]
+  <tr>
+    <td colspan="2" class="bz_section_spacer"></td>
+  </tr>
+[% END %]
+
+
+
+
+[%############################################################################%]
 [%# Block for dependencies                                                   #%]
 [%############################################################################%]
 
 [% BLOCK dependencies %]
-  <th align="right">
+
+  <th class="field_label">
     <label for="[% dep.fieldname %]"[% " accesskey=\"$accesskey\"" IF accesskey %]>
     [% dep.title %]</label>:
   </th>
-  <td>
-  [% FOREACH depbug = bug.${dep.fieldname} %]
-    [% depbug FILTER bug_link(depbug) FILTER none %][% " " %]
-  [% END %]
-  </td>
-  <td>
+  <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(', ') %]">
+      [% END %]
+    </span>
+    
+    [% FOREACH depbug = bug.${dep.fieldname} %]
+      [% depbug FILTER bug_link(depbug) FILTER none %][% " " %]
+    [% END %]
     [% IF bug.check_can_change_field(dep.fieldname, 0, 1) %]
-      <input name="[% dep.fieldname %]" id="[% dep.fieldname %]"
-             value="[% bug.${dep.fieldname}.join(', ') %]">
-    [% ELSE %]
-      <input type="hidden" id="[% dep.fieldname %]" name="[% dep.fieldname %]"
-             value="[% bug.${dep.fieldname}.join(', ') %]">
+      <span id="[% dep.fieldname %]_edit_container" class="edit_me bz_default_hidden" >
+        (<a href="#" id="[% dep.fieldname %]_edit_action">edit</a>)
+      </span>
+      <script type="text/javascript">
+        hideEditableField('[% dep.fieldname %]_edit_container', 
+                          '[% dep.fieldname %]_input_area', 
+                          '[% dep.fieldname %]_edit_action', 
+                          '[% dep.fieldname %]', 
+                          "[% bug.${dep.fieldname}.join(', ') %]");
+      </script>
     [% END %]
   </td>
+  
   [% accesskey = undef %]
+  
 [% END %]
 
+[%############################################################################%]
+[%# Block for Time Tracking Group                                            #%]
+[%############################################################################%]
+
+[% BLOCK section_timetracking %]
+  <table class="bz_time_tracking_table">
+    <tr>
+      <th>
+        <label for="estimated_time">Orig. Est.</label>
+      </th>
+      <th>
+        Current Est.
+      </th>
+      <th>
+        <label for="work_time">Hours Worked</label>
+      </th>
+      <th>
+        <label for="remaining_time">Hours Left</label>
+      </th>
+      <th>
+        %Complete
+      </th>
+      <th>
+        Gain
+      </th>
+      <th>
+        <label for="deadline">Deadline</label>
+      </th>
+    </tr>
+    <tr>
+      <td>
+        <input name="estimated_time" id="estimated_time"
+               value="[% PROCESS formattimeunit
+                                 time_unit=bug.estimated_time %]"
+               size="6" maxlength="6">
+      </td>
+      <td>
+        [% PROCESS formattimeunit
+                   time_unit=(bug.actual_time + bug.remaining_time) %]
+      </td>
+      <td>
+        [% PROCESS formattimeunit time_unit=bug.actual_time %] +
+        <input name="work_time" id="work_time"
+               value="0" size="3" maxlength="6"
+               onchange="adjustRemainingTime();">
+      </td>
+      <td>
+        <input name="remaining_time" id="remaining_time"
+               value="[% PROCESS formattimeunit
+                                 time_unit=bug.remaining_time %]"
+               size="6" maxlength="6" onchange="updateRemainingTime();">
+      </td>
+      <td>
+        [% PROCESS calculatepercentage act=bug.actual_time
+                                       rem=bug.remaining_time %]
+      </td>
+      <td>
+        [% PROCESS formattimeunit time_unit=bug.estimated_time - (bug.actual_time + bug.remaining_time) %]
+      </td>
+       <td>
+         <input name="deadline" id="deadline" value="[% bug.deadline %]"
+                size="10" maxlength="10"><br />
+         <small>(YYYY-MM-DD)</small>
+      </td>        
+    </tr>
+    <tr>
+      <td colspan="7" class="bz_summarize_time">
+        <a href="summarize_time.cgi?id=[% bug.bug_id %]&amp;do_depends=1">
+        Summarize time (including time for [% terms.bugs %]
+        blocking this [% terms.bug %])</a>
+      </td>
+    </tr>
+  </table> 
+[% END %]
 
 [%############################################################################%]
 [%# Block for 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 %]
       <select id="[% selname %]" name="[% selname %]">
         [% FOREACH x = bug.choices.${selname} %]
@@ -829,10 +1118,12 @@
         [% END %]
       </select>
     [% ELSE %]
-      <input type="hidden" id="[% selname %]" name="[% selname %]" value="[% bug.${selname} FILTER html %]">
       [% bug.${selname} FILTER html %]
     [% END %]
+  [% IF NOT no_td %]
   </td>
+  [% END %]
+  [% no_td = 0 %]
 [% END %]
 
 [%############################################################################%]
@@ -840,15 +1131,16 @@
 [%############################################################################%]
 
 [% BLOCK input %]
+  [% IF no_td != 1 %]
   <td[% " colspan=\"$colspan\"" IF colspan %]>
+  [% END %]
     [% val = value ? value : bug.$inputname %]
     [% IF bug.check_can_change_field(inputname, 0, 1) %]
        <input id="[% inputname %]" name="[% inputname %]"
               value="[% val FILTER html %]"[% " size=\"$size\"" IF size %]
-              [% " maxlength=\"$maxlength\"" IF maxlength %]>
+              [% " maxlength=\"$maxlength\"" IF maxlength %]
+              [% " spellcheck=\"$spellcheck\"" IF spellcheck %]>
     [% ELSE %]
-       <input type="hidden" name="[% inputname %]" id="[% inputname %]"
-              value="[% val FILTER html %]">
       [% IF size && val.length > size %]
         <span title="[% val FILTER html %]">
           [% val FILTER truncate(size) FILTER html %]
@@ -857,9 +1149,33 @@
         [% val FILTER html %]
       [% END %]
     [% END %]
+  [% IF no_td != 1 %]  
   </td>
+  [% END %]
+  [% no_td = 0 %]
   [% maxlength = 0 %]
   [% colspan = 0 %]
   [% size = 0 %]
   [% value = undef %]
+  [% spellcheck = undef %]
 [% END %]
+
+[%############################################################################%]
+[%# Block 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>
+[% END %]
+
diff --git a/BugsSite/template/en/custom/bug/navigate.html.tmpl b/BugsSite/template/en/custom/bug/navigate.html.tmpl
index 4d98a50..93feae6 100644
--- a/BugsSite/template/en/custom/bug/navigate.html.tmpl
+++ b/BugsSite/template/en/custom/bug/navigate.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -20,9 +19,23 @@
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
+[% IF bottom_navigator == 1 %]
+  <ul class="related_actions">
+    <li><a href="show_bug.cgi?format=multiple&amp;id=
+                [% bug.bug_id FILTER url_quote %]">Format For Printing</a></li>
+    <li>&nbsp;-&nbsp;<a href="show_bug.cgi?ctype=xml&amp;id=
+                        [% bug.bug_id  FILTER url_quote %]">XML</a></li>
+    <li>&nbsp;-&nbsp;<a href="enter_bug.cgi?cloned_bug_id=
+                        [% bug.bug_id  FILTER url_quote %]">Clone This 
+                        [% terms.Bug %]</a></li>
+    [%# Links to more things users can do with this bug. %]
+    [% Hook.process("links") %]
+    <li>&nbsp;-&nbsp;<a href="#">Top of page </a></li>
+    </ul>
+[% END %]        
 
-<div id="navigate">
 
+<div class="navigation">
 [% IF bug_list && bug_list.size > 0 %]
   [% this_bug_idx = lsearch(bug_list, bug.bug_id) %]
   <b>[% terms.Bug %] List:</b>
@@ -31,24 +44,34 @@
   [% END %]
 
 [% IF this_bug_idx != -1 %]
+[%# if WEBKIT_CHANGES %]
   <a href="show_bug.cgi?id=[% bug_list.first %]">|&laquo; First</a>
   <a href="show_bug.cgi?id=[% bug_list.last %]">Last &raquo;|</a>
+[%# endif // WEBKIT_CHANGES %]
 [% END %]
 
   [% IF bug.bug_id %]
     [% IF this_bug_idx != -1 %]
       [% IF this_bug_idx > 0 %]
         [% prev_bug = this_bug_idx - 1 %]
+[%# if WEBKIT_CHANGES %]
         <a href="show_bug.cgi?id=[% bug_list.$prev_bug %]">&laquo; Prev</a>
+[%# endif // WEBKIT_CHANGES %]
       [% ELSE %]
+[%# if WEBKIT_CHANGES %]
         <i><font color="#777777">&laquo; Prev</font></i>
+[%# endif // WEBKIT_CHANGES %]
       [% END %]
 
       [% IF this_bug_idx + 1 < bug_list.size %]
         [% next_bug = this_bug_idx + 1 %]
+[%# if WEBKIT_CHANGES %]
         <a href="show_bug.cgi?id=[% bug_list.$next_bug %]">Next &raquo;</a>
+[%# endif // WEBKIT_CHANGES %]
       [% ELSE %]
+[%# if WEBKIT_CHANGES %]
         <i><font color="#777777">Next &raquo;</font></i>
+[%# endif // WEBKIT_CHANGES %]
       [% END %]
     [% ELSE %]
       (This [% terms.bug %] is not in your last search results)
@@ -61,12 +84,13 @@
 [% 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">|&laquo; First</font></i>
   <i><font color="#777777">Last &raquo;|</font></i>
   <i><font color="#777777">&laquo; Prev</font></i>
   <i><font color="#777777">Next &raquo;</font></i>
+[%# endif // WEBKIT_CHANGES %]
   &nbsp;&nbsp;
   <i><font color="#777777">No search results available</font></i>
 [% END %]
-
 </div>
diff --git a/BugsSite/template/en/custom/bug/show.html.tmpl b/BugsSite/template/en/custom/bug/show.html.tmpl
deleted file mode 100644
index e3f8198..0000000
--- a/BugsSite/template/en/custom/bug/show.html.tmpl
+++ /dev/null
@@ -1,67 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gervase Markham <gerv@gerv.net>
-  #                 Vaskin Kissoyan <vkissoyan@yahoo.com>
-  #                 Bradley Baetz <bbaetz@student.usyd.edu.au>
-  #                 Max Kanat-Alexander <mkanat@bugzilla.org>
-  #%]
-
-[% PROCESS global/variables.none.tmpl %]
-
-[%# This script/template only handles one bug #%]
-[% bug = bugs.0 %]
-
-[% IF !header_done %]
-  [% filtered_desc = bug.short_desc FILTER html %]
-  [% filtered_timestamp = bug.delta_ts FILTER time %]
-  [% PROCESS global/header.html.tmpl
-    title = "$terms.Bug $bug.bug_id &ndash; $filtered_desc"
-    header = ""
-    subheader = "$terms.Bug $bug.bug_id: $filtered_desc"
-    header_addl_info = "Modified: $filtered_timestamp"
-    bodyclasses = ['bz_bug',
-                   "bz_status_$bug.bug_status",
-                   "bz_component_$bug.component",
-                   "bz_bug_$bug.bug_id"
-                  ]
-  %]
-[% END %]
-
-[% IF nextbug %]
-  <hr>
-  <p>
-    The next [% terms.bug %] in your list is [% terms.bug %]
-    <a href="show_bug.cgi?id=[% bug.bug_id %]">[% bug.bug_id %]</a>:
-  </p>
-  <hr>
-[% END %]
-
-[% PROCESS bug/navigate.html.tmpl %]
-
-<hr>
-
-[% PROCESS bug/edit.html.tmpl %]
-
-<hr>
-
-[% PROCESS bug/navigate.html.tmpl %]
-
-<br>
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/custom/global/header.html.tmpl b/BugsSite/template/en/custom/global/header.html.tmpl
index e3fedc2..66c24bb 100644
--- a/BugsSite/template/en/custom/global/header.html.tmpl
+++ b/BugsSite/template/en/custom/global/header.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -35,7 +34,7 @@
   # style: string. CSS style.
   # style_urls: list. List of URLs to CSS style sheets.
   # message: string. A message to display to the user. May contain HTML.
-  # rsslink: RSS link URL, May contain HTML
+  # rsslink: RSS link URL, May contain HTML (WEBKIT_CHANGES)
   #%]
 
 [% IF message %]
@@ -70,20 +69,6 @@
 
     [% PROCESS 'global/setting-descs.none.tmpl' %]
 
-    [% IF javascript %]
-      <script type="text/javascript">
-        [% javascript %]
-      </script>
-    [% END %]
-    
-    [% IF javascript_urls %]
-      [% FOREACH javascript_url = javascript_urls %]
-        <script src="[% javascript_url FILTER html %]" type="text/javascript"></script>
-      [% END %]
-    [% END %]
-
-    [%+ INCLUDE "global/help-header.html.tmpl" %]
-
     [%# Set up the skin CSS cascade:
       #  1. Standard Bugzilla stylesheet set (persistent)
       #  2. Standard Bugzilla stylesheet set (selectable)
@@ -203,16 +188,40 @@
             type="text/css">
     <![endif]-->
 
+
+    [% IF javascript %]
+      <script type="text/javascript">
+        [% javascript %]
+      </script>
+    [% 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 %]
+    [% 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 %]
 
     [%# Required for the 'Autodiscovery' feature in Firefox 2 and IE 7. %]
     <link rel="search" type="application/opensearchdescription+xml"
                        title="[% terms.Bugzilla %]" href="./search_plugin.cgi">
+    <link rel="shortcut icon" href="images/favicon.ico" >
+    [% Hook.process("additional_header") %]
   </head>
 
 [%# Migration note: contents of the old Param 'bodyhtml' go in the body tag,
@@ -220,7 +229,7 @@
   #%]
 
   <body onload="[% onload %]"
-        class="[% Param('urlbase').replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') %]
+        class="[% urlbase.replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') %]
                [% FOREACH class = bodyclasses %]
                  [% ' ' %][% class FILTER css_class_quote %]
                [% END %]">
@@ -232,30 +241,28 @@
 
 <div id="header">
 
-[% INCLUDE global/banner.html.tmpl %]
+[%# INCLUDE global/banner.html.tmpl #%]
 
 <table border="0" cellspacing="0" cellpadding="0" id="titles">
 <tr>
     <td id="title">
-      <p>[% terms.Bugzilla %]
-      [% " &ndash; $header" IF header %]</p>
+      <p>WebKit [% terms.Bugzilla %]
+[%# if WEBKIT_CHANGES %]
+      [%# " &ndash; $header" IF header %]</p>
+[%# endif // WEBKIT_CHANGES %]
     </td>
 
-  [% IF subheader %]
-    <td id="subtitle">
-      <p class="subheader">[% subheader %]</p>
-    </td>
-  [% END %]
-
-  [% IF header_addl_info %]
-    <td id="information">
-      <p class="header_addl_info">[% header_addl_info %]</p>
-    </td>
-  [% END %]
+[%# WEBKIT_CHANGES: Removed subheader and header_addl_info output %]
 </tr>
 </table>
 
-[% PROCESS "global/common-links.html.tmpl" btn_id = "find_top" %]
+[%# if WEBKIT_CHANGES %]
+  [% IF header || subheader %]
+    <div id="bug_title">[% header %][% ": $subheader" IF subheader %]</div>
+  [% END %]
+[%# endif // WEBKIT_CHANGES %]
+
+[% PROCESS "global/common-links.html.tmpl" qs_suffix = "_top" %]
 
 </div>
 
diff --git a/BugsSite/template/en/custom/list/list.html.tmpl b/BugsSite/template/en/custom/list/list.html.tmpl
index 984bc94..9b1a9a4 100644
--- a/BugsSite/template/en/custom/list/list.html.tmpl
+++ b/BugsSite/template/en/custom/list/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -36,46 +35,54 @@
   [% title = title _ ": " _ (searchname OR defaultsavename) FILTER html %]
 [% END %]
 
-[% style_urls = [ "skins/standard/buglist.css" ] %]
 [% qorder = order FILTER url_quote IF order %]
-[% message = "buglist_sorted_by_relevance" IF sorted_by_relevance %]
 
 
 [%############################################################################%]
 [%# Page Header                                                              #%]
 [%############################################################################%]
 
+[%# 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" ]
+  doc_section = "query.html#list"
 %]
+[%# endif // WEBKIT_CHANGES %]
 
-<div align="center">
-  [% 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 %]
+<div class="bz_query_head" align="center">
+  <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 %]
+  </span>
 
   [% IF debug %]
-    <p>
+    <p class="bz_query_debug">
       [% FOREACH debugline = debugdata %]
         [% debugline FILTER html %]<br>
       [% END %]
     </p>
-    <p>[% query FILTER html %]</p>
+    <p class="bz_query">[% query FILTER html %]</p>
   [% 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." %]
-    <a href="quips.cgi"><i>[% quip FILTER html %]</i></a>
+    <span class="bz_quip">
+      <a href="quips.cgi"><i>[% quip FILTER html %]</i></a>
+    </span>
   [% END %]
 
 </div>
 
 [% IF toolong %]
-  <h2>
+  <h2 class="bz_smallminded">
     This list is too long for [% terms.Bugzilla %]'s little mind; the
     Next/Prev/First/Last buttons won't appear on individual [% terms.bugs %].
   </h2>
@@ -88,7 +95,9 @@
 [%############################################################################%]
 
 [% IF bugs.size > 9 %]
-  [% bugs.size %] [%+ terms.bugs %] found.
+  <span class="bz_result_count">
+    [% bugs.size %] [%+ terms.bugs %] found.
+  </span>
 [% END %]
 
 [%############################################################################%]
@@ -110,13 +119,15 @@
 [%# Succeeding Status Line                                                   #%]
 [%############################################################################%]
 
-[% IF bugs.size == 0 %]
-  [% terms.zeroSearchResults %].
-[% ELSIF bugs.size == 1 %]
-  One [% terms.bug %] found.
-[% ELSE %]
-  [% bugs.size %] [%+ terms.bugs %] found.
-[% END %]
+<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>
 
 <br>
 
@@ -137,7 +148,7 @@
 <table>
   <tr>
     [% IF bugs.size > 0 %]
-      <td valign="middle">
+      <td valign="middle" class="bz_query_buttons">
         <form method="post" action="show_bug.cgi">
           [% FOREACH id = buglist %]
             <input type="hidden" name="id" value="[% id FILTER html %]">
@@ -164,12 +175,14 @@
       
       <td>&nbsp;</td>
       
-      <td valign="middle">
+      <td valign="middle" class="bz_query_links">
         <a href="buglist.cgi?
         [% urlquerypart FILTER html %]&amp;ctype=csv">CSV</a> |
         <a href="buglist.cgi?
         [% urlquerypart FILTER html %]&amp;title=
+[%# if WEBKIT_CHANGES %]
         [%- title FILTER html %]&amp;ctype=rss">RSS</a> |
+[%# endif // WEBKIT_CHANGES %]
         <a href="buglist.cgi?
         [% urlquerypart FILTER html %]&amp;ctype=ics">iCalendar</a> |
         <a href="colchange.cgi?
@@ -193,7 +206,7 @@
       </td>
     [% END %]
     
-    <td valign="middle">
+    <td valign="middle" class="bz_query_edit">
       [% editqueryname = searchname OR defaultsavename OR '' %]
       <a href="query.cgi?[% urlquerypart FILTER html %]
       [% IF editqueryname != '' %]&amp;known_name=
@@ -202,16 +215,17 @@
     </td>
       
     [% IF searchtype == "saved" %]
-      <td valign="middle" nowrap="nowrap">
+      <td valign="middle" nowrap="nowrap" class="bz_query_forget">
         |
         <a href="buglist.cgi?cmdtype=dorem&amp;remaction=forget&amp;namedcmd=
-                [% searchname FILTER url_quote %]">Forget&nbsp;Search&nbsp;'
-                [% searchname FILTER html %]'</a>
+                [% searchname FILTER url_quote %]&amp;token=
+                [% issue_hash_token([search_id, searchname]) FILTER url_quote %]">
+          Forget&nbsp;Search&nbsp;'[% searchname FILTER html %]'</a>
       </td>
     [% ELSE %]
       <td>&nbsp;</td>
       
-      <td valign="middle">
+      <td valign="middle" class="bz_query_remember">
         <form method="get" action="buglist.cgi">
           <input type="submit" id="remember" value="Remember search"> as 
           <input type="hidden" name="newquery" 
@@ -227,7 +241,7 @@
 </table>
 
 [% IF cgi.param('product').size == 1 && cgi.param('product') != "" %]
-  <p>
+  <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>
   </p>
diff --git a/BugsSite/template/en/custom/request/queue.html.tmpl b/BugsSite/template/en/custom/request/queue.html.tmpl
index c235b93..7a14852 100644
--- a/BugsSite/template/en/custom/request/queue.html.tmpl
+++ b/BugsSite/template/en/custom/request/queue.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -32,8 +31,8 @@
     table.requests th { text-align: left; }
     table#filtering th { text-align: right; }
   "
-  onload="selectProduct(document.forms[0], 'product', 'component', 'Any');"
-  javascript_urls=["productmenu.js"]
+  onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, 'Any');"
+  javascript_urls=["js/productform.js"]
 %]
 
 <p>
@@ -53,7 +52,7 @@
            title="Requester's email address"></td>
       <th>Product:</th>
       <td>
-        <select name="product" onchange="selectProduct(this.form, 'product', 'component', 'Any');">
+        <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 %]"
@@ -90,11 +89,9 @@
       <td>
         <select name="component">
           <option value="">Any</option>
-          [% FOREACH prod = products %]
-            [% FOREACH comp = prod.components %]
-              <option value="[% comp.name FILTER html %]" [% "selected" IF cgi.param('component') == comp.name %]>
-                [% comp.name FILTER html %]</option>
-            [% END %]
+          [% FOREACH comp = components %]
+            <option value="[% comp FILTER html %]" [% "selected" IF cgi.param('component') == comp %]>
+              [% comp FILTER html %]</option>
           [% END %]
         </select>
       </td>
@@ -185,7 +182,9 @@
 
 [% BLOCK display_attachment %]
   [% IF request.attach_id %]
+[%# if WEBKIT_CHANGES %]
     <a href="attachment.cgi?id=[% request.attach_id %]&amp;action=review">
+[%# endif // WEBKIT_CHANGES %]
       [% request.attach_id %]: [%+ request.attach_summary FILTER html %]</a>
   [% ELSE %]
     N/A
diff --git a/BugsSite/template/en/default/account/auth/login-small.html.tmpl b/BugsSite/template/en/default/account/auth/login-small.html.tmpl
index bb0f7a4..19aaca1 100644
--- a/BugsSite/template/en/default/account/auth/login-small.html.tmpl
+++ b/BugsSite/template/en/default/account/auth/login-small.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -56,7 +55,7 @@
         <th>&nbsp;</th>
         <td>
           <input type="checkbox" id="Bugzilla_remember" name="Bugzilla_remember" value="on"
-                 [% "checked" IF Param('rememberlogin') == "defaulton" %]>
+                 [%+ "checked" IF Param('rememberlogin') == "defaulton" %]>
           <label for="Bugzilla_remember">Remember my Login</label>
         </td>
       </tr>
diff --git a/BugsSite/template/en/default/account/auth/login.html.tmpl b/BugsSite/template/en/default/account/auth/login.html.tmpl
index 55ec04c..e8f8fa1 100644
--- a/BugsSite/template/en/default/account/auth/login.html.tmpl
+++ b/BugsSite/template/en/default/account/auth/login.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -64,7 +63,7 @@
         <th>&nbsp;</th>
         <td>
           <input type="checkbox" id="Bugzilla_remember" name="Bugzilla_remember" value="on"
-                 [% "checked" IF Param('rememberlogin') == "defaulton" %]>
+                 [%+ "checked" IF Param('rememberlogin') == "defaulton" %]>
           <label for="Bugzilla_remember">Remember my Login</label>
         </td>
       </tr>
diff --git a/BugsSite/template/en/default/account/cancel-token.txt.tmpl b/BugsSite/template/en/default/account/cancel-token.txt.tmpl
index db54663..155c441 100644
--- a/BugsSite/template/en/default/account/cancel-token.txt.tmpl
+++ b/BugsSite/template/en/default/account/cancel-token.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -33,7 +32,7 @@
 mistake or someone attempting to break into your [% terms.Bugzilla %] account.
 
 Take a look at the information below and forward this email
-to [% maintainer %] if you suspect foul play.
+to [% Param('maintainer') %] if you suspect foul play.
 
             Token: [% token %]
        Token Type: [% tokentype %]
@@ -58,24 +57,24 @@
   [% IF    cancelaction == 'account_exists' %]
     Account [% email %] already exists.
 
-  [% ELSIF cancelaction == 'email_change_cancelled' %]
+  [% ELSIF cancelaction == 'email_change_canceled' %]
     The request to change the email address for
     the [% old_email %] account to [% new_email %] has
     been canceled.
 
-  [% ELSIF cancelaction == 'email_change_cancelled_reinstated' %]
+  [% ELSIF cancelaction == 'email_change_canceled_reinstated' %]
     The request to change the email address for your account
     to [% new_email %] has been canceled. Your old account
     settings have been reinstated.
 
-  [% ELSIF cancelaction == 'emailold_change_cancelled' %]
+  [% ELSIF cancelaction == 'emailold_change_canceled' %]
     The request to change the email address for your account
     to [% new_email %] has been canceled.
 
-  [% ELSIF cancelaction == 'password_change_cancelled' %]
+  [% ELSIF cancelaction == 'password_change_canceled' %]
     You have requested cancellation.
 
-  [% ELSIF cancelaction == 'account_creation_cancelled' %]
+  [% ELSIF cancelaction == 'account_creation_canceled' %]
     The creation of the user account [% emailaddress %]
     has been canceled.
 
diff --git a/BugsSite/template/en/default/account/create.html.tmpl b/BugsSite/template/en/default/account/create.html.tmpl
index 2e8739b..db5df1b 100644
--- a/BugsSite/template/en/default/account/create.html.tmpl
+++ b/BugsSite/template/en/default/account/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -38,19 +37,34 @@
 
 <p>
   To create a [% terms.Bugzilla %] account, all you need to do is to enter
-  a legitimate e-mail address. You will receive an email at this address
-  to confirm the creation of your account. <b>You will not be able to log
-  in until you receive the email.</b> If it doesn't arrive within a
-  reasonable amount of time, you can contact the maintainer of
-  this [% terms.Bugzilla %] installation
+[% IF Param('emailsuffix') == '' %]
+  a legitimate email address.
+[% ELSE %]
+  an accountname which when combined with [% Param('emailsuffix') %]
+  corresponds to an address where you receive email.
+[% END %]
+  You will receive an email at this address to confirm the creation of your
+  account. <b>You will not be able to log in until you receive the email.</b>
+  If it doesn't arrive within a reasonable amount of time, you may contact
+  the maintainer of this [% terms.Bugzilla %] installation
   at <a href="mailto:[% Param("maintainer") %]">[% Param("maintainer") %]</a>.
 </p>
 
+[% IF Param('createemailregexp') == '.*' && Param('emailsuffix') == '' %]
+<p>
+  <b>PRIVACY NOTICE:</b> [% terms.Bugzilla %] is an open [% terms.bug %]
+  tracking system. Activity on most [% terms.bugs %], including email
+  addresses, will be visible to the public. We <b>recommend</b> using a
+  secondary account or free web email service (such as Gmail, Yahoo,
+  Hotmail, or similar) to avoid receiving spam at your primary email address.
+</p>
+[% END %]
+
 <form id="account_creation_form" method="get" action="createaccount.cgi">
   <table>
     <tr>
       <td align="right">
-        <b>E-mail address:</b>
+        <b>Email address:</b>
       </td>
       <td>
         <input size="35" id="login" name="login">
diff --git a/BugsSite/template/en/default/account/created.html.tmpl b/BugsSite/template/en/default/account/created.html.tmpl
index 58064f2..d794198 100644
--- a/BugsSite/template/en/default/account/created.html.tmpl
+++ b/BugsSite/template/en/default/account/created.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -33,10 +32,9 @@
 [% PROCESS global/header.html.tmpl title = title %]
 
 <p>
-  To confirm the creation of the user account <tt>[% login FILTER html %]</tt>,
-  use the URL given in the email you will receive. If you take no action in the
-  next [% constants.MAX_TOKEN_AGE FILTER html %] days, this request will
-  automatically be canceled.
+  A confirmation email has been sent containing a link to continue
+  creating an account. The link will expire if an account is not
+  created within [% constants.MAX_TOKEN_AGE FILTER html %] days.
 </p>
 
 [% PROCESS global/footer.html.tmpl %] 
diff --git a/BugsSite/template/en/default/account/email/change-new.txt.tmpl b/BugsSite/template/en/default/account/email/change-new.txt.tmpl
index d3494e6..e7f32e8 100644
--- a/BugsSite/template/en/default/account/email/change-new.txt.tmpl
+++ b/BugsSite/template/en/default/account/email/change-new.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -32,12 +31,12 @@
 
 To confirm the change, visit the following link:
 
-[%+ Param('urlbase') %]token.cgi?t=[% token FILTER url_quote %]&a=cfmem
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cfmem
 
 If you are not the person who made this request, or you wish to cancel
 this request, visit the following link:
 
-[%+ Param('urlbase') %]token.cgi?t=[% token FILTER url_quote %]&a=cxlem
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&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) %]).
diff --git a/BugsSite/template/en/default/account/email/change-old.txt.tmpl b/BugsSite/template/en/default/account/email/change-old.txt.tmpl
index b50cbfa..cf00ebb 100644
--- a/BugsSite/template/en/default/account/email/change-old.txt.tmpl
+++ b/BugsSite/template/en/default/account/email/change-old.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -41,7 +40,7 @@
 If you are not the person who made this request, or you wish to cancel
 this request, visit the following link:
 
-[%+ Param('urlbase') %]token.cgi?t=[% token FILTER url_quote %]&a=cxlem
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cxlem
 
 If you do nothing, and [%+ newemailaddress %] confirms this request,
 the change will be made permanent after [%+ max_token_age %] days
diff --git a/BugsSite/template/en/default/account/email/confirm-new.html.tmpl b/BugsSite/template/en/default/account/email/confirm-new.html.tmpl
index 45c12b8..437c1b7 100644
--- a/BugsSite/template/en/default/account/email/confirm-new.html.tmpl
+++ b/BugsSite/template/en/default/account/email/confirm-new.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -27,11 +26,9 @@
 
 [% expiration_ts = date + (constants.MAX_TOKEN_AGE * 86400) %]
 <div>
-  To complete the creation of your user account, you must choose a password in the
-  form below. You can also enter your real name, which is optional.<p>
-  If you don't fill this form before
-  <u>[%+ time2str("%B %o, %Y at %H:%M %Z", expiration_ts) %]</u>,
-  the creation of this account will be automatically canceled.
+  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>
 
 <form id="confirm_account_form" method="post" action="token.cgi">
@@ -43,7 +40,7 @@
       <td>[% email FILTER html %]</td>
     </tr>
     <tr>
-      <th align="right"><label for="realname">Real Name</label>:</th>
+      <th align="right"><small><i>(OPTIONAL)</i></small> <label for="realname">Real Name</label>:</th>
       <td><input type="text" id="realname" name="realname" value=""></td>
     </tr>
     <tr>
@@ -51,7 +48,7 @@
       <td><input type="password" id="passwd1" name="passwd1" value=""></td>
     </tr>
     <tr>
-      <th align="right"><label for="passwd1">Re-type your password</label>:</th>
+      <th align="right"><label for="passwd2">Confirm your password</label>:</th>
       <td><input type="password" id="passwd2" name="passwd2" value=""></td>
     </tr>
     <tr>
@@ -61,4 +58,20 @@
   </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>.
+</p>
+
+<p>
+  If you do not wish to create an account with this email click the
+  cancel account button below and your details will be forgotten.
+</p>
+
+<form id="cancel_account_form" method="post" action="token.cgi">
+  <input type="hidden" name="t" value="[% token FILTER html %]">
+  <input type="hidden" name="a" value="cancel_new_account">
+  <input type="submit" id="confirm" value="Cancel Account">
+</form>
+
 [% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/account/email/confirm.html.tmpl b/BugsSite/template/en/default/account/email/confirm.html.tmpl
index 5bf79f0..39add32 100644
--- a/BugsSite/template/en/default/account/email/confirm.html.tmpl
+++ b/BugsSite/template/en/default/account/email/confirm.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/account/email/request-new.txt.tmpl b/BugsSite/template/en/default/account/email/request-new.txt.tmpl
index 3fcdddf..36cd947 100644
--- a/BugsSite/template/en/default/account/email/request-new.txt.tmpl
+++ b/BugsSite/template/en/default/account/email/request-new.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -31,15 +30,28 @@
 [%+ terms.Bugzilla %] has received a request to create a user account
 using your email address ([% email %]).
 
-To confirm that you want to create an account using that email address,
-visit the following link:
+To continue creating an account using this email address, visit the 
+following link by [%+ time2str("%B %o, %Y at %H:%M %Z", expiration_ts) %]:
 
-[%+ Param('urlbase') %]token.cgi?t=[% token FILTER url_quote %]&a=request_new_account
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=request_new_account
 
-If you are not the person who made this request, or you wish to cancel
-this request, visit the following link:
+If you did not receive this email before [%+ time2str("%B %o, %Y at %H:%M %Z", expiration_ts) %] or
+you wish to create an account using a different email address you can begin
+again by going to:
 
-[%+ Param('urlbase') %]token.cgi?t=[% token FILTER url_quote %]&a=cancel_new_account
+[%+ urlbase %]createaccount.cgi
 
-If you do nothing, the request will lapse after [%+ constants.MAX_TOKEN_AGE %] days
-(on [%+ time2str("%B %o, %Y at %H:%M %Z", expiration_ts) %]).
+[% IF Param('createemailregexp') == '.*' && Param('emailsuffix') == '' %]
+PRIVACY NOTICE: [% terms.Bugzilla %] is an open [% terms.bug %] tracking system. Activity on most
+[%+ terms.bugs %], including email addresses, will be visible to the public. We recommend
+using a secondary account or free web email service (such as Gmail, Yahoo,
+Hotmail, or similar) to avoid receiving spam at your primary email address.
+[% END %]
+
+If you do not wish to create an account, or if this request was made in
+error you can do nothing or visit the following link:
+
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cancel_new_account
+
+If the above links do not work, or you have any other issues regarding
+your account, please contact administration at [% Param('maintainer') %].
diff --git a/BugsSite/template/en/default/account/password/forgotten-password.txt.tmpl b/BugsSite/template/en/default/account/password/forgotten-password.txt.tmpl
index 75720e7..f8687cb 100644
--- a/BugsSite/template/en/default/account/password/forgotten-password.txt.tmpl
+++ b/BugsSite/template/en/default/account/password/forgotten-password.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -30,12 +29,12 @@
 You have (or someone impersonating you has) requested to change your 
 [%+ terms.Bugzilla %] password. To complete the change, visit the following link:
 
-[%+ Param('urlbase') %]token.cgi?t=[% token FILTER url_quote %]&a=cfmpw
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&a=cfmpw
 
 If you are not the person who made this request, or you wish to cancel
 this request, visit the following link:
 
-[%+ Param('urlbase') %]token.cgi?t=[% token FILTER url_quote %]&a=cxlpw
+[%+ urlbase %]token.cgi?t=[% token FILTER url_quote %]&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 
diff --git a/BugsSite/template/en/default/account/password/set-forgotten-password.html.tmpl b/BugsSite/template/en/default/account/password/set-forgotten-password.html.tmpl
index 89d3dd0..7035868 100644
--- a/BugsSite/template/en/default/account/password/set-forgotten-password.html.tmpl
+++ b/BugsSite/template/en/default/account/password/set-forgotten-password.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
  [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/account/prefs/account.html.tmpl b/BugsSite/template/en/default/account/prefs/account.html.tmpl
index e914ecf..15f0702 100644
--- a/BugsSite/template/en/default/account/prefs/account.html.tmpl
+++ b/BugsSite/template/en/default/account/prefs/account.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -51,7 +50,7 @@
     </tr>              
 
     <tr>
-      <th align="right">Re-enter new password:</th>
+      <th align="right">Confirm new password:</th>
       <td>
         <input type="password" name="new_password2">
       </td>
@@ -95,5 +94,7 @@
       </tr>
     [% END %]
   [% END %]
-  
+
+  [% Hook.process('field') %]
+
 </table>
diff --git a/BugsSite/template/en/default/account/prefs/email.html.tmpl b/BugsSite/template/en/default/account/prefs/email.html.tmpl
index 617bec8..ad9b370 100644
--- a/BugsSite/template/en/default/account/prefs/email.html.tmpl
+++ b/BugsSite/template/en/default/account/prefs/email.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/account/prefs/permissions.html.tmpl b/BugsSite/template/en/default/account/prefs/permissions.html.tmpl
index 38bbfba..4a097e2 100644
--- a/BugsSite/template/en/default/account/prefs/permissions.html.tmpl
+++ b/BugsSite/template/en/default/account/prefs/permissions.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -36,7 +35,7 @@
   <tr>
     <td>
       [% IF has_bits.size %]
-        You have the following permission [% terms.bits %] set on your account:
+        You have the following permission bits set on your account:
         <table align="center">
           [% FOREACH bit_description = has_bits %]
             <tr>
@@ -63,7 +62,7 @@
         [% END %]
 
       [% ELSE %]
-        There are no permission [% terms.bits %] set on your account.
+        There are no permission bits set on your account.
       [% END %]
 
       [% IF user.groups.editusers %]
@@ -72,7 +71,7 @@
         all permissions for all users.
       [% ELSIF set_bits.size %]
         <br>
-        And you can turn on or off the following [% terms.bits %] for
+        And you can turn on or off the following bits for
         <a href="editusers.cgi">other users</a>:
           <table align="center">
           [% FOREACH bit_description = set_bits %]
diff --git a/BugsSite/template/en/default/account/prefs/prefs.html.tmpl b/BugsSite/template/en/default/account/prefs/prefs.html.tmpl
index da1b489..71e411d 100644
--- a/BugsSite/template/en/default/account/prefs/prefs.html.tmpl
+++ b/BugsSite/template/en/default/account/prefs/prefs.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -41,6 +40,8 @@
    title = "User Preferences"
    subheader = filtered_login
    style_urls = ['skins/standard/admin.css']
+   javascript_urls = ['js/util.js']
+   doc_section = "userpreferences.html"
  %]
 
 [% tabs = [{ name => "settings", label => "General Preferences",
@@ -84,6 +85,7 @@
 [% IF current_tab.saveable %]
   <form name="userprefsform" method="post" action="userprefs.cgi">
     <input type="hidden" name="tab" value="[% current_tab.name %]">
+    <input type="hidden" name="token" value="[% token FILTER html %]">
 [% END %]
 
 [% PROCESS "account/prefs/${current_tab.name}.html.tmpl" 
diff --git a/BugsSite/template/en/default/account/prefs/saved-searches.html.tmpl b/BugsSite/template/en/default/account/prefs/saved-searches.html.tmpl
index 4beb0e8..cf458e8 100644
--- a/BugsSite/template/en/default/account/prefs/saved-searches.html.tmpl
+++ b/BugsSite/template/en/default/account/prefs/saved-searches.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -31,17 +30,7 @@
       var bless_groups = [[% bless_group_ids.join(",") FILTER js %]];
       var checkbox = document.getElementById(group.name.replace(/share_(\d+)/, "force_$1"));
 
-      // Check if the selected group is in the bless group array
-      var found = false;
-      for (var i = 0; i < bless_groups.length; i++) {
-        if (bless_groups[i] == group.value) {
-          found = true;
-          break;
-        }
-      }
-
-      // Enable or disable checkbox
-      if (found) {
+      if (bz_isValueInArray(bless_groups, group.value)) {
         checkbox.disabled = false;
       } else {
         checkbox.disabled = true;
@@ -118,7 +107,8 @@
             Remove from <a href="editwhines.cgi">whining</a> first
           [% ELSE %]
             <a href="buglist.cgi?cmdtype=dorem&amp;remaction=forget&amp;namedcmd=
-                     [% q.name FILTER url_quote %]">Forget</a>
+                     [% q.name FILTER url_quote %]&amp;token=
+                     [% issue_hash_token([q.id, q.name]) FILTER url_quote %]">Forget</a>
           [% END %]
         </td>
         <td align="center">
@@ -148,6 +138,10 @@
                      %]>
               <label for="force_[% q.id FILTER html %]">Add to footer</label>
             [% END %]
+            [% IF q.shared_with_users %]
+              (shared with [% q.shared_with_users FILTER html %]
+              [%+ q.shared_with_users > 1 ? "users" : "user" %])
+            [% END %]
           </td>
         [% END %]
       </tr>
@@ -172,9 +166,15 @@
         Shared By
       </th>
       <th>
+        Shared To
+      </th>
+      <th>
         Run
       </th>
       <th>
+        Edit
+      </th>
+      <th>
         Show in
         Footer
       </th>
@@ -185,11 +185,16 @@
       <tr>
         <td>[% q.name FILTER html %]</td>
         <td>[% q.user.identity FILTER html %]</td>
+        <td>[% q.shared_with_group.name FILTER html %]</td>
         <td>
           <a href="buglist.cgi?cmdtype=dorem&amp;remaction=run&amp;namedcmd=
                    [% q.name FILTER url_quote %]&amp;sharer_id=
                    [% q.user.id FILTER url_quote %]">Run</a>
         </td>
+        <td>
+          <a href="query.cgi?[% q.edit_link FILTER html %]&amp;known_name=
+                   [% q.name FILTER url_quote %]">Edit</a>
+        </td>
         <td align="center">
           <input type="checkbox" 
                  name="link_in_footer_[% q.id FILTER html %]"
@@ -201,7 +206,7 @@
     [% END %]
     [% IF !found_shared_query %]
       <tr>
-        <td colspan="4" style="text-align: center">
+        <td colspan="6" style="text-align: center">
           &lt;None&gt;
         </td>
       </tr>
diff --git a/BugsSite/template/en/default/account/prefs/settings.html.tmpl b/BugsSite/template/en/default/account/prefs/settings.html.tmpl
index c47615dd..f8b6ba4 100644
--- a/BugsSite/template/en/default/account/prefs/settings.html.tmpl
+++ b/BugsSite/template/en/default/account/prefs/settings.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/account/profile-activity.html.tmpl b/BugsSite/template/en/default/account/profile-activity.html.tmpl
index fe8f9fb..3ef904d 100644
--- a/BugsSite/template/en/default/account/profile-activity.html.tmpl
+++ b/BugsSite/template/en/default/account/profile-activity.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -36,7 +35,7 @@
   #%]
 
 [% title = BLOCK %]
-  View User Account Log for '[% otheruser.login FILTER html %]'
+  Account History for '[% otheruser.login FILTER html %]'
 [% END %]
 
 
diff --git a/BugsSite/template/en/default/admin/admin.html.tmpl b/BugsSite/template/en/default/admin/admin.html.tmpl
new file mode 100644
index 0000000..c681767
--- /dev/null
+++ b/BugsSite/template/en/default/admin/admin.html.tmpl
@@ -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): Frédéric Buclin <LpSolit@gmail.com>
+  #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% title = BLOCK %]
+  Administer your installation ([% terms.Bugzilla %]
+  [%+ constants.BUGZILLA_VERSION FILTER html %])
+[% END %]
+
+[% PROCESS global/header.html.tmpl title = title
+                                   style_urls = ['skins/standard/admin.css']
+                                   doc_section = "administration.html"
+%]
+
+<div>
+  This page is only accessible to empowered users. You can access administrive pages
+  from here (based on your privileges), letting you configure different aspects of
+  this installation. Note: some sections may not be accessible to you and are marked
+  using a lighter color.
+</div>
+
+<table>
+  <tr>
+    <td class="admin_links">
+      <dl>
+        [% class = user.groups.tweakparams ? "" : "forbidden" %]
+        <dt 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>
+        <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>
+        <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>
+        <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>
+        <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
+                    || user.get_products_by_permission("editcomponents").size) ? "" : "forbidden" %]
+        <dt 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>
+        <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>
+      </dl>
+    </td>
+
+    <td class="admin_links">
+      <dl>
+        [% class = user.groups.admin ? "" : "forbidden" %]
+        <dt class="[% class %]"><a href="editfields.cgi">Custom Fields</a></dt>
+        <dd class="[% class %]">[% terms.Bugzilla %] lets you define fields which are
+        not implemented by default, based on your local and specific requirements.
+        These fields can then be used as any other field, meaning that you can set
+        them in [% terms.bugs %] and run any search involving them.<br>
+        Before creating new fields, keep in mind that too many fields may make the user
+        interface more complex and harder to use. Be sure you have investigated other ways
+        to satisfy your needs before doing this.</dd>
+
+        <dt 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>
+        <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>
+        <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>
+        <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>
+        <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>
+      </dl>
+    </td>
+  </tr>
+</table>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/classifications/add.html.tmpl b/BugsSite/template/en/default/admin/classifications/add.html.tmpl
index d549bbc..7254779 100644
--- a/BugsSite/template/en/default/admin/classifications/add.html.tmpl
+++ b/BugsSite/template/en/default/admin/classifications/add.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/classifications/del.html.tmpl b/BugsSite/template/en/default/admin/classifications/del.html.tmpl
index 9b726a8..8e48768 100644
--- a/BugsSite/template/en/default/admin/classifications/del.html.tmpl
+++ b/BugsSite/template/en/default/admin/classifications/del.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/classifications/edit.html.tmpl b/BugsSite/template/en/default/admin/classifications/edit.html.tmpl
index 5693945..b3ef22b 100644
--- a/BugsSite/template/en/default/admin/classifications/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/classifications/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/classifications/new.html.tmpl b/BugsSite/template/en/default/admin/classifications/new.html.tmpl
deleted file mode 100644
index ba62e5d..0000000
--- a/BugsSite/template/en/default/admin/classifications/new.html.tmpl
+++ /dev/null
@@ -1,32 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Albert Ting <alt@sonic.net>
-  #%]
-
-[% PROCESS global/header.html.tmpl
-  title = "Adding new classification"
-%]
-
-OK, done.
-
-<p>Back to the <a href="./">main [% terms.bugs %] page</a>,
-<a href="editproducts.cgi?action=add&amp;classification=[% classification FILTER url_quote %]">add</a> products to this new classification, 
-or <a href="editclassifications.cgi"> edit</a> more classifications.
-
-[% PROCESS global/footer.html.tmpl %] 
diff --git a/BugsSite/template/en/default/admin/classifications/reclassify.html.tmpl b/BugsSite/template/en/default/admin/classifications/reclassify.html.tmpl
index 113c6f6..08b85af 100644
--- a/BugsSite/template/en/default/admin/classifications/reclassify.html.tmpl
+++ b/BugsSite/template/en/default/admin/classifications/reclassify.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -45,9 +44,9 @@
 
     </tr><tr>
       <td valign="top">Products:</td>
-      <td valign="top">Products</td>
+      <td valign="top">Other Classifications</td>
       <td></td>
-      <td valign="top">[% classification.name FILTER html %] Products</td>
+      <td valign="top">This Classification</td>
 
     </tr><tr>
       <td></td>
diff --git a/BugsSite/template/en/default/admin/classifications/select.html.tmpl b/BugsSite/template/en/default/admin/classifications/select.html.tmpl
index fd3aaf4..d6b352d 100644
--- a/BugsSite/template/en/default/admin/classifications/select.html.tmpl
+++ b/BugsSite/template/en/default/admin/classifications/select.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/classifications/update.html.tmpl b/BugsSite/template/en/default/admin/classifications/update.html.tmpl
deleted file mode 100644
index 68ce277..0000000
--- a/BugsSite/template/en/default/admin/classifications/update.html.tmpl
+++ /dev/null
@@ -1,41 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Albert Ting <alt@sonic.net>
-  #%]
-
-[% PROCESS global/header.html.tmpl
-  title = "Update classification"
-%]
-
-[% IF updated_sortkey %] 
-  Updated sortkey.<br>
-[% END %]
-
-[% IF updated_description %] 
-  Updated description.<br>
-[% END %]
-
-[% IF updated_classification %] 
-  Updated classification name.<br>
-[% END %]
-
-<p>Back to the <a href="./">main [% terms.bugs %] page</a>
-or <a href="editclassifications.cgi"> edit</a> more classifications.
-
-[% PROCESS global/footer.html.tmpl %] 
diff --git a/BugsSite/template/en/default/admin/components/confirm-delete.html.tmpl b/BugsSite/template/en/default/admin/components/confirm-delete.html.tmpl
index 7a09566..53bba1e 100644
--- a/BugsSite/template/en/default/admin/components/confirm-delete.html.tmpl
+++ b/BugsSite/template/en/default/admin/components/confirm-delete.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -26,7 +25,8 @@
   #               which the component belongs.
   #%]
 
-[% title = BLOCK %]Delete Component of Product '[% product.name FILTER html %]'
+[% title = BLOCK %]Delete component '[% comp.name FILTER html %]'
+from '[% product.name FILTER html %]' product
   [% END %]
 
 [% PROCESS global/header.html.tmpl
diff --git a/BugsSite/template/en/default/admin/components/create.html.tmpl b/BugsSite/template/en/default/admin/components/create.html.tmpl
index 5b376bc..86411ad 100644
--- a/BugsSite/template/en/default/admin/components/create.html.tmpl
+++ b/BugsSite/template/en/default/admin/components/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -25,12 +24,9 @@
   #               which the component belongs.
   #%]
   
-[% title = BLOCK %]Add component to Product '[% product.name FILTER html %]'[% END %]
-[% subheader = BLOCK %]This page allows you to add a new component to product
-                '[% product.name FILTER html %]'.[% END %]
+[% title = BLOCK %]Add component to the [% product.name FILTER html %] product[% END %]
 [% PROCESS global/header.html.tmpl
   title = title
-  subheader = subheader
 %]
 
 <form method="post" action="editcomponents.cgi">
@@ -58,7 +54,6 @@
            id => "initialowner"
            value => ""
            size => 64
-           emptyok => 1
          %]
       </td>
     </tr>
@@ -99,8 +94,6 @@
   <hr>
   <input type="submit" id="create" value="Add">
   <input type="hidden" name="action" value="new">
-  <input type="hidden" name='open_name' value='All Open'>
-  <input type="hidden" name='nonopen_name' value='All Closed'>
   <input type="hidden" name='product' value="[% product.name FILTER html %]">
   <input type="hidden" name="token" value="[% token FILTER html %]">
 </form>
diff --git a/BugsSite/template/en/default/admin/components/created.html.tmpl b/BugsSite/template/en/default/admin/components/created.html.tmpl
deleted file mode 100644
index 090cfd8..0000000
--- a/BugsSite/template/en/default/admin/components/created.html.tmpl
+++ /dev/null
@@ -1,41 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
-  #%]
-
-[%# INTERFACE:
-  # comp: object; Bugzilla::Component object representing the component the
-  #               user created.
-  # product: object; Bugzilla::Product object representing the product to
-  #               which the component belongs.
-  #%]
-  
-[% title = BLOCK %]Adding new Component of Product
-                   '[% product.name FILTER html %]'[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-<p>The component '<a href="editcomponents.cgi?action=edit&amp;product=
-   [%- product.name FILTER url_quote %]&amp;component=[% comp.name FILTER url_quote %]">
-   [%- comp.name FILTER html %]</a>' has been created.</p>
-
-[% PROCESS admin/components/footer.html.tmpl %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/components/deleted.html.tmpl b/BugsSite/template/en/default/admin/components/deleted.html.tmpl
deleted file mode 100644
index 2b89ac0..0000000
--- a/BugsSite/template/en/default/admin/components/deleted.html.tmpl
+++ /dev/null
@@ -1,59 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
-  #%]
-
-[%# INTERFACE:
-  # comp: object; Bugzilla::Component object representing the component the
-  #               user deleted.
-  # product: object; Bugzilla::Product object representing the product to
-  #               which the component belongs.
-  #%]
-  
-[% title = BLOCK %]Deleted Component '[% comp.name FILTER html %]' from Product
-                   '[% product.name FILTER html %]'[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-<p>
-[% IF comp.bug_count %]
-  [% comp.bug_count FILTER none %]
-  [%- IF comp.bug_count > 1 %] 
-    [%+ terms.bugs %]
-  [% ELSE %]
-    [%+ terms.bug %]
-  [% END %]
-  deleted.
-  </p><p>
-  All references to those deleted [% terms.bugs %] have been removed.
-[% ELSE %]
-  No [% terms.bugs %] existed for the component.
-[% END %]
-</p>
-
-<p>Flag inclusions and exclusions deleted.</p>
-
-<p>Component '[% comp.name FILTER html %]' deleted.</p>
-
-[% PROCESS admin/components/footer.html.tmpl
-  no_edit_component_link = 1
- %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/components/edit.html.tmpl b/BugsSite/template/en/default/admin/components/edit.html.tmpl
index 0e3415d..9ddb8ca 100644
--- a/BugsSite/template/en/default/admin/components/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/components/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -64,7 +63,6 @@
            id => "initialowner"
            value => comp.default_assignee.login
            size => 64
-           emptyok => 1
          %]
       </td>
   
@@ -122,7 +120,7 @@
    <input type="hidden" name="componentold" value="[% comp.name FILTER html %]">
    <input type="hidden" name="product" value="[% product.name FILTER html %]">
    <input type="hidden" name="token" value="[% token FILTER html %]">
-   <input type="submit" value="Update" id="update"> or <a 
+   <input type="submit" value="Save Changes" id="update"> or <a 
         href="editcomponents.cgi?action=del&amp;product=
         [%- product.name FILTER url_quote %]&amp;component=
         [%- comp.name FILTER url_quote %]">Delete</a> this component.
diff --git a/BugsSite/template/en/default/admin/components/footer.html.tmpl b/BugsSite/template/en/default/admin/components/footer.html.tmpl
index c5fd789..b2e105e 100644
--- a/BugsSite/template/en/default/admin/components/footer.html.tmpl
+++ b/BugsSite/template/en/default/admin/components/footer.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/components/list.html.tmpl b/BugsSite/template/en/default/admin/components/list.html.tmpl
index c0862eb..990b079 100644
--- a/BugsSite/template/en/default/admin/components/list.html.tmpl
+++ b/BugsSite/template/en/default/admin/components/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/components/select-product.html.tmpl b/BugsSite/template/en/default/admin/components/select-product.html.tmpl
index 5be48fe..0910f98 100644
--- a/BugsSite/template/en/default/admin/components/select-product.html.tmpl
+++ b/BugsSite/template/en/default/admin/components/select-product.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/components/updated.html.tmpl b/BugsSite/template/en/default/admin/components/updated.html.tmpl
deleted file mode 100644
index 3a07a77..0000000
--- a/BugsSite/template/en/default/admin/components/updated.html.tmpl
+++ /dev/null
@@ -1,98 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
-  #                 Akamai Technologies <bugzilla-dev@akamai.com>
-  #                 Max Kanat-Alexander <mkanat@bugzilla.org>
-  #%]
-
-[%# INTERFACE:
-  #
-  # 'updated_XXX' variables are booleans, and are defined if the
-  # 'XXX' field was updated during the edit just being handled.
-  #
-  # updated_name: the name of the component updated
-  #
-  # updated_description: the component description updated
-  #
-  # updated_initialowner: the default assignee updated
-  #
-  # updated_initialqacontact: the default qa contact updated
-  #
-  # updated_initialcc: the default initial cc list
-  #
-  # comp: object; Bugzilla::Component object representing the component 
-  #               user updated.
-  # product: object; Bugzilla::Product object representing the product to
-  #               which the component belongs.
-  #
-  # initial_cc_names: a comma-separated list of the login names of
-  #                   the Initial CC, if it was updated.
-  #%]
-  
-[% title = BLOCK %]Updating Component '[% comp.name FILTER html %]' of Product
-                   '[% product.name FILTER html %]'[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-[% IF updated_description %]
-  <table>
-    <tr>
-      <td>Updated description to:</td>
-      <td>'[% comp.description FILTER html_light %]'</td>
-    </tr>
-  </table>
-[% END %]
-
-[% IF updated_initialowner %]
-  <p>Updated Default Assignee to: '[% comp.default_assignee.login FILTER html %]'.</p>
-[% END %]
-
-[% IF updated_initialqacontact %]
-  <p>
-  [% IF comp.default_qa_contact.id %]
-    Updated Default QA Contact to '[% comp.default_qa_contact.login FILTER html %]'.
-  [% ELSE %]
-    Removed Default QA Contact.
-  [% END %]
-  </p>
-[% END %]
-
-[% IF updated_name %]
-  <p>Updated Component name to: '[% comp.name FILTER html %]'.</p>
-[% END %]
-
-[% IF updated_initialcc %]
-  [% IF initial_cc_names %]
-    <p>Updated Default CC list to:
-      '[% initial_cc_names FILTER html %]'.</p>
-  [% ELSE %]
-    <p>Removed the Default CC list.</p>
-  [% END %]
-[% END %]
-
-[% UNLESS updated_description || updated_initialowner || 
-          updated_initialqacontact || updated_name  ||
-          updated_initialcc %]
-  <p>Nothing changed for component '[% comp.name FILTER html %]'.</p>
-[% END %]
-
-[% PROCESS admin/components/footer.html.tmpl %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/confirm-action.html.tmpl b/BugsSite/template/en/default/admin/confirm-action.html.tmpl
index 6e8caa6..521d2d1 100644
--- a/BugsSite/template/en/default/admin/confirm-action.html.tmpl
+++ b/BugsSite/template/en/default/admin/confirm-action.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -21,6 +20,8 @@
   # token_action: the action the token was supposed to serve.
   # expected_action: the action the user was going to do.
   # script_name: the script generating this warning.
+  # alternate_script: the suggested script to redirect the user to
+  #                   if he declines submission.
   #%]
 
 [% PROCESS "global/field-descs.none.tmpl" %]
@@ -90,8 +91,8 @@
                exclude="^(Bugzilla_login|Bugzilla_password)$" %]
     <input type="submit" id="confirm" value="Confirm Changes">
   </form>
-  <p>Or throw away these changes and go back to <a href="[% script_name FILTER html %]">
-    [%- script_name FILTER html %]</a>.</p>
+  <p>Or throw away these changes and go back to <a href="[% alternate_script FILTER html %]">
+    [%- alternate_script FILTER html %]</a>.</p>
 [% END %]
 
 [% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/custom_fields/confirm-delete.html.tmpl b/BugsSite/template/en/default/admin/custom_fields/confirm-delete.html.tmpl
new file mode 100644
index 0000000..c936836
--- /dev/null
+++ b/BugsSite/template/en/default/admin/custom_fields/confirm-delete.html.tmpl
@@ -0,0 +1,66 @@
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # Contributor(s): Alexander Eiser <alexe@ed.ca>
+  #%]
+
+[%# INTERFACE:
+  # field: object; the field object that you are trying to delete.
+  # token: string; the delete_field token required to complete deletion.
+  #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% title = BLOCK %]
+  Delete the Custom Field '[% field.name FILTER html %]' ([% field.description FILTER html %])
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+           title = title
+           doc_section = "custom-fields.html#delete-custom-fields"
+%]
+
+<table border="1" cellpadding="4" cellspacing="0">
+<tr bgcolor="#6666FF">
+  <th valign="top" align="left">Field</th>
+  <th valign="top" align="left">Value</th>
+</tr>
+<tr>
+  <td valign="top">Custom Field:</td>
+  <td valign="top">[% field.name FILTER html %]</td>
+</tr>
+<tr>
+  <td valign="top">Description:</td>
+  <td valign="top">[% field.description FILTER html %]</td>
+</tr>
+<tr>
+  <td valign="top">Type:</td>
+  <td valign="top">[% field_types.${field.type} FILTER html %]</td>
+</tr>
+</table>
+
+<h2>Confirmation</h2>
+
+<p>
+  Are you sure you want to remove this field from the database?<br>
+  <em>This action will only be successful if the field is obsolete,
+  and has never been used in [% terms.abug FILTER html %].</em>
+</p>
+
+<a href="editfields.cgi?action=delete&amp;name=[% field.name FILTER html %]&amp;token=[% token FILTER html %]">
+  Delete field '[% field.description FILTER html %]'</a>
+
+<p>
+  <a href="editfields.cgi">Back to the list of existing custom fields</a>
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/custom_fields/create.html.tmpl b/BugsSite/template/en/default/admin/custom_fields/create.html.tmpl
index f7ab722..5dd50ce 100644
--- a/BugsSite/template/en/default/admin/custom_fields/create.html.tmpl
+++ b/BugsSite/template/en/default/admin/custom_fields/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,7 +21,9 @@
 
 [% PROCESS global/header.html.tmpl
            title = "Add a new Custom Field"
-           onload = "document.getElementById('new_bugmail').disabled = true;" %]
+           onload = "document.getElementById('new_bugmail').disabled = true;"
+           doc_section = "custom-fields.html#add-custom-fields"
+%]
 
 <script type="text/javascript">
   <!--
diff --git a/BugsSite/template/en/default/admin/custom_fields/edit.html.tmpl b/BugsSite/template/en/default/admin/custom_fields/edit.html.tmpl
index 882b17b..02334ab 100644
--- a/BugsSite/template/en/default/admin/custom_fields/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/custom_fields/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -26,7 +25,9 @@
 
 [% PROCESS global/header.html.tmpl
            title = title
-           onload = "toggleCheckbox(document.getElementById('enter_bug'), 'new_bugmail');" %]
+           onload = "toggleCheckbox(document.getElementById('enter_bug'), 'new_bugmail');"
+           doc_section = "custom-fields.html#edit-custom-fields"
+%]
 
 <script type="text/javascript">
   <!--
@@ -85,7 +86,8 @@
       <th>&nbsp;</th>
       <td>&nbsp;</td>
     </tr>
-    [% IF field.type == constants.FIELD_TYPE_SINGLE_SELECT %]
+    [% IF field.type == constants.FIELD_TYPE_SINGLE_SELECT
+          || field.type == constants.FIELD_TYPE_MULTI_SELECT %]
       <tr>
         <th>&nbsp;</th>
         <td colspan="3">
@@ -102,6 +104,15 @@
   <input type="submit" id="edit" value="Submit">
 </form>
 
+[% IF field.obsolete %]
+<p>
+  <a href="editfields.cgi?action=del&amp;name=[% field.name FILTER html %]">Remove
+    this custom field from the database.</a><br>
+  This action will only be successful if the custom field has never been used
+  in [% terms.abug %].<br>
+</p>
+[% END %]
+
 <p>
   <a href="editfields.cgi">Back to the list of existing custom fields</a>
 </p>
diff --git a/BugsSite/template/en/default/admin/custom_fields/list.html.tmpl b/BugsSite/template/en/default/admin/custom_fields/list.html.tmpl
index 03c0401..6f2e68b 100644
--- a/BugsSite/template/en/default/admin/custom_fields/list.html.tmpl
+++ b/BugsSite/template/en/default/admin/custom_fields/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -20,7 +19,12 @@
 
 [% PROCESS "global/field-descs.none.tmpl" %]
 
-[% PROCESS global/header.html.tmpl title = "Custom Fields" %]
+[% PROCESS global/header.html.tmpl
+  title = "Custom Fields"
+  doc_section = "custom-fields.html"
+%]
+
+[% delete_contentlink = BLOCK %]editfields.cgi?action=del&amp;name=%%name%%[% END %]
 
 [% columns = [
      {
@@ -51,6 +55,11 @@
      {
        name => "obsolete"
        heading => "Is Obsolete"
+     },
+     {
+       name => "action"
+       heading => "Action"
+       content => ""
      }
    ]
 %]
@@ -71,6 +80,17 @@
   %]
 [% END %]
 
+
+[% overrides.action = [ {
+     match_value => 1
+     match_field => 'obsolete'
+     override_content => 1
+     content => "Delete"
+     override_contentlink => 1
+     contentlink => delete_contentlink
+   } ]
+%] 
+
 [% PROCESS admin/table.html.tmpl
      columns = columns
      overrides = overrides
diff --git a/BugsSite/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl b/BugsSite/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
index 4cd0014..3320ae4 100644
--- a/BugsSite/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
+++ b/BugsSite/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -19,13 +18,15 @@
   # 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.
-  # field: string; The name of the 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 FILTER html %]' field[% END %]
+[% title = BLOCK %]
+  Delete Value '[% value FILTER html %]' from the '[% field.description FILTER html %]'
+  ([% field.name FILTER html %]) field
+[% END %]
 
 [% PROCESS global/header.html.tmpl
   title = title
@@ -39,7 +40,7 @@
 </tr>
 <tr>
   <td valign="top">Field Name:</td>
-  <td valign="top">[% field FILTER html %]</td>
+  <td valign="top">[% field.description FILTER html %]</td>
 </tr>
 <tr>
   <td valign="top">Field Value:</td>
@@ -49,9 +50,9 @@
   <td valign="top">[% terms.Bugs %]:</td>
   <td valign="top">
 [% IF bug_count %]
-  <a title="List of [% terms.bugs %] where '[% field FILTER html %]' is '
+  <a title="List of [% terms.bugs %] where '[% field.description FILTER html %]' is '
             [% value FILTER html %]'"
-     href="buglist.cgi?[% field FILTER url_quote %]=[%- value FILTER url_quote %]">[% bug_count FILTER html %]</a>
+     href="buglist.cgi?[% field.name FILTER url_quote %]=[%- value FILTER url_quote %]">[% bug_count FILTER html %]</a>
 [% ELSE %]
   None
 [% END %]
@@ -64,12 +65,12 @@
 [% IF (param_name.defined && Param(param_name) == value) || bug_count || (value_count == 1) %]
 
   <p>Sorry, but the '[% value FILTER html %]' value cannot be deleted
-  from the '[% field FILTER html %]' field for the following reason(s):</p>
+  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 FILTER html %]' field.
+          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
@@ -85,8 +86,8 @@
             is 1 [% terms.bug %] 
           [% END %]
           with this field value. You must change the field value on
-          <a title="List of [% terms.bugs %] where '[% field FILTER html %]' is '[% value FILTER html %]'"
-             href="buglist.cgi?[% field FILTER url_quote %]=[% value FILTER url_quote %]">
+          <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 %]
               those [% terms.bugs %] 
             [% ELSE %]
@@ -98,7 +99,7 @@
 
     [% IF value_count == 1 %]
       <li>'[% value FILTER html %]' is the last value for
-          '[%- field FILTER html %]', and so it can not be deleted.
+          '[%- field.description FILTER html %]', and so it can not be deleted.
     [% END %]
   </ul>
 
@@ -109,7 +110,7 @@
   <form method="post" action="editvalues.cgi">
     <input type="submit" value="Yes, delete" id="delete">
     <input type="hidden" name="action" value="delete">
-    <input type="hidden" name="field" value="[% field FILTER html %]">
+    <input type="hidden" name="field" value="[% field.name FILTER html %]">
     <input type="hidden" name="value" value="[% value FILTER html %]">
     <input type="hidden" name="token" value="[% token FILTER html %]">
   </form>
diff --git a/BugsSite/template/en/default/admin/fieldvalues/create.html.tmpl b/BugsSite/template/en/default/admin/fieldvalues/create.html.tmpl
index 29bf857..fe906bf 100644
--- a/BugsSite/template/en/default/admin/fieldvalues/create.html.tmpl
+++ b/BugsSite/template/en/default/admin/fieldvalues/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -12,20 +11,24 @@
   # The Original Code is the Bugzilla Bug Tracking System.
   #
   # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+  #                 Frédéric Buclin <LpSolit@gmail.com>
   #%]
 
 [%# INTERFACE:
-  # field: string; name of the field the value is being created for
+  # field: object; the field the value is being created for
   #%]
   
-[% title = BLOCK %]Add Value for the '[% field FILTER html %]' field[% END %]
-[% subheader = BLOCK %]This page allows you to add a new value for the
-                '[% field FILTER html %]' field.[% END %]
+[% title = BLOCK %]
+  Add Value for the '[% field.description FILTER html %]' ([% field.name FILTER html %]) field
+[% END %]
 [% PROCESS global/header.html.tmpl
   title = title
-  subheader = subheader
 %]
 
+<p>
+  This page allows you to add a new value for the '[% field.description FILTER html %]' field.
+</p>
+
 <form method="post" action="editvalues.cgi">
   <table border="0" cellpadding="4" cellspacing="0">
     <tr>
@@ -38,10 +41,28 @@
       <td><input id="sortkey" size="10" maxlength="20" name="sortkey"
                  value=""></td>
     </tr>
+    [% IF field.name == "bug_status" %]
+      <tr>
+        <th align="right"><label for="is_open">Status Type:</label></th>
+        <td>
+          <input type="radio" id="open_status" name="is_open" value="1" checked="checked">
+          <label for="open_status">Open</label><br>
+          <input type="radio" id="closed_status" name="is_open" value="0">
+          <label for="closed_status">Closed (requires a Resolution)</label>
+        </td>
+      </tr>
+      <tr>
+        <th>&nbsp;</th>
+        <td>
+          Note: The open/close attribute can only be set now, when you create
+          the status. It cannot be edited later.
+        </td>
+      </tr>
+    [% END %]
   </table>
   <input type="submit" id="create" value="Add">
   <input type="hidden" name="action" value="new">
-  <input type="hidden" name='field' value="[% field FILTER html %]">
+  <input type="hidden" name='field' value="[% field.name FILTER html %]">
   <input type="hidden" name="token" value="[% token FILTER html %]">
 </form>
 
diff --git a/BugsSite/template/en/default/admin/fieldvalues/created.html.tmpl b/BugsSite/template/en/default/admin/fieldvalues/created.html.tmpl
deleted file mode 100644
index 2184771..0000000
--- a/BugsSite/template/en/default/admin/fieldvalues/created.html.tmpl
+++ /dev/null
@@ -1,37 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
-  #%]
-
-[%# INTERFACE:
-  # value: string; the name of the newly created field value
-  # field: string; the name of the field the value belongs to
-  #%]
-  
-[% title = BLOCK %]New Value '[% value FILTER html %]' added to 
-  '[% field FILTER html %]' field[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-<p>The value '<a title="Edit value '[% value FILTER html %]' of 
-   for the '[% field FILTER html %]' field"
-   href="editvalues.cgi?action=edit&amp;field=
-   [%- field FILTER url_quote %]&amp;value=[% value FILTER url_quote %]">
-   [%- value FILTER html %]</a>' has been added as a valid choice for
-   the '[% field FILTER html %]' field.</p>
-
-[% PROCESS admin/fieldvalues/footer.html.tmpl %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/fieldvalues/deleted.html.tmpl b/BugsSite/template/en/default/admin/fieldvalues/deleted.html.tmpl
deleted file mode 100644
index c966897..0000000
--- a/BugsSite/template/en/default/admin/fieldvalues/deleted.html.tmpl
+++ /dev/null
@@ -1,36 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
-  #%]
-
-[%# INTERFACE:
-  # value: string; the field value that was deleted.
-  #
-  # field: string; the field the value was deleted from.
-  #
-  #%]
-  
-[% title = BLOCK %]Deleted Value '[% value FILTER html %]' for the
-                   '[% field FILTER html %]' Field[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-<p>Field Value '[% value FILTER html %]' deleted.</p>
-
-[% PROCESS admin/fieldvalues/footer.html.tmpl
-  no_edit_link = 1
- %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/fieldvalues/edit.html.tmpl b/BugsSite/template/en/default/admin/fieldvalues/edit.html.tmpl
index e19b7c0..98b480b 100644
--- a/BugsSite/template/en/default/admin/fieldvalues/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/fieldvalues/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -17,13 +16,15 @@
 [%# INTERFACE:
   # value: string; The field value we are editing.
   # sortkey: number; Sortkey of the field value we are editing.
-  # field: string; The field this value belongs to.
+  # field: object; The field this value belongs to.
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
 
-[% title = BLOCK %]Edit Value '[% value FILTER html %]'  '
-                   [%- field FILTER html %]'[% END %]
+[% title = BLOCK %]
+  Edit Value '[% value FILTER html %]' for the '[% field.description FILTER html %]'
+  ([% field.name FILTER html %]) field
+[% END %]
 [% PROCESS global/header.html.tmpl
   title = title
 %]
@@ -48,15 +49,20 @@
       <td><input id="sortkey" size="20" maxlength="20" name="sortkey" 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>
+      </tr>
+    [% END %]
   </table>
 
   <input type="hidden" name="valueold" value="[% value FILTER html %]">
   <input type="hidden" name="sortkeyold" value="[% sortkey FILTER html %]">
   <input type="hidden" name="action" value="update">
-  <input type="hidden" name="field" value="[% field FILTER html %]">
+  <input type="hidden" name="field" value="[% field.name FILTER html %]">
   <input type="hidden" name="token" value="[% token FILTER html %]">
-  <input type="submit" id="update" value="Update">
+  <input type="submit" id="update" value="Save Changes">
 </form>
 
 [% PROCESS admin/fieldvalues/footer.html.tmpl
diff --git a/BugsSite/template/en/default/admin/fieldvalues/footer.html.tmpl b/BugsSite/template/en/default/admin/fieldvalues/footer.html.tmpl
index 27428f6..dcb6dbc 100644
--- a/BugsSite/template/en/default/admin/fieldvalues/footer.html.tmpl
+++ b/BugsSite/template/en/default/admin/fieldvalues/footer.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -16,8 +15,7 @@
 
 [%# INTERFACE:
   # value: string; the value being inserted/edited.
-  # field: string; the name of the field which the value
-  #                  belongs/belonged to
+  # field: object; the field which the value belongs/belonged to.
   #
   # no_XXX_link: boolean; if defined, then don't show the corresponding
   #                       link. Supported parameters are:
@@ -32,24 +30,24 @@
 <p>
 
 [% UNLESS no_add_link %]
-  <a title="Add a value for the '[% field FILTER html %]' field."
+  <a title="Add a value for the '[% field.description FILTER html %]' field."
      href="editvalues.cgi?action=add&amp;field=
-          [%- field FILTER url_quote %]">Add</a> a value.
+          [%- field.name FILTER url_quote %]">Add</a> a value.
 [% END %]
 
 [% IF value && !no_edit_link %]
   Edit value <a 
   title="Edit value '[% value FILTER html %]' for the '
-         [%- field FILTER html %]' field"
+         [%- field.name FILTER html %]' field"
   href="editvalues.cgi?action=edit&amp;field=
-        [%- field FILTER url_quote %]&amp;value=[% value FILTER url_quote %]">
+        [%- field.name FILTER url_quote %]&amp;value=[% value FILTER url_quote %]">
         '[% value FILTER html %]'</a>.
 [% END %]
 
 [% UNLESS no_edit_other_link %]
   Edit other values for the <a 
   href="editvalues.cgi?field=
-        [%- field FILTER url_quote %]">'[% field FILTER html %]'</a> field.
+        [%- field.name FILTER url_quote %]">'[% field.description FILTER html %]'</a> field.
 
 [% END %]
 
diff --git a/BugsSite/template/en/default/admin/fieldvalues/list.html.tmpl b/BugsSite/template/en/default/admin/fieldvalues/list.html.tmpl
index 9e3da3c..d14bbc2 100644
--- a/BugsSite/template/en/default/admin/fieldvalues/list.html.tmpl
+++ b/BugsSite/template/en/default/admin/fieldvalues/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -20,7 +19,7 @@
   #   - sortkey: number; The sortkey used to order the value when 
   #              displayed to the user in a list.
   #
-  # field: string; the name of the field we are editing values for.
+  # field: object; the field we are editing values for.
   # static: array; list of values which cannot be renamed nor deleted.
   #%]
 
@@ -29,16 +28,16 @@
 
 [% PROCESS global/variables.none.tmpl %]
 
-[% title = BLOCK %]Select value for the
-                   '[% field FILTER html %]' field[% END %]
+[% title = BLOCK %]Select value for the '[% field.description FILTER html %]'
+                   ([% field.name FILTER html %]) field[% END %]
 [% PROCESS global/header.html.tmpl
   title = title
 %]
 
 [% edit_contentlink = BLOCK %]editvalues.cgi?action=edit&amp;field=
-  [%- field FILTER url_quote %]&amp;value=%%name%%[% END %]
+  [%- field.name FILTER url_quote %]&amp;value=%%name%%[% END %]
 [% delete_contentlink = BLOCK %]editvalues.cgi?action=del&amp;field=
-  [%- field FILTER url_quote %]&amp;value=%%name%%[% END %]
+  [%- field.name FILTER url_quote %]&amp;value=%%name%%[% END %]
 
 
 [% columns = [
diff --git a/BugsSite/template/en/default/admin/fieldvalues/select-field.html.tmpl b/BugsSite/template/en/default/admin/fieldvalues/select-field.html.tmpl
index fa9f82a..3704d42 100644
--- a/BugsSite/template/en/default/admin/fieldvalues/select-field.html.tmpl
+++ b/BugsSite/template/en/default/admin/fieldvalues/select-field.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/fieldvalues/updated.html.tmpl b/BugsSite/template/en/default/admin/fieldvalues/updated.html.tmpl
deleted file mode 100644
index 7b76abb..0000000
--- a/BugsSite/template/en/default/admin/fieldvalues/updated.html.tmpl
+++ /dev/null
@@ -1,55 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
-  #%]
-
-[%# INTERFACE:
-  #
-  # 'updated_XXX' variables are booleans, and are defined if the
-  # 'XXX' field was updated during the edit just being handled.
-  # Variables called just 'XXX' are strings, and are the _new_ contents
-  # of the fields.
-  #
-  # value & updated_value: the name of the field value
-  # sortkey & updated_sortkey: the field value sortkey
-  # field: string; the field that the value belongs to
-  # default_value_updated: boolean; whether the default value for
-  #                        this field has been updated
-  #%]
-  
-[% title = BLOCK %]Updating Value '[% value FILTER html %]' of the
-                   '[% field FILTER html %]' Field[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-[% IF updated_value %]
-  <p>Updated field value to: '[% value FILTER html %]'.</p>
-  [% IF default_value_updated %]
-    <p>Note that this value is the default for this field.
-    All references to the default value will now point to this new value.</p>
-  [% END %]
-[% END %]
-
-[% IF updated_sortkey %]
-  <p>Updated field value sortkey to: '[% sortkey FILTER html %]'.</p>
-[% END %]
-
-[% UNLESS (updated_sortkey || updated_value) %]
-  <p>Nothing changed for field value '[% value FILTER html %]'.</p>
-[% END %]
-
-[% PROCESS admin/fieldvalues/footer.html.tmpl %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/flag-type/confirm-delete.html.tmpl b/BugsSite/template/en/default/admin/flag-type/confirm-delete.html.tmpl
index 0af9fb5..ed90941 100644
--- a/BugsSite/template/en/default/admin/flag-type/confirm-delete.html.tmpl
+++ b/BugsSite/template/en/default/admin/flag-type/confirm-delete.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -23,16 +22,22 @@
 
 [% title = BLOCK %]Confirm Deletion of Flag Type '[% flag_type.name FILTER html %]'[% END %]
 
-[% PROCESS global/header.html.tmpl title = title %]
+[% PROCESS global/header.html.tmpl
+  title = title
+  doc_section = "flags-overview.html#flags-delete"
+%]
 
 <p>
-   There are [% flag_type.flag_count %] flags of type [% flag_type.name FILTER html %].
-   If you delete this type, those flags will also be deleted.  Note that
-   instead of deleting the type you can
+  [% IF flag_type.flag_count %]
+    There are [% flag_type.flag_count %] flags of type [% flag_type.name FILTER html %].
+    If you delete this type, those flags will also be deleted.
+  [% END %]
+
+  Note that instead of deleting the type you can
    <a href="editflagtypes.cgi?action=deactivate&amp;id=[% flag_type.id %]&amp;token=
            [%- token FILTER html %]">deactivate it</a>,
-   in which case the type and its flags will remain in the database
-   but will not appear in the [% terms.Bugzilla %] UI.
+   in which case the type [% IF flag_type.flag_count %] and its flags [% END %] will remain
+   in the database but will not appear in the [% terms.Bugzilla %] UI.
 </p>
 
 <table>
diff --git a/BugsSite/template/en/default/admin/flag-type/edit.html.tmpl b/BugsSite/template/en/default/admin/flag-type/edit.html.tmpl
index e78c836..ebebf50 100644
--- a/BugsSite/template/en/default/admin/flag-type/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/flag-type/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -34,10 +33,12 @@
   [% typeLabelLowerSingular = BLOCK %]attachment[% END %]
 [% END %]
 
+[% doc_section = "flags-overview.html#flags-create" %]
 [% IF last_action == "copy" %]
   [% title = BLOCK %]Create Flag Type Based on [% type.name FILTER html %][% END %]
 [% ELSIF last_action == "edit" %]
   [% title = BLOCK %]Edit Flag Type [% type.name FILTER html %][% END %]
+  [% doc_section = "flags-overview.html#flags-edit" %]
 [% END %]
 
 [% PROCESS global/header.html.tmpl
@@ -46,8 +47,9 @@
     table#form th { text-align: right; vertical-align: baseline; white-space: nowrap; }
     table#form td { text-align: left; vertical-align: baseline; }
   "
-  onload="selectProduct(document.forms[0], 'product', 'component', '__Any__');"
-  javascript_urls=["productmenu.js"]
+  onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, '__Any__');"
+  javascript_urls=["js/productform.js"]
+  doc_section = doc_section
 %]
 
 <form method="post" action="editflagtypes.cgi">
@@ -100,7 +102,7 @@
           <tr>
             <td style="vertical-align: top;">
               <b>Product/Component:</b><br>
-              <select name="product" onchange="selectProduct(this.form, 'product', 'component', '__Any__');">
+              <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 %]" 
@@ -148,16 +150,18 @@
     <tr>
       <th>&nbsp;</th>
       <td>
-        <input type="checkbox" name="is_active" [% "checked" IF type.is_active || !type.is_active.defined %]>
-        active (flags of this type appear in the UI and can be set)
+        <input type="checkbox" id="is_active" name="is_active"
+               [% " checked" IF type.is_active || !type.is_active.defined %]>
+        <label for="is_active">active (flags of this type appear in the UI and can be set)</label>
       </td>
     </tr>
 
     <tr>
       <th>&nbsp;</th>
       <td>
-        <input type="checkbox" name="is_requestable" [% "checked" IF type.is_requestable || !type.is_requestable.defined %]>
-        requestable (users can ask for flags of this type to be set)
+        <input type="checkbox" id="is_requestable" name="is_requestable"
+               [% " checked" IF type.is_requestable || !type.is_requestable.defined %]>
+        <label for="is_requestable">requestable (users can ask for flags of this type to be set)</label>
       </td>
     </tr>
 
@@ -179,16 +183,20 @@
     <tr>
       <th>&nbsp;</th>
       <td>
-        <input type="checkbox" name="is_requesteeble" [% "checked" IF type.is_requesteeble || !type.is_requesteeble.defined %]>
-        specifically requestable (users can ask specific other users to set flags of this type as opposed to just asking the wind)
+        <input type="checkbox" id="is_requesteeble" name="is_requesteeble"
+               [% " checked" IF type.is_requesteeble || !type.is_requesteeble.defined %]>
+        <label for="is_requesteeble">specifically requestable (users can ask specific other users
+          to set flags of this type as opposed to just asking the wind)</label>
       </td>
     </tr>
 
     <tr>
       <th>&nbsp;</th>
       <td>
-        <input type="checkbox" name="is_multiplicable" [% "checked" IF type.is_multiplicable || !type.is_multiplicable.defined %]>
-        multiplicable (multiple flags of this type can be set on the same [% typeLabelLowerSingular %])
+        <input type="checkbox" id="is_multiplicable" name="is_multiplicable"
+               [% " checked" IF type.is_multiplicable || !type.is_multiplicable.defined %]>
+        <label for="is_multiplicable">multiplicable (multiple flags of this type can be set on
+          the same [% typeLabelLowerSingular %])</label>
       </td>
     </tr>
 
diff --git a/BugsSite/template/en/default/admin/flag-type/list.html.tmpl b/BugsSite/template/en/default/admin/flag-type/list.html.tmpl
index 3346f95..d4bba94 100644
--- a/BugsSite/template/en/default/admin/flag-type/list.html.tmpl
+++ b/BugsSite/template/en/default/admin/flag-type/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -31,8 +30,9 @@
     .inactive { color: #787878; }
     .multiplicable { display: block; }
   "
-  onload="selectProduct(document.forms[0], 'product', 'component', '__All__');"
-  javascript_urls=["productmenu.js"]
+  onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, '__All__');"
+  javascript_urls=["js/productform.js"]
+  doc_section = "flags-overview.html#flag-types"
 %]
 
 <p>
@@ -60,7 +60,7 @@
     <tr>
       <th><label for="product">Product:</label></th>
       <td>
-        <select name="product" onchange="selectProduct(this.form, 'product', 'component', '__Any__');">
+        <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 %]"
diff --git a/BugsSite/template/en/default/admin/groups/change.html.tmpl b/BugsSite/template/en/default/admin/groups/change.html.tmpl
deleted file mode 100644
index 3137fd8..0000000
--- a/BugsSite/template/en/default/admin/groups/change.html.tmpl
+++ /dev/null
@@ -1,111 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Dave Miller <justdave@syndicomm.com>
-  #                 Joel Peshkin <bugreport@peshkin.net>
-  #                 Jacob Steenhagen <jake@bugzilla.org>
-  #                 Vlad Dascalu <jocuri@softhome.net>
-  #%]
-
-[%# INTERFACE:
-  # action: integer. Can be 1, 2 or 3, depending on the action
-  #         performed:
-  #         1 - remove_explicit_members
-  #         2 - remove_explicit_members_regexp
-  #         3 - no conversion, just save the changes
-  # changes: boolean int. Is 1 if changes occurred.
-  # gid: integer. The ID of the group.
-  # name: the name of the product where removal is performed.
-  # regexp: the regexp according to which the update is performed.
-  #%]
-
-[% IF (action == 1) %]
-  [% title = "Confirm: Remove All Explicit Members?" %]
-[% ELSIF (action == 2) %]
-  [% title = "Confirm: Remove Explicit Members in the Regular Expression?" %]
-[% ELSE %]
-  [% title = "Updating group hierarchy" %]
-[% END %]
-
-[% PROCESS global/header.html.tmpl %]
-
-<p>
-  Checking....
-
-  [% IF changes %]
-    changed.
-  [% END %]
-</p>
-    
-[% IF (action == 1) || (action == 2) %]
-  [% IF changes %]
-    <p>Group updated, please confirm removal:</p>
-  [% END %]
-
-  [% IF (action == 1) %]
-    <p>This option will remove all explicitly defined users
-  [% ELSIF regexp %]
-    <p>This option will remove all users included in the regular expression:
-    [% regexp FILTER html %]
-  [% ELSE %]
-    <p>
-      <b>There is no regular expression defined.</b>
-      No users will be removed.
-    </p>
-  [% END %]
-  
-  [% IF ((action == 1) || regexp) %]
-    from group [% name FILTER html %].</p>
-    
-    <p>
-      Generally, you will only need to do this when upgrading groups
-      created with [% terms.Bugzilla %] versions 2.16 and prior. Use
-      this option with <b>extreme care</b> and consult the documentation
-      for further information.
-    </p>
-    
-    <form method="post" action="editgroups.cgi">
-      <input type="hidden" name="group" value="[% gid FILTER html %]">
-    
-      [% IF (action == 2) %]
-        <input type="hidden" name="action" value="remove_all_regexp">
-      [% ELSE %]
-        <input type="hidden" name="action" value="remove_all">
-      [% END %]
-    
-      <input name="confirm" type="submit" value="Confirm">
-      <p>Or <a href="editgroups.cgi">return to the Edit Groups page</a>.</p>
-    </form>
-  [% END %]
-[% ELSE %]
-  [%# if we got this far, the admin doesn't want to convert, so just save
-    # their changes %]
-
-  [% IF changes %]
-    <p>Done.</p>
-  [% ELSE %]
-    <p>
-      You didn't change anything! If you really meant it, hit the <b>Back</b>
-      button and try again.
-    </p>
-  [% END %]
-    
-  <p>Back to the <a href="editgroups.cgi">group list</a>.</p>
-[% END %]
-
-[% PROCESS global/footer.html.tmpl %] 
diff --git a/BugsSite/template/en/default/admin/groups/confirm-remove.html.tmpl b/BugsSite/template/en/default/admin/groups/confirm-remove.html.tmpl
new file mode 100644
index 0000000..cdb070d
--- /dev/null
+++ b/BugsSite/template/en/default/admin/groups/confirm-remove.html.tmpl
@@ -0,0 +1,66 @@
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # The Initial Developer of the Original Code is Netscape Communications
+  # Corporation. Portions created by Netscape are
+  # Copyright (C) 1998 Netscape Communications Corporation. All
+  # Rights Reserved.
+  #
+  # Contributor(s): Dave Miller <justdave@syndicomm.com>
+  #                 Joel Peshkin <bugreport@peshkin.net>
+  #                 Jacob Steenhagen <jake@bugzilla.org>
+  #                 Vlad Dascalu <jocuri@softhome.net>
+  #                 Max Kanat-Alexander <mkanat@bugzilla.org>
+  #%]
+
+[%# INTERFACE:
+  # group: The Bugzilla::Group being changed.
+  # regexp: the regexp according to which the update is performed.
+  #%]
+
+[% IF regexp %]
+  [% title = "Confirm: Remove Explicit Members in the Regular Expression?" %]
+[% ELSE %]
+  [% title = "Confirm: Remove All Explicit Members?" %]
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+  title = title
+  doc_section = "groups.html"
+%]
+
+[% IF regexp %]
+  <p>This option will remove all users from '[% group.name FILTER html %]'
+    whose login names match the regular expression:
+    '[% regexp FILTER html %]'</p>
+[% ELSE %]
+  <p>This option will remove all explicitly defined users
+    from '[% group.name FILTER html %].'</p>
+[% END %]
+  
+<p>Generally, you will only need to do this when upgrading groups
+  created with [% terms.Bugzilla %] versions 2.16 and earlier. Use
+  this option with <b>extreme care</b> and consult the documentation
+  for further information.
+</p>
+    
+<form method="post" action="editgroups.cgi">
+  <input type="hidden" name="group_id" value="[% group.id FILTER html %]">
+  <input type="hidden" name="regexp" value="[% regexp FILTER html %]">
+  <input type="hidden" name="action" value="remove_regexp">
+  
+  <input name="token" type="hidden" value="[% token FILTER html %]">
+  <input name="confirm" type="submit" value="Confirm">
+  <p>Or <a href="editgroups.cgi">return to the Edit Groups page</a>.</p>
+</form>
+    
+[% PROCESS global/footer.html.tmpl %] 
diff --git a/BugsSite/template/en/default/admin/groups/create.html.tmpl b/BugsSite/template/en/default/admin/groups/create.html.tmpl
index fb25530..b3ac723 100644
--- a/BugsSite/template/en/default/admin/groups/create.html.tmpl
+++ b/BugsSite/template/en/default/admin/groups/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -29,6 +28,7 @@
 [% PROCESS global/header.html.tmpl
   title = "Add group"
   subheader = "This page allows you to define a new user group."
+  doc_section = "groups.html#create-groups"
 %]
 
 <form method="post" action="editgroups.cgi">
@@ -42,11 +42,20 @@
     <td><input size="40" name="desc"></td>
     <td><input size="30" name="regexp"></td>
     <td><input type="checkbox" name="isactive" value="1" checked></td>
-  </tr></table><hr>
+  </tr>
+  <tr>
+    <th>Icon URL:</th>
+    <td colspan="3"><input type="text" size="70" maxlength="255" id="icon_url" name="icon_url"></td>
+  </tr>
+  [% Hook.process('field') %]
+  </table>
 
-  <input type="checkbox" name="insertnew" value="1"
+  <hr>
+
+  <input type="checkbox" id="insertnew" name="insertnew" value="1"
     [% IF Param("makeproductgroups") %] checked[% END %]>
-    Insert new group into all existing products.<p>
+  <label for="insertnew">Insert new group into all existing products.</label>
+  <p>
   <input type="submit" id="create" value="Add">
   <input type="hidden" name="action" value="new">
   <input type="hidden" name="token" value="[% token FILTER html %]">
@@ -55,8 +64,7 @@
 <p><b>Name</b> is what is used with the B<!-- blah -->ugzilla->user->in_group()
 function in any customized cgi files you write that use a given group.
 It can also be used by people submitting [% terms.bugs %] by email to
-limit [% terms.abug %] to a certain set of groups. It may not contain
-any spaces.</p>
+limit [% terms.abug %] to a certain set of groups.</p>
 
 <p><b>Description</b> is what will be shown in the [% terms.bug %] reports
 to members of the group where they can choose whether
@@ -75,6 +83,13 @@
 automatically grant membership to this group to anyone with an 
 email address that matches this regular expression.</p>
 
+<p>
+  <b>Icon URL</b> is optional, and is the URL pointing to the icon
+  used to identify the group. It may be either a relative URL to the base URL
+  of this installation or an absolute URL. This icon will be displayed
+  in comments in [% terms.bugs %] besides the name of the author of comments.
+</p>
+
 <p>By default, the new group will be associated with existing 
 products. Unchecking the "Insert new group into all existing 
 products" option will prevent this and make the group become 
diff --git a/BugsSite/template/en/default/admin/groups/created.html.tmpl b/BugsSite/template/en/default/admin/groups/created.html.tmpl
deleted file mode 100644
index 4ac051c..0000000
--- a/BugsSite/template/en/default/admin/groups/created.html.tmpl
+++ /dev/null
@@ -1,38 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Dave Miller <justdave@syndicomm.com>
-  #                 Joel Peshkin <bugreport@peshkin.net>
-  #                 Jacob Steenhagen <jake@bugzilla.org>
-  #                 Vlad Dascalu <jocuri@softhome.net>
-  #%]
-
-[%# INTERFACE:
-  # none
-  #%]
-
-[% PROCESS global/header.html.tmpl
-  title = "Adding new group"
-%]
-
-<p>OK, done.</p>
-
-<p><a href="editgroups.cgi?action=add">Add</a> another group or
-go back to the <a href="editgroups.cgi">group list</a>.</p>
-
-[% PROCESS global/footer.html.tmpl %] 
diff --git a/BugsSite/template/en/default/admin/groups/delete.html.tmpl b/BugsSite/template/en/default/admin/groups/delete.html.tmpl
index 662fc88..e847ada 100644
--- a/BugsSite/template/en/default/admin/groups/delete.html.tmpl
+++ b/BugsSite/template/en/default/admin/groups/delete.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -37,6 +36,7 @@
 
 [% PROCESS global/header.html.tmpl
   title = "Delete group"
+  doc_section = "groups.html"
 %]
 
 <table border="1">
diff --git a/BugsSite/template/en/default/admin/groups/deleted.html.tmpl b/BugsSite/template/en/default/admin/groups/deleted.html.tmpl
deleted file mode 100644
index be067fa..0000000
--- a/BugsSite/template/en/default/admin/groups/deleted.html.tmpl
+++ /dev/null
@@ -1,38 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Dave Miller <justdave@syndicomm.com>
-  #                 Joel Peshkin <bugreport@peshkin.net>
-  #                 Jacob Steenhagen <jake@bugzilla.org>
-  #                 Vlad Dascalu <jocuri@softhome.net>
-  #%]
-
-[%# INTERFACE:
-  # name: string. The name of the group.
-  #%]
-
-
-[% PROCESS global/header.html.tmpl
-  title = "Deleting group"
-%]
-
-<p>The group [% name FILTER html %] has been deleted.</p>
-
-<p>Go back to the <a href="editgroups.cgi">group list</a>.
-
-[% PROCESS global/footer.html.tmpl %] 
diff --git a/BugsSite/template/en/default/admin/groups/edit.html.tmpl b/BugsSite/template/en/default/admin/groups/edit.html.tmpl
index 6f333f5..17d8ca1 100644
--- a/BugsSite/template/en/default/admin/groups/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/groups/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -20,51 +19,47 @@
   #                 Joel Peshkin <bugreport@peshkin.net>
   #                 Jacob Steenhagen <jake@bugzilla.org>
   #                 Vlad Dascalu <jocuri@softhome.net>
+  #                 Max Kanat-Alexander <mkanat@bugzilla.org>
   #%]
 
 [%# INTERFACE:
-  # group_id: number. The group ID.
-  # name: string. The name of the group. [grantor]
-  # description: string. The description of the group.
-  # regexp: string. The regular expression for the users of the group.
-  # isactive: boolean int. Shows if the group is still active.
-  # isbuggroup: boolean int. Is 1 if this is a bug group.
-  # groups: array with group objects having the properties:
-  #   - grpid: number. The ID of the group.
-  #   - grpname: string. The name of the group. [member]
-  #   - grpdesc: string. The description of the group.
-  #   - grpmember: boolean int. Is 1 if members of the group are to inherit
-  #                membership in the group being edited.
-  #   - blessmember: boolean int. Is 1 if members of the group are to be able
-  #                  to bless users into the group being edited.
-  #   - membercansee: boolean int. Is 1 if the members of the group are to
-  #                   be aware of the group being edited and its members.
+  # group - A Bugzilla::Group representing the group being edited.
+  # *_current - Arrays of Bugzilla::Group objects that show the current
+  #             values for this group, as far as grants.
+  # *_available - Arrays of Bugzilla::Group objects that show the current 
+  #               available values for each grant.
   #%]
 
-[% title = BLOCK %]Change Group: [% name FILTER html %][% END %]
+[% title = BLOCK %]Change Group: [% group.name FILTER html %][% END %]
 
 [% PROCESS global/header.html.tmpl
   title = title
-  style = "tr.odd_row {
-               background: #e9e9e9; 
-           }
-           .permissions th {
-               background: #000000;
-               color: #ffffff;
-           }
-          "
+  doc_section = "groups.html#edit-groups"
+  style = "
+    .grant_table { border-collapse: collapse; }
+    .grant_table td, .grant_table th {
+        padding-left: .5em;
+    }
+    .grant_table td.one, .grant_table th.one {
+        border-right: 1px solid black;
+        padding-right: .5em;
+    }
+  "
 %]
 
 <form method="post" action="editgroups.cgi">
+  <input type="hidden" name="action" value="postchanges">
+  <input type="hidden" name="group_id" value="[% group.id FILTER html %]">
+
   <table border="1" cellpadding="4">
     <tr>
       <th>Group:</th>
       <td>
-        [% IF isbuggroup %]
-          <input type="hidden" name="oldname" value="[% name FILTER html %]">
-          <input type="text" name="name" size="60" value="[% name FILTER html %]">
+        [% IF group.is_bug_group %]
+          <input type="text" name="name" size="60" 
+                 value="[% group.name FILTER html %]">
         [% ELSE %]
-          [% name FILTER html %]
+          [% group.name FILTER html %]
         [% END %]
       </td>
     </tr>
@@ -72,11 +67,11 @@
     <tr>
       <th>Description:</th>
       <td>
-        [% IF isbuggroup %]
-          <input type="hidden" name="olddesc" value="[% description FILTER html %]">
-          <input type="text" name="desc" size="70" value="[% description FILTER html %]">
+        [% IF group.is_bug_group %]
+          <input type="text" name="desc" size="70" 
+                 value="[% group.description FILTER html %]">
         [% ELSE %]
-          [% description FILTER html %]
+          [% group.description FILTER html %]
         [% END %]
       </td>
     </tr>
@@ -84,143 +79,171 @@
     <tr>
       <th>User Regexp:</th>
       <td>
-        <input type="hidden" name="oldregexp" value="[% regexp FILTER html %]">
-        <input type="text" name="regexp" size="40" value="[% regexp FILTER html %]">
+        <input type="text" name="regexp" size="40" 
+               value="[% group.user_regexp FILTER html %]">
       </td>
     </tr>
 
-    [% IF isbuggroup %]
+    <tr>
+      <th>
+        Icon URL:
+        [% IF group.icon_url %]
+          <img src="[% group.icon_url FILTER html %]" alt="[% group.name FILTER html %]">
+        [% END %]
+      </th>
+      <td>
+        <input type="text" name="icon_url" size="70" maxlength="255"
+               value="[% group.icon_url FILTER html %]">
+      </td>
+    </tr>
+
+    [% IF group.is_bug_group %]
       <tr>
         <th>Use For [% terms.Bugs %]:</th>
         <td>
-          <input type="checkbox" name="isactive" value="1" [% (isactive == 1) ? "checked" : "" %]>
-          <input type="hidden" name="oldisactive" value="[% isactive FILTER html %]">
+          <input type="checkbox" name="isactive" 
+                 value="1" [% 'checked="checked"' IF group.is_active %]>
         </td>
       </tr>
     [% END %]
+    [% Hook.process('field') %]
   </table>
 
-  <p>Users become members of this group in one of three ways:</p>
-    <ul>
-      <li> by being explicity included when the user is edited.
-      <li> by matching the user regexp above.
-      <li> by being a member of one of the groups included in this group
-           by checking the boxes below.
-    </ul>
+  <h4>Group Permissions</h4>
 
-  [% usevisibility = Param('usevisibilitygroups') %]
-
-    <h4>Group Permissions</h4>
-  <table class="permissions" cellspacing="0" cellpadding="2">
-    <tr>     
-      [% IF usevisibility %]
-        <th>
-          Visible
-        </th>
-      [% END %]
-      <th>
-        Grant
-      </th>
-      <th>
-        Inherit
-      </th>
-      <th>
-        Group
-      </th>
-      <th>
-        Description
-      </th>
-    </tr>
-    [% row = 0 %]
-    [% FOREACH group = groups %]
-      [% row = row + 1 %]
-      <tr [% 'class="odd_row"' IF row % 2 %]>
-        [% IF usevisibility %]
-          <td align="center">
-            <input type="checkbox" name="cansee-[% group.grpid FILTER none %]" 
-              [% group.membercansee ? "checked " : "" %]value="1">
-            <input type="hidden" name="oldcansee-[% group.grpid FILTER none %]"
-              value="[% group.membercansee FILTER none %]">
-          </td>
-        [% END %]
-        [% IF group_id != group.grpid %]
-          <td align="center">
-            <input type="checkbox" name="bless-[% group.grpid FILTER html %]" [% group.blessmember ? "checked " : "" %]value="1">
-            <input type="hidden" name="oldbless-[% group.grpid FILTER html %]" value="[% group.blessmember FILTER html %]">
-          </td>
-          <td align="center">
-            <input type="checkbox" name="grp-[% group.grpid FILTER html %]" [% group.grpmember ? "checked " : "" %]value="1">
-            <input type="hidden" name="oldgrp-[% group.grpid FILTER html %]" value="[% group.grpmember FILTER html %]">
-          </td>
-          <td align="left" class="groupname">
-            <a href="[% "editgroups.cgi?action=changeform&group=${group.grpid}" FILTER html %]">
-              [% group.grpnam FILTER html %]
-            </a>
-          </td>
-        [% ELSE %]
-          <td>
-            <input type="hidden" name="oldbless-[% group.grpid FILTER html %]" value="0">
-          </td>
-          <td>
-            <input type="hidden" name="oldgrp-[% group.grpid FILTER html %]" value="0">
-          </td>
-          <td align="left" class="groupname">
-            <em>
-              [% group.grpnam FILTER html %]
-            </em>
-          </td>
-        [% END %]
-        <td align="left" class="groupdesc">[% group.grpdesc FILTER html_light %]</td>
-      </tr>
-    [% END %]
-  </table>
-
-  <input type="submit" id="update" value="Save Changes">
-  <br>
-  <dl>
-    [% IF usevisibility %]
-      <dt>Visibility:</dt>
-      <dd>
-        Members of the selected groups can be aware of the 
-        "[% name FILTER html %]" group
-      </dd>
-    [% END %]
-    <dt>Grant:</dt>
-    <dd>
-    Members of the selected groups can grant membership to the
-    "[% name FILTER html %]" group
-    </dd>
-    <dt>Inherit:</dt>
-    <dd>
-      Members of the selected groups are automatically members of the
-      "[% name FILTER html %]" group
-    </dd>
-  </dl>
-  <table width="76%" border="0">
+  <table class="grant_table">
     <tr>
-      <td>
-        <h4>Conversion of groups created with [% terms.Bugzilla %]
-        versions 2.16 and prior:</h4>
+      <th class="one">Groups That Are a Member of This Group<br>
+        (&quot;Users in <var>X</var> are automatically in  
+         [%+ group.name FILTER html %]&quot;)</th>
+      <th>Groups That This Group Is a Member Of<br>
+        (&quot;If you are in [% group.name FILTER html %], you are 
+         automatically also in...&quot;)</th>
+    </tr>
+    <tr>
+      <td class="one">
+        [% PROCESS select_pair name = "members" size = 10
+                   items_available = members_available
+                     items_current = members_current %]
+      </td>
+  
+      <td>[% PROCESS select_pair name = "member_of" size = 10
+                     items_available = member_of_available
+                       items_current = member_of_current %]</td>
+    </tr>
+  </table>
 
-        <ul>
-          <li>Remove all explicit memberships from this group: 
-            <input name="remove_explicit_members" type="submit" id="remove_explicit_members" value="Remove Memberships">
-          </li>
+  <table class="grant_table">
+    <tr>
+      <th class="one">
+        Groups That Can Grant Membership in This Group<br>
+        (&quot;Users in <var>X</var> can add other users to 
+         [%+ group.name FILTER html %]&quot;)
 
-          <li>Remove all explicit memberships that are included in the above
-            regular expression: 
-            <input name="remove_explicit_members_regexp" type="submit" id="remove_explicit_members_regexp" value="Remove memberships included in regular expression"> 
-          </li>
-        </ul>
+      </th>
+     <th>Groups That This Group Can Grant Membership In<br>
+       (&quot;Users in [% group.name FILTER html %] can add users to...&quot;)
+     </th>
+    </tr>
+    <tr>
+      <td class="one">
+        [% PROCESS select_pair name = "bless_from" size = 10
+                   items_available = bless_from_available
+                     items_current = bless_from_current %]
+      </td>
+      <td>[% PROCESS select_pair name = "bless_to" size = 10
+                     items_available = bless_to_available
+                       items_current = bless_to_current %]
       </td>
     </tr>
   </table>
 
-  <input type="hidden" name="action" value="postchanges">
-  <input type="hidden" name="group" value="[% group_id FILTER html %]">
+  [% IF Param('usevisibilitygroups') %]
+    <table class="grant_table">
+      <tr>
+        <th class="one">
+          Groups That Can See This Group<br>
+          (&quot;Users in <var>X</var> can see users in
+           [%+ group.name FILTER html %]&quot;)
+        </th>
+       <th>Groups That This Group Can See<br>
+         (&quot;Users in [% group.name FILTER html %] can see users in...&quot;)
+       </th>
+      </tr>
+      <tr>
+        <td class="one">
+          [% PROCESS select_pair name = "visible_from" size = 10
+                     items_available = visible_from_available
+                       items_current = visible_from_current %]
+        </td>
+        <td>[% PROCESS select_pair name = "visible_to_me" size = 10
+                       items_available = visible_to_me_available
+                         items_current = visible_to_me_current %]
+        </td>
+      </tr>
+    </table>
+  [% END %]
+
+  <input type="submit" value="Update Group">
   <input type="hidden" name="token" value="[% token FILTER html %]">
 </form>
+  
+<h4>Mass Remove</h4>
 
+<p>You can use this form to do mass-removal of users from groups.
+  This is often very useful if you upgraded from [% terms.Bugzilla %] 
+  2.16.</p>
+
+<table><tr><td>
+<form method="post" action="editgroups.cgi">
+  <fieldset>
+    <legend>Remove all explict memberships from users whose login names
+      match the following regular expression:</legend>
+    <input type="text" size="20" name="regexp">
+    <input type="submit" value="Remove Memberships">
+
+    <p>If you leave the field blank, all explicit memberships in 
+      this group will be removed.</p>
+
+    <input type="hidden" name="action" value="confirm_remove">
+    <input type="hidden" name="group_id" value="[% group.id FILTER html %]">
+  </fieldset>
+</form>
+</td></tr></table>
+ 
 <p>Back to the <a href="editgroups.cgi">group list</a>.</p>
 
 [% PROCESS global/footer.html.tmpl %] 
+
+[% BLOCK select_pair %]
+  <table class="select_pair">
+    <tr>
+      <th><label for="[% "${name}_add" FILTER html %]">Add<br>
+        (select to add)</label></th>
+      <th><label for="[% "${name}_remove" FILTER html %]">Current<br>
+        (select to remove)</label></th>
+    </tr>
+    <tr>
+      <td>
+        <select multiple="multiple" size="[% size FILTER html %]"
+                name="[% "${name}_add" FILTER html %]"
+                id="[% "${name}_add" FILTER html %]">
+          [% FOREACH item = items_available %]
+            <option value="[% item.id FILTER html %]">
+              [% item.name FILTER html %]</option>
+          [% END %]
+        </select>
+      </td>
+      <td>
+        <select multiple="multiple" size="[% size FILTER html %]"
+                name="[% "${name}_remove" FILTER html %]"
+                id="[% "${name}_remove" FILTER html %]">
+          [% FOREACH item = items_current %]
+            <option value="[% item.id FILTER html %]">
+              [% item.name FILTER html %]</option>
+          [% END %]
+        </select>
+      </td>
+    </tr>
+  </table>
+[% END %]
diff --git a/BugsSite/template/en/default/admin/groups/list.html.tmpl b/BugsSite/template/en/default/admin/groups/list.html.tmpl
index 617bbd3..029e5f0 100644
--- a/BugsSite/template/en/default/admin/groups/list.html.tmpl
+++ b/BugsSite/template/en/default/admin/groups/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -35,6 +34,7 @@
 [% PROCESS global/header.html.tmpl
   title = "Edit Groups"
   subheader = "This lets you edit the groups available to put users in."
+  doc_section = "groups.html"
 %]
 
 [% edit_contentlink = "editgroups.cgi?action=changeform&amp;group=%%id%%" %]
diff --git a/BugsSite/template/en/default/admin/groups/remove.html.tmpl b/BugsSite/template/en/default/admin/groups/remove.html.tmpl
deleted file mode 100644
index 8c41333..0000000
--- a/BugsSite/template/en/default/admin/groups/remove.html.tmpl
+++ /dev/null
@@ -1,62 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Dave Miller <justdave@syndicomm.com>
-  #                 Joel Peshkin <bugreport@peshkin.net>
-  #                 Jacob Steenhagen <jake@bugzilla.org>
-  #                 Vlad Dascalu <jocuri@softhome.net>
-  #%]
-
-[%# INTERFACE:
-  # remove_all: boolean int. Is 1 if the action was remove_all,
-  #         and 0 if the action was remove_all_regexp.
-  # name: string. The place where removal is performed.
-  # regexp: string. The regexp according to which the removal is performed.
-  # users: array with group objects having the properties:
-  #   - login: string. The login which is removed.
-  #%]
-
-
-[% IF remove_all %]
-  [% title = BLOCK %]
-    Removing All Explicit Group Memberships from '[% name FILTER html %]'
-  [% END %]
-[% ELSE %]
-  [% title = BLOCK %]
-    Removing All Explicit Group Memberships Matching Group RegExp from '[% name FILTER html %]'
-  [% END %]
-[% END %]
-
-[% PROCESS global/header.html.tmpl %]
-
-[% IF remove_all %]
-  <p><b>Removing explicit membership</b></p>
-[% ELSE %]
-  <p><b>Removing explicit memberships of users matching
-  '[% regexp FILTER html %]'...</b></p>
-[% END %]
-    
-[% FOREACH user = users %]
-  [% user.login FILTER html %] removed<br>
-[% END %]
-
-<p><b>Done</b>.</p>
-
-<p>Back to the <a href="editgroups.cgi">group list</a>.</p>
-
-[% PROCESS global/footer.html.tmpl %] 
diff --git a/BugsSite/template/en/default/admin/keywords/confirm-delete.html.tmpl b/BugsSite/template/en/default/admin/keywords/confirm-delete.html.tmpl
index 0d68524..20a6dee 100644
--- a/BugsSite/template/en/default/admin/keywords/confirm-delete.html.tmpl
+++ b/BugsSite/template/en/default/admin/keywords/confirm-delete.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -32,7 +31,7 @@
 <p>
   [% IF keyword.bug_count == 1 %]
     There is one [% terms.bug %] with this keyword set.
-  [% ELSE %]
+  [% ELSIF keyword.bug_count > 1 %]
     There are [% keyword.bug_count FILTER html %] [%+ terms.bugs %] with
     this keyword set.
   [% END %]
@@ -44,7 +43,6 @@
 <form method="post" action="editkeywords.cgi">
   <input type="hidden" name="id" value="[% keyword.id FILTER html %]">
   <input type="hidden" name="action" value="delete">
-  <input type="hidden" name="reallydelete" value="1">
   <input type="hidden" name="token" value="[% token FILTER html %]">
   <input type="submit" id="delete"
          value="Yes, really delete the keyword">
diff --git a/BugsSite/template/en/default/admin/keywords/create.html.tmpl b/BugsSite/template/en/default/admin/keywords/create.html.tmpl
index 9440145..e5d6aa0 100644
--- a/BugsSite/template/en/default/admin/keywords/create.html.tmpl
+++ b/BugsSite/template/en/default/admin/keywords/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/keywords/created.html.tmpl b/BugsSite/template/en/default/admin/keywords/created.html.tmpl
deleted file mode 100644
index 940a609..0000000
--- a/BugsSite/template/en/default/admin/keywords/created.html.tmpl
+++ /dev/null
@@ -1,36 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Terry Weissman <terry@mozilla.org>
-  #                 Vlad Dascalu <jocuri@softhome.net>
-  #%]
-
-[%# INTERFACE:
-  # name: string; the name of the current keyword.
-  #%]
-  
-[% PROCESS global/header.html.tmpl
-  title = "Adding new keyword"
-%]
-
-<p>The keyword [% name FILTER html %] has been added.</p>
-
-<p><a href="editkeywords.cgi">Edit existing keywords</a> or
-<a href="editkeywords.cgi?action=add">add another keyword</a>.</p>
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/keywords/edit.html.tmpl b/BugsSite/template/en/default/admin/keywords/edit.html.tmpl
index 81f072b..c4b9a64 100644
--- a/BugsSite/template/en/default/admin/keywords/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/keywords/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -63,7 +62,7 @@
     </tr>
   </table>
 
-  <input type="submit" id="update" value="Update">
+  <input type="submit" id="update" value="Save Changes">
   <input type="hidden" name="action" value="update">
   <input type="hidden" name="id" value="[% keyword.id FILTER html %]">
   <input type="hidden" name="token" value="[% token FILTER html %]">
diff --git a/BugsSite/template/en/default/admin/keywords/list.html.tmpl b/BugsSite/template/en/default/admin/keywords/list.html.tmpl
index 90b079f..c400a23 100644
--- a/BugsSite/template/en/default/admin/keywords/list.html.tmpl
+++ b/BugsSite/template/en/default/admin/keywords/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -55,7 +54,7 @@
      { 
        heading => "Action" 
        content => "Delete"
-       contentlink => "editkeywords.cgi?action=delete&amp;id=%%id%%"
+       contentlink => "editkeywords.cgi?action=del&amp;id=%%id%%"
      }
    ]
 %]
diff --git a/BugsSite/template/en/default/admin/keywords/rebuild-cache.html.tmpl b/BugsSite/template/en/default/admin/keywords/rebuild-cache.html.tmpl
deleted file mode 100644
index f9f6f31..0000000
--- a/BugsSite/template/en/default/admin/keywords/rebuild-cache.html.tmpl
+++ /dev/null
@@ -1,56 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Terry Weissman <terry@mozilla.org>
-  #                 Vlad Dascalu <jocuri@softhome.net>
-  #                 Max Kanat-Alexander <mkanat@bugzilla.org>
-  #%]
-
-[%# INTERFACE:
-  # action: string; the current action (either "update" or "delete").
-  # keyword: A Bugzilla::Keyword object
-  #%]
-  
-[% IF action == "update" %]
-  [% title = "Update keyword"%]
-  [% status = "updated" %]
-[% ELSIF action == "delete" %]
-  [% title = "Delete keyword" %]
-  [% status = "deleted" %]
-[% END %]
-
-[% PROCESS global/header.html.tmpl %]
-
-Keyword [% keyword.name FILTER html %] [%+ status FILTER html %].
-
-<p>
-  <b>After you have finished deleting or modifying keywords,
-  you need to rebuild the keyword cache.</b><br>
-  
-  Warning: on a very large installation of [% terms.Bugzilla %],
-  this can take several minutes.
-</p>
-
-<p>
-  <b><a href="sanitycheck.cgi?rebuildkeywordcache=1">Rebuild
-  keyword cache</a></b>
-</p>
-
-<p><a href="editkeywords.cgi">Edit more keywords</a>.</p>
-
-[% PROCESS global/footer.html.tmpl %] 
diff --git a/BugsSite/template/en/default/admin/milestones/confirm-delete.html.tmpl b/BugsSite/template/en/default/admin/milestones/confirm-delete.html.tmpl
index b1f893f..ea89b80 100644
--- a/BugsSite/template/en/default/admin/milestones/confirm-delete.html.tmpl
+++ b/BugsSite/template/en/default/admin/milestones/confirm-delete.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -76,8 +75,9 @@
       is 1 [% terms.bug %]
     [% END %]
     entered for this milestone! When you delete this milestone,
-    <b><blink>ALL</blink></b> stored [% terms.bugs %] will be reassigned to
-    the default milestone of this product.
+    <b><blink>ALL</blink></b> of these [% terms.bugs %] will be retargeted
+    to [% product.default_milestone FILTER html %], the default milestone for 
+    the [% product.name FILTER html %] product.
   </td></tr>
   </table>
 
diff --git a/BugsSite/template/en/default/admin/milestones/create.html.tmpl b/BugsSite/template/en/default/admin/milestones/create.html.tmpl
index 44e958d..d7c2f58 100644
--- a/BugsSite/template/en/default/admin/milestones/create.html.tmpl
+++ b/BugsSite/template/en/default/admin/milestones/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/milestones/created.html.tmpl b/BugsSite/template/en/default/admin/milestones/created.html.tmpl
deleted file mode 100644
index 9342e50..0000000
--- a/BugsSite/template/en/default/admin/milestones/created.html.tmpl
+++ /dev/null
@@ -1,44 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
-  #%]
-
-[%# INTERFACE:
-  # milestone: object; Bugzilla::Milestone object representing the
-  #                    milestone the user created.
-  #
-  # product: object; Bugzilla::Product object representing the product to
-  #               which the milestone belongs.
-  #%]
-  
-[% title = BLOCK %]Adding new Milestone of Product
-                   '[% product.name FILTER html %]'[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-<p>The milestone '<a title="Edit milestone '[% milestone.name FILTER html %]' of 
-   product '[% product.name FILTER html %]'"
-   href="editmilestones.cgi?action=edit&amp;product=
-   [%- product.name FILTER url_quote %]&amp;milestone=[% milestone.name FILTER url_quote %]">
-   [%- milestone.name FILTER html %]</a>' has been created.</p>
-
-[% PROCESS admin/milestones/footer.html.tmpl %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/milestones/deleted.html.tmpl b/BugsSite/template/en/default/admin/milestones/deleted.html.tmpl
deleted file mode 100644
index 8d20407..0000000
--- a/BugsSite/template/en/default/admin/milestones/deleted.html.tmpl
+++ /dev/null
@@ -1,58 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
-  #                 Frédéric Buclin <LpSolit@gmail.com>
-  #%]
-
-[%# INTERFACE:
-  # product: object; Bugzilla::Product object representing the product to
-  #               which the milestone belongs.
-  # milestone: object; Bugzilla::Milestone object representing the
-  #                    milestone the user deleted.
-  #%]
-  
-[% title = BLOCK %]Deleted Milestone '[% milestone.name FILTER html %]' of Product
-                   '[% product.name FILTER html %]'[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-<p>
-[% IF milestone.bug_count %]
-
-  [% milestone.bug_count FILTER none %]
-  [% IF milestone.bug_count > 1 %] 
-    [%+ terms.bugs %]
-  [% ELSE %]
-    [%+ terms.bug %]
-  [% END %]
-  reassigned to the default milestone.
-
-[% ELSE %]
-  No [% terms.bugs %] were targetted at the milestone.
-[% END %]
-</p>
-
-<p>Milestone '[% milestone.name FILTER html %]' deleted.</p>
-
-[% PROCESS admin/milestones/footer.html.tmpl
-  no_edit_milestone_link = 1
- %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/milestones/edit.html.tmpl b/BugsSite/template/en/default/admin/milestones/edit.html.tmpl
index fbe0dd8..dfe9d1b 100644
--- a/BugsSite/template/en/default/admin/milestones/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/milestones/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -54,7 +53,7 @@
   <input type="hidden" name="milestoneold" value="[% milestone.name FILTER html %]">
   <input type="hidden" name="action" value="update">
   <input type="hidden" name="product" value="[% product.name FILTER html %]">
-  <input type="submit" id="update" value="Update">
+  <input type="submit" id="update" value="Save Changes">
   <input type="hidden" name="token" value="[% token FILTER html %]">
 </form>
 
diff --git a/BugsSite/template/en/default/admin/milestones/footer.html.tmpl b/BugsSite/template/en/default/admin/milestones/footer.html.tmpl
index 774223e..e91e5f9 100644
--- a/BugsSite/template/en/default/admin/milestones/footer.html.tmpl
+++ b/BugsSite/template/en/default/admin/milestones/footer.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/milestones/list.html.tmpl b/BugsSite/template/en/default/admin/milestones/list.html.tmpl
index 01e705e..00e5bd9 100644
--- a/BugsSite/template/en/default/admin/milestones/list.html.tmpl
+++ b/BugsSite/template/en/default/admin/milestones/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/milestones/select-product.html.tmpl b/BugsSite/template/en/default/admin/milestones/select-product.html.tmpl
index a9e53d3..587db6d 100644
--- a/BugsSite/template/en/default/admin/milestones/select-product.html.tmpl
+++ b/BugsSite/template/en/default/admin/milestones/select-product.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/milestones/updated.html.tmpl b/BugsSite/template/en/default/admin/milestones/updated.html.tmpl
deleted file mode 100644
index f559855..0000000
--- a/BugsSite/template/en/default/admin/milestones/updated.html.tmpl
+++ /dev/null
@@ -1,51 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
-  #%]
-
-[%# INTERFACE:
-  # product: object; Bugzilla::Product object representing the product to
-  #               which the milestone belongs.
-  #
-  # 'updated_XXX' variables are booleans, and are defined if the
-  # 'XXX' field was updated during the edit just being handled.
-  #%]
-
-[% title = BLOCK %]Updating Milestone '[% milestone.name FILTER html %]' of Product
-                   '[% product.name FILTER html %]'[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-[% IF updated_name %]
-  <p>Updated Milestone name to: '[% milestone.name FILTER html %]'.</p>
-[% END %]
-
-[% IF updated_sortkey %]
-  <p>Updated Milestone sortkey to: '[% milestone.sortkey FILTER html %]'.</p>
-[% END %]
-
-[% UNLESS updated_sortkey || updated_name %]
-  <p>Nothing changed for milestone '[% milestone.name FILTER html %]'.</p>
-
-[% END %]
-
-[% PROCESS admin/milestones/footer.html.tmpl %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/params/admin.html.tmpl b/BugsSite/template/en/default/admin/params/admin.html.tmpl
index f869572..639ea66 100644
--- a/BugsSite/template/en/default/admin/params/admin.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/admin.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/params/attachment.html.tmpl b/BugsSite/template/en/default/admin/params/attachment.html.tmpl
index 785d918..47d29bf 100644
--- a/BugsSite/template/en/default/admin/params/attachment.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/attachment.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -25,6 +24,39 @@
 %]
 
 [% param_descs = {
+  allow_attachment_display =>
+    "If this option is on, users will be able to view attachments from"
+    _ " their browser, if their browser supports the attachment's MIME type."
+    _ " If this option is off, users are forced to download attachments,"
+    _ " even if the browser is able to display them."
+    _ "<p>This is a security restriction for installations where untrusted"
+    _ " users may upload attachments that could be potentially damaging if"
+    _ " viewed directly in the browser.</p>"
+    _ "<p>It is highly recommended that you set the <tt>attachment_base</tt>"
+    _ " parameter if you turn this parameter on.",
+
+  attachment_base => 
+    "When the <tt>allow_attachment_display</tt> parameter is on, it is "
+    _ " possible for a malicious attachment to steal your cookies or"
+    _ " perform an attack on $terms.Bugzilla using your credentials."
+    _ "<p>If you would like additional security on attachments to avoid"
+    _ " this, set this parameter to an alternate URL for your $terms.Bugzilla"
+    _ " that is not the same as <tt>urlbase</tt> or <tt>sslbase</tt>."
+    _ " That is, a different domain name that resolves to this exact"
+    _ " same $terms.Bugzilla installation.</p>"
+    _ "<p>Note that if you have set the"
+    _ " <a href=\"editparams.cgi?section=core#cookiedomain\"><tt>cookiedomain</tt>"
+    _" parameter</a>, you should set <tt>attachment_base</tt> to use a"
+    _ " domain that would <em>not</em> be matched by"
+    _ " <tt>cookiedomain</tt>.</p>"
+    _ "<p>For added security, you can insert <tt>%bugid%</tt> into the URL,"
+    _ " which will be replaced with the ID of the current $terms.bug that"
+    _ " the attachment is on, when you access an attachment. This will limit"
+    _ " attachments to accessing only other attachments on the same"
+    _ " ${terms.bug}. Remember, though, that all those possible domain names "
+    _ " (such as <tt>1234.your.domain.com</tt>) must point to this same"
+    _ " $terms.Bugzilla instance.",
+
   allow_attachment_deletion => "If this option is on, administrators will be able to delete " _
                                "the content of attachments.",
 
diff --git a/BugsSite/template/en/default/admin/params/auth.html.tmpl b/BugsSite/template/en/default/admin/params/auth.html.tmpl
index df9f36f..94a748b 100644
--- a/BugsSite/template/en/default/admin/params/auth.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/auth.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -18,6 +17,7 @@
   #
   # Contributor(s): Dave Miller <justdave@bugzilla.org>
   #                 Frédéric Buclin <LpSolit@gmail.com>
+  #                 Marc Schumann <wurblzap@gmail.com>
   #%]
 [%
    title = "User Authentication"
@@ -67,12 +67,22 @@
                             ${terms.Bugzilla}'s built-in authentication. This is the most common
                             choice.
                           </dd>
+                          <dt>RADIUS</dt>
+                          <dd>
+                            RADIUS authentication using a RADIUS server.
+                            This method is experimental; please see the
+                            $terms.Bugzilla documentation for more information.
+                            Using this method requires
+                            <a href=\"?section=radius\">additional
+                            parameters</a> to be set.
+                          </dd>
                           <dt>LDAP</dt>
                           <dd>
                             LDAP authentication using an LDAP server.
                             Please see the $terms.Bugzilla documentation
                             for more information. Using this method requires
-                            additional parameters to be set above.
+                            <a href=\"?section=ldap\">additional
+                            parameters</a> to be set.
                           </dd>
                         </dl>",
 
diff --git a/BugsSite/template/en/default/admin/params/bugchange.html.tmpl b/BugsSite/template/en/default/admin/params/bugchange.html.tmpl
index b9475d4..458bc4e 100644
--- a/BugsSite/template/en/default/admin/params/bugchange.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/bugchange.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -25,6 +24,9 @@
 %]
 
 [% param_descs = {
+  duplicate_or_move_bug_status => "When $terms.abug is marked as a duplicate of another one " _
+                                  "or is moved to another installation, use this $terms.bug status."
+
   letsubmitterchoosepriority => "If this is on, then people submitting $terms.bugs can " _
                                 "choose an initial priority for that ${terms.bug}. " _
                                 "If off, then all $terms.bugs initially have the default " _
@@ -38,36 +40,15 @@
   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}?",
 
-  commentoncreate => "If this option is on, the user needs to enter a description " _
-                     "when entering a new ${terms.bug}.",
-
-  commentonaccept => "If this option is on, the user needs to enter a short comment if " _
-                     "he accepts the ${terms.bug}.",
-
   commentonclearresolution => "If this option is on, the user needs to enter a short comment if " _
                               "the ${terms.bug}'s resolution is cleared.",
 
-  commentonconfirm => "If this option is on, the user needs to enter a short comment " _
-                      "when confirming a ${terms.bug}.",
-
-  commentonresolve => "If this option is on, the user needs to enter a short comment if " _
-                      "the $terms.bug is resolved.",
-
-  commentonreassign => "If this option is on, the user needs to enter a short comment if " _
-                       "the $terms.bug is reassigned.",
+  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.",
 
-  commentonreopen => "If this option is on, the user needs to enter a short comment if " _
-                     "the $terms.bug is reopened.",
-
-  commentonverify => "If this option is on, the user needs to enter a short comment if " _
-                     "the $terms.bug is verified.",
-
-  commentonclose => "If this option is on, the user needs to enter a short comment if " _
-                    "the $terms.bug is closed.",
-
   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/BugsSite/template/en/default/admin/params/bugfields.html.tmpl b/BugsSite/template/en/default/admin/params/bugfields.html.tmpl
index 6bd108c..bdd9ad8 100644
--- a/BugsSite/template/en/default/admin/params/bugfields.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/bugfields.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/params/bugmove.html.tmpl b/BugsSite/template/en/default/admin/params/bugmove.html.tmpl
index 4797b61..911bc33 100644
--- a/BugsSite/template/en/default/admin/params/bugmove.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/bugmove.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/params/common.html.tmpl b/BugsSite/template/en/default/admin/params/common.html.tmpl
index 34cd1d3..c23c2ca 100644
--- a/BugsSite/template/en/default/admin/params/common.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/common.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -17,22 +16,29 @@
   # Rights Reserved.
   #
   # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+  #                 Marc Schumann <wurblzap@gmail.com>
   #%]
 [%# INTERFACE:
   # panel: hash representing the current panel.
   #%]
 
+[% sortlist_separator = '---' %]
+
 <dl>
   [% FOREACH param = panel.param_list %]
     <dt><a name="[% param.name FILTER html %]">[% param.name FILTER html %]</a></dt>
     <dd>[% panel.param_descs.${param.name} FILTER none %]
       <p>
       [% IF param.type == "t" %]
-        <input size="80" name="[% param.name FILTER html %]"
-               value="[% Param(param.name) FILTER html %]">
+        <input type="text" size="80" name="[% param.name FILTER html %]"
+               id="[% param.name FILTER html %]" value="[% Param(param.name) FILTER html %]">
+      [% ELSIF param.type == "p" %]
+        <input type="password" size="80" name="[% param.name FILTER html %]"
+               id="[% param.name FILTER html %]" value="[% Param(param.name) FILTER html %]"
+               autocomplete="off">
       [% ELSIF param.type == "l" %]
-        <textarea name="[% param.name FILTER html %]" rows="10" cols="80">
-            [% Param(param.name) FILTER html %]</textarea>
+        <textarea name="[% param.name FILTER html %]" id="[% param.name FILTER html %]"
+                  rows="10" cols="80">[% Param(param.name) FILTER html %]</textarea>
       [% ELSIF param.type == "b" %]
         <input type="radio" name="[% param.name FILTER html %]" id="[% param.name FILTER html %]-on"
                value=1 [% "checked=\"checked\"" IF Param(param.name) %]>
@@ -45,7 +51,7 @@
         [% boxSize = param.choices.size IF param.choices.size < 5 %]
 
         <select multiple="multiple" size="[% boxSize FILTER html %]"
-                name="[% param.name FILTER html %]">
+                name="[% param.name FILTER html %]" id="[% param.name FILTER html %]">
           [% FOREACH item = param.choices %]
             <option value="[% item FILTER html %]"
                     [% " selected=\"selected\"" IF lsearch(Param(param.name), item) != -1 %]>
@@ -53,8 +59,59 @@
             </option>
           [% END %]
         </select>
+      [% ELSIF param.type == "o" %]
+        <script type="text/javascript"><!--
+          document.write("<span style=\"display: none\">");
+        // -->
+        </script>
+        <input id="input_[% param.name FILTER html %]" size="80"
+               name="[% param.name FILTER html %]"
+               value="[% Param(param.name) FILTER html %]"><br>
+        <script type="text/javascript"><!--
+          document.write("<\/span>");
+        // -->
+        </script>
+        [% boxSize = 7 %]
+        [% boxSize = 3 + param.choices.size IF param.choices.size < 7 %]
+        [% plist = Param(param.name).split(',') %]
+
+        <script type="text/javascript"><!--
+          document.write(
+            '<table>' +
+            '  <tr>' +
+            '    <td rowspan="2">' +
+            '      <select id="select_[% param.name FILTER html %]"' +
+            '              size="[% boxSize FILTER html %]"' +
+            '              name="select_[% param.name FILTER html %]">' +
+                     [% FOREACH item = plist %]
+            '          <option value="[% item FILTER html %]">[% item FILTER html %]<\/option>' +
+                     [% END %]
+            '        <option class="sortlist_separator"' +
+            '                disabled="disabled"' +
+            '                value="[% sortlist_separator %]">active&uarr;&nbsp;&darr;inactive<\/option>' +
+                     [% FOREACH item = param.choices %]
+                       [% IF lsearch(plist, item) == -1 %]
+            '            <option value="[% item FILTER html %]">[% item FILTER html %]<\/option>' +
+                       [% END %]
+                     [% END %]
+            '      <\/select>' +
+            '    <\/td>' +
+            '    <td style="vertical-align: bottom">' +
+            '      <button type="button"' +
+            '              onClick="sortedList_moveItem(\'[% param.name FILTER html %]\', -1, \'[% sortlist_separator %]\');">&uarr;<\/button>' +
+            '    <\/td>' +
+            '  <\/tr>' +
+            '  <tr>' +
+            '    <td style="vertical-align: top">' +
+            '      <button type="button"' +
+            '              onClick="sortedList_moveItem(\'[% param.name FILTER html %]\', +1, \'[% sortlist_separator %]\');">&darr;<\/button>' +
+            '    <\/td>' +
+            '  <\/tr>' +
+            '<\/table>');
+        // -->
+        </script>
       [% ELSIF param.type == "s" %]
-        <select name="[% param.name FILTER html %]">
+        <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) %]>
diff --git a/BugsSite/template/en/default/admin/params/core.html.tmpl b/BugsSite/template/en/default/admin/params/core.html.tmpl
index 3fe66de..ae1d995f 100644
--- a/BugsSite/template/en/default/admin/params/core.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/core.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -36,7 +35,9 @@
                   "$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.",
+                  "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.",
@@ -59,7 +60,8 @@
                 "all sites served by this web server or virtual host to read " _
                 "$terms.Bugzilla cookies.",
 
-  timezone => "The timezone that your database server lives in. If set to '', " _
+  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 " _
diff --git a/BugsSite/template/en/default/admin/params/dependencygraph.html.tmpl b/BugsSite/template/en/default/admin/params/dependencygraph.html.tmpl
index 9ac2ad0..181cced 100644
--- a/BugsSite/template/en/default/admin/params/dependencygraph.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/dependencygraph.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/params/editparams.html.tmpl b/BugsSite/template/en/default/admin/params/editparams.html.tmpl
index 2a9b785..a35ec0f 100644
--- a/BugsSite/template/en/default/admin/params/editparams.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/editparams.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -57,6 +56,8 @@
    title = title
    message = message
    style_urls = ['skins/standard/params.css']
+   javascript_urls = ['js/params.js']
+   doc_section = "parameters.html"
 %]
 
 <table border="0" width="100%">
diff --git a/BugsSite/template/en/default/admin/params/groupsecurity.html.tmpl b/BugsSite/template/en/default/admin/params/groupsecurity.html.tmpl
index 6307779..440e1cf 100644
--- a/BugsSite/template/en/default/admin/params/groupsecurity.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/groupsecurity.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -25,7 +24,7 @@
 %]
 
 [% param_descs = {
-  makeproductgroups => "If this is on, $terms.Bugzilla will associate a $terms.bug group " _
+  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 " _
diff --git a/BugsSite/template/en/default/admin/params/index.html.tmpl b/BugsSite/template/en/default/admin/params/index.html.tmpl
index 317ac6c..bfa3e2c 100644
--- a/BugsSite/template/en/default/admin/params/index.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/index.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/params/l10n.html.tmpl b/BugsSite/template/en/default/admin/params/l10n.html.tmpl
deleted file mode 100644
index 1ab062c..0000000
--- a/BugsSite/template/en/default/admin/params/l10n.html.tmpl
+++ /dev/null
@@ -1,46 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer 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 = "Localization"
-   desc = "Define what languages you want made available to your users"
-%]
-
-[%# Get the list of available languages %]
-[% available_languages = "unknown" %]
-[% FOREACH param = params %]
-  [% IF param.name == "languages" %]
-    [% available_languages = param.extra_desc.available_languages FILTER html %]
-  [% END %]
-[% END %]
-
-[% param_descs = {
-  languages => "A comma-separated list of RFC 1766 language tags. These " _
-               "identify the languages in which you wish $terms.Bugzilla output " _
-               "to be displayed. Note that you must install the appropriate " _
-               "language pack before adding a language to this Param. The " _
-               "language used is the one in this list with the highest " _
-               "q-value in the user's Accept-Language header.<br> " _
-               "Available languages: $available_languages" ,
-
-  defaultlanguage => "The UI language $terms.Bugzilla falls back on if no suitable " _
-                     "language is found in the user's Accept-Language header." }
-%]
diff --git a/BugsSite/template/en/default/admin/params/ldap.html.tmpl b/BugsSite/template/en/default/admin/params/ldap.html.tmpl
index a3c7e46..cdc585d 100644
--- a/BugsSite/template/en/default/admin/params/ldap.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/ldap.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -30,10 +29,12 @@
                 "URI syntax can also be used, such as "_
                 "ldaps://ldap.company.com (for a secure connection) or " _
                 "ldapi://%2fvar%2flib%2fldap_sock (for a socket-based " _
-                "local connection.",
+                "local connection. Multiple hostnames or URIs can be comma " _
+                "separated; each will be tried in turn until a connection is " _
+                "established.",
 
-  LDAPstartls => "Whether to require encrypted communication once normal " _
-                 "LDAP connection achieved with the server.",
+  LDAPstarttls => "Whether to require encrypted communication once a normal " _
+                  "LDAP connection is achieved with the server.",
 
   LDAPbinddn => "If your LDAP server requires that you use a binddn and password " _
                 "instead of binding anonymously, enter it here " _
diff --git a/BugsSite/template/en/default/admin/params/mta.html.tmpl b/BugsSite/template/en/default/admin/params/mta.html.tmpl
index 7af38ed..800fbad 100644
--- a/BugsSite/template/en/default/admin/params/mta.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/mta.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -56,6 +55,12 @@
 
   smtpserver => "The SMTP server address (if using SMTP for mail delivery).",
 
+  smtp_username => "The username to pass to the SMTP server for SMTP authentication. " _
+                   "Leave this field empty if your SMTP server doesn't require authentication.",
+
+  smtp_password => "The password to pass to the SMTP server for SMTP authentication. " _
+                   "This field has no effect if the smtp_username parameter is left empty.",
+
   smtp_debug => "If enabled, this will print detailed information to your" _
                 " web server's error log about the communication between" _
                 " $terms.Bugzilla and your SMTP server. You can use this to" _
diff --git a/BugsSite/template/en/default/admin/params/patchviewer.html.tmpl b/BugsSite/template/en/default/admin/params/patchviewer.html.tmpl
index ad15262..389acc1 100644
--- a/BugsSite/template/en/default/admin/params/patchviewer.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/patchviewer.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/params/query.html.tmpl b/BugsSite/template/en/default/admin/params/query.html.tmpl
index cdc4847..8d6aba4 100644
--- a/BugsSite/template/en/default/admin/params/query.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/query.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -21,7 +20,7 @@
   #%]
 [%
    title = "Query Defaults"
-   desc = "Default options for query and buglists"
+   desc = "Default options for query and $terms.bug lists"
 %]
 
 [% param_descs = {
diff --git a/BugsSite/template/en/default/admin/params/radius.html.tmpl b/BugsSite/template/en/default/admin/params/radius.html.tmpl
new file mode 100644
index 0000000..ef2282d
--- /dev/null
+++ b/BugsSite/template/en/default/admin/params/radius.html.tmpl
@@ -0,0 +1,54 @@
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # The Initial Developer of the Original Code is Marc Schumann.
+  # Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
+  # All rights reserved.
+  #
+  # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+  #%]
+[%
+   title = "RADIUS"
+   desc = "Configure this first before choosing RADIUS as an authentication method"
+%]
+
+[% param_descs = {
+  RADIUS_server => "The name (and optionally port) of your RADIUS server " _
+                   "(e.g. <code>radius.company.com</code>, or " _
+                   "<code>radius.company.com:portnum</code>).<br>" _
+                   "Required only if " _
+                   "<a href=\"?section=auth#user_verify_class\">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 " _
+                   "<code>user_verify_class</code> parameter</a> contains " _
+                   "<code>RADIUS</code>.",
+
+  RADIUS_NAS_IP => "The NAS-IP-Address attribute to be used when exchanging " _
+                   "data with your RADIUS server. " _
+                   "If unspecified, <code>127.0.0.1</code> will be used.<br>" _
+                   "Useful only if " _
+                   "<a href=\"?section=auth#user_verify_class\">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 " _
+                         "<code>user_verify_class</code> parameter</a> " _
+                         "contains <code>RADIUS</code>.",
+  }
+%]
diff --git a/BugsSite/template/en/default/admin/params/shadowdb.html.tmpl b/BugsSite/template/en/default/admin/params/shadowdb.html.tmpl
index 7d03838..c812284 100644
--- a/BugsSite/template/en/default/admin/params/shadowdb.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/shadowdb.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/params/usermatch.html.tmpl b/BugsSite/template/en/default/admin/params/usermatch.html.tmpl
index 66f22ce..178a728 100644
--- a/BugsSite/template/en/default/admin/params/usermatch.html.tmpl
+++ b/BugsSite/template/en/default/admin/params/usermatch.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/products/confirm-delete.html.tmpl b/BugsSite/template/en/default/admin/products/confirm-delete.html.tmpl
index 530b0ce..e5e982b 100644
--- a/BugsSite/template/en/default/admin/products/confirm-delete.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/confirm-delete.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -256,6 +255,8 @@
 
 [% END %]
 
+[% Hook.process("confirmation") %]
+
 [% IF product.bug_count == 0 || Param('allowbugdeletion') %]
 
   <p>Do you really want to delete this product?</p>
diff --git a/BugsSite/template/en/default/admin/products/create.html.tmpl b/BugsSite/template/en/default/admin/products/create.html.tmpl
index 5fb7d8b..e1cd381 100644
--- a/BugsSite/template/en/default/admin/products/create.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/products/created.html.tmpl b/BugsSite/template/en/default/admin/products/created.html.tmpl
deleted file mode 100644
index f198542..0000000
--- a/BugsSite/template/en/default/admin/products/created.html.tmpl
+++ /dev/null
@@ -1,36 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # Contributor(s): Gabriel S. Oliveira <gabriel@async.com.br>
-  #%]
-
-[%# INTERFACE:
-  # product: Bugzilla::Product object; the Product created.
-  #
-  #%]
-
-[% PROCESS global/header.html.tmpl 
-  title = 'New Product Created'
-%]
-<br>
-<div style='border: 1px red solid; padding: 1ex;'>
-  <b>You will need to   
-   <a href="editcomponents.cgi?action=add&product=[% product.name FILTER url_quote %]">
-     add at least one component
-   </a> before you can enter [% terms.bugs %] against this product
-  </b>
-</div>
-
-[% PROCESS "admin/products/footer.html.tmpl" %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/products/deleted.html.tmpl b/BugsSite/template/en/default/admin/products/deleted.html.tmpl
deleted file mode 100644
index 049bcae..0000000
--- a/BugsSite/template/en/default/admin/products/deleted.html.tmpl
+++ /dev/null
@@ -1,50 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # Contributor(s): Tiago R. Mello <timello@async.com.br>
-  #
-  #%]
-
-[%# INTERFACE:
-  # product: Bugzilla::Product object; The product
-  #
-  #%]
-
-[% PROCESS global/header.html.tmpl
-  title = 'Deleting product'
-%]
-
-[% IF product.bug_count %]
-  All references to deleted [% terms.bugs %] removed.
-[% END %]
-
-<p>
-  Components deleted.<br>
-  Versions deleted.<br>
-  Milestones deleted.
-</p>
-
-<p>
-  Group controls deleted.<br>
-  Flag inclusions and exclusions deleted.
-</p>
-
-<p>
-  Product [% product.name FILTER html %] deleted.
-</p>
-
-[% PROCESS admin/products/footer.html.tmpl
-           no_edit_product_link = 1
-%]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/products/edit-common.html.tmpl b/BugsSite/template/en/default/admin/products/edit-common.html.tmpl
index 1a1ecab..c05a878 100644
--- a/BugsSite/template/en/default/admin/products/edit-common.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/edit-common.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -107,7 +106,7 @@
   <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">[% status_descs.UNCONFIRMED FILTER html %]</a>
+    <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 %]">
diff --git a/BugsSite/template/en/default/admin/products/edit.html.tmpl b/BugsSite/template/en/default/admin/products/edit.html.tmpl
index 72a5532..2d346c6 100644
--- a/BugsSite/template/en/default/admin/products/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -23,17 +22,21 @@
   #
   # classification: Bugzilla::Classification object; The classification 
   #                                                  the product is in
-  #
-  # groups_controls: a hash of group controls related to the product.
   #%]
 
-[% title = BLOCK %]Edit Product[% END %]
+[% title = BLOCK %]Edit Product '[% product.name FILTER html %]'[% END %]
 
 [% PROCESS global/header.html.tmpl
   title = title
   style_urls = ['skins/standard/admin.css']
 %]
 
+[% group_control = {${constants.CONTROLMAPNA}        => 'NA',
+                    ${constants.CONTROLMAPSHOWN}     => 'Shown',
+                    ${constants.CONTROLMAPDEFAULT}   => 'Default',
+                    ${constants.CONTROLMAPMANDATORY} => 'Mandatory'}
+ %]
+
 <form method="post" action="editproducts.cgi">
   <table border="0" cellpadding="4" cellspacing="0">
 
@@ -104,12 +107,12 @@
         </a>
       </th>
       <td>
-        [% IF group_controls.size %]
-          [% FOREACH g = group_controls.values %]
+        [% IF product.group_controls.size %]
+          [% FOREACH g = product.group_controls.values %]
             <b>[% g.group.name FILTER html %]:</b>&nbsp;
             [% IF g.group.isactive %]
-              [% g.membercontrol FILTER html %]/
-              [% g.othercontrol FILTER html %]
+              [% group_control.${g.membercontrol} FILTER html %]/
+              [% group_control.${g.othercontrol} FILTER html %]
               [% IF g.entry %], ENTRY[% END %]
               [% IF g.canedit %], CANEDIT[% END %]
               [% IF g.editcomponents %], editcomponents[% END %]
@@ -138,7 +141,7 @@
   <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="Update">
+  <input type="submit" name="submit" value="Save Changes">
 </form>
   
 [% PROCESS "admin/products/footer.html.tmpl"
diff --git a/BugsSite/template/en/default/admin/products/footer.html.tmpl b/BugsSite/template/en/default/admin/products/footer.html.tmpl
index d13f78c..4b8fe05 100644
--- a/BugsSite/template/en/default/admin/products/footer.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/footer.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl b/BugsSite/template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl
index e215676..1fc92c9 100644
--- a/BugsSite/template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/groupcontrol/confirm-edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/products/groupcontrol/edit.html.tmpl b/BugsSite/template/en/default/admin/products/groupcontrol/edit.html.tmpl
index 819c8e5..c793ff6 100644
--- a/BugsSite/template/en/default/admin/products/groupcontrol/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/groupcontrol/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -288,7 +287,7 @@
       [% terms.Bugs %] in this product are permitted to be restricted to this
       group and are placed in this group by default.  Users who are members of this group
       will be able to place [% terms.bugs %] in this group.  Non-members will be
-      able to restrict [% terms.bugs %] to this group on entry and will do so by default
+      able to restrict [% terms.bugs %] to this group on entry and will do so by default.
     </td>
   </tr>
   <tr>
diff --git a/BugsSite/template/en/default/admin/products/groupcontrol/updated.html.tmpl b/BugsSite/template/en/default/admin/products/groupcontrol/updated.html.tmpl
index 5719bc6..52456a47 100644
--- a/BugsSite/template/en/default/admin/products/groupcontrol/updated.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/groupcontrol/updated.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/products/list-classifications.html.tmpl b/BugsSite/template/en/default/admin/products/list-classifications.html.tmpl
index 5372170..4eddad3 100644
--- a/BugsSite/template/en/default/admin/products/list-classifications.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/list-classifications.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/products/list.html.tmpl b/BugsSite/template/en/default/admin/products/list.html.tmpl
index 0992de2..3f15769 100644
--- a/BugsSite/template/en/default/admin/products/list.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/products/updated.html.tmpl b/BugsSite/template/en/default/admin/products/updated.html.tmpl
index 372f158..4d5f518 100644
--- a/BugsSite/template/en/default/admin/products/updated.html.tmpl
+++ b/BugsSite/template/en/default/admin/products/updated.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -97,13 +96,13 @@
   <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>
+    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>.
+     <br><a href="[%- product.milestone_url FILTER html %]">
+     [%- product.milestone_url FILTER html %]</a>.
   [% ELSE %]
     be empty.
   [% END %]
diff --git a/BugsSite/template/en/default/admin/sanitycheck/list.html.tmpl b/BugsSite/template/en/default/admin/sanitycheck/list.html.tmpl
new file mode 100644
index 0000000..4642972
--- /dev/null
+++ b/BugsSite/template/en/default/admin/sanitycheck/list.html.tmpl
@@ -0,0 +1,37 @@
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # The Initial Developer of the Original Code is Frédéric Buclin.
+  #
+  # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+  #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl title = "Sanity Check"
+                                   style_urls = ['skins/standard/admin.css'] %]
+
+<div>
+  <p>
+    [% terms.Bugzilla %] is checking the referential integrity of your database.
+    This may take several minutes to complete.
+  </p>
+
+  <p>
+    Errors, if any, will be <span class="alert">emphasized like this</span>.
+    Depending on the errors found, some links will be displayed allowing you
+    to easily fix them. Fixing these errors will automatically run this script
+    again (so be aware that it may take an even longer time than the first run).
+  </p>
+</div>
+
+<hr>
diff --git a/BugsSite/template/en/default/admin/sanitycheck/messages.html.tmpl b/BugsSite/template/en/default/admin/sanitycheck/messages.html.tmpl
new file mode 100644
index 0000000..2847a35
--- /dev/null
+++ b/BugsSite/template/en/default/admin/sanitycheck/messages.html.tmpl
@@ -0,0 +1,330 @@
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # The Initial Developer of the Original Code is Frédéric Buclin.
+  #
+  # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+  #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% san_message = BLOCK %]
+  [% IF san_tag == "checks_start" %]
+    OK, now running sanity checks.
+
+  [% ELSIF san_tag == "checks_completed" %]
+    Sanity check completed.
+
+  [% ELSIF san_tag == "attachment_reference_deletion_start" %]
+    OK, now removing all references to deleted attachments.
+
+  [% ELSIF san_tag == "attachment_reference_deletion_end" %]
+    All references to deleted attachments have been removed.
+
+  [% ELSIF san_tag == "bug_check_alert" %]
+    [% errortext FILTER html %]: [% INCLUDE bug_list badbugs = badbugs %]
+
+  [% ELSIF san_tag == "bug_check_repair" %]
+    <a href="sanitycheck.cgi?[% param FILTER url_quote %]=1">[% text FILTER html %]</a>.
+
+  [% ELSIF san_tag == "bug_check_creation_date" %]
+    Checking for [% terms.bugs %] with no creation date (which makes them invisible).
+
+  [% ELSIF san_tag == "bug_check_creation_date_error_text" %]
+    [% terms.Bugs %] with no creation date
+
+  [% ELSIF san_tag == "bug_check_creation_date_repair_text" %]
+    Repair missing creation date for these [% terms.bugs %]
+
+  [% ELSIF san_tag == "bug_check_bugs_fulltext" %]
+    Checking for [% terms.bugs %] with no entry for full text searching.
+
+  [% ELSIF san_tag == "bug_check_bugs_fulltext_error_text" %]
+    [% terms.Bugs %] with no entry for full text searching
+
+  [% ELSIF san_tag == "bug_check_bugs_fulltext_repair_text" %]
+    Repair missing full text search entries for these [% terms.bugs %]
+
+  [% ELSIF san_tag == "bug_check_res_dupl" %]
+    Checking resolution/duplicates
+
+  [% ELSIF san_tag == "bug_check_res_dupl_error_text" %]
+    [% terms.Bugs %] found on duplicates table that are not marked duplicate
+
+  [% ELSIF san_tag == "bug_check_res_dupl_error_text2" %]
+    [% terms.Bugs %] found marked resolved duplicate and not on duplicates table
+
+  [% ELSIF san_tag == "bug_check_status_res" %]
+    Checking statuses/resolutions
+
+  [% ELSIF san_tag == "bug_check_status_res_error_text" %]
+    [% terms.Bugs %] with open status and a resolution
+
+  [% ELSIF san_tag == "bug_check_status_res_error_text2" %]
+    [% terms.Bugs %] with non-open status and no resolution
+
+  [% ELSIF san_tag == "bug_check_status_everconfirmed" %]
+    Checking statuses/everconfirmed
+
+  [% ELSIF san_tag == "bug_check_status_everconfirmed_error_text" %]
+    [% terms.Bugs %] that are UNCONFIRMED but have everconfirmed set
+
+  [% ELSIF san_tag == "bug_check_status_everconfirmed_error_text2" %]
+    [% terms.Bugs %] with confirmed status but don't have everconfirmed set
+
+  [% ELSIF san_tag == "bug_check_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
+
+  [% ELSIF san_tag == "bug_check_control_values_alert" %]
+    Found [% entries FILTER html %] bad group_control_map entries
+
+  [% ELSIF san_tag == "bug_check_control_values_violation" %]
+    Checking for [% terms.bugs %] with groups violating their product's group controls
+
+  [% ELSIF san_tag == "bug_check_control_values_error_text" %]
+    Have groups not permitted for their products
+
+  [% ELSIF san_tag == "bug_check_control_values_repair_text" %]
+    Permit the missing groups for the affected products
+    (set member control to <code>SHOWN</code>)
+
+  [% ELSIF san_tag == "bug_check_control_values_error_text2" %]
+    Are missing groups required for their products
+
+  [% ELSIF san_tag == "bug_creation_date_start" %]
+    OK, now fixing missing [% terms.bug %] creation dates.
+
+  [% ELSIF san_tag == "bug_creation_date_fixed" %]
+    [% bug_count FILTER html %] [%+ terms.bugs %] have been fixed.
+
+  [% ELSIF san_tag == "bugs_fulltext_start" %]
+    OK, now fixing [% terms.bug %] entries for full text searching.
+
+  [% ELSIF san_tag == "bugs_fulltext_fixed" %]
+    [% bug_count FILTER html %] [%+ terms.bugs %] have been fixed.
+
+  [% ELSIF san_tag == "bug_reference_deletion_start" %]
+    OK, now removing all references to deleted [% terms.bugs %].
+
+  [% ELSIF san_tag == "bug_reference_deletion_end" %]
+    All references to deleted [% terms.bugs %] have been removed.
+
+  [% ELSIF san_tag == "cross_check_to" %]
+    Checking references to [% table FILTER html %].[% field FILTER html %]...
+
+  [% ELSIF san_tag == "cross_check_from" %]
+    ... from [% table FILTER html %].[% field FILTER html %].
+
+  [% ELSIF san_tag == "cross_check_alert" %]
+    Bad value '[% value FILTER html %]' found in
+    [%+ table FILTER html %].[% field FILTER html %]
+    [% IF keyname %]
+      [% IF keyname == "bug_id" %]
+        ([% PROCESS bug_link bug_id = key %])
+      [% ELSE %]
+        ([% keyname FILTER html %] == '[% key FILTER html %]')
+      [% END %]
+    [% END %]
+
+  [% ELSIF san_tag == "cross_check_attachment_has_references" %]
+    <a href="sanitycheck.cgi?remove_invalid_attach_references=1">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
+    invalid references to non existent [% terms.bugs %].</a>
+
+  [% ELSIF san_tag == "double_cross_check_to" %]
+    Checking references to [% table FILTER html %].[% field1 FILTER html %] /
+    [%+ table FILTER html %].[% field2 FILTER html %]...
+
+  [% ELSIF san_tag == "double_cross_check_from" %]
+    ... from [% table FILTER html %].[% field1 FILTER html %] /
+    [%+ table FILTER html %].[% field2 FILTER html %].
+
+  [% ELSIF san_tag == "double_cross_check_alert" %]
+    Bad values '[% value1 FILTER html %]', '[% value2 FILTER html %]' found
+    in [% table FILTER html %].[% field1 FILTER html %] /
+    [%+ table FILTER html %].[% field2 FILTER html %].
+    [% IF keyname %]
+      [% IF keyname == "bug_id" %]
+        ([% PROCESS bug_link bug_id = key %])
+      [% ELSE %]
+        ([% keyname FILTER html %] == '[% key FILTER html %]')
+      [% END %]
+    [% END %]
+
+  [% ELSIF san_tag == "flag_check_start" %]
+    Checking for flags being in the wrong product/component.
+
+  [% ELSIF san_tag == "flag_deletion_start" %]
+    OK, now deleting invalid flags.
+
+  [% ELSIF san_tag == "flag_deletion_end" %]
+    Invalid flags deleted.
+
+  [% ELSIF san_tag == "flag_alert" %]
+    Invalid flag [% flag_id FILTER html %] for
+    [% IF attach_id %]
+      attachment [% attach_id FILTER html %] in
+    [% END %]
+    [%+ PROCESS bug_link bug_id = bug_id %].
+
+  [% ELSIF san_tag == "flag_fix" %]
+    <a href="sanitycheck.cgi?remove_invalid_flags=1">Click
+    here to delete invalid flags</a>
+
+  [% ELSIF san_tag == "group_control_map_entries_creation" %]
+    OK, now creating <code>SHOWN</code> member control entries
+    for product/group combinations lacking one.
+
+  [% ELSIF san_tag == "group_control_map_entries_update" %]
+    Updating <code>NA/<em>xxx</em></code> group control setting
+    for group <em>[% group_name FILTER html %]</em> to
+    <code>SHOWN/<em>xxx</em></code> in product
+    <em>[% product_name FILTER html %]</em>.
+
+  [% ELSIF san_tag == "group_control_map_entries_generation" %]
+    Generating <code>SHOWN/NA</code> group control setting
+    for group <em>[% group_name FILTER html %]</em> in product
+    <em>[% product_name FILTER html %]</em>.
+
+  [% ELSIF san_tag == "group_control_map_entries_repaired" %]
+    Repaired [% counter FILTER html %] defective group control settings.
+
+  [% ELSIF san_tag == "keyword_check_start" %]
+    Checking keywords table.
+
+  [% ELSIF san_tag == "keyword_check_alert" %]
+    Duplicate entry in keyworddefs for id [% id FILTER html %].
+
+  [% ELSIF san_tag == "keyword_check_invalid_name" %]
+    Bogus name in keyworddefs for id [% id FILTER html %].
+
+  [% ELSIF san_tag == "keyword_check_invalid_id" %]
+    Bogus keywordids [% id FILTER html %] found in keywords table.
+
+  [% ELSIF san_tag == "keyword_check_duplicated_ids" %]
+    Duplicate keyword IDs found in [% PROCESS bug_link bug_id = id %].
+
+  [% ELSIF san_tag == "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.
+
+  [% ELSIF san_tag == "profile_login_alert" %]
+    Bad profile email address, id=[% id FILTER html %],
+    &lt;[% email FILTER html %]&gt;.
+
+  [% ELSIF san_tag == "repair_bugs" %]
+    Repair these [% terms.bugs %].
+
+  [% ELSIF san_tag == "send_bugmail_start" %]
+    OK, now attempting to send unsent mail.
+
+  [% ELSIF san_tag == "send_bugmail_status" %]
+    [% bug_count FILTER html %] [%+ terms.bugs %] found with
+    possibly unsent mail.
+
+  [% ELSIF san_tag == "send_bugmail_end" %]
+    Unsent mail has been sent.
+
+  [% ELSIF san_tag == "unsent_bugmail_check" %]
+    Checking for unsent mail
+
+  [% ELSIF san_tag == "unsent_bugmail_alert" %]
+    [% terms.Bugs %] that have changes but no mail sent for at least
+    half an hour: [% INCLUDE bug_list badbugs = badbugs %]
+
+  [% ELSIF san_tag == "unsent_bugmail_fix" %]
+    <a href="sanitycheck.cgi?rescanallBugMail=1">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 %].
+
+  [% ELSIF san_tag == "whines_obsolete_target_deletion_start" %]
+    OK, now removing non-existent users/groups from whines.
+
+  [% ELSIF san_tag == "whines_obsolete_target_deletion_end" %]
+    Non-existent users/groups have been removed from whines.
+
+  [% ELSIF san_tag == "whines_obsolete_target_start" %]
+    Checking for whines with non-existent users/groups.
+
+  [% ELSIF san_tag == "whines_obsolete_target_alert" %]
+    [% FOREACH schedule = schedules %]
+      Non-existent [% (type == constants.MAILTO_USER) ? "user" : "group" FILTER html %]
+      [%+ schedule.1 FILTER html %] for whine schedule [% schedule.0 FILTER html %]<br>
+    [% END %]
+
+  [% ELSIF san_tag == "whines_obsolete_target_fix" %]
+    <a href="sanitycheck.cgi?remove_old_whine_targets=1">Click here to
+    remove old users/groups</a>
+
+  [% END %]
+[% END %]
+
+[% san_message FILTER html %]
+
+
+[% BLOCK bug_list %]
+  [% FOREACH bug_id = badbugs %]
+    [%# Do not use FILTER bug_link() here, because bug_link() calls get_text()
+     # which itself calls this template again, generating a recursion error.
+     # I doubt having a tooltip with the bug status and summary is so
+     # important here anyway, as you can click the "(as buglist)" link. %]
+    <a href="show_bug.cgi?id=[% bug_id FILTER url_quote %]">[% bug_id FILTER html %]</a>
+    [% ", " IF !loop.last %]
+  [% END %]
+  (<a href="buglist.cgi?bug_id=[% badbugs.join(",") FILTER url_quote %]">as [% terms.bug %] list</a>).
+[% END %]
+
+[% BLOCK bug_link %]
+  <a href="show_bug.cgi?id=[% bug_id FILTER url_quote %]">[% terms.bug %] [%+ bug_id FILTER html %]</a>
+[% END %]
diff --git a/BugsSite/template/en/default/admin/settings/edit.html.tmpl b/BugsSite/template/en/default/admin/settings/edit.html.tmpl
index 08a370c..7f95f88 100644
--- a/BugsSite/template/en/default/admin/settings/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/settings/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -16,8 +15,7 @@
   #%]
 
 [%# INTERFACE:
-  # setting_names: an array of strings
-  # settings:      a hash of hashes, keyed by setting_name.
+  # settings:      a hash of hashes, keyed by setting name.
   #                Each hash contains:
   #                 is_enabled    - boolean
   #                 default_value - string (global default for this setting)
@@ -57,7 +55,7 @@
         <th>Enabled</th>
       </tr>
 
-      [% FOREACH name = setting_names %]
+      [% FOREACH name = settings.keys %]
           [% checkbox_name = name _ '-enabled' %]
           <tr>
             <td align="right">
diff --git a/BugsSite/template/en/default/admin/settings/updated.html.tmpl b/BugsSite/template/en/default/admin/settings/updated.html.tmpl
deleted file mode 100644
index 0b83a27..0000000
--- a/BugsSite/template/en/default/admin/settings/updated.html.tmpl
+++ /dev/null
@@ -1,27 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
-  #
-  #%]
-
-[% PROCESS global/header.html.tmpl
-   title = "Default Preferences Updated"
- %]
-
-Your changes to the Default Preferences have been saved.<br>
-<br>
-Return to the <a
-href="editsettings.cgi?action=load">Default Preferences</a> page.
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/sudo.html.tmpl b/BugsSite/template/en/default/admin/sudo.html.tmpl
index 4b7b607..680bcfb 100644
--- a/BugsSite/template/en/default/admin/sudo.html.tmpl
+++ b/BugsSite/template/en/default/admin/sudo.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -24,6 +23,7 @@
 [% PROCESS global/header.html.tmpl
    title = "Begin sudo session"
    style_urls = ['skins/standard/admin.css']
+   doc_section = "useradmin.html#impersonatingusers"
  %]
  
 [% DEFAULT target_login = "" %]
@@ -52,10 +52,9 @@
     [% INCLUDE global/userselect.html.tmpl
        id => "target_login"
        name => "target_login"
-       value => "$target_login_default"
+       value => target_login_default
        accesskey => "u"
        size => 30
-       multiple => 5
     %]
   </p>
   
diff --git a/BugsSite/template/en/default/admin/table.html.tmpl b/BugsSite/template/en/default/admin/table.html.tmpl
index d13dceb..303aba7 100644
--- a/BugsSite/template/en/default/admin/table.html.tmpl
+++ b/BugsSite/template/en/default/admin/table.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -71,7 +70,7 @@
 
 [%###################  TABLE HEADER   ######################%]
 
-<table border="1" cellpadding="4" cellspacing="0">
+<table id="admin_table" border="1" cellpadding="4" cellspacing="0">
   <tr bgcolor="#6666FF">
     [% FOREACH c = columns %]
       [%# Default to align left for headers %]
diff --git a/BugsSite/template/en/default/admin/users/confirm-delete.html.tmpl b/BugsSite/template/en/default/admin/users/confirm-delete.html.tmpl
index aa3d6da..3852839 100644
--- a/BugsSite/template/en/default/admin/users/confirm-delete.html.tmpl
+++ b/BugsSite/template/en/default/admin/users/confirm-delete.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -49,18 +48,13 @@
   title = title
   style_urls = ['skins/standard/admin.css',
                 'skins/standard/editusers.css']
+  doc_section = "useradmin.html#user-account-deletion"
 %]
 
 [% PROCESS admin/users/listselectvars.html.tmpl
   listselectionvalues = listselectionvalues
 %]
 
-[% responsibilityterms = {
-  'default_assignee'   => 'Default Assignee',
-  'default_qa_contact' => 'Default QA Contact'
-  }
-%]
-
 <table class="main">
   <tr>
     <th>Login name:</th>
@@ -88,30 +82,7 @@
     <tr>
       <th>Product responsibilities:</th>
       <td>
-        <ul>
-          [% FOREACH component = otheruser.product_responsibilities %]
-            <li>
-              [% andstring = '' %]
-              [% FOREACH responsibility = ['default_assignee', 'default_qa_contact'] %]
-                [% IF component.${responsibility}.id == otheruser.id %]
-                  [% andstring %] [% responsibilityterms.$responsibility %]
-                  [% andstring = ' and ' %]
-                [% END %]
-              [% END %]
-              for
-              [% IF user.in_group("editcomponents", component.product_id) %]
-                <a href="editcomponents.cgi?action=edit&amp;product=
-                         [% component.product.name FILTER url_quote %]&amp;component=
-                         [% component.name FILTER url_quote %]">
-              [% END %]
-                [%+ component.product.name FILTER html %]:
-                [% component.name FILTER html %]
-              [% IF user.in_group("editcomponents", component.product_id) %]
-                </a>
-              [% END %]
-            </li>
-          [% END %]
-        </ul>
+        [% PROCESS admin/users/responsibilities.html.tmpl otheruser = otheruser %]
       </td>
     </tr>
   [% END %]
@@ -130,17 +101,34 @@
       For now, you can
     [% END %]
 [% ELSE %]
+  [% accept_deletion = 1 %]
 
-  <h2>Confirmation</h2>
-
-  [% display_warning = 0 %]
-
-  [% IF reporter || bugs_activity || flags.setter || longdescs || profiles_activity %]
+  [% IF attachments || reporter || bugs_activity || flags.setter || longdescs || profiles_activity %]
     <div class="criticalmessages">
-      <p>The following deletions are <b>highly not recommended</b> and
-      will generate referential integrity inconsistencies!</p>
+      <p>The following deletions are <b>unsafe</b> and would generate referential
+      integrity inconsistencies!</p>
 
       <ul>
+        [% IF attachments %]
+          <li>
+            [% otheruser.login FILTER html %]
+            <a href="buglist.cgi?field0-0-0=attachments.submitter&type0-0-0=equals&value0-0-0=
+               [%- otheruser.login FILTER url_quote %]">has submitted
+            [% IF attachments == 1 %]
+              one attachment
+            [% ELSE %]
+              [%+ attachments %] attachments
+            [% END %]</a>.
+            If you delete the user account, the database records will be
+            inconsistent, resulting in
+            [% IF attachments == 1 %]
+              this attachment
+            [% ELSE %]
+              these attachments
+            [% END %]
+            not appearing in [% terms.bugs %] any more.
+          </li>
+        [% END %]
         [% IF reporter %]
           <li>
             [% otheruser.login FILTER html %]
@@ -234,11 +222,11 @@
         [% END %]
       </ul>
     </div>
-    [% display_warning = 1 %]
+    [% accept_deletion = 0 %]
   [% END %]
 
-  [% IF assignee_or_qa || cc || email_setting || flags.requestee ||
-        namedqueries || profile_setting || series || votes || watch.watched ||
+  [% 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 %]
     <div class="warningmessages">
       <p>The following deletions are <b>safe</b> and will not generate
@@ -274,6 +262,17 @@
             If you delete the user account, it will be removed from these CC lists.
           </li>
         [% END %]
+        [% IF component_cc %]
+          <li>
+            [% otheruser.login FILTER html %] is on the default CC list of
+            [% IF component_cc == 1 %]
+              one component
+            [% ELSE %]
+              [%+ component_cc %] components
+            [% END %].
+            If you delete the user account, it will be removed from these CC lists.
+          </li>
+        [% END %]
         [% IF email_setting %]
           <li>
             The user's e-mail settings will be deleted along with the user
@@ -356,6 +355,23 @@
             will be deleted along with the user account.
           </li>
         [% END %]
+        [% IF quips %]
+          <li>
+            [% otheruser.login FILTER html %] has submitted
+            [% IF quips == 1 %]
+              a quip
+            [% ELSE %]
+              [%+ quips %] quips
+            [% END %].
+            If you delete the user account,
+            [% IF quips == 1 %]
+              this quip
+            [% ELSE %]
+              these quips
+            [% END %]
+            will have no author anymore, but will remain available.
+          </li>
+        [% END %]
         [% IF votes %]
           <li>
             [% otheruser.login FILTER html %] has voted on
@@ -431,28 +447,28 @@
         [% END %]
       </ul>
     </div>
-    [% display_warning = 1 %]
+
+    [% IF accept_deletion %]
+      <p class="areyoureallyreallysure">
+        Please be aware of the consequences of this before continuing.
+      </p>
+      <p>Do you really want to delete this user account?</p>
+
+      <form method="post" action="editusers.cgi">
+        <p>
+          <input type="submit" id="delete" value="Yes, delete"/>
+          <input type="hidden" name="action" value="delete" />
+          <input type="hidden" name="userid" value="[% otheruser.id %]" />
+          <input type="hidden" name="token" value="[% token FILTER html %]">
+          [% INCLUDE listselectionhiddenfields %]
+        </p>
+      </form>
+      <p>If you do not want to delete the user account at this time,
+    [% ELSE %]
+      <p><b>You cannot delete this user account</b> due to unsafe actions reported above. You can
+    [% END %]
+
   [% END %]
-
-  [% IF display_warning %]
-    <p class="areyoureallyreallysure">
-      Please be aware of the consequences of this before continuing.
-    </p>
-  [% END %]
-
-  <p>Do you really want to delete this user account?</p>
-
-  <form method="post" action="editusers.cgi">
-    <p>
-      <input type="submit" id="delete" value="Yes, delete"/>
-      <input type="hidden" name="action" value="delete" />
-      <input type="hidden" name="userid" value="[% otheruser.id %]" />
-      <input type="hidden" name="token" value="[% token FILTER html %]">
-      [% INCLUDE listselectionhiddenfields %]
-    </p>
-  </form>
-
-  <p>If you do not want to delete the user account at this time,
 [% END %]
 
   <a href="editusers.cgi?action=edit&amp;userid=[% otheruser.id %]
diff --git a/BugsSite/template/en/default/admin/users/create.html.tmpl b/BugsSite/template/en/default/admin/users/create.html.tmpl
index 66cdd91..6fd5b67 100644
--- a/BugsSite/template/en/default/admin/users/create.html.tmpl
+++ b/BugsSite/template/en/default/admin/users/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -24,6 +23,7 @@
   title = "Add user"
   style_urls = ['skins/standard/editusers.css']
   onload = "document.forms['f'].login.focus()"
+  doc_section = "useradmin.html#createnewusers"
 %]
 
 [% PROCESS admin/users/listselectvars.html.tmpl
diff --git a/BugsSite/template/en/default/admin/users/edit.html.tmpl b/BugsSite/template/en/default/admin/users/edit.html.tmpl
index abc1246..3efa4b8 100644
--- a/BugsSite/template/en/default/admin/users/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/users/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -30,7 +29,8 @@
 [% PROCESS global/header.html.tmpl
   title = title
   message = message
-  style_urls = ['skins/standard/editusers.css']
+  style_urls = ['skins/standard/admin.css', 'skins/standard/editusers.css']
+  doc_section = "useradmin.html#modifyusers"
 %]
 
 [% PROCESS admin/users/listselectvars.html.tmpl
@@ -52,7 +52,7 @@
           <tr>
             [% IF editusers %]
               <th colspan="3">
-                Can turn these [% terms.bits %] on for other users
+                Can turn these bits on for other users
               </th>
             [% END %]
           </tr>
@@ -72,9 +72,7 @@
                               name="bless_[% group.id %]"
                               value="1"
                               [% ' checked="checked"' IF perms.directbless %] />
-                  [% ']' IF perms.indirectbless %]
-                [% %]<input type="hidden" name="oldbless_[% group.id %]"
-                            value="[% perms.directbless %]" /></td>
+                  [% ']' IF perms.indirectbless %]</td>
               [% END %]
               <td class="checkbox">
                 [% '[' IF perms.derivedmember %]
@@ -85,9 +83,7 @@
                            value="1"
                            [% ' checked="checked"' IF perms.directmember %] />
                 [% '*' IF perms.regexpmember %]
-                [% ']' IF perms.derivedmember %]
-              [% %]<input type="hidden" name="oldgroup_[% group.id %]"
-                          value="[% perms.directmember %]" /></td>
+                [% ']' IF perms.derivedmember %]</td>
               <td class="groupname">
                 <label for="group_[% group.id %]">
                   <strong>[% group.name FILTER html %]:</strong>
@@ -100,18 +96,29 @@
       </td>
     </tr>
   [% END %]
+
+  <tr>
+    <th>Product responsibilities:</th>
+    <td>
+      [% IF otheruser.product_responsibilities.size %]
+        [% PROCESS admin/users/responsibilities.html.tmpl otheruser = otheruser %]
+      [% ELSE %]
+        <em>none</em>
+      [% END %]
+    </td>
+  </tr>
 </table>
 
 <p>
-  <input type="submit" id="update" value="Update" />
+  <input type="submit" id="update" value="Save Changes" />
   <input type="hidden" name="userid" value="[% otheruser.id %]" />
   <input type="hidden" name="action" value="update" />
   <input type="hidden" name="token" value="[% token FILTER html %]">
   [% INCLUDE listselectionhiddenfields %]
 
   or <a href="editusers.cgi?action=activity&amp;userid=[% otheruser.id %]"
-        title="View the account log for user '
-        [%- otheruser.login FILTER html %]'">View this user's account log</a>
+        title="View Account History for '
+        [%- otheruser.login FILTER html %]'">View Account History</a>
 </p>
 </form>
 <p>
diff --git a/BugsSite/template/en/default/admin/users/list.html.tmpl b/BugsSite/template/en/default/admin/users/list.html.tmpl
index 41c5016..4788e52 100644
--- a/BugsSite/template/en/default/admin/users/list.html.tmpl
+++ b/BugsSite/template/en/default/admin/users/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -25,6 +24,7 @@
 [% PROCESS global/header.html.tmpl
   title = "Select user"
   style_urls = ['skins/standard/editusers.css']
+  doc_section = "useradmin.html"
 %]
 
 [% PROCESS admin/users/listselectvars.html.tmpl
@@ -42,7 +42,7 @@
    {name               => 'realname'
     heading            => 'Real name'
    }
-   {heading            => 'User Account Log'
+   {heading            => 'Account History'
     content            => 'View'
     contentlink        => 'editusers.cgi?action=activity' _
                                   '&amp;userid=%%userid%%' _
diff --git a/BugsSite/template/en/default/admin/users/listselectvars.html.tmpl b/BugsSite/template/en/default/admin/users/listselectvars.html.tmpl
index 781e85a..a6eae57 100644
--- a/BugsSite/template/en/default/admin/users/listselectvars.html.tmpl
+++ b/BugsSite/template/en/default/admin/users/listselectvars.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/users/responsibilities.html.tmpl b/BugsSite/template/en/default/admin/users/responsibilities.html.tmpl
new file mode 100644
index 0000000..bbf121a
--- /dev/null
+++ b/BugsSite/template/en/default/admin/users/responsibilities.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.
+  #
+  # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+  #                 Frédéric Buclin <LpSolit@gmail.com>
+  #%]
+
+[% hidden_products = 0 %]
+<table id="user_responsibilities" border="0">
+  [% FOREACH item = otheruser.product_responsibilities %]
+    [% IF !user.can_see_product(item.product.name) %]
+      [% hidden_products = 1 %]
+      [% NEXT %]
+    [% END %]
+    <tbody>
+    <tr>
+      <th colspan="3" class="product">Product: [% item.product.name FILTER html %]</th>
+    </tr>
+    <tr>
+      <th>Component</th>
+      <th>Default Assignee</th>
+      <th>Default QA Contact</th>
+    </tr>
+    [% FOREACH component = item.components %]
+      <tr>
+        <td>
+          [% IF user.in_group("editcomponents", component.product_id) %]
+            <a href="editcomponents.cgi?action=edit&amp;product=
+                     [% item.product.name FILTER url_quote %]&amp;component=
+                     [% component.name FILTER url_quote %]">
+          [% END %]
+          [% component.name FILTER html %]
+          [% IF user.in_group("editcomponents", component.product_id) %]
+            </a>
+          [% END %]
+        </td>
+        [% FOREACH responsibility = ['default_assignee', 'default_qa_contact'] %]
+          <td class="center">
+            [% component.$responsibility.id == otheruser.id ? "X" : "&nbsp;" %]
+          </td>
+        [% END %]
+      </tr>
+    [% END %]
+    </tbody>
+  [% END %]
+</table>
+
+[% IF hidden_products %]
+  <p class="criticalmessages">The user is involved in at least one product which you cannot
+  see (and so is not listed above). You have to ask an administrator with enough
+  privileges to edit this user's roles for these products.</p>
+[% END %]
diff --git a/BugsSite/template/en/default/admin/users/search.html.tmpl b/BugsSite/template/en/default/admin/users/search.html.tmpl
index ae37bd1..82e0afd 100644
--- a/BugsSite/template/en/default/admin/users/search.html.tmpl
+++ b/BugsSite/template/en/default/admin/users/search.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -28,6 +27,7 @@
   title = "Search users"
   style_urls = ['skins/standard/editusers.css']
   onload = "document.forms['f'].matchstr.focus()"
+  doc_section = "useradmin.html#user-account-search"
 %]
 
 [% PROCESS admin/users/listselectvars.html.tmpl
diff --git a/BugsSite/template/en/default/admin/users/userdata.html.tmpl b/BugsSite/template/en/default/admin/users/userdata.html.tmpl
index e7afe66..feee4c5 100644
--- a/BugsSite/template/en/default/admin/users/userdata.html.tmpl
+++ b/BugsSite/template/en/default/admin/users/userdata.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -31,7 +30,7 @@
         [% IF !otheruser.groups.bz_sudo_protect %]
           <br />
           <a href="relogin.cgi?action=prepare-sudo&amp;target_login=
-          [%- otheruser.login FILTER html %]">Impersonate this user</a>
+          [%- otheruser.login FILTER url_quote %]">Impersonate this user</a>
         [% END %]
       [% END %]
     [% ELSE %]
@@ -51,7 +50,14 @@
     [% END %]
   </td>
 </tr>
+
+[%# XXX This condition (can_change_password) will cause a problem
+  # if we ever have a login system that can create accounts through
+  # createaccount.cgi but can't change passwords.
+  #%]
+  
 [% IF editusers %]
+  [% IF user.authorizer.can_change_password %]
   <tr>
     <th><label for="password">Password:</label></th>
     <td>
@@ -63,6 +69,7 @@
       [% END %]
     </td>
   </tr>
+  [% END %]
   <tr>
     <th><label for="disable_mail">[% terms.Bug %]mail Disabled:</label></th>
     <td>
diff --git a/BugsSite/template/en/default/admin/versions/confirm-delete.html.tmpl b/BugsSite/template/en/default/admin/versions/confirm-delete.html.tmpl
index a621359..88ffceb 100644
--- a/BugsSite/template/en/default/admin/versions/confirm-delete.html.tmpl
+++ b/BugsSite/template/en/default/admin/versions/confirm-delete.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/versions/create.html.tmpl b/BugsSite/template/en/default/admin/versions/create.html.tmpl
index 57ff200..8b4ba64 100644
--- a/BugsSite/template/en/default/admin/versions/create.html.tmpl
+++ b/BugsSite/template/en/default/admin/versions/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/versions/created.html.tmpl b/BugsSite/template/en/default/admin/versions/created.html.tmpl
deleted file mode 100644
index a0ce291..0000000
--- a/BugsSite/template/en/default/admin/versions/created.html.tmpl
+++ /dev/null
@@ -1,43 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
-  #%]
-
-[%# INTERFACE:
-  # product: object; Bugzilla::Product object representing the product to
-  #                  which the version belongs.
-  # version: object; Bugzilla::Version object representing the
-  #                  newly created version
-  #%]
-  
-[% title = BLOCK %]Adding new Version of Product
-                   '[% product.name FILTER html %]'[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-<p>The version '<a title="Edit version '[% version.name FILTER html %]' of product '
-   [%- product.name FILTER html %]'"
-   href="editversions.cgi?action=edit&amp;product=
-   [%- product.name FILTER url_quote %]&amp;version=[% version.name FILTER url_quote %]">
-   [%- version.name FILTER html %]</a>' has been created.</p>
-
-[% PROCESS admin/versions/footer.html.tmpl %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/versions/deleted.html.tmpl b/BugsSite/template/en/default/admin/versions/deleted.html.tmpl
deleted file mode 100644
index 38232a9..0000000
--- a/BugsSite/template/en/default/admin/versions/deleted.html.tmpl
+++ /dev/null
@@ -1,41 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
-  #%]
-
-[%# INTERFACE:
-  # product: object; Bugzilla::Product object representing the product to
-  #               which the version belongs.
-  # version: object; Bugzilla::Version object representing the
-  #                    version the user deleted.
-  #%]
-  
-[% title = BLOCK %]Deleted Version '[% version.name FILTER html %]' of Product
-                   '[% product.name FILTER html %]'[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-<p>Version '[% version.name FILTER html %]' deleted.</p>
-
-[% PROCESS admin/versions/footer.html.tmpl
-  no_edit_version_link = 1
- %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/versions/edit.html.tmpl b/BugsSite/template/en/default/admin/versions/edit.html.tmpl
index 632c7a5..2a7c784 100644
--- a/BugsSite/template/en/default/admin/versions/edit.html.tmpl
+++ b/BugsSite/template/en/default/admin/versions/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -49,7 +48,7 @@
   <input type="hidden" name="action" value="update">
   <input type="hidden" name="product" value="[% product.name FILTER html %]">
   <input type="hidden" name="token" value="[% token FILTER html %]">
-  <input type="submit" id="update" value="Update">
+  <input type="submit" id="update" value="Save Changes">
 </form>
 
 [% PROCESS admin/versions/footer.html.tmpl
diff --git a/BugsSite/template/en/default/admin/versions/footer.html.tmpl b/BugsSite/template/en/default/admin/versions/footer.html.tmpl
index 5d3f259..8d96a12 100644
--- a/BugsSite/template/en/default/admin/versions/footer.html.tmpl
+++ b/BugsSite/template/en/default/admin/versions/footer.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/versions/list.html.tmpl b/BugsSite/template/en/default/admin/versions/list.html.tmpl
index 0df94b8..45e3333 100644
--- a/BugsSite/template/en/default/admin/versions/list.html.tmpl
+++ b/BugsSite/template/en/default/admin/versions/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/versions/select-product.html.tmpl b/BugsSite/template/en/default/admin/versions/select-product.html.tmpl
index f98d1d1..7fded47 100644
--- a/BugsSite/template/en/default/admin/versions/select-product.html.tmpl
+++ b/BugsSite/template/en/default/admin/versions/select-product.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/admin/versions/updated.html.tmpl b/BugsSite/template/en/default/admin/versions/updated.html.tmpl
deleted file mode 100644
index 851fd63..0000000
--- a/BugsSite/template/en/default/admin/versions/updated.html.tmpl
+++ /dev/null
@@ -1,45 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gavin Shelley <bugzilla@chimpychompy.org>
-  #%]
-
-[%# INTERFACE:
-  # product: object; Bugzilla::Product object representing the product to
-  #               which the version belongs.
-  # version: object; Bugzilla::Version object representing the
-  #                    version the user updated.
-  #
-  # updated: boolean; defined if the 'name' field was updated
-  #%]
-  
-[% title = BLOCK %]Updating Version '[% version.name FILTER html %]' of Product
-                   '[% product.name FILTER html %]'[% END %]
-[% PROCESS global/header.html.tmpl
-  title = title
-%]
-
-[% IF updated %]
-  <p>Updated Version name to: '[% version.name FILTER html %]'.</p>
-[% ELSE %]
-  <p>Nothing changed for version '[% version.name FILTER html %]'.</p>
-[% END %]
-
-[% PROCESS admin/versions/footer.html.tmpl %]
-
-[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/workflow/comment.html.tmpl b/BugsSite/template/en/default/admin/workflow/comment.html.tmpl
new file mode 100644
index 0000000..2fa78f0
--- /dev/null
+++ b/BugsSite/template/en/default/admin/workflow/comment.html.tmpl
@@ -0,0 +1,92 @@
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+  #                 Gervase Markham <gerv@mozilla.org>
+  #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% INCLUDE global/header.html.tmpl
+   title = "Comments Required on Status Transitions"
+   style_urls = ['skins/standard/admin.css']
+%]
+
+<script type="text/javascript">
+<!--
+  function toggle_cell(cell) {
+    if (cell.checked)
+      cell.parentNode.className = "checkbox-cell checked";
+    else
+      cell.parentNode.className = "checkbox-cell";
+  }
+//-->
+</script>
+
+<p>
+  This page allows you to define which status transitions require a comment
+  by the user doing the change.
+</p>
+
+<form id="workflow_form" method="POST" action="editworkflow.cgi">
+<table>
+  <tr>
+    <th colspan="2">&nbsp;</th>
+    <th colspan="[% statuses.size FILTER html %]" class="title">To</th>
+  </tr>
+
+  <tr>
+    <th rowspan="[% statuses.size + 2 FILTER html %]" class="title">From</th>
+    <th>&nbsp;</th>
+    [% FOREACH status = statuses %]
+      <th class="col-header[% status.is_open ? " open-status" : " closed-status" %]">
+        [% status.name FILTER html %]
+      </th>
+    [% END %]
+  </tr>
+
+  [%# This defines the entry point in the workflow %]
+  [% p = [{id => 0, name => "{Start}", is_open => 1}] %]
+  [% FOREACH status = p.merge(statuses) %]
+    <tr class="highlight">
+      <th align="right" class="[% status.is_open ? "open-status" : "closed-status" %]">
+        [% status.name FILTER html %]
+      </th>
+
+      [% FOREACH new_status = statuses %]
+        [% IF workflow.${status.id}.${new_status.id}.defined %]
+          <td align="center" class="checkbox-cell
+              [% " checked" IF workflow.${status.id}.${new_status.id} %]"
+              title="From [% status.name FILTER html %] to [% new_status.name FILTER html %]">
+            <input type="checkbox" name="c_[% status.id %]_[% new_status.id %]"
+                   id="c_[% status.id %]_[% new_status.id %]" onclick="toggle_cell(this)"
+              [% " checked='checked'" IF workflow.${status.id}.${new_status.id} %]>
+          </td>
+        [% ELSE %]
+          <td class="checkbox-cell forbidden">&nbsp;</td>
+        [% END %]
+      [% END %]
+    </tr>
+  [% END %]
+</table>
+
+<p align="center">
+  <input type="hidden" name="action" value="update_comment">
+  <input type="hidden" name="token" value="[% token FILTER html %]">
+  <input type="submit" value="Commit Changes"> -
+  <a href="editworkflow.cgi?action=edit_comment">Cancel Changes</a> -
+  <a href="editworkflow.cgi">View Current Workflow</a>
+</p>
+
+</form>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/admin/workflow/edit.html.tmpl b/BugsSite/template/en/default/admin/workflow/edit.html.tmpl
new file mode 100644
index 0000000..1328ce0
--- /dev/null
+++ b/BugsSite/template/en/default/admin/workflow/edit.html.tmpl
@@ -0,0 +1,110 @@
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+  #                 Gervase Markham <gerv@mozilla.org>
+  #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% INCLUDE global/header.html.tmpl
+   title = "Edit Workflow"
+   style_urls = ['skins/standard/admin.css']
+%]
+
+<script type="text/javascript">
+<!--
+  function toggle_cell(cell) {
+    if (cell.checked)
+      cell.parentNode.className = "checkbox-cell checked";
+    else
+      cell.parentNode.className = "checkbox-cell";
+  }
+//-->
+</script>
+
+<p>
+  This page allows you to define which status transitions are valid in your workflow.
+  For compatibility with older versions of [% terms.Bugzilla %], reopening [% terms.abug %]
+  will only display either [% get_status("UNCONFIRMED") FILTER html %] or
+  [%+ get_status("REOPENED") FILTER html %] (if allowed by your workflow) but not
+  both. The decision depends on whether the [% terms.bug %] has ever been confirmed or not.
+  So it is a good idea to allow both transitions and let [% terms.Bugzilla %] select the
+  correct one.
+</p>
+
+<form id="workflow_form" method="POST" action="editworkflow.cgi">
+<table>
+  <tr>
+    <th colspan="2">&nbsp;</th>
+    <th colspan="[% statuses.size FILTER html %]" class="title">To</th>
+  </tr>
+
+  <tr>
+    <th rowspan="[% statuses.size + 2 FILTER html %]" class="title">From</th>
+    <th>&nbsp;</th>
+    [% FOREACH status = statuses %]
+      <th class="col-header[% status.is_open ? " open-status" : " closed-status" %]">
+        [% status.name FILTER html %]
+      </th>
+    [% END %]
+  </tr>
+
+  [%# This defines the entry point in the workflow %]
+  [% p = [{id => 0, name => "{Start}", is_open => 1}] %]
+  [% FOREACH status = p.merge(statuses) %]
+    <tr class="highlight">
+      <th align="right" class="[% status.is_open ? "open-status" : "closed-status" %]">
+        [% status.name FILTER html %]
+      </th>
+
+      [% FOREACH new_status = statuses %]
+        [% IF status.id != new_status.id && (status.id || new_status.is_open) %]
+          [% checked = workflow.${status.id}.${new_status.id}.defined ? 1 : 0 %]
+          [% mandatory = (status.id && new_status.name == Param("duplicate_or_move_bug_status")) ? 1 : 0 %]
+          <td align="center" class="checkbox-cell[% " checked" IF checked || mandatory %]"
+              title="From [% status.name FILTER html %] to [% new_status.name FILTER html %]">
+            <input type="checkbox" name="w_[% status.id %]_[% new_status.id %]"
+                   id="w_[% status.id %]_[% new_status.id %]" onclick="toggle_cell(this)"
+                   [%+ "checked='checked'" IF checked || mandatory %]
+                   [%+ "disabled='disabled'" IF mandatory %]>
+          </td>
+        [% ELSE %]
+          <td class="checkbox-cell forbidden">&nbsp;</td>
+        [% END %]
+      [% END %]
+    </tr>
+  [% END %]
+</table>
+
+<p>
+  When [% terms.abug %] is marked as a duplicate of another one or is moved
+  to another installation, the [% terms.bug %] status is automatically set to
+  <b>[% Param("duplicate_or_move_bug_status") FILTER html %]</b>. All transitions to
+  this [% terms.bug %] status must then be valid (this is the reason why you cannot edit
+  them above).<br>
+  Note: you can change this setting by visiting the
+  <a href="editparams.cgi?section=bugchange#duplicate_or_move_bug_status">Parameters</a>
+  page and editing the <i>duplicate_or_move_bug_status</i> parameter.
+</p>
+
+<p align="center">
+  <input type="hidden" name="action" value="update">
+  <input type="hidden" name="token" value="[% token FILTER html %]">
+  <input type="submit" value="Commit Changes"> -
+  <a href="editworkflow.cgi">Cancel Changes</a> -
+  <a href="editworkflow.cgi?action=edit_comment">View Comments Required on Status Transitions</a>
+</p>
+
+</form>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/attachment/cancel-create-dupe.html.tmpl b/BugsSite/template/en/default/attachment/cancel-create-dupe.html.tmpl
new file mode 100644
index 0000000..f838955
--- /dev/null
+++ b/BugsSite/template/en/default/attachment/cancel-create-dupe.html.tmpl
@@ -0,0 +1,48 @@
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # The Initial Developer of the Original Code is Olav Vitters.
+  #
+  # Contributor(s): Olav Vitters <olav@bkor.dhs.org>
+  #                 David Lawrence <dkl@redhat.com>
+  #%]
+
+[%# INTERFACE:
+  # bugid:    integer. ID of the bug report that this attachment relates to.
+  # attachid: integer. ID of the previous attachment recently created.
+  #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% PROCESS global/header.html.tmpl
+  title = "Already filed attachment"
+%]
+
+[% USE Bugzilla %]
+
+<table cellpadding="20">
+  <tr>
+    <td bgcolor="#ff0000">
+      <font size="+2">
+        You already used the form to file
+        <a href="[% urlbase FILTER html %]attachment.cgi?id=[% attachid FILTER url_quote %]&action=edit">attachment [% attachid FILTER url_quote %]</a>.
+      </font>
+    </td>
+  </tr>
+</table>
+
+<p>
+  You can either <a href="[% urlbase FILTER html %]attachment.cgi?bugid=[% bugid FILTER url_quote %]&action=enter">
+  create a new attachment</a> or [% "go back to $terms.bug $bugid" FILTER bug_link(bugid) FILTER none %].
+<p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/attachment/choose.html.tmpl b/BugsSite/template/en/default/attachment/choose.html.tmpl
index bce392f..700abb4 100644
--- a/BugsSite/template/en/default/attachment/choose.html.tmpl
+++ b/BugsSite/template/en/default/attachment/choose.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/attachment/confirm-delete.html.tmpl b/BugsSite/template/en/default/attachment/confirm-delete.html.tmpl
index 0b8bc29..14c76c3 100644
--- a/BugsSite/template/en/default/attachment/confirm-delete.html.tmpl
+++ b/BugsSite/template/en/default/attachment/confirm-delete.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,11 +21,13 @@
 [% PROCESS global/variables.none.tmpl %]
 
 [% title = BLOCK %]
-  Delete Attachment [% a.id FILTER html %] of
-  [%+ "$terms.Bug " _ a.bug_id FILTER bug_link(a.bug_id) FILTER none %]
+  Delete Attachment [% a.id FILTER html %] of [% terms.Bug %] [%+ a.bug_id FILTER html %]
 [% END %]
 
-[% PROCESS global/header.html.tmpl title = title %]
+[% PROCESS global/header.html.tmpl
+  title = title
+  doc_section = "attachments.html"
+%]
 
 <table border="1" cellpadding="4" cellspacing="0">
   <tr bgcolor="#6666FF">
diff --git a/BugsSite/template/en/default/attachment/content-types.html.tmpl b/BugsSite/template/en/default/attachment/content-types.html.tmpl
index 0315349..471222a 100644
--- a/BugsSite/template/en/default/attachment/content-types.html.tmpl
+++ b/BugsSite/template/en/default/attachment/content-types.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/attachment/create.html.tmpl b/BugsSite/template/en/default/attachment/create.html.tmpl
index 381c759..1064815 100644
--- a/BugsSite/template/en/default/attachment/create.html.tmpl
+++ b/BugsSite/template/en/default/attachment/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,7 +21,7 @@
   #                 Marc Schumann <wurblzap@gmail.com>
   #%]
 
-[% PROCESS global/variables.none.tmpl %]
+[% PROCESS "global/field-descs.none.tmpl" %]
 
 [%# Define strings that will serve as the title and header of this page %]
 [% title = BLOCK %]Create New Attachment for [% terms.Bug %] #[% bug.bug_id %][% END %]
@@ -37,11 +36,13 @@
   onload="setContentTypeDisabledState(document.entryform);"
   style_urls = [ 'skins/standard/create_attachment.css' ]
   javascript_urls = [ "js/attachment.js" ]
+  doc_section = "attachments.html"
 %]
 
 <form name="entryform" method="post" action="attachment.cgi" enctype="multipart/form-data">
   <input type="hidden" name="bugid" value="[% bug.bug_id %]">
   <input type="hidden" name="action" value="insert">
+  <input type="hidden" name="token" value="[% token FILTER html %]">
 
   <table class="attachment_entry">
     [% PROCESS attachment/createformcontents.html.tmpl %]
@@ -74,6 +75,21 @@
               check the box below.</em><br>
           <input type="checkbox" id="takebug" name="takebug" value="1">
           <label for="takebug">take [% terms.bug %]</label>
+          [% bug_statuses = [] %]
+          [% FOREACH bug_status = bug.status.can_change_to %]
+            [% NEXT IF bug_status.name == "UNCONFIRMED" && !bug.product_obj.votes_to_confirm %]
+            [% 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>
+              [% 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>
+              [% END %]
+            </select>
+          [% END %]
         </td>
       </tr>
     [% END %]
diff --git a/BugsSite/template/en/default/attachment/created.html.tmpl b/BugsSite/template/en/default/attachment/created.html.tmpl
index e6037bc..faf1b87 100644
--- a/BugsSite/template/en/default/attachment/created.html.tmpl
+++ b/BugsSite/template/en/default/attachment/created.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -20,27 +19,40 @@
   #%]
 
 [%# INTERFACE:
-  # bugid: integer. ID of the bug we just attached an attachment to.
-  # attachid: integer. ID of the attachment just created.
-  # description: string. Description of the attachment just created.
-  # contenttype: string. The Content Type we attached it as.
+  # attachment: object of the attachment just created.
   # contenttypemethod: string. How we got the content type of the attachment.
   #  Possible values: autodetect, list, manual.
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
+[% bug = bugs.0 %]
+[% 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 = "Changes Submitted"
+  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>
   <dt>
-    <a title="[% description FILTER html %]" href="attachment.cgi?id=[% attachid %]&amp;action=edit">Attachment #[% attachid %]</a>
-    to [% "$terms.bug $bugid" FILTER bug_link(bugid) %] created
+    <a title="[% attachment.description FILTER html %]"
+       href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">Attachment #[% attachment.id %]</a>
+    to [% "$terms.bug $attachment.bug_id" FILTER bug_link(attachment.bug_id) FILTER none %] created
   </dt>
   <dd>
-    [% PROCESS "bug/process/bugmail.html.tmpl" mailing_bugid = bugid %]
+    [% PROCESS "bug/process/bugmail.html.tmpl" mailing_bugid = attachment.bug_id %]
     [% IF convertedbmp %]
       <p>
         <b>Note:</b> [% terms.Bugzilla %] automatically converted your BMP image file to a
@@ -50,9 +62,9 @@
     [% IF contenttypemethod == 'autodetect' %]
       <p>
         <b>Note:</b> [% terms.Bugzilla %] automatically detected the content type
-        <em>[% contenttype %]</em> for this attachment.  If this is
-        incorrect, correct the value by
-        editing the attachment's <a href="attachment.cgi?id=[% attachid %]&amp;action=edit">details</a>.
+        <em>[% attachment.contenttype FILTER html %]</em> for this attachment.  If this is
+        incorrect, correct the value by editing the attachment's
+        <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">details</a>.
       </p>
     [% END %]
 
@@ -62,8 +74,8 @@
 </dl>
 
 <p>
-<a href="attachment.cgi?bugid=[% bugid %]&amp;action=enter">Create
- Another Attachment to [% terms.Bug %] #[% bugid %]</a>
+<a href="attachment.cgi?bugid=[% attachment.bug_id %]&amp;action=enter">Create
+ Another Attachment to [% terms.Bug %] [%+ attachment.bug_id %]</a>
 </p>
 
-[% PROCESS global/footer.html.tmpl %]
+[% PROCESS bug/show.html.tmpl %]
diff --git a/BugsSite/template/en/default/attachment/createformcontents.html.tmpl b/BugsSite/template/en/default/attachment/createformcontents.html.tmpl
index 2056490..956b7bc 100644
--- a/BugsSite/template/en/default/attachment/createformcontents.html.tmpl
+++ b/BugsSite/template/en/default/attachment/createformcontents.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/attachment/delete_reason.txt.tmpl b/BugsSite/template/en/default/attachment/delete_reason.txt.tmpl
index 45879f6..e4a1fc4 100644
--- a/BugsSite/template/en/default/attachment/delete_reason.txt.tmpl
+++ b/BugsSite/template/en/default/attachment/delete_reason.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -15,12 +14,12 @@
   #%]
 
 [%# INTERFACE:
-  # attachid: ID of the attachment the user wants to delete.
+  # attachment: object of the attachment the user wants to delete.
   # reason: string; The reason provided by the user.
   # date: the date when the request to delete the attachment was made.
   #%]
 
-The content of attachment [% attachid %] has been deleted by
+The content of attachment [% attachment.id %] has been deleted by
     [%+ user.identity %]
 [% IF reason %]
 who provided the following reason:
diff --git a/BugsSite/template/en/default/attachment/diff-file.html.tmpl b/BugsSite/template/en/default/attachment/diff-file.html.tmpl
index cd54ab3..85dd220 100644
--- a/BugsSite/template/en/default/attachment/diff-file.html.tmpl
+++ b/BugsSite/template/en/default/attachment/diff-file.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -23,8 +22,11 @@
   # between the elements.  This is necessary because DOM parent-child-sibling
   # relations can change and screw up the javascript for restoring, collapsing
   # and expanding.  Do not change without testing all three of those.
+  # Also, the first empty row is required because 'table-layout: fixed' only
+  # considers the first row to determine column widths. If a colspan is found,
+  # it then share the width equally among all columns, which we don't want.
   #%]
-<table class="file_table"><thead><tr><td class="file_head" colspan="2"><a href="#" onclick="return twisty_click(this)">[% collapsed ? '(+)' : '(-)' %]</a><input type="checkbox" name="[% file.filename FILTER html %]"[% collapsed ? '' : ' checked' %] style="display: none"> 
+<table class="file_table"><thead><tr><td class="num"></td><td></td><td class="num"></td><td></td></tr><tr><td class="file_head" colspan="4"><a href="#" onclick="return twisty_click(this)">[% collapsed ? '(+)' : '(-)' %]</a><input type="checkbox" name="[% file.filename FILTER html %]"[% collapsed ? '' : ' checked' %] style="display: none">
   [% IF lxr_prefix && !file.is_add %]
     <a href="[% lxr_prefix %]">[% file.filename FILTER html %]</a>
   [% ELSE %]
@@ -49,7 +51,7 @@
 [% section_num = 0 %]
 [% FOREACH section = sections %]
   [% section_num = section_num + 1 %]
-  <tr><th colspan="2" class="section_head">
+  <tr><th colspan="4" class="section_head">
     <table cellpadding="0" cellspacing="0">
     <tr><th width="95%" align="left">
   [% IF file.is_add %]
@@ -79,38 +81,67 @@
   <a name="[% file.filename FILTER html %]_sec[% section_num %]" href="#[% file.filename FILTER html %]_sec[% section_num %]">Link&nbsp;Here</a>&nbsp;
     </th></tr></table>
   </th></tr>
+  [% current_line_old = section.old_start %]
+  [% current_line_new = section.new_start %]
   [% FOREACH group = section.groups %]
     [% IF group.context %]
       [% FOREACH line = group.context %]
-        <tr><td><pre>[% line FILTER html %]</pre></td><td><pre>[% line FILTER html %]</pre></td></tr>
+        <tr>
+          <td class="num">[% current_line_old %]</td>
+          <td><pre>[% line FILTER html %]</pre></td>
+          <td class="num">[% current_line_new %]</td>
+          <td><pre>[% line FILTER html %]</pre></td>
+        </tr>
+        [% current_line_old = current_line_old + 1 %]
+        [% current_line_new = current_line_new + 1 %]
       [% END %]
     [% END %]
     [% IF group.plus.size %]
       [% IF group.minus.size %]
         [% i = 0 %]
+        [%# 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) %]
           [% currentloop = 0 %]
           [% WHILE currentloop < 500 && (i < group.plus.size || i < group.minus.size) %]
-            <tr class="changed">
-              <td><pre>[% group.minus.$i FILTER html %]</pre></td>
-              <td><pre>[% group.plus.$i FILTER html %]</pre></td>
+            <tr>
+              <td class="num">[% curr_old %]</td>
+              <td class="changed"><pre>[% group.minus.$i FILTER html %]</pre></td>
+              <td class="num">[% curr_new %]</td>
+              <td class="changed"><pre>[% group.plus.$i FILTER html %]</pre></td>
             </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 %]
+        [% current_line_new = current_line_new + group.plus.size %]
       [% ELSE %]
         [% FOREACH line = group.plus %]
           [% IF file.is_add %]
             <tr>
-              <td class="added" colspan="2"><pre>[% line FILTER html %]</pre></td>
+              <td class="num">[% current_line_new %]</td>
+              <td class="added" colspan="3"><pre>[% line FILTER html %]</pre></td>
             </tr>
           [% ELSE %]
             <tr>
-              <td></td>
+              <td class="num"></td><td></td>
+              <td class="num">[% current_line_new %]</td>
               <td class="added"><pre>[% line FILTER html %]</pre></td>
             </tr>
           [% END %]
+          [% current_line_new = current_line_new + 1 %]
         [% END %]
       [% END %]
     [% ELSE %]
@@ -118,14 +149,17 @@
         [% FOREACH line = group.minus %]
           [% IF file.is_remove %]
             <tr>
-              <td class="removed" colspan="2"><pre>[% line FILTER html %]</pre></td>
+              <td class="num">[% current_line_old %]</td>
+              <td class="removed" colspan="3"><pre>[% line FILTER html %]</pre></td>
             </tr>
           [% ELSE %]
             <tr>
+              <td class="num">[% current_line_old %]</td>
               <td class="removed"><pre>[% line FILTER html %]</pre></td>
-              <td></td>
+              <td class="num"></td><td></td>
             </tr>
           [% END %]
+          [% current_line_old = current_line_old + 1 %]
         [% END %]
       [% END %]
     [% END %]
diff --git a/BugsSite/template/en/default/attachment/diff-footer.html.tmpl b/BugsSite/template/en/default/attachment/diff-footer.html.tmpl
index 63d2ade..d263d9b 100644
--- a/BugsSite/template/en/default/attachment/diff-footer.html.tmpl
+++ b/BugsSite/template/en/default/attachment/diff-footer.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/attachment/diff-header.html.tmpl b/BugsSite/template/en/default/attachment/diff-header.html.tmpl
index 97a6d05..c6b14d9 100644
--- a/BugsSite/template/en/default/attachment/diff-header.html.tmpl
+++ b/BugsSite/template/en/default/attachment/diff-header.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -37,7 +36,6 @@
   font-size: 1em;
   background-color: #c3c3c3;
   border: 1px solid black;
-  width: 100%;
 }
 
 .file_head a {
@@ -51,7 +49,6 @@
 }
 
 .section_head {
-  width: 100%;
   background-color: #f0f0f0;
   border: 1px solid black;
   text-align: left;
@@ -67,15 +64,11 @@
   border-bottom: 1px solid black;
 }
 
-tbody.file td {
-  border-left: 1px dashed black;
-  border-right: 1px dashed black;
-  width: 50%;
-}
-
 tbody.file pre {
   display: inline;
-  white-space: -moz-pre-wrap;
+  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;
 }
 
@@ -95,6 +88,13 @@
   background-color: #FFCC99;
 }
 
+.num {
+  background-color: #ffe9ae;
+  text-align:right;
+  padding: 0 0.3em;
+  width: 3em;
+}
+
 .warning {
   color: red
 }
@@ -196,7 +196,7 @@
     return twisty.parentNode.parentNode.parentNode.nextSibling;
   }
   function get_twisty_from_tbody(tbody) {
-    return tbody.previousSibling.firstChild.firstChild.firstChild;
+    return tbody.previousSibling.firstChild.nextSibling.firstChild.firstChild;
   }
 [% END %]
 
@@ -221,7 +221,7 @@
   [% subheader = BLOCK %]
     [% bugsummary FILTER html %]
   [% END %]
-  [% PROCESS global/header.html.tmpl %]
+  [% PROCESS global/header.html.tmpl doc_section = "attachments.html#patchviewer" %]
 [% ELSE %]
   <html>
   <head>
diff --git a/BugsSite/template/en/default/attachment/edit.html.tmpl b/BugsSite/template/en/default/attachment/edit.html.tmpl
index 94581ab..1b00df9 100644
--- a/BugsSite/template/en/default/attachment/edit.html.tmpl
+++ b/BugsSite/template/en/default/attachment/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -36,15 +35,12 @@
   title = title
   header = header
   subheader = subheader
-  style = "
-    table.attachment_info th { text-align: right; vertical-align: top; }
-    table.attachment_info td { text-align: left; vertical-align: top; }
-    #noview { text-align: left; vertical-align: middle; }
-
-    table#flags th, table#flags td { font-size: small; vertical-align: baseline; text-align: left; }
-  "
+  doc_section = "attachments.html"
 %]
 
+[%# 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';
@@ -53,37 +49,7 @@
   var has_viewed_as_diff = 0;
   function editAsComment()
     {
-      // Get the content of the document as a string.
-      var viewFrame = document.getElementById('viewFrame');
-      var aSerializer = new XMLSerializer();
-      var contentDocument = viewFrame.contentDocument;
-      var theContent = aSerializer.serializeToString(contentDocument);
-
-      // If this is a plaintext document, remove cruft that Mozilla adds
-      // because it treats it as an HTML document with a big PRE section.
-      // http://bugzilla.mozilla.org/show_bug.cgi?id=86012
-      var contentType = '[% attachment.contenttype FILTER js %]';
-      if ( contentType == 'text/plain' )
-        {
-          theContent = theContent.replace( /^<html><head\/?><body><pre>/i , "" );
-          theContent = theContent.replace( /<\/pre><\/body><\/html>$/i , "" );
-          theContent = theContent.replace( /&lt;/gi , "<" );
-          theContent = theContent.replace( /&gt;/gi , ">" );
-          theContent = theContent.replace( /&amp;/gi , "&" );
-        }
-
-      // Add mail-style quote indicators (>) to the beginning of each line.
-      // ".*\n" matches lines that end with a newline, while ".+" matches
-      // the rare situation in which the last line of a file does not end
-      // with a newline.
-      theContent = theContent.replace( /(.*\n|.+)/g , ">$1" );
-
       switchToMode('edit');
-
-      // Copy the contents of the diff into the textarea
-      var editFrame = document.getElementById('editFrame');
-      editFrame.value = theContent + "\n\n";
-
       has_edited = 1;
     }
   function undoEditAsComment()
@@ -204,6 +170,10 @@
   <input type="hidden" name="id" value="[% attachment.id %]">
   <input type="hidden" name="action" value="update">
   <input type="hidden" name="contenttypemethod" value="manual">
+  <input type="hidden" name="delta_ts" value="[% attachment.modification_time FILTER html %]">
+  [% IF user.id %]
+    <input type="hidden" name="token" value="[% issue_hash_token([attachment.id, attachment.modification_time]) FILTER html %]">
+  [% END %]
 
   <table class="attachment_info" width="100%">
 
@@ -242,11 +212,11 @@
                    value="[% attachment.contenttype FILTER html %]"><br>
 
           <input type="checkbox" id="ispatch" name="ispatch" value="1"
-                 [% 'checked="checked"' IF attachment.ispatch %]>
+                 [%+ '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 %]>
+                 [%+ '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"
@@ -254,6 +224,7 @@
             <label for="isprivate">private</label><br>
           [% END %]
           <br>
+        </small>
 
         [% IF flag_types.size > 0 %]
           [% PROCESS "flag/list.html.tmpl" bug_id = attachment.bug_id
@@ -261,8 +232,8 @@
         [% END %]
 
         <div id="smallCommentFrame">
-          <b><label for="comment">Comment</label> (on the
-          [%+ terms.bug %]):</b><br>
+          <b><small><label for="comment">Comment</label> (on the
+          [%+ terms.bug %]):</small></b><br>
             [% INCLUDE global/textarea.html.tmpl
               id      = 'comment'
               name    = 'comment'
@@ -283,42 +254,10 @@
               && attachment.datasize > 0 %]
           | <a href="attachment.cgi?id=[% attachment.id %]&amp;action=delete">Delete</a>
         [% END %]
-        </small>
       </td>
 
       [% IF !attachment.datasize %]
         <td width="75%"><b>The content of this attachment has been deleted.</b></td>
-      [% ELSIF isviewable %]
-        <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'
-          %]
-          <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>
       [% ELSIF attachment.isurl %]
         <td width="75%">
           <a href="[% attachment.data FILTER html %]">
@@ -331,6 +270,50 @@
             [% 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>
diff --git a/BugsSite/template/en/default/attachment/list.html.tmpl b/BugsSite/template/en/default/attachment/list.html.tmpl
index 99f5106..546041e 100644
--- a/BugsSite/template/en/default/attachment/list.html.tmpl
+++ b/BugsSite/template/en/default/attachment/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -135,9 +134,11 @@
       [% 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);">Hide Obsolete</a> ([% obsolete_attachments %])
           [% END %]
-          <a href="attachment.cgi?bugid=[% bugid %]&amp;action=viewall">View All</a>
+          [% IF Param("allow_attachment_display") %]
+            <a href="attachment.cgi?bugid=[% bugid %]&amp;action=viewall">View All</a>
+          [% END %]
         </span>
       [% END %]
       <a href="attachment.cgi?bugid=[% bugid %]&amp;action=enter">Add an attachment</a>
diff --git a/BugsSite/template/en/default/attachment/midair.html.tmpl b/BugsSite/template/en/default/attachment/midair.html.tmpl
new file mode 100644
index 0000000..f0883b5
--- /dev/null
+++ b/BugsSite/template/en/default/attachment/midair.html.tmpl
@@ -0,0 +1,78 @@
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # The Initial Developer of the Original Code is Netscape Communications
+  # Corporation. Portions created by Netscape are
+  # Copyright (C) 1998 Netscape Communications Corporation. All
+  # Rights Reserved.
+  #
+  # Contributor(s): Myk Melez <myk@mozilla.org>
+  #                 Frédéric Buclin <LpSolit@gmail.com>
+  #%]
+
+[%# INTERFACE:
+  # operations: array; bug activity since the user last displayed the attachment form,
+  #   used by bug/activity/table.html.tmpl to display recent changes that will
+  #   be overwritten if the user submits these changes.  See that template
+  #   for further documentation.
+  # attachment: object; the attachment being changed.
+  #%]
+
+[%# The global Bugzilla->cgi object is used to obtain form variable values. %]
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+[% PROCESS global/variables.none.tmpl %]
+[% PROCESS global/header.html.tmpl title = "Mid-air collision!" %]
+
+<h1>Mid-air collision detected!</h1>
+
+<p>
+  Someone else has made changes to
+  <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">attachment [% attachment.id %]</a>
+  of [% "$terms.bug $attachment.bug_id" FILTER bug_link(attachment.bug_id) FILTER none %]
+  at the same time you were trying to. The changes made were:
+</p>
+
+<p>
+  [% PROCESS "bug/activity/table.html.tmpl" incomplete_data=0 %]
+</p>
+
+[% IF cgi.param("comment") %]
+<p>
+  Your comment was:<br>
+  <blockquote><pre class="bz_comment_text">
+    [% cgi.param("comment") FILTER wrap_comment FILTER html %]
+  </pre></blockquote>
+</p>
+[% END %]
+
+<p>
+You have the following choices:
+</p>
+
+<ul>
+  <li>
+    <form method="post" action="attachment.cgi">
+      [% PROCESS "global/hidden-fields.html.tmpl" exclude="^Bugzilla_(login|password)$" %]
+      <input type="submit" id="process" value="Submit my changes anyway">
+        This will cause all of the above changes to be overwritten.
+    </form>
+  </li>
+  <li>
+    Throw away my changes, and
+    <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">revisit
+    attachment [% attachment.id %]</a>
+  </li>
+</ul>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/attachment/show-multiple.html.tmpl b/BugsSite/template/en/default/attachment/show-multiple.html.tmpl
index 9c0fc57..36088c9 100644
--- a/BugsSite/template/en/default/attachment/show-multiple.html.tmpl
+++ b/BugsSite/template/en/default/attachment/show-multiple.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -32,10 +31,6 @@
   title = title
   header = header
   subheader = filtered_summary
-  style = "
-    table.attachment_info th { text-align: right; vertical-align: top; }
-    table.attachment_info td { text-align: left; vertical-align: top; }
-  "
 %]
 
 <br>
@@ -86,7 +81,7 @@
     </tr>
   </table>
 
-  [% IF a.isviewable %]
+  [% 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>
diff --git a/BugsSite/template/en/default/attachment/updated.html.tmpl b/BugsSite/template/en/default/attachment/updated.html.tmpl
index a66b74e..bc22b46 100644
--- a/BugsSite/template/en/default/attachment/updated.html.tmpl
+++ b/BugsSite/template/en/default/attachment/updated.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -21,26 +20,45 @@
   #%]
 
 [%# INTERFACE:
-  # bugid: integer. ID of the bug we are updating.
-  # attachid: integer. ID of the attachment we just attached.
+  # attachment: object of the attachment we just attached.
   #%]
 
 [% 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 global/header.html.tmpl
-  title = "Changes Submitted"
+  title = "Changes Submitted to Attachment $attachment.id of $terms.Bug $attachment.bug_id"
+  header = "$terms.Bug&nbsp;$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>
   <dt>Changes to
-    <a href="attachment.cgi?id=[% attachid %]&amp;action=edit">attachment [% attachid %]</a>
-    of [% "$terms.bug $bugid" FILTER bug_link(bugid) %] submitted
+    <a href="attachment.cgi?id=[% attachment.id %]&amp;action=edit">attachment [% attachment.id %]</a>
+    of [% "$terms.bug $attachment.bug_id" FILTER bug_link(attachment.bug_id) FILTER none %] submitted
   </dt>
   <dd>
-    [% PROCESS "bug/process/bugmail.html.tmpl" mailing_bugid = bugid %]
+    [% PROCESS "bug/process/bugmail.html.tmpl" mailing_bugid = attachment.bug_id %]
     [%# Links to more information about the changed bug. %]
     [% Hook.process("links") %]
   </dd>
 </dl>
 
-[% PROCESS global/footer.html.tmpl %]
+[% PROCESS bug/show.html.tmpl %]
diff --git a/BugsSite/template/en/default/bug/activity/show.html.tmpl b/BugsSite/template/en/default/bug/activity/show.html.tmpl
index 2acaf3a..a457df0 100644
--- a/BugsSite/template/en/default/bug/activity/show.html.tmpl
+++ b/BugsSite/template/en/default/bug/activity/show.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -20,27 +19,31 @@
   #%]
 
 [%# INTERFACE:
-  # bug_id: integer. The bug ID.
+  # bug: object. The bug whose activity is being displayed.
+  # operations: array of hashes, see activity/table.html.tmpl.
   #
   # This template also needs to be called with the interface to the
-  # activity.html.tmpl template fulfilled.
+  # activity/table.html.tmpl template fulfilled.
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
 
+[% filtered_desc = bug.short_desc FILTER html %]
 [% PROCESS global/header.html.tmpl
-  title = "Changes made to $terms.bug $bug_id"
-  header = "Activity log"
-  subheader = "$terms.Bug <a href=\"show_bug.cgi?id=$bug_id\">$bug_id</a>"
+  title = "Changes made to $terms.bug $bug.bug_id"
+  header = "Activity log for $terms.bug $bug.bug_id: $filtered_desc"
  %]
 
-<br>
+<p>
+  [% "Back to $terms.bug $bug.bug_id" FILTER bug_link(bug.bug_id) FILTER none %]
+</p>
 
 [% PROCESS bug/activity/table.html.tmpl %]
 
-<p>
-  <a href="show_bug.cgi?id=[% bug_id %]">Back to [% terms.bug %] 
-  [%+ bug_id %]</a>
-</p>
+[% IF operations.size > 0 %]
+  <p>
+    [% "Back to $terms.bug $bug.bug_id" FILTER bug_link(bug.bug_id) FILTER none %]
+  </p>
+[% END %]
 
 [% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/bug/activity/table.html.tmpl b/BugsSite/template/en/default/bug/activity/table.html.tmpl
index 5bfb79f..b676eb1 100644
--- a/BugsSite/template/en/default/bug/activity/table.html.tmpl
+++ b/BugsSite/template/en/default/bug/activity/table.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -74,7 +73,7 @@
                 <a href="attachment.cgi?id=[% change.attachid %]">
                 Attachment #[% change.attachid %]</a>
               [% END %]
-              [% change.field %]
+              [%+ change.field %]
             </td>
             <td>
               [% IF change.removed.defined %]
@@ -83,7 +82,7 @@
                       change.fieldname == 'work_time' %]
                   [% PROCESS formattimeunit time_unit=change.removed %]
                 [% ELSIF change.fieldname == 'bug_status' %]
-                  [% status_descs.${change.removed} FILTER html %]
+                  [% get_status(change.removed) FILTER html %]
                 [% ELSIF change.fieldname == 'resolution' %]
                   [% get_resolution(change.removed) FILTER html %]
                 [% ELSIF change.fieldname == 'blocked' ||
@@ -103,7 +102,7 @@
                       change.fieldname == 'work_time' %]
                   [% PROCESS formattimeunit time_unit=change.added %]
                 [% ELSIF change.fieldname == 'bug_status' %]
-                  [% status_descs.${change.added} FILTER html %]
+                  [% get_status(change.added) FILTER html %]
                 [% ELSIF change.fieldname == 'resolution' %]
                   [% get_resolution(change.added) FILTER html %]
                 [% ELSIF change.fieldname == 'blocked' ||
diff --git a/BugsSite/template/en/default/bug/choose.html.tmpl b/BugsSite/template/en/default/bug/choose.html.tmpl
index 65b260b..9009d38 100644
--- a/BugsSite/template/en/default/bug/choose.html.tmpl
+++ b/BugsSite/template/en/default/bug/choose.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/comments.html.tmpl b/BugsSite/template/en/default/bug/comments.html.tmpl
index 5add6d5..bf9326e 100644
--- a/BugsSite/template/en/default/bug/comments.html.tmpl
+++ b/BugsSite/template/en/default/bug/comments.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -37,6 +36,58 @@
         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>
 
@@ -69,6 +120,11 @@
     [% 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 %]
 
 [% FOREACH comment = comments %]
   [% IF count >= start_at %]
@@ -90,51 +146,58 @@
 [% BLOCK a_comment %]
   [% IF NOT comment.isprivate || isinsider %]
     <div class="bz_comment[% " bz_private" IF comment.isprivate %]
-                [% " bz_comment_hilite" IF marks.$count %]">
+                [% " bz_comment_hilite" IF marks.$count %]
+                [% " bz_first_comment" IF count == description %]">
       [% IF count == description %]
-        <table>
-          <tr>
-            <th align="left">
-              <b><a name="c0" href="show_bug.cgi?id=[% bug.bug_id %]#c0">
-                Description</a>:</b>&nbsp;&nbsp;
-                [% IF mode == "edit" %]
-                  [%%]<script type="text/javascript"><!-- 
-                    addReplyLink(0);
-                  //--></script>
-                [% END %]
-            </th>
-            <td align="left" width="30%">
-              <b>Opened:</b> [% bug.creation_ts FILTER time %]
-            </td>
-          </tr>
-        </table>
+        [% class_name = "bz_first_comment_head" %]
+        [% comment_label = "" %]
+        [% comment_link = "Description" %]
+        [% decoration = "" %]
       [% ELSE %]
-        <span class="bz_comment_head">
-          <span class="comment_rule">-------</span> <i>Comment
-          <a name="c[% count %]" href="show_bug.cgi?id=[% bug.bug_id %]#c[% count %]">
-            #[% count %]</a> From 
-          <a href="mailto:[% comment.email FILTER html %]">
-            [% comment.name FILTER html %]</a>
-          [%+ comment.time FILTER time %] 
-          </i>
-          [% IF mode == "edit" %]
-          <script type="text/javascript"><!-- 
-            addReplyLink([% count %]); //--></script>
-          [% END %]
-          <span class="comment_rule">-------</span>
-        </span>
+        [% class_name = "bz_comment_head" %]
+        [% comment_label = "Comment" %]
+        [% comment_link = "#" _ count %]
+        [% decoration = '<span class="comment_rule">-------</span>' %]
       [% 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>
+
+        [% IF mode == "edit" %]
+          <script type="text/javascript"><!--
+            addCollapseLink([% count %]);
+            addReplyLink([% count %], [% comment.id %]); //-->
+          </script>
+        [% END %]
+        [%+ decoration FILTER none %]
+      </span>
+
       [% IF mode == "edit" && isinsider %]
         <i>
-          <input type="hidden" name="oisprivate-[% count %]" 
-                 value="[% comment.isprivate %]">
-          <input type="hidden" name="when-[% count %]" value="[% comment.time %]">
-          <input type="checkbox" name="isprivate-[% count %]" value="1"
+          <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 %])"
-                 id="isprivate-[% count %]"
                  [% " checked=\"checked\"" IF comment.isprivate %]>
-          <label for="isprivate-[% count %]">Private</label>
+          <label for="isprivate_[% comment.id %]">Private</label>
         </i>
       [% END %]
       [% IF user.in_group(Param('timetrackinggroup')) &&
@@ -152,7 +215,8 @@
 [% ELSE %]
     [% wrapped_comment = comment.body FILTER wrap_comment %]
 [% END %]
-<pre[% ' id="comment_text_' _ count _ '"' IF mode == "edit" %]>
+<pre class="bz_comment_text" 
+     [% ' id="comment_text_' _ count _ '"' IF mode == "edit" %]>
   [%- wrapped_comment FILTER quoteUrls(bug.bug_id) -%]
 </pre>
     </div>
diff --git a/BugsSite/template/en/default/bug/create/comment-guided.txt.tmpl b/BugsSite/template/en/default/bug/create/comment-guided.txt.tmpl
index 23dec73..df04d8f 100644
--- a/BugsSite/template/en/default/bug/create/comment-guided.txt.tmpl
+++ b/BugsSite/template/en/default/bug/create/comment-guided.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@mozilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -37,7 +36,7 @@
 Reproducible: [%+ cgi.param("reproducible") %]
 [% END %]
 
-[% IF !(cgi.param("reproduce_steps").match('^1\.\s+2\.\s+3\.\s+$') || cgi.param("reproduce_steps").match('^\s*$')) %]
+[% IF !(cgi.param("reproduce_steps").match('^1\.\s*2\.\s*3\.\s*$') || cgi.param("reproduce_steps").match('^\s*$')) %]
 Steps to Reproduce:
 [%+ cgi.param("reproduce_steps") %]
 [% END %]
diff --git a/BugsSite/template/en/default/bug/create/comment.txt.tmpl b/BugsSite/template/en/default/bug/create/comment.txt.tmpl
index bac3c2c..e7339d3 100644
--- a/BugsSite/template/en/default/bug/create/comment.txt.tmpl
+++ b/BugsSite/template/en/default/bug/create/comment.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/create/confirm-create-dupe.html.tmpl b/BugsSite/template/en/default/bug/create/confirm-create-dupe.html.tmpl
index 9c2b3db..b0a5cdd 100644
--- a/BugsSite/template/en/default/bug/create/confirm-create-dupe.html.tmpl
+++ b/BugsSite/template/en/default/bug/create/confirm-create-dupe.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/create/create-guided.html.tmpl b/BugsSite/template/en/default/bug/create/create-guided.html.tmpl
index 741f486..9f2a21b 100644
--- a/BugsSite/template/en/default/bug/create/create-guided.html.tmpl
+++ b/BugsSite/template/en/default/bug/create/create-guided.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@mozilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -68,40 +67,6 @@
 }
 </script>
 
-[%# Browser sniff to try and reduce the incidence of Netscape 6/7 bugs %]
-
-[% IF cgi.user_agent('Netscape(\d)') %]
-  [% matches = cgi.user_agent().match('Netscape(\d)') %]
-  <div style="background-color: lightgrey;
-              border: 1px solid black;
-              padding: 2px">
-    <font color="#990000">
-      <b>
-        You are using Netscape [% matches.0 %].
-        Report [% terms.bugs %] with this browser to the
-        <a href="http://home.netscape.com/browsers/[% matches.0 %]/feedback/index.html">
-         Netscape [% matches.0 %] Feedback Center.</a>
-      </b>
-    </font>
-    This form is only for reporting [% terms.bugs %] in the Mozilla web browser
-    and other products from mozilla.org. To report [% terms.abug %] you find 
-    in Netscape [% matches.0 %] with this form, you must reproduce it first in 
-    a recent build of
-    <a href="http://ftp.mozilla.org/pub/mozilla.org/mozilla/nightly/latest/">Mozilla</a>,
-    <a href="http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/latest-trunk/">Firefox</a>,
-<a href="http://ftp.mozilla.org/pub/mozilla.org/thunderbird/nightly/latest-trunk/">Thunderbird</a> or
-    <a href="http://ftp.mozilla.org/pub/mozilla.org/camino/nightly/latest/">Camino</a>
-    to make sure the problem hasn't been fixed already.
-  </div>
-[% END %]
-
-[% IF cgi.user_agent('Gecko/') %]
-  [% matches = cgi.user_agent().match('Gecko/(\d+)') %]
-  
-[% END %]
-
-<!-- @@@ Need UA checking here -->
-
 <a name="step1"></a>
 <h3>Step 1 of 3 - has your [% terms.bug %] already been reported?</h3>
 
@@ -122,13 +87,13 @@
 [% END %]
 
 <p>
-    <a href="duplicates.cgi?[% productstring %]&format=simple" target="somebugs">All-time Top 100</a> (loaded initially) |
-    <a href="duplicates.cgi?[% productstring %]&format=simple&sortby=delta&reverse=1&maxrows=100&changedsince=14" target="somebugs">Hot in the last two weeks</a>  
+    <a href="duplicates.cgi?[% productstring %]&amp;format=simple" target="somebugs">All-time Top 100</a> (loaded initially) |
+    <a href="duplicates.cgi?[% productstring %]&amp;format=simple&amp;sortby=delta&amp;reverse=1&amp;maxrows=100&amp;changedsince=14" target="somebugs">Hot in the last two weeks</a>  
 </p>
 
 <iframe name="somebugs" id="somebugs"
   style="border: 2px black solid"
-  src="duplicates.cgi?[% productstring %]&format=simple">
+  src="duplicates.cgi?[% productstring %]&amp;format=simple">
 </iframe>
 
 <p>
@@ -210,6 +175,11 @@
           <td valign="top">
             <select name="component" id="component"
                     size="5" onchange="PutDescription()">
+              [% IF NOT default.component_ %]
+                [%# Various b.m.o. products have a "General" component,
+                    which is a useful default. %]
+                [% default.component_ = "General" %]
+              [% END %]
               [% FOREACH c = product.components %]
                 <option value="[% c.name FILTER html %]"
                   [% " selected=\"selected\"" IF c.name == default.component_ %]>
@@ -256,9 +226,8 @@
     </td>
   </tr>
 
-  [% op_sys = [ "Windows 98", "Windows NT", "Windows 2000", "Windows XP",
-                "Mac System 9.x", "MacOS X",
-                "Linux", "All", "other" ] %]
+  [% op_sys = [ "Windows 2000", "Windows XP", "Windows Vista", "Windows 7",
+                "Mac OS X", "Linux", "All", "Other" ] %]
 
   <tr>
     <td align="right" valign="top">
@@ -317,7 +286,7 @@
     </td>
     <td valign="top">
       <input type="text" size="80" name="short_desc" id="short_desc" 
-             maxlength="255">
+             maxlength="255" spellcheck="true">
       <p>
         A sentence which summarises the problem.
         Please be descriptive and use lots of keywords.
@@ -451,11 +420,12 @@
         Add any additional information you feel may be
         relevant to this [% terms.bug %], such as the <b>theme</b> you were
         using (does the [% terms.bug %] still occur
-        with the default theme?), a <b>Talkback crash ID</b>, or special
+        with the default theme?), a 
+        <b><a href="http://kb.mozillazine.org/Quality_Feedback_Agent">Talkback crash ID</a></b>, or special
         information about <b>your computer's configuration</b>. Any information
         longer than a few lines, such as a <b>stack trace</b> or <b>HTML
         testcase</b>, should be added
-        using the "Create a new Attachment" link on the [% terms.bug %], after
+        using the "Add an Attachment" link on the [% terms.bug %], after
         it is filed. If you believe that it's relevant, please also include
         your build configuration, obtained by typing <tt>about:buildconfig</tt>
         into your URL bar.
diff --git a/BugsSite/template/en/default/bug/create/create.html.tmpl b/BugsSite/template/en/default/bug/create/create.html.tmpl
index fc6024a..ee819a2 100644
--- a/BugsSite/template/en/default/bug/create/create.html.tmpl
+++ b/BugsSite/template/en/default/bug/create/create.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,6 +21,7 @@
   #                 Marc Schumann <wurblzap@gmail.com>
   #                 Akamai Technologies <bugzilla-dev@akamai.com>
   #                 Max Kanat-Alexander <mkanat@bugzilla.org>
+  #                 Frédéric Buclin <LpSolit@gmail.com>
   #%]
 
 [% PROCESS "global/field-descs.none.tmpl" %]
@@ -30,8 +30,11 @@
 
 [% PROCESS global/header.html.tmpl
   title = title
-  style_urls = [ 'skins/standard/create_attachment.css' ]
-  javascript_urls = [ "js/attachment.js" ]
+  style_urls = [ 'skins/standard/create_attachment.css',
+                 'skins/standard/yui/calendar.css' ]
+  javascript_urls = [ "js/attachment.js", "js/util.js",
+                      "js/yui/yahoo-dom-event.js", "js/yui/calendar.js",
+                      "js/field.js" ]
 %]
 
 <script type="text/javascript">
@@ -41,6 +44,7 @@
 var last_initialowner;
 var initialccs = new Array([% product.components.size %]);
 var components = new Array([% product.components.size %]);
+var comp_desc = new Array([% product.components.size %]);
 var flags = new Array([% product.components.size %]);
 [% IF Param("useqacontact") %]
     var initialqacontacts = new Array([% product.components.size %]);
@@ -49,6 +53,7 @@
 [% count = 0 %]
 [%- FOREACH c = product.components %]
     components[[% count %]] = "[% c.name FILTER js %]";
+    comp_desc[[% count %]] = "[% c.description FILTER html_light FILTER js %]";
     initialowners[[% count %]] = "[% c.default_assignee.login FILTER js %]";
     [% flag_list = [] %]
     [% FOREACH f = c.flag_types.bug %]
@@ -102,6 +107,7 @@
         }
 
         document.getElementById('initial_cc').innerHTML = initialccs[index];
+        document.getElementById('comp_desc').innerHTML = comp_desc[index];
 
         [% IF Param("useqacontact") %]
             var contact = initialqacontacts[index];
@@ -161,11 +167,10 @@
 <input type="hidden" name="product" value="[% product.name FILTER html %]">
 <input type="hidden" name="token" value="[% token FILTER html %]">
 
-<table cellspacing="2" cellpadding="0" border="0">
-
+<table cellspacing="4" cellpadding="2" border="0">
+<tbody>
   <tr>
-    <td>&nbsp;</td>
-    <td colspan="3">
+    <td colspan="4">
     [%# Migration note: The following file corresponds to the old Param
       # 'entryheaderhtml'
       #%]
@@ -174,65 +179,98 @@
   </tr>
 
   <tr>
-    <td>&nbsp;</td>
-    <td colspan="3">&nbsp;</td>
+    <td colspan="4">&nbsp;</td>
   </tr>
 
   <tr>
-    <td align="right" valign="top"><strong>Reporter:</strong></td>
-    <td valign="top">[% user.login FILTER html %]</td>
+    <th>Product:</th>
+    <td width="10%">[% product.name FILTER html %]</td>
 
-    <td align="right" valign="top"><strong>Product:</strong></td>
-    <td valign="top">[% product.name FILTER html %]</td>
+    <th>Reporter:</th>
+    <td width="100%">[% user.login FILTER html %]</td>
   </tr>
 
   [%# We can't use the select block in these two cases for various reasons. %]
   <tr>
-    <td align="right" valign="top">
-      <strong>Version:</strong>
-    </td>
+    <th>
+      <a href="describecomponents.cgi?product=[% product.name FILTER url_quote %]">
+      Component</a>:
+    </th>
     <td>
-      <select name="version" size="5">
-        [%- FOREACH v = version %]
-          <option value="[% v FILTER html %]"
-            [% " selected=\"selected\"" IF v == default.version %]>[% v FILTER html -%]
-          </option>
-        [%- END %]
-      </select>
-    </td>
+      <select name="component" onchange="set_assign_to();" size="7">
+        [%# Build the lists of assignees and QA contacts if "usemenuforusers" is enabled. %]
+        [% IF Param("usemenuforusers") %]
+          [% assignees_list = user.get_userlist.clone %]
+          [% qa_contacts_list = user.get_userlist.clone %]
+        [% END %]
 
-    <td align="right" valign="top">
-      <strong>
-        <a href="describecomponents.cgi?product=[% product.name FILTER url_quote %]">
-          Component</a>:
-      </strong>
-    </td>
-    <td>
-      <select name="component" onchange="set_assign_to();" size="5">
         [%- FOREACH c = product.components %]
           <option value="[% c.name FILTER html %]"
             [% " selected=\"selected\"" IF c.name == default.component_ %]>
             [% c.name FILTER html -%]
           </option>
+          [% IF Param("usemenuforusers") %]
+            [% INCLUDE build_userlist default_user = c.default_assignee,
+                                      userlist = assignees_list %]
+            [% INCLUDE build_userlist default_user = c.default_qa_contact,
+                                      userlist = qa_contacts_list %]
+          [% END %]
         [%- END %]
       </select>
     </td>
+
+    <td colspan="2">
+      [%# Enclose the fieldset in a nested table so that its width changes based
+        # on the length on the component description. %]
+      <table>
+        <tr>
+          <td>
+            <fieldset>
+              <legend>Component Description</legend>
+              <div id="comp_desc" class="comment">Select a component to read its description.</div>
+            </fieldset>
+          </td>
+        </tr>
+      </table>
+    </td>
   </tr>
 
   <tr>
-    <td>&nbsp;</td>
-    <td colspan="3">&nbsp;</td>
+    <th rowspan="3">Version:</th>
+    <td rowspan="3">
+      <select name="version" size="5">
+        [%- FOREACH v = version %]
+          <option value="[% v FILTER html %]"
+            [% ' selected="selected"' IF v == default.version %]>[% v FILTER html -%]
+          </option>
+        [%- END %]
+      </select>
+    </td>
+
+    [% sel = { description => 'Severity', name => 'bug_severity' } %]
+    [% INCLUDE select %]
   </tr>
 
   <tr>
-    [% sel = { description => 'Severity', name => 'bug_severity' } %]
-    [% INCLUDE select %]
-
     [% sel = { description => 'Platform', name => 'rep_platform' } %]
     [% INCLUDE select %]
   </tr>
 
   <tr>
+    [% sel = { description => 'OS', name => 'op_sys' } %]
+    [% INCLUDE select %]
+  </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">&nbsp;</td>
+    [% END %]
+
     [% IF Param('letsubmitterchoosepriority') %]
       [% sel = { description => 'Priority', name => 'priority' } %]
       [% INCLUDE select %]
@@ -241,63 +279,82 @@
         <input type="hidden" name="priority" value="[% default.priority FILTER html %]">
       </td>
     [% END %]
-
-    [% sel = { description => 'OS', name => 'op_sys' } %]
-    [% INCLUDE select %]
   </tr>
+</tbody>
 
-  [% IF Param('usetargetmilestone') && Param('letsubmitterchoosemilestone') %]
+[% IF !Param('defaultplatform') || !Param('defaultopsys') %]
+  <tbody>
     <tr>
-      [% sel = { description => 'Target Milestone', name => 'target_milestone' } %]
-      [% INCLUDE select %]
-      <td colspan="2">&nbsp;</td>
+      <th>&nbsp;</th>
+      <td colspan="3" class="comment">
+        We've made a guess at your
+        [% IF Param('defaultplatform') %]
+          operating system. Please check it
+        [% ELSIF Param('defaultopsys') %]
+          platform. Please check it
+        [% ELSE %]
+          operating system and platform. Please check them
+        [% END %]
+        and make any corrections if necessary.
+      </td>
     </tr>
-  [% END %]
+  </tbody>
+[% END %]
 
+<tbody class="expert_fields">
   <tr>
-    <td>&nbsp;</td>
-    <td colspan="3">&nbsp;</td>
+    <td colspan="4">&nbsp;</td>
   </tr>
 
   <tr>
 [% IF bug_status.size <= 1 %]
   <input type="hidden" name="bug_status" 
          value="[% default.bug_status FILTER html %]">
-    <td align="right" valign="top"><strong>Initial State:</strong></td>
-    <td valign="top">[% status_descs.${default.bug_status} FILTER html %]</td>
+    <th>Initial State:</th>
+    <td>[% get_status(default.bug_status) FILTER html %]</td>
 [% ELSE %]
     [% sel = { description => 'Initial State', name => 'bug_status' } %]
     [% INCLUDE select %]
 [% END %]
+
     <td>&nbsp;</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) +
                       (Param("usebugaliases") ? 1 : 0)
     %]
-    <td rowspan="[% num_rows FILTER html %]" valign="top">
+
+    <td rowspan="[% num_rows FILTER html %]">
       [% IF product.flag_types.bug.size > 0 %]
-        [% PROCESS "flag/list.html.tmpl" flag_types = product.flag_types.bug
-                                         any_flags_requesteeble = 1
-                                         flag_table_id = "bug_flags"
-        %]
+        [% display_flag_headers = 0 %]
+        [% any_flags_requesteeble = 0 %]
+
+        [% FOREACH flag_type = product.flag_types.bug %]
+          [% NEXT UNLESS flag_type.is_active %]
+          [% display_flag_headers = 1 %]
+          [% SET any_flags_requesteeble = 1 IF flag_type.is_requestable && flag_type.is_requesteeble %]
+        [% END %]
+
+        [% IF display_flag_headers %]
+          [% PROCESS "flag/list.html.tmpl" flag_types = product.flag_types.bug
+                                           any_flags_requesteeble = any_flags_requesteeble
+                                           flag_table_id = "bug_flags"
+          %]
+        [% END %]
       [% END %]
     </td>
   </tr>
 
   <tr>
-    <td align="right">
-      <strong>
-        <a href="page.cgi?id=fields.html#assigned_to">Assign To</a>:
-      </strong>
-    </td>
+    <th><a href="page.cgi?id=fields.html#assigned_to">Assign To</a>:</th>
     <td colspan="2">
       [% INCLUDE global/userselect.html.tmpl
          name => "assigned_to"
          value => assigned_to
          disabled => assigned_to_disabled
-         size => 32
+         size => 30
          emptyok => 1
+         custom_userlist => assignees_list
        %]
       <noscript>(Leave blank to assign to component's default assignee)</noscript>
     </td>
@@ -305,14 +362,15 @@
   
 [% IF Param("useqacontact") %]
     <tr>
-      <td align="right"><strong>QA Contact:</strong></td>
+      <th>QA Contact:</th>
       <td colspan="2">
       [% INCLUDE global/userselect.html.tmpl
          name => "qa_contact"
          value => qa_contact
          disabled => qa_contact_disabled
-         size => 32
+         size => 30
          emptyok => 1
+         custom_userlist => qa_contacts_list
        %]
         <noscript>(Leave blank to assign to default qa contact)</noscript>
       </td>
@@ -320,20 +378,20 @@
 [% END %]
 
   <tr>
-    <td align="right"><strong>Cc:</strong></td>
+    <th>CC:</th>
     <td colspan="2">
       [% INCLUDE global/userselect.html.tmpl
          name => "cc"
          value => cc
          disabled => cc_disabled
-         size => 45
+         size => 30
          multiple => 5
        %]
     </td>
   </tr>
 
   <tr>
-    <th align="right">Default CC:</th>
+    <th>Default CC:</th>
     <td colspan="2">
       <div id="initial_cc">
           <!-- This has to happen after everything above renders,
@@ -345,19 +403,18 @@
   </tr>
   
   <tr>
-    <td>&nbsp;</td>
-    <td colspan="2"></td>
+    <td colspan="3">&nbsp;</td>
   </tr>
 
 [% IF user.in_group(Param('timetrackinggroup')) %]
   <tr>
-    <td align="right"><strong>Estimated Hours:</strong></td>
+    <th>Estimated Hours:</th>
     <td colspan="2">
       <input name="estimated_time" size="6" maxlength="6" value="0.0">
     </td>
   </tr>
   <tr>
-    <td align="right"><strong>Deadline:</strong></td>
+    <th>Deadline:</th>
     <td colspan="2">
       <input name="deadline" size="10" maxlength="10" value="[% deadline FILTER html %]">
       <small>(YYYY-MM-DD)</small>
@@ -365,14 +422,13 @@
   </tr>
 
   <tr>
-    <td>&nbsp;</td>
-    <td colspan="2"></td>
+    <td colspan="3">&nbsp;</td>
   </tr>
 [% END %]
 
 [% IF Param("usebugaliases") %]
   <tr>
-    <td align="right"><strong>Alias:</strong></td>
+    <th>Alias:</th>
     <td colspan="2">
       <input name="alias" size="20">
     </td>
@@ -380,31 +436,35 @@
 [% END %]
 
   <tr>
-    <td align="right"><strong>URL:</strong></td>
+    <th>URL:</th>
     <td colspan="2">
-      <input name="bug_file_loc" size="60"
+      <input name="bug_file_loc" size="40"
              value="[% bug_file_loc FILTER html %]">
     </td>
   </tr>
+</tbody>
 
+<tbody>
   [% USE Bugzilla %]
-  [% FOREACH field = Bugzilla.get_fields({ obsolete => 0, custom => 1, 
-                                           enter_bug => 1 }) %]
-    [% SET value = ${field.name} IF ${field.name}.defined %]
+
+  [% 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=2 %]
+      [% PROCESS bug/field.html.tmpl editable=1 value_span=3 %]
     </tr>
   [% END %]
 
   <tr>
-    <td align="right"><strong>Summary:</strong></td>
-    <td colspan="2">
-      <input name="short_desc" size="60" value="[% short_desc FILTER html %]"
-             maxlength="255">
+    <th>Summary:</th>
+    <td colspan="3">
+      <input name="short_desc" size="70" value="[% short_desc FILTER html %]"
+             maxlength="255" spellcheck="true">
     </td>
   </tr>
 
-  <tr><td align="right" valign="top"><strong>Description:</strong></td>
+  <tr>
+    <th>Description:</th>
     <td colspan="3">
       [% defaultcontent = BLOCK %]
         [% IF cloned_bug_id %]
@@ -430,7 +490,7 @@
 
   [% IF Param("insidergroup") && user.in_group(Param("insidergroup")) %]
     <tr>
-      <td></td>
+      <th>&nbsp;</th>
       <td colspan="3">
         &nbsp;&nbsp;
         <input type="checkbox" id="commentprivacy" name="commentprivacy"
@@ -441,9 +501,11 @@
       </td>
     </tr>
   [% END %]
+</tbody>
 
+<tbody class="expert_fields">
   <tr>
-    <th align="right" valign="top">Attachment:</th>
+    <th>Attachment:</th>
     <td colspan="3">
       <script type="text/javascript">
         <!--
@@ -480,38 +542,34 @@
   [% IF user.in_group('editbugs', product.id) %]
     [% IF use_keywords %]
       <tr>
-        <td align="right" valign="top">
-          <strong>
-            <a href="describekeywords.cgi">Keywords</a>:
-          </strong>
-        </td>
+        <th><a href="describekeywords.cgi">Keywords</a>:</th>
         <td colspan="3">
-          <input name="keywords" size="60" value="[% keywords FILTER html %]"> (optional)
+          <input id="keywords" name="keywords" size="40"
+                 value="[% keywords FILTER html %]"> (optional)
         </td>
       </tr>
     [% END %]
+
     <tr>
-      <td align="right">
-        <strong>Depends on:</strong>
-      </td>
-      <td>
+      <th>Depends on:</th>
+      <td colspan="3">
         <input name="dependson" accesskey="d" value="[% dependson FILTER html %]">
       </td>
     </tr>
     <tr>
-      <td align="right">
-        <strong>Blocks:</strong>
-      </td>
-      <td>
+      <th>Blocks:</th>
+      <td colspan="3">
         <input name="blocked" accesskey="b" value="[% blocked FILTER html %]">
       </td>
     </tr>
   [% END %]
+</tbody>
 
+<tbody>
+  [% IF group.size %]
   <tr>
-    <td></td>
+    <th>&nbsp;</th>
     <td colspan="3">
-    [% IF group.size %]
       <br>
         <strong>
           Only users in all of the selected groups can view this [% terms.bug %]:
@@ -531,46 +589,27 @@
           [% " checked=\"checked\"" IF g.checked %]>
           <label for="bit-[% g.bit %]">[% g.description FILTER html_light %]</label><br>
       [% END %]
-      <br>
-    [% END %]
     </td>
   </tr>
+  [% END %]
 
   [%# Form controls for entering additional data about the bug being created. %]
   [% Hook.process("form") %]
 
   <tr>
-    <td></td>
+    <th>&nbsp;</th>
     <td colspan="3">
-      <input type="submit" id="commit" value="    Commit    "
+      <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;">
       &nbsp;&nbsp;&nbsp;&nbsp;
       <input type="submit" name="maketemplate" id="maketemplate"
-             value="Remember values as bookmarkable template">
+             value="Remember values as bookmarkable template"
+             class="expert_fields">
     </td>
   </tr>
-
-[% UNLESS (Param('defaultplatform') && Param('defaultopsys')) %]
-  <tr>
-    <td></td>
-    <td colspan="3">
-      <br>
-      We've made a guess at your
-  [% IF Param('defaultplatform') %]
-      operating system. Please check it
-  [% ELSIF Param('defaultopsys') %]
-      platform. Please check it
-  [% ELSE %]
-      operating system and platform. Please check them
-  [% END %]
-      and, if we got it wrong, email
-      [%+ Param('maintainer') %].
-    </td>
-  </tr>
-[% END %]
-
+</tbody>
   </table>
   <input type="hidden" name="form_name" value="enter_bug">
 </form>
@@ -586,12 +625,9 @@
 
 [% BLOCK select %]
   [% IF sel.description %]
-  <td align="right">
-    <strong>
-      <a href="page.cgi?id=fields.html#[% sel.name %]">
-        [% sel.description %]</a>:
-    </strong>
-  </td>
+    <th>
+      <a href="page.cgi?id=fields.html#[% sel.name %]">[% sel.description %]</a>:
+    </th>
   [% END %]
 
   <td>
@@ -600,7 +636,7 @@
       <option value="[% x FILTER html %]"
         [% " selected=\"selected\"" IF x == default.${sel.name} %]>
         [% IF sel.name == "bug_status" %]
-          [% status_descs.$x FILTER html %]
+          [% get_status(x) FILTER html %]
         [% ELSE %]
           [% x FILTER html %]
         [% END %]</option>
@@ -608,3 +644,21 @@
     </select>
   </td>
 [% END %]
+
+[% BLOCK build_userlist %]
+  [% user_found = 0 %]
+  [% default_login = default_user.login %]
+  [% RETURN UNLESS default_login %]
+
+  [% FOREACH user = userlist %]
+    [% IF user.login == default_login %]
+      [% user_found = 1 %]
+      [% LAST %]
+    [% END %]
+  [% END %]
+
+  [% userlist.push({login    => default_login,
+                    identity => default_user.identity,
+                    visible  => 1})
+     UNLESS user_found %]
+[% END %]
diff --git a/BugsSite/template/en/default/bug/create/created.html.tmpl b/BugsSite/template/en/default/bug/create/created.html.tmpl
index 17f057c..6226fdc 100644
--- a/BugsSite/template/en/default/bug/create/created.html.tmpl
+++ b/BugsSite/template/en/default/bug/create/created.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -37,6 +36,11 @@
 
 [% 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" ]
+  
+  
 %]
 
 [% header_done = 1 %]
@@ -58,7 +62,7 @@
 
 <hr>
 
-[% PROCESS bug/navigate.html.tmpl %]
+[% PROCESS bug/navigate.html.tmpl bottom_navigator => 1 %]
 
 <br>
 
diff --git a/BugsSite/template/en/default/bug/create/make-template.html.tmpl b/BugsSite/template/en/default/bug/create/make-template.html.tmpl
index 6a7ea6a..1397483 100644
--- a/BugsSite/template/en/default/bug/create/make-template.html.tmpl
+++ b/BugsSite/template/en/default/bug/create/make-template.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/create/user-message.html.tmpl b/BugsSite/template/en/default/bug/create/user-message.html.tmpl
index dbbd114..ac2cc29 100644
--- a/BugsSite/template/en/default/bug/create/user-message.html.tmpl
+++ b/BugsSite/template/en/default/bug/create/user-message.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/dependency-graph.html.tmpl b/BugsSite/template/en/default/bug/dependency-graph.html.tmpl
index 5c1eb84..37dcde0 100644
--- a/BugsSite/template/en/default/bug/dependency-graph.html.tmpl
+++ b/BugsSite/template/en/default/bug/dependency-graph.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -64,42 +63,39 @@
 
 <hr>
 
-<form action="showdependencygraph.cgi">
+<form action="showdependencygraph.cgi" method="GET">
   <table>
     <tr>
+      <th align="left"><label for="id">[% terms.Bug %] numbers</label>:</th>
+      <td><input id="id" name="id" value="[% bug_id %]"></td>
       <td>
-        [% terms.Bug %] numbers:
-        <input name="id" value="[% bug_id %]">
+        <input type="checkbox" id="showsummary" name="showsummary" [% " checked" IF showsummary %]>
+        <label for="showsummary">Show the summaries of all displayed [% terms.bugs %]</label>
       </td>
     </tr>
 
     <tr>
-      <td>
-        <input type="checkbox" name="doall"
-        [% " checked" IF doall %]>
-        Show <b>every</b> [% terms.bug %] in the system with dependencies
+      <th align="left"><label for="display">Display:</label></th>
+      <td colspan="2">
+        <select id="display" name="display">
+          <option value="tree"[% 'selected="selected"' IF (!display || display == "tree") %]>
+            Restrict to [% terms.bugs %] having a direct relationship with entered [% terms.bugs %]</option>
+          <option value="web" [% 'selected="selected"' IF display == "web" %]>
+            Show all [% terms.bugs %] having any relationship with entered [% terms.bugs %]</option>
+          <option value="doall" [% 'selected="selected"' IF display == "doall" %]>
+            Show every [% terms.bug %] in the system with dependencies</option>
+        </select>
       </td>
     </tr>
 
     <tr>
-      <td colspan="3">
-        <input type="checkbox" name="showsummary"
-          [% " checked" IF showsummary %]>
-        Show the summaries of all displayed [% terms.bugs %]
-      </td>
-    </tr>
-
-    <tr>
-      <td colspan="3">
-        <select name="rankdir">
-          <option value="TB"
-            [% " selected" IF rankdir == "TB" %]>
-            Orient top-to-bottom
-          </option>
-          <option value="LR"
-            [% " selected" IF rankdir == "LR" %]>
-            Orient left-to-right
-          </option>
+      <th align="left"><label for="rankdir">Orientation:</label></th>
+      <td colspan="2">
+        <select id="rankdir" name="rankdir">
+          <option value="TB"[% " selected" IF rankdir == "TB" %]>Top to bottom</option>
+          <option value="BT"[% " selected" IF rankdir == "BT" %]>Bottom to top</option>
+          <option value="LR"[% " selected" IF rankdir == "LR" %]>Left to right</option>
+          <option value="RL"[% " selected" IF rankdir == "RL" %]>Right to left</option>
         </select>
       </td>
     </tr>
diff --git a/BugsSite/template/en/default/bug/dependency-tree.html.tmpl b/BugsSite/template/en/default/bug/dependency-tree.html.tmpl
index db3144f..cc49d2f 100644
--- a/BugsSite/template/en/default/bug/dependency-tree.html.tmpl
+++ b/BugsSite/template/en/default/bug/dependency-tree.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,7 +21,7 @@
   #                 André Batosti <batosti@async.com.br>
   #%]
 
-[% PROCESS global/variables.none.tmpl %]
+[% PROCESS 'global/field-descs.none.tmpl' %]
 
 [% filtered_desc = blocked_tree.$bugid.short_desc FILTER html %]
 [% PROCESS global/header.html.tmpl
@@ -32,6 +31,7 @@
    javascript_urls = ["js/expanding-tree.js"]
    style_urls      = ["skins/standard/dependency-tree.css"]
    subheader      = filtered_desc
+   doc_section = "hintsandtips.html#dependencytree"
 %]
 
 [% PROCESS depthControlToolbar %]
@@ -153,7 +153,7 @@
 [% END %]
 
 [% BLOCK buginfo %]
-  [% bug.bug_status FILTER html -%] [%+ bug.resolution FILTER html %];
+  [% get_status(bug.bug_status) FILTER html -%] [%+ get_resolution(bug.resolution) FILTER html %];
   [%-%] assigned to [% bug.assigned_to.login FILTER html %]
   [%-%][% "; Target: " _ bug.target_milestone IF bug.target_milestone %]
 [% END %]
diff --git a/BugsSite/template/en/default/bug/edit.html.tmpl b/BugsSite/template/en/default/bug/edit.html.tmpl
index de132c2..baf5833 100644
--- a/BugsSite/template/en/default/bug/edit.html.tmpl
+++ b/BugsSite/template/en/default/bug/edit.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -21,6 +20,8 @@
   #                 Max Kanat-Alexander <mkanat@bugzilla.org>
   #                 Frédéric Buclin <LpSolit@gmail.com>
   #                 Olav Vitters <olav@bkor.dhs.org>
+  #                 Guy Pyrzak <guy.pyrzak@gmail.com>
+  #                 Elliotte Martin <emartin@everythingsolved.com>
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
@@ -31,36 +32,43 @@
 
   <script type="text/javascript">
   <!--
-
+  
   /* Outputs a link to call replyToComment(); used to reduce HTML output */
-  function addReplyLink(id) {
+  function addReplyLink(id, real_id) {
       /* XXX this should really be updated to use the DOM Core's
        * createElement, but finding a container isn't trivial.
        */
-      document.write('[<a href="#add_comment" onclick="replyToComment(' + 
-                     id + ');">reply<' + '/a>]');
+      [% IF user.settings.quote_replies.value != 'off' %]
+        document.write('[<a href="#add_comment" onclick="replyToComment(' + 
+                       id + ',' + real_id + ');">reply<' + '/a>]');
+      [% END %]
   }
 
   /* Adds the reply text to the `comment' textarea */
-  function replyToComment(id) {
-      /* 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/);
-
+  function replyToComment(id, real_id) {
+      var prefix = "(In reply to comment #" + id + ")\n";
       var replytext = "";
-      for (var i=0; i < text.length; i++) {
-          replytext += "> " + text[i] + "\n"; 
-      }
+      [% IF user.settings.quote_replies.value == 'quoted_reply' %]
+        /* pre id="comment_name_N" */
+        var text_elem = document.getElementById('comment_text_'+id);
+        var text = getText(text_elem);
 
-      replytext = "(In reply to comment #" + id + ")\n" + replytext + "\n";
+        /* make sure we split on all newlines -- IE or Moz use \r and \n
+         * respectively.
+         */
+        text = text.split(/\r|\n/);
+
+        for (var i=0; i < text.length; i++) {
+            replytext += "> " + text[i] + "\n"; 
+        }
+
+        replytext = prefix + replytext + "\n";
+      [% ELSIF user.settings.quote_replies.value == 'simple_reply' %]
+        replytext = prefix;
+      [% END %]
 
     [% IF Param("insidergroup") && user.in_group(Param("insidergroup")) %]
-      if (document.getElementById('isprivate-'+id).checked) {
+      if (document.getElementById('isprivate_' + real_id).checked) {
           document.getElementById('newcommentprivacy').checked = 'checked';
       }
     [% END %]
@@ -136,247 +144,78 @@
   <input type="hidden" name="delta_ts" value="[% bug.delta_ts %]">
   <input type="hidden" name="longdesclength" value="[% bug.longdescs.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 %]">
 
-  [%# That's the main table, which contains all editable fields. %]
+  [% PROCESS section_title %]
   <table>
     <tr>
-      <td valign="top">
-        <fieldset>
-          <legend>Details</legend>
-          <table>
-
-            [%# *** Summary *** %]
-            <tr>
-              <td align="right">
-                <label for="short_desc" accesskey="s"><b><u>S</u>ummary</b></label>:
-              </td>
-              [% PROCESS input inputname => "short_desc" size => "60" colspan => 2
-                               maxlength => 255 %]
-            </tr>
-
-            <tr>
-              <td colspan="3">
-                <table>
-                  <tr>
-                    [%# *** ID, product, component, status and resolution *** %]
-                    <td valign="top">[% PROCESS section_details1 %]</td>
-
-                    [%# *** Platform, OS, severity, priority, version and milestone *** %]
-                    <td valign="top">[% PROCESS section_details2 %]</td>
-                  </tr>
-                </table>
-              </td>
-            </tr>
-
-            <tr>
-              <td colspan="3"><hr size="1"></td>
-            </tr>
-
-            [%# *** URL Whiteboard Keywords *** %]
-
-            <tr>
-              <td align="right">
-                <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>
-              [% PROCESS input inputname => "bug_file_loc" size => "60" colspan => 2 %]
-            </tr>
-
-            [% IF Param('usestatuswhiteboard') %]
-              <tr>
-                <td align="right">
-                  <label for="status_whiteboard" accesskey="w"><b><u>W</u>hiteboard</b></label>:
-                </td>
-                [% PROCESS input inputname => "status_whiteboard" size => "60" colspan => 2 %]
-              </tr>
-            [% END %]
-
-            [% IF use_keywords %]
-              <tr>
-                <td align="right">
-                  <label for="keywords" accesskey="k">
-                    <b><a href="describekeywords.cgi"><u>K</u>eywords</a></b></label>:
-                </td>
-                [% PROCESS input inputname => "keywords" size => 60 colspan => 2
-                                 value => bug.keywords.join(', ') %]
-              </tr>
-            [% END %]
-
-            [%# *** Custom Fields *** %]
-
-            [% USE Bugzilla %]
-            [% fields = Bugzilla.get_fields({ obsolete => 0, custom => 1 }) %]
-            [% IF fields %]
-              [% FOREACH field = fields %]
-                <tr>
-                  [% PROCESS bug/field.html.tmpl value=bug.${field.name}
-                                                 editable = bug.check_can_change_field(field.name, 0, 1)
-                                                 value_span = 2 %]
-                </tr>
-              [% END %]
-            [% END %]
-
-            [%# *** Dependencies *** %]
-
-            <tr>
-              [% PROCESS dependencies
-                         dep = { title => "Depends&nbsp;on", fieldname => "dependson" } %]
-            </tr>
-
-            <tr>
-              [% PROCESS dependencies accesskey = "b"
-                         dep = { title => "<u>B</u>locks", fieldname => "blocked" } %]
-            </tr>
-
-            <tr>
-              <th>&nbsp;</th>
-
-              <td colspan="2">
-                <a href="showdependencytree.cgi?id=[% bug.bug_id %]&amp;hide_resolved=1">Show
-                   dependency tree</a>
-
-                [% IF Param('webdotbase') %]
-                  - <a href="showdependencygraph.cgi?id=[% bug.bug_id %]">Show
-                       dependency graph</a>
-                [% END %]
-              </td>
-            </tr>
-          </table>
-        </fieldset>
-      </td>
-
-      [%# Force the layout to be displayed now, before drawing the second column of the table.
-        # This should prevent bug 370739 when using Firefox 2. %]
-      <script type="text/javascript">
-        <!--
-        var v = document.body.offsetHeight;
-        //-->
-      </script>
-
-      <td valign="top">
-        [%# *** Reporter, owner and QA contact *** %]
-        <fieldset>
-          <legend>People</legend>
+      [%# 1st Column %]
+      <td id="bz_show_bug_column_1" class="bz_show_bug_column">     
+        <table>
+          [%# *** ID, product, component, status, resolution, Hardware, and  OS *** %]
+          [% PROCESS section_status %]
+          
+          [% PROCESS section_spacer %]
+          
+          [% PROCESS section_details1 %]
+          
+          [% PROCESS section_spacer %]
+          
+          [%# *** severity, priority, version and milestone *** %]
+          [% PROCESS section_details2 %]            
+          
+          [%# *** assigned to and qa contact *** %]
           [% PROCESS section_people %]
-        </fieldset>
+          
+          [% PROCESS section_spacer %]
+          
+          [% PROCESS section_url_keyword_whiteboard %]
+          
+          [% PROCESS section_spacer %]
+          
+          [%# *** Dependencies *** %]
+          [% PROCESS section_dependson_blocks %]
+          
+        </table>
+      </td>
+      <td>
+        <div class="bz_column_spacer">&nbsp;</div>
+      </td>
+      [%# 2nd Column %]
+      <td id="bz_show_bug_column_2" class="bz_show_bug_column">
+        <table cellpadding="3" cellspacing="1">
+        [%# *** Reported and modified dates *** %]
+         [% PROCESS section_dates %]
+         
+         [% PROCESS section_cclist %]
+         
+         [% PROCESS section_spacer %] 
+         
+         [% PROCESS section_customfields %]
+         
+         [% PROCESS section_spacer %]
+         
+         [% Hook.process("after_custom_fields") %]
+                
+         [% PROCESS section_flags %]
 
-        [%# *** Flags *** %]
-        [% show_bug_flags = 0 %]
-        [% FOREACH type = bug.flag_types %]
-          [% IF (type.flags && type.flags.size > 0) || (user.id && type.is_active) %]
-            [% show_bug_flags = 1 %]
-            [% LAST %]
-          [% END %]
-        [% END %]
-        [% IF show_bug_flags %]
-          <fieldset>
-            <legend>Flags</legend>
-            <table cellspacing="1" cellpadding="1">
-              <tr>
-                <td colspan="2" valign="top">
-                  [% 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>
-            </table>
-          </fieldset>
-        [% END %]
+        </table>
+      </td>
+    </tr>
+    <tr>
+      <td colspan="3">
+          <hr id="bz_top_half_spacer">
       </td>
     </tr>
   </table>
 
+  
+  [% PROCESS section_restrict_visibility %]
   [% IF user.in_group(Param('timetrackinggroup')) %]
     <br>
-    <table cellspacing="0" cellpadding="4" border="1">
-      <tr>
-        <th align="center" bgcolor="#cccccc">
-          <label for="estimated_time">Orig. Est.</label>
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          Current Est.
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          <label for="work_time">Hours Worked</label>
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          <label for="remaining_time">Hours Left</label>
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          %Complete
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          Gain
-        </th>
-        <th align="center" bgcolor="#cccccc">
-          <label for="deadline">Deadline</label>
-        </th>
-      </tr>
-      <tr>
-        <td align="center">
-          <input name="estimated_time" id="estimated_time"
-                 value="[% PROCESS formattimeunit
-                                   time_unit=bug.estimated_time %]"
-                 size="6" maxlength="6">
-        </td>
-        <td align="center">
-          [% PROCESS formattimeunit
-                     time_unit=(bug.actual_time + bug.remaining_time) %]
-        </td>
-        <td align="center">
-          [% PROCESS formattimeunit time_unit=bug.actual_time %] +
-          <input name="work_time" id="work_time"
-                 value="0" size="3" maxlength="6"
-                 onchange="adjustRemainingTime();">
-        </td>
-        <td align="center">
-          <input name="remaining_time" id="remaining_time"
-                 value="[% PROCESS formattimeunit
-                                   time_unit=bug.remaining_time %]"
-                 size="6" maxlength="6" onchange="updateRemainingTime();">
-        </td>
-        <td align="center">
-          [% PROCESS calculatepercentage act=bug.actual_time
-                                         rem=bug.remaining_time %]
-        </td>
-        <td align="center">
-          [% PROCESS formattimeunit time_unit=bug.estimated_time - (bug.actual_time + bug.remaining_time) %]
-        </td>
-         <td align="center">
-           <input name="deadline" id="deadline" value="[% bug.deadline %]"
-                  size="10" maxlength="10"><br />
-           <small>(YYYY-MM-DD)</small>
-        </td>        
-      </tr>
-      <tr>
-        <td colspan="7" align="right">
-          <a href="summarize_time.cgi?id=[% bug.bug_id %]&amp;do_depends=1">
-          Summarize time (including time for [% terms.bugs %]
-          blocking this [% terms.bug %])</a>
-        </td>
-      </tr>
-    </table>
+    [% PROCESS section_timetracking %]
   [% END %]
+  
 
 [%# *** Attachments *** %]
 
@@ -393,8 +232,11 @@
   <br>
   <table cellpadding="1" cellspacing="1">
     <tr>
-      <td>
+      <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"
@@ -403,7 +245,6 @@
             <label for="newcommentprivacy">Private</label>
           [% END %]
           <br>
-          <a name="add_comment"></a>
           [% INCLUDE global/textarea.html.tmpl
                      name      = 'comment'
                      id        = 'comment'
@@ -411,208 +252,197 @@
                      maxrows   = 25
                      cols      = constants.COMMENT_COLS
           %]
-
-          [% IF NOT bug.cc || NOT bug.cc.contains(user.login) %]
-            [% has_role = bug.user.isreporter
-                          || bug.assigned_to.id == user.id
-                          || (Param('useqacontact')
-                              && bug.qa_contact
-                              && bug.qa_contact.id == user.id) %]
-
-            <br>
-            <input type="checkbox" id="addselfcc" name="addselfcc"
-              [% " checked=\"checked\""
-                   IF user.settings.state_addselfcc.value == 'always'
-                      || (!has_role
-                          && user.settings.state_addselfcc.value == 'cc_unless_role') %]>
-            <label for="addselfcc">Add [% user.identity FILTER html %] to CC list</label>
-          [% END %]
+          <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="show_bug.cgi?id=[% bug.bug_id %]&amp;GoAheadAndLogIn=1">log in</a>
+              <a href="[% IF Param('ssl') != 'never' %][% Param('sslbase') %][% END %]show_bug.cgi?id=[% bug.bug_id %]&amp;GoAheadAndLogIn=1">log in</a>
               before you can comment on or make changes to this [% terms.bug %].
             </p>
           </fieldset>
         [% END %]
-      </td>
-
-      <td valign="top">
-        <fieldset>
-          <legend>Related actions</legend>
-          <ul>
-            <li><a href="show_activity.cgi?id=[% bug.bug_id %]">View [% terms.Bug %] Activity</a></li>
-            <li><a href="show_bug.cgi?format=multiple&amp;id=[% bug.bug_id %]">Format For Printing</a></li>
-            <li><a href="show_bug.cgi?ctype=xml&amp;id=[% bug.bug_id %]">XML</a></li>
-            <li><a href="enter_bug.cgi?cloned_bug_id=[% bug.bug_id %]">Clone This [% terms.Bug %]</a></li>
-          </ul>
-
-          [% IF bug.use_votes %]
-            <br>
-            <b><a href="page.cgi?id=voting.html">Votes</a></b>: [% bug.votes %]
-            <ul>
-              [% IF bug.votes %]
-                <li><a href="votes.cgi?action=show_bug&amp;bug_id=[% bug.bug_id %]">Show
-                             votes for this [% terms.bug %]</a></li>
-              [% END %]
-              <li><a href="votes.cgi?action=show_user&amp;bug_id=[% bug.bug_id %]#vote_[% bug.bug_id %]">Vote
-                           for this [% terms.bug %]</a></li>
-            </ul>
-          [% END %]
-
-          [%# Links to more things users can do with this bug. %]
-          [% Hook.process("links") %]
-        </fieldset>
+        [%# *** Additional Comments *** %]
+        <hr>
+        <div id="comments">
+        [% PROCESS bug/comments.html.tmpl
+           comments = bug.longdescs
+           mode = user.id ? "edit" : "show"
+         %]
+        </div>
+        
       </td>
     </tr>
   </table>
-  <br>
-
-  [% IF bug.groups.size > 0 %]
-    [% inallgroups = 1 %]
-    [% inagroup = 0 %]
-    [% FOREACH group = bug.groups %]
-      [% SET inallgroups = 0 IF NOT group.ingroup %]
-      [% SET inagroup = 1 IF group.ison %]
-
-      [% IF NOT group.mandatory %]
-        [% IF NOT emitted_description %]
-          [% emitted_description = 1 %]
-          <br>
-          <b>Only users in all of the selected groups can view this [% terms.bug %]:</b>
-          <br>
-          <font size="-1">
-            (Unchecking all boxes makes this a more public [% terms.bug %].)
-          </font>
-          <br>
-          <br>
-        [% END %]
-
-      &nbsp;&nbsp;&nbsp;&nbsp;
-      <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 %]
-    [% END %]
-
-    [% IF NOT inallgroups %]
-      <b>
-        Only members of a group can change the visibility of [% terms.abug %] for
-        that group
-      </b>
-    <br>
-    [% END %]
-
-    [% IF inagroup %]
-      <p>
-        <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>
-      </p>
-
-      <p>
-        <input type="checkbox" value="1"
-               name="reporter_accessible" id="reporter_accessible"
-               [% " checked" IF bug.reporter_accessible %]
-               [% " disabled=\"disabled\"" UNLESS bug.check_can_change_field("reporter_accessible", 0, 1) %]>
-        <label for="reporter_accessible">Reporter</label>
-        <input type="checkbox" value="1"
-               name="cclist_accessible" id="cclist_accessible"
-               [% " checked" IF bug.cclist_accessible %]
-               [% " disabled=\"disabled\"" UNLESS bug.check_can_change_field("cclist_accessible", 0, 1) %]>
-        <label for="cclist_accessible">CC List</label>
-      </p>
-    [% END %]
-  [% END %]
-
-[% PROCESS bug/knob.html.tmpl IF user.id %]
-
-[%# *** Additional Comments *** %]
-
-<hr>
-
-<div id="comments">
-[% PROCESS bug/comments.html.tmpl
-   comments = bug.longdescs
-   mode = user.id ? "edit" : "show"
- %]
-</div>
-
 </form>
 
 [%############################################################################%]
+[%# Block for the Title (alias and short desc)                               #%]
+[%############################################################################%]
+
+[% BLOCK section_title %]
+  [%# That's the main table, which contains all editable fields. %]
+  <div class="bz_alias_short_desc_container">
+    
+     <a href="show_bug.cgi?id=[% bug.bug_id %]">
+        <b>[% terms.Bug %]&nbsp;[% bug.bug_id FILTER html %]</b></a> - 
+     <span id="summary_alias_container" class="bz_default_hidden"> 
+      [% IF Param("usebugaliases") %]
+        [% IF bug.alias != "" %]
+          (<span id="alias_nonedit_display">[% bug.alias FILTER html %]</span>) 
+        [% END %]
+      [% END %]
+      <span id="short_desc_nonedit_display">[% bug.short_desc FILTER html %]</span>
+      [% IF bug.check_can_change_field('short_desc', 0, 1) || 
+            bug.check_can_change_field('alias', 0, 1)  %]
+        <small class="editme">(<a href="#" id="editme_action">edit</a>)</small>
+      [% END %]
+     </span>
+  
+       
+    <div id="summary_alias_input">
+      <table id="summary"> 
+        [% IF Param("usebugaliases") %]
+          <tr>
+          [% IF bug.check_can_change_field('alias', 0, 1) %]      
+            <td>
+              <label 
+                for="alias" 
+                title="a name for the 
+                       [% terms.bug %] that can be used in place of its ID number, 
+                       [%%] e.g. when adding it to a list of dependencies"
+                >Alias</label>:</td><td>
+          [% ELSIF bug.alias %]
+            <td colspan="2">(
+          [% ELSE %]
+            <td colspan="2">
+          [% END %]
+          [% PROCESS input inputname => "alias" 
+                     size => "20" 
+                     maxlength => "20"  
+                     no_td => 1 
+                     %][% ")" IF NOT bug.check_can_change_field('alias', 0, 1) 
+                                  && bug.alias %]
+          </td>
+        </tr>
+        [% END %] 
+        [%# *** Summary *** %]
+        <tr>
+          <td>
+            <label accesskey="s" for="short_desc"><u>S</u>ummary</label>:
+          </td>
+          <td>
+            [% PROCESS input inputname => "short_desc" size => "80" colspan => 2
+                             maxlength => 255 spellcheck => "true" no_td => 1 %]
+          </td>
+        </tr>
+      </table>
+    </div>
+  </div>
+  <script type="text/javascript">
+    hideAliasAndSummary('[% bug.short_desc FILTER js %]', '[% bug.alias FILTER js %]');
+  </script>
+[% END %]
+
+[%############################################################################%]
 [%# Block for the first table in the "Details" section                       #%]
 [%############################################################################%]
 
 [% BLOCK section_details1 %]
-  <table cellspacing="1" cellpadding="1">
-    <tr>
-      <td align="right">
-        [% IF Param('useclassification') && bug.classification_id != 1 %]
-          <b>[[% bug.classification FILTER html %]]</b>
-        [% END %]
-        <b>[% terms.Bug %]#</b>:
-      </td>
-      <td>
-        <a href="[% Param('urlbase') %]show_bug.cgi?id=[% bug.bug_id %]">
-           [% bug.bug_id %]</a>
-      </td>
-    </tr>
 
-    [% IF Param("usebugaliases") %]
-      <tr>
-        <td align="right">
-          <label for="alias" title="a name for the [% terms.bug %] that can be used in place of its ID number, f.e. when adding it to a list of dependencies"><b>Alias</b></label>:
-        </td>
-        [% PROCESS input inputname => "alias" size => "20" maxlength => "20" %]
-      </tr>
-    [% END %]
-
+    [%#############%]
+    [%#  PRODUCT  #%]
+    [%#############%]
     <tr>
-      <td align="right">
+      <td class="field_label">
         <label for="product" accesskey="p"><b><u>P</u>roduct</b></label>:
       </td>
       [% PROCESS select selname => "product" %]
     </tr>
-
+    [%###############%]    
+    [%#  Component  #%]
+    [%###############%]
     <tr>
-      <td align="right">
-        <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 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" %]
     </tr>
-
     <tr>
-      <td align="right">
-        <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
+      <td class="field_label">
+        <label for="version"><b>Version</b></label>:
       </td>
-      <td>[% status_descs.${bug.bug_status} FILTER html %]</td>
-    </tr>
 
+      [% PROCESS select selname => "version" %]
+    </tr>
+    [%############%]    
+    [%# PLATFORM #%]
+    [%############%]    
     <tr>
-      <td align="right">
-        <b><a href="page.cgi?id=fields.html#resolution">Resolution</a></b>:
+      <td class="field_label">
+        <label for="rep_platform" accesskey="h"><b>Platform</b></label>:
       </td>
       <td>
-        [% get_resolution(bug.resolution) FILTER html %]
-        [% IF bug.resolution == "DUPLICATE" %]
-          of [% terms.bug %] [%+ "${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
-        [% END %]
+       [% PROCESS select selname => "rep_platform" no_td=> 1 %]
+       [%+ PROCESS select selname => "op_sys" no_td=> 1 %]
+       <script type="text/javascript">
+         assignToDefaultOnChange(['product', 'component']);
+       </script>
       </td>
     </tr>
-  </table>
+
+
+
+[% END %]
+
+[%############################################################################%]
+[%# Block for the status section                                             #%]
+[%############################################################################%]
+
+[% BLOCK section_status %]
+  <tr>
+    <td class="field_label">
+      <b><a href="page.cgi?id=fields.html#status">Status</a></b>:
+    </td>
+    <td id="bz_field_status">
+      <span id="static_bug_status">
+        [% get_status(bug.bug_status) FILTER html %]
+        [% IF bug.resolution %]
+          [%+ get_resolution(bug.resolution) FILTER html %]
+          [% IF bug.dup_id %]
+            of [% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
+          [% END %]
+        [% END %]
+        [% IF bug.user.canedit || bug.user.isreporter %]
+          (<a href="#add_comment" 
+              onclick="window.setTimeout(function() { document.getElementById('bug_status').focus(); }, 10)">edit</a>)
+        [% END %]
+      </span>
+    </td>
+  </tr>
 [% END %]
 
 [%############################################################################%]
@@ -620,56 +450,51 @@
 [%############################################################################%]
 
 [% BLOCK section_details2 %]
-  <table cellspacing="1" cellpadding="1">
-    <tr>
-      <td align="right">
-        <label for="rep_platform" accesskey="h"><b><u>H</u>ardware</b></label>:
-      </td>
-      [% PROCESS select selname => "rep_platform" %]
-    </tr>
 
+ [%###############################################################%]
+ [%# Importance (priority, severity and votes) #%]
+ [%###############################################################%]
     <tr>
-      <td align="right">
-        <label for="op_sys" accesskey="o"><b><u>O</u>S</b></label>:
+      <td class="field_label">
+        <label for="priority" accesskey="i">
+          <b><a href="page.cgi?id=fields.html#importance"><u>I</u>mportance</a></b></label>:
       </td>
-      [% PROCESS select selname => "op_sys" %]
-    </tr>
-
-    <tr>
-      <td align="right">
-        <label for="version"><b>Version</b></label>:
+      <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&amp;bug_id=[% bug.bug_id %]">
+              [% bug.votes %] 
+              [% IF bug.votes == 1 %]
+                vote
+              [% ELSE %]
+                votes
+              [% END %]</a> 
+          [% END %]    
+          (<a href="votes.cgi?action=show_user&amp;bug_id=
+                  [% bug.bug_id %]#vote_[% bug.bug_id %]">vote</a>)
+          </span>  
+        [% END %]
       </td>
-      [% PROCESS select selname => "version" %]
-    </tr>
-
-    <tr>
-      <td align="right">
-        <label for="priority" accesskey="i"><b><a href="page.cgi?id=fields.html#priority">Pr<u>i</u>ority</a></b></label>:
-      </td>
-      [% PROCESS select selname => "priority" %]
-    </tr>
-
-    <tr>
-      <td align="right">
-        <label for="bug_severity"><b><a href="page.cgi?id=fields.html#bug_severity">Severity</a></b></label>:
-      </td>
-      [% PROCESS select selname = "bug_severity" %]
     </tr>
 
     [% IF Param("usetargetmilestone") && bug.target_milestone %]
       <tr>
-        <td align="right">
+        <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 %]
+            Target&nbsp;Milestone[% "</a>" IF bug.milestoneurl %]
           [%%]</b></label>:
         </td>
         [% PROCESS select selname = "target_milestone" %]
       </tr>
     [% END %]
-  </table>
+  
 [% END %]
 
 [%############################################################################%]
@@ -677,81 +502,376 @@
 [%############################################################################%]
 
 [% BLOCK section_people %]
-  <table cellpadding="1" cellspacing="1">
-    <tr>
-      <td align="right">
-        <b>Reporter</b>:
-      </td>
-      <td>
-        <a href="mailto:[% bug.reporter.email FILTER html %]">
-          [% bug.reporter.identity FILTER html %]</a>
-      </td>
-    </tr>
 
     <tr>
-      <td align="right">
-        <b><a href="page.cgi?id=fields.html#assigned_to">Assigned&nbsp;To</a></b>:
+      <td class="field_label">
+        <b><a href="page.cgi?id=fields.html#assigned_to">Assigned To</a></b>:
       </td>
       <td>
-        <a href="mailto:[% bug.assigned_to.email FILTER html %]">
-           [% bug.assigned_to.identity FILTER html %]</a>
+        [% 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 %]
+              (<a href="#" id="bz_assignee_edit_action">edit</a>)
+            </span>
+          </div>
+          <div id="bz_assignee_input">
+            [% INCLUDE global/userselect.html.tmpl
+                 id => "assigned_to"
+                 name => "assigned_to"
+                 value => bug.assigned_to.login
+                 size => 30
+            %]
+            <br>
+            <input type="checkbox" id="set_default_assignee" name="set_default_assignee" value="1">
+            <label id="set_default_assignee_label" for="set_default_assignee">Reset Assignee to default</label>
+          </div>
+          <script type="text/javascript">
+           hideEditableField('bz_assignee_edit_container', 
+                             'bz_assignee_input', 
+                             'bz_assignee_edit_action', 
+                             'assigned_to', 
+                             '[% bug.assigned_to.login FILTER js %]' );
+           initDefaultCheckbox('assignee');                  
+          </script>
+        [% ELSE %]
+          [% INCLUDE user_identity user => bug.assigned_to %]
+        [% END %]
       </td>
     </tr>
 
     [% IF Param('useqacontact') %]
     <tr>
-      <td align="right">
+      <td class="field_label">
         <label for="qa_contact" accesskey="q"><b><u>Q</u>A Contact</b></label>:
       </td>
-      <td colspan="7">
+      <td>
+
         [% IF bug.check_can_change_field("qa_contact", 0, 1) %]
-          [% INCLUDE global/userselect.html.tmpl
-              id => "qa_contact"
-              name => "qa_contact"
-              value => bug.qa_contact.login
-              size => 30
-              emptyok => 1
-          %]
-        [% ELSE %]
-          <input type="hidden" name="qa_contact" id="qa_contact"
-                 value="[% bug.qa_contact.login FILTER html %]">
-          <a href="mailto:[% bug.qa_contact.email FILTER html %]">
-            [% IF bug.qa_contact.login && bug.qa_contact.login.length > 30 %]
-              <span title="[% bug.qa_contact.login FILTER html %]">
-                [% bug.qa_contact.identity FILTER truncate(30) FILTER html %]
-              </span>
-            [% ELSE %]
-              [% bug.qa_contact.identity FILTER html %]
+          [% 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>
+              (<a href="#" id="bz_qa_contact_edit_action">edit</a>)
+            </span>
+          </div>
+          [% END %]
+          <div id="bz_qa_contact_input">
+            [% INCLUDE global/userselect.html.tmpl
+                id => "qa_contact"
+                name => "qa_contact"
+                value => bug.qa_contact.login
+                size => 30
+                emptyok => 1
+            %]
+            <br>
+            <input type="checkbox" id="set_default_qa_contact" name="set_default_qa_contact" value="1">
+            <label for="set_default_qa_contact" id="set_default_qa_contact_label">Reset QA Contact to default</label>
+          </div>
+          <script type="text/javascript">
+            [% IF bug.qa_contact != "" %]
+              hideEditableField('bz_qa_contact_edit_container', 
+                                 'bz_qa_contact_input', 
+                                 'bz_qa_contact_edit_action', 
+                                 'qa_contact', 
+                                 '[% bug.qa_contact.login FILTER js %]');
             [% END %]
-          </a>
+            initDefaultCheckbox('qa_contact');
+          </script>
+        [% ELSE %]
+          [% INCLUDE user_identity user => bug.qa_contact %]
         [% END %]
       </td>
     </tr>
     [% END %]
+[% END %]
 
-    [% IF user.id %]
-      <tr>
-        <td align="right" valign="top">
-          <label for="newcc" accesskey="a"><b><u>A</u>dd&nbsp;CC</b></label>:
-        </td>
-        <td>
-           [% INCLUDE global/userselect.html.tmpl
-              id => "newcc"
-              name => "newcc"
-              value => ""
-              size => 30
-              multiple => 5
-            %]
-        </td>
-      </tr>
+[%############################################################################%]
+[%# 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>
+    <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)") %]
+           <a href="[% bug.bug_file_loc FILTER html %]" target="_blank"
+              title="[% bug.bug_file_loc FILTER html %]">
+             [% bug.bug_file_loc FILTER truncate(40) FILTER html %]</a>
+        [% ELSE %]
+          [% bug.bug_file_loc FILTER html %]
+        [% END %]
+        (<a href="#" id="bz_url_edit_action">edit</a>)</span>
+      [% END %]
+      <span id="bz_url_input_area">
+        [% url_output =  PROCESS input no_td=1 inputname => "bug_file_loc" size => "40" colspan => 2 %]
+        [% IF NOT bug.check_can_change_field("bug_file_loc", 0, 1)  %]
+          <a href="[% bug.bug_file_loc FILTER html %]">[% url_output FILTER none %]</a>
+        [% ELSE %]
+          [% url_output FILTER none %]
+        [% END %]
+      </span>
+      [% IF bug.check_can_change_field("bug_file_loc", 0, 1) %]
+        <script type="text/javascript">
+          hideEditableField('bz_url_edit_container', 
+                            'bz_url_input_area', 
+                            'bz_url_edit_action', 
+                            'bug_file_loc', 
+                            "[% bug.bug_file_loc FILTER js %]");
+        </script>
+      [% END %]
+    </td>
+  </tr>
+  
+  [% IF Param('usestatuswhiteboard') %]
+    <tr>
+      <td class="field_label">
+        <label for="status_whiteboard" accesskey="w"><b><u>W</u>hiteboard</b></label>:
+      </td>
+      [% PROCESS input inputname => "status_whiteboard" size => "40" colspan => 2 %]
+    </tr>
+  [% END %]
+  
+  [% IF use_keywords %]
+    <tr>
+      <td class="field_label">
+        <label for="keywords" accesskey="k">
+          <b><a href="describekeywords.cgi"><u>K</u>eywords</a></b></label>:
+      </td>
+      [% PROCESS input inputname => "keywords" size => 40 colspan => 2
+                       value => bug.keywords.join(', ') %]
+    </tr>
+  [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Depends On / Blocks                                              #%]
+[%############################################################################%]
+[% BLOCK section_dependson_blocks %]
+  <tr>
+    [% PROCESS dependencies
+               dep = { title => "Depends&nbsp;on", fieldname => "dependson" } %]
+  </tr>
+  
+  <tr>
+    [% PROCESS dependencies accesskey = "b"
+               dep = { title => "<u>B</u>locks", fieldname => "blocked" } %]
+  
+  <tr>
+    <th>&nbsp;</th>
+  
+    <td colspan="2" align="left" id="show_dependency_tree_or_graph">
+      Show dependency <a href="showdependencytree.cgi?id=[% bug.bug_id %]&amp;hide_resolved=1">tree</a>
+  
+      [% IF Param('webdotbase') %]
+        /&nbsp;<a href="showdependencygraph.cgi?id=[% bug.bug_id %]">graph</a>
+      [% END %]
+    </td>
+  </tr>
+[% END %]
+
+
+[%############################################################################%]
+[%# Block for Restricting Visibility                                         #%]
+[%############################################################################%]
+
+[% BLOCK section_restrict_visibility %]
+  [% RETURN UNLESS bug.groups.size %]
+
+  [% inallgroups = 1 %]
+  [% inagroup = 0 %]
+  [% emitted_description = 0 %]
+
+  [% FOREACH group = bug.groups %]
+    [% SET inallgroups = 0 IF NOT group.ingroup %]
+    [% SET inagroup = 1 IF group.ison %]
+
+    [% NEXT IF group.mandatory %]
+
+    [% IF NOT emitted_description %]
+      [% emitted_description = 1 %]
+      <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 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 emitted_description %]
+    [% IF NOT inallgroups %]
+      <b>Only members of a group can change the visibility of [% terms.abug %] for that group.</b>
+      <br>
+    [% END %]
+      </td>
+    </tr>
+    [% "</table>" IF NOT inagroup %]
+  [% END %]
+
+  [% IF inagroup %]
+    [% IF NOT emitted_description %]
+      [% emitted_description = 1 %]
+      <table>
+    [% END %]
     <tr>
-      [% IF bug.cc %]
-        <td align="right" valign="top">
-          <label for="cc"><b>CC</b></label>:
+      <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>
+        </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>
+        </div>
+      </td>
+    </tr>
+  </table>
+  [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Dates                                                          #%]
+[%############################################################################%]
+
+[% BLOCK section_dates %]
+  <tr>
+    <td class="field_label">
+      <b>Reported</b>:
+    </td>
+    <td>
+     [% bug.creation_ts FILTER time %] by [% INCLUDE user_identity user => bug.reporter %]
+    </td>
+  </tr>
+  
+  <tr>
+    <td class="field_label">
+      <b> Modified</b>:
+    </td>
+    <td>
+      [% bug.delta_ts FILTER time FILTER replace(':\d\d$', '') FILTER replace(':\d\d ', ' ')%] 
+      (<a href="show_activity.cgi?id=[% bug.bug_id %]">[%# terms.Bug %]History</a>)
+    </td>
+  
+  </tr>
+[% END %]
+
+[%############################################################################%]
+[%# Block for CC LIST                                                        #%]
+[%############################################################################%]
+[% BLOCK section_cclist %]
+  [% IF user.id %]
+    <tr>
+        <td class="field_label">
+          <label for="newcc" accesskey="a"><b>CC List</b>:</label>
         </td>
-        <td valign="top">
+      <td>
+        [% IF user.id %]
+          [% IF NOT bug.cc || NOT bug.cc.contains(user.login) %]
+            [% has_role = bug.user.isreporter
+                          || bug.assigned_to.id == user.id
+                          || (Param('useqacontact')
+                              && bug.qa_contact
+                              && bug.qa_contact.id == user.id) %]
+            <input type="checkbox" id="addselfcc" name="addselfcc"
+              [% " checked=\"checked\""
+                   IF user.settings.state_addselfcc.value == 'always'
+                      || (!has_role
+                          && user.settings.state_addselfcc.value == 'cc_unless_role') %]>
+            <label for="addselfcc">Add me to CC list</label>
+            <br> 
+          [% END %]
+        [% END %]
+        [% bug.cc.size || 0  FILTER html %] 
+        [% IF bug.cc.size == 1 %]
+          user
+        [% ELSE %]
+          users
+        [% END %]
+        [% IF user.id %]
+          [% IF bug.cc.contains( user.email ) %]
+            including you
+          [% END %]
+        [% END %]
+        <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>
@@ -763,47 +883,216 @@
             [%%]<label for="removecc">Remove selected CCs</label>
             <br>
           [% END %]
-        </td>
-      [% ELSE %]
-        <td colspan="2"><input type="hidden" name="cc" value=""></td>
-      [% END %]
+        [% END %]
+        </div>
+        <script type="text/javascript">
+          hideEditableField( 'cc_edit_area_showhide_container', 
+                             'cc_edit_area', 
+                             'cc_edit_area_showhide', 
+                             '', 
+                             '');  
+        </script>
+      </td>
     </tr>
-  </table>
+  [% END %]
 [% END %]
 
 [%############################################################################%]
+[%# Block for FLAGS                                                          #%]
+[%############################################################################%]
+
+[% BLOCK section_flags %]
+  [%# *** Flags *** %]
+  [% show_bug_flags = 0 %]
+  [% FOREACH type = bug.flag_types %]
+    [% IF (type.flags && type.flags.size > 0) || (user.id && type.is_active) %]
+      [% show_bug_flags = 1 %]
+      [% LAST %]
+    [% END %]
+  [% END %]
+  [% IF show_bug_flags %]
+    <tr>
+      <td class="field_label">
+        <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 %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Custom Fields                                                  #%]
+[%############################################################################%]
+
+[% BLOCK section_customfields %]
+[%# *** Custom Fields *** %]
+
+  [% USE Bugzilla %]
+  [% FOREACH field = Bugzilla.active_custom_fields %]
+    <tr>
+      [% PROCESS bug/field.html.tmpl value=bug.${field.name}
+                                     editable = bug.check_can_change_field(field.name, 0, 1)
+                                     value_span = 2 %]
+    </tr>
+  [% END %]
+[% END %]
+
+[%############################################################################%]
+[%# Block for Section Spacer                                                 #%]
+[%############################################################################%]
+
+[% BLOCK section_spacer %]
+  <tr>
+    <td colspan="2" class="bz_section_spacer"></td>
+  </tr>
+[% END %]
+
+
+
+
+[%############################################################################%]
 [%# Block for dependencies                                                   #%]
 [%############################################################################%]
 
 [% BLOCK dependencies %]
-  <th align="right">
+
+  <th class="field_label">
     <label for="[% dep.fieldname %]"[% " accesskey=\"$accesskey\"" IF accesskey %]>
     [% dep.title %]</label>:
   </th>
-  <td>
-  [% FOREACH depbug = bug.${dep.fieldname} %]
-    [% depbug FILTER bug_link(depbug) FILTER none %][% " " %]
-  [% END %]
-  </td>
-  <td>
+  <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(', ') %]">
+      [% END %]
+    </span>
+    
+    [% FOREACH depbug = bug.${dep.fieldname} %]
+      [% depbug FILTER bug_link(depbug) FILTER none %][% " " %]
+    [% END %]
     [% IF bug.check_can_change_field(dep.fieldname, 0, 1) %]
-      <input name="[% dep.fieldname %]" id="[% dep.fieldname %]"
-             value="[% bug.${dep.fieldname}.join(', ') %]">
-    [% ELSE %]
-      <input type="hidden" id="[% dep.fieldname %]" name="[% dep.fieldname %]"
-             value="[% bug.${dep.fieldname}.join(', ') %]">
+      <span id="[% dep.fieldname %]_edit_container" class="edit_me bz_default_hidden" >
+        (<a href="#" id="[% dep.fieldname %]_edit_action">edit</a>)
+      </span>
+      <script type="text/javascript">
+        hideEditableField('[% dep.fieldname %]_edit_container', 
+                          '[% dep.fieldname %]_input_area', 
+                          '[% dep.fieldname %]_edit_action', 
+                          '[% dep.fieldname %]', 
+                          "[% bug.${dep.fieldname}.join(', ') %]");
+      </script>
     [% END %]
   </td>
+  
   [% accesskey = undef %]
+  
 [% END %]
 
+[%############################################################################%]
+[%# Block for Time Tracking Group                                            #%]
+[%############################################################################%]
+
+[% BLOCK section_timetracking %]
+  <table class="bz_time_tracking_table">
+    <tr>
+      <th>
+        <label for="estimated_time">Orig. Est.</label>
+      </th>
+      <th>
+        Current Est.
+      </th>
+      <th>
+        <label for="work_time">Hours Worked</label>
+      </th>
+      <th>
+        <label for="remaining_time">Hours Left</label>
+      </th>
+      <th>
+        %Complete
+      </th>
+      <th>
+        Gain
+      </th>
+      <th>
+        <label for="deadline">Deadline</label>
+      </th>
+    </tr>
+    <tr>
+      <td>
+        <input name="estimated_time" id="estimated_time"
+               value="[% PROCESS formattimeunit
+                                 time_unit=bug.estimated_time %]"
+               size="6" maxlength="6">
+      </td>
+      <td>
+        [% PROCESS formattimeunit
+                   time_unit=(bug.actual_time + bug.remaining_time) %]
+      </td>
+      <td>
+        [% PROCESS formattimeunit time_unit=bug.actual_time %] +
+        <input name="work_time" id="work_time"
+               value="0" size="3" maxlength="6"
+               onchange="adjustRemainingTime();">
+      </td>
+      <td>
+        <input name="remaining_time" id="remaining_time"
+               value="[% PROCESS formattimeunit
+                                 time_unit=bug.remaining_time %]"
+               size="6" maxlength="6" onchange="updateRemainingTime();">
+      </td>
+      <td>
+        [% PROCESS calculatepercentage act=bug.actual_time
+                                       rem=bug.remaining_time %]
+      </td>
+      <td>
+        [% PROCESS formattimeunit time_unit=bug.estimated_time - (bug.actual_time + bug.remaining_time) %]
+      </td>
+       <td>
+         <input name="deadline" id="deadline" value="[% bug.deadline %]"
+                size="10" maxlength="10"><br />
+         <small>(YYYY-MM-DD)</small>
+      </td>        
+    </tr>
+    <tr>
+      <td colspan="7" class="bz_summarize_time">
+        <a href="summarize_time.cgi?id=[% bug.bug_id %]&amp;do_depends=1">
+        Summarize time (including time for [% terms.bugs %]
+        blocking this [% terms.bug %])</a>
+      </td>
+    </tr>
+  </table> 
+[% END %]
 
 [%############################################################################%]
 [%# Block for 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 %]
       <select id="[% selname %]" name="[% selname %]">
         [% FOREACH x = bug.choices.${selname} %]
@@ -813,10 +1102,12 @@
         [% END %]
       </select>
     [% ELSE %]
-      <input type="hidden" id="[% selname %]" name="[% selname %]" value="[% bug.${selname} FILTER html %]">
       [% bug.${selname} FILTER html %]
     [% END %]
+  [% IF NOT no_td %]
   </td>
+  [% END %]
+  [% no_td = 0 %]
 [% END %]
 
 [%############################################################################%]
@@ -824,15 +1115,16 @@
 [%############################################################################%]
 
 [% BLOCK input %]
+  [% IF no_td != 1 %]
   <td[% " colspan=\"$colspan\"" IF colspan %]>
+  [% END %]
     [% val = value ? value : bug.$inputname %]
     [% IF bug.check_can_change_field(inputname, 0, 1) %]
        <input id="[% inputname %]" name="[% inputname %]"
               value="[% val FILTER html %]"[% " size=\"$size\"" IF size %]
-              [% " maxlength=\"$maxlength\"" IF maxlength %]>
+              [% " maxlength=\"$maxlength\"" IF maxlength %]
+              [% " spellcheck=\"$spellcheck\"" IF spellcheck %]>
     [% ELSE %]
-       <input type="hidden" name="[% inputname %]" id="[% inputname %]"
-              value="[% val FILTER html %]">
       [% IF size && val.length > size %]
         <span title="[% val FILTER html %]">
           [% val FILTER truncate(size) FILTER html %]
@@ -841,9 +1133,33 @@
         [% val FILTER html %]
       [% END %]
     [% END %]
+  [% IF no_td != 1 %]  
   </td>
+  [% END %]
+  [% no_td = 0 %]
   [% maxlength = 0 %]
   [% colspan = 0 %]
   [% size = 0 %]
   [% value = undef %]
+  [% spellcheck = undef %]
 [% END %]
+
+[%############################################################################%]
+[%# Block 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>
+[% END %]
+
diff --git a/BugsSite/template/en/default/bug/field.html.tmpl b/BugsSite/template/en/default/bug/field.html.tmpl
index 07617f1..664cbee 100644
--- a/BugsSite/template/en/default/bug/field.html.tmpl
+++ b/BugsSite/template/en/default/bug/field.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -42,22 +41,69 @@
 [% IF editable %]
   [% SWITCH field.type %]
     [% CASE constants.FIELD_TYPE_FREETEXT %]
-        <input name="[% field.name FILTER html %]" value="[% value FILTER html %]" size="60">
-    [% CASE constants.FIELD_TYPE_SINGLE_SELECT %]
-        <select id="[% field.name FILTER html %]" name="[% field.name FILTER html %]">
+        <input id="[% field.name FILTER html %]" name="[% field.name FILTER html %]"
+               value="[% value FILTER html %]" size="40"
+               maxlength="[% constants.MAX_FREETEXT_LENGTH FILTER none %]">
+    [% CASE constants.FIELD_TYPE_DATETIME %]
+      <input name="[% field.name FILTER html %]" size="20"
+             id="[% field.name FILTER html %]"
+             value="[% value FILTER html %]"
+             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 %]"
+           class="yui-skin-sam"></div>
+
+      <script type="text/javascript">
+        createCalendar('[% field.name 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"
+                [% END %]
+                >
           [% IF allow_dont_change %]
-            <option value="[% dontchange FILTER html %]">
+            <option value="[% dontchange FILTER html %]"
+                   [% ' selected="selected"' IF value == dontchange %]>
               [% dontchange FILTER html %]
             </option>
           [% END %]
           [% FOREACH legal_value = field.legal_values %]
             <option value="[% legal_value FILTER html %]"
-                [%- " selected=\"selected\"" IF value == legal_value %]>
+                [%- " selected=\"selected\"" IF value.contains(legal_value).size %]>
                 [%- legal_value 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 %]
+     [% CASE constants.FIELD_TYPE_TEXTAREA %]
+       [% INCLUDE global/textarea.html.tmpl
+           id = field.name name = field.name minrows = 4 maxrows = 8
+           cols = 60 defaultcontent = value %]
   [% END %]
+[% ELSIF field.type == constants.FIELD_TYPE_TEXTAREA %]
+  <div class="uneditable_textarea">[% value FILTER wrap_comment(60)
+                                            FILTER html %]</div>
 [% ELSE %]
-  [% value FILTER html %]
+  [% value.join(', ') FILTER html %]
 [% END %]
 </td>
diff --git a/BugsSite/template/en/default/bug/knob.html.tmpl b/BugsSite/template/en/default/bug/knob.html.tmpl
index 9eeeb42..4cf6031 100644
--- a/BugsSite/template/en/default/bug/knob.html.tmpl
+++ b/BugsSite/template/en/default/bug/knob.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -18,191 +17,134 @@
   #
   # Contributor(s): Gervase Markham <gerv@gerv.net>
   #                 Vaskin Kissoyan <vkissoyan@yahoo.com>
+  #                 Frédéric Buclin <LpSolit@gmail.com>
+  #                 Guy Pyrzak <guy.pyrzak@gmail.com>
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
-
-[%# *** Knob *** %]
-
-<br>
-<div id="knob">
-  <div id="knob-options">
-
-  [% knum = 1 %]
+<div id="status">
   [% initial_action_shown = 0 %]
+  [% show_resolution = 0 %]
+  [% bug_status_select_displayed = 0 %]
 
-  [% IF bug.isunconfirmed && bug.user.canconfirm %]
+  [% 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 %]
-    <input type="radio" id="knob-confirm" name="knob" value="confirm">
-    <label for="knob-confirm">
-      Confirm [% terms.bug %] (change status to <b>[% status_descs.NEW FILTER html %]</b>)
-    </label>
-    <br>
-    [% knum = knum + 1 %]
+    [% 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 %]
   [% END %]
 
-  [% IF bug.isopened && bug.bug_status != "ASSIGNED" && bug.user.canedit
-        && (!bug.isunconfirmed || bug.user.canconfirm) %]
-    [% PROCESS initial_action %]
-    <input type="radio" id="knob-accept" name="knob" value="accept">
-    <label for="knob-accept">
-      Accept [% terms.bug %] (
-      [% IF bug.isunconfirmed %]confirm [% terms.bug %], and [% END %]change
-      status to <b>[% status_descs.ASSIGNED FILTER html %]</b>)
-    </label>
-    <br>
-    [% knum = knum + 1 %]
-  [% 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 %]
-        <input type="radio" id="knob-clear" name="knob" value="clearresolution">
-        <label for="knob-clear">
-          Clear the resolution (remove the current resolution of
-          <b>[% get_resolution(bug.resolution) FILTER html %]</b>)
-        </label>
-        <br>
-        [% knum = knum + 1 %]
       [% END %]
-
-      [% PROCESS initial_action %]
-      <input type="radio" id="knob-resolve" name="knob" value="resolve">
-      <label for="knob-resolve">
-        Resolve [% terms.bug %], changing 
-        <a href="page.cgi?id=fields.html#resolution">resolution</a> to
-      </label>  
-      [% PROCESS select_resolution %]
-
-      [% PROCESS duplicate %]
-
-      [% IF bug.user.canedit %]
-        <input type="radio" id="knob-reassign" name="knob" value="reassign">
-        <label for="knob-reassign">
-          <a href="page.cgi?id=fields.html#assigned_to">Reassign</a> 
-          [% terms.bug %] to
-        </label>
-        [% safe_assigned_to = FILTER js; bug.assigned_to.login; END %]
-        [% INCLUDE global/userselect.html.tmpl
-             id => "assigned_to"
-             name => "assigned_to"
-             value => bug.assigned_to.login
-             size => 32
-             onchange => "if ((this.value != '$safe_assigned_to') && (this.value != '')) {
-                               document.changeform.knob[$knum].checked=true;
-                          }"
-        %]
-        <br>
-        [% IF bug.isunconfirmed && bug.user.canconfirm %]
-          &nbsp;&nbsp;&nbsp;&nbsp;<input type="checkbox" id="andconfirm" name="andconfirm">
-          <label for="andconfirm">
-            and confirm [% terms.bug %] (change status to <b>[% status_descs.NEW FILTER html %]</b>)
-          </label>
-          <br>
-        [% END %]
-        [% knum = knum + 1 %]
-
-        <input type="radio" id="knob-reassign-cmp" name="knob" value="reassignbycomponent">
-        <label for="knob-reassign-cmp">
-          Reassign [% terms.bug %] to default assignee
-          [% " and QA contact," IF Param('useqacontact') %]
-          and add Default CC of selected component
-        </label>
-        <br>
-        [% IF bug.isunconfirmed && bug.user.canconfirm %]
-          &nbsp;&nbsp;&nbsp;&nbsp;<input type="checkbox" id="compconfirm" name="compconfirm">
-          <label for="compconfirm">
-            and confirm [% terms.bug %] (change status to <b>[% status_descs.NEW FILTER html %]</b>)
-          </label>
-          <br>
-        [% END %]
-        [% knum = knum + 1 %]
-      [% END %]
-    [% ELSE %]
-      [% IF bug.resolution != "MOVED" ||
-           (bug.resolution == "MOVED" && bug.user.canmove) %]
+    [% ELSIF bug.resolution != "MOVED" || bug.user.canmove  %]
         [% PROCESS initial_action %]
-        <input type="radio" id="knob-change-resolution" name="knob" value="change_resolution">
-        <label for="knob-change-resolution">
-          Change <a href="page.cgi?id=fields.html#resolution">resolution</a> to
-        </label>
-        [% PROCESS select_resolution %]
-
-        [% PROCESS duplicate %]
-
-        <input type="radio" id="knob-reopen" name="knob" value="reopen">
-        <label for="knob-reopen">
-          Reopen [% terms.bug %]
-        </label>
-        <br>
-        [% knum = knum + 1 %]
-      [% END %]
-      [% IF bug.bug_status == "RESOLVED" %]
-        [% PROCESS initial_action %]
-        <input type="radio" id="knob-verify" name="knob" value="verify">
-        <label for="knob-verify">
-          Mark [% terms.bug %] as <b>[% status_descs.VERIFIED FILTER html %]</b>
-        </label>
-        <br>
-        [% knum = knum + 1 %]
-      [% END %]
-      [% IF bug.bug_status != "CLOSED" %]
-        [% PROCESS initial_action %]
-        <input type="radio" id="knob-close" name="knob" value="close">
-        <label for="knob-close">
-          Mark [% terms.bug %] as <b>[% status_descs.CLOSED FILTER html %]</b>
-        </label>
-        <br>
-        [% knum = knum + 1 %]
-      [% END %]
+        [% 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 %]
-  </div>
-
-  <div id="knob-buttons">
-  <input type="submit" value="Commit" id="commit">
-    [% IF bug.user.canmove %]
-      &nbsp; <font size="+1"><b> | </b></font> &nbsp;
-      <input type="submit" name="action" id="action"
-             value="[% Param("move-button-text") %]">
+  [% IF bug.user.canedit || bug.user.isreporter %]  
+    [% IF show_resolution %]
+      <noscript><br>resolved&nbsp;as&nbsp;</noscript>
+      <span id="resolution_settings">[% PROCESS select_resolution %]</span>
     [% END %]
-  </div>
+    <noscript><br> duplicate</noscript>
+    
+    <span id="duplicate_settings">of 
+      <span id="dup_id_container" class="bz_default_hidden">
+        [% "${terms.bug} ${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
+        (<a href="#" id="dup_id_edit_action">edit</a>)
+      </span
+      ><input id="dup_id" name="dup_id" size="6"
+              value="[% bug.dup_id FILTER html %]">
+    </span>
+    <div id="dup_id_discoverable" class="bz_default_hidden">
+      <a href="#" id="dup_id_discoverable_action">Mark as Duplicate</a>
+    </div>
+  [% 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');
+  hideEditableField( "dup_id_container", "dup_id", 'dup_id_edit_action',
+                     'dup_id', '[% bug.dup_id FILTER js %]' )
+  showHideStatusItems( "",  ['[% "is_duplicate" IF bug.dup_id %]',
+                             '[% bug.bug_status FILTER js %]']);
+  YAHOO.util.Event.addListener( 'bug_status', "change", showHideStatusItems,
+                                ['[% "is_duplicate" IF bug.dup_id %]',
+                                 '[% bug.bug_status FILTER js %]']);
+  YAHOO.util.Event.addListener( 'resolution', "change", showDuplicateItem);
+  YAHOO.util.Event.addListener( 'dup_id_discoverable_action',
+                                'click',
+                                setResolutionToDuplicate,
+                                '[% Param('duplicate_or_move_bug_status')
+                                                                FILTER js %]');
+  YAHOO.util.Event.addListener( window, 'load',  showHideStatusItems,
+                              ['[% "is_duplicate" IF bug.dup_id %]',
+                               '[% bug.bug_status FILTER js %]'] );
+</script>
 
 [%# Common actions %]
 
 [% BLOCK initial_action %]
-  [%# Only show 'Leave as' action in combination with another knob %]
   [% IF !initial_action_shown %]
-    <input type="radio" id="knob-leave" name="knob" value="none" checked="checked">
-    <label for="knob-leave">
-      Leave as <b>[% status_descs.${bug.bug_status} FILTER html %]&nbsp;
-                  [% get_resolution(bug.resolution) FILTER html %]</b>
-    </label>
-    <br>
+    <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"
-          onchange="document.changeform.knob[[% knum %]].checked=true">
+  <select name="resolution" id="resolution">
     [% FOREACH r = bug.choices.resolution %]
-      <option value="[% r FILTER html %]">[% get_resolution(r) FILTER html %]</option>
+      [% 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>
-  <br>
-  [% knum = knum + 1 %]
-[% END %]
-
-[% BLOCK duplicate %]
-  <input type="radio" id="knob-duplicate" name="knob" value="duplicate">
-  <label for="knob-duplicate">
-    Mark the [% terms.bug %] as duplicate of [% terms.bug %] #
-  </label>
-  <input name="dup_id" size="6"
-         onchange="if (this.value != '') {document.changeform.knob[[% knum %]].checked=true}">
-  <br>
-  [% knum = knum + 1 %]
 [% END %]
diff --git a/BugsSite/template/en/default/bug/navigate.html.tmpl b/BugsSite/template/en/default/bug/navigate.html.tmpl
index b6e1351..7b8f3c8 100644
--- a/BugsSite/template/en/default/bug/navigate.html.tmpl
+++ b/BugsSite/template/en/default/bug/navigate.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -20,7 +19,23 @@
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
+[% IF bottom_navigator == 1 %]
+  <ul class="related_actions">
+    <li><a href="show_bug.cgi?format=multiple&amp;id=
+                [% bug.bug_id FILTER url_quote %]">Format For Printing</a></li>
+    <li>&nbsp;-&nbsp;<a href="show_bug.cgi?ctype=xml&amp;id=
+                        [% bug.bug_id  FILTER url_quote %]">XML</a></li>
+    <li>&nbsp;-&nbsp;<a href="enter_bug.cgi?cloned_bug_id=
+                        [% bug.bug_id  FILTER url_quote %]">Clone This 
+                        [% terms.Bug %]</a></li>
+    [%# Links to more things users can do with this bug. %]
+    [% Hook.process("links") %]
+    <li>&nbsp;-&nbsp;<a href="#">Top of page </a></li>
+    </ul>
+[% END %]        
 
+
+<div class="navigation">
 [% IF bug_list && bug_list.size > 0 %]
   [% this_bug_idx = lsearch(bug_list, bug.bug_id) %]
   <b>[% terms.Bug %] List:</b>
@@ -66,3 +81,4 @@
   &nbsp;&nbsp;
   <i><font color="#777777">No search results available</font></i>
 [% END %]
+</div>
diff --git a/BugsSite/template/en/default/bug/process/bugmail.html.tmpl b/BugsSite/template/en/default/bug/process/bugmail.html.tmpl
index 670c2b0..7129922 100644
--- a/BugsSite/template/en/default/bug/process/bugmail.html.tmpl
+++ b/BugsSite/template/en/default/bug/process/bugmail.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/process/confirm-duplicate.html.tmpl b/BugsSite/template/en/default/bug/process/confirm-duplicate.html.tmpl
index eb3b82b..d89c8da 100644
--- a/BugsSite/template/en/default/bug/process/confirm-duplicate.html.tmpl
+++ b/BugsSite/template/en/default/bug/process/confirm-duplicate.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/process/header.html.tmpl b/BugsSite/template/en/default/bug/process/header.html.tmpl
index f15648c..4aecfc1 100644
--- a/BugsSite/template/en/default/bug/process/header.html.tmpl
+++ b/BugsSite/template/en/default/bug/process/header.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -42,4 +41,9 @@
   [% title = "Change Votes" %]
 [% END %]
 
-[% PROCESS global/header.html.tmpl %]
+[% 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"
+%]
diff --git a/BugsSite/template/en/default/bug/process/midair.html.tmpl b/BugsSite/template/en/default/bug/process/midair.html.tmpl
index b66be2c..d7e980e 100644
--- a/BugsSite/template/en/default/bug/process/midair.html.tmpl
+++ b/BugsSite/template/en/default/bug/process/midair.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -29,7 +28,7 @@
   #   entered a comment along with their change) or a number less than that
   #   (if they didn't), in which case no comments are displayed.
   # comments: array; all the comments on the bug.
-  # bug_id: number; the ID of the bug being changed.
+  # bug: Bugzilla::Bug; the bug being changed.
   #%]
 
 [%# The global Bugzilla->cgi object is used to obtain form variable values. %]
@@ -46,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.id) FILTER none %]
   at the same time you were trying to.
   The changes made were:
 </p>
@@ -59,7 +58,7 @@
 <p>
   Added the comment(s):
   <blockquote>
-    [% PROCESS "bug/comments.html.tmpl" bug = { 'bug_id' => bug_id } %]
+    [% PROCESS "bug/comments.html.tmpl" %]
   </blockquote>
 </p>
 [% END %]
@@ -67,7 +66,9 @@
 [% IF cgi.param("comment") %]
 <p>
   Your comment was:<br>
-  <blockquote><pre>[% cgi.param("comment") FILTER wrap_comment FILTER html %]</pre></blockquote>
+  <blockquote><pre class="bz_comment_text">
+    [% cgi.param("comment") FILTER wrap_comment FILTER html %]
+  </pre></blockquote>
 </p>
 [% END %]
 
@@ -78,7 +79,10 @@
 <ul>
   <li>
     <form method="post" action="process_bug.cgi">
-      [% PROCESS "global/hidden-fields.html.tmpl" exclude="^Bugzilla_(login|password)$" %]
+      <input type="hidden" name="delta_ts" 
+             value="[% bug.delta_ts FILTER html %]">
+      [% PROCESS "global/hidden-fields.html.tmpl" 
+          exclude="^Bugzilla_login|Bugzilla_password|delta_ts$" %]
       <input type="submit" id="process" value="Submit my changes anyway">
         This will cause all of the above changes to be overwritten
         [% ", except for the added comment(s)" IF comments.size > start_at %].
@@ -86,7 +90,7 @@
   </li>
   <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.id) FILTER none %]
   </li>
 </ul>
 
diff --git a/BugsSite/template/en/default/bug/process/results.html.tmpl b/BugsSite/template/en/default/bug/process/results.html.tmpl
index 9265a44..d2adca8 100644
--- a/BugsSite/template/en/default/bug/process/results.html.tmpl
+++ b/BugsSite/template/en/default/bug/process/results.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/process/verify-new-product.html.tmpl b/BugsSite/template/en/default/bug/process/verify-new-product.html.tmpl
index 1b62250..2eeb927 100644
--- a/BugsSite/template/en/default/bug/process/verify-new-product.html.tmpl
+++ b/BugsSite/template/en/default/bug/process/verify-new-product.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -18,28 +17,20 @@
   #
   # Contributor(s): Myk Melez <myk@mozilla.org>
   #                 Frédéric Buclin <LpSolit@gmail.com>
+  #                 Max Kanat-Alexander <mkanat@bugzilla.org>
   #%]
 
 [%# INTERFACE:
-  # verify_fields: boolean; whether or not to verify
-  #   the version, component, and target milestone fields
+  # product: object; the new product.
   # versions: array; versions for the new product.
   # components: array; components for the new product.
   # milestones: array; milestones for the new product.
   # defaults: hash; keys are names of fields, values are defaults for
   #   those fields
-  # verify_bug_group: boolean; whether or not to ask the user
-  #   if they want to add the bug to its new product's group
-  # use_target_milestone: boolean; whether or not to use
-  #   the target milestone field
-  # old_groups: a list of group objects which are not available
-  #   for the new product.
+  #
+  # verify_bug_groups: If groups need to be confirmed in addition to fields.
   #%]
 
-[%# The global Bugzilla->cgi object is used to obtain form variable values. %]
-[% USE Bugzilla %]
-[% cgi = Bugzilla.cgi %]
-
 [% PROCESS global/variables.none.tmpl %]
 
 [% PROCESS global/header.html.tmpl
@@ -47,92 +38,153 @@
 
 <form action="process_bug.cgi" method="post">
 
-[% PROCESS "global/hidden-fields.html.tmpl"
-     exclude=(verify_fields ? "^version|component|target_milestone$" : "") %]
-
-<input type="hidden" name="confirm_product_change" value="1">
-[%# Verify the version, component, and target milestone fields. %]
-[% IF verify_fields %]
-  <h3>Verify Version, Component[% ", Target Milestone" IF use_target_milestone %]</h3>
-
-  <p>
-  [% IF use_target_milestone %]
-    You are moving the [% terms.bug %](s) to the product 
-    <b>[% cgi.param("product") FILTER html %]</b>,
-    and the version, component, and/or target milestone fields are no longer
-    correct.  Please set the correct version, component, and target milestone now:
-  [% ELSE %]
-    You are moving the [% terms.bug %](s) to the product 
-    <b>[% cgi.param("product") FILTER html %]</b>,
-    and the version and component fields are no longer correct.
-    Please set the correct version and component now:
-  [% END %]
-  </p>
-
-  <table>
-    <tr>
-      <td>
-        <b>Version:</b><br>
-        [% PROCESS "global/select-menu.html.tmpl" 
-                   name="version"
-                   options=versions
-                   default=defaults.version
-                   size=10 %]
-      </td>
-      <td>
-        <b>Component:</b><br>
-        [% PROCESS "global/select-menu.html.tmpl"
-                   name="component"
-                   options=components
-                   default=defaults.component
-                   size=10 %]
-      </td>
-      [% IF use_target_milestone %]
-        <td>
-          <b>Target Milestone:</b><br>
-        [% PROCESS "global/select-menu.html.tmpl"
-                   name="target_milestone"
-                   options=milestones
-                   default=defaults.target_milestone
-                   size=10 %]
-        </td>
-      [% END %]
-    </tr>
-  </table>
-
+[% SET exclude_items = ['version', 'component', 'target_milestone'] %]
+[% IF verify_bug_groups %]
+  [% exclude_items.push('bit-\d+') %]
 [% END %]
 
-[% IF verify_bug_group || old_groups.size %]
-  <h3>Verify [% terms.Bug %] Group</h3>
+[% PROCESS "global/hidden-fields.html.tmpl"
+     exclude = '^' _ exclude_items.join('|') _ '$' %]
 
-  [% IF old_groups.size %]
-    <p>The following groups are not legal for the <b>[% cgi.param("product") FILTER html %]</b>
-    product or you are not allowed to restrict [% terms.bugs %] to these groups:</p>
-    <ul>
-      [% FOREACH group = old_groups %]
-        <li>[% group.name FILTER html %]: [% group.description FILTER html %]</li>
+<input type="hidden" name="confirm_product_change" value="1">
+    
+[%# Verify the version, component, and target milestone fields. %]
+<h3>Verify Version, Component
+  [%- ", Target Milestone" 
+      IF Param("usetargetmilestone")
+         && bug.check_can_change_field('target_milestone', 0, 1) %]</h3>
+
+<p>
+[% IF Param("usetargetmilestone") 
+   && bug.check_can_change_field('target_milestone', 0, 1) 
+%]
+  You are moving the [% terms.bug %](s) to the product 
+  <b>[% product.name FILTER html %]</b>,
+  and the version, component, and/or target milestone fields are no longer
+  correct.  Please set the correct version, component, and target milestone now:
+[% ELSE %]
+  You are moving the [% terms.bug %](s) to the product 
+  <b>[% product.name FILTER html %]</b>,
+  and the version and component fields are no longer correct.
+  Please set the correct version and component now:
+[% END %]
+</p>
+
+<table>
+  <tr>
+    <td>
+      <b>Version:</b><br>
+      [% IF versions.size == 1 %]
+        [% SET default_version = versions.0 %]
+      [% ELSE %]
+        [% SET default_version = defaults.version %]
       [% END %]
-    </ul>
-    <p><b>[%+ terms.Bugs %] will no longer be restricted to these groups and may become
-    public if no other group applies.</b></p>
+      [% PROCESS "global/select-menu.html.tmpl" 
+                 name="version"
+                 options=versions
+                 default=default_version
+                 size=10 %]
+    </td>
+    <td>
+      <b>Component:</b><br>
+      [% IF components.size == 1 %]
+        [% SET default_component = components.0 %]
+      [% ELSE %]
+        [% SET default_component = defaults.component %]
+      [% END %]
+      [% PROCESS "global/select-menu.html.tmpl"
+                 name="component"
+                 options=components
+                 default=default_component
+                 size=10 %]
+    </td>
+    [% IF Param("usetargetmilestone") 
+          && bug.check_can_change_field('target_milestone', 0, 1) 
+    %]
+      <td>
+        <b>Target Milestone:</b><br>
+      [% PROCESS "global/select-menu.html.tmpl"
+                 name="target_milestone"
+                 options=milestones
+                 default=defaults.milestone
+                 size=10 %]
+      </td>
+    [% END %]
+  </tr>
+</table>
+
+[% IF verify_bug_groups %]
+  <h3>Verify [% terms.Bug %] Group</h3>
+  
+  [% IF old_groups.size %]
+    <p>These groups are not legal for the '[% product.name FILTER html %]'
+    product or you are not allowed to restrict [% terms.bugs %] to these groups.
+    [%+ terms.Bugs %] will no longer be restricted to these groups and may become
+    public if no other group applies:<br>
+    [% FOREACH group = old_groups %]
+      <input type="checkbox" id="bit-[% group.id FILTER html %]"
+             name="bit-[% group.id FILTER html %]" disabled="disabled" value="1">
+      <label for="bit-[% group.id FILTER html %]">
+        [% group.name FILTER html %]: [% group.description FILTER html %]
+      </label>
+      <br>
+    [% END %]
+    </p>
   [% END %]
 
-  [% IF verify_bug_group %]
-    <p>
-      This new product has default groups associated to it. Do you want to add the
-      [%+ terms.bug %] to them?<br>
-      <input type="radio" id="add_no" name="addtonewgroup" value="no"><label for="add_no">no</label><br>
-      <input type="radio" id="add_yes" name="addtonewgroup" value="yes"><label for="add_yes">yes</label><br>
-      <input type="radio" id="add_yesifinold" name="addtonewgroup" value="yesifinold" checked="checked">
-        <label for="add_yesifinold">yes, but only if the [% terms.bug %] was in any of
-        its old product's default groups</label>
+  [% mandatory_groups = [] %]
+  [% optional_groups = [] %]
+
+  [% FOREACH gid = product.group_controls.keys %]
+    [% group = product.group_controls.$gid %]
+    [% NEXT UNLESS group.group.is_active %]
+
+    [% IF group.membercontrol == constants.CONTROLMAPMANDATORY
+          || (group.othercontrol ==  constants.CONTROLMAPMANDATORY && !user.in_group(group.group.name)) %]
+      [% mandatory_groups.push(group) %]
+    [% ELSIF (group.membercontrol != constants.CONTROLMAPNA && user.in_group(group.group.name))
+              || group.othercontrol != constants.CONTROLMAPNA %]
+      [% optional_groups.push(group) %]
+    [% END %]
+  [% END %]
+
+  [% IF optional_groups.size %]
+    <p>These groups are optional. You can decide to restrict [% terms.bugs %] to
+    one or more of the following groups:<br>
+    [% FOREACH group = optional_groups %]
+      <input type="hidden" name="defined_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))
+                 || (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 %]">
+        [% group.group.name FILTER html %]: [% group.group.description FILTER html %]
+      </label>
+      <br>
+    [% END %]
+    </p>
+  [% END %]
+
+  [% IF mandatory_groups.size %]
+    <p>These groups are mandatory and [% terms.bugs %] will be automatically
+    restricted to these groups:<br>
+    [% FOREACH group = mandatory_groups %]
+      <input type="checkbox" id="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 %]">
+        [% group.group.name FILTER html %]: [% group.group.description FILTER html %]
+      </label>
+      <br>
+    [% END %]
     </p>
   [% END %]
 [% END %]
 
-<p>
-  <input type="submit" id="change_product" value="Commit">
-</p>
+<input type="submit" id="change_product" value="Commit">
 
 </form>
 <hr>
diff --git a/BugsSite/template/en/default/bug/show-multiple.html.tmpl b/BugsSite/template/en/default/bug/show-multiple.html.tmpl
index d9dc627..173d98e 100644
--- a/BugsSite/template/en/default/bug/show-multiple.html.tmpl
+++ b/BugsSite/template/en/default/bug/show-multiple.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -21,6 +20,7 @@
   #                 Toms Baugis <toms@myrealbox.com>
   #                 Olav Vitters <olav@bkor.dhs.org>
   #                 Max Kanat-Alexander <mkanat@bugzilla.org>
+  #                 Elliotte Martin <emartin@everythingsolved.com>
   #%]
 
 [% PROCESS "global/field-descs.none.tmpl" %]
@@ -30,11 +30,22 @@
   h1 = ""
   style_urls = ["skins/standard/show_multiple.css",
                 "skins/standard/buglist.css"]
+  doc_section = "bug_page.html"
 %]
 [% PROCESS bug/time.html.tmpl %]
 [% IF bugs.first %]
+  [% ids = [] %]
   [% FOREACH bug = bugs %]
     [% PROCESS bug_display %]
+    [% ids.push(bug.bug_id) UNLESS bug.error %]
+  [% END %]
+  [% IF ids.size > 1 %]
+    <div class="bz_query_buttons">
+      <form method="post" action="buglist.cgi">
+        <input type="hidden" name="bug_id" value="[% ids.join(",") FILTER html %]">
+        <input type="submit" id="short_format" value="Short Format">
+      </form>
+    </div>
   [% END %]
 [% ELSE %]
   <p>
@@ -53,9 +64,10 @@
 [% BLOCK bug_display %]
   <h1>
     [% terms.Bug %] 
-    <a href="show_bug.cgi?id=[% bug.bug_id %]">[% bug.bug_id %]</a>
+    <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 %]
-      ([% bug.alias FILTER html %])
+      (<a href="show_bug.cgi?id=[% bug.alias FILTER url_quote %]">
+        [% bug.alias FILTER html %]</a>)
     [% END %]
   </h1>
 
@@ -117,7 +129,7 @@
     <tr>
       <th>[% field_descs.bug_status  FILTER html %]:</th>
       <td>
-        [% status_descs.${bug.bug_status} FILTER html %]
+        [% get_status(bug.bug_status) FILTER html %]
         [%+ get_resolution(bug.resolution) FILTER html %]
       </td>
 
@@ -167,7 +179,7 @@
 
     [% USE Bugzilla %]
     [% field_counter = 0 %]
-    [% FOREACH field = Bugzilla.get_fields({ obsolete => 0, custom => 1 }) %]
+    [% FOREACH field = Bugzilla.active_custom_fields %]
         [% field_counter = field_counter + 1 %]
         [%# Odd-numbered fields get an opening <tr> %]
         [% '<tr>' IF field_counter % 2 %]
@@ -268,6 +280,9 @@
       </tr>
     [% END %]
   [% END %]
+  
+  [% Hook.process("last_row", "bug/show-multiple.html.tmpl") %]
+   
   </table>
 
 
@@ -276,7 +291,6 @@
   [% PROCESS bug/comments.html.tmpl
      comments = bug.longdescs %]
 
-  <hr>
 [% END %]
 
 
diff --git a/BugsSite/template/en/default/bug/show.html.tmpl b/BugsSite/template/en/default/bug/show.html.tmpl
index c652773..cf61274 100644
--- a/BugsSite/template/en/default/bug/show.html.tmpl
+++ b/BugsSite/template/en/default/bug/show.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -30,16 +29,25 @@
 [% 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 &ndash; $filtered_desc"
     header = "$terms.Bug&nbsp;$bug.bug_id"
     subheader = filtered_desc
     header_addl_info = "Last modified: $filtered_timestamp"
-    bodyclasses = ['bz_bug',
-                   "bz_status_$bug.bug_status",
-                   "bz_component_$bug.component",
-                   "bz_bug_$bug.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"
   %]
 [% END %]
 
@@ -54,13 +62,11 @@
 
 [% PROCESS bug/navigate.html.tmpl %]
 
-<hr>
-
 [% PROCESS bug/edit.html.tmpl %]
 
 <hr>
 
-[% PROCESS bug/navigate.html.tmpl %]
+[% PROCESS bug/navigate.html.tmpl bottom_navigator => 1%]
 
 <br>
 
diff --git a/BugsSite/template/en/default/bug/show.xml.tmpl b/BugsSite/template/en/default/bug/show.xml.tmpl
index 61b597f..c59b2be 100644
--- a/BugsSite/template/en/default/bug/show.xml.tmpl
+++ b/BugsSite/template/en/default/bug/show.xml.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org #%]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,10 +21,10 @@
   #%]
 [% PROCESS bug/time.html.tmpl %]
 <?xml version="1.0" [% IF Param('utf8') %]encoding="UTF-8" [% END %]standalone="yes" ?>
-<!DOCTYPE bugzilla SYSTEM "[% Param('urlbase') %]bugzilla.dtd">
+<!DOCTYPE bugzilla SYSTEM "[% urlbase FILTER html %]bugzilla.dtd">
 
 <bugzilla version="[% constants.BUGZILLA_VERSION %]"
-          urlbase="[% Param('urlbase') %]"
+          urlbase="[% urlbase FILTER xml %]"
           maintainer="[% Param('maintainer') FILTER xml %]"
 [% IF user.id %]
           exporter="[% user.email FILTER xml %]"
@@ -45,6 +44,11 @@
         [% END %]
       [% END %]
 
+      [%# This is here so automated clients can still use process_bug.cgi %]
+      [% IF displayfields.token && user.id %]
+          <token>[% issue_hash_token([bug.id, bug.delta_ts]) FILTER xml %]</token>
+      [% END %]
+
       [%# Now handle 'special' fields #%]
       [% IF displayfields.group %]
         [% FOREACH g = bug.groups %]
@@ -57,6 +61,7 @@
       [% 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 %]
@@ -69,7 +74,7 @@
         [% FOREACH c = bug.longdescs %]
           [% NEXT IF c.isprivate && !user.in_group(Param("insidergroup")) %]
           <long_desc isprivate="[% c.isprivate FILTER xml %]">
-            <who name="[% c.name FILTER xml %]">[% c.email FILTER xml %]</who>
+            <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) %]
               <work_time>[% PROCESS formattimeunit time_unit = c.work_time FILTER xml %]</work_time>
@@ -93,12 +98,18 @@
             <filename>[% a.filename FILTER xml %]</filename>
             <type>[% a.contenttype FILTER xml %]</type>
             <size>[% a.datasize FILTER xml %]</size>
-        [% IF displayfields.attachmentdata %]
-            <data encoding="base64">[% a.data FILTER base64 %]</data>
-        [% END %]        
+            <attacher>[% a.attacher.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 %]
@@ -109,6 +120,9 @@
           </attachment>
         [% END %]
       [% END %]
+      
+      [% Hook.process("bug_end") %]
+
     </bug>
   [% END %]
 [% END %]
diff --git a/BugsSite/template/en/default/bug/summarize-time.html.tmpl b/BugsSite/template/en/default/bug/summarize-time.html.tmpl
index 47f381b..b8bd073 100644
--- a/BugsSite/template/en/default/bug/summarize-time.html.tmpl
+++ b/BugsSite/template/en/default/bug/summarize-time.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -12,6 +11,7 @@
   # The Original Code is the Bugzilla Bug Tracking System.
   #
   # Contributor(s): Christian Reis <kiko@async.com.br>
+  #                 Frédéric Buclin <LpSolit@gmail.com>
   #%]
 
 [% USE date %]
@@ -21,9 +21,9 @@
 [% title = "Time Summary " %]
 [% IF do_depends %]
     [% title = title _ "for " %]
-    [% header = title _ "$terms.Bug $ids.0" FILTER bug_link(ids.0) FILTER none %]
-    [% title = title _ "$terms.Bug $ids.0: " %]
-    [% header = (header _ " (and $terms.bugs blocking it)") IF do_depends %]
+    [% header = "$terms.Bug $ids.0" FILTER bug_link(ids.0) FILTER none %]
+    [% header = title _ header _ " (and $terms.bugs blocking it)" %]
+    [% title = title _ "$terms.Bug $ids.0" %]
 [% ELSE %]
     [% title = title _ "($ids.size $terms.bugs selected)" %]
     [% header = title %]
@@ -33,50 +33,43 @@
     title = title 
     header = header 
     style_urls = ["skins/standard/summarize-time.css"]
+    doc_section = "timetracking.html"
     %]
 
-[% IF ids.size == 0 %]
+[% INCLUDE query_form %]
 
-    <p>No [% terms.bugs %] specified or visible.</p>
+[% IF do_report %]
 
-[% ELSE %]
+  [% global.grand_total = 0 %]
 
-    [% INCLUDE query_form %]
-
-    [% IF do_report %]
-
-        [% global.grand_total = 0 %]
-
-        [% FOREACH workdata = part_list %]
-            [% part = parts.shift %]
-            <div align="right">
-              <h4 style="padding-right: 2em; margin: 0;">
-            [% IF part.0 or part.1 %]
-               [% part.0 OR "Up" FILTER html %] to [% part.1 OR "now" FILTER html %]
-            [% ELSE %]
-               Full summary (no period specified)
-            [% END %]
-              </h4>
-            </div>
-            [% IF group_by == "number" %]
-                [% INCLUDE number_report %]
-            [% ELSE %]
-                [% INCLUDE owner_report %]
-            [% END %]
+  [% FOREACH workdata = part_list %]
+    [%# parts contains date ranges (from, to). %]
+    [% part = parts.shift %]
+    <div align="right">
+      <h4 style="padding-right: 2em; margin: 0;">
+        [% IF part.0 or part.1 %]
+          [% part.0 OR "Up" FILTER html %] to [% part.1 OR "now" FILTER html %]
+        [% ELSE %]
+          Full summary (no period specified)
         [% END %]
-
-        [% IF monthly %]
-            <h4 style="margin: 0">Total of [% global.grand_total FILTER format("%.2f") %] hours worked</h4>
-            <hr noshade size="1">
-        [% END %]
-
-        [% IF null.keys.size > 0 %] 
-            [% INCLUDE inactive_report %]
-            <h4 style="margin: 0">Total of [% null.keys.size %]
-                inactive [% terms.bugs %]</h4>
-        [% END %]
-
+      </h4>
+    </div>
+    [% IF group_by == "number" %]
+      [% INCLUDE number_report %]
+    [% ELSE %]
+      [% INCLUDE owner_report %]
     [% END %]
+  [% END %]
+
+  [% IF monthly %]
+    <h4 style="margin: 0">Total of [% global.grand_total FILTER format("%.2f") %] hours worked</h4>
+    <hr noshade size="1">
+  [% END %]
+
+  [% IF null.size > 0 %]
+    [% INCLUDE inactive_report %]
+    <h4 style="margin: 0">Total of [% null.size %] inactive [% terms.bugs %]</h4>
+  [% END %]
 
 [% END %]
 
@@ -89,7 +82,7 @@
   #%]
 
 [% BLOCK owner_report %]
-    [% global.total = 0 global.bug_count = {} global.owner_count = {}%]
+    [% global.total = 0 global.bug_count = {} global.owner_count = {} %]
     <table cellpadding="4" cellspacing="0" width="90%" class="realitems owner">
         [% FOREACH owner = workdata.keys.sort %]
             [% INCLUDE do_one_owner owner=owner ownerdata=workdata.$owner
@@ -112,19 +105,13 @@
         [% bug_id = bugdata.bug_id %]
         [% global.bug_count.$bug_id = 1 %]
         [% IF detailed %]
-            [%# XXX oy what a hack %]
-            [% timerow = '<td width="100" align="right" valign="top">' _ bugdata.total_time _ '</td>' %]
-            [% INCLUDE bug_header cid=col id=bug_id bug_status=bugdata.bug_status
-                                  short_desc=bugdata.short_desc extra=timerow %]
-             [% col = col + 1 %]
+            [% INCLUDE bug_header cid=col id=bug_id bugdata=bugdata extra=1 %]
+            [% col = col + 1 %]
         [% END %]
         [% subtotal = subtotal + bugdata.total_time %]
     [% END %]
     <tr>
-      <td colspan="3">&nbsp;</td>
-      <td align="right">
-      <b>Total</b>:
-      </td>
+      <td colspan="4" align="right"><b>Total</b>:</td>
       <td align="right" class="subtotal" width="100">
         <b>[% subtotal FILTER format("%.2f") %]</b></td>
         [% global.total = global.total + subtotal %]
@@ -141,13 +128,12 @@
     [% global.total = 0 global.owner_count = {} global.bug_count = {} %]
 
     <table cellpadding="4" cellspacing="0" width="90%" class="realitems number">
-    [% keys = sort_bug_keys(workdata.keys) %]
-    [% FOREACH bug = keys %]
-        [% INCLUDE do_one_bug bug=bug bugdata=workdata.$bug
+    [% FOREACH bug = workdata.keys.nsort %]
+        [% INCLUDE do_one_bug id=bug bugdata=workdata.$bug
                               detailed=detailed %]
     [% END %]
 
-    [% additional = "$global.bug_count.size $terms.bugs &amp; 
+    [% additional = "$global.bug_count.size $terms.bugs &
                      $global.owner_count.size developers" %]
     [% INCLUDE section_total additional=additional colspan=2 %]
     </table>
@@ -155,13 +141,8 @@
 
 [% BLOCK do_one_bug %]
     [% subtotal = 0.00 cid = 0 %]
-
-    [%# hack apart the ID and summary. Sad. %]
-    [% items = bug.split(";") %]
-    [% id = items.shift %]
-    [% status = items.shift %]
     [% global.bug_count.$id = 1 %]
-    [% INCLUDE bug_header id=id bug_status=status short_desc=items.join(";") %]
+    [% INCLUDE bug_header id=id %]
 
     [% FOREACH owner = bugdata.sort("login_name") %]
         [% work_time = owner.total_time %]
@@ -185,17 +166,21 @@
       </td>
       <td align="right" class="subtotal" width="100">
         <b>[% subtotal FILTER format("%.2f") %]</b>
-      </td></tr>
-      [% global.total = global.total + subtotal %]
+      </td>
+    </tr>
+    [% global.total = global.total + subtotal %]
 [% END %]
 
 [% BLOCK bug_header %]
     <tr class="bug_header[% '2' IF cid % 2 %]">
-        <td width="10" valign="top">
-        [% INCLUDE buglink id=id %]</td>
-        <td width="10"><b>[% status_descs.${bug_status} FILTER html %]</b></td>
-        <td colspan="2">[% short_desc FILTER html %]</td>
-        [% extra FILTER none %]
+        <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 colspan="2">[% bugs.$id.short_desc FILTER html %]</td>
+        [% IF extra %]
+          <td align="right" valign="top">[% bugdata.total_time FILTER html %]</td>
+        [% END %]
     </tr>
 [% END %]
 
@@ -204,9 +189,8 @@
     <h3>Inactive [% terms.bugs %]</h3>
     <table cellpadding="4" cellspacing="0" width="90%" class="zeroitems">
     [% cid = 0 %]
-    [% FOREACH bug_id = null.keys.nsort %]
-        [% INCLUDE bug_header id=bug_id bug_status=null.$bug_id.1 
-                   short_desc=null.$bug_id.0 cid=cid %]
+    [% FOREACH bug_id = null.nsort %]
+        [% INCLUDE bug_header id=bug_id cid=cid %]
         [% cid = cid + 1 %]
     [% END %]
     </table>
@@ -214,20 +198,18 @@
 
 
 [% BLOCK section_total %]
-    [% IF global.total > 0 %]
+  [% IF global.total > 0 %]
     <tr class="section_total">
-        <td align="left" width="10">
-        <b>Totals</b></td>
-    <td colspan="[% colspan FILTER none %]" align="right"><b>[% additional FILTER none %]</b></td>
-    <td align="right">&nbsp;&nbsp; 
-        <b>[% global.total FILTER format("%.2f") %]</b>
-    </td></tr>
-    [% ELSE %]
-        <tr><td>
-        No time allocated during the specified period.
-        </td></tr>
-    [% END %]
-    [% global.grand_total = global.grand_total + global.total %]
+      <td><b>Totals</b></td>
+      <td colspan="[% colspan FILTER html %]" align="right"><b>[% additional FILTER html %]</b></td>
+      <td align="right"><b>[% global.total FILTER format("%.2f") %]</b></td>
+    </tr>
+  [% ELSE %]
+    <tr>
+      <td>No time allocated during the specified period.</td>
+    </tr>
+  [% END %]
+  [% global.grand_total = global.grand_total + global.total %]
 [% END %]
 
 [%#
@@ -271,11 +253,11 @@
 <tr><td align="right">
   <b>Group by</b>:
 </td><td colspan="2">
-  <input type="radio" name="group_by" id="number" value="number" [%
+  <input type="radio" name="group_by" id="number" value="number" [%+
     'checked="checked"' IF group_by == "number"
   %]><label 
   for="number" accesskey="n">[% terms.Bug %] <u>N</u>umber</label>
-  <input type="radio" name="group_by" id="owner" value="owner" [%
+  <input type="radio" name="group_by" id="owner" value="owner" [%+
     'checked="checked"' IF group_by == "owner"
   %]><label 
   for="owner" accesskey="d"><u>D</u>eveloper</label>
@@ -302,20 +284,9 @@
 </tr></table>
 
 </form>
-<script type="application/x-javascript">
+<script type="text/javascript">
 <!--
    document.forms['summary'].start_date.focus()
 //--></script>
 <hr noshade size=1>
 [% END %]
-
-[%#
-  #
-  # Utility
-  #
-  #%]
-
-[% BLOCK buglink %]
-    <a href="show_bug.cgi?id=[% id FILTER url_quote %]"><b>[% terms.Bug %]&nbsp;[% id FILTER html %]</b></a>
-[% END %]
-
diff --git a/BugsSite/template/en/default/bug/time.html.tmpl b/BugsSite/template/en/default/bug/time.html.tmpl
index f6a5b82..e070e7d 100644
--- a/BugsSite/template/en/default/bug/time.html.tmpl
+++ b/BugsSite/template/en/default/bug/time.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/votes/delete-all.html.tmpl b/BugsSite/template/en/default/bug/votes/delete-all.html.tmpl
index ffede1d..41b7512 100644
--- a/BugsSite/template/en/default/bug/votes/delete-all.html.tmpl
+++ b/BugsSite/template/en/default/bug/votes/delete-all.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/votes/list-for-bug.html.tmpl b/BugsSite/template/en/default/bug/votes/list-for-bug.html.tmpl
index d23205f..b93d1f3 100644
--- a/BugsSite/template/en/default/bug/votes/list-for-bug.html.tmpl
+++ b/BugsSite/template/en/default/bug/votes/list-for-bug.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/bug/votes/list-for-user.html.tmpl b/BugsSite/template/en/default/bug/votes/list-for-user.html.tmpl
index 1a8153c..50dff7d 100644
--- a/BugsSite/template/en/default/bug/votes/list-for-user.html.tmpl
+++ b/BugsSite/template/en/default/bug/votes/list-for-user.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -26,6 +25,7 @@
   #           voting: 
   #            name: name of product
   #            bugs: list of bugs the user has voted for
+  #            bug_ids: list of bug ids the user has voted for
   #            onevoteonly: one or more votes allowed per bug?
   #            total: users current vote count for the product
   #            maxvotes: max votes allowed for a user in this product
@@ -34,6 +34,8 @@
   # bug_id: number; if the user is voting for a bug, this is the bug id
   #
   # canedit: boolean; Should the votes be presented in a form, or readonly? 
+  #
+  # all_bug_ids: List of all bug ids the user has voted for, across all products
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
@@ -90,7 +92,10 @@
         [% END %]
         <tr>
           <th>[% product.name FILTER html %]</th>
-          <td colspan="3">
+          <td colspan="2" ><a href="buglist.cgi?bug_id=
+              [%- product.bug_ids.join(",") FILTER url_quote %]">([% terms.bug %] list)</a>
+          </td>
+          <td>
             [% IF product.maxperbug < product.maxvotes AND
                   product.maxperbug > 1 %]
               <font size="-1">
@@ -143,7 +148,9 @@
     </table>
 
     [% IF canedit %]
-      <input type="submit" value="Change My Votes" id="change">
+      <input type="submit" value="Change My Votes" id="change"> or 
+      <a href="buglist.cgi?bug_id=[% all_bug_ids.join(",") FILTER url_quote %]">view all
+        as [% terms.bug %] list</a>
       <br>
       <br>
       To change your votes,
@@ -155,6 +162,9 @@
         change the checkbox
       [% END %]
       and then click <b>Change My Votes</b>.
+    [% ELSE %]
+       <a href="buglist.cgi?bug_id=[% all_bug_ids.join(",") FILTER url_quote %]">View all
+         as [% terms.bug %] list</a>
     [% END %]
   </form>
 [% ELSE %]
diff --git a/BugsSite/template/en/default/config.js.tmpl b/BugsSite/template/en/default/config.js.tmpl
index 9c66cad..6661700 100644
--- a/BugsSite/template/en/default/config.js.tmpl
+++ b/BugsSite/template/en/default/config.js.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -26,7 +25,7 @@
 
 // the global bugzilla url
 var installation = {
-  base_url        : '[% Param('urlbase') FILTER js %]',
+  base_url        : '[% urlbase FILTER js %]',
   install_version : '[% constants.BUGZILLA_VERSION FILTER js %]',
   maintainer      : '[% Param('maintainer') FILTER js %]'
 };
@@ -114,7 +113,14 @@
 var field = [
 [% FOREACH x = field %]
   { name:        '[% x.name FILTER js %]', 
-    description: '[% (field_descs.${x.name} OR x.description) FILTER js %]' },
+    description: '[% (field_descs.${x.name} OR x.description) FILTER js %]',
+    [%-# These values are meaningful for custom fields only. %]
+    [% IF x.custom %]
+    type:        '[% x.type FILTER js %]',
+    type_desc:   '[% field_types.${x.type} FILTER js %]',
+    enter_bug:   '[% x.enter_bug FILTER js %]',
+    [% END %]
+  },
 [% END %]
 ];
 
diff --git a/BugsSite/template/en/default/config.rdf.tmpl b/BugsSite/template/en/default/config.rdf.tmpl
index 36152cf..2850a5a 100644
--- a/BugsSite/template/en/default/config.rdf.tmpl
+++ b/BugsSite/template/en/default/config.rdf.tmpl
@@ -1,4 +1,3 @@
-[% template_version = "1.0@bugzilla.org" %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -17,6 +16,7 @@
   # Rights Reserved.
   #
   # Contributor(s): Myk Melez <myk@mozilla.org>
+  #                 Frédéric Buclin <LpSolit@gmail.com>
   #%]
 
 <?xml version="1.0"[% IF Param('utf8') %] encoding="UTF-8"[% END %]?>
@@ -27,7 +27,7 @@
      xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:bz="http://www.bugzilla.org/rdf#">
 
-<bz:installation rdf:about="[% Param('urlbase') FILTER html %]">
+<bz:installation rdf:about="[% urlbase FILTER xml %]">
   <bz:install_version>[% constants.BUGZILLA_VERSION FILTER html %]</bz:install_version>
   <bz:maintainer>[% Param('maintainer') FILTER html %]</bz:maintainer>
 
@@ -118,13 +118,14 @@
     <Seq>
       [% FOREACH product = products %]
         <li>
-          <bz:product rdf:about="[% Param('urlbase') %]product.cgi?name=[% product.name FILTER url_quote %]">
+          <bz:product rdf:about="[% urlbase FILTER xml %]product.cgi?name=[% product.name FILTER url_quote %]">
             <bz:name>[% product.name FILTER html %]</bz:name>
 
             <bz:components>
               <Seq>
                 [% FOREACH component = product.components %]
-                  <li resource="[% Param('urlbase') %]component.cgi?name=[% component.name FILTER url_quote %]"/>
+                  <li resource="[% urlbase FILTER xml %]component.cgi?name=[% component.name FILTER url_quote 
+                      %]&amp;product=[% product.name FILTER url_quote %]"/>
                 [% END %]
               </Seq>
             </bz:components>
@@ -132,7 +133,7 @@
             <bz:versions>
               <Seq>
                 [% FOREACH version = product.versions %]
-                  <li resource="[% Param('urlbase') %]version.cgi?name=[% version.name FILTER url_quote %]"/>
+                  <li resource="[% urlbase FILTER xml %]version.cgi?name=[% version.name FILTER url_quote %]"/>
                 [% END %]
               </Seq>
             </bz:versions>
@@ -141,7 +142,7 @@
               <bz:target_milestones>
                 <Seq>
                   [% FOREACH milestone = product.milestones %]
-                    <li resource="[% Param('urlbase') %]milestone.cgi?name=[% milestone.name FILTER url_quote %]"/>
+                    <li resource="[% urlbase FILTER xml %]milestone.cgi?name=[% milestone.name FILTER url_quote %]"/>
                   [% END %]
                 </Seq>
               </bz:target_milestones>
@@ -153,13 +154,26 @@
     </Seq>
   </bz:products>
 
+  [% all_visible_flag_types = {} %]
   <bz:components>
     <Seq>
       [% FOREACH product = products %]
         [% FOREACH component = product.components %]
           <li>
-            <bz:component rdf:about="[% Param('urlbase') %]component.cgi?name=[% component.name FILTER url_quote %]">
+            <bz:component rdf:about="[% urlbase FILTER xml %]component.cgi?name=[% component.name FILTER url_quote 
+                          %]&amp;product=[% product.name FILTER url_quote %]">
               <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
+                        %]&amp;name=[% flag_type.name FILTER url_quote %]" />
+                  [% END %]
+                </Seq>
+              </bz:flag_types>
             </bz:component>
           </li>
         [% END %]
@@ -172,7 +186,7 @@
       [% FOREACH product = products %]
         [% FOREACH version = product.versions %]
           <li>
-            <bz:version rdf:about="[% Param('urlbase') %]version.cgi?name=[% version.name FILTER url_quote %]">
+            <bz:version rdf:about="[% urlbase FILTER xml %]version.cgi?name=[% version.name FILTER url_quote %]">
               <bz:name>[% version.name FILTER html %]</bz:name>
             </bz:version>
           </li>
@@ -187,7 +201,7 @@
         [% FOREACH product = products %]
           [% FOREACH milestone = product.milestones %]
             <li>
-              <bz:target_milestone rdf:about="[% Param('urlbase') %]milestone.cgi?name=[% milestone.name FILTER url_quote %]">
+              <bz:target_milestone rdf:about="[% urlbase FILTER xml %]milestone.cgi?name=[% milestone.name FILTER url_quote %]">
                 <bz:name>[% milestone.name FILTER html %]</bz:name>
               </bz:target_milestone>
             </li>
@@ -197,14 +211,39 @@
     </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
+                        %]&amp;name=[% flag_type.name FILTER url_quote %]">
+            <bz:id>[% flag_type.id FILTER html %]</bz:id>
+            <bz:name>[% flag_type.name FILTER html %]</bz:name>
+            <bz:description>[% flag_type.description FILTER html %]</bz:description>
+            <bz:type>[% flag_type.target_type FILTER html %]</bz:type>
+            <bz:requestable>[% flag_type.is_requestable FILTER html %]</bz:requestable>
+            <bz:specifically_requestable>[% flag_type.is_requesteeble FILTER html %]</bz:specifically_requestable>
+            <bz:multiplicable>[% flag_type.is_multiplicable FILTER html %]</bz:multiplicable>
+          </bz:flag_type>
+        </li>
+      [% END %]
+    </Seq>
+  </bz:flag_types>
+
   <bz:fields>
     <Seq>
       [% PROCESS "global/field-descs.none.tmpl" %]
       [% FOREACH item = field %]
         <li>
-          <bz:field rdf:about="[% Param('urlbase') %]field.cgi?name=[% item.name FILTER url_quote %]">
+          <bz:field rdf:about="[% urlbase FILTER xml %]field.cgi?name=[% item.name FILTER url_quote %]">
             <bz:name>[% item.name FILTER html %]</bz:name>
             <bz:description>[% (field_descs.${item.name} OR item.description) FILTER html %]</bz:description>
+            [%-# These values are meaningful for custom fields only. %]
+            [% IF item.custom %]
+              <bz:type>[% item.type FILTER html %]</bz:type>
+              <bz:type_desc>[% field_types.${item.type} FILTER html %]</bz:type_desc>
+              <bz:enter_bug>[% item.enter_bug FILTER html %]</bz:enter_bug>
+            [% END %]
           </bz:field>
         </li>
       [% END %]
diff --git a/BugsSite/template/en/default/email/newchangedmail.txt.tmpl b/BugsSite/template/en/default/email/newchangedmail.txt.tmpl
index ecdf426..7c0e30a 100644
--- a/BugsSite/template/en/default/email/newchangedmail.txt.tmpl
+++ b/BugsSite/template/en/default/email/newchangedmail.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,10 +21,13 @@
 [% PROCESS "global/variables.none.tmpl" %]
 From: [% Param('mailfrom') %]
 To: [% to %]
-Subject: [[% terms.Bug %] [%+ bugid %]] [% neworchanged %][%+ summary %]
+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 %]
@@ -38,12 +40,12 @@
 X-Bugzilla-Changed-Fields: [% changedfields %]
 [%+ threadingmarker %]
 
-[%+ Param('urlbase') %]show_bug.cgi?id=[% bugid %]
+[%+ urlbase %]show_bug.cgi?id=[% bugid %]
 
 [%+ diffs %]
 
 -- 
-Configure [% terms.bug %]mail: [% Param('urlbase') %]userprefs.cgi?tab=email
+Configure [% terms.bug %]mail: [% urlbase %]userprefs.cgi?tab=email
 ------- You are receiving this mail because: -------
 [% FOREACH relationship = reasons %]
   [% SWITCH relationship %]
diff --git a/BugsSite/template/en/default/admin/classifications/delete.html.tmpl b/BugsSite/template/en/default/email/sanitycheck.txt.tmpl
similarity index 64%
rename from BugsSite/template/en/default/admin/classifications/delete.html.tmpl
rename to BugsSite/template/en/default/email/sanitycheck.txt.tmpl
index 046c146..9c19269 100644
--- a/BugsSite/template/en/default/admin/classifications/delete.html.tmpl
+++ b/BugsSite/template/en/default/email/sanitycheck.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -16,16 +15,22 @@
   # Copyright (C) 1998 Netscape Communications Corporation. All
   # Rights Reserved.
   #
-  # Contributor(s): Albert Ting <alt@sonic.net>
+  # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
   #%]
 
-[% PROCESS global/header.html.tmpl
-  title = "Classification deleted"
-%]
+[% PROCESS "global/variables.none.tmpl" %]
+From: [% Param('mailfrom') %]
+To: [% addressee %]
+Subject: [[% terms.Bugzilla %]] Sanity Check Results
+X-Bugzilla-Type: sanitycheck
 
-Classification [% classification.name FILTER html %] deleted.<br>
+[%+ urlbase %]sanitycheck.cgi
 
-<p>Back to the <a href="./">main [% terms.bugs %] page</a>
-or <a href="editclassifications.cgi"> edit</a> more classifications.
+Below can you read the sanity check results.
+[% IF error_found %]
+Some errors have been found.
+[% ELSE %]
+No errors have been found.
+[% END %]
 
-[% PROCESS global/footer.html.tmpl %] 
+[% output FILTER txt %]
diff --git a/BugsSite/template/en/default/email/sudo.txt.tmpl b/BugsSite/template/en/default/email/sudo.txt.tmpl
index b6841e4..74fbc49 100644
--- a/BugsSite/template/en/default/email/sudo.txt.tmpl
+++ b/BugsSite/template/en/default/email/sudo.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -41,4 +40,4 @@
 
     If you feel that this action was inappropriate, please contact 
 [%+ Param("maintainer") %].  For more information on this feature, 
-visit <[% Param("urlbase") %]page.cgi?id=sudo.html>.
+visit <[% urlbase %]page.cgi?id=sudo.html>.
diff --git a/BugsSite/template/en/default/email/votes-removed.txt.tmpl b/BugsSite/template/en/default/email/votes-removed.txt.tmpl
index 6bf2afd..bfb37c9 100644
--- a/BugsSite/template/en/default/email/votes-removed.txt.tmpl
+++ b/BugsSite/template/en/default/email/votes-removed.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -28,12 +27,29 @@
 
 Some or all of your votes have been removed from [% terms.bug %] [%+ bugid %].
 
-[% votesoldtext %]
+You had [% votesold FILTER html %] [%+ IF votesold == 1 %]vote[% ELSE %]votes[% END
+%] on this [% terms.bug %], but [% votesremoved FILTER html %] have been removed.
 
-[% votesnewtext %]
+[% IF votesnew %]
+You still have [% votesnew FILTER html %] [%+ IF votesnew == 1 %]vote[% ELSE %]votes[% END %] on this [% terms.bug %].
+[% ELSE %]
+You have no more votes remaining on this [% terms.bug %].
+[% END %]
 
-Reason: [% reason %]
+Reason:
+[% IF reason == "votes_bug_moved" %]
+  This [% terms.bug %] has been moved to a different product.
+
+[% ELSIF reason == "votes_too_many_per_bug" %]
+  The rules for voting on this product has changed;
+  you had too many votes for a single [% terms.bug %].
+
+[% ELSIF reason == "votes_too_many_per_user" %]
+  The rules for voting on this product has changed; you had
+  too many total votes, so all votes have been removed.
+[% END %]
 
 
-[% Param("urlbase") %]show_bug.cgi?id=[% bugid %]
+
+[% urlbase %]show_bug.cgi?id=[% bugid %]
 
diff --git a/BugsSite/template/en/default/email/whine.txt.tmpl b/BugsSite/template/en/default/email/whine.txt.tmpl
index e50964e..caf43eb 100644
--- a/BugsSite/template/en/default/email/whine.txt.tmpl
+++ b/BugsSite/template/en/default/email/whine.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,17 +21,17 @@
 [% PROCESS "global/field-descs.none.tmpl" %]
 From: [% Param("mailfrom") %]
 To: [% email %][% Param("emailsuffix") %]
-Subject: Your [% terms.Bugzilla %] buglist needs attention.
+Subject: Your [% terms.Bugzilla %] [%+ terms.bug %] list needs attention.
 X-Bugzilla-Type: whine
 
 [This e-mail has been automatically generated.]
 
 You have one or more [% terms.bugs %] assigned to you in the [% terms.Bugzilla %] 
-[% terms.bug %] tracking system ([% Param("urlbase") %]) that require
+[% terms.bug %] tracking system ([% urlbase %]) that require
 attention.
 
-All of these [% terms.bugs %] are in the [% status_descs.NEW %] or 
-[% status_descs.REOPENED %] state, and have not been
+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.
 You need to take a look at them, and decide on an initial action.
 
@@ -47,20 +46,20 @@
 (3) You decide the [% terms.bug %] belongs to you, but you can't solve it this moment.
     Just use the "Accept [% terms.bug %]" command.
 
-To get a list of all [% status_descs.NEW %]/[% status_descs.REOPENED %] [%+ terms.bugs %], you can use this URL (bookmark
+To get a list of all [% get_status("NEW") %]/[% get_status("REOPENED") %] [%+ terms.bugs %], you can use this URL (bookmark
 it if you like!):
 
- [% Param("urlbase") %]buglist.cgi?bug_status=NEW&bug_status=REOPENED&assigned_to=[% email %]
+ [% urlbase %]buglist.cgi?bug_status=NEW&bug_status=REOPENED&assigned_to=[% email %]
 
 Or, you can use the general query page, at 
-[%+ Param("urlbase") %]query.cgi
+[%+ urlbase %]query.cgi
 
-Appended below are the individual URLs to get to all of your [% status_descs.NEW %] [%+ terms.bugs %]
-that haven't been touched for a week or more.
+Appended below are the individual URLs to get to all of your [% get_status("NEW") %] [%+ terms.bugs %]
+that haven't been touched for [% Param("whinedays") %] days or more.
 
 You will get this message once a day until you've dealt with these [% terms.bugs %]!
 
 [% FOREACH bug = bugs %]
  [%+ bug.summary %]
-    -> [% Param("urlbase") %]show_bug.cgi?id=[% bug.id %]
+    -> [% urlbase %]show_bug.cgi?id=[% bug.id %]
 [% END %]
diff --git a/BugsSite/template/en/default/filterexceptions.pl b/BugsSite/template/en/default/filterexceptions.pl
index 1ea527c..d5b4125 100644
--- a/BugsSite/template/en/default/filterexceptions.pl
+++ b/BugsSite/template/en/default/filterexceptions.pl
@@ -62,8 +62,7 @@
   '"value${chartnum}-${rownum}-${colnum}"', 
   '"type${chartnum}-${rownum}-${colnum}"', 
   'field.name', 
-  'field.description', 
-  'type.name', 
+  'type.name',
   'type.description', 
   '"${chartnum}-${rownum}-${newor}"', 
   '"${chartnum}-${newand}-0"', 
@@ -191,8 +190,7 @@
 
 'list/edit-multiple.html.tmpl' => [
   'group.id', 
-  'knum', 
-  'menuname', 
+  'menuname',
 ],
 
 'list/list.rdf.tmpl' => [
@@ -216,11 +214,6 @@
   'bug.bug_id', 
 ],
 
-'global/help.html.tmpl' => [
-  'h.id', 
-  'h.html', 
-],
-
 'global/choose-product.html.tmpl' => [
   'target',
 ],
@@ -281,8 +274,7 @@
 ],
 
 'bug/comments.html.tmpl' => [
-  'comment.isprivate', 
-  'comment.time', 
+  'comment.id',
   'bug.bug_id',
 ],
 
@@ -321,10 +313,7 @@
   '" size=\"$size\"" IF size',
   '" maxlength=\"$maxlength\"" IF maxlength',
   'flag.status',
-],
-
-'bug/knob.html.tmpl' => [
-  'knum', 
+  '" spellcheck=\"$spellcheck\"" IF spellcheck',
 ],
 
 'bug/navigate.html.tmpl' => [
@@ -335,7 +324,6 @@
 ],
 
 'bug/show-multiple.html.tmpl' => [
-  'bug.bug_id', 
   'attachment.id', 
   'flag.status',
 ],
@@ -392,16 +380,11 @@
 ],
 
 'bug/create/create-guided.html.tmpl' => [
-  'matches.0', 
   'tablecolour',
   'sel',
   'productstring', 
 ],
 
-'bug/activity/show.html.tmpl' => [
-  'bug_id', 
-],
-
 'bug/activity/table.html.tmpl' => [
   'change.attachid', 
   'change.field', 
@@ -413,10 +396,8 @@
 ],
 
 'attachment/created.html.tmpl' => [
-  'attachid', 
-  'bugid', 
-  'contenttype', 
-  '"$terms.bug $bugid" FILTER bug_link(bugid)',
+  'attachment.id',
+  'attachment.bug_id',
 ],
 
 'attachment/edit.html.tmpl' => [
@@ -432,14 +413,17 @@
   'obsolete_attachments',
 ],
 
+'attachment/midair.html.tmpl' => [
+  'attachment.id',
+],
+
 'attachment/show-multiple.html.tmpl' => [
   'a.id',
   'flag.status'
 ],
 
 'attachment/updated.html.tmpl' => [
-  'attachid', 
-  '"$terms.bug $bugid" FILTER bug_link(bugid)',
+  'attachment.id',
 ],
 
 'attachment/diff-header.html.tmpl' => [
@@ -459,13 +443,25 @@
   'file.plus_lines',
   'bonsai_prefix',
   'section.old_start',
-  'section_num'
+  'section_num',
+  'current_line_old',
+  'current_line_new',
+  'curr_old',
+  'curr_new'
+],
+
+'admin/admin.html.tmpl' => [
+  'class'
 ],
 
 'admin/table.html.tmpl' => [
   'link_uri'
 ],
 
+'admin/params/common.html.tmpl' => [
+  'sortlist_separator', 
+],
+
 'admin/products/groupcontrol/confirm-edit.html.tmpl' => [
   'group.count', 
 ],
@@ -521,14 +517,15 @@
 ],
 
 'admin/users/confirm-delete.html.tmpl' => [
-  'andstring',
-  'responsibilityterms.$responsibility',
+  'attachments',
   'reporter',
   'assignee_or_qa',
   'cc',
+  'component_cc',
   'flags.requestee',
   'flags.setter',
   'longdescs',
+  'quips',
   'votes',
   'series',
   'watch.watched',
@@ -541,14 +538,22 @@
 'admin/users/edit.html.tmpl' => [
   'otheruser.id',
   'group.id',
-  'perms.directbless',
-  'perms.directmember',
 ],
 
 'admin/components/edit.html.tmpl' => [
   'comp.bug_count'
 ],
 
+'admin/workflow/edit.html.tmpl' => [
+  'status.id',
+  'new_status.id',
+],
+
+'admin/workflow/comment.html.tmpl' => [
+  'status.id',
+  'new_status.id',
+],
+
 'account/login.html.tmpl' => [
   'target', 
 ],
diff --git a/BugsSite/template/en/default/flag/list.html.tmpl b/BugsSite/template/en/default/flag/list.html.tmpl
index 61ecf2d..5d7f78d 100644
--- a/BugsSite/template/en/default/flag/list.html.tmpl
+++ b/BugsSite/template/en/default/flag/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -45,10 +44,19 @@
   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<inputElements.length ; i++ )
+    for ( var i=0 ; i<allElements.length ; i++ )
     {
-      inputElement = inputElements.item(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
@@ -128,15 +136,32 @@
         </td>
         [% IF any_flags_requesteeble %]
           <td>
-            [% IF type.is_active && type.is_requesteeble %]
+            [% IF (type.is_active && type.is_requestable && type.is_requesteeble) || flag.requestee %]
               <span style="white-space: nowrap;">
-                (<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 %]
-                 >)
+                [% IF Param('usemenuforusers') %]
+                  [% flag_custom_list = flag.type.grant_list %]
+                  [% IF !(type.is_active && type.is_requestable && type.is_requesteeble) %]
+                    [%# We are here only because there was already a requestee. In this case,
+                        the only valid action is to remove the requestee or leave it alone;
+                        nothing else. %]
+                    [% flag_custom_list = [flag.requestee] %]
+                  [% END %]
+                  [% 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 %]
               </span>
             [% END %]
           </td>
@@ -156,7 +181,7 @@
         <td>
           <select id="flag_type-[% type.id %]" name="flag_type-[% type.id %]" 
                   title="[% type.description FILTER html %]"
-                  [% " disabled=\"disabled\"" UNLESS user.can_request_flag(type) %]
+                  [% " 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>
@@ -171,11 +196,22 @@
         </td>
         [% IF any_flags_requesteeble %]
           <td>
-            [% IF type.is_requesteeble %]
+            [% IF type.is_requestable && type.is_requesteeble %]
               <span style="white-space: nowrap;">
-                (<input type="text" size="30" maxlength="255"
-                        id="requestee_type-[% type.id %]" 
-                        name="requestee_type-[% type.id %]">)
+                [% 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>
@@ -200,7 +236,7 @@
       <td>
         <select id="flag_type-[% type.id %]" name="flag_type-[% type.id %]" 
                 title="[% type.description FILTER html %]"
-                [% " disabled=\"disabled\"" UNLESS user.can_request_flag(type) %]
+                [% " 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>
@@ -215,12 +251,23 @@
       </td>
       [% IF any_flags_requesteeble %]
         <td>
-          [% IF type.is_requesteeble %]
-              <span style="white-space: nowrap;">
+          [% 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 %]">)
-              </span>
+              [% END %]
+            </span>
           [% END %]
         </td>
       [% END %]
diff --git a/BugsSite/template/en/default/global/banner.html.tmpl b/BugsSite/template/en/default/global/banner.html.tmpl
index 620233a..ab1c2a8 100644
--- a/BugsSite/template/en/default/global/banner.html.tmpl
+++ b/BugsSite/template/en/default/global/banner.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/choose-classification.html.tmpl b/BugsSite/template/en/default/global/choose-classification.html.tmpl
index 7f5a5bc..6a37719 100644
--- a/BugsSite/template/en/default/global/choose-classification.html.tmpl
+++ b/BugsSite/template/en/default/global/choose-classification.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/choose-product.html.tmpl b/BugsSite/template/en/default/global/choose-product.html.tmpl
index a24ca61..0a38db6 100644
--- a/BugsSite/template/en/default/global/choose-product.html.tmpl
+++ b/BugsSite/template/en/default/global/choose-product.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/code-error.html.tmpl b/BugsSite/template/en/default/global/code-error.html.tmpl
index 303c732..80645a8 100644
--- a/BugsSite/template/en/default/global/code-error.html.tmpl
+++ b/BugsSite/template/en/default/global/code-error.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -150,6 +149,10 @@
     Cannot seem to handle <code>[% field FILTER html %]</code>
     and <code>[% type FILTER html %]</code> together.
 
+  [% 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.
@@ -210,9 +213,6 @@
     There is no such group: [% group FILTER html %]. Check your $webservergroup
     setting in [% constants.bz_locations.localconfig FILTER html %].
 
-  [% ELSIF error == "list_comparison_error" %]
-    Unexpected error in list comparing code.
-
   [% ELSIF error == "mismatched_bug_ids_on_obsolete" %]
     Attachment [% attach_id FILTER html %] ([% description FILTER html %]) 
     is attached to [% terms.bug %] [%+ attach_bug_id FILTER html %], 
@@ -279,7 +279,7 @@
     The specified LDAP attribute [% attr FILTER html %] was not found.
 
   [% ELSIF error == "ldap_connect_failed" %]
-    Could not connect to the LDAP server <code>[% server FILTER html %]</code>.
+    Could not connect to the LDAP server(s) <code>[% server FILTER html %]</code>.
 
   [% ELSIF error == "ldap_start_tls_failed" %]
     Could not start TLS with LDAP server: <code>[% error FILTER html %]</code>.
@@ -309,6 +309,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 == "param_must_be_numeric" %]
     [% title = "Invalid Parameter" %]
     Invalid parameter passed to [% function FILTER html %].
@@ -336,6 +340,9 @@
     outside the package. This function may only be called from
     a subclass of <code>[% superclass FILTER html %]</code>.
 
+  [% ELSIF error == "radius_preparation_error" %]
+    An error occurred while preparing for a RADIUS authentication request:
+    <code>[% errstr FILTER html %]</code>.
 
   [% ELSIF error == "unknown_comparison_type" %]
     Specified comparison type is not supported.
@@ -387,11 +394,7 @@
 
   [% ELSIF error == "undefined_field" %]
     Form field [% field FILTER html %] was not defined.
-    [%# Useful message if browser did not select show_bug radio button %]
-    [% IF field == "knob" %]
-      Check that the "Leave as..." radio button was selected.
-    [% END %]
-
+    
   [% ELSIF error == "unknown_action" %]
     [% IF action %]
        Unknown action [% action FILTER html %]!
@@ -406,20 +409,9 @@
     [% title = "Attachment Must Be Patch" %]
     Attachment #[% attach_id FILTER html %] must be a patch.
 
-  [% ELSIF error == "nested_transaction" %]
-    Attempted to start a new transaction without finishing previous one first.
-
   [% ELSIF error == "not_in_transaction" %]
     Attempted to end transaction without starting one first.
 
-  [% ELSIF error == "already_locked" %]
-    Attempted to lock a table without releasing previous lock first:
-    <p>Tables already locked:<br>[% current FILTER html %]
-    <p>Tables requesting locking:<br>[% new FILTER html %]
-
-  [% ELSIF error == "no_matching_lock" %]
-    Attempted to unlock tables without locking them first.
-
   [% ELSIF error == "comma_operator_deprecated" %]
     [% title = "SQL query generator internal error" %]
     There is an internal error in the SQL query generation code,
diff --git a/BugsSite/template/en/default/global/common-links.html.tmpl b/BugsSite/template/en/default/global/common-links.html.tmpl
index dca5d72..8b4c284 100644
--- a/BugsSite/template/en/default/global/common-links.html.tmpl
+++ b/BugsSite/template/en/default/global/common-links.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -20,7 +19,7 @@
   #                 Svetlana Harisova <light@rathedg.com>
   #%]
 
-[% DEFAULT btn_id = "find" %]
+[% DEFAULT qs_suffix = "" %]
 
 <ul class="links">
   <li><a href="./">Home</a></li>
@@ -33,19 +32,21 @@
         onsubmit="if (this.quicksearch.value == '')
                   { alert('Please enter one or more search terms first.');
                     return false; } return true;">
-    <input class="txt" type="text" name="quicksearch">
-    <input class="btn" type="submit" value="Find" id="[% btn_id FILTER html %]">
+    <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>
 
   <li><span class="separator">| </span><a href="report.cgi">Reports</a></li>
 
   <li>
-    <span class="separator">| </span>
-    [% IF user.id %]
-      <a href="request.cgi?requester=[% user.login FILTER url_quote %]&amp;requestee=
-               [% user.login FILTER url_quote %]&amp;do_union=1&amp;group=type">My Requests</a>
-    [% ELSE %]
-      <a href="request.cgi">Requests</a>
+    [% IF Param('shutdownhtml') || Bugzilla.has_flags %]
+      <span class="separator">| </span>
+      [% IF user.id %]
+        <a href="request.cgi?requester=[% user.login FILTER url_quote %]&amp;requestee=
+                 [% user.login FILTER url_quote %]&amp;do_union=1&amp;group=type&amp;action=queue">My Requests</a>
+      [% ELSE %]
+        <a href="request.cgi">Requests</a>
+      [% END %]
     [% END %]
   [%-# Work around FF bug: keep this on one line %]</li>
 
@@ -55,6 +56,16 @@
 
   [% 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
+          || user.get_products_by_permission("editcomponents").size %]
+      <li><span class="separator">| </span><a href="admin.cgi">Administration</a></li>
+    [% END %]
+
+    [% PROCESS link_to_documentation %]
+
     <li>
       <span class="separator">| </span>
       [% IF user.authorizer.can_logout %]
@@ -75,6 +86,9 @@
           && user.authorizer.user_can_create_account %]
       <li><span class="separator">| </span><a href="createaccount.cgi">New&nbsp;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. %]
@@ -103,3 +117,12 @@
     [% END %]
   [% END %]
 </ul>
+[% Hook.process("link-row") %]
+[% BLOCK link_to_documentation %]
+    [% IF doc_section && Param('docs_urlbase') %]
+      <li>
+        <span class="separator">| </span>
+        <a href="[% docs_urlbase _ doc_section FILTER html %]" target="_blank">Help</a>
+      </li>
+    [% END %]
+[% END %]
diff --git a/BugsSite/template/en/default/global/confirm-action.html.tmpl b/BugsSite/template/en/default/global/confirm-action.html.tmpl
new file mode 100644
index 0000000..e57a83c
--- /dev/null
+++ b/BugsSite/template/en/default/global/confirm-action.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 Frédéric Buclin.
+  # Portions created by Frédéric Buclin are Copyright (C) 2008
+  # Frédéric Buclin. All Rights Reserved.
+  #
+  # Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
+  #%]
+
+[%# INTERFACE:
+  # script_name: the script generating this warning.
+  # token: a valid token for the current action.
+  # reason: reason of the failure.
+  #%]
+
+[% PROCESS global/header.html.tmpl title = "Suspicious Action"
+                                   style_urls = ['skins/standard/global.css'] %]
+
+<div class="throw_error">
+  [% IF reason == "expired_token" %]
+    Your changes have been rejected because you exceeded the time limit
+    of [% constants.MAX_TOKEN_AGE FILTER html %] days before submitting your
+    changes to [% script_name FILTER html %]. Your page may have been displayed
+    for too long, or old changes have been resubmitted by accident.
+
+  [% ELSIF reason == "missing_token" %]
+    It looks like you didn't come from the right page.
+    One reason could be that you entered the URL in the address bar of your
+    web browser directly, which should be safe. Another reason could be that
+    you clicked on a URL which redirected you here <b>without your consent</b>.
+
+  [% ELSIF reason == "invalid_token" %]
+    You submitted changes to [% script_name FILTER html %] with an invalid
+    token, which may indicate that someone tried to abuse you, for instance
+    by making you click on a URL which redirected you here <b>without your
+    consent</b>.
+  [% END %]
+  <p>
+    Are you sure you want to commit these changes?
+  </p>
+</div>
+
+<form name="check" id="check" method="post" action="[% script_name FILTER html %]">
+  [% PROCESS "global/hidden-fields.html.tmpl"
+             exclude="^(Bugzilla_login|Bugzilla_password|token)$" %]
+  <input type="hidden" name="token" value="[% token FILTER html %]">
+  <input type="submit" id="confirm" value="Yes, Confirm Changes">
+</form>
+
+<p><a href="index.cgi">No, throw away these changes</a> (you will be redirected
+to the home page).</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/BugsSite/template/en/default/global/confirm-user-match.html.tmpl b/BugsSite/template/en/default/global/confirm-user-match.html.tmpl
index 38ac8dd..5b209df 100644
--- a/BugsSite/template/en/default/global/confirm-user-match.html.tmpl
+++ b/BugsSite/template/en/default/global/confirm-user-match.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -54,11 +53,18 @@
   >
 
   <p>
-    [% terms.Bugzilla %] cannot make a conclusive match for one or more of the
-    names and/or email addresses you entered on the previous page.<br>
-    Please examine the lists of potential matches below and select the
-    one you want, or go back to the previous page to revise the names
-    you entered.
+    [% IF matchmultiple %]
+      [% terms.Bugzilla %] cannot make a conclusive match for one or more
+      of the names and/or email addresses you entered on the previous page.
+      <br>Please examine the lists of potential matches below and select the
+      ones you want,
+    [% ELSE %]
+      [% terms.Bugzilla %] is configured to require verification whenever
+      you enter a name or partial email address.
+      <br>Below are the names/addresses you entered and the matched accounts.
+      Please confirm that they are correct,
+    [% END %]
+    or go back to the previous page to revise the names you entered.
   </p>
 [% ELSE %]
   [% PROCESS global/header.html.tmpl title="Match Failed" %]
diff --git a/BugsSite/template/en/default/global/docslinks.html.tmpl b/BugsSite/template/en/default/global/docslinks.html.tmpl
index 56c0206..712dfb4 100644
--- a/BugsSite/template/en/default/global/docslinks.html.tmpl
+++ b/BugsSite/template/en/default/global/docslinks.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -46,7 +45,7 @@
 [% BLOCK docslinkslist %]
   [% FOREACH docslink = docstype.keys %]
     <li>
-      <a href="[% Param('docs_urlbase') %]
+      <a href="[% docs_urlbase FILTER html %]
                [% docslink FILTER none %]">[% docstype.$docslink FILTER html %]</a>
     </li>
   [% END %]
diff --git a/BugsSite/template/en/default/global/field-descs.none.tmpl b/BugsSite/template/en/default/global/field-descs.none.tmpl
index 94ba948..344dc55 100644
--- a/BugsSite/template/en/default/global/field-descs.none.tmpl
+++ b/BugsSite/template/en/default/global/field-descs.none.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -23,66 +22,89 @@
 
 [% PROCESS global/variables.none.tmpl %]
 
-[% field_descs = { "[Bug creation]"       => "[$terms.Bug creation]",
-                   "actual_time"          => "Actual Hours"
-                   "alias"                => "Alias",
-                   "assigned_to"          => "Assignee",
-                   "blocked"              => "Blocks",
-                   "bug_file_loc"         => "URL",
-                   "bug_id"               => "$terms.Bug ID",
-                   "bug_severity"         => "Severity",
-                   "bug_status"           => "Status",
-                   "changeddate"          => "Changed",
-                   "cc"                   => "CC",
-                   "classification"       => "Classification",
-                   "cclist_accessible"    => "CC list accessible?",
-                   "component_id"         => "Component ID",
-                   "component"            => "Component",
-                   "creation_ts"          => "$terms.Bug Creation time",
-                   "deadline"             => "Deadline",
-                   "delta_ts"             => "Changed",
-                   "dependson"            => "Depends on",
-                   "dup_id"               => "Duplicate",
-                   "estimated_time"       => "Orig. Est.",
-                   "everconfirmed"        => "Ever confirmed?",
-                   "groupset"             => "Groupset",
-                   "keywords"             => "Keywords",
-                   "newcc"                => "CC",
-                   "op_sys"               => "OS",
-                   "opendate"             => "Opened",
-                   "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?",
-                   "resolution"           => "Resolution",
-                   "setting"              => "Setting",
-                   "settings"             => "Settings",
-                   "short_desc"           => "Summary",
-                   "status_whiteboard"    => "Whiteboard",
-                   "target_milestone"     => "Target Milestone",
-                   "version"              => "Version",
-                   "votes"                => "Votes",
-                   "work_time"            => "Hours Worked"} %]
+[% 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. %]
 
-[% USE Bugzilla %]
-[% FOREACH bz_field = Bugzilla.get_fields() %]
-  [% SET field_descs.${bz_field.name} = bz_field.description
-     IF !field_descs.${bz_field.name}.defined %]
+[% 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 %]
 
 [% field_types = { ${constants.FIELD_TYPE_UNKNOWN}       => "Unknown Type",
                    ${constants.FIELD_TYPE_FREETEXT}      => "Free Text",
-                   ${constants.FIELD_TYPE_SINGLE_SELECT} => "Drop Down" } %]
+                   ${constants.FIELD_TYPE_SINGLE_SELECT} => "Drop Down",
+                   ${constants.FIELD_TYPE_MULTI_SELECT}  => "Multiple-Selection Box",
+                   ${constants.FIELD_TYPE_TEXTAREA}      => "Large Text Box",
+                   ${constants.FIELD_TYPE_DATETIME}      => "Date/Time",
+                } %]
 
 [% status_descs = { "UNCONFIRMED" => "UNCONFIRMED",
                     "NEW"         => "NEW",
@@ -92,6 +114,8 @@
                     "VERIFIED"    => "VERIFIED",
                     "CLOSED"      => "CLOSED" } %]
 
+[% MACRO get_status(status) GET status_descs.$status || status %]
+
 [% resolution_descs = { "FIXED"      => "FIXED",
                         "INVALID"    => "INVALID",
                         "WONTFIX"    => "WONTFIX",
diff --git a/BugsSite/template/en/default/global/footer.html.tmpl b/BugsSite/template/en/default/global/footer.html.tmpl
index 7de84aa..0379f02 100644
--- a/BugsSite/template/en/default/global/footer.html.tmpl
+++ b/BugsSite/template/en/default/global/footer.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/header.html.tmpl b/BugsSite/template/en/default/global/header.html.tmpl
index 0a73a1a..a25cf70 100644
--- a/BugsSite/template/en/default/global/header.html.tmpl
+++ b/BugsSite/template/en/default/global/header.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -70,20 +69,6 @@
 
     [% PROCESS 'global/setting-descs.none.tmpl' %]
 
-    [% IF javascript %]
-      <script type="text/javascript">
-        [% javascript %]
-      </script>
-    [% END %]
-    
-    [% IF javascript_urls %]
-      [% FOREACH javascript_url = javascript_urls %]
-        <script src="[% javascript_url FILTER html %]" type="text/javascript"></script>
-      [% END %]
-    [% END %]
-
-    [%+ INCLUDE "global/help-header.html.tmpl" %]
-
     [%# Set up the skin CSS cascade:
       #  1. Standard Bugzilla stylesheet set (persistent)
       #  2. Standard Bugzilla stylesheet set (selectable)
@@ -203,6 +188,26 @@
             type="text/css">
     <![endif]-->
 
+
+    [% IF javascript %]
+      <script type="text/javascript">
+        [% javascript %]
+      </script>
+    [% 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 %]
+    [% END %]
+
     [%# this puts the live bookmark up on firefox for the Atom feed %]
     [% IF atomlink %]
        <link rel="alternate" 
@@ -213,6 +218,8 @@
     [%# Required for the 'Autodiscovery' feature in Firefox 2 and IE 7. %]
     <link rel="search" type="application/opensearchdescription+xml"
                        title="[% terms.Bugzilla %]" href="./search_plugin.cgi">
+    <link rel="shortcut icon" href="images/favicon.ico" >
+    [% Hook.process("additional_header") %]
   </head>
 
 [%# Migration note: contents of the old Param 'bodyhtml' go in the body tag,
@@ -220,7 +227,7 @@
   #%]
 
   <body onload="[% onload %]"
-        class="[% Param('urlbase').replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') %]
+        class="[% urlbase.replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') %]
                [% FOREACH class = bodyclasses %]
                  [% ' ' %][% class FILTER css_class_quote %]
                [% END %]">
@@ -255,7 +262,7 @@
 </tr>
 </table>
 
-[% PROCESS "global/common-links.html.tmpl" btn_id = "find_top" %]
+[% PROCESS "global/common-links.html.tmpl" qs_suffix = "_top" %]
 
 </div>
 
diff --git a/BugsSite/template/en/default/global/help-header.html.tmpl b/BugsSite/template/en/default/global/help-header.html.tmpl
deleted file mode 100644
index 96814e9..0000000
--- a/BugsSite/template/en/default/global/help-header.html.tmpl
+++ /dev/null
@@ -1,91 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Gervase Markham <gerv@gerv.net>
-  #%]
-
-[% USE Bugzilla %]
-[% cgi = Bugzilla.cgi %]
-
-[% IF cgi.param("help") %]
-  [% IF cgi.user_agent("Mozilla/5") %]
-    <style type="text/css">
-    .help {
-        border-style: solid;
-        border-color: #F0A000;
-        background-color: #FFFFFF;
-        padding: 5;
-        position: absolute;
-    }
-    </style>
-
-    <script type="text/javascript">
-    var currentHelp;
-
-    function initHelp() {
-        for (var i = 0; i < document.forms.length; i++) {
-            for (var j = 0; j < document.forms[i].elements.length; j++) {
-                [%# MS decided to add fieldsets to the elements array; and 
-                  # Mozilla decided to copy this brokenness. Grr. 
-                  #%]
-                if (document.forms[i].elements[j].tagName != 'FIELDSET') {
-                    document.forms[i].elements[j].onmouseover = showHelp;
-                }
-            }
-        }
-
-        document.body.onclick = hideHelp;
-    }
-
-    function showHelp() {      
-        hideHelp();
-        var newHelp = document.getElementById(this.name + '_help');
-        if (newHelp) {
-            currentHelp = newHelp;
-
-            var mytop = this.offsetTop;
-            var myleft = this.offsetLeft;
-            var myparent = this.offsetParent;
-            while (myparent.tagName != 'BODY') {
-                mytop = mytop + myparent.offsetTop;
-                myleft = myleft + myparent.offsetLeft;
-                myparent = myparent.offsetParent;
-            }
-
-            currentHelp.style.top = mytop + this.offsetHeight + 5 + "px";
-            currentHelp.style.left = myleft + "px";
-            currentHelp.style.display='';
-        }
-    }
-
-    function hideHelp() {
-        if (currentHelp) {
-            currentHelp.style.display='none';
-        }
-    }
-    </script>
-  [% END %]
-[% ELSE %]
-  <script type="text/javascript">
-  <!--
-  [%# Avoid warnings by having a dummy function %]
-  function initHelp() {}
-  // -->
-  </script>
-[% END %]
-
diff --git a/BugsSite/template/en/default/global/help.html.tmpl b/BugsSite/template/en/default/global/help.html.tmpl
index cc69534..36439bc 100644
--- a/BugsSite/template/en/default/global/help.html.tmpl
+++ b/BugsSite/template/en/default/global/help.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -23,12 +22,11 @@
 [% cgi = Bugzilla.cgi %]
 
 [% IF cgi.param("help") %]
-  [% IF cgi.user_agent("Mozilla/5") %]
-      [% FOREACH h = help_html %]
-      <div id="[% h.id %]_help" class="help" style="display: none;">
-        [%- h.html -%]  
-      </div>
-      [% END %]
-  [% END %]
+  <script type="text/javascript"> <!--
+    [% FOREACH h = help_html %]
+      g_helpTexts["[% h.id FILTER js %]"] = "[%- h.html FILTER js -%]";
+    [% END %]
+    // -->
+  </script>
 [% END %]
 
diff --git a/BugsSite/template/en/default/global/hidden-fields.html.tmpl b/BugsSite/template/en/default/global/hidden-fields.html.tmpl
index 063cdd3..24f15c4 100644
--- a/BugsSite/template/en/default/global/hidden-fields.html.tmpl
+++ b/BugsSite/template/en/default/global/hidden-fields.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/initialize.none.tmpl b/BugsSite/template/en/default/global/initialize.none.tmpl
index 93bfbe3..a6c4897 100644
--- a/BugsSite/template/en/default/global/initialize.none.tmpl
+++ b/BugsSite/template/en/default/global/initialize.none.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/js-products.html.tmpl b/BugsSite/template/en/default/global/js-products.html.tmpl
index 57126f0..8ca206f 100644
--- a/BugsSite/template/en/default/global/js-products.html.tmpl
+++ b/BugsSite/template/en/default/global/js-products.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,12 +21,14 @@
 
 [%# The javascript block gets used in header.html.tmpl. %]
 [% javascript = BLOCK %]
-  var usetms = 0; // do we have target milestone?
-  var first_load = 1; // is this the first time we load the page?
-  var last_sel = []; // caches last selection
+  var useclassification = false; // No classification level in use
+  var first_load = true; // Is this the first time we load the page?
+  var last_sel = []; // Caches last selection
   var cpts = new Array();
+  [% n = 1 %]
   [% FOREACH prod = products %]
-    cpts['[% prod.name FILTER js %]'] = [
+    cpts['[% n %]'] = [
       [%- FOREACH comp = prod.components %]'[% comp.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
+    [% n = n+1 %]
   [% END %]
 [% END %]
diff --git a/BugsSite/template/en/default/global/message.html.tmpl b/BugsSite/template/en/default/global/message.html.tmpl
index eac8e5b..e578a7f 100644
--- a/BugsSite/template/en/default/global/message.html.tmpl
+++ b/BugsSite/template/en/default/global/message.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/message.txt.tmpl b/BugsSite/template/en/default/global/message.txt.tmpl
index e8ec1e51..9329cdb 100644
--- a/BugsSite/template/en/default/global/message.txt.tmpl
+++ b/BugsSite/template/en/default/global/message.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/messages.html.tmpl b/BugsSite/template/en/default/global/messages.html.tmpl
index cea7f6d..4889b9c 100644
--- a/BugsSite/template/en/default/global/messages.html.tmpl
+++ b/BugsSite/template/en/default/global/messages.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -43,7 +42,7 @@
       this [% terms.Bugzilla %] installation.
     [% END %]
 
-  [% ELSIF message_tag == "account_creation_cancelled" %]
+  [% ELSIF message_tag == "account_creation_canceled" %]
     [% title = "User Account Creation Canceled" %]
     The creation of the user account [% account FILTER html %] has been
     canceled.
@@ -118,7 +117,7 @@
   [% ELSIF message_tag == "attachment_creation_failed" %]
     The [% terms.bug %] was created successfully, but attachment creation
     failed.
-    Please add your attachment by clicking the "Create a New Attachment" link
+    Please add your attachment by clicking the "Add an Attachment" link
     below.
 
   [% ELSIF message_tag == "bug_confirmed_by_votes" %]
@@ -142,14 +141,18 @@
     [% link  = "Click here if the page does not redisplay automatically." %]
 
   [% ELSIF message_tag == "buglist_updated_named_query" %]
-    Your search named <code>[% queryname FILTER html %]</code> has been updated.
+    Your search named <code><a 
+     href="buglist.cgi?cmdtype=runnamed&amp;namedcmd=[% queryname FILTER url_quote %]"
+    >[% queryname FILTER html %]</a></code> has been updated.
 
   [% ELSIF message_tag == "buglist_new_default_query" %]
     OK, you now have a new default search.  You may
     also bookmark the result of any individual search.
 
   [% ELSIF message_tag == "buglist_new_named_query" %]
-    OK, you have a new search named <code>[% queryname FILTER html %]</code>.
+    OK, you have a new search named <code><a
+     href="buglist.cgi?cmdtype=runnamed&amp;namedcmd=[% queryname FILTER url_quote %]"
+    >[% queryname FILTER html %]</a></code>.
 
   [% ELSIF message_tag == "buglist_query_gone" %]
     [% title = "Search is gone" %]
@@ -170,39 +173,298 @@
     Click <a href="[% redirect_url FILTER html %]">here</a>
     if the page does not automatically refresh.
 
+  [% ELSIF message_tag == "classification_created" %]
+    [% title = "New Classification Created" %]
+    The <em>[% classification.name FILTER html %]</em> classification has been created.
+
+  [% ELSIF message_tag == "classification_deleted" %]
+    [% title = "Classification Deleted" %]
+    The <em>[% classification 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
+      have been saved:
+      <ul>
+        [% IF updated_classification %]
+          <li>Classification name updated</li>
+        [% END %]
+        [% IF updated_description %]
+          <li>Description updated</li>
+        [% END %]
+        [% IF updated_sortkey %]
+          <li>Sortkey updated</li>
+        [% END %]
+      </ul>
+    [% ELSE %]
+      No changes made to <em>[% classification FILTER html %]</em>.
+    [% END %]
+
+  [% ELSIF message_tag == "component_created" %]
+    [% title = "Component Created" %]
+    The component <em>[% comp.name FILTER html %]</em> has been created.
+
+  [% ELSIF message_tag == "component_deleted" %]
+    [% title = "Component Deleted" %]
+    The component <em>[% comp.name FILTER html %]</em> has been deleted.
+    [% IF comp.bug_count %]
+      All [% terms.bugs %] being in this component and all references
+      to them have also been deleted.
+    [% END %]
+
+  [% ELSIF message_tag == "component_updated" %]
+    [% title = "Component Updated" %]
+    [% IF changes.keys.size %]
+      Changes to the component <em>[% comp.name FILTER html %]</em> have been saved:
+      <ul>
+      [% IF changes.name.defined %]
+        <li>Name updated to '[% comp.name FILTER html %]'</li>
+      [% END %]
+      [% IF changes.description.defined %]
+        <li>Description updated to '[% comp.description FILTER html_light %]'</li>
+      [% END %]
+      [% IF changes.initialowner.defined %]
+        <li>Default assignee updated to '[% comp.default_assignee.login FILTER html %]'</li>
+      [% END %]
+      [% IF changes.initialqacontact.defined %]
+        [% IF comp.default_qa_contact.id %]
+          <li>Default QA contact updated to '[% comp.default_qa_contact.login FILTER html %]'</li>
+        [% ELSE %]
+          <li>Default QA contact deleted</li>
+        [% END %]
+      [% END %]
+      [% IF changes.cc_list.defined %]
+        [% IF comp.initial_cc.size %]
+          [% cc_list = [] %]
+          [% FOREACH cc_user = comp.initial_cc %]
+            [% cc_list.push(cc_user.login) %]
+          [% END %]
+          <li>Default CC list updated to [% cc_list.join(", ") FILTER html %]</li>
+        [% ELSE %]
+          <li>Default CC list deleted</li>
+        [% END %]
+      [% END %]
+    [% ELSE %]
+      No changes made to <em>[% comp.name FILTER html %]</em>.
+    [% END %]
+
   [% ELSIF message_tag == "custom_field_created" %]
     [% title = "Custom Field Created" %]
     The new custom field '[% field.name FILTER html %]' has been
     successfully created.
 
+  [% ELSIF message_tag == "custom_field_deleted" %]
+    [% title = "Custom Field Deleted" %]
+    The custom field '[% field.name FILTER html %]' has been
+    successfully deleted.
+
   [% ELSIF message_tag == "custom_field_updated" %]
     [% title = "Custom Field Updated" %]
     Properties of the '[% field.name FILTER html %]' field have been
     successfully updated.
 
-  [% ELSIF message_tag == "emailold_change_cancelled" %]
+  [% ELSIF message_tag == "default_settings_updated" %]
+    [% IF changes_saved %]
+      Changes to default preferences have been saved.
+    [% ELSE %]
+      No changes made.
+    [% END %]
+
+  [% ELSIF message_tag == "emailold_change_canceled" %]
     [% title = "Cancel Request to Change Email Address" %]
     The request to change the email address for your account to
     [%+ new_email FILTER html %] has been canceled.
 
-  [% ELSIF message_tag == "email_change_cancelled" %]
+  [% ELSIF message_tag == "email_change_canceled" %]
     [% title = "Cancel Request to Change Email Address" %]
     The request to change the email address for the
     account [%+ old_email FILTER html %] to
     [%+ new_email FILTER html %] has been canceled.
 
-  [% ELSIF message_tag == "email_change_cancelled_reinstated" %]
+  [% ELSIF message_tag == "email_change_canceled_reinstated" %]
     [% title = "Cancel Request to Change Email Address" %]
     The request to change the email address for the
     account [%+ old_email FILTER html %] to 
     [%+ new_email FILTER html %] has been canceled.
    Your old account settings have been reinstated.
 
+  [% ELSIF message_tag == "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>
+    (<em>[% field.name FILTER html %]</em>) field.
+    [% IF field.name == "bug_status" %]
+      You should now visit the <a href="editworkflow.cgi">status workflow page</a>
+      to include your new [% terms.bug %] status.
+    [% END %]
+
+  [% ELSIF message_tag == "field_value_deleted" %]
+    [% title = "Field Value Deleted" %]
+    The value <em>[% value 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
+      <em>[% field.description FILTER html %]</em>
+      (<em>[% field.name FILTER html %]</em>) field have 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 %]
+        [% END %]
+        [% IF updated_sortkey %]
+          <li>Field value sortkey updated to <em>[% sortkey FILTER html %]</em></li>
+        [% END %]
+      </ul>
+    [% ELSE %]
+      No changes made to the field value <em>[% value FILTER html %]</em>.
+    [% END %]
+
+  [% ELSIF message_tag == "flag_cleared" %]
+    Some flags didn't apply in the new product/component
+    and have been cleared.
+
   [% ELSIF message_tag == "flag_creation_failed" %]
     [% title = "Flag Creation Failure" %]
     An error occured while validating flags:
     [%+ flag_creation_error FILTER none %]
 
+  [% ELSIF message_tag == "get_field_desc" %]
+    [% field_descs.$field_name FILTER html %]
+
+  [% ELSIF message_tag == "get_resolution" %]
+    [% get_resolution(resolution) FILTER html %]
+
+  [% ELSIF message_tag == "get_status" %]
+    [% get_status(status) FILTER html %]
+
+  [% ELSIF message_tag == "group_created" %]
+    [% title = "New Group Created" %]
+    The group <em>[% group.name FILTER html %]</em> has been created.
+
+  [% ELSIF message_tag == "group_deleted" %]
+    [% title = "Group Deleted" %]
+    The group <em>[% name FILTER html %]</em> has been deleted.
+
+  [% ELSIF message_tag == "group_membership_removed" %]
+    [% title = "Group Membership Removed" %]
+    [% IF users.size %]
+      Explicit membership to the <em>[% group FILTER html %]</em> group removed
+      [% IF regexp %] for users matching '[% regexp FILTER html %]'[% END %]:
+      [% FOREACH user = users %]
+        [%+ user.login FILTER html %]
+      [% END %]
+    [% ELSE %]
+      No users are being affected by your action.
+    [% END %]
+
+  [% ELSIF message_tag == "group_updated" %]
+    [% IF changes.keys.size %]
+      The following changes have been made to the '[% group.name FILTER html %]'
+      group:
+      <ul>
+      [% FOREACH field = changes.keys.sort %]
+        [% SWITCH field %]
+          [% CASE 'name' %]
+            <li>The name was changed to '[% changes.name.1 FILTER html %]'</li>
+          [% CASE 'description' %]
+            <li>The description was updated.</li>
+          [% CASE 'userregexp' %]
+            <li>The regular expression was updated.</li>
+          [% CASE 'isactive' %]
+            [% IF changes.isactive.1 %]
+              <li>The group will now be used for [% terms.bugs %].</li>
+            [% ELSE %]
+              <li>The group will no longer be used for [% terms.bugs %].</li>
+            [% END %]
+          [% CASE 'icon_url' %]
+            <li>The group icon URL has been updated.</li>
+          [% CASE 'members_add' %]
+            <li>The following groups are now members of this group:
+              [%+ changes.members_add.join(', ') FILTER html %]</li>
+          [% CASE 'members_remove' %]
+            <li>The following groups are no longer members of this group:
+              [%+ changes.members_remove.join(', ') FILTER html %]</li>
+          [% CASE 'member_of_add' %]
+            <li>This group is now a member of the following groups:
+              [%+ changes.member_of_add.join(', ') FILTER html %]</li>
+          [% CASE 'member_of_remove' %]
+            <li>This group is no longer a member of the following groups:
+              [%+ changes.member_of_remove.join(', ') FILTER html %]</li>
+          [% CASE 'bless_from_add' %]
+            <li>The following groups may now add users to this group:
+              [%+ changes.bless_from_add.join(', ') FILTER html %]</li>
+          [% CASE 'bless_from_remove' %]
+            <li>The following groups may no longer add users to this group:
+              [%+ changes.bless_from_remove.join(', ') FILTER html %]</li>
+          [% CASE 'bless_to_add' %]
+            <li>This group may now add users to the following groups:
+              [%+ changes.bless_to_add.join(', ') FILTER html %]</li>
+          [% CASE 'bless_to_remove' %]
+            <li>This group may no longer add users to the following groups:
+              [%+ changes.bless_to_remove.join(', ') FILTER html %]</li>
+          [% CASE 'visible_from_add' %]
+            <li>The following groups can now see users in this group:
+              [%+ changes.visible_from_add.join(', ') FILTER html %]</li>
+          [% CASE 'visible_from_remove' %]
+            <li>The following groups may no longer see users in this group:
+              [%+ changes.visible_from_remove.join(', ') FILTER html %]</li>
+          [% CASE 'visible_to_me_add' %]
+            <li>This group may now see users in the following groups:
+              [%+ changes.visible_to_me_add.join(', ') FILTER html %]</li>
+          [% CASE 'visible_to_me_remove' %]
+            <li>This group may no longer see users in the following groups:
+              [%+ changes.visible_to_me_remove.join(', ') FILTER html %]</li>
+        [% END %]
+      [% END %]
+      </ul>
+    [% ELSE %]
+      You didn't request any change for the '[% group.name FILTER html %]'
+      group.
+    [% END %]
+
+  [% ELSIF message_tag == "keyword_created" %]
+    [% title = "New Keyword Created" %]
+    The keyword <em>[% name FILTER html %]</em> has been created.
+
+  [% ELSIF message_tag == "keyword_deleted" %]
+    [% title = "Keyword Deleted" %]
+    The <em>[% keyword.name FILTER html %]</em> keyword has been deleted.
+    <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" %]
+    [% IF changes.keys.size %]
+      Changes to the <em>[% keyword.name FILTER html %]</em> keyword have
+      been saved:
+      <ul>
+        [% IF changes.name.defined %]
+          <li>
+            Keyword renamed to <em>[% keyword.name FILTER html %]</em>.
+            <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>
+        [% END %]
+        [% IF changes.description.defined %]
+          <li>Description updated to <em>[% keyword.description FILTER html %]</em></li>
+        [% END %]
+      </ul>
+    [% ELSE %]
+      No changes made.
+    [% END %]
+
   [% ELSIF message_tag == "logged_out" %]
     [% title = "Logged Out" %]
     [% url = "index.cgi?GoAheadAndLogIn=1" %]
@@ -215,6 +477,35 @@
     [% title = "$terms.Bugzilla Login Changed" %]
     Your [% terms.Bugzilla %] login has been changed.
 
+  [% ELSIF message_tag == "milestone_created" %]
+    [% title = "Milestone Created" %]
+    The milestone <em>[% milestone.name FILTER html %]</em> has been created.
+
+  [% ELSIF message_tag == "milestone_deleted" %]
+    [% title = "Milestone Deleted" %]
+    The milestone <em>[% milestone.name FILTER html %]</em> has been deleted.
+    [% IF milestone.bug_count %]
+      [%+ terms.Bugs %] targetted to this milestone have been retargetted to
+      the default milestone <em>[% product.default_milestone FILTER html %]</em>.
+    [% END %]
+
+  [% ELSIF message_tag == "milestone_updated" %]
+    [% title = "Milestone Updated" %]
+    [% IF changes.size %]
+      Changes to the milestone <em>[% milestone.name FILTER html %]</em>
+      have been saved:
+      <ul>
+        [% IF changes.value.defined %]
+          <li>Milestone name updated to <em>[% milestone.name FILTER html %]</em></li>
+        [% END %]
+        [% IF changes.sortkey.defined %]
+          <li>Sortkey updated to <em>[% milestone.sortkey FILTER html %]</em>
+        [% END %]
+      </ul>
+    [% ELSE %]
+      No changes made to milestone <em>[% milestone.name FILTER html %]</em>.
+    [% END %]
+
   [% ELSIF message_tag == "parameters_updated" %]
     [% title = "Parameters Updated" %]
     [% IF param_changed.size > 0 %]
@@ -234,7 +525,7 @@
       clear the <em>shutdownhtml</em> field.
     [% END%]
 
-  [% ELSIF message_tag == "password_change_cancelled" %]
+  [% ELSIF message_tag == "password_change_canceled" %]
     [% title = "Cancel Request to Change Password" %]
     Your request has been canceled.
 
@@ -249,32 +540,21 @@
 
   [% ELSIF message_tag == "flag_type_created" %]
     [% title = "Flag Type Created" %]
-      The flag type <em>[% name FILTER html %]</em> has been created.
-      <a href="editflagtypes.cgi">Back to flag types.</a>
-    
+    The flag type <em>[% name FILTER html %]</em> has been created.
+
   [% ELSIF message_tag == "flag_type_changes_saved" %]
     [% title = "Flag Type Changes Saved" %]
-    <p>
-      Your changes to the flag type <em>[% name FILTER html %]</em> 
-      have been saved.
-      <a href="editflagtypes.cgi">Back to flag types.</a>
-    </p>
-    
+    Your changes to the flag type <em>[% name FILTER html %]</em>
+    have been saved.
+
   [% ELSIF message_tag == "flag_type_deleted" %]
     [% title = "Flag Type Deleted" %]
-    <p>
-      The flag type <em>[% name FILTER html %]</em> has been deleted.
-      <a href="editflagtypes.cgi">Back to flag types.</a>
-    </p>
-    
+    The flag type <em>[% name FILTER html %]</em> has been deleted.
+
   [% ELSIF message_tag == "flag_type_deactivated" %]
     [% title = "Flag Type Deactivated" %]
-    <p>
-      The flag type <em>[% flag_type.name FILTER html %]</em> 
-      has been deactivated.
-      <a href="editflagtypes.cgi">Back to flag types.</a>
-    </p>
-    
+    The flag type <em>[% flag_type.name FILTER html %]</em> has been deactivated.
+
   [% ELSIF message_tag == "install_admin_get_email" %]
     Enter the e-mail address of the administrator:
 
@@ -284,9 +564,6 @@
   [% ELSIF message_tag == "install_admin_get_password" %]
     Enter a password for the administrator account:
 
-  [% ELSIF message_tag == "install_admin_get_password2" %]
-    Please retype the password to verify:
-
   [% ELSIF message_tag == "install_admin_created" %]
     [% user.login FILTER html %] is now set up as an administrator.
 
@@ -304,6 +581,9 @@
   [% ELSIF message_tag == "install_column_rename" %]
     Renaming column '[% old FILTER html %]' to '[% new FILTER html %]'...
 
+  [% ELSIF message_tag == "install_confirm_password" %]
+    Please retype the password to verify:
+
   [% ELSIF message_tag == "install_default_classification" %]
     Creating default classification '[% name FILTER html %]'...
 
@@ -313,6 +593,12 @@
   [% ELSIF message_tag == "install_file_perms_fix" %]
     Fixing file permissions...
 
+  [% ELSIF message_tag == "install_fk_add" %]
+    Adding foreign key: [% table FILTER html %].[% column FILTER html %] -&gt; [% fk.TABLE FILTER html %].[% fk.COLUMN FILTER html %]...
+
+  [% ELSIF message_tag == "install_fk_drop" %]
+    Dropping foreign key: [% table FILTER html %].[% column FILTER html %] -&gt; [% fk.TABLE FILTER html %].[% fk.COLUMN FILTER html %]...
+
   [% ELSIF message_tag == "install_group_create" %]
     Creating group [% name FILTER html %]...
 
@@ -331,16 +617,22 @@
     account) to ensure it is set up as you wish - this includes
     setting the 'urlbase' option to the correct URL.
 
+  [% ELSIF message_tag == "install_reset_password" %]
+    Enter a new password for [% user.login FILTER html %]:
+
+  [% ELSIF message_tag == "install_reset_password_done" %]
+    New password set.
+
   [% ELSIF message_tag == "install_webservergroup_empty" %]
     ****************************************************************************
     WARNING! You have not entered a value for the "webservergroup" parameter
     in localconfig. This means that certain files and directories which need
-    to be editable by both you and the webserver must be world writable, and
+    to be editable by both you and the web server must be world writable, and
     other files (including the localconfig file which stores your database
     password) must be world readable. This means that _anyone_ who can obtain
     local access to this machine can do whatever they want to your 
     [%+ terms.Bugzilla %] installation, and is probably also able to run 
-    arbitrary Perl code as the user that the webserver runs as.
+    arbitrary Perl code as the user that the web server runs as.
 
     You really, really, really need to change this setting.
     ****************************************************************************
@@ -360,6 +652,21 @@
     Verify that the file permissions in your [% terms.Bugzilla %] directory are
     suitable for your system. Avoid unnecessary write access.
 
+  [% ELSIF message_tag == "product_created" %]
+    [% title = "Product Created" %]
+    The product <em>[% product.name FILTER html %]</em> has been created. You will need to
+    <a href="editcomponents.cgi?action=add&product=[% product.name FILTER url_quote %]">
+    add at least one component</a> before anyone can enter [% terms.bugs %] against this product.
+
+  [% ELSIF message_tag == "product_deleted" %]
+    [% title = "Product Deleted" %]
+    The product <em>[% product.name FILTER html %]</em> and all its versions,
+    components, milestones and group controls have been deleted.
+    [% IF product.bug_count %]
+      All [% terms.bugs %] being in this product and all references
+      to them have also been deleted.
+    [% END %]
+
   [% ELSIF message_tag == "product_invalid" %]
     [% title = "$terms.Bugzilla Component Descriptions" %]
     The product <em>[% product FILTER html %]</em> does not exist
@@ -368,8 +675,19 @@
 
   [% ELSIF message_tag == "remaining_time_zeroed" %]
     The [% field_descs.remaining_time FILTER html %] field has been 
-    set to zero automatically as part of marking this [% terms.bug %]
-    as either RESOLVED or CLOSED.
+    set to zero automatically as part of closing this [% terms.bug %]
+    or moving it from one closed state to another.
+
+  [% ELSIF message_tag == "sanitycheck" %]
+    [%# We use this way to call sanitycheck-specific messages so that
+      # we can still use get_text(). %]
+    [% PROCESS "admin/sanitycheck/messages.html.tmpl" %]
+
+  [% ELSIF message_tag == "series_all_open" %]
+    All Open
+
+  [% ELSIF message_tag == "series_all_closed" %]
+    All Closed
 
   [% ELSIF message_tag == "sudo_started" %]
     [% title = "Sudo session started" %]
@@ -405,6 +723,9 @@
   [% ELSIF message_tag == "term" %]
     [% terms.$term FILTER html %]
 
+  [% ELSIF message_tag == "unexpected_flag_types" %]
+    Some flags could not be set. Please check your changes.
+
   [% ELSIF message_tag == "user_match_failed" %]
     You entered a username that did not match any known 
     [% terms.Bugzilla %] users, so we have instead left
@@ -414,7 +735,24 @@
     You entered a username that matched more than one
     user, so we have instead left the [% match_field FILTER html %]
     field blank.
-    
+
+  [% ELSIF message_tag == "version_created" %]
+    [% title = "Version Created" %]
+    The version <em>[% version.name FILTER html %]</em> of product
+    <em>[% product.name FILTER html %]</em> has been created.
+
+  [% ELSIF message_tag == "version_deleted" %]
+    [% title = "Version Deleted" %]
+    The version <em>[% version.name FILTER html %]</em> of product
+    <em>[% product.name FILTER html %]</em> has been deleted.
+
+  [% ELSIF message_tag == "version_updated" %]
+    [% title = "Version Updated" %]
+    Version renamed as <em>[% version.name FILTER html %]</em>.
+
+  [% ELSIF message_tag == "workflow_updated" %]
+    The workflow has been updated.
+
   [% ELSE %]
     [%# Give sensible error if error functions are used incorrectly.
       #%]        
diff --git a/BugsSite/template/en/default/global/per-bug-queries.html.tmpl b/BugsSite/template/en/default/global/per-bug-queries.html.tmpl
index 1b9b21b..c2fc398 100644
--- a/BugsSite/template/en/default/global/per-bug-queries.html.tmpl
+++ b/BugsSite/template/en/default/global/per-bug-queries.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -72,7 +71,7 @@
         </select>
 
         [% IF Param('docs_urlbase') %]
-          <a href="[% Param('docs_urlbase') %]query.html#individual-buglists">the named tag</a>
+          <a href="[% docs_urlbase FILTER html %]query.html#individual-buglists">the named tag</a>
         [% ELSE %]
           the named tag
         [% END %]
diff --git a/BugsSite/template/en/default/global/select-menu.html.tmpl b/BugsSite/template/en/default/global/select-menu.html.tmpl
index 3233ad7..f8d4d68 100644
--- a/BugsSite/template/en/default/global/select-menu.html.tmpl
+++ b/BugsSite/template/en/default/global/select-menu.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/setting-descs.none.tmpl b/BugsSite/template/en/default/global/setting-descs.none.tmpl
index 921b759..10290ad 100644
--- a/BugsSite/template/en/default/global/setting-descs.none.tmpl
+++ b/BugsSite/template/en/default/global/setting-descs.none.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -41,5 +40,8 @@
    "never"                            => "Never",
    "cc_unless_role"                   => "Only if I have no role on them",
    "lang"                             => "Language used in email",
+   "quote_replies"                    => "Quote the associated comment when you click on its reply link",
+   "quoted_reply"                     => "Quote the full comment",
+   "simple_reply"                     => "Reference the comment number only",
                    } 
 %]
diff --git a/BugsSite/template/en/default/global/site-navigation.html.tmpl b/BugsSite/template/en/default/global/site-navigation.html.tmpl
index 507b779..2acbcf44 100644
--- a/BugsSite/template/en/default/global/site-navigation.html.tmpl
+++ b/BugsSite/template/en/default/global/site-navigation.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -31,29 +30,30 @@
 [% cgi = Bugzilla.cgi %]
 
 [% IF NOT (cgi.user_agent("MSIE [1-6]") OR cgi.user_agent("Mozilla/4")) %]
-  <link rel="Top" href="[% Param('urlbase') %]">
+  <link rel="Top" href="[% urlbase FILTER html %]">
 
   [%# *** Bug List Navigation *** %]
-  [% IF bug && bug_list && bug_list.size > 0 %]
+  [% 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 %]">
 
-    [% current_bug_idx = lsearch(bug_list, bug.bug_id) %]
+    [% IF bug && bug.bug_id %]
+      [% current_bug_idx = lsearch(bug_list, bug.bug_id) %]
+      [% IF current_bug_idx != -1 %]
 
-    [% 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 > 0 %]
-      [% prev_bug = current_bug_idx - 1 %]
-        <link rel="Prev" href="show_bug.cgi?id=[% bug_list.$prev_bug %]">
+        [% 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 %]
-
-      [% 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 %]
 
diff --git a/BugsSite/template/en/default/global/tabs.html.tmpl b/BugsSite/template/en/default/global/tabs.html.tmpl
index 25b931d..4f363c2 100644
--- a/BugsSite/template/en/default/global/tabs.html.tmpl
+++ b/BugsSite/template/en/default/global/tabs.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/textarea.html.tmpl b/BugsSite/template/en/default/global/textarea.html.tmpl
index 8dd0cae..006158b 100644
--- a/BugsSite/template/en/default/global/textarea.html.tmpl
+++ b/BugsSite/template/en/default/global/textarea.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/global/useful-links.html.tmpl b/BugsSite/template/en/default/global/useful-links.html.tmpl
index c0a8ecf..a7c6e1a 100644
--- a/BugsSite/template/en/default/global/useful-links.html.tmpl
+++ b/BugsSite/template/en/default/global/useful-links.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -29,69 +28,10 @@
 <ul id="useful-links">
   <li id="links-actions">
     <div class="label">Actions: </div>
-      [% PROCESS "global/common-links.html.tmpl" btn_id = "find_bottom" %]
+      [% PROCESS "global/common-links.html.tmpl" qs_suffix = "_bottom" %]
   </li>
 
-  [%# We have no other choices than enumerating all required privileges to have
-    # at least one link in this section. %]
-  [% IF user.login
-     && (user.groups.tweakparams || user.groups.editusers || user.can_bless
-         || (Param('useclassification') && user.groups.editclassifications)
-         || user.groups.editcomponents || user.groups.admin || user.groups.creategroups
-         || user.get_products_by_permission("editcomponents").size
-         || user.groups.editkeywords || user.groups.bz_canusewhines) %]
-
-    [% print_pipe = 0 %]
-    <li id="links-edit">
-      <div class="label">Edit: </div>
-      <ul class="links">
-        [% IF user.groups.tweakparams %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editparams.cgi">Parameters</a></li>
-          [% print_pipe = 1 %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editsettings.cgi">Default Preferences</a></li>
-        [% END %]
-        [% IF user.groups.editcomponents %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="sanitycheck.cgi">Sanity Check</a></li>
-          [% print_pipe = 1 %]
-        [% END %]
-        [% IF user.groups.editusers || user.can_bless %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editusers.cgi">Users</a></li>
-          [% print_pipe = 1 %]
-        [% END %]
-        [% IF Param('useclassification') && user.groups.editclassifications %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editclassifications.cgi">Classifications</a></li>
-          [% print_pipe = 1 %]
-        [% END %]
-        [% IF user.groups.editcomponents || user.get_products_by_permission("editcomponents").size %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editproducts.cgi">Products</a></li>
-          [% print_pipe = 1 %]
-        [% END %]
-        [% IF user.groups.editcomponents %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editflagtypes.cgi">Flags</a></li>
-          [% print_pipe = 1 %]
-        [% END %]
-        [% IF user.groups.admin %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editfields.cgi">Custom Fields</a></li>
-          [% print_pipe = 1 %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editvalues.cgi">Field Values</a></li>
-        [% END %]
-        [% IF user.groups.creategroups %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editgroups.cgi">Groups</a></li>
-          [% print_pipe = 1 %]
-        [% END %]
-        [% IF user.groups.editkeywords %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editkeywords.cgi">Keywords</a></li>
-          [% print_pipe = 1 %]
-        [% END %]
-        [% IF user.groups.bz_canusewhines %]
-          <li>[% '<span class="separator">| </span>' IF print_pipe %]<a href="editwhines.cgi">Whining</a></li>
-          [% print_pipe = 1 %]
-        [% END %]
-      </ul>
-    </li>
-  [% END %]
-    
-    [%# Saved searches %]
+  [%# Saved searches %]
     
   [% IF user.showmybugslink OR user.queries.size 
         OR user.queries_subscribed.size 
diff --git a/BugsSite/template/en/default/global/user-error.html.tmpl b/BugsSite/template/en/default/global/user-error.html.tmpl
index bbd54ba..bbae9b3 100644
--- a/BugsSite/template/en/default/global/user-error.html.tmpl
+++ b/BugsSite/template/en/default/global/user-error.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -48,9 +47,18 @@
 [% error_message = BLOCK %]
   [% IF    error == "account_creation_disabled" %]
     [% title = "Account Creation Disabled" %]
-    User account creation has been disabled or restricted.
+    User account creation has been disabled.
     <hr>
-    New accounts must be created by an administrator.
+    New accounts must be created by an administrator. The
+    maintainer is [% Param("maintainer") %].
+
+  [% ELSIF error == "account_creation_restricted" %]
+    [% title = "Account Creation Restricted" %]
+    User account creation has been restricted.
+    <hr>
+    Contact your administrator or the maintainer
+    ([% Param("maintainer") %]) for information about
+    creating an account.
 
   [% ELSIF error == "account_disabled" %]
     [% title = "Account Disabled" %]
@@ -68,10 +76,6 @@
       that login name.
     [% END %]
 
-  [% ELSIF error == "account_inexistent" %]
-    [% title = "Account Does Not Exist" %]
-    There is no [% terms.Bugzilla %] account with that login name.
-
   [% ELSIF error == "alias_has_comma_or_space" %]
     [% title = "Invalid Characters In Alias" %]
     The alias you entered, <em>[% alias FILTER html %]</em>,
@@ -86,10 +90,6 @@
     has already taken the alias <em>[% alias FILTER html %]</em>.
     Please choose another one.
 
-  [% ELSIF error == "alias_not_defined" %]
-    [% title = "Alias Is Not Defined" %]
-    You did not supply an alias to this [% terms.bug %].
-
   [% ELSIF error == "alias_is_numeric" %]
     [% title = "Alias Is Numeric" %]
     You tried to give this [% terms.bug %] the alias <em>[% alias FILTER html %]</em>,
@@ -146,9 +146,13 @@
       schedule
     [% ELSIF action == "use" %]
       use
+    [% ELSIF action == "approve" %]
+      approve
     [% END %]
 
-    [% IF object == "attachment" %]
+    [% IF object == "administrative_pages" %]
+      administrative pages
+    [% ELSIF object == "attachment" %]
       this attachment
     [% ELSIF object == "bugs" %]
       [%+ terms.bugs %]
@@ -187,7 +191,7 @@
     [% ELSIF object == "settings" %]
       settings
     [% ELSIF object == "sudo_session" %]
-      an sudo session
+      a sudo session
     [% ELSIF object == "timetracking_summaries" %]
       time-tracking summary reports
     [% ELSIF object == "user" %]
@@ -196,6 +200,8 @@
       users
     [% ELSIF object == "versions" %]
       versions
+    [% ELSIF object == "workflow" %]
+      the workflow
     [% END %].
 
     [% Hook.process("auth_failure") %]
@@ -228,19 +234,25 @@
                     'query.html#list' => "$terms.Bug lists"} %]
     You may not search, or create saved searches, without any search terms.
 
-  [% ELSIF error == "bugs_not_changed" %]
-    [% title = BLOCK %][% terms.Bugs %] Not Changed[% END %]
-    You do not seem to have made any changes in the selected [% terms.bugs %],
-    so there is nothing to Commit.
-
   [% ELSIF error == "chart_too_large" %]
     [% title = "Chart Too Large" %]
     Sorry, but 2000 x 2000 is the maximum size for a chart.
 
+  [% 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_required" %]
     [% title = "Comment Required" %]
-    You have to specify a <b>comment</b> on this change.
-    Please explain your change.
+    You have to specify a
+    [% IF old && new %]
+      <b>comment</b> when changing the status of [% terms.abug %] from
+      [%+ old.name FILTER html %] to [% new.name FILTER html %].
+    [% ELSIF new %]
+      description for this [% terms.bug %].
+    [% ELSE %]
+      <b>comment</b> on this change.
+    [% END %]
 
   [% ELSIF error == "comment_too_long" %]
     [% title = "Comment Too Long" %]
@@ -283,12 +295,13 @@
 
   [% ELSIF error == "component_already_exists" %]
     [% title = "Component Already Exists" %]
-    A component with the name '[% name FILTER html %]' already exists.
+    The <em>[% product.name FILTER html %]</em> product already has
+    a component named <em>[% name FILTER html %]</em>.
 
   [% ELSIF error == "component_blank_description" %]
     [% title = "Blank Component Description Not Allowed" %]
-    You must enter a non-blank description for component '[% name FILTER html %]'.
-     
+    You must enter a non-blank description for this component.
+
   [% ELSIF error == "component_blank_name" %]
     [% title = "Blank Component Name Not Allowed" %]
     You must enter a name for this new component.
@@ -302,21 +315,31 @@
   [% 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.size %] characters).
+    '[% name FILTER html %]' is too long ([% name.length %] characters).
 
   [% ELSIF error == "component_need_initialowner" %]
     [% title = "Component Requires Default Assignee" %]
-    You must enter a default assignee for component '[% name FILTER html %]'.
-
-  [% ELSIF error == "component_not_valid" %]
-    [% title = "Specified Component Does Not Exist" %]
-    Product [% product FILTER html %] does not have a component
-    named [% name FILTER html %].
+    A default assignee is required for this component.
 
   [% ELSIF error == "customfield_nonexistent" %]
     [% title = "Unknown Custom Field" %]
     There is no custom field with the name '[% name FILTER html %]'.
 
+  [% ELSIF error == "customfield_not_obsolete" %]
+    [% title = "Custom Field Not Obsolete" %]
+    The custom field '[% name FILTER html %]' is not obsolete.
+    Please obsolete a custom field before attempting to delete it.
+
+  [% ELSIF error == "customfield_has_activity" %]
+    [% title = "Custom Field Has Activity" %]
+    The custom field '[% name FILTER html %]' cannot be deleted because
+    it has recorded activity.
+
+  [% ELSIF error == "customfield_has_contents" %]
+    [% title = "Custom Field Has Contents" %]
+    The custom field '[% name FILTER html %]' cannot be deleted because
+    at least one [% terms.bug %] has a non empty value for this field.
+
   [% ELSIF error == "dependency_loop_multi" %]
     [% title = "Dependency Loop Detected" %]
     The following [% terms.bug %](s) would appear on both the "depends on"
@@ -331,9 +354,10 @@
     [% title = "Dependency Loop Detected" %]
     You can't make [% terms.abug %] block itself or depend on itself.
 
-  [% ELSIF error == "description_required" %]
-    [% title = "Description Required" %]
-    You must provide a description of the [% terms.bug %].
+  [% ELSIF error == "dupe_id_required" %]
+    [% title = "Duplicate $terms.Bug Id Required" %]
+    You must specify [% terms.abug %] id to mark this [% terms.bug %]
+    as a duplicate of.
 
   [% ELSIF error == "dupe_not_allowed" %]
     [% title = "Cannot mark $terms.bugs as duplicates" %]
@@ -414,7 +438,7 @@
   [% ELSIF error == "fieldvalue_already_exists" %]
     [% title = "Field Value Already Exists" %]
     The value '[% value FILTER html %]' already exists for the
-    '[%- field FILTER html %]' field.
+    '[%- field.description FILTER html %]' field.
 
   [% ELSIF error == "fieldvalue_doesnt_exist" %]
     [% title = "Specified Field Value Does Not Exist" %]
@@ -424,7 +448,7 @@
   [% ELSIF error == "fieldvalue_is_default" %]
     [% title = "Specified Field Value Is Default" %]
     '[% value FILTER html %]' is the default value for
-    the '[% field FILTER html %]' field and cannot be deleted.
+    the '[% field.description FILTER html %]' field and cannot be deleted.
     [% IF user.groups.tweakparams %]
       You have to <a href="editparams.cgi?section=bugfields#
       [%- param_name FILTER url_quote %]">change</a> the default value first.
@@ -438,17 +462,23 @@
   [% 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 FILTER html %]' field.
+    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 FILTER html %]' field.
+    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.
 
+  [% ELSIF error == "fieldvalue_reserved_word" %]
+    [% title = "Reserved Word Not Allowed" %]
+    You cannot use the '[% value FILTER html %]' value 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 %]'
@@ -457,7 +487,7 @@
   [% 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
+    '[% field FILTER html %]' field, because there are still
     [%+ count FILTER html %] [%+ terms.bugs %] using it.
 
   [% ELSIF error == "fieldvalue_undefined" %]
@@ -473,7 +503,7 @@
     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>
-    We recommend that you store your attachment elsewhere on the web,
+    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.
@@ -559,7 +589,7 @@
     <code>[% name FILTER html %]
           [% IF status == "X" %][% old_status FILTER html %][% END %]</code>.
 
-    Only a sufficiently empowered user can make this change.
+    Only a user with the required permissions may make this change.
 
   [% ELSIF error == "format_not_found" %]
     [% title = "Format Not Found" %]
@@ -571,6 +601,13 @@
     The sort key must be an integer between 0 and 32767 inclusive.
     It cannot be <em>[% sortkey FILTER html %]</em>.
 
+  [% ELSIF error == "freetext_too_long" %]
+    [% title = "Text Too Long" %]
+    The text you entered 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>
+
   [% ELSIF error == "group_cannot_delete" %]
     [% title = "Cannot Delete Group" %]
     The <em>[% name FILTER html %]</em> group cannot be deleted because
@@ -579,6 +616,12 @@
     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.
@@ -597,6 +640,19 @@
     In order to delete this group, you first have to change the
     [%+ param FILTER html %] to make [% attr FILTER html %] point to another group.
 
+
+  [% ELSIF error == "group_invalid_removal" %]
+    You tried to remove [% terms.bug %] [%+ bug.id FILTER html %]
+    from 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.
+
   [% ELSIF error == "group_not_specified" %]
     [% title = "Group not specified" %]
     No group was specified.
@@ -629,16 +685,26 @@
     [% title = "Your Search Makes No Sense" %]
     The only legal values for the <em>Attachment is patch</em> field are
     0 and 1.
-         
+
+  [% ELSIF error == "illegal_bug_status_transition" %]
+    [% title = "Illegal $terms.Bug Status Change" %]
+    [% IF old.defined %]
+      You are not allowed to change the [% terms.bug %] status from
+      [%+ old.name FILTER html %] to [% new.name FILTER html %].
+    [% ELSE %]
+      You are not allowed to file new [% terms.bugs %] with the
+      [%+ new.name FILTER html %] status. 
+    [% END %]
+
   [% ELSIF error == "illegal_change" %]
     [% title = "Not allowed" %]
-    You tried to change the 
+    You tried to change the
     <strong>[% field_descs.$field FILTER html %]</strong> field 
-    [% IF oldvalue %]
-      from <em>[% oldvalue FILTER html %]</em>
+    [% IF oldvalue.defined %]
+      from <em>[% oldvalue.join(', ') FILTER html %]</em>
     [% END %]
-    [% IF newvalue %]
-      to <em>[% newvalue FILTER html %]</em>
+    [% IF newvalue.defined %]
+      to <em>[% newvalue.join(', ') FILTER html %]</em>
     [% END %]
     , but only
     [% IF privs < 3 %]
@@ -646,14 +712,14 @@
       [% IF privs < 2 %] or reporter [% END %]
       of the [% terms.bug %], or
     [% END %]
-    a sufficiently empowered user may change that field.
+    a user with the required permissions may change that field.
 
   [% ELSIF error == "illegal_change_deps" %]
     [% title = "Not allowed" %]
     You tried to change the 
     <strong>[% field_descs.$field FILTER html %]</strong> field 
-    but only a user empowered to edit 
-    both involved [% terms.bugs %] may change that field.
+    but only a user allowed to edit 
+    both related [% terms.bugs %] may change that field.
 
   [% ELSIF error == "illegal_changed_in_last_x_days" %]
     [% title = "Your Search Makes No Sense" %]
@@ -711,6 +777,13 @@
     [% docslinks = {'reporting.html' => 'Reporting'} %]
     You are not authorized to edit this series. To do this, you must either
     be its creator, or an administrator.
+
+  [% ELSIF error == "illegal_time" %]
+    [% title = "Illegal Time" %]
+    '<tt>[% time FILTER html %]</tt>' is not a legal time.
+    [% IF format %]
+      Please use the format '<tt>[% format FILTER html %]</tt>'.
+    [% END %]
         
   [% ELSIF error == "insufficient_data_points" %]
     [% docslinks = {'reporting.html' => 'Reporting'} %]
@@ -720,12 +793,14 @@
     [% title = "Invalid Attachment ID" %]
     The attachment id [% attach_id FILTER html %] is invalid.
 
-  [% ELSIF error == "invalid_bug_id_non_existent" %]
+  [% ELSIF error == "bug_id_does_not_exist" %]
     [% title = BLOCK %]Invalid [% terms.Bug %] ID[% END %]
     [% terms.Bug %] #[% bug_id FILTER html %] does not exist.
     
-  [% ELSIF error == "invalid_bug_id_or_alias" %]
-    [% title = BLOCK %]Invalid [% terms.Bug %] ID[% END %]
+  [% ELSIF error == "improper_bug_id_field_value" %]
+    [% title = BLOCK %]
+      [% IF bug_id %]Invalid [% ELSE %]Missing [% END %] [% terms.Bug %] ID
+    [% END %]
     [% IF bug_id %]
       '[% bug_id FILTER html %]' is not a valid [% terms.bug %] number
       [% IF Param("usebugaliases") %]
@@ -867,36 +942,27 @@
     [% title = "Default milestone not deletable" %]
     [% admindocslinks = {'products.html' => 'Administering products',
                          'milestones.html' => 'About Milestones'} %]
-    Sorry, but [% milestone.name FILTER html %] is the default milestone 
-    for product '[% product.name FILTER html %]', and so it can not be 
-    deleted.
+    Sorry, but [% milestone.name FILTER html %] is the default milestone
+    for the '[% milestone.product.name FILTER html %]' product, and so
+    it cannot be deleted.
 
   [% ELSIF error == "milestone_name_too_long" %]
     [% title = "Milestone Name Is Too Long" %]
     The name of a milestone is limited to 20 characters. 
     '[% name FILTER html %]' is too long ([% name.length %] characters).
 
-  [% ELSIF error == "milestone_not_specified" %]
-    [% title = "No Milestone Specified" %]
-    No milestone specified when trying to edit milestones.
-
-  [% ELSIF error == "milestone_not_valid" %]
-    [% title = "Specified Milestone Does Not Exist" %]
-    The milestone '[% milestone FILTER html %]' for product 
-    '[% product FILTER html %]' does not exist.
-
   [% ELSIF error == "milestone_required" %]
     [% title = "Milestone Required" %]
-    You must determine a target milestone for [% terms.bug %] 
-    [%+ bug_id FILTER html %]
+    You must select a target milestone for [% terms.bug %]
+    [%+ bug.id FILTER html %]
     if you are going to accept it.  Part of accepting 
     [%+ terms.abug %] is giving an estimate of when it will be fixed.
 
   [% ELSIF error == "milestone_sortkey_invalid" %]
     [% title = "Invalid Milestone Sortkey" %]
-    The sortkey '[% sortkey FILTER html %]' for milestone '
-    [% name FILTER html %]' is not in the range -32768 &le; sortkey
-    &le; 32767.
+    The sortkey '[% sortkey FILTER html %]' is not in the range
+    [%+ constants.MIN_SMALLINT FILTER html %] &le; sortkey &le;
+    [%+ constants.MAX_SMALLINT FILTER html %].
 
   [% ELSIF error == "misarranged_dates" %]
     [% title = "Misarranged Dates" %]
@@ -975,6 +1041,11 @@
       does not exist.
     [% END %]
 
+  [% ELSIF error == "missing_resolution" %]
+    [% title = "Resolution Required" %]
+    A valid resolution is required to mark [% terms.bugs %] as
+    [%+ status FILTER upper FILTER html %].
+
   [% ELSIF error == "move_bugs_disabled" %]
     [% title = BLOCK %][% terms.Bug %] Moving Disabled[% END %]
     Sorry, [% terms.bug %] moving has been disabled. If you need
@@ -1014,7 +1085,12 @@
 
   [% ELSIF error == "no_bugs_chosen" %]
     [% title = BLOCK %]No [% terms.Bugs %] Selected[% END %]
-    You apparently didn't choose any [% terms.bugs %] to modify.
+    You apparently didn't choose any [% terms.bugs %]
+    [% IF action == "modify" %]
+      to modify.
+    [% ELSIF action == "view" %]
+      to view.
+    [% END %]
 
   [% ELSIF error == "no_bug_ids" %]
     [% title = BLOCK %]No [% terms.Bugs %] Selected[% END %]
@@ -1033,11 +1109,6 @@
     [% title = "No Tag Selected" %]
     You didn't select a tag from which to remove [% terms.bugs %].
 
-  [% ELSIF error == "no_component_change_for_multiple_products" %]
-    [% title = "Action Not Permitted" %]
-    You cannot change the component for a list of [% terms.bugs %] covering more than
-    one product.
-
   [% ELSIF error == "no_dupe_stats" %]
     [% title = "Cannot Find Duplicate Statistics" %]
     [% admindocslinks = {'extraconfig.html' => 'Setting up the collecstats.pl job'} %]
@@ -1063,6 +1134,11 @@
     and an error
     occurred opening yesterday's dupes file: [% error_msg FILTER html %].
 
+  [% ELSIF error == "no_initial_bug_status" %]
+    [% title = "No Initial $terms.Bug Status" %]
+    No [% terms.bug %] status is available on [% terms.bug %] creation.
+    Please report the problem to [% Param("maintainer") %].
+
   [% ELSIF error == "no_new_quips" %]
     [% title = "No New Quips" %]
     [% admindocslinks = {'quips.html' => 'Controlling quip usage'} %]
@@ -1102,6 +1178,24 @@
     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 %]
+    [% 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 %]
+    [% title = BLOCK %]Invalid [% type FILTER ucfirst FILTER html %][% END %]
+    There is no [% type FILTER html %] named '[% name FILTER html %]'
+    [% IF product.defined %]
+      in the '[% product.name FILTER html %]' product
+    [% END %].
+    [% IF class == "Bugzilla::User" %]
+      Either you mis-typed the name or that user has not yet registered
+      for a [% terms.Bugzilla %] account.
+    [% END %]
+
   [% ELSIF error == "old_password_incorrect" %]
     [% title = "Incorrect Old Password" %]
     You did not enter your old password correctly.
@@ -1211,9 +1305,9 @@
     You must enter a name for the new product.
   
   [% ELSIF error == "product_disabled" %]
-    [% title = BLOCK %]Product closed for [% terms.Bugs %] Entry[% END %]
+    [% title = BLOCK %]Product closed for [% terms.Bug %] Entry[% END %]
     [% admindocslinks = {'products.html' => 'Administering products'} %]
-    Sorry, entering [% terms.bugs %] into the
+    Sorry, entering [% terms.abug %] into the
     product <em>[% product.name FILTER html %]</em> has been disabled.
 
   [% ELSIF error == "product_edit_denied" %]
@@ -1227,7 +1321,7 @@
     [% title = BLOCK %]Product has [% terms.Bugs %][% END %]
     [% admindocslinks = {'products.html' => 'Administering products'} %]
     There are [% nb FILTER html %] [%+ terms.bugs %] entered for this product!
-    You must reassign those [% terms.bugs %] to another product before you
+    You must move those [% terms.bugs %] to another product before you
     can delete this one.
 
   [% ELSIF error == "product_must_have_description" %]
@@ -1255,8 +1349,9 @@
     The name <em>[% name FILTER html %]</em> is already used by another
     saved search. You first have to
     <a href="buglist.cgi?cmdtype=dorem&amp;remaction=forget&amp;namedcmd=
-    [%- name FILTER url_quote %]">delete</a> it if you really want to use
-    this name.
+    [%- name FILTER url_quote %]&amp;token=
+    [% issue_hash_token([query_id, name]) FILTER url_quote %]">delete</a>
+    it if you really want to use this name.
 
   [% ELSIF error == "query_name_missing" %]
     [% title = "No Search Name Specified" %]
@@ -1299,6 +1394,10 @@
     [% title = "Summary Needed" %]
     You must enter a summary for this [% terms.bug %].
 
+  [% ELSIF error == "resolution_cant_clear" %]
+    [% terms.Bug %] [%+ bug_id FILTER bug_link(bug_id) FILTER none %] is 
+    closed, so you cannot clear its resolution.
+
   [% ELSIF error == "resolution_not_allowed" %]
     [% title = "Resolution Not Allowed" %]
     You cannot set a resolution for open [% terms.bugs %].
@@ -1394,7 +1493,7 @@
     <tt>[% product FILTER html %]</tt> product, which exceeds the maximum of
     [%+ max FILTER html %] votes for this product.
 
-  [% ELSIF error == "token_inexistent" %]
+  [% ELSIF error == "token_does_not_exist" %]
     [% title = "Token Does Not Exist" %]
     The token you submitted does not exist, has expired, or has
     been canceled.
@@ -1438,11 +1537,6 @@
     [% title = "No Version Specified" %]
     No version specified when trying to edit versions.
 
-  [% ELSIF error == "version_not_valid" %]
-    [% title = "Specified Version Does Not Exist" %]
-    The version '[% version FILTER html %]' for product 
-    '[% product FILTER html %]' does not exist.
-
   [% ELSIF error == "users_deletion_disabled" %]
     [% title = "Deletion not activated" %]
     [% admindocslinks = {'useradmin.html' => 'User administration'} %]
@@ -1537,10 +1631,8 @@
 
 <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>
@@ -1574,3 +1666,17 @@
 [% END %]            
 
 [% PROCESS global/footer.html.tmpl %]
+
+[% BLOCK object_name %]
+  [% IF class == "Bugzilla::User" %]
+    user
+  [% ELSIF class == "Bugzilla::Component" %]
+    component
+  [% ELSIF class == "Bugzilla::Version" %]
+    version
+  [% ELSIF class == "Bugzilla::Milestone" %]
+    milestone
+  [% ELSIF class == "Bugzilla::Status" %]
+    status
+  [% END %]
+[% END %]
diff --git a/BugsSite/template/en/default/global/userselect.html.tmpl b/BugsSite/template/en/default/global/userselect.html.tmpl
index 7a46891..35075ef 100644
--- a/BugsSite/template/en/default/global/userselect.html.tmpl
+++ b/BugsSite/template/en/default/global/userselect.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -12,11 +11,10 @@
   # The Original Code is the Bugzilla Bug Tracking System.
   #
   # Contributor(s): Byron Jones <bugzilla@glob.com.au>
+  #                 Frédéric Buclin <LpSolit@gmail.com>
   #%]
 
 [%# INTERFACE:
-  # userlist: select only; array reference with list of users and identities
-  #                        userlist is built by Bugzilla::User::get_userlist()
   # name: mandatory; field name
   # id: optional; field id
   # value: optional; default field value/selection
@@ -26,7 +24,7 @@
   # size: optional, input only; size attribute value
   # emptyok: optional, select only;  if true, prepend menu option 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
   #%]
 
 [% IF Param("usemenuforusers") %]
@@ -40,13 +38,35 @@
   [% IF emptyok %]
     <option value=""></option>
   [% END %]
-  [% FOREACH tmpuser = user.get_userlist %]
-    [% IF tmpuser.visible OR value.match("\\b$tmpuser.login\\b") %]
+
+  [% UNLESS custom_userlist %]
+    [% custom_userlist = user.get_userlist %]
+  [% END %]
+
+  [% SET selected = {} %]
+  [% IF value.defined %]
+    [% FOREACH selected_value IN value.split(', ') %]
+      [% SET selected.$selected_value = 1 %]
+    [% END %]
+  [% END %]
+
+  [% FOREACH tmpuser = custom_userlist %]
+    [% IF tmpuser.visible OR selected.${tmpuser.login} == 1 %]
       <option value="[% tmpuser.login FILTER html %]"
-        [% " selected=\"selected\"" IF value.match("\\b$tmpuser.login\\b") %]
+        [% IF selected.${tmpuser.login} == 1 %]
+          selected="selected"
+          [%# A user account appears only once. Remove it from the list, so that
+            # we know if there are some selected accounts which have not been listed. %]
+          [% selected.delete(tmpuser.login) %]
+        [% END %]
       >[% tmpuser.identity FILTER html %]</option>
     [% END %]
   [% END %]
+
+  [%# If the list is not empty, this means some accounts have not been mentioned yet. %]
+  [% FOREACH selected_user = selected.keys %]
+    <option value="[% selected_user FILTER html %]" selected="selected">[% selected_user FILTER html %]</option>
+  [% END %]
 </select>
 [% ELSE %]
 <input
diff --git a/BugsSite/template/en/default/global/variables.none.tmpl b/BugsSite/template/en/default/global/variables.none.tmpl
index d19d640..4fa6089 100644
--- a/BugsSite/template/en/default/global/variables.none.tmpl
+++ b/BugsSite/template/en/default/global/variables.none.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -28,19 +27,6 @@
   # Remember to PROCESS rather than INCLUDE this template. 
   #%]
 
-[%# Note on changing terms:
-  # Changing this will not affect "linkification" of your new terms.
-  # This means if you change "bug" to "problem", then if you have
-  # "problem 3" in a comment, it won't become a clickable URL.
-  # To have that feature, you must edit the quoteUrls function in
-  # Bugzilla/Template.pm.
-  # Change the line:
-  # my $bug_re = qr/bug\s*\#?\s*(\d+)/i;
-  # to something like:
-  # my $bug_re = qr/(?:bug|problem)\s*\#?\s*(\d+)/i;
-  # (here "problem" was used instead of bug - substitute as needed!).
-  #%]
-
 [% terms = {
   "bug" => "bug",
   "Bug" => "Bug",
@@ -50,8 +36,6 @@
   "bugs" => "bugs",
   "Bugs" => "Bugs",
   "zeroSearchResults" => "Zarro Boogs found",
-  "bit" => "bit",
-  "bits" => "bits",
   "Bugzilla" => "Bugzilla"
   }
 %]
diff --git a/BugsSite/template/en/default/index.html.tmpl b/BugsSite/template/en/default/index.html.tmpl
index d129cb6..9e0ec8a 100644
--- a/BugsSite/template/en/default/index.html.tmpl
+++ b/BugsSite/template/en/default/index.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# -*- mode: html -*- %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
@@ -46,7 +45,7 @@
     var sidebarname=window.location.host;
     if (!/bug/i.test(sidebarname))
       sidebarname="[% terms.Bugzilla %] "+sidebarname;
-    window.sidebar.addPanel (sidebarname, "[% Param('urlbase') %]sidebar.cgi", "");
+    window.sidebar.addPanel (sidebarname, "[% urlbase FILTER html %]sidebar.cgi", "");
   }
   else
   {
@@ -107,7 +106,7 @@
     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="[% Param('docs_urlbase') FILTER html %]using.html">
+    <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>
 
@@ -131,7 +130,7 @@
 [% 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('[% Param('urlbase') %]search_plugin.cgi')">Install
+      <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>
 
diff --git a/BugsSite/template/en/default/list/change-columns.html.tmpl b/BugsSite/template/en/default/list/change-columns.html.tmpl
index 9b610fe..88ae478 100644
--- a/BugsSite/template/en/default/list/change-columns.html.tmpl
+++ b/BugsSite/template/en/default/list/change-columns.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -41,7 +40,7 @@
   <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 %]>
+      [%+ "checked='checked'" IF lsearch(collist, column) != -1 %]>
     <label for="[% column %]">
       [% (field_descs.${column} || column) FILTER html %]
     </label>
@@ -50,28 +49,35 @@
 
   <p>
     <input id="nosplitheader" type="radio" name="splitheader" value="0"
-      [% "checked='checked'" IF NOT splitheader %]>
+      [%+ "checked='checked'" IF NOT splitheader %]>
     <label for="nosplitheader">
       Normal headers (prettier)
     </label>
     <br>
 
     <input id="splitheader" type="radio" name="splitheader" value="1"
-      [% "checked='checked'" IF splitheader %]>
+      [%+ "checked='checked'" IF splitheader %]>
     <label for="splitheader">
       Stagger headers (often makes list more compact)
     </label>
   </p>
 
+  [% IF saved_search %]
+    <p>
+      <input type="hidden" name="saved_search"
+             value="[% saved_search.id FILTER html%]" >
+      <input type="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>
+  [% END %]
+
   <p>
     <input type="submit" id="change" value="Change Columns">
   </p>
-</form>
 
-<form action="colchange.cgi">
-  <input type="hidden" name="rememberedquery" value="[% buffer FILTER html %]">
-  <input type="hidden" name="resetit" value="1">
-  <input type="submit" id="reset" 
+  <input type="submit" id="resetit" name="resetit" 
          value="Reset to [% terms.Bugzilla %] default">
 </form>
 
diff --git a/BugsSite/template/en/default/list/edit-multiple.html.tmpl b/BugsSite/template/en/default/list/edit-multiple.html.tmpl
index 38e4e93..3e582b3 100644
--- a/BugsSite/template/en/default/list/edit-multiple.html.tmpl
+++ b/BugsSite/template/en/default/list/edit-multiple.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -18,12 +17,15 @@
   #
   # Contributor(s): Myk Melez <myk@mozilla.org>
   #                 Max Kanat-Alexander <mkanat@bugzilla.org>
+  #                 Frédéric Buclin <LpSolit@gmail.com>
+  #                 Guy Pyrzak <guy.pyrzak@gmail.com>
   #%]
 
 [% PROCESS global/variables.none.tmpl %]
 
 [% dontchange = "--do_not_change--" %]
 <input type="hidden" name="dontchange" value="[% dontchange FILTER html %]">
+<input type="hidden" name="token" value="[% token FILTER html %]">
 
 <script type="text/javascript">
   var numelements = document.forms.changeform.elements.length;
@@ -130,7 +132,11 @@
       </td>
     [% END %]
   </tr>
-
+  
+  <tr>
+    <th><label for="bug_status">Status:</label></th>
+    <td colspan="3">[% PROCESS status_section %]</td>
+  </tr>
   [% IF user.in_group(Param("timetrackinggroup")) %]
     <tr>
       <th><label for="estimated_time">Estimated Hours:</label></th>
@@ -140,6 +146,15 @@
                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>
+    </tr>
+    <tr>
       <th><label for="remaining_time">Remaining Hours:</label></th>
       <td>
         <input id="remaining_time"
@@ -147,17 +162,37 @@
                value="[% dontchange FILTER html %]"
                size="6">
       </td>
+      <th>&nbsp;</th>
+      <td>&nbsp;</td>
     </tr>
   [% END %]
 
+  <tr>
+    <th><label for="assigned_to">Assignee:</label></th>
+    <td colspan="3">
+      [% INCLUDE global/userselect.html.tmpl
+           id => "assigned_to"
+           name => "assigned_to"
+           value => dontchange
+           size => 32
+      %]
+      <input type="checkbox" id="set_default_assignee" name="set_default_assignee" value="1">
+      <label for="set_default_assignee">Reset Assignee to default</label>
+    </td>
+  </tr>
+
   [% IF Param("useqacontact") %]
     <tr>
       <th><label for="qa_contact">QA Contact:</label></th>
       <td colspan="3">
-        <input id="qa_contact"
-               name="qa_contact"
-               value="[% dontchange FILTER html %]"
-               size="32">
+        [% INCLUDE global/userselect.html.tmpl
+             id => "qa_contact"
+             name => "qa_contact"
+             value => dontchange
+             size => 32
+        %]
+        <input type="checkbox" id="set_default_qa_contact" name="set_default_qa_contact" value="1">
+        <label for="set_default_qa_contact">Reset QA Contact to default</label>
       </td>
     </tr>
   [% END %]
@@ -208,7 +243,7 @@
   [% END %]
 
   [% USE Bugzilla %]
-  [% FOREACH field = Bugzilla.get_fields({ obsolete => 0, custom => 1 }) %]
+  [% FOREACH field = Bugzilla.active_custom_fields %]
     <tr>
       [% PROCESS bug/field.html.tmpl value = dontchange
                                      editable = 1
@@ -216,6 +251,8 @@
     </tr>
   [% END %]
 
+  [% Hook.process("after_custom_fields") %]
+
 </table>
 
 <b><label for="comment">Additional Comments:</label></b><br>
@@ -229,7 +266,7 @@
 
 [% IF groups.size > 0 %]
 
-  <b>Groupset:</b><br>
+  <b>Groups:</b><br>
   <table border="1">
     <tr>
       <th>Don't<br>change<br>this group<br>restriction</th>
@@ -246,7 +283,7 @@
       <td align="center">
         <input type="radio" name="bit-[% group.id %]" value="0">
       </td>
-      [% IF group.isactive %]
+      [% IF group.is_active %]
         <td align="center">
           <input type="radio" name="bit-[% group.id %]" value="1">
         </td>
@@ -256,7 +293,7 @@
       [% END %]
 
       <td>
-        [% SET inactive = !group.isactive %]
+        [% SET inactive = !group.is_active %]
         [% group.description FILTER html_light FILTER inactive(inactive) %]
       </td>
 
@@ -271,92 +308,6 @@
   [% END %]
 
 [% END %]
-
-
-
-[% knum = 0 %]
-<input id="knob-none" type="radio" name="knob" value="none" checked="checked">
-<label for="knob-none">Do nothing else</label><br>
-
-[% IF bugstatuses.size == 1 && bugstatuses.0 == unconfirmedstate %]
-  [% knum = knum + 1 %]
-  <input id="knob-confirm" type="radio" name="knob" value="confirm">
-  <label for="knob-confirm">
-    Confirm [% terms.bugs %] (change status to <b>[% status_descs.NEW FILTER html %]</b>)
-  </label><br>
-[% END %]
-
-[%# If all the bugs being changed are open, allow the user to accept them,
-    clear their resolution or resolve them. %]
-[% IF !bugstatuses.containsany(closedstates) %]
-  [% knum = knum + 1 %]
-  <input id="knob-accept" type="radio" name="knob" value="accept">
-  <label for="knob-accept">
-    Accept [% terms.bugs %] (change status to <b>[% status_descs.ASSIGNED FILTER html %]</b>)
-  </label><br>
-
-  [% knum = knum + 1 %]
-  <input id="knob-clearresolution" type="radio" name="knob" value="clearresolution">
-  <label for="knob-clearresolution">Clear the resolution</label><br>
-
-  [% knum = knum + 1 %]
-  <input id="knob-resolve" type="radio" name="knob" value="resolve">
-  <label for="knob-resolve">
-    Resolve [% terms.bugs %], changing <a href="page.cgi?id=fields.html#resolution">resolution</a> to
-  </label>
-  <select name="resolution" onchange="document.forms.changeform.knob[[% knum %]].checked=true">
-    [% FOREACH resolution = resolutions %]
-      [% NEXT IF !resolution %]
-      <option value="[% resolution FILTER html %]">
-        [% get_resolution(resolution) FILTER html %]
-      </option>
-    [% END %]
-  </select><br>
-
-[% END %]
-
-[%# If all the bugs are closed, allow the user to reopen them. %]
-[% IF !bugstatuses.containsany(openstates) %]
-  [% knum = knum + 1 %]
-  <input id="knob-reopen" type="radio" name="knob" value="reopen">
-  <label for="knob-reopen">Reopen [% terms.bugs %]</label><br>
-[% END %]
-
-[% IF bugstatuses.size == 1 %]
-  [% IF bugstatuses.contains('RESOLVED') %]
-    [% knum = knum + 1 %]
-    <input id="knob-verify" type="radio" name="knob" value="verify">
-    <label for="knob-verify">Mark [% terms.bugs %] as <b>[% status_descs.VERIFIED FILTER html %]</b></label><br>
-  [% END %]
-[% END %]
-
-[% IF !bugstatuses.containsany(openstates) AND !bugstatuses.contains('CLOSED') %]
-  [% knum = knum + 1 %]
-  <input id="knob-close" type="radio" name="knob" value="close">
-  <label for="knob-close">Mark [% terms.bugs %] as <b>[% status_descs.CLOSED FILTER html %]</b></label><br>
-[% END %]
-
-[% knum = knum + 1 %]
-<input id="knob-reassign" type="radio" name="knob" value="reassign">
-<label for="knob-reassign"><a href="page.cgi?id=fields.html#assigned_to">
-  Reassign</a> [% terms.bugs %] to
-</label>
-[% INCLUDE global/userselect.html.tmpl
-   name => "assigned_to"
-   value => user.login
-   size => 32
-   onchange => "document.forms.changeform.knob[$knum].checked=true;"
-%]<br>
-
-[% knum = knum + 1 %]
-<input id="knob-reassignbycomponent"
-       type="radio"
-       name="knob"
-       value="reassignbycomponent">
-<label for="knob-reassignbycomponent">
-  Reassign [% terms.bugs %] to default assignee of selected component
-</label><br>
-
 <input type="submit" id="commit" value="Commit">
 
 [% IF Param('move-enabled') && user.is_mover %]
@@ -378,3 +329,51 @@
     [% END %]
   </select>
 [% END %]
+
+[%############################################################################%]
+[%# Status Block                                                             #%]
+[%############################################################################%]
+
+[% BLOCK status_section %]
+  [% all_open_bugs = !current_bug_statuses.containsany(closedstates) %]
+  [% all_closed_bugs = !current_bug_statuses.containsany(openstates) %]
+  [% closed_status_array = [] %]
+  
+  <select name="bug_status" id="bug_status"> 
+    <option value="[% dontchange FILTER html %]" selected="selected">[% dontchange FILTER html %]</option>
+  
+    [% FOREACH bug_status = new_bug_statuses %]
+      <option value="[% bug_status.name FILTER html %]">
+        [% get_status(bug_status.name) FILTER html %]
+      </option>
+      [% IF !bug_status.is_open %]
+        [% filtered_status =  bug_status.name FILTER js %]
+        [% closed_status_array.push( filtered_status ) %]
+      [% END %]
+    [% END %]
+  
+  [%# If all the bugs being changed are closed, allow the user to change their resolution. %]
+  [% IF all_closed_bugs %]
+    [% filtered_status = dontchange FILTER js %]
+    [% closed_status_array.push( filtered_status ) %]
+  [% END %]
+  </select>
+  
+  <span id="resolution_settings">
+  <select id="resolution" name="resolution">
+      <option value="[% dontchange FILTER html %]" selected >[% dontchange FILTER html %]</option>
+    [% FOREACH r = resolutions %]
+      [% NEXT IF !r %]
+      [% NEXT IF r == "DUPLICATE" || r == "MOVED" %]
+      <option value="[% r FILTER html %]">[% get_resolution(r) FILTER html %]</option>
+    [% END %]
+  </select>
+  </span>
+
+  <script type="text/javascript">
+  var close_status_array = new Array("[% closed_status_array.join('", "') FILTER none %]");
+    YAHOO.util.Event.addListener('bug_status', "change", showHideStatusItems, '[% "is_duplicate" IF bug.dup_id %]');
+    YAHOO.util.Event.onDOMReady( showHideStatusItems );
+  </script>
+
+[% END %]
diff --git a/BugsSite/template/en/default/list/list-simple.html.tmpl b/BugsSite/template/en/default/list/list-simple.html.tmpl
index 8494baf..125a164 100644
--- a/BugsSite/template/en/default/list/list-simple.html.tmpl
+++ b/BugsSite/template/en/default/list/list-simple.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -39,7 +38,7 @@
 
   <head>
     <title>[% title FILTER html %]</title>
-    <base href="[% Param("urlbase") %]">
+    <base href="[% urlbase FILTER html %]">
     <link href="skins/standard/buglist.css" rel="stylesheet" type="text/css">
   </head>
 
diff --git a/BugsSite/template/en/default/list/list.atom.tmpl b/BugsSite/template/en/default/list/list.atom.tmpl
index 837c70b..bfebbb8 100644
--- a/BugsSite/template/en/default/list/list.atom.tmpl
+++ b/BugsSite/template/en/default/list/list.atom.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -33,23 +32,23 @@
 <feed xmlns="http://www.w3.org/2005/Atom">
   <title>[% title FILTER xml %]</title>
   <link rel="alternate" type="text/html"
-        href="[% Param('urlbase') %]buglist.cgi?
+        href="[% urlbase FILTER html %]buglist.cgi?
         [%- urlquerypart.replace('ctype=atom[&]?','') FILTER xml %]"/>
   <link rel="self" type="application/atom+xml"
-        href="[% Param('urlbase') %]buglist.cgi?
+        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>
-  <id>[% Param('urlbase') %]buglist.cgi?[% urlquerypart FILTER xml %]</id>
+  <id>[% urlbase FILTER html %]buglist.cgi?[% urlquerypart FILTER xml %]</id>
 
   [% FOREACH bug = bugs %]
   <entry>
     <title>[% "@" IF bug.secure_mode %][[% terms.Bug %] [%+ bug.bug_id FILTER xml %]] [% bug.short_desc FILTER xml %]</title>
     <link rel="alternate" type="text/html"
-          href="[% Param('urlbase') FILTER xml %]show_bug.cgi?id=
+          href="[% urlbase FILTER html %]show_bug.cgi?id=
           [%- bug.bug_id FILTER xml %]"/>
-    <id>[% Param('urlbase') FILTER xml %]show_bug.cgi?id=[% bug.bug_id FILTER xml %]</id>
+    <id>[% urlbase FILTER xml %]show_bug.cgi?id=[% bug.bug_id FILTER xml %]</id>
     <author>
       <name>[% bug.reporter_realname FILTER xml %]</name>
     </author>
@@ -61,22 +60,39 @@
       <table>
       <tr>
         <th>Field</th><th>Value</th>
-      </tr><tr>
-        <td>[% columns.opendate.title FILTER html %]</td>
-        <td>[% bug.opendate FILTER html %]</td>
-      </tr><tr>
+      </tr><tr class="bz_feed_product">
+        <td>[% columns.product.title FILTER html %]</td>
+        <td>[% bug.product FILTER html %]</td>
+      </tr><tr class="bz_feed_component">
+        <td>[% columns.component.title FILTER html %]</td>
+        <td>[% bug.component FILTER html %]</td>
+      </tr><tr class="bz_feed_assignee">
         <td>[% columns.assigned_to_realname.title FILTER html %]</td>
         <td>[% bug.assigned_to_realname FILTER html %]</td>
-      </tr><tr>
-        <td>[% columns.priority.title FILTER html %]</td>
-        <td>[% bug.priority FILTER html %]</td>
-      </tr><tr>
-        <td>[% columns.bug_severity.title FILTER html %] </td>
-        <td>[% bug.bug_severity FILTER html %]</td>
-      </tr><tr>
+      </tr><tr class="bz_feed_reporter">
+        <td>[% columns.reporter_realname.title FILTER html %]</td>
+        <td>[% bug.reporter_realname 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>
-      </tr><tr>
+      </tr><tr class="bz_feed_resolution">
+        <td>[% columns.resolution.title FILTER html %] </td>
+        <td>[% bug.resolution FILTER html %]</td>
+      </tr><tr class="bz_feed_priority">
+        <td>[% columns.priority.title FILTER html %]</td>
+        <td>[% 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>
+      [% IF Param("usetargetmilestone") %]
+      </tr><tr class="bz_feed_target_milestone">
+        <td>[% columns.target_milestone.title FILTER html %]</td>
+        <td>[% bug.target_milestone FILTER html %]</td>
+      [% END %]
+      </tr><tr class="bz_feed_creation_date">
+        <td>[% columns.opendate.title FILTER html %]</td>
+        <td>[% bug.opendate FILTER html %]</td>
+      </tr><tr class="bz_feed_changed_date">
         <td>[% columns.changeddate.title FILTER html %]</td>
         <td>[% bug.changeddate FILTER html -%]</td>
       </tr>
diff --git a/BugsSite/template/en/default/list/list.csv.tmpl b/BugsSite/template/en/default/list/list.csv.tmpl
index 4175088..9ce1c73 100644
--- a/BugsSite/template/en/default/list/list.csv.tmpl
+++ b/BugsSite/template/en/default/list/list.csv.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -38,7 +37,7 @@
         [% rawcolumn = column.replace("date", "time") %]
         [% bug.$column = date.format(bug.$rawcolumn, "%Y-%m-%d %H:%M:%S") %]
       [% ELSIF column == 'bug_status' %]
-        [% bug.$column = status_descs.${bug.$column} %]
+        [% bug.$column = get_status(bug.$column) %]
       [% ELSIF column == 'resolution' %]
         [%- bug.$column = get_resolution(bug.$column) %]
       [% END %]
diff --git a/BugsSite/template/en/default/list/list.html.tmpl b/BugsSite/template/en/default/list/list.html.tmpl
index a12c39a..be17414 100644
--- a/BugsSite/template/en/default/list/list.html.tmpl
+++ b/BugsSite/template/en/default/list/list.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -36,9 +35,7 @@
   [% title = title _ ": " _ (searchname OR defaultsavename) FILTER html %]
 [% END %]
 
-[% style_urls = [ "skins/standard/buglist.css" ] %]
 [% qorder = order FILTER url_quote IF order %]
-[% message = "buglist_sorted_by_relevance" IF sorted_by_relevance %]
 
 
 [%############################################################################%]
@@ -49,33 +46,41 @@
   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" ]
+  doc_section = "query.html#list"
 %]
 
-<div align="center">
-  [% 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 %]
+<div class="bz_query_head" align="center">
+  <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 %]
+  </span>
 
   [% IF debug %]
-    <p>
+    <p class="bz_query_debug">
       [% FOREACH debugline = debugdata %]
         [% debugline FILTER html %]<br>
       [% END %]
     </p>
-    <p>[% query FILTER html %]</p>
+    <p class="bz_query">[% query FILTER html %]</p>
   [% 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." %]
-    <a href="quips.cgi"><i>[% quip FILTER html %]</i></a>
+    <span class="bz_quip">
+      <a href="quips.cgi"><i>[% quip FILTER html %]</i></a>
+    </span>
   [% END %]
 
 </div>
 
 [% IF toolong %]
-  <h2>
+  <h2 class="bz_smallminded">
     This list is too long for [% terms.Bugzilla %]'s little mind; the
     Next/Prev/First/Last buttons won't appear on individual [% terms.bugs %].
   </h2>
@@ -88,7 +93,9 @@
 [%############################################################################%]
 
 [% IF bugs.size > 9 %]
-  [% bugs.size %] [%+ terms.bugs %] found.
+  <span class="bz_result_count">
+    [% bugs.size %] [%+ terms.bugs %] found.
+  </span>
 [% END %]
 
 [%############################################################################%]
@@ -110,13 +117,15 @@
 [%# Succeeding Status Line                                                   #%]
 [%############################################################################%]
 
-[% IF bugs.size == 0 %]
-  [% terms.zeroSearchResults %].
-[% ELSIF bugs.size == 1 %]
-  One [% terms.bug %] found.
-[% ELSE %]
-  [% bugs.size %] [%+ terms.bugs %] found.
-[% END %]
+<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>
 
 <br>
 
@@ -137,7 +146,7 @@
 <table>
   <tr>
     [% IF bugs.size > 0 %]
-      <td valign="middle">
+      <td valign="middle" class="bz_query_buttons">
         <form method="post" action="show_bug.cgi">
           [% FOREACH id = buglist %]
             <input type="hidden" name="id" value="[% id FILTER html %]">
@@ -164,7 +173,7 @@
       
       <td>&nbsp;</td>
       
-      <td valign="middle">
+      <td valign="middle" class="bz_query_links">
         <a href="buglist.cgi?
         [% urlquerypart FILTER html %]&amp;ctype=csv">CSV</a> |
         <a href="buglist.cgi?
@@ -193,7 +202,7 @@
       </td>
     [% END %]
     
-    <td valign="middle">
+    <td valign="middle" class="bz_query_edit">
       [% editqueryname = searchname OR defaultsavename OR '' %]
       <a href="query.cgi?[% urlquerypart FILTER html %]
       [% IF editqueryname != '' %]&amp;known_name=
@@ -202,16 +211,17 @@
     </td>
       
     [% IF searchtype == "saved" %]
-      <td valign="middle" nowrap="nowrap">
+      <td valign="middle" nowrap="nowrap" class="bz_query_forget">
         |
         <a href="buglist.cgi?cmdtype=dorem&amp;remaction=forget&amp;namedcmd=
-                [% searchname FILTER url_quote %]">Forget&nbsp;Search&nbsp;'
-                [% searchname FILTER html %]'</a>
+                [% searchname FILTER url_quote %]&amp;token=
+                [% issue_hash_token([search_id, searchname]) FILTER url_quote %]">
+          Forget&nbsp;Search&nbsp;'[% searchname FILTER html %]'</a>
       </td>
     [% ELSE %]
       <td>&nbsp;</td>
       
-      <td valign="middle">
+      <td valign="middle" class="bz_query_remember">
         <form method="get" action="buglist.cgi">
           <input type="submit" id="remember" value="Remember search"> as 
           <input type="hidden" name="newquery" 
@@ -227,7 +237,7 @@
 </table>
 
 [% IF cgi.param('product').size == 1 && cgi.param('product') != "" %]
-  <p>
+  <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>
   </p>
diff --git a/BugsSite/template/en/default/list/list.ics.tmpl b/BugsSite/template/en/default/list/list.ics.tmpl
index bc08d2f..f8953d9 100644
--- a/BugsSite/template/en/default/list/list.ics.tmpl
+++ b/BugsSite/template/en/default/list/list.ics.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -27,8 +26,8 @@
 BEGIN:VTODO
 [%+ PROCESS ics_dtstart +%]
 [%+ PROCESS ics_summary +%]
-[%+ PROCESS ics_uid base_url=Param('urlbase') bug_id=bug.bug_id +%]
-[%+ PROCESS ics_url base_url=Param('urlbase') bug_id=bug.bug_id +%]
+[%+ PROCESS ics_uid base_url=urlbase bug_id=bug.bug_id +%]
+[%+ PROCESS ics_url base_url=urlbase bug_id=bug.bug_id +%]
 [%+ PROCESS ics_status bug_status = bug.bug_status +%]
 [%+ PROCESS ics_dtstamp +%]
 [% IF bug.changeddate %]
diff --git a/BugsSite/template/en/default/list/list.js.tmpl b/BugsSite/template/en/default/list/list.js.tmpl
index e6bc794..7e9664c 100644
--- a/BugsSite/template/en/default/list/list.js.tmpl
+++ b/BugsSite/template/en/default/list/list.js.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/list/list.rdf.tmpl b/BugsSite/template/en/default/list/list.rdf.tmpl
index 06376ac..873e4ed 100644
--- a/BugsSite/template/en/default/list/list.rdf.tmpl
+++ b/BugsSite/template/en/default/list/list.rdf.tmpl
@@ -1,4 +1,3 @@
-[% template_version = "1.0@bugzilla.org" %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -26,14 +25,14 @@
      xmlns:bz="http://www.bugzilla.org/rdf#"
      xmlns:nc="http://home.netscape.com/NC-rdf#">
 
-<bz:result rdf:about="[% Param('urlbase') %]buglist.cgi?[% urlquerypart FILTER html %]">
-  <bz:installation rdf:resource="[% Param('urlbase') %]" />
+<bz:result rdf:about="[% urlbase FILTER xml %]buglist.cgi?[% urlquerypart FILTER html %]">
+  <bz:installation rdf:resource="[% urlbase FILTER xml %]" />
   <bz:bugs>
     <Seq>
     [% FOREACH bug = bugs %]
       <li>
         
-        <bz:bug rdf:about="[% Param('urlbase') %]show_bug.cgi?id=[% bug.bug_id %]">
+        <bz:bug rdf:about="[% urlbase FILTER xml %]show_bug.cgi?id=[% bug.bug_id %]">
           
           <bz:id nc:parseType="Integer">[% bug.bug_id %]</bz:id>
         
diff --git a/BugsSite/template/en/default/list/quips.html.tmpl b/BugsSite/template/en/default/list/quips.html.tmpl
index df3346a..d6000d5 100644
--- a/BugsSite/template/en/default/list/quips.html.tmpl
+++ b/BugsSite/template/en/default/list/quips.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -125,6 +124,9 @@
               </a>
             </td>
             <td>
+              <input type="hidden" name="defined_quipid_[% quipid FILTER html %]"
+                     id="defined_quipid_[% quipid FILTER html %]"
+                     value="1">
               <input type="checkbox" name="quipid_[% quipid FILTER html %]"
                      id="quipid_[% quipid FILTER html %]"
                      [%- ' checked="checked"' IF quips.$quipid.approved %]>
@@ -148,7 +150,7 @@
                       +'value="Check All" onclick="SetCheckboxes(true);">');
         //--></script>
 
-      <input type="submit" id="update" value="Update">
+      <input type="submit" id="update" value="Save Changes">
     </form>
     <br>
   [% END %]
diff --git a/BugsSite/template/en/default/list/server-push.html.tmpl b/BugsSite/template/en/default/list/server-push.html.tmpl
index be7a63c..d1c157f 100644
--- a/BugsSite/template/en/default/list/server-push.html.tmpl
+++ b/BugsSite/template/en/default/list/server-push.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/list/table.html.tmpl b/BugsSite/template/en/default/list/table.html.tmpl
index 5889936..811ed02 100644
--- a/BugsSite/template/en/default/list/table.html.tmpl
+++ b/BugsSite/template/en/default/list/table.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -54,6 +53,7 @@
     "short_desc"        => { wrap => 1 } ,
     "short_short_desc"  => { maxlength => 60 , ellipsis => "..." , wrap => 1 } ,
     "status_whiteboard" => { title => "Whiteboard" , wrap => 1 } , 
+    "keywords"          => { wrap => 1 } ,
     "component"         => { maxlength => 8 , title => "Comp" } , 
     "product"           => { maxlength => 8 } , 
     "version"           => { maxlength => 5 , title => "Vers" } , 
@@ -81,19 +81,19 @@
       [% END %]
     </colgroup>
 
-    <tr align="left">
+    <tr class="bz_buglist_header bz_first_buglist_header" align="left">
       [% IF dotweak %]
       <th>&nbsp;</th>
       [% END %]
       <th colspan="[% splitheader ? 2 : 1 %]" class="first-child">
-        [% IF sorted_by_relevance %]
-          ID
-        [% ELSE %]
-          <a href="buglist.cgi?
-                    [% urlquerypart FILTER html %]&amp;order=bugs.bug_id
-                    [%-#%]&amp;query_based_on=
-                    [% defaultsavename OR searchname FILTER url_quote %]">ID</a>
+        [% desc = '' %]
+        [% IF (om = order.match("^bugs\.bug_id( desc)?")) %]
+          [% desc = ' desc' IF NOT om.0 %]
         [% END %]
+        <a href="buglist.cgi?
+                  [% urlquerypart FILTER html %]&amp;order=bugs.bug_id[% desc FILTER url_quote %]
+                  [%-#%]&amp;query_based_on=
+                  [% defaultsavename OR searchname FILTER url_quote %]">ID</a>
       </th>
 
       [% IF splitheader %]
@@ -104,7 +104,7 @@
           [% PROCESS columnheader %]
         [% END %]
 
-        </tr><tr align="left">
+        </tr><tr class="bz_buglist_header" align="left">
         [% IF dotweak %]
           <th>&nbsp;</th>
         [% END %]
@@ -130,23 +130,24 @@
 
 [% BLOCK columnheader %]
   <th colspan="[% splitheader ? 2 : 1 %]">
-    [% IF sorted_by_relevance %]
-      [%- abbrev.$id.title || field_descs.$id || column.title -%]
+    [% IF column.name.match('\s+AS\s+') %]
+      [%# For aliased columns, use their ID for sorting. %]
+      [% column.sortalias = id %]
     [% ELSE %]
-      [% 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 %]
-      <a href="buglist.cgi?[% urlquerypart FILTER html %]&amp;order=
-        [% column.sortalias FILTER url_quote %]
-        [% ",$order" FILTER url_quote IF order %]
-        [%-#%]&amp;query_based_on=
-        [% defaultsavename OR searchname FILTER url_quote %]">
-          [%- abbrev.$id.title || field_descs.$id || column.title -%]</a>
+      [%# 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 %]&amp;order=
+      [% column.sortalias FILTER url_quote %][% desc FILTER url_quote %]
+      [% ",$order" FILTER url_quote IF order %]
+      [%-#%]&amp;query_based_on=
+      [% defaultsavename OR searchname FILTER url_quote %]">
+        [%- abbrev.$id.title || field_descs.$id || column.title -%]</a>
   </th>
 [% END %]
 
@@ -194,7 +195,7 @@
                column == 'estimated_time' %]
         [% PROCESS formattimeunit time_unit=bug.$column %] 
       [% ELSIF column == 'bug_status' %]
-        [%- status_descs.${bug.$column}.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html %]
+        [%- 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 %]
       [% ELSE %]
diff --git a/BugsSite/template/en/default/pages/bug-writing.html.tmpl b/BugsSite/template/en/default/pages/bug-writing.html.tmpl
index 6c6f27d..035876bb 100644
--- a/BugsSite/template/en/default/pages/bug-writing.html.tmpl
+++ b/BugsSite/template/en/default/pages/bug-writing.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/pages/fields.html.tmpl b/BugsSite/template/en/default/pages/fields.html.tmpl
index 6b77f8b..90ec2d0 100644
--- a/BugsSite/template/en/default/pages/fields.html.tmpl
+++ b/BugsSite/template/en/default/pages/fields.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -55,45 +54,47 @@
     <td>
       <dl>
         <dt>
-          <b>[% status_descs.UNCONFIRMED FILTER html %]</b>
+          <b>[% get_status("UNCONFIRMED") FILTER html %]</b>
         </dt>
         <dd>
           This [% terms.bug %] has recently been added to the database. 
           Nobody has validated that this [% terms.bug %] is true. Users
           who have the "canconfirm" permission set may confirm
-          this [% terms.bug %], changing its state to [% status_descs.NEW FILTER html %]. Or, it may be
-          directly resolved and marked [% status_descs.RESOLVED FILTER html %].
+          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 %].
         </dd>
 
         <dt>
-          <b>[% status_descs.NEW FILTER html %]</b>
+          <b>[% get_status("NEW") FILTER html %]</b>
         </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>[% status_descs.ASSIGNED FILTER html %]</b>, passed
-          on to someone else, and remain <b>[% status_descs.NEW FILTER html %]</b>, or resolved and marked
-          <b>[% status_descs.RESOLVED FILTER html %]</b>.
+          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>
 
         <dt>
-          <b>[% status_descs.ASSIGNED FILTER html %]</b>
+          <b>[% get_status("ASSIGNED") FILTER html %]</b>
         </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>[% status_descs.NEW FILTER html %]</b>, or resolved and become <b>[% status_descs.RESOLVED FILTER html  %]</b>.
+          person and become <b>[% get_status("NEW") FILTER html %]</b>, or
+          resolved and become <b>[% get_status("RESOLVED") FILTER html %]</b>.
         </dd>
 
         <dt>
-          <b>[% status_descs.REOPENED FILTER html %]</b>
+          <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>[% status_descs.WORKSFORME FILTER html %]</b> [% terms.bug %] is
-          <b>[% status_descs.REOPENED FILTER html %]</b> when more information shows up and
+          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>[% status_descs.ASSIGNED FILTER html %]</b> or <b>[% status_descs.RESOLVED FILTER html %]</b>.
+          either marked <b>[% get_status("ASSIGNED") FILTER html %]</b> or
+          <b>[% get_status("RESOLVED") FILTER html %]</b>.
         </dd>
       </dl>
     </td>
@@ -114,33 +115,34 @@
     <td>
       <dl>
         <dt>
-          <b>[% status_descs.RESOLVED FILTER html %]</b>
+          <b>[% get_status("RESOLVED") FILTER html %]</b>
         </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>[% status_descs.REOPENED FILTER html %]</b>, are marked <b>[% status_descs.VERIFIED FILTER html %]</b>, or are closed for
-          good and marked <b>[% status_descs.CLOSED FILTER html %]</b>.
+          <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>
 
         <dt>
-          <b>[% status_descs.VERIFIED FILTER html %]</b>
+          <b>[% get_status("VERIFIED") FILTER html %]</b>
         </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>[% status_descs.CLOSED FILTER html %]</b>.
+          <b>[% get_status("CLOSED") FILTER html %]</b>.
         </dd>
 
         <dt>
-          <b>[% status_descs.CLOSED FILTER html %]</b>
+          <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>[% status_descs.REOPENED FILTER html %]</b>.
+          do so by becoming <b>[% get_status("REOPENED") FILTER html %]</b>.
         </dd>
       </dl>
     </td>
@@ -204,6 +206,18 @@
   </tr>
 </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><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).
+
 <h2><a name="bug_severity">Severity</a></h2>
 This field describes the impact of [% terms.abug %]. 
 
@@ -227,6 +241,13 @@
   </tr>
 
   <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
@@ -246,14 +267,6 @@
     <td>Request for enhancement</td>
 </table>
 
-<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). 
-
-
 <h2><a name="rep_platform">Platform</a></h2>
 This is the hardware platform against which the [% terms.bug %] was
 reported. Legal platforms include: 
@@ -292,12 +305,13 @@
 
 <p>
 This is the person in charge of resolving the [% terms.bug %]. Every time
-this field changes, the status changes to <b>[% status_descs.NEW FILTER html %]</b> to make it
+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 [% status_descs.NEW FILTER html %], [% descs.bug_status_descs.ASSIGNED FILTER html %] and 
-[% status_descs.REOPENED FILTER html %]. When searching for [% terms.bugs %] that have been resolved or
+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>
 
diff --git a/BugsSite/template/en/default/pages/linked.html.tmpl b/BugsSite/template/en/default/pages/linked.html.tmpl
index bd59330..52b1735 100644
--- a/BugsSite/template/en/default/pages/linked.html.tmpl
+++ b/BugsSite/template/en/default/pages/linked.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -31,7 +30,7 @@
 <hr>
 
 <p>
-<pre>
+<pre class="bz_comment_text">
 [%- cgi.param("text") FILTER wrap_comment FILTER quoteUrls FILTER html -%]
 </pre>
 </p>
@@ -46,7 +45,7 @@
 <hr>
 
 <p>
-<pre>
+<pre class="bz_comment_text">
 [%- cgi.param("text") FILTER wrap_comment FILTER quoteUrls -%]
 </pre>
 </p>
diff --git a/BugsSite/template/en/default/pages/linkify.html.tmpl b/BugsSite/template/en/default/pages/linkify.html.tmpl
index af0a86b..b936e86 100644
--- a/BugsSite/template/en/default/pages/linkify.html.tmpl
+++ b/BugsSite/template/en/default/pages/linkify.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/pages/quicksearch.html.tmpl b/BugsSite/template/en/default/pages/quicksearch.html.tmpl
index b8d412d..d551e7a 100644
--- a/BugsSite/template/en/default/pages/quicksearch.html.tmpl
+++ b/BugsSite/template/en/default/pages/quicksearch.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/pages/quicksearchhack.html.tmpl b/BugsSite/template/en/default/pages/quicksearchhack.html.tmpl
index 821f3ad..4d96f5d 100644
--- a/BugsSite/template/en/default/pages/quicksearchhack.html.tmpl
+++ b/BugsSite/template/en/default/pages/quicksearchhack.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -237,6 +236,36 @@
   <td><tt>kw</tt></td>
   <td><a href="describekeywords.cgi">Keywords</a></td>
 </tr>
+<tr>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+  <td><tt>group</tt></td>
+  <td>&nbsp;</td>
+  <td>Group</td>
+</tr>
+
+<!-- Flags -->
+
+<tr>
+  <td>&nbsp;</td>
+  <td rowspan="2"><i>flag</i><b>?</b><i>requestee</i></td>
+  <td><tt>flag</tt></td>
+  <td>&nbsp;</td>
+  <td>Flag name and status (+, - or ?)</td>
+</tr>
+<tr>
+  <td>&nbsp;</td>
+  <td><tt>requestee</tt></td>
+  <td><tt>req</tt></td>
+  <td>Flag requestee (login)</td>
+</tr>
+<tr>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+  <td><tt>setter</tt></td>
+  <td><tt>set</tt></td>
+  <td>Flag setter (login)</td>
+</tr>
 
 <!-- Attachments -->
 
@@ -343,6 +372,10 @@
   <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>
diff --git a/BugsSite/template/en/default/pages/release-notes.html.tmpl b/BugsSite/template/en/default/pages/release-notes.html.tmpl
index a218603..3280ce6 100644
--- a/BugsSite/template/en/default/pages/release-notes.html.tmpl
+++ b/BugsSite/template/en/default/pages/release-notes.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -20,13 +19,572 @@
 
 [% PROCESS global/variables.none.tmpl %]
 [% INCLUDE global/header.html.tmpl 
-  title = "$terms.Bugzilla 3.0.3 Release Notes" 
+  title = "$terms.Bugzilla 3.2.3 Release Notes" 
   style_urls = ['skins/standard/release-notes.css'] 
 %]
 
 <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><a name="v32_introduction"></a>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><a name="v32_point">Updates in this 3.2.x Release</a></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 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>
+</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. 
+    <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
+  <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><a name="v32_req_perl"></a>Perl</h3>
+
+<p>Perl <span class="req_new">v<strong>5.8.1</strong></span></p>
+
+[% INCLUDE db_req db='mysql' dbd_new = 1 %]
+
+[% INCLUDE db_req db='pg' %]
+
+[% INCLUDE db_req db='oracle' %]
+
+<h3><a name="v32_req_modules"></a>Required Perl Modules</h3>
+
+[% INCLUDE req_table reqs = REQUIRED_MODULES 
+                     new = []
+                     updated = ['Template-Toolkit', 'Email-MIME',
+                                'Email-MIME-Modifier', 'CGI.pm'] %]
+
+<h3><a name="v32_req_optional_mod"></a>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 = []
+                     include_feature = 1 %]
+
+<h2><a name="v32_feat"></a>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><a name="v32_feat_ui"></a>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><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>
+
+<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><a name="v32_feat_install"></a>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><a name="v32_feat_oracle"></a>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><a name="v32_feat_utf8"></a>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><a name="v32_feat_grcons"></a>Group Icons</a></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><a name="v32_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 For Users</h4>
+
+<ul>
+  <li><strong>[% terms.Bugs %]</strong>: You can now reassign
+    [%+ terms.abug %] at the same time as you are changing its status.</li>
+  <li><strong>[% terms.Bugs %]</strong>: When entering [% terms.abug %], 
+    you will now see the description of a component when you select it.</li>
+  <li><strong>[% terms.Bugs %]</strong>: The [% terms.bug %] view now 
+    contains some <a href="http://microformats.org/about/">Microformats</a>,
+    most notably for users' names and email addresses.</li>
+  <li><strong>[% terms.Bugs %]</strong>: You can now remove a QA Contact
+    from [% terms.abug %] simply by clearing the QA Contact field.</li>
+  <li><strong>[% terms.Bugs %]</strong>: There is now a user preference 
+    that will allow you to exclude the quoted text when replying 
+    to comments.</li>
+  <li><strong>[% terms.Bugs %]</strong>: You can now expand or collapse
+    individual comments in the [% terms.bug %] view.</li>
+
+  <li><strong>Attachments</strong>: There is now "mid-air collision" 
+      protection when editing attachments.</li>
+  <li><strong>Attachments</strong>: Patches in the Diff Viewer now show 
+    line numbers (<a href="https://bugzilla.mozilla.org/attachment.cgi?id=327546">Example</a>).</li>
+  <li><strong>Attachments</strong>: After creating or updating an attachment,
+    you will be immediately shown the [% terms.bug %] that the attachment
+    is on.</li>
+
+  <li><strong>Search</strong>: You can now reverse the sort of 
+    [%+ terms.abug %] list by clicking on a column header again.</li>
+  <li><strong>Search</strong>: Atom feeds of [% terms.bug %] lists now
+    contain more fields.</li>
+  <li><strong>Search</strong>: QuickSearch now supports searching flags
+    and groups. It also now includes the OS field in the list of fields
+    it searches by default.</li>
+  <li><strong>Search</strong>: "Help" text can now appear on query.cgi 
+    for Internet Explorer and other non-Firefox browsers. (It always
+    could appear for Firefox.)</li>
+
+  <li>[% terms.Bugzilla %] now ships with an icon that will show
+    up next to the URL in most browsers. If you want to replace it,
+    it's in <kbd>images/favicon.ico</kbd>.</li>
+
+  <li>You can now set the Deadline when using "Change Several 
+    [%+ terms.Bugs %] At Once"</li>
+  <li><strong>Saved Searches</strong> now save their column list, so if 
+    you customize the list of columns and save your search, it will 
+    always contain those columns.</li>
+  <li><strong>Saved Searches</strong>: When you share a search, you can
+    now see how many users have subscribed to it, on 
+    <kbd>userprefs.cgi</kbd>.</li>
+  <li><strong>Saved Searches</strong>: You can now see what group a 
+    shared search was shared to, on the list of available shared searches
+    in <kbd>userprefs.cgi</kbd>.</li>
+  <li><strong>Flags</strong>: If your installation uses drop-down user 
+    lists, the flag requestee box will now contain only users who are 
+    actually allowed to take requests.</li>
+  <li><strong>Flags</strong>: If somebody makes a request to you, and you
+    change the requestee to somebody else, the requester is no longer set
+    to you. In other words, you can "redirect" requests and maintain the
+    original requester.</li>
+  <li><strong>Flags</strong>: Emails about flags now will thread properly
+    in email clients to be a part of [% terms.abug %]'s thread.</li>
+  <li>When using <kbd>email_in.pl</kbd>, you can now add users to the CC
+    list by just using <kbd>@cc</kbd> as the field name.</li>
+  <li>Many pages (particularly administrative pages) now contain links to
+    the relevant section of the [% terms.Bugzilla %] Guide, so you can read 
+    the documentation for that page.</li>
+  <li>Dependency Graphs should render more quickly, as they now (by default)
+    only include the same [% terms.bugs %] that you'd see in the dependency
+    tree.</li>
+</ul>
+
+<h4>Enhancements For Administrators</h4>
+
+<ul>
+  <li><strong>Admin UI</strong>: Instead of having the Administration 
+    Control Panel links in the footer, there is now just one link called 
+   "Administration" that takes you to a page that links to all the
+    administrative controls for [% terms.Bugzilla %].</li>
+  <li><strong>Admin UI</strong>: Administrative pages no longer display
+    confirmation pages, instead they redirect you to some useful page 
+    and display a message about what changed.</li>
+  <li><strong>Admin UI</strong>: The interface for editing group 
+     inheritance in <kbd>editgroups.cgi</kbd> is much clearer now.</li>
+  <li><strong>Admin UI</strong>: When editing a user, you can now see 
+    all the components where that user is the Default Assignee or Default
+    QA Contact.</li>
+
+  <li><strong>Email</strong>: For installations that use SMTP to send 
+    mail (as opposed to Sendmail), [%+ terms.Bugzilla %] now supports
+    SMTP Authentication, so that it can log in to your mail server 
+    before sending messages.</li>
+  <li><strong>Email</strong>: Using the "Test" mail delivery method now
+     creates a valid mbox file to make testing easier.</li>
+
+  <li><strong>Authentication</strong>: [% terms.Bugzilla %] now correctly 
+    handles LDAP records which contain multiple email addresses. (The first
+    email address in the list that is a valid [% terms.Bugzilla %] account 
+    will be used, or if this is a new user, the first email address in 
+    the list will be used.)</li>
+  <li><strong>Authentication</strong>: [% terms.Bugzilla %] can now take 
+    a list of LDAP servers to try in order until it gets a successful
+    connection.</li>
+  <li><strong>Authentication</strong>: [% terms.Bugzilla %] now supports 
+    RADIUS authentication.</li>
+
+  <li><strong>Security</strong>: The login cookie is now created as 
+    "HTTPOnly" so that it can't be read by possibly malicious scripts. 
+    Also, if SSL is enabled on your installation, the login cookie is
+    now only sent over SSL connections.</li>
+  <li><strong>Security</strong>: The <code>ssl</code> parameter now protects
+    every page a logged-in user accesses, when set to "authenticated sessions."
+    Also, SSL is now enforced appropriately in the WebServices interface when
+    the parameter is set.</li>
+
+  <li><strong>Database</strong>: [% terms.Bugzilla %] now uses transactions in 
+    the database instead of table locks. This should generally improve
+    performance with many concurrent users. It also means if there is
+    an unexpected error in the middle of a page, all database changes made
+    during that page will be rolled back.</li>
+  <li><strong>Database</strong>: You no longer have to set 
+    <code>max_packet_size</code> in MySQL to add large attachments. However,
+    you may need to set it manually if you restore a mysqldump into your
+    database.</li>
+
+  <li>New WebService functions:
+    <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bug.html">B<!-- -->ug.add_comment</a>
+    and <a href="[% docs_urlbase FILTER html %]api/Bugzilla/WebService/Bugzilla.html">Bugzilla.extensions</a>.</li>
+
+  <li>You can now delete custom fields, but only if they have never been
+    set on any [% terms.bug %].</li>
+  <li>There is now a <kbd>--reset-password</kbd> argument to 
+    <kbd>checksetup.pl</kbd> that allows you to reset a user's password
+    from the command line.</li>
+  <li>There is now a script called <kbd>sanitycheck.pl</kbd> that you can
+    run from the command line. It works just like <kbd>sanitycheck.cgi</kbd>.
+    By default, it only outputs anything if there's an error, so it's
+    ideal for administrators who want to run it nightly in a cron job.</li>
+  <li>The <kbd>strict_isolation</kbd> parameter now prevents you from setting
+    users who cannot see [% terms.abug %] as a CC, Assignee, or QA
+    Contact. Previously it only prevented you from adding users who
+    could not <em>edit</em> the [% terms.bug %].</li>
+  <li>Extensions can now add their own headers to the HTML &lt;head&gt;
+    for things like custom CSS and so on.</li>
+  <li><kbd>sanitycheck.cgi</kbd> has been templatized, meaning that the
+    entire [% terms.Bugzilla %] UI is now contained in templates.</li>
+  <li>When setting the <kbd>sslbase</kbd> parameter, you can now specify
+    a port number in the URL.</li>
+  <li>When importing [% terms.bugs %] using <kbd>importxml.pl</kbd>,
+    attachments will have their actual creator set as their creator,
+    instead of the person who exported the [% terms.bug %] from the other
+    system.</li>
+  <li>The voting system is off by default in new installs. This is to
+    prepare for the fact that it will be moved into an extension at
+    some point in the future.</li>
+  <li>The <code>shutdownhtml</code> parameter now works even when 
+    [% terms.Bugzilla %]'s database server is down.</li>
+</ul>
+
+<h3>Enhancements for Localizers (or Localized Installations)</h3>
+
+<ul>
+  <li>The documentation can now be localized--in other words, you can have
+    documentation installed for multiple languages at once and
+    [%+ terms.Bugzilla %] will link to the correct language in its internal
+    documentation links.</li>
+  <li>[% terms.Bugzilla %] no longer uses the <kbd>languages</kbd> parameter.
+    Instead it reads the <kbd>template/</kbd> directory to see which
+    languages are available.</li>
+  <li>Some of the messages printed by <kbd>checksetup.pl</kbd> can now
+    be localized. See <kbd>template/en/default/setup/strings.txt.pl</kbd>.
+</ul>
+
+<h2><a name="v32_issues"></a>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><a name="v32_upgrading"></a>How to Upgrade From An Older Version</h2>
+
+<h3><a name="v32_upgrading_notes"></a>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><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>[&#37;# version = 1.0 &#37;]</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>
@@ -51,7 +609,7 @@
   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 [% terms.Bugzilla %] 3.0.</p>
+  and this one.</p>
 
 <h2><a name="v30_point">Updates in this 3.0.x Release</a></h2>
 
@@ -61,6 +619,91 @@
   <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>
@@ -112,7 +755,7 @@
 
 <ul>
   <li>For users of Firefox 2, the <code>show_bug.cgi</code> user interface
-    should no longer "collapse" after you modify a [% terms.bug %].
+    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
@@ -132,7 +775,7 @@
     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 a [% terms.bug %] between products would remove groups
+  <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
@@ -209,27 +852,156 @@
   <li>Perl v<strong>5.8.1</strong> (Windows platforms)</li>
 </ul>
 
-[% PROCESS db_req db='mysql' %]
+<h3><a name="v30_req_mysql"></a>For MySQL Users</h3>
 
-[% PROCESS db_req db='pg' %]
+<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>
 
-[% INCLUDE req_table reqs = REQUIRED_MODULES 
-                     new = ['Email-MIME-Modifier', 'Email-Send']
-                     updated = ['DBI'] %]
+<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>
 
-[% INCLUDE req_table reqs = OPTIONAL_MODULES
-                     new  = ['libwww-perl', 'SOAP-Lite', 'mod_perl',
-                             'Apache-DBI', 'Email-MIME-Attachment-Stripper',
-                             'Email-Reply']
-                     updated = ['CGI']
-                     include_feature = 1 %]
+<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>
 
@@ -354,14 +1126,14 @@
   to the <kbd>xmlrpc.cgi</kbd> on your installation.</p>
 
 <p>Documentation can be found in the 
-  <a href="[% Param('docs_urlbase') FILTER html %]api/">[% terms.Bugzilla %] 
+  <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 &quot;skins&quot; installed,
   and users can pick between them. To write a skin, you just have to
-  write several CSS files. See the <a href="[% Param('docs_urlbase') FILTER html %]cust-skins.html">Custom
+  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
@@ -415,7 +1187,7 @@
   unsupported add-on, but it is now an official interface to
   [%+ terms.Bugzilla %].</p>
 
-<p>For more details see the <a href="[% Param('docs_urlbase') FILTER html %]api/email_in.html">documentation
+<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 %] 
@@ -425,7 +1197,7 @@
   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 a [% terms.bug %]
+<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>
@@ -482,7 +1254,7 @@
     in your user preferences.</li>
   <li>You can hide obsolete attachments on [% terms.abug %] by clicking
     &quot;Hide Obsolete&quot; at the bottom of the attachment table.</li>
-  <li>If a [% terms.bug %] has flags set, and you move it to a different 
+  <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
@@ -517,7 +1289,7 @@
   <li>When viewing [% terms.bug %] activity, fields that hold [% terms.bug %] 
     numbers (such as &quot;Blocks&quot;) will have the [% terms.bug %] numbers
     displayed as links to those [% terms.bugs %].</li>
-  <li>When viewing the &quot;Keywords&quot; field in a [% terms.bug %] list,
+  <li>When viewing the &quot;Keywords&quot; field in [% terms.abug %] list,
     it will be sorted alphabetically, so you can sanely sort a list on
     that field.</li>
   <li>In most places, the Version field is now sorted using a version-sort
@@ -612,6 +1384,25 @@
 
 <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>
@@ -633,7 +1424,7 @@
 
 <h2><a name="v30_upgrading"></a>How to Upgrade From An Older Version</h2>
 
-<h3>Notes For Upgraders</h3>
+<h3><a name="v30_upgrading_notes"></a>Notes For Upgraders</h3>
 
 <ul>
   <li>If you upgrade by CVS, there are several .cvsignore files
@@ -662,48 +1453,9 @@
 
 <h3>Steps For Upgrading</h3>
 
-<ol>
-  <li>Read these entire Release Notes, particularly the &quot;Notes for
-    Upgraders&quot; section above.</li>
-
-  <li>View the <a href="sanitycheck.cgi">Sanity Check</a> page on your
-   installation before upgrading. Attempt to fix all warnings that 
-   the page produces before you go any further, or you may experience 
-   problems during your upgrade.</li>
-
-  <li>Make a backup of the [% terms.Bugzilla %] database before you upgrade,
-   perhaps by using <kbd>mysqldump</kbd>. <strong>THIS IS VERY 
-   IMPORTANT</strong>. If anything goes wrong during the upgrade, your
-   installation can be corrupted beyond recovery. Having a backup keeps you
-   safe.
-
-    <p>Example: <kbd>mysqldump -u root -p bu[%# trick filter %]gs &gt; 
-      bu[%# trick filter %]gs-db.sql</kbd></p></li>
-
-  <li>Replace the files in your installation with the new version of 
-   [% terms.Bugzilla %], or you can try to use CVS to upgrade.
-
-   <p>You can also use a brand-new [% terms.Bugzilla %] directory, as long 
-     as you copy over the old <kbd>data/</kbd> directory and the 
-     <kbd>localconfig</kbd> file to the new installation.</p></li>
-
-  <li>Now follow the standard 
-    <a href="[% Param('docs_urlbase') FILTER html %]installing-bugzilla.html">
-    [%- terms.Bugzilla %] installation process</a>.</li>
-
-  <li>Run <kbd>checksetup.pl</kbd> after you install the new version.</li>
-
-  <li>View the <a href="sanitycheck.cgi">Sanity Check</a> page again after 
-    you run <kbd>checksetup.pl</kbd>.</li>
-
-  <li>It is recommended that, if possible, you fix any problems you find
-   immediately. Failure to do this may mean that [% terms.Bugzilla %] will 
-   not work correctly. Be aware that if the sanity check page contains more
-   errors after an upgrade, it doesn't necessarily mean there are more 
-   errors in your database than there were before, as additional tests 
-   are added to the sanity check over time, and it is possible that those
-   errors weren't being checked for in the old version.</li>
-</ol>
+<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>
@@ -740,7 +1492,7 @@
 
 <p>[% terms.Bugzilla %] now supports a code hook mechanism. See the 
   documentation for 
-  <a href="[% Param('docs_urlbase') FILTER html %]api/Bugzilla/Hook.html">Bugzilla::Hook</a>
+  <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
@@ -757,7 +1509,7 @@
 
 <p>[% terms.Bugzilla %] now ships with all of its perldoc built
   as HTML. Go ahead and read the
-  <a href="[% Param('docs_urlbase') FILTER html %]api/">API Documentation</a>
+  <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>
 
@@ -800,7 +1552,7 @@
 <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="[% Param('docs_urlbase') FILTER html %]api/Bugzilla/Auth.html">Bugzilla::Auth
+  <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>
@@ -808,7 +1560,7 @@
 <h3><a name="v30_code_obj"></a>Bugzilla::Object</h3>
 
 <p>There is a new base class for most of our objects, 
-  <a href="[% Param('docs_urlbase') FILTER html %]api/Bugzilla/Object.html">Bugzilla::Object</a>.
+  <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>
 
@@ -833,7 +1585,7 @@
   <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="[% Param('docs_urlbase') FILTER html %]api/checksetup.html">checksetup
+    <a href="[% docs_urlbase FILTER html %]api/checksetup.html">checksetup
     documentation</a> and <a href="https://bugzilla.mozilla.org/showdependencytree.cgi?id=277502&amp;hide_resolved=0">[% terms.Bugzilla %]
    [%+ terms.bug %] 277502</a> for details.</li>
   <li>Instead of <kbd>UserInGroup()</kbd>, all of [% terms.Bugzilla %] now 
@@ -857,7 +1609,7 @@
     <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 a [% terms.bug %] object.</li>
+    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>
@@ -867,20 +1619,25 @@
 
 <p>Release notes for versions of [% terms.Bugzilla %] for versions
   prior to 3.0 are only available in text format: 
-  <a href="docs/rel_notes.txt">Release Notes for [% terms.Bugzilla %] 2.22
+  <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><a name="v30_req_[% db FILTER html %]"></a>For [% m.name FILTER html %] 
+  <h3><a name="v32_req_[% db FILTER html %]"></a>For [% m.name FILTER html %] 
     Users</h3>
 
   <ul>
-    <li>[% m.name FILTER html %] v[% m.db_version FILTER html %]</li>
+    <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 %] v[% m.dbd.version FILTER html %]</li>
+      [%+ 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 %]
 
diff --git a/BugsSite/template/en/default/pages/sudo.html.tmpl b/BugsSite/template/en/default/pages/sudo.html.tmpl
index ca57a81..dff2d7d 100644
--- a/BugsSite/template/en/default/pages/sudo.html.tmpl
+++ b/BugsSite/template/en/default/pages/sudo.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/pages/voting.html.tmpl b/BugsSite/template/en/default/pages/voting.html.tmpl
index bec38b4..4e6fb47 100644
--- a/BugsSite/template/en/default/pages/voting.html.tmpl
+++ b/BugsSite/template/en/default/pages/voting.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -52,9 +51,9 @@
 <ul>
   <li>Bring up the [% terms.bug %] in question.</li>
 
-  <li>Click on the "Vote for this [% terms.bug %]" link that appears just 
-  above the "Additional Comments" field. (If no such link appears, then voting 
-  may not be allowed in this [% terms.bug %]'s product.)</li>
+  <li>Click on the "(vote)" link that appears on the right of the "Importance"
+  fields. (If no such link appears, then voting may not be allowed in
+  this [% terms.bug %]'s product.)</li>
 
   <li>Indicate how many votes you want to give this [% terms.bug %]. This page 
   also displays how many votes you've given to other [% terms.bugs %], so you 
diff --git a/BugsSite/template/en/default/reports/chart.csv.tmpl b/BugsSite/template/en/default/reports/chart.csv.tmpl
index 87866df..f9e2f2b 100644
--- a/BugsSite/template/en/default/reports/chart.csv.tmpl
+++ b/BugsSite/template/en/default/reports/chart.csv.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/chart.html.tmpl b/BugsSite/template/en/default/reports/chart.html.tmpl
index 821b6b0..06a8d79 100644
--- a/BugsSite/template/en/default/reports/chart.html.tmpl
+++ b/BugsSite/template/en/default/reports/chart.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/chart.png.tmpl b/BugsSite/template/en/default/reports/chart.png.tmpl
index a3933e4..c4fa04f 100644
--- a/BugsSite/template/en/default/reports/chart.png.tmpl
+++ b/BugsSite/template/en/default/reports/chart.png.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/components.html.tmpl b/BugsSite/template/en/default/reports/components.html.tmpl
index d135a7e..351c7d0 100644
--- a/BugsSite/template/en/default/reports/components.html.tmpl
+++ b/BugsSite/template/en/default/reports/components.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/create-chart.html.tmpl b/BugsSite/template/en/default/reports/create-chart.html.tmpl
index 7001d2c..d911d03 100644
--- a/BugsSite/template/en/default/reports/create-chart.html.tmpl
+++ b/BugsSite/template/en/default/reports/create-chart.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/duplicates-simple.html.tmpl b/BugsSite/template/en/default/reports/duplicates-simple.html.tmpl
index a92f3c1..61d0c6f 100644
--- a/BugsSite/template/en/default/reports/duplicates-simple.html.tmpl
+++ b/BugsSite/template/en/default/reports/duplicates-simple.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/duplicates-table.html.tmpl b/BugsSite/template/en/default/reports/duplicates-table.html.tmpl
index eb7eec1..5dbef21 100644
--- a/BugsSite/template/en/default/reports/duplicates-table.html.tmpl
+++ b/BugsSite/template/en/default/reports/duplicates-table.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/duplicates.html.tmpl b/BugsSite/template/en/default/reports/duplicates.html.tmpl
index f60f285..e4ea738 100644
--- a/BugsSite/template/en/default/reports/duplicates.html.tmpl
+++ b/BugsSite/template/en/default/reports/duplicates.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -72,13 +71,13 @@
           work with:</td>
       <td>
         <input type="radio" name="sortvisible" id="entirelist" value="0"
-          [% "checked" IF NOT sortvisible %]>
+          [%+ "checked" IF NOT sortvisible %]>
         <label for="entirelist">
           entire list
         </label>
         <br>
         <input type="radio" name="sortvisible" id="visiblelist" value="1"
-          [% "checked" IF sortvisible %]>
+          [%+ "checked" IF sortvisible %]>
         <label for="visiblelist">
           currently visible list
         </label>
@@ -117,7 +116,7 @@
       </td>
       <td>
         <input type="checkbox" name="openonly" id="openonly" value="1"
-          [% "checked" IF openonly %]>
+          [%+ "checked" IF openonly %]>
       </td>
     </tr>
 
diff --git a/BugsSite/template/en/default/reports/duplicates.rdf.tmpl b/BugsSite/template/en/default/reports/duplicates.rdf.tmpl
deleted file mode 100644
index 15594c7..0000000
--- a/BugsSite/template/en/default/reports/duplicates.rdf.tmpl
+++ /dev/null
@@ -1,51 +0,0 @@
-[% template_version = "1.0@bugzilla.org" %]
-[%# The contents of this file are subject to the Mozilla Public
-  # License Version 1.1 (the "License"); you may not use this file
-  # except in compliance with the License. You may obtain a copy of
-  # the License at http://www.mozilla.org/MPL/
-  #
-  # Software distributed under the License is distributed on an "AS
-  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
-  # implied. See the License for the specific language governing
-  # rights and limitations under the License.
-  #
-  # The Original Code is the Bugzilla Bug Tracking System.
-  #
-  # The Initial Developer of the Original Code is Netscape Communications
-  # Corporation. Portions created by Netscape are
-  # Copyright (C) 1998 Netscape Communications Corporation. All
-  # Rights Reserved.
-  #
-  # Contributor(s): Myk Melez <myk@mozilla.org>
-  #%]
-
-<?xml version="1.0"[% IF Param('utf8') %] encoding="UTF-8"[% END %]?>
-<!-- [% template_version %] -->
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:bz="http://www.bugzilla.org/rdf#"
-     xmlns:nc="http://home.netscape.com/NC-rdf#">
-
-<bz:duplicates_report rdf:about="[% Param('urlbase') %]data/duplicates.rdf">
-  <bz:bugs>
-    <Seq>
-      [% FOREACH bug = bugs %]
-        <li>
-          <bz:bug rdf:about="[% Param('urlbase') %]show_bug.cgi?id=[% bug.id %]">
-            <bz:id nc:parseType="Integer">[% bug.id %]</bz:id>
-            <bz:resolution>[% bug.resolution FILTER html %]</bz:resolution>
-            <bz:duplicate_count nc:parseType="Integer">[% bug.count %]</bz:duplicate_count>
-            <bz:duplicate_delta nc:parseType="Integer">[% bug.delta %]</bz:duplicate_delta>
-            <bz:component>[% bug.component FILTER html %]</bz:component>
-            <bz:severity>[% bug.bug_severity FILTER html %]</bz:severity>
-            <bz:os>[% bug.op_sys FILTER html %]</bz:os>
-            <bz:target_milestone>[% bug.target_milestone FILTER html %]</bz:target_milestone>
-            <bz:summary>[% bug.short_desc FILTER html %]</bz:summary>
-          </bz:bug>
-        </li>
-      [% END %]
-    </Seq>
-  </bz:bugs>
-</bz:duplicates_report>
-
-</RDF>
diff --git a/BugsSite/template/en/default/reports/edit-series.html.tmpl b/BugsSite/template/en/default/reports/edit-series.html.tmpl
index 690e243..7fbdcbd 100644
--- a/BugsSite/template/en/default/reports/edit-series.html.tmpl
+++ b/BugsSite/template/en/default/reports/edit-series.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/keywords.html.tmpl b/BugsSite/template/en/default/reports/keywords.html.tmpl
index 1a0ae0b..10e6573 100644
--- a/BugsSite/template/en/default/reports/keywords.html.tmpl
+++ b/BugsSite/template/en/default/reports/keywords.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/menu.html.tmpl b/BugsSite/template/en/default/reports/menu.html.tmpl
index f5c18be..db5b192 100644
--- a/BugsSite/template/en/default/reports/menu.html.tmpl
+++ b/BugsSite/template/en/default/reports/menu.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -28,6 +27,7 @@
 
 [% PROCESS global/header.html.tmpl
   title = "Reporting and Charting Kitchen"
+  doc_section = "reporting.html"
 %]
 
 <p>
diff --git a/BugsSite/template/en/default/reports/old-charts.html.tmpl b/BugsSite/template/en/default/reports/old-charts.html.tmpl
index 724dba6..ca3ba6c 100644
--- a/BugsSite/template/en/default/reports/old-charts.html.tmpl
+++ b/BugsSite/template/en/default/reports/old-charts.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -22,8 +21,11 @@
 
 [% PROCESS "global/field-descs.none.tmpl" %]
 
-[% PROCESS global/header.html.tmpl title = "$terms.Bug Charts"
-                                   h1 = "Welcome to the $terms.Bugzilla Charting Kitchen" %]
+[% PROCESS global/header.html.tmpl
+  title = "$terms.Bug Charts"
+  h1 = "Welcome to the $terms.Bugzilla Charting Kitchen"
+  doc_section = "reporting.html#charts"
+%]
 
 <div align="center">
   [% IF url_image %]
diff --git a/BugsSite/template/en/default/reports/report-bar.png.tmpl b/BugsSite/template/en/default/reports/report-bar.png.tmpl
index 2c29a35..74e2ca3 100644
--- a/BugsSite/template/en/default/reports/report-bar.png.tmpl
+++ b/BugsSite/template/en/default/reports/report-bar.png.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -29,7 +28,7 @@
 
 [% IF col_field == 'bug_status' %]
   [% FOR i IN [ 0 .. data.0.0.max ] %]
-    [% data.0.0.$i = status_descs.${data.0.0.$i} %]
+    [% data.0.0.$i = get_status(data.0.0.$i) %]
   [% END %]
 [% END %]
 
@@ -41,7 +40,7 @@
 
 [% IF row_field == 'bug_status' %]
   [% FOR i IN [ 0 .. row_names.max ] %]
-    [% row_names.$i = status_descs.${row_names.$i} %]
+    [% row_names.$i = get_status(row_names.$i) %]
   [% END %]
 [% END %]
 
diff --git a/BugsSite/template/en/default/reports/report-line.png.tmpl b/BugsSite/template/en/default/reports/report-line.png.tmpl
index 24215af..d4982bc 100644
--- a/BugsSite/template/en/default/reports/report-line.png.tmpl
+++ b/BugsSite/template/en/default/reports/report-line.png.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -29,7 +28,7 @@
 
 [% IF col_field == 'bug_status' %]
   [% FOR i IN [ 0 .. data.0.0.max ] %]
-    [% data.0.0.$i = status_descs.${data.0.0.$i} %]
+    [% data.0.0.$i = get_status(data.0.0.$i) %]
   [% END %]
 [% END %]
 
@@ -41,7 +40,7 @@
 
 [% IF row_field == 'bug_status' %]
   [% FOR i IN [ 0 .. row_names.max ] %]
-    [% row_names.$i = status_descs.${row_names.$i} %]
+    [% row_names.$i = get_status(row_names.$i) %]
   [% END %]
 [% END %]
 
diff --git a/BugsSite/template/en/default/reports/report-pie.png.tmpl b/BugsSite/template/en/default/reports/report-pie.png.tmpl
index 3eb73b1..342d9b7 100644
--- a/BugsSite/template/en/default/reports/report-pie.png.tmpl
+++ b/BugsSite/template/en/default/reports/report-pie.png.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -25,7 +24,7 @@
 
 [% IF col_field == 'bug_status' %]
   [% FOR i IN [ 0 .. data.0.0.max ] %]
-    [% data.0.0.$i = status_descs.${data.0.0.$i} %]
+    [% data.0.0.$i = get_status(data.0.0.$i) %]
   [% END %]
 [% END %]
 
diff --git a/BugsSite/template/en/default/reports/report-simple.html.tmpl b/BugsSite/template/en/default/reports/report-simple.html.tmpl
index 9ef8638..d358109 100644
--- a/BugsSite/template/en/default/reports/report-simple.html.tmpl
+++ b/BugsSite/template/en/default/reports/report-simple.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/report-table.csv.tmpl b/BugsSite/template/en/default/reports/report-table.csv.tmpl
index 2f7f867..cf37749 100644
--- a/BugsSite/template/en/default/reports/report-table.csv.tmpl
+++ b/BugsSite/template/en/default/reports/report-table.csv.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -42,7 +41,7 @@
   [% FOREACH col = col_names -%]
     [% colsepchar %]
     [% IF col_field == 'bug_status' %]
-      [% status_descs.$col FILTER csv -%]
+      [% get_status(col) FILTER csv -%]
     [% ELSIF col_field == 'resolution' %]
       [% get_resolution(col) FILTER csv -%]
     [% ELSE %]
@@ -55,7 +54,7 @@
 
 [% FOREACH row = row_names %]
   [% IF row_field == 'bug_status' %]
-    [% status_descs.$row FILTER csv -%]
+    [% get_status(row) FILTER csv -%]
   [% ELSIF row_field == 'resolution' %]
     [% get_resolution(row) FILTER csv -%]
   [% ELSE %]
diff --git a/BugsSite/template/en/default/reports/report-table.html.tmpl b/BugsSite/template/en/default/reports/report-table.html.tmpl
index 0456868..0ebe631 100644
--- a/BugsSite/template/en/default/reports/report-table.html.tmpl
+++ b/BugsSite/template/en/default/reports/report-table.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -81,7 +80,7 @@
         [% col_idx = 1 - col_idx %]
         <td class="[% classes.$row_idx.$col_idx %]">
           [% IF col_field == 'bug_status' %]
-            [% status_descs.$col FILTER html FILTER replace('^ $','&nbsp;') %]
+            [% get_status(col) FILTER html FILTER replace('^ $','&nbsp;') %]
           [% ELSIF col_field == 'resolution' %]
             [% get_resolution(col) FILTER html FILTER replace('^ $','&nbsp;') %]
           [% ELSE %]
@@ -102,7 +101,7 @@
     <tr>
       <td class="[% classes.$row_idx.$col_idx %]" align="right">
         [% IF row_field == 'bug_status' %]
-          [% status_descs.$row FILTER html FILTER replace('^ $','&nbsp;') %]
+          [% get_status(row) FILTER html FILTER replace('^ $','&nbsp;') %]
         [% ELSIF row_field == 'resolution' %]
           [% get_resolution(row) FILTER html FILTER replace('^ $','&nbsp;') %]
         [% ELSE %]
diff --git a/BugsSite/template/en/default/reports/report.csv.tmpl b/BugsSite/template/en/default/reports/report.csv.tmpl
index 6c3ad46..f26bc1f 100644
--- a/BugsSite/template/en/default/reports/report.csv.tmpl
+++ b/BugsSite/template/en/default/reports/report.csv.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/report.html.tmpl b/BugsSite/template/en/default/reports/report.html.tmpl
index 06acaf4..b8e5221 100644
--- a/BugsSite/template/en/default/reports/report.html.tmpl
+++ b/BugsSite/template/en/default/reports/report.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/series-common.html.tmpl b/BugsSite/template/en/default/reports/series-common.html.tmpl
index 42bf0b7..35586cb 100644
--- a/BugsSite/template/en/default/reports/series-common.html.tmpl
+++ b/BugsSite/template/en/default/reports/series-common.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/reports/series.html.tmpl b/BugsSite/template/en/default/reports/series.html.tmpl
index 966c499..3cf9390 100644
--- a/BugsSite/template/en/default/reports/series.html.tmpl
+++ b/BugsSite/template/en/default/reports/series.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -70,7 +69,7 @@
             completely, if you want to change who can make series public. %]
         [% IF user.in_group('admin') %]      
           <input type="checkbox" name="public"
-                 [% "checked='checked'" IF default.public.0 %]>
+                 [%+ "checked='checked'" IF default.public.0 %]>
           <span style="font-weight: bold;">Visible to all<br>
           (within group restrictions)</span> 
         [% END %]
diff --git a/BugsSite/template/en/default/request/email.txt.tmpl b/BugsSite/template/en/default/request/email.txt.tmpl
index 7010f01..81948c4 100644
--- a/BugsSite/template/en/default/request/email.txt.tmpl
+++ b/BugsSite/template/en/default/request/email.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -29,9 +28,15 @@
                 '?' => "asked" } %]
 
 [% to_identity = "" %]
+[% on_behalf_of = 0 %]
 [% IF flag.status == '?' %]
-  [% to_identity = flag.requestee.identity _ " for" %]
   [% subject_status = "requested" %]
+  [% IF flag.setter.id == user.id %]
+    [% to_identity = flag.requestee.identity _ " for" %]
+  [% ELSE %]
+    [% on_behalf_of = 1 %]
+    [% IF flag.requestee %][% to_identity = " to " _ flag.requestee.identity %][% END %]
+  [% END %]
 [% ELSE %]
   [% IF flag.requester %]
     [% to_identity = flag.requester.identity _ "'s request for" %]
@@ -44,21 +49,27 @@
 [%- IF attachment %] :
   [Attachment [% attachment.id %]] [% attachment.description %][% END %]
 X-Bugzilla-Type: request
+[%+ threadingmarker %]
 
 [%+ USE wrap -%]
 [%- FILTER bullet = wrap(80) -%]
 
+[% IF on_behalf_of %]
+[% user.identity %] has reassigned [% flag.setter.identity %]'s request for [% flag.type.name %]
+[% to_identity %]:
+[% ELSE %]
 [% user.identity %] has [% statuses.${flag.status} %] [%+ to_identity %] [%+ flag.type.name %]:
+[% END %]
 
 [% terms.Bug %] [%+ bugidsummary %]
 [% END %]
-[%+ Param('urlbase') %]show_bug.cgi?id=[% bug.bug_id %]
+[%+ urlbase %]show_bug.cgi?id=[% bug.bug_id %]
 [% IF attachment %]
 
 [% FILTER bullet = wrap(80) %]
 Attachment [% attidsummary %]
 [%- END %]
-[%+ Param('urlbase') %]attachment.cgi?id=[% attachment.id %]&action=edit
+[%+ urlbase %]attachment.cgi?id=[% attachment.id %]&action=edit
 [%- END %]
 [%- FILTER bullet = wrap(80) %]
 
diff --git a/BugsSite/template/en/default/request/queue.html.tmpl b/BugsSite/template/en/default/request/queue.html.tmpl
index c80ecae..af911b2 100644
--- a/BugsSite/template/en/default/request/queue.html.tmpl
+++ b/BugsSite/template/en/default/request/queue.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -32,8 +31,8 @@
     table.requests th { text-align: left; }
     table#filtering th { text-align: right; }
   "
-  onload="selectProduct(document.forms[0], 'product', 'component', 'Any');"
-  javascript_urls=["productmenu.js"]
+  onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, 'Any');"
+  javascript_urls=["js/productform.js"]
 %]
 
 <p>
@@ -53,7 +52,7 @@
            title="Requester's email address"></td>
       <th>Product:</th>
       <td>
-        <select name="product" onchange="selectProduct(this.form, 'product', 'component', 'Any');">
+        <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 %]"
@@ -90,11 +89,9 @@
       <td>
         <select name="component">
           <option value="">Any</option>
-          [% FOREACH prod = products %]
-            [% FOREACH comp = prod.components %]
-              <option value="[% comp.name FILTER html %]" [% "selected" IF cgi.param('component') == comp.name %]>
-                [% comp.name FILTER html %]</option>
-            [% END %]
+          [% FOREACH comp = components %]
+            <option value="[% comp FILTER html %]" [% "selected" IF cgi.param('component') == comp %]>
+              [% comp FILTER html %]</option>
           [% END %]
         </select>
       </td>
diff --git a/BugsSite/template/en/default/search/boolean-charts.html.tmpl b/BugsSite/template/en/default/search/boolean-charts.html.tmpl
index 90234d2..97a10d4 100644
--- a/BugsSite/template/en/default/search/boolean-charts.html.tmpl
+++ b/BugsSite/template/en/default/search/boolean-charts.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -61,7 +60,7 @@
       <td>
         <input type="checkbox" id="negate[% chartnum FILTER html %]"
              name="negate[% chartnum FILTER html %]" value="1"
-            [% "checked" IF chart.negate %]>
+            [%+ "checked" IF chart.negate %]>
         <label for="negate[% chartnum FILTER html %]">
           Not (negate this whole chart)
         </label>
@@ -75,8 +74,9 @@
       <td>
         <select name="[% "field${chartnum}-${rownum}-${colnum}" %]">
           [% FOREACH field = fields %]
-            <option value="[% field.name %]"
-              [%- " selected" IF field.name == col.field %]>[% field.description %]</option>
+            <option value="[% field.name %]" [% "selected" IF field.name == col.field %]>
+              [% field_descs.${field.name} || field.description FILTER html %]
+            </option>
           [% END %]
         </select>
 
diff --git a/BugsSite/template/en/default/search/form.html.tmpl b/BugsSite/template/en/default/search/form.html.tmpl
index 30e2f26..05b52dc 100644
--- a/BugsSite/template/en/default/search/form.html.tmpl
+++ b/BugsSite/template/en/default/search/form.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -93,12 +92,12 @@
         if (useclassification && f.classification.selectedIndex > -1) {
             selectClassification(f.classification, f.product, f.component, f.version, milestone);
         } else {
-            selectProduct(f.product, f.component, f.version, milestone);
+            selectProduct(f.product, f.component, f.version, milestone, null);
         }
     } else if (selectmode == 1) {
         selectClassification(f.classification, f.product, f.component, f.version, milestone);
     } else {
-        selectProduct(f.product, f.component, f.version, milestone);
+        selectProduct(f.product, f.component, f.version, milestone, null);
     }
 }
 
@@ -148,7 +147,7 @@
     </td>
     <td>
       [% IF button_name %]
-        <input type="submit" id="[% button_name.replace (' ' , '_') FILTER html %]"
+        <input type="submit" id="[% button_name FILTER css_class_quote %]_top"
                value="[% button_name FILTER html %]">
       [% END %]
     </td>
@@ -327,10 +326,10 @@
         <label for="deadlinefrom" accesskey="l">Dead<u>l</u>ine</label>:
       </th>
       <td>
-        from&nbsp;
-        <input name="deadlinefrom" id="deadlinefrom" size="10" maxlength="10">&nbsp;
-        to&nbsp;
-        <input name="deadlineto" size="10" maxlength="10">
+        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>
@@ -453,7 +452,7 @@
 <table cellspacing="0" cellpadding="0">
   <tr>
     <td>
-      Any one of:
+      Any of:
     </td>
   </tr>
   <tr>
@@ -514,6 +513,7 @@
       [% FOREACH qv = [
         { name => "substring", description => "contains" },
         { name => "exact", description => "is" },
+        { name => "notequals", description => "is not" },
         { name => "regexp", description => "matches regexp" },
         { name => "notregexp", description => "doesn't match regexp" } ] %]
 
@@ -631,7 +631,7 @@
         <option value="[% name FILTER html %]"
           [% " selected" IF lsearch(default.${sel.name}, name) != -1 %]>
           [% IF sel.name == "bug_status" %]
-            [% status_descs.${name} FILTER html %]
+            [% get_status(name) FILTER html %]
           [% ELSIF sel.name == "resolution" %]
             [% get_resolution(name) FILTER html %]
           [% ELSE %]
diff --git a/BugsSite/template/en/default/search/knob.html.tmpl b/BugsSite/template/en/default/search/knob.html.tmpl
index 39ecaa1..d0381e1 100644
--- a/BugsSite/template/en/default/search/knob.html.tmpl
+++ b/BugsSite/template/en/default/search/knob.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/search/search-advanced.html.tmpl b/BugsSite/template/en/default/search/search-advanced.html.tmpl
index 5d9849c..1f1fd50 100644
--- a/BugsSite/template/en/default/search/search-advanced.html.tmpl
+++ b/BugsSite/template/en/default/search/search-advanced.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -37,9 +36,11 @@
 
 [% PROCESS global/header.html.tmpl
   title = "Search for $terms.bugs"
-  onload = "doOnSelectProduct(0); initHelp();"
+  onload = "doOnSelectProduct(0); enableHelp();"
   javascript = js_data
-  javascript_urls = [ "js/productform.js" ]
+  javascript_urls = [ "js/productform.js" "js/util.js" "js/help.js" ]
+  style_urls = [ "skins/standard/help.css" ]
+  doc_section = "query.html"
   style = "dl.bug_changes dt {
              margin-top: 15px;
            }"
@@ -50,25 +51,19 @@
 [% button_name = "Search" %]
 
 [%# The decent help requires Javascript %]
+<script type="text/javascript"> <!--
 [% IF NOT cgi.param("help") %]
-  [% IF cgi.user_agent("Mozilla/5") %]
-    <script type="text/javascript"> <!--
-      document.write("<p><a href='query.cgi?help=1&amp;format=advanced'>Give me some help<\/a> (reloads page).<\/p>");
-      // -->
-    </script>
-  [% END %]
+  document.write("<p><a href='query.cgi?help=1&amp;format=advanced'>Give me some help<\/a> (reloads page).<\/p>");
 [% ELSE %]
-  <p>
-      For help, mouse over the page elements.
-      <font color="red">
-      [% IF cgi.user_agent("Mozilla/5") %]
-        Note that if the help popups are hidden by form element scroll bars,
-        this is a b<!-- word broken up to pass test 009 -->ug in your browser,
-        not in [% terms.Bugzilla %].
-      [% END %]
-    </font>
-  </p>
+  [% 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>
+
 <form method="get" action="buglist.cgi" name="queryform">
 
 [% PROCESS search/form.html.tmpl %]
@@ -81,7 +76,6 @@
 
 </form>
 
-[% PROCESS "search/search-help.html.tmpl" IF cgi.param("help") %]
 
 [% END %]
 
diff --git a/BugsSite/template/en/default/search/search-create-series.html.tmpl b/BugsSite/template/en/default/search/search-create-series.html.tmpl
index 2d36505..da1011e 100644
--- a/BugsSite/template/en/default/search/search-create-series.html.tmpl
+++ b/BugsSite/template/en/default/search/search-create-series.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -36,6 +35,7 @@
   onload = "doOnSelectProduct(0);"
   javascript = js_data 
   javascript_urls = [ "js/productform.js" ]
+  doc_section = "reporting.html#charts-new-series"
 %]
 
 <form method="get" action="chart.cgi" name="chartform">
diff --git a/BugsSite/template/en/default/search/search-help.html.tmpl b/BugsSite/template/en/default/search/search-help.html.tmpl
index 3f39b97..a087927 100644
--- a/BugsSite/template/en/default/search/search-help.html.tmpl
+++ b/BugsSite/template/en/default/search/search-help.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/search/search-plugin.xml.tmpl b/BugsSite/template/en/default/search/search-plugin.xml.tmpl
index 36a71f2..fe870a8 100644
--- a/BugsSite/template/en/default/search/search-plugin.xml.tmpl
+++ b/BugsSite/template/en/default/search/search-plugin.xml.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org #%]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -21,5 +20,5 @@
 <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>
-<Url type="text/html" method="GET" template="[% Param('urlbase') %]buglist.cgi?quicksearch={searchTerms}"/>
+<Url type="text/html" method="GET" template="[% urlbase FILTER xml %]buglist.cgi?quicksearch={searchTerms}"/>
 </OpenSearchDescription>
diff --git a/BugsSite/template/en/default/search/search-report-graph.html.tmpl b/BugsSite/template/en/default/search/search-report-graph.html.tmpl
index bab14ae..61dd3b5 100644
--- a/BugsSite/template/en/default/search/search-report-graph.html.tmpl
+++ b/BugsSite/template/en/default/search/search-report-graph.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -35,6 +34,7 @@
   onload = "doOnSelectProduct(0); chartTypeChanged()"
   javascript = js_data
   javascript_urls = [ "js/productform.js" ]
+  doc_section = "reporting.html#reports"
 %]
 
 [% PROCESS "search/search-report-select.html.tmpl" %]
@@ -129,7 +129,7 @@
 [% PROCESS search/form.html.tmpl %]
 
 <br>
-<input type="submit" id="[% button_name FILTER html %]"
+<input type="submit" id="[% button_name FILTER css_class_quote %]"
        value="[% button_name FILTER html %]">
 <input type="hidden" name="action" value="wrap">
 <hr>
diff --git a/BugsSite/template/en/default/search/search-report-select.html.tmpl b/BugsSite/template/en/default/search/search-report-select.html.tmpl
index 05b413a..e893bf6 100644
--- a/BugsSite/template/en/default/search/search-report-select.html.tmpl
+++ b/BugsSite/template/en/default/search/search-report-select.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/search/search-report-table.html.tmpl b/BugsSite/template/en/default/search/search-report-table.html.tmpl
index 9f7f2dd..55d62a1 100644
--- a/BugsSite/template/en/default/search/search-report-table.html.tmpl
+++ b/BugsSite/template/en/default/search/search-report-table.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -35,6 +34,7 @@
   onload = "doOnSelectProduct(0)"
   javascript = js_data
   javascript_urls = [ "js/productform.js" ]
+  doc_section = "reporting.html#reports"
 %]
 
 [% PROCESS "search/search-report-select.html.tmpl" %]
@@ -81,7 +81,7 @@
 [% PROCESS search/form.html.tmpl %]
 
 <br>
-<input type="submit" id="[% button_name.replace (' ' , '_') FILTER html %]"
+<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">
diff --git a/BugsSite/template/en/default/search/search-specific.html.tmpl b/BugsSite/template/en/default/search/search-specific.html.tmpl
index 5e3c938..d35d600 100644
--- a/BugsSite/template/en/default/search/search-specific.html.tmpl
+++ b/BugsSite/template/en/default/search/search-specific.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/search/tabs.html.tmpl b/BugsSite/template/en/default/search/tabs.html.tmpl
index 2fe05fa..6676f05 100644
--- a/BugsSite/template/en/default/search/tabs.html.tmpl
+++ b/BugsSite/template/en/default/search/tabs.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/setup/strings.txt.pl b/BugsSite/template/en/default/setup/strings.txt.pl
new file mode 100644
index 0000000..f1b5008
--- /dev/null
+++ b/BugsSite/template/en/default/setup/strings.txt.pl
@@ -0,0 +1,69 @@
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Initial Developer of the Original Code is Everything Solved.
+# Portions created by Everything Solved are Copyright (C) 2007
+# Everything Solved. All Rights Reserved.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+
+# This file contains a single hash named %strings, which is used by the
+# installation code to display strings before Template-Toolkit can safely
+# be loaded.
+#
+# Each string supports a very simple substitution system, where you can
+# have variables named like ##this## and they'll be replaced by the string
+# variable with that name.
+#
+# Please keep the strings in alphabetical order by their name.
+
+%strings = (
+    any  => 'any',
+    blacklisted => '(blacklisted)',
+    checking_for => 'Checking for',
+    checking_dbd      => 'Checking available perl DBD modules...',
+    checking_optional => 'The following Perl modules are optional:',
+    checking_modules  => 'Checking perl modules...',
+    done => 'done.',
+    header => "* This is Bugzilla ##bz_ver## on perl ##perl_ver##\n"
+            . "* Running on ##os_name## ##os_ver##",
+    install_all => <<EOT,
+
+To attempt an automatic install of every required and optional module
+with one command, do:
+
+  ##perl## install-module.pl --all
+
+EOT
+    install_data_too_long => <<EOT,
+WARNING: Some of the data in the ##table##.##column## column is longer than
+its new length limit of ##max_length## characters. The data that needs to be
+fixed is printed below with the value of the ##id_column## column first and
+then the value of the ##column## column that needs to be fixed:
+
+EOT
+    install_module => 'Installing ##module## version ##version##...',
+    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
+    module_found => "found v##ver##",
+    module_not_found => "not found",
+    module_ok => 'ok',
+    module_unknown_version => "found unknown version",
+    template_precompile   => "Precompiling templates...",
+    template_removing_dir => "Removing existing compiled templates...",
+);
+
+1;
diff --git a/BugsSite/template/en/default/sidebar.xul.tmpl b/BugsSite/template/en/default/sidebar.xul.tmpl
index a93312f..31c1472 100644
--- a/BugsSite/template/en/default/sidebar.xul.tmpl
+++ b/BugsSite/template/en/default/sidebar.xul.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# -*- 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
@@ -26,7 +25,7 @@
 
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
-<?xml-stylesheet href="[% Param('urlbase') %]skins/standard/panel.css" 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"
@@ -40,7 +39,7 @@
 }
 
 function load_relative_url( aRelativeURL ) {
-    aRelativeURL = '[% Param('urlbase') %]' + aRelativeURL;
+    aRelativeURL = '[% urlbase FILTER xml %]' + aRelativeURL;
     _content.location = aRelativeURL;
 }
 
@@ -125,7 +124,7 @@
   <box orient="horizontal">
     <spring flex="1"/>
     <html align="right">
-      <html:a class="text-link" href="[% Param('urlbase') %]sidebar.cgi">reload</html:a>
+      <html:a class="text-link" href="[% urlbase FILTER xml %]sidebar.cgi">reload</html:a>
     </html>
   </box>
 </window>
diff --git a/BugsSite/template/en/default/welcome-admin.html.tmpl b/BugsSite/template/en/default/welcome-admin.html.tmpl
index 66ab72d..6e5e36b 100644
--- a/BugsSite/template/en/default/welcome-admin.html.tmpl
+++ b/BugsSite/template/en/default/welcome-admin.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -24,7 +23,7 @@
 
 [% PROCESS global/header.html.tmpl
    title = title
-   h3 = "version $constants.BUGZILLA_VERSION"
+   header_addl_info = "version $constants.BUGZILLA_VERSION"
    style_urls = [ 'skins/standard/index.css' ]
 %]
 
@@ -35,11 +34,11 @@
   The goal of this page is to inform you about the last steps required to set up
   your installation correctly.</p>
 
-  <p>As an administrator, several administrative links are available at the bottom of
-  this page. These links will always be visible, on all pages. Among these links,
-  you must visit at least the <a href="editparams.cgi">Parameters</a> one,
-  which is the page from where you can set all important parameters for this installation.
-  By clicking this link, you will be able to set among others:</p>
+  <p>As an administrator, you have access to all administrative pages, accessible from
+  the <a href="admin.cgi">Administration</a> link visible at the bottom of this page.
+  This link will always be visible, on all pages. From there, you must visit at least
+  the <a href="editparams.cgi">Parameters</a> page, from where you can set all important
+  parameters for this installation; among others:</p>
 
   <ul>
     <li><a href="editparams.cgi?section=core#maintainer">maintainer</a>, the person
diff --git a/BugsSite/template/en/default/whine/mail.html.tmpl b/BugsSite/template/en/default/whine/mail.html.tmpl
index 8fe11d6..e1df9db 100644
--- a/BugsSite/template/en/default/whine/mail.html.tmpl
+++ b/BugsSite/template/en/default/whine/mail.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -51,7 +50,7 @@
 
     <p align="left">
       [% IF author.login == recipient.login %]
-        <a href="[%+ Param('urlbase') FILTER html %]editwhines.cgi">Click
+        <a href="[%+ urlbase FILTER html %]editwhines.cgi">Click
             here to edit your whine schedule</a>
       [% ELSE %]
         This search was scheduled by [% author.login FILTER html %].
@@ -77,13 +76,13 @@
 
     [% FOREACH bug=query.bugs %]
       <tr>
-        <td align="left"><a href="[%+ Param('urlbase') FILTER html %]show_bug.cgi?id=
+        <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">[% status_descs.${bug.bug_status} 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>
       </tr>
diff --git a/BugsSite/template/en/default/whine/mail.txt.tmpl b/BugsSite/template/en/default/whine/mail.txt.tmpl
index c7dcef3..4375ee1 100644
--- a/BugsSite/template/en/default/whine/mail.txt.tmpl
+++ b/BugsSite/template/en/default/whine/mail.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
@@ -40,7 +39,7 @@
 
 [% IF author.login == recipient.login %]
   To edit your whine schedule, visit the following URL:
-  [%+ Param('urlbase') %]editwhines.cgi
+  [%+ urlbase %]editwhines.cgi
 [% ELSE %]
   This search was scheduled by [% author.login %].
 [% END %]
@@ -53,12 +52,12 @@
 
  [% FOREACH bug=query.bugs %]
   [% terms.Bug +%] [%+ bug.bug_id %]:
-  [%+ Param('urlbase') %]show_bug.cgi?id=[% 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: [%+ status_descs.${bug.bug_status} %]
+    Status: [%+ get_status(bug.bug_status) %]
             [%- IF bug.resolution -%] Resolution: [% get_resolution(bug.resolution) -%]
                                 [%- END %]
    Summary: [% bug.short_desc %]
diff --git a/BugsSite/template/en/default/whine/multipart-mime.txt.tmpl b/BugsSite/template/en/default/whine/multipart-mime.txt.tmpl
index 208faf8..0c22575 100644
--- a/BugsSite/template/en/default/whine/multipart-mime.txt.tmpl
+++ b/BugsSite/template/en/default/whine/multipart-mime.txt.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
   # except in compliance with the License. You may obtain a copy of
diff --git a/BugsSite/template/en/default/whine/schedule.html.tmpl b/BugsSite/template/en/default/whine/schedule.html.tmpl
index 59a4096..0917f47 100644
--- a/BugsSite/template/en/default/whine/schedule.html.tmpl
+++ b/BugsSite/template/en/default/whine/schedule.html.tmpl
@@ -1,4 +1,3 @@
-[%# 1.0@bugzilla.org %]
 [%# -*- mode: html -*- %]
 [%# The contents of this file are subject to the Mozilla Public
   # License Version 1.1 (the "License"); you may not use this file
@@ -36,7 +35,11 @@
 [% PROCESS global/variables.none.tmpl %]
 
 [% title = "Set up whining" %]
-[% PROCESS global/header.html.tmpl %]
+[% PROCESS global/header.html.tmpl
+  title = title
+  style_urls = ['skins/standard/admin.css']
+  doc_section = "whining.html"
+%]
 
 <p>
   "Whining" is when [% terms.Bugzilla %] executes a saved query at a regular interval
@@ -86,13 +89,12 @@
 
 [% FOREACH event = events %]
 
-<table cellspacing="2" cellpadding="2" width="100%"
-       style="border: 1px solid;">
+<table cellspacing="2" cellpadding="2" style="border: 1px solid;">
   <tr>
-    <th align="left" bgcolor="#FFFFFF" colspan="2">
-      Event: 
+    <th align="left">
+      Event:
     </th>
-    <td align="right">
+    <td align="right" colspan="2">
       <input type="submit" value="Remove Event"
              name="remove_event_[% event.key %]"
              id="remove_event_[% event.key %]">
@@ -103,7 +105,7 @@
     <td valign="top" align="right">
       Email subject line:
     </td>
-    <td>
+    <td colspan="2">
       <input type="text" name="event_[% event.key %]_subject"
              size="60" maxlength="128" value="
              [%- event.value.subject FILTER html %]">
@@ -114,7 +116,7 @@
     <td valign="top" align="right">
       Descriptive text sent within whine message:
     </td>
-    <td>
+    <td colspan="2">
       [% INCLUDE global/textarea.html.tmpl
          name           = "event_${event.key}_body"
          minrows        = 3
@@ -132,7 +134,7 @@
       <td valign="top" align="right">
         Schedule:
       </td>
-      <td align="left" bgcolor="#FFEEEE">
+      <td class="unset" colspan="2">
         Not scheduled to run<br>
         <input type="submit" value="Add a new schedule"
                name="add_schedule_[% event.key %]"
@@ -146,18 +148,20 @@
       <td valign="top" align="right">
         Schedule:
       </td>
-      <td align="left" bgcolor="#EEFFEE">
+      <td class="set" colspan="2">
 
         <table>
           <tr>
             <th>
               Interval
             </th>
-            [% IF mail_others %]
-              <th>
+            <th>
+              [% IF mail_others %]
                 Mail to
-              </th>
-            [% END %]
+              [% END %]
+            </th>
+            <th>
+            </th>
           </tr>
           [% FOREACH schedule = event.value.schedule %]
             <tr>
@@ -199,18 +203,16 @@
             </tr>
           [% END %]
 
-        <tr>
-          <td>
-            <input type="submit" value="Add a new schedule"
-                   name="add_schedule_[% event.key %]"
-                   id="add_schedule_[% event.key %]">
-          </td>
-        </tr>
-
+          <tr>
+            <td colspan="3">
+              <input type="submit" value="Add a new schedule"
+                     name="add_schedule_[% event.key %]"
+                     id="add_schedule_[% event.key %]">
+            </td>
+          </tr>
         </table>
 
       </td>
-
     </tr>
 
   [% END %]
@@ -221,7 +223,7 @@
       <td valign="top" align="right">
         Searches:
       </td>
-      <td align="left" colspan="1">
+      <td align="left">
         No searches <br>
         <input type="submit" value="Add a new query"
                name="add_query_[% event.key %]"
@@ -238,13 +240,15 @@
       <td valign="top" align="right">
         Searches:
       </td>
-      <td align="left">
+      <td align="left" colspan="2">
 
         <table>
           <tr>
             <th>Sort</th>
             <th>Search</th>
             <th>Title</th>
+            <th></th>
+            <th></th>
           </tr>
 
           [% FOREACH query = event.value.queries %]
@@ -271,10 +275,10 @@
               <td align="left">
                 <input type="hidden" value="[% query.onemailperbug FILTER html %]"
                        name="orig_query_onemailperbug_[% query.id %]">
-                <input type="checkbox" [% IF query.onemailperbug == 1 %]
-                       checked [% END %]name="query_onemailperbug_
-                       [% query.id %]">
-                One message per [% terms.bug %]
+                <input type="checkbox" [% IF query.onemailperbug == 1 %] checked [% END %]
+                       id="query_onemailperbug_[% query.id %]"
+                       name="query_onemailperbug_[% query.id %]">
+                <label for="query_onemailperbug_[% query.id %]">One message per [% terms.bug %]</label>
               </td>
               <td align="right">
                 <input type="submit" value="Remove"
@@ -291,14 +295,13 @@
                      name="add_query_[% event.key %]"
                      id="add_query_[% event.key %]">
             </td>
+            <td align="right" colspan="2">
+              <input type="submit" value="Update / Commit" name="commit" id="update">
+            </td>
           </tr>
-
         </table>
 
       </td>
-      <td align="right" valign="bottom">
-        <input type="submit" value="Update / Commit" name="commit" id="update">
-      </td>
     </tr>
 
   [% END %]
diff --git a/BugsSite/testserver.pl b/BugsSite/testserver.pl
index 108a19a..9b64927 100755
--- a/BugsSite/testserver.pl
+++ b/BugsSite/testserver.pl
@@ -19,7 +19,7 @@
 # issues as possible.
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 BEGIN {
     my $envpath = $ENV{'PATH'};
@@ -45,7 +45,7 @@
 }
 
 
-# Try to determine the GID used by the webserver.
+# 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) {
@@ -82,7 +82,7 @@
         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 webserver configuration section of the Bugzilla guide. 
+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) {
@@ -100,8 +100,8 @@
 } else {
     print 
 "TEST-FAILED Fetch of skins/standard/index/front.png failed
-Your webserver could not fetch $url.
-Check your webserver configuration and try again.\n";
+Your web server could not fetch $url.
+Check your web server configuration and try again.\n";
     exit(1);
 }
 
@@ -118,7 +118,7 @@
     print "TEST-FAILED Webserver is not executing CGI files.\n"; 
 }
 
-# Make sure that webserver is honoring .htaccess files
+# Make sure that the web server is honoring .htaccess files
 my $localconfig = bz_locations()->{'localconfig'};
 $localconfig =~ s~^\./~~;
 $url = $ARGV[0] . "/$localconfig";
@@ -127,7 +127,7 @@
     print 
 "TEST-FAILED Webserver is permitting fetch of $url.
 This is a serious security problem.
-Check your webserver configuration.\n";
+Check your web server configuration.\n";
     exit(1);
 } else {
     print "TEST-OK Webserver is preventing fetch of $url.\n";
diff --git a/BugsSite/token.cgi b/BugsSite/token.cgi
index e45f49f..d7f9f3c 100755
--- a/BugsSite/token.cgi
+++ b/BugsSite/token.cgi
@@ -28,7 +28,7 @@
 # Make it harder for us to do dangerous things in Perl.
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -72,7 +72,7 @@
   # Make sure the token exists in the database.
   my ($tokentype) = $dbh->selectrow_array('SELECT tokentype FROM tokens
                                            WHERE token = ?', undef, $::token);
-  $tokentype || ThrowUserError("token_inexistent");
+  $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' ) {
@@ -101,11 +101,10 @@
 # If the user is requesting a password change, make sure they submitted
 # their login name and it exists in the database, and that the DB module is in
 # the list of allowed verification methods.
-my $login_name;
+my $user_account;
 if ( $::action eq 'reqpw' ) {
-    $login_name = $cgi->param('loginname');
-    defined $login_name
-      || ThrowUserError("login_needed_for_password_change");
+    my $login_name = $cgi->param('loginname')
+                       || ThrowUserError("login_needed_for_password_change");
 
     # check verification methods
     unless (Bugzilla->user->authorizer->can_change_password) {
@@ -115,10 +114,7 @@
     validate_email_syntax($login_name)
         || ThrowUserError('illegal_email_address', {addr => $login_name});
 
-    my ($user_id) = $dbh->selectrow_array('SELECT userid FROM profiles WHERE ' .
-                                          $dbh->sql_istrcmp('login_name', '?'),
-                                          undef, $login_name);
-    $user_id || ThrowUserError("account_inexistent");
+    $user_account = Bugzilla::User->check($login_name);
 }
 
 # If the user is changing their password, make sure they submitted a new
@@ -142,7 +138,7 @@
 # that variable and runs the appropriate code.
 
 if ($::action eq 'reqpw') { 
-    requestChangePassword($login_name);
+    requestChangePassword($user_account);
 } elsif ($::action eq 'cfmpw') { 
     confirmChangePassword(); 
 } elsif ($::action eq 'cxlpw') { 
@@ -175,8 +171,8 @@
 ################################################################################
 
 sub requestChangePassword {
-    my ($login_name) = @_;
-    Bugzilla::Token::IssuePasswordToken($login_name);
+    my ($user) = @_;
+    Bugzilla::Token::IssuePasswordToken($user);
 
     $vars->{'message'} = "password_change_request";
 
@@ -194,7 +190,7 @@
 }
 
 sub cancelChangePassword {    
-    $vars->{'message'} = "password_change_cancelled";
+    $vars->{'message'} = "password_change_canceled";
     Bugzilla::Token::Cancel($::token, $vars->{'message'});
 
     print $cgi->header();
@@ -215,13 +211,13 @@
     
     # Update the user's password in the profiles table and delete the token
     # from the tokens table.
-    $dbh->bz_lock_tables('profiles WRITE', 'tokens WRITE');
+    $dbh->bz_start_transaction();
     $dbh->do(q{UPDATE   profiles
                SET      cryptpassword = ?
                WHERE    userid = ?},
              undef, ($cryptedpassword, $userid) );
     $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $::token);
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     Bugzilla->logout_user_by_id($userid);
 
@@ -265,7 +261,7 @@
 
     # Update the user's login name in the profiles table and delete the token
     # from the tokens table.
-    $dbh->bz_lock_tables('profiles WRITE', 'tokens WRITE');
+    $dbh->bz_start_transaction();
     $dbh->do(q{UPDATE   profiles
                SET      login_name = ?
                WHERE    userid = ?},
@@ -273,7 +269,7 @@
     $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $::token);
     $dbh->do(q{DELETE FROM tokens WHERE userid = ?
                AND tokentype = 'emailnew'}, undef, $userid);
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     # The email address has been changed, so we need to rederive the groups
     my $user = new Bugzilla::User($userid);
@@ -300,7 +296,7 @@
     my ($old_email, $new_email) = split(/:/,$eventdata);
 
     if($tokentype eq "emailold") {
-        $vars->{'message'} = "emailold_change_cancelled";
+        $vars->{'message'} = "emailold_change_canceled";
 
         my $actualemail = $dbh->selectrow_array(
                             q{SELECT login_name FROM profiles
@@ -308,12 +304,10 @@
         
         # check to see if it has been altered
         if($actualemail ne $old_email) {
-            $dbh->bz_lock_tables('profiles WRITE');
             $dbh->do(q{UPDATE   profiles
                        SET      login_name = ?
                        WHERE    userid = ?},
                      undef, ($old_email, $userid));
-            $dbh->bz_unlock_tables();
 
             # email has changed, so rederive groups
             # Note that this is done _after_ the tables are unlocked
@@ -324,22 +318,20 @@
             my $user = new Bugzilla::User($userid);
             $user->derive_regexp_groups;
 
-            $vars->{'message'} = "email_change_cancelled_reinstated";
+            $vars->{'message'} = "email_change_canceled_reinstated";
         } 
     } 
     else {
-        $vars->{'message'} = 'email_change_cancelled'
+        $vars->{'message'} = 'email_change_canceled'
      }
 
     $vars->{'old_email'} = $old_email;
     $vars->{'new_email'} = $new_email;
     Bugzilla::Token::Cancel($::token, $vars->{'message'}, $vars);
 
-    $dbh->bz_lock_tables('tokens WRITE');
     $dbh->do(q{DELETE FROM tokens WHERE userid = ?
                AND tokentype = 'emailold' OR tokentype = 'emailnew'},
              undef, $userid);
-    $dbh->bz_unlock_tables();
 
     # Return HTTP response headers.
     print $cgi->header();
@@ -354,8 +346,9 @@
     $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
     $vars->{'date'} = str2time($date);
 
-    # We require a HTTPS connection if possible.
-    if (Bugzilla->params->{'sslbase'} ne ''
+    # 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'});
@@ -394,7 +387,7 @@
 sub cancel_create_account {
     my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($::token);
 
-    $vars->{'message'} = 'account_creation_cancelled';
+    $vars->{'message'} = 'account_creation_canceled';
     $vars->{'account'} = $login_name;
     Bugzilla::Token::Cancel($::token, $vars->{'message'});
 
diff --git a/BugsSite/userprefs.cgi b/BugsSite/userprefs.cgi
index 8f2e69f..3b01e8f 100755
--- a/BugsSite/userprefs.cgi
+++ b/BugsSite/userprefs.cgi
@@ -24,7 +24,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -90,8 +90,14 @@
                         undef, $user->id);
         $oldcryptedpwd || ThrowCodeError("unable_to_retrieve_password");
 
-        if (crypt(scalar($cgi->param('Bugzilla_password')), $oldcryptedpwd) ne 
-                  $oldcryptedpwd) 
+        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) 
         {
             ThrowUserError("old_password_incorrect");
         }
@@ -138,7 +144,7 @@
             is_available_username($new_login_name)
               || ThrowUserError("account_exists", {email => $new_login_name});
 
-            Bugzilla::Token::IssueEmailChangeToken($user->id, $old_login_name,
+            Bugzilla::Token::IssueEmailChangeToken($user, $old_login_name,
                                                    $new_login_name);
 
             $vars->{'email_changes_saved'} = 1;
@@ -247,11 +253,15 @@
     my $dbh = Bugzilla->dbh;
     my $cgi = Bugzilla->cgi;
     my $user = Bugzilla->user;
-    
+
+    if (Bugzilla->params->{"supportwatchers"}) {
+        Bugzilla::User::match_field($cgi, { 'new_watchedusers' => {'type' => 'multi'} });
+    }
+
     ###########################################################################
     # Role-based preferences
     ###########################################################################
-    $dbh->bz_lock_tables("email_setting WRITE");
+    $dbh->bz_start_transaction();
 
     # Delete all the user's current preferences
     $dbh->do("DELETE FROM email_setting WHERE user_id = ?", undef, $user->id);
@@ -298,7 +308,7 @@
         }
     }
 
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     ###########################################################################
     # User watching
@@ -307,11 +317,7 @@
         && (defined $cgi->param('new_watchedusers')
             || defined $cgi->param('remove_watched_users'))) 
     {
-        # Just in case.  Note that this much locking is actually overkill:
-        # we don't really care if anyone reads the watch table.  So 
-        # some small amount of contention could be gotten rid of by
-        # using user-defined locks rather than table locking.
-        $dbh->bz_lock_tables('watch WRITE', 'profiles READ');
+        $dbh->bz_start_transaction();
 
         # Use this to protect error messages on duplicate submissions
         my $old_watch_ids =
@@ -319,7 +325,8 @@
                                    . " WHERE watcher = ?", undef, $user->id);
 
         # The new information given to us by the user.
-        my @new_watch_names = split(/[,\s]+/, $cgi->param('new_watchedusers'));
+        my $new_watched_users = join(',', $cgi->param('new_watchedusers')) || '';
+        my @new_watch_names = split(/[,\s]+/, $new_watched_users);
         my %new_watch_ids;
 
         foreach my $username (@new_watch_names) {
@@ -351,7 +358,7 @@
             }
         }
 
-        $dbh->bz_unlock_tables();
+        $dbh->bz_commit_transaction();
     }
 }
 
@@ -502,7 +509,7 @@
 
 # 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 
-# prior to login if we might possibly be in an sudo session.
+# 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');
 $cgi->delete('Bugzilla_login', 'Bugzilla_password') if ($cgi->cookie('sudo'));
@@ -520,6 +527,9 @@
 
 $vars->{'current_tab_name'} = $current_tab_name;
 
+my $token = $cgi->param('token');
+check_token_data($token, 'edit_user_prefs') if $cgi->param('dosave');
+
 # Do any saving, and then display the current tab.
 SWITCH: for ($current_tab_name) {
     /^account$/ && do {
@@ -550,6 +560,11 @@
                    { current_tab_name => $current_tab_name });
 }
 
+delete_token($token) if $cgi->param('dosave');
+if ($current_tab_name ne 'permissions') {
+    $vars->{'token'} = issue_session_token('edit_user_prefs');
+}
+
 # Generate and return the UI (HTML page) from the appropriate template.
 print $cgi->header();
 $template->process("account/prefs/prefs.html.tmpl", $vars)
diff --git a/BugsSite/votes.cgi b/BugsSite/votes.cgi
index 9805ae4..961db7a 100755
--- a/BugsSite/votes.cgi
+++ b/BugsSite/votes.cgi
@@ -25,7 +25,7 @@
 #                 Frédéric Buclin <LpSolit@gmail.com>
 
 use strict;
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -130,9 +130,7 @@
 
     my $canedit = (Bugzilla->params->{'usevotes'} && $userid == $who) ? 1 : 0;
 
-    $dbh->bz_lock_tables('bugs READ', 'products READ', 'votes WRITE',
-             'cc READ', 'bug_group_map READ', 'user_group_map READ',
-             'group_group_map READ', 'groups READ', 'group_control_map READ');
+    $dbh->bz_start_transaction();
 
     if ($canedit && $bug_id) {
         # Make sure there is an entry for this bug
@@ -146,6 +144,7 @@
         }
     }
 
+    my @all_bug_ids;
     my @products;
     my $products = $user->get_selectable_products;
     # Read the votes data for this user for each product.
@@ -153,6 +152,7 @@
         next unless ($product->votes_per_user > 0);
 
         my @bugs;
+        my @bug_ids;
         my $total = 0;
         my $onevoteonly = 0;
 
@@ -180,6 +180,8 @@
             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,
@@ -189,6 +191,7 @@
         if ($#bugs > -1) {
             push (@products, { name => $product->name,
                                bugs => \@bugs,
+                               bug_ids => \@bug_ids,
                                onevoteonly => $onevoteonly,
                                total => $total,
                                maxvotes => $product->votes_per_user,
@@ -197,12 +200,13 @@
     }
 
     $dbh->do('DELETE FROM votes WHERE vote_count <= 0');
-    $dbh->bz_unlock_tables();
+    $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)
@@ -296,9 +300,7 @@
     # 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_lock_tables('bugs WRITE', 'bugs_activity WRITE',
-                         'votes WRITE', 'longdescs WRITE',
-                         'products READ', 'fielddefs READ');
+    $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
@@ -337,7 +339,7 @@
         my $confirmed = CheckIfVotedConfirmed($id, $who);
         push (@updated_bugs, $id) if $confirmed;
     }
-    $dbh->bz_unlock_tables();
+    $dbh->bz_commit_transaction();
 
     $vars->{'type'} = "votes";
     $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
diff --git a/BugsSite/whine.pl b/BugsSite/whine.pl
index bfe6abc..e49e481 100755
--- a/BugsSite/whine.pl
+++ b/BugsSite/whine.pl
@@ -26,7 +26,7 @@
 
 use strict;
 
-use lib ".";
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
@@ -206,12 +206,7 @@
     # Loop until there's something to return
     until (scalar keys %{$event}) {
 
-        $dbh->bz_lock_tables('whine_schedules WRITE',
-                             'whine_events READ',
-                             'profiles READ',
-                             'groups READ',
-                             'group_group_map READ',
-                             'user_group_map READ');
+        $dbh->bz_start_transaction();
 
         # Get the event ID for the first pending schedule
         $sth_next_scheduled_event->execute;
@@ -275,7 +270,7 @@
             reset_timer($sid);
         }
 
-        $dbh->bz_unlock_tables();
+        $dbh->bz_commit_transaction();
 
         # Only set $event if the user is allowed to do whining
         if ($owner->in_group('bz_canusewhines')) {
@@ -358,10 +353,11 @@
 #
 sub mail {
     my $args = shift;
+    my $addressee = $args->{recipient};
+    # Don't send mail to someone whose bugmail notification is disabled.
+    return if $addressee->email_disabled;
 
-    # Don't send mail to someone on the nomail list.
-    return if $args->{recipient}->email_disabled;
-
+    my $template = Bugzilla->template_inner($addressee->settings->{'lang'}->{'value'});
     my $msg = ''; # it's a temporary variable to hold the template output
     $args->{'alternatives'} ||= [];
 
@@ -392,6 +388,7 @@
     $template->process("whine/multipart-mime.txt.tmpl", $args, \$msg)
         or die($template->error());
 
+    Bugzilla->template_inner("");
     MessageToMTA($msg);
 
     delete $args->{'boundary'};
@@ -474,7 +471,7 @@
                 push @{$thisquery->{'bugs'}}, $bug;
             }
         }
-        unless ($thisquery->{'onemailperbug'}) {
+        if (!$thisquery->{'onemailperbug'} && @{$thisquery->{'bugs'}}) {
             push @{$return_queries}, $thisquery;
         }
     }
@@ -489,7 +486,7 @@
 sub get_query {
     my ($name, $user) = @_;
     my $qname = $name;
-    $sth_get_query->execute($user->{'id'}, $qname);
+    $sth_get_query->execute($user->id, $qname);
     my $fetched = $sth_get_query->fetch;
     $sth_get_query->finish;
     return $fetched ? $fetched->[0] : '';
diff --git a/BugsSite/whineatnews.pl b/BugsSite/whineatnews.pl
index 12a86cb..df90062 100755
--- a/BugsSite/whineatnews.pl
+++ b/BugsSite/whineatnews.pl
@@ -29,7 +29,7 @@
 # touched for more than the number of days specified in the whinedays param.
 
 use strict;
-use lib '.';
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Mailer;
@@ -85,9 +85,11 @@
     $vars->{'bugs'} = \@bugs;
 
     my $msg;
-    my $template = Bugzilla->template;
-    $template->process("email/whine.txt.tmpl", $vars, \$msg);
+    my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+    $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/BugsSite/xml.cgi b/BugsSite/xml.cgi
index 8316971..ce6a7c39 100755
--- a/BugsSite/xml.cgi
+++ b/BugsSite/xml.cgi
@@ -24,7 +24,7 @@
 
 use strict;
 
-use lib qw(.);
+use lib qw(. lib);
 use Bugzilla;
 
 my $cgi = Bugzilla->cgi;
diff --git a/BugsSite/xmlrpc.cgi b/BugsSite/xmlrpc.cgi
index c17cab8..d5042eb 100755
--- a/BugsSite/xmlrpc.cgi
+++ b/BugsSite/xmlrpc.cgi
@@ -16,10 +16,13 @@
 # Contributor(s): Marc Schumann <wurblzap@gmail.com>
 
 use strict;
-use lib qw(.);
+use lib qw(. lib);
 
 use Bugzilla;
 use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Hook;
+use Bugzilla::WebService::Constants;
 
 # Use an eval here so that runtests.pl accepts this script even if SOAP-Lite
 # is not installed.
@@ -28,12 +31,34 @@
 $@ && ThrowCodeError('soap_not_installed');
 
 Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_WEBSERVICE);
+local $SOAP::Constants::FAULT_SERVER;
+$SOAP::Constants::FAULT_SERVER = ERROR_UNKNOWN_FATAL;
+# The line above is used, this one is ignored, but SOAP::Lite
+# might start using this constant (the correct one) for XML-RPC someday.
+local $XMLRPC::Constants::FAULT_SERVER;
+$XMLRPC::Constants::FAULT_SERVER = ERROR_UNKNOWN_FATAL;
+
+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({'Bugzilla' => 'Bugzilla::WebService::Bugzilla',
-                     'Bug'      => 'Bugzilla::WebService::Bug',
-                     'User'     => 'Bugzilla::WebService::User',
-                     'Product'  => 'Bugzilla::WebService::Product',
-                    })
-    ->on_action(\&Bugzilla::WebService::handle_login)
+    ->dispatch_with($dispatch)
+    ->on_action(sub { Bugzilla::WebService::handle_login($dispatch, @_) } )
     ->handle;