Upgrade to Bugzilla 4.2.7.
authorddkilzer@apple.com <ddkilzer@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 16 Oct 2014 16:01:12 +0000 (16:01 +0000)
committerddkilzer@apple.com <ddkilzer@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 16 Oct 2014 16:01:12 +0000 (16:01 +0000)
Conflicts:
    buglist.cgi
    report.cgi

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

102 files changed:
Websites/bugs.webkit.org/.htaccess
Websites/bugs.webkit.org/Bugzilla.pm
Websites/bugs.webkit.org/Bugzilla/Attachment.pm
Websites/bugs.webkit.org/Bugzilla/Attachment/PatchReader.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Login/Cookie.pm
Websites/bugs.webkit.org/Bugzilla/Auth/Verify/LDAP.pm
Websites/bugs.webkit.org/Bugzilla/Bug.pm
Websites/bugs.webkit.org/Bugzilla/CGI.pm
Websites/bugs.webkit.org/Bugzilla/Config/MTA.pm
Websites/bugs.webkit.org/Bugzilla/Constants.pm
Websites/bugs.webkit.org/Bugzilla/DB.pm
Websites/bugs.webkit.org/Bugzilla/DB/Mysql.pm
Websites/bugs.webkit.org/Bugzilla/DB/Oracle.pm
Websites/bugs.webkit.org/Bugzilla/DB/Pg.pm
Websites/bugs.webkit.org/Bugzilla/DB/Schema.pm
Websites/bugs.webkit.org/Bugzilla/DB/Schema/Oracle.pm
Websites/bugs.webkit.org/Bugzilla/DB/Schema/Pg.pm
Websites/bugs.webkit.org/Bugzilla/Field.pm
Websites/bugs.webkit.org/Bugzilla/Field/ChoiceInterface.pm
Websites/bugs.webkit.org/Bugzilla/FlagType.pm
Websites/bugs.webkit.org/Bugzilla/Group.pm
Websites/bugs.webkit.org/Bugzilla/Hook.pm
Websites/bugs.webkit.org/Bugzilla/Install/DB.pm
Websites/bugs.webkit.org/Bugzilla/Install/Requirements.pm
Websites/bugs.webkit.org/Bugzilla/Mailer.pm
Websites/bugs.webkit.org/Bugzilla/Object.pm
Websites/bugs.webkit.org/Bugzilla/Search.pm
Websites/bugs.webkit.org/Bugzilla/Search/Clause.pm
Websites/bugs.webkit.org/Bugzilla/Search/Condition.pm
Websites/bugs.webkit.org/Bugzilla/Search/Quicksearch.pm
Websites/bugs.webkit.org/Bugzilla/Search/Saved.pm
Websites/bugs.webkit.org/Bugzilla/Template.pm
Websites/bugs.webkit.org/Bugzilla/Token.pm
Websites/bugs.webkit.org/Bugzilla/User.pm
Websites/bugs.webkit.org/Bugzilla/User/Setting.pm
Websites/bugs.webkit.org/Bugzilla/WebService/Bug.pm
Websites/bugs.webkit.org/Bugzilla/WebService/Constants.pm
Websites/bugs.webkit.org/Bugzilla/WebService/Server.pm
Websites/bugs.webkit.org/Bugzilla/WebService/Server/XMLRPC.pm
Websites/bugs.webkit.org/Bugzilla/WebService/User.pm
Websites/bugs.webkit.org/Bugzilla/WebService/Util.pm
Websites/bugs.webkit.org/attachment.cgi
Websites/bugs.webkit.org/buglist.cgi
Websites/bugs.webkit.org/contrib/bz_webservice_demo.pl
Websites/bugs.webkit.org/contrib/convert-workflow.pl
Websites/bugs.webkit.org/docs/en/xml/Bugzilla-Guide.xml
Websites/bugs.webkit.org/docs/en/xml/administration.xml
Websites/bugs.webkit.org/docs/en/xml/customization.xml
Websites/bugs.webkit.org/docs/en/xml/installation.xml
Websites/bugs.webkit.org/docs/en/xml/modules.xml
Websites/bugs.webkit.org/docs/en/xml/using.xml
Websites/bugs.webkit.org/editflagtypes.cgi
Websites/bugs.webkit.org/email_in.pl
Websites/bugs.webkit.org/enter_bug.cgi
Websites/bugs.webkit.org/extensions/Example/Extension.pm
Websites/bugs.webkit.org/extensions/OldBugMove/Extension.pm
Websites/bugs.webkit.org/extensions/Voting/Config.pm [new file with mode: 0644]
Websites/bugs.webkit.org/extensions/Voting/Extension.pm
Websites/bugs.webkit.org/importxml.pl
Websites/bugs.webkit.org/jobqueue.pl
Websites/bugs.webkit.org/js/custom-search.js
Websites/bugs.webkit.org/js/field.js
Websites/bugs.webkit.org/js/yui/swfstore/swfstore.swf
Websites/bugs.webkit.org/process_bug.cgi
Websites/bugs.webkit.org/query.cgi
Websites/bugs.webkit.org/report.cgi
Websites/bugs.webkit.org/show_bug.cgi
Websites/bugs.webkit.org/showdependencygraph.cgi
Websites/bugs.webkit.org/skins/contrib/Dusk/global.css
Websites/bugs.webkit.org/skins/standard/global.css
Websites/bugs.webkit.org/skins/standard/show_bug.css
Websites/bugs.webkit.org/template/en/default/account/auth/login-small.html.tmpl
Websites/bugs.webkit.org/template/en/default/account/auth/login.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/flag-type/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/admin/params/editparams.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/comments.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/create/create.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/dependency-tree.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/edit.html.tmpl
Websites/bugs.webkit.org/template/en/default/bug/field-events.js.tmpl
Websites/bugs.webkit.org/template/en/default/bug/field-help.none.tmpl
Websites/bugs.webkit.org/template/en/default/bug/link.html.tmpl
Websites/bugs.webkit.org/template/en/default/email/bugmail.html.tmpl
Websites/bugs.webkit.org/template/en/default/filterexceptions.pl
Websites/bugs.webkit.org/template/en/default/global/code-error.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/confirm-user-match.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/field-descs.none.tmpl
Websites/bugs.webkit.org/template/en/default/global/header.html.tmpl
Websites/bugs.webkit.org/template/en/default/global/user-error.html.tmpl
Websites/bugs.webkit.org/template/en/default/list/server-push.html.tmpl
Websites/bugs.webkit.org/template/en/default/list/table.html.tmpl
Websites/bugs.webkit.org/template/en/default/pages/release-notes.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/report-table.html.tmpl
Websites/bugs.webkit.org/template/en/default/reports/report.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/form.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/knob.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/search-advanced.html.tmpl
Websites/bugs.webkit.org/template/en/default/search/search-report-select.html.tmpl
Websites/bugs.webkit.org/template/en/default/setup/strings.txt.pl
Websites/bugs.webkit.org/token.cgi
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/Constants.pm
Websites/bugs.webkit.org/xt/lib/Bugzilla/Test/Search/FieldTest.pm

index affa4db..925403a 100644 (file)
@@ -1,5 +1,5 @@
 # Don't allow people to retrieve non-cgi executable files or our private data
-<FilesMatch ^(.*\.pm|.*\.pl|.*localconfig.*)$>
+<FilesMatch (\.pm|\.pl|\.tmpl|localconfig.*)$>
   deny from all
 </FilesMatch>
 <FilesMatch ^(localconfig.js|localconfig.rdf)$>
index 65ddcc2..5b39e4c 100644 (file)
@@ -592,7 +592,8 @@ sub fields {
         }
     }
 
-    return $do_by_name ? \%requested : [values %requested];
+    return $do_by_name ? \%requested
+        : [sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name } values %requested];
 }
 
 sub active_custom_fields {
@@ -847,7 +848,7 @@ in a hashref:
 =item C<by_name>
 
 If false (or not specified), this method will return an arrayref of
-the requested fields. The order of the returned fields is random.
+the requested fields.
 
 If true, this method will return a hashref of fields, where the keys
 are field names and the valules are L<Bugzilla::Field> objects.
index b1f47d0..69939a6 100644 (file)
@@ -723,11 +723,8 @@ sub validate_obsolete {
         $attachment->validate_can_edit($bug->product_id)
           || ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
 
-        $vars->{'description'} = $attachment->description;
-
         if ($attachment->bug_id != $bug->bug_id) {
             $vars->{'my_bug_id'} = $bug->bug_id;
-            $vars->{'attach_bug_id'} = $attachment->bug_id;
             ThrowCodeError('mismatched_bug_ids_on_obsolete', $vars);
         }
 
index 01a624a..cfc7610 100644 (file)
@@ -37,7 +37,6 @@ sub process_diff {
         $last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
         # Actually print out the patch.
         print $cgi->header(-type => 'text/plain',
-                           -x_content_type_options => "nosniff",
                            -expires => '+3M');
         disable_utf8();
         $reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
@@ -119,7 +118,6 @@ sub process_interdiff {
         $last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
         # Actually print out the patch.
         print $cgi->header(-type => 'text/plain',
-                           -x_content_type_options => "nosniff",
                            -expires => '+3M');
         disable_utf8();
     }
index 91fb820..de9188c 100644 (file)
@@ -60,8 +60,8 @@ sub get_login_info {
         trick_taint($login_cookie);
         detaint_natural($user_id);
 
-        my $is_valid =
-          $dbh->selectrow_array('SELECT 1
+        my $db_cookie =
+          $dbh->selectrow_array('SELECT cookie
                                    FROM logincookies
                                   WHERE cookie = ?
                                         AND userid = ?
@@ -69,7 +69,7 @@ sub get_login_info {
                                  undef, ($login_cookie, $user_id, $ip_addr));
 
         # If the cookie is valid, return a valid username.
-        if ($is_valid) {
+        if (defined $db_cookie && $login_cookie eq $db_cookie) {
             # If we logged in successfully, then update the lastused 
             # time on the login cookie
             $dbh->do("UPDATE logincookies SET lastused = NOW() 
index cdc802c..0f10f9f 100644 (file)
@@ -41,6 +41,7 @@ use Bugzilla::User;
 use Bugzilla::Util;
 
 use Net::LDAP;
+use Net::LDAP::Util qw(escape_filter_value);
 
 use constant admin_can_create_account => 0;
 use constant user_can_create_account  => 0;
@@ -144,6 +145,7 @@ sub check_credentials {
 
 sub _bz_search_params {
     my ($username) = @_;
+    $username = escape_filter_value($username);
     return (base   => Bugzilla->params->{"LDAPBaseDN"},
             scope  => "sub",
             filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"} 
index a848c86..7b86ab2 100644 (file)
@@ -523,17 +523,14 @@ sub possible_duplicates {
     if ($dbh->FULLTEXT_OR) {
         my $joined_terms = join($dbh->FULLTEXT_OR, @words);
         ($where_sql, $relevance_sql) = 
-            $dbh->sql_fulltext_search('bugs_fulltext.short_desc', 
-                                      $joined_terms, 1);
+            $dbh->sql_fulltext_search('bugs_fulltext.short_desc', $joined_terms);
         $relevance_sql ||= $where_sql;
     }
     else {
         my (@where, @relevance);
-        my $count = 0;
         foreach my $word (@words) {
-            $count++;
             my ($term, $rel_term) = $dbh->sql_fulltext_search(
-                'bugs_fulltext.short_desc', $word, $count);
+                'bugs_fulltext.short_desc', $word);
             push(@where, $term);
             push(@relevance, $rel_term || $term);
         }
@@ -733,6 +730,17 @@ sub run_create_validators {
     my $class  = shift;
     my $params = $class->SUPER::run_create_validators(@_);
 
+    # Add classification for checking mandatory fields which depend on it
+    $params->{classification} = $params->{product}->classification->name;
+
+    my @mandatory_fields = @{ Bugzilla->fields({ is_mandatory => 1,
+                                                 enter_bug    => 1,
+                                                 obsolete     => 0 }) };
+    foreach my $field (@mandatory_fields) {
+        $class->_check_field_is_mandatory($params->{$field->name}, $field,
+                                          $params);
+    }
+
     my $product = delete $params->{product};
     $params->{product_id} = $product->id;
     my $component = delete $params->{component};
@@ -757,18 +765,11 @@ sub run_create_validators {
     delete $params->{resolution};
     delete $params->{lastdiffed};
     delete $params->{bug_id};
+    delete $params->{classification};
 
     Bugzilla::Hook::process('bug_end_of_create_validators',
                             { params => $params });
 
-    my @mandatory_fields = @{ Bugzilla->fields({ is_mandatory => 1,
-                                                 enter_bug    => 1,
-                                                 obsolete     => 0 }) };
-    foreach my $field (@mandatory_fields) {
-        $class->_check_field_is_mandatory($params->{$field->name}, $field,
-                                          $params);
-    }
-
     return $params;
 }
 
@@ -1371,7 +1372,7 @@ sub _check_bug_status {
     }
 
     # Check if a comment is required for this change.
-    if ($new_status->comment_required_on_change_from($old_status) && !$comment)
+    if ($new_status->comment_required_on_change_from($old_status) && !$comment->{'thetext'})
     {
         ThrowUserError('comment_required', { old => $old_status,
                                              new => $new_status });
@@ -1465,8 +1466,12 @@ sub _check_component {
     $name || ThrowUserError("require_component");
     my $product = blessed($invocant) ? $invocant->product_obj 
                                      : $params->{product};
-    my $obj = Bugzilla::Component->check({ product => $product, name => $name });
-    return $obj;
+    my $old_comp = blessed($invocant) ? $invocant->component : '';
+    my $object = Bugzilla::Component->check({ product => $product, name => $name });
+    if ($object->name ne $old_comp && !$object->is_active) {
+        ThrowUserError('value_inactive', { class => ref($object), value => $name });
+    }
+    return $object;
 }
 
 sub _check_creation_ts {
@@ -1908,10 +1913,14 @@ sub _check_target_milestone {
     my ($invocant, $target, undef, $params) = @_;
     my $product = blessed($invocant) ? $invocant->product_obj 
                                      : $params->{product};
+    my $old_target = blessed($invocant) ? $invocant->target_milestone : '';
     $target = trim($target);
     $target = $product->default_milestone if !defined $target;
     my $object = Bugzilla::Milestone->check(
         { product => $product, name => $target });
+    if ($old_target && $object->name ne $old_target && !$object->is_active) {
+        ThrowUserError('value_inactive', { class => ref($object),  value => $target });
+    }
     return $object->name;
 }
 
@@ -1934,8 +1943,11 @@ sub _check_version {
     $version = trim($version);
     my $product = blessed($invocant) ? $invocant->product_obj 
                                      : $params->{product};
-    my $object = 
-        Bugzilla::Version->check({ product => $product, name => $version });
+    my $old_vers = blessed($invocant) ? $invocant->version : '';
+    my $object = Bugzilla::Version->check({ product => $product, name => $version });
+    if ($object->name ne $old_vers && !$object->is_active) {
+        ThrowUserError('value_inactive', { class => ref($object), value => $version });
+    }
     return $object->name;
 }
 
@@ -1953,6 +1965,12 @@ sub _check_field_is_mandatory {
 
     return if !$field->is_visible_on_bug($params || $invocant);
 
+    return if ($field->type == FIELD_TYPE_SINGLE_SELECT
+                 && scalar @{ get_legal_field_values($field->name) } == 1);
+
+    return if ($field->type == FIELD_TYPE_MULTI_SELECT
+                 && !scalar @{ get_legal_field_values($field->name) });
+
     if (ref($value) eq 'ARRAY') {
         $value = join('', @$value);
     }
@@ -2464,9 +2482,9 @@ sub _set_product {
                 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}];
+            $vars{components} = [map { $_->name } grep($_->is_active, @{$product->components})];
+            $vars{milestones} = [map { $_->name } grep($_->is_active, @{$product->milestones})];
+            $vars{versions}   = [map { $_->name } grep($_->is_active, @{$product->versions})];
         }
 
         if (!$verified) {
@@ -2872,7 +2890,8 @@ sub add_see_also {
         # ref bug id for sending changes email.
         my $ref_bug = delete $field_values->{ref_bug};
         if ($class->isa('Bugzilla::BugUrl::Bugzilla::Local')
-            and !$skip_recursion)
+            and !$skip_recursion
+            and $ref_bug->check_can_change_field('see_also', '', $self->id, \$privs))
         {
             $ref_bug->add_see_also($self->id, 'skip_recursion');
             push @{ $self->{_update_ref_bugs} }, $ref_bug;
@@ -2904,12 +2923,15 @@ sub remove_see_also {
     # we need to notify changes for that bug too.
     $removed_bug_url = $removed_bug_url->[0];
     if (!$skip_recursion and $removed_bug_url
-        and $removed_bug_url->isa('Bugzilla::BugUrl::Bugzilla::Local'))
+        and $removed_bug_url->isa('Bugzilla::BugUrl::Bugzilla::Local')
+        and $removed_bug_url->ref_bug_url)
     {
         my $ref_bug
             = Bugzilla::Bug->check($removed_bug_url->ref_bug_url->bug_id);
 
-        if (Bugzilla->user->can_edit_product($ref_bug->product_id)) {
+        if (Bugzilla->user->can_edit_product($ref_bug->product_id)
+            and $ref_bug->check_can_change_field('see_also', $self->id, '', \$privs))
+        {
             my $self_url = $removed_bug_url->local_uri($self->id);
             $ref_bug->remove_see_also($self_url, 'skip_recursion');
             push @{ $self->{_update_ref_bugs} }, $ref_bug;
@@ -3632,9 +3654,13 @@ sub bug_alias_to_id {
 # Subroutines
 #####################################################################
 
-# Represents which fields from the bugs table are handled by process_bug.cgi.
+# Returns a list of currently active and editable bug fields,
+# including multi-select fields.
 sub editable_bug_fields {
     my @fields = Bugzilla->dbh->bz_table_columns('bugs');
+    # Add multi-select fields
+    push(@fields, map { $_->name } @{Bugzilla->fields({obsolete => 0,
+                                                       type => FIELD_TYPE_MULTI_SELECT})});
     # Obsolete custom fields are not editable.
     my @obsolete_fields = @{ Bugzilla->fields({obsolete => 1, custom => 1}) };
     @obsolete_fields = map { $_->name } @obsolete_fields;
@@ -3642,7 +3668,7 @@ sub editable_bug_fields {
                         "lastdiffed", @obsolete_fields) 
     {
         my $location = firstidx { $_ eq $remove } @fields;
-        # Custom multi-select fields are not stored in the bugs table.
+        # Ensure field exists before attempting to remove it.
         splice(@fields, $location, 1) if ($location > -1);
     }
     # Sorted because the old @::log_columns variable, which this replaces,
index e0e1c40..4dd223a 100644 (file)
@@ -169,6 +169,16 @@ sub clean_search_url {
     # Delete leftovers from the login form
     $self->delete('Bugzilla_remember', 'GoAheadAndLogIn');
 
+    # Delete the token if we're not performing an action which needs it
+    unless ((defined $self->param('remtype')
+             && ($self->param('remtype') eq 'asdefault'
+                 || $self->param('remtype') eq 'asnamed'))
+            || (defined $self->param('remaction')
+                && $self->param('remaction') eq 'forget'))
+    {
+        $self->delete("token");
+    }
+
     foreach my $num (1,2,3) {
         # If there's no value in the email field, delete the related fields.
         if (!$self->param("email$num")) {
@@ -306,6 +316,14 @@ sub header {
         unshift(@_, '-x_frame_options' => 'SAMEORIGIN');
     }
 
+    # Add X-XSS-Protection header to prevent simple XSS attacks
+    # and enforce the blocking (rather than the rewriting) mode.
+    unshift(@_, '-x_xss_protection' => '1; mode=block');
+
+    # Add X-Content-Type-Options header to prevent browsers sniffing
+    # the MIME type away from the declared Content-Type.
+    unshift(@_, '-x_content_type_options' => 'nosniff');
+
     return $self->SUPER::header(@_) || "";
 }
 
@@ -353,7 +371,7 @@ sub 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);
+    utf8::decode($input) if defined $input && !ref $input && !utf8::is_utf8($input);
     return $input;
 }
 
index c90e5dc..aeded37 100644 (file)
@@ -34,6 +34,12 @@ package Bugzilla::Config::MTA;
 use strict;
 
 use Bugzilla::Config::Common;
+# Return::Value 1.666002 pollutes the error log with warnings about this
+# deprecated module. We have to set NO_CLUCK = 1 before loading Email::Send
+# to disable these warnings.
+BEGIN {
+    $Return::Value::NO_CLUCK = 1;
+}
 use Email::Send;
 
 our $sortkey = 1200;
index 7a4c573..065be0a 100644 (file)
@@ -203,7 +203,7 @@ use Memoize;
 # CONSTANTS
 #
 # Bugzilla version
-use constant BUGZILLA_VERSION => "4.2.1";
+use constant BUGZILLA_VERSION => "4.2.7";
 
 # Location of the remote and local XML files to track new releases.
 use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
index 0c84163..1f9c315 100644 (file)
@@ -405,8 +405,10 @@ sub sql_string_until {
 }
 
 sub sql_in {
-    my ($self, $column_name, $in_list_ref) = @_;
-    return " $column_name IN (" . join(',', @$in_list_ref) . ") ";
+    my ($self, $column_name, $in_list_ref, $negate) = @_;
+    return " $column_name "
+             . ($negate ? "NOT " : "")
+             . "IN (" . join(',', @$in_list_ref) . ") ";
 }
 
 sub sql_fulltext_search {
index 06bf3d8..9ddb466 100644 (file)
@@ -325,9 +325,8 @@ sub bz_setup_database {
     # 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') {
+    my %engines = @{$self->selectcol_arrayref('SHOW ENGINES', {Columns => [1,2]})};
+    if (!$engines{InnoDB} || $engines{InnoDB} !~ /^(YES|DEFAULT)$/) {
         die install_string('mysql_innodb_disabled');
     }
 
@@ -941,7 +940,9 @@ sub _bz_raw_column_info {
               $index = name of an index
  Returns:     An abstract index definition, always in hashref format.
               If the index does not exist, the function returns undef.
+
 =cut
+
 sub bz_index_info_real {
     my ($self, $table, $index) = @_;
 
index 2cbd19a..20eb0e5 100644 (file)
@@ -56,6 +56,8 @@ use constant BLOB_TYPE => { ora_type => ORA_BLOB };
 use constant MIN_LONG_READ_LEN => 32 * 1024;
 use constant FULLTEXT_OR => ' OR ';
 
+our $fulltext_label = 0;
+
 sub new {
     my ($class, $params) = @_;
     my ($user, $pass, $host, $dbname, $port) = 
@@ -72,7 +74,7 @@ sub new {
     my $dsn = "dbi:Oracle:host=$host;sid=$dbname";
     $dsn .= ";port=$port" if $port;
     my $attrs = { FetchHashKeyName => 'NAME_lc',  
-                  LongReadLen => max(Bugzilla->params->{'maxattachmentsize'},
+                  LongReadLen => max(Bugzilla->params->{'maxattachmentsize'} || 0,
                                      MIN_LONG_READ_LEN) * 1024,
                 };
     my $self = $class->db_new({ dsn => $dsn, user => $user, 
@@ -124,7 +126,8 @@ sub bz_explain {
 sub sql_group_concat {
     my ($self, $text, $separator) = @_;
     $separator = $self->quote(', ') if !defined $separator;
-    return "group_concat(T_CLOB_DELIM($text, $separator))";
+    my ($distinct, $rest) = $text =~/^(\s*DISTINCT\s|)(.+)$/i;
+    return "group_concat($distinct T_CLOB_DELIM(NVL($rest, ' '), $separator))";
 }
 
 sub sql_regexp {
@@ -170,11 +173,13 @@ sub sql_from_days{
 
     return " TO_DATE($date,'J') ";
 }
+
 sub sql_fulltext_search {
-    my ($self, $column, $text, $label) = @_;
+    my ($self, $column, $text) = @_;
     $text = $self->quote($text);
     trick_taint($text);
-    return "CONTAINS($column,$text,$label) > 0", "SCORE($label)";
+    $fulltext_label++;
+    return "CONTAINS($column,$text,$fulltext_label) > 0", "SCORE($fulltext_label)";
 }
 
 sub sql_date_format {
@@ -211,16 +216,16 @@ sub sql_position {
 }
 
 sub sql_in {
-    my ($self, $column_name, $in_list_ref) = @_;
+    my ($self, $column_name, $in_list_ref, $negate) = @_;
     my @in_list = @$in_list_ref;
-    return $self->SUPER::sql_in($column_name, $in_list_ref) if $#in_list < 1000;
+    return $self->SUPER::sql_in($column_name, $in_list_ref, $negate) if $#in_list < 1000;
     my @in_str;
     while (@in_list) {
         my $length = $#in_list + 1;
         my $splice = $length > 1000 ? 1000 : $length;
         my @sub_in_list = splice(@in_list, 0, $splice);
         push(@in_str, 
-             $self->SUPER::sql_in($column_name, \@sub_in_list)); 
+             $self->SUPER::sql_in($column_name, \@sub_in_list, $negate));
     }
     return "( " . join(" OR ", @in_str) . " )";
 }
@@ -310,8 +315,9 @@ sub adjust_statement {
     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;
-    
+    # and its CURRENT_DATE is a date+time, so wrap in TRUNC()
+    $part =~ s/\bCURRENT_DATE\b(?:\(\))?/TRUNC(CURRENT_DATE)/io;
+
     # Oracle use SUBSTR instead of SUBSTRING
     $part =~ s/\bSUBSTRING\b/SUBSTR/io;
    
@@ -341,7 +347,8 @@ sub adjust_statement {
                     if ($is_select and !$has_from);
 
         # Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
-        $nonstring =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
+        # and its CURRENT_DATE is a date+time, so wrap in TRUNC()
+        $nonstring =~ s/\bCURRENT_DATE\b(?:\(\))?/TRUNC(CURRENT_DATE)/io;
 
         # Oracle use SUBSTR instead of SUBSTRING
         $nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
@@ -543,14 +550,19 @@ sub bz_setup_database {
               . " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
     
     # Create types for group_concat
-    my $t_clob_delim = $self->selectcol_arrayref("
-        SELECT TYPE_NAME FROM USER_TYPES WHERE TYPE_NAME=?",
-        undef, 'T_CLOB_DELIM'); 
-
-    if ( !@$t_clob_delim ) {
-        $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
-              . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256));");
-    }
+    my $type_exists = $self->selectrow_array("SELECT 1 FROM user_types
+                                              WHERE type_name = 'T_GROUP_CONCAT'");
+    $self->do("DROP TYPE T_GROUP_CONCAT") if $type_exists;
+    $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
+          . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256)"
+          . ", MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2"
+          . ");");
+    $self->do("CREATE OR REPLACE TYPE BODY T_CLOB_DELIM IS
+                  MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2 is
+                  BEGIN
+                      RETURN p_CONTENT;
+                  END;
+              END;");
 
     $self->do("CREATE OR REPLACE TYPE T_GROUP_CONCAT AS OBJECT 
                (  CLOB_CONTENT CLOB,
@@ -635,11 +647,25 @@ sub bz_setup_database {
 
     $self->SUPER::bz_setup_database(@_);
 
+    my $sth = $self->prepare("SELECT OBJECT_NAME FROM USER_OBJECTS WHERE OBJECT_NAME = ?");
     my @tables = $self->bz_table_list_real();
+
     foreach my $table (@tables) {
         my @columns = $self->bz_table_columns_real($table);
         foreach my $column (@columns) {
             my $def = $self->bz_column_info($table, $column);
+            # bz_add_column() before Bugzilla 4.2.3 didn't handle primary keys
+            # correctly (bug 731156). We have to add missing sequences and
+            # triggers ourselves.
+            if ($def->{TYPE} =~ /SERIAL/i) {
+                my $sequence = "${table}_${column}_SEQ";
+                my $exists = $self->selectrow_array($sth, undef, $sequence);
+                if (!$exists) {
+                    my @sql = $self->_get_create_seq_ddl($table, $column);
+                    $self->do($_) foreach @sql;
+                }
+            }
+
             if ($def->{REFERENCES}) {
                 my $references = $def->{REFERENCES};
                 my $update = $references->{UPDATE} || 'CASCADE';
@@ -653,15 +679,13 @@ sub bz_setup_database {
                     $to_table = 'tag';
                 }
                 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);
+                    my $trigger_name = uc($fk_name . "_UC");
+                    my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
                     if(@$exist_trigger) {
                         $self->do("DROP TRIGGER $trigger_name");
                     }
   
-                     my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
+                    my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
                          . " AFTER UPDATE OF $to_column ON $to_table "
                          . " REFERENCING "
                          . " NEW AS NEW "
@@ -672,22 +696,46 @@ sub bz_setup_database {
                          . "        SET $column = :NEW.$to_column"
                          . "      WHERE $column = :OLD.$to_column;"
                          . " END $trigger_name;";
-                         $self->do($tr_str);
-               }
-         }
-     }
-   }
+                    $self->do($tr_str);
+                }
+            }
+        }
+    }
 
    # Drop the trigger which causes bug 541553
    my $trigger_name = "PRODUCTS_MILESTONEURL";
-   my $exist_trigger = $self->selectcol_arrayref(
-       "SELECT OBJECT_NAME FROM USER_OBJECTS
-        WHERE OBJECT_NAME = ?", undef, $trigger_name);
+   my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
    if(@$exist_trigger) {
        $self->do("DROP TRIGGER $trigger_name");
    }
 }
 
+# These two methods have been copied from Bugzilla::DB::Schema::Oracle.
+sub _get_create_seq_ddl {
+    my ($self, $table, $column) = @_;
+
+    my $seq_name = "${table}_${column}_SEQ";
+    my $seq_sql = "CREATE SEQUENCE $seq_name INCREMENT BY 1 START WITH 1 " .
+                  "NOMAXVALUE NOCYCLE NOCACHE";
+    my $trigger_sql = $self->_get_create_trigger_ddl($table, $column, $seq_name);
+    return ($seq_sql, $trigger_sql);
+}
+
+sub _get_create_trigger_ddl {
+    my ($self, $table, $column, $seq_name) = @_;
+
+    my $trigger_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
+                    . " BEFORE INSERT ON $table "
+                    . " FOR EACH ROW "
+                    . " BEGIN "
+                    . "   SELECT ${seq_name}.NEXTVAL "
+                    . "   INTO :NEW.$column FROM DUAL; "
+                    . " END;";
+    return $trigger_sql;
+}
+
+############################################################################
+
 package Bugzilla::DB::Oracle::st;
 use base qw(DBI::st);
  
index b6be640..4f81893 100644 (file)
@@ -215,11 +215,12 @@ sub bz_check_server_version {
     my $self = shift;
     my ($db) = @_;
     my $server_version = $self->SUPER::bz_check_server_version(@_);
-    my ($major_version) = $server_version =~ /^(\d+)/;
-    # Pg 9 requires DBD::Pg 2.17.2 in order to properly read bytea values.
+    my ($major_version, $minor_version) = $server_version =~ /^0*(\d+)\.0*(\d+)/;
+    # Pg 9.0 requires DBD::Pg 2.17.2 in order to properly read bytea values.
+    # Pg 9.2 requires DBD::Pg 2.19.3 as spclocation no longer exists.
     if ($major_version >= 9) {
-        local $db->{dbd}->{version} = '2.17.2';
-        local $db->{name} = $db->{name} . ' 9+';
+        local $db->{dbd}->{version} = ($minor_version >= 2) ? '2.19.3' : '2.17.2';
+        local $db->{name} = $db->{name} . " ${major_version}.$minor_version";
         Bugzilla::DB::_bz_check_dbd(@_);
     }
 }
index 00ff4ac..1e598c6 100644 (file)
@@ -1858,6 +1858,7 @@ C<ALTER TABLE> SQL statement
 
 
 sub get_fk_ddl {
+
 =item C<_get_fk_ddl>
 
 =over
@@ -1871,7 +1872,9 @@ Protected method. Translates the C<REFERENCES> item of a column into SQL.
 =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
@@ -1972,6 +1975,7 @@ Converts a TYPE from the L</ABSTRACT_SCHEMA> format into the real SQL type.
 }
 
 sub get_column {
+
 =item C<get_column($table, $column)>
 
  Description: Public method to get the abstract definition of a column.
@@ -2837,6 +2841,7 @@ sub serialize_abstract {
               in the same fashion as) the current version of Schema. 
               However, it will represent the serialized data instead of
               ABSTRACT_SCHEMA.
+
 =cut
 
 sub deserialize_abstract {
index f2d5b8b..a61b1e3 100644 (file)
@@ -199,6 +199,35 @@ sub _get_fk_name {
     return $fk_name;
 }
 
+sub get_add_column_ddl {
+    my $self = shift;
+    my ($table, $column, $definition, $init_value) = @_;
+    my @sql;
+
+    # Create sequences and triggers to emulate SERIAL datatypes.
+    if ($definition->{TYPE} =~ /SERIAL/i) {
+        # Clone the definition to not alter the original one.
+        my %def = %$definition;
+        # Oracle requires to define the column is several steps.
+        my $pk = delete $def{PRIMARYKEY};
+        my $notnull = delete $def{NOTNULL};
+        @sql = $self->SUPER::get_add_column_ddl($table, $column, \%def, $init_value);
+        push(@sql, $self->_get_create_seq_ddl($table, $column));
+        push(@sql, "UPDATE $table SET $column = ${table}_${column}_SEQ.NEXTVAL");
+        push(@sql, "ALTER TABLE $table MODIFY $column NOT NULL") if $notnull;
+        push(@sql, "ALTER TABLE $table ADD PRIMARY KEY ($column)") if $pk;
+    }
+    else {
+        @sql = $self->SUPER::get_add_column_ddl(@_);
+        # Create triggers to deal with empty string. 
+        if ($definition->{TYPE} =~ /varchar|TEXT/i && $definition->{NOTNULL}) {
+            push(@sql, _get_notnull_trigger_ddl($table, $column));
+        }
+    }
+
+    return @sql;
+}
+
 sub get_alter_column_ddl {
     my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
 
@@ -364,6 +393,29 @@ sub get_rename_column_ddl {
     return @sql;
 }
 
+sub get_drop_column_ddl {
+    my $self = shift;
+    my ($table, $column) = @_;
+    my @sql;
+    push(@sql, $self->SUPER::get_drop_column_ddl(@_));
+    my $dbh=Bugzilla->dbh;
+    my $trigger_name = uc($table . "_" . $column);
+    my $exist_trigger = $dbh->selectcol_arrayref(
+        "SELECT OBJECT_NAME FROM USER_OBJECTS
+         WHERE OBJECT_NAME = ?", undef, $trigger_name);
+    if(@$exist_trigger) {
+        push(@sql, "DROP TRIGGER $trigger_name");
+    }
+    # If this column is of type SERIAL, we need to drop the sequence
+    # and trigger that went along with it.
+    my $def = $self->get_column_abstract($table, $column);
+    if ($def->{TYPE} =~ /SERIAL/i) {
+        push(@sql, "DROP SEQUENCE ${table}_${column}_SEQ");
+        push(@sql, "DROP TRIGGER ${table}_${column}_TR");
+    }
+    return @sql;
+}
+
 sub get_rename_table_sql {
     my ($self, $old_name, $new_name) = @_;
     if (lc($old_name) eq lc($new_name)) {
@@ -465,20 +517,4 @@ sub get_set_serial_sql {
     return @sql;
 } 
 
-sub get_drop_column_ddl {
-    my $self = shift;
-    my ($table, $column) = @_;
-    my @sql;
-    push(@sql, $self->SUPER::get_drop_column_ddl(@_));
-    my $dbh=Bugzilla->dbh;
-    my $trigger_name = uc($table . "_" . $column);
-    my $exist_trigger = $dbh->selectcol_arrayref(
-        "SELECT OBJECT_NAME FROM USER_OBJECTS
-         WHERE OBJECT_NAME = ?", undef, $trigger_name);
-    if(@$exist_trigger) {
-        push(@sql, "DROP TRIGGER $trigger_name");
-    }
-    return @sql;
-}
-
 1;
index ef6e567..d21f509 100644 (file)
@@ -90,6 +90,16 @@ sub _initialize {
 } #eosub--_initialize
 #--------------------------------------------------------------------
 
+sub get_create_database_sql {
+    my ($self, $name) = @_;
+    # We only create as utf8 if we have no params (meaning we're doing
+    # a new installation) or if the utf8 param is on.
+    my $create_utf8 = Bugzilla->params->{'utf8'}
+                      || !defined Bugzilla->params->{'utf8'};
+    my $charset = $create_utf8 ? "ENCODING 'UTF8' TEMPLATE template0" : '';
+    return ("CREATE DATABASE $name $charset");
+}
+
 sub get_rename_column_ddl {
     my ($self, $table, $old_name, $new_name) = @_;
     if (lc($old_name) eq lc($new_name)) {
index dbee5df..81677c7 100644 (file)
@@ -1016,7 +1016,11 @@ sub create {
     # the parameter isn't sent to create().
     $params->{sortkey} = undef if !exists $params->{sortkey};
     $params->{type} ||= 0;
-    
+    # We mark the custom field as obsolete till it has been fully created,
+    # to avoid race conditions when viewing bugs at the same time.
+    my $is_obsolete = $params->{obsolete};
+    $params->{obsolete} = 1 if $params->{custom};
+
     $dbh->bz_start_transaction();
     $class->check_required_create_fields(@_);
     my $field_values      = $class->run_create_validators($params);
@@ -1045,6 +1049,10 @@ sub create {
             # Insert a default value of "---" into the legal values table.
             $dbh->do("INSERT INTO $name (value) VALUES ('---')");
         }
+
+        # Restore the original obsolete state of the custom field.
+        $dbh->do('UPDATE fielddefs SET obsolete = 0 WHERE id = ?', undef, $field->id)
+          unless $is_obsolete;
     }
 
     return $field;
index 87354a1..3292536 100644 (file)
@@ -183,6 +183,7 @@ sub is_set_on_bug {
     # This allows bug/create/create.html.tmpl to pass in a hashref that 
     # looks like a bug object.
     my $value = blessed($bug) ? $bug->$field_name : $bug->{$field_name};
+    $value = $value->name if blessed($value);
     return 0 if !defined $value;
 
     if ($self->field->type == FIELD_TYPE_BUG_URLS
index b30065a..811530c 100644 (file)
@@ -95,7 +95,7 @@ use constant VALIDATORS => {
     description      => \&_check_description,
     cc_list          => \&_check_cc_list,
     target_type      => \&_check_target_type,
-    sortkey          => \&_check_sortey,
+    sortkey          => \&_check_sortkey,
     is_active        => \&Bugzilla::Object::check_boolean,
     is_requestable   => \&Bugzilla::Object::check_boolean,
     is_requesteeble  => \&Bugzilla::Object::check_boolean,
@@ -325,7 +325,7 @@ sub _check_target_type {
     return $target_type;
 }
 
-sub _check_sortey {
+sub _check_sortkey {
     my ($invocant, $sortkey) = @_;
 
     (detaint_natural($sortkey) && $sortkey <= MAX_SMALLINT)
@@ -681,7 +681,10 @@ sub sqlify_criteria {
     }
     if ($criteria->{product_id}) {
         my $product_id = $criteria->{product_id};
-        
+        detaint_natural($product_id)
+          || ThrowCodeError('bad_arg', { argument => 'product_id',
+                                         function => 'Bugzilla::FlagType::sqlify_criteria' });
+
         # Add inclusions to the query, which simply involves joining the table
         # by flag type ID and target product/component.
         push(@$tables, "INNER JOIN flaginclusions AS i ON flagtypes.id = i.type_id");
@@ -698,6 +701,10 @@ sub sqlify_criteria {
         my $addl_join_clause = "";
         if ($criteria->{component_id}) {
             my $component_id = $criteria->{component_id};
+            detaint_natural($component_id)
+              || ThrowCodeError('bad_arg', { argument => 'component_id',
+                                             function => 'Bugzilla::FlagType::sqlify_criteria' });
+
             push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
             $join_clause .= "AND (e.component_id = $component_id OR e.component_id IS NULL) ";
         }
@@ -711,7 +718,10 @@ sub sqlify_criteria {
     }
     if ($criteria->{group}) {
         my $gid = $criteria->{group};
-        detaint_natural($gid);
+        detaint_natural($gid)
+          || ThrowCodeError('bad_arg', { argument => 'group',
+                                         function => 'Bugzilla::FlagType::sqlify_criteria' });
+
         push(@criteria, "(flagtypes.grant_group_id = $gid " .
                         " OR flagtypes.request_group_id = $gid)");
     }
index b7532fe..3824077 100644 (file)
@@ -189,7 +189,9 @@ sub check_members_are_visible {
     my $self = shift;
     my $user = Bugzilla->user;
     return if !Bugzilla->params->{'usevisibilitygroups'};
-    my $is_visible = grep { $_->id == $_ } @{ $user->visible_groups_inherited };
+
+    my $group_id = $self->id;
+    my $is_visible = grep { $_ == $group_id } @{ $user->visible_groups_inherited };
     if (!$is_visible) {
         ThrowUserError('group_not_visible', { group => $self });
     }
index da17946..c658989 100644 (file)
@@ -426,6 +426,12 @@ Sometimes this is C<undef>, meaning that we are parsing text that is
 not a bug comment (but could still be some other part of a bug, like
 the summary line).
 
+=item C<user>
+
+The L<Bugzilla::User> object representing the user who will see the text.
+This is useful to determine how much confidential information can be displayed
+to the user.
+
 =back
 
 =head2 buglist_columns
index 6b9dd65..3ac8377 100644 (file)
@@ -3538,9 +3538,6 @@ sub _migrate_user_tags {
                                      VALUES (?, ?)');
     my $sth_nq = $dbh->prepare('UPDATE namedqueries SET query = ?
                                 WHERE id = ?');
-    my $sth_nq_footer = $dbh->prepare(
-        'DELETE FROM namedqueries_link_in_footer 
-               WHERE user_id = ? AND namedquery_id = ?');
 
     if (scalar @$tags) {
         print install_string('update_queries_to_tags'), "\n";
@@ -3580,13 +3577,11 @@ sub _migrate_user_tags {
             next if !$bug_id;
             $sth_bug_tag->execute($bug_id, $tag_id);
         }
-        
+
         # Existing tags may be used in whines, or shared with
         # other users. So we convert them rather than delete them.
         $uri->query_param('tag', $tag_name);
         $sth_nq->execute($uri->query, $query_id);
-        # But we don't keep showing them in the footer.
-        $sth_nq_footer->execute($user_id, $query_id);
     }
 
     $dbh->bz_commit_transaction();
index 1e7fc97..83723b3 100644 (file)
@@ -32,6 +32,13 @@ use List::Util qw(max);
 use Safe;
 use Term::ANSIColor;
 
+# Return::Value 1.666002 pollutes the error log with warnings about this
+# deprecated module. We have to set NO_CLUCK = 1 before loading Email::Send
+# in have_vers() to disable these warnings.
+BEGIN {
+    $Return::Value::NO_CLUCK = 1;
+}
+
 use base qw(Exporter);
 our @EXPORT = qw(
     REQUIRED_MODULES
@@ -547,26 +554,6 @@ sub print_module_instructions {
         ( (!$output and @{$check_results->{missing}})
           or ($output and $check_results->{any_missing}) ) ? 1 : 0;
 
-    # We only print the PPM repository note if we have to.
-    my $perl_ver = sprintf('%vd', $^V);
-    if ($need_module_instructions && ON_ACTIVESTATE && vers_cmp($perl_ver, '5.12') < 0) {
-        # URL when running Perl 5.8.x.
-        my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
-        # Packages for Perl 5.10 are not compatible with Perl 5.8.
-        if (vers_cmp($perl_ver, '5.10') > -1) {
-            $url_to_theory58S = 'http://cpan.uwinnipeg.ca/PPMPackages/10xx/';
-        }
-        print colored(
-            install_string('ppm_repo_add', 
-                           { theory_url => $url_to_theory58S }),
-            COLOR_ERROR);
-
-        # ActivePerls older than revision 819 require an additional command.
-        if (ON_ACTIVESTATE < 819) {
-            print install_string('ppm_repo_up');
-        }
-    }
-
     if ($need_module_instructions or @{ $check_results->{apache} }) {
         # If any output was required, we want to close the "table"
         print "*" x TABLE_WIDTH . "\n";
index 7e42cb6..1c4fb61 100644 (file)
@@ -48,6 +48,12 @@ use Encode qw(encode);
 use Encode::MIME::Header;
 use Email::Address;
 use Email::MIME;
+# Return::Value 1.666002 pollutes the error log with warnings about this
+# deprecated module. We have to set NO_CLUCK = 1 before loading Email::Send
+# to disable these warnings.
+BEGIN {
+    $Return::Value::NO_CLUCK = 1;
+}
 use Email::Send;
 
 sub MessageToMTA {
index 422a2ff..d4574ab 100644 (file)
@@ -801,7 +801,7 @@ your own C<DB_COLUMNS> subroutine in a subclass.)
 The name of the column that should be considered to be the unique
 "name" of this object. The 'name' is a B<string> that uniquely identifies
 this Object in the database. Defaults to 'name'. When you specify 
-C<{name => $name}> to C<new()>, this is the column that will be 
+C<< {name => $name} >> to C<new()>, this is the column that will be 
 matched against in the DB.
 
 =item C<ID_FIELD>
@@ -964,7 +964,7 @@ for each placeholder in C<condition>, in order.
 
 This is to allow subclasses to have complex parameters, and then to
 translate those parameters into C<condition> and C<values> when they
-call C<$self->SUPER::new> (which is this function, usually).
+call C<< $self->SUPER::new >> (which is this function, usually).
 
 If you try to call C<new> outside of a subclass with the C<condition>
 and C<values> parameters, Bugzilla will throw an error. These parameters
@@ -1089,8 +1089,9 @@ Notes:       In order for this function to work in your subclass,
              your subclass's L</ID_FIELD> must be of C<SERIAL>
              type in the database.
 
-             Subclass Implementors: This function basically just
-             calls L</check_required_create_fields>, then
+Subclass Implementors:
+             This function basically just calls 
+             L</check_required_create_fields>, then
              L</run_create_validators>, and then finally
              L</insert_create_data>. So if you have a complex system that
              you need to implement, you can do it by calling these
@@ -1283,9 +1284,9 @@ C<0> otherwise.
 
  Returns:     A list of objects, or an empty list if there are none.
 
- Notes:       Note that you must call this as C<$class->get_all>. For 
-              example, C<Bugzilla::Keyword->get_all>
-              C<Bugzilla::Keyword::get_all> will not work.
+ Notes:       Note that you must call this as $class->get_all. For 
+              example, Bugzilla::Keyword->get_all
+              Bugzilla::Keyword::get_all will not work.
 
 =back
 
index 1097b32..8e419c0 100644 (file)
@@ -290,12 +290,14 @@ use constant OPERATOR_FIELD_OVERRIDE => {
     },
     dependson        => MULTI_SELECT_OVERRIDE,
     keywords         => MULTI_SELECT_OVERRIDE,
-    'flagtypes.name' => MULTI_SELECT_OVERRIDE,
+    'flagtypes.name' => {
+        _non_changed => \&_flagtypes_nonchanged,
+    },
     longdesc => {
-        %{ MULTI_SELECT_OVERRIDE() },
         changedby     => \&_long_desc_changedby,
         changedbefore => \&_long_desc_changedbefore_after,
         changedafter  => \&_long_desc_changedbefore_after,
+        _non_changed  => \&_long_desc_nonchanged,
     },
     'longdescs.count' => {
         changedby     => \&_long_desc_changedby,
@@ -690,8 +692,16 @@ sub sql {
     my ($self) = @_;
     return $self->{sql} if $self->{sql};
     my $dbh = Bugzilla->dbh;
-    
+
     my ($joins, $clause) = $self->_charts_to_conditions();
+
+    if (!$clause->as_string
+        && !Bugzilla->params->{'search_allow_no_criteria'}
+        && !$self->{allow_unlimited})
+    {
+        ThrowUserError('buglist_parameters_required');
+    }
+
     my $select = join(', ', $self->_sql_select);
     my $from = $self->_sql_from($joins);
     my $where = $self->_sql_where($clause);
@@ -822,28 +832,47 @@ sub _add_extra_column {
 }
 
 # These are the columns that we're going to be actually SELECTing.
+sub _display_columns {
+    my ($self) = @_;
+    return @{ $self->{display_columns} } if $self->{display_columns};
+
+    # Do not alter the list from _input_columns at all, even if there are
+    # duplicated columns. Those are passed by the caller, and the caller
+    # expects to get them back in the exact same order.
+    my @columns = $self->_input_columns;
+
+    # Only add columns which are not already listed.
+    my %list = map { $_ => 1 } @columns;
+    foreach my $column ($self->_extra_columns) {
+        push(@columns, $column) unless $list{$column}++;
+    }
+    $self->{display_columns} = \@columns;
+    return @{ $self->{display_columns} };
+}
+
+# These are the columns that are involved in the query.
 sub _select_columns {
     my ($self) = @_;
     return @{ $self->{select_columns} } if $self->{select_columns};
 
     my @select_columns;
-    foreach my $column ($self->_input_columns, $self->_extra_columns) {
+    foreach my $column ($self->_display_columns) {
         if (my $add_first = COLUMN_DEPENDS->{$column}) {
             push(@select_columns, @$add_first);
         }
         push(@select_columns, $column);
     }
-    
+    # Remove duplicated columns.
     $self->{select_columns} = [uniq @select_columns];
     return @{ $self->{select_columns} };
 }
 
-# This takes _select_columns and translates it into the actual SQL that
+# This takes _display_columns and translates it into the actual SQL that
 # will go into the SELECT clause.
 sub _sql_select {
     my ($self) = @_;
     my @sql_fields;
-    foreach my $column ($self->_select_columns) {
+    foreach my $column ($self->_display_columns) {
         my $alias = $column;
         # Aliases cannot contain dots in them. We convert them to underscores.
         $alias =~ s/\./_/g;
@@ -1172,14 +1201,7 @@ sub _sql_where {
     # SQL a bit more readable for debugging.
     my $where = join("\n   AND ", $self->_standard_where);
     my $clause_sql = $main_clause->as_string;
-    if ($clause_sql) {
-        $where .= "\n   AND " . $clause_sql;
-    }
-    elsif (!Bugzilla->params->{'search_allow_no_criteria'}
-           && !$self->{allow_unlimited})
-    {
-        ThrowUserError('buglist_parameters_required');
-    }
+    $where .= "\n   AND " . $clause_sql if $clause_sql;
     return $where;
 }
 
@@ -1670,6 +1692,7 @@ sub _handle_chart {
         value      => $string_value,
         all_values => $value,
         joins      => [],
+        condition  => $condition,
     );
     $search_args{quoted} = $self->_quote_unless_numeric(\%search_args);
     # This should add a "term" selement to %search_args.
@@ -1747,7 +1770,9 @@ sub do_search_function {
 sub _do_operator_function {
     my ($self, $func_args) = @_;
     my $operator = $func_args->{operator};
-    my $operator_func = OPERATORS->{$operator};
+    my $operator_func = OPERATORS->{$operator}
+      || ThrowCodeError("search_field_operator_unsupported",
+                        { operator => $operator });
     $self->$operator_func($func_args);
 }
 
@@ -1840,8 +1865,14 @@ sub _quote_unless_numeric {
 }
 
 sub build_subselect {
-    my ($outer, $inner, $table, $cond) = @_;
-    return "$outer IN (SELECT $inner FROM $table WHERE $cond)";
+    my ($outer, $inner, $table, $cond, $negate) = @_;
+    # Execute subselects immediately to avoid dependent subqueries, which are
+    # large performance hits on MySql
+    my $q = "SELECT DISTINCT $inner FROM $table WHERE $cond";
+    my $dbh = Bugzilla->dbh;
+    my $list = $dbh->selectcol_arrayref($q);
+    return $negate ? "1=1" : "1=2" unless @$list;
+    return $dbh->sql_in($outer, $list, $negate);
 }
 
 # Used by anyexact to get the list of input values. This allows us to
@@ -2029,8 +2060,8 @@ sub _contact_pronoun {
     my ($self, $args) = @_;
     my $value = $args->{value};
     my $user = $self->_user;
-    
-    if ($value =~ /^\%group/) {
+
+    if ($value =~ /^\%group\.[^%]+%$/) {
         $self->_contact_exact_group($args);
     }
     elsif ($value =~ /^(%\w+%)$/) {
@@ -2047,11 +2078,17 @@ sub _contact_exact_group {
     my $dbh = Bugzilla->dbh;
     my $user = $self->_user;
     
+    # We already know $value will match this regexp, else we wouldn't be here.
     $value =~ /\%group\.([^%]+)%/;
-    my $group = Bugzilla::Group->check({ name => $1, _error => 'invalid_group_name' });
-    $group->check_members_are_visible();
+    my $group_name = $1;
+    my $group = Bugzilla::Group->check({ name => $group_name, _error => 'invalid_group_name' });
+    # Pass $group_name instead of $group->name to the error message
+    # to not leak the existence of the group.
     $user->in_group($group)
-      || ThrowUserError('invalid_group_name', {name => $group->name});
+      || ThrowUserError('invalid_group_name', { name => $group_name });
+    # Now that we know the user belongs to this group, it's safe
+    # to disclose more information.
+    $group->check_members_are_visible();
 
     my $group_ids = Bugzilla::Group->flatten_group_membership($group->id);
     my $table = "user_group_map_$chart_id";
@@ -2242,7 +2279,7 @@ sub _user_nonchanged {
             my $table = $first_join->{table};
             my $columns = "bug_id";
             $columns .= ",isprivate" if @{ $first_join->{extra} };
-            my $new_table = "SELECT $columns FROM $table AS $as $join_sql";
+            my $new_table = "SELECT DISTINCT $columns FROM $table AS $as $join_sql";
             $first_join->{table} = "($new_table)";
             # We always want to LEFT JOIN the generated table.
             delete $first_join->{join};
@@ -2292,6 +2329,48 @@ sub _long_desc_changedbefore_after {
     };
     push(@$joins, $join);
     $args->{term} = "$table.bug_when IS NOT NULL";
+
+    # If the user is not part of the insiders group, they cannot see
+    # private comments
+    if (!$self->_user->is_insider) {
+        $args->{term} .= " AND $table.isprivate = 0";
+    }
+}
+
+sub _long_desc_nonchanged {
+    my ($self, $args) = @_;
+    my ($chart_id, $operator, $value, $joins) =
+        @$args{qw(chart_id operator value joins)};
+    my $dbh = Bugzilla->dbh;
+
+    my $table = "longdescs_$chart_id";
+    my $join_args = {
+        chart_id   => $chart_id,
+        sequence   => $chart_id,
+        field      => 'longdesc',
+        full_field => "$table.thetext",
+        operator   => $operator,
+        value      => $value,
+        all_values => $value,
+        quoted     => $dbh->quote($value),
+        joins      => [],
+    };
+    $self->_do_operator_function($join_args);
+
+    # If the user is not part of the insiders group, they cannot see
+    # private comments
+    if (!$self->_user->is_insider) {
+        $join_args->{term} .= " AND $table.isprivate = 0";
+    }
+
+    my $join = {
+        table => 'longdescs',
+        as    => $table,
+        extra => [ $join_args->{term} ],
+    };
+    push(@$joins, $join);
+
+    $args->{term} =  "$table.comment_id IS NOT NULL";
 }
 
 sub _content_matches {
@@ -2299,9 +2378,9 @@ sub _content_matches {
     my ($chart_id, $joins, $fields, $operator, $value) =
         @$args{qw(chart_id joins fields operator value)};
     my $dbh = Bugzilla->dbh;
-    
+
     # "content" is an alias for columns containing text for which we
-    # can search a full-text index and retrieve results by relevance, 
+    # 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
@@ -2315,9 +2394,9 @@ sub _content_matches {
     
     # Create search terms to add to the SELECT and WHERE clauses.
     my ($term1, $rterm1) =
-        $dbh->sql_fulltext_search("$table.$comments_col", $value, 1);
+        $dbh->sql_fulltext_search("$table.$comments_col", $value);
     my ($term2, $rterm2) =
-        $dbh->sql_fulltext_search("$table.short_desc", $value, 2);
+        $dbh->sql_fulltext_search("$table.short_desc", $value);
     $rterm1 = $term1 if !$rterm1;
     $rterm2 = $term2 if !$rterm2;
 
@@ -2534,6 +2613,7 @@ sub _multiselect_multiple {
     
     my @terms;
     foreach my $word (@words) {
+        next if $word eq '';
         $args->{value} = $word;
         $args->{quoted} = $dbh->quote($word);
         push(@terms, $self->_multiselect_term($args));
@@ -2548,6 +2628,53 @@ sub _multiselect_multiple {
     }
 }
 
+sub _flagtypes_nonchanged {
+    my ($self, $args) = @_;
+    my ($chart_id, $operator, $value, $joins, $condition) =
+        @$args{qw(chart_id operator value joins condition)};
+    my $dbh = Bugzilla->dbh;
+
+    # For 'not' operators, we need to negate the whole term.
+    # If you search for "Flags" (does not contain) "approval+" we actually want
+    # to return *bugs* that don't contain an approval+ flag.  Without rewriting
+    # the negation we'll search for *flags* which don't contain approval+.
+    if ($operator =~ s/^not//) {
+        $args->{operator} = $operator;
+        $condition->operator($operator);
+        $condition->negate(1);
+    }
+
+    my $subselect_args = {
+        chart_id   => $chart_id,
+        sequence   => $chart_id,
+        field      => 'flagtypes.name',
+        full_field =>  $dbh->sql_string_concat("flagtypes_$chart_id.name", "flags_$chart_id.status"),
+        operator   => $operator,
+        value      => $value,
+        all_values => $value,
+        quoted     => $dbh->quote($value),
+        joins      => [],
+    };
+    $self->_do_operator_function($subselect_args);
+    my $subselect_term = $subselect_args->{term};
+
+    # don't call build_subselect as this must run as a true sub-select
+    $args->{term} = "EXISTS (
+        SELECT 1
+          FROM bugs bugs_$chart_id
+          LEFT JOIN attachments AS attachments_$chart_id
+                    ON bugs_$chart_id.bug_id = attachments_$chart_id.bug_id
+          LEFT JOIN flags AS flags_$chart_id
+                    ON bugs_$chart_id.bug_id = flags_$chart_id.bug_id
+                       AND (flags_$chart_id.attach_id = attachments_$chart_id.attach_id
+                            OR flags_$chart_id.attach_id IS NULL)
+          LEFT JOIN flagtypes AS flagtypes_$chart_id
+                    ON flags_$chart_id.type_id = flagtypes_$chart_id.id
+     WHERE bugs_$chart_id.bug_id = bugs.bug_id
+           AND $subselect_term
+    )";
+}
+
 sub _multiselect_nonchanged {
     my ($self, $args) = @_;
     my ($chart_id, $joins, $field, $operator) =
@@ -2625,8 +2752,7 @@ sub _multiselect_term {
     my $term = $args->{term};
     $term .= $args->{_extra_where} || '';
     my $select = $args->{_select_field} || 'bug_id';
-    my $not_sql = $not ? "NOT " : '';
-    return "bugs.bug_id ${not_sql}IN (SELECT $select FROM $table WHERE $term)";
+    return build_subselect("bugs.bug_id", $select, $table, $term, $not);
 }
 
 ###############################
@@ -2701,15 +2827,14 @@ sub _anyexact {
 
 sub _anywordsubstr {
     my ($self, $args) = @_;
-    my ($full_field, $value) = @$args{qw(full_field value)};
-    
+
     my @terms = $self->_substring_terms($args);
     $args->{term} = join("\n\tOR ", @terms);
 }
 
 sub _allwordssubstr {
     my ($self, $args) = @_;
-    
+
     my @terms = $self->_substring_terms($args);
     $args->{term} = join("\n\tAND ", @terms);
 }
@@ -2774,8 +2899,10 @@ sub _changedbefore_changedafter {
         extra => ["$table.fieldid = $field_id",
                   "$table.bug_when $sql_operator $sql_date"],
     };
-    push(@$joins, $join);
+
     $args->{term} = "$table.bug_when IS NOT NULL";
+    $self->_changed_security_check($args, $join);
+    push(@$joins, $join);
 }
 
 sub _changedfrom_changedto {
@@ -2794,9 +2921,10 @@ sub _changedfrom_changedto {
         extra => ["$table.fieldid = $field_id",
                   "$table.$column = $quoted"],
     };
-    push(@$joins, $join);
 
     $args->{term} = "$table.bug_when IS NOT NULL";
+    $self->_changed_security_check($args, $join);
+    push(@$joins, $join);
 }
 
 sub _changedby {
@@ -2815,8 +2943,32 @@ sub _changedby {
         extra => ["$table.fieldid = $field_id",
                   "$table.who = $user_id"],
     };
-    push(@$joins, $join);
+
     $args->{term} = "$table.bug_when IS NOT NULL";
+    $self->_changed_security_check($args, $join);
+    push(@$joins, $join);
+}
+
+sub _changed_security_check {
+    my ($self, $args, $join) = @_;
+    my ($chart_id, $field) = @$args{qw(chart_id field)};
+
+    my $field_object = $self->_chart_fields->{$field}
+        || ThrowCodeError("invalid_field_name", { field => $field });
+    my $field_id = $field_object->id;
+
+    # If the user is not part of the insiders group, they cannot see
+    # changes to attachments (including attachment flags) that are private
+    if ($field =~ /^(?:flagtypes\.name$|attach)/ and !$self->_user->is_insider) {
+        $join->{then_to} = {
+            as    => "attach_${field_id}_$chart_id",
+            table => 'attachments',
+            from  => "act_${field_id}_$chart_id.attach_id",
+            to    => 'attach_id',
+        };
+
+        $args->{term} .= " AND COALESCE(attach_${field_id}_$chart_id.isprivate, 0) = 0";
+    }
 }
 
 ######################
index a068ce5..5f5ea5b 100644 (file)
@@ -93,25 +93,29 @@ sub walk_conditions {
 
 sub as_string {
     my ($self) = @_;
-    my @strings;
-    foreach my $child (@{ $self->children }) {
-        next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
-        next if $child->isa('Bugzilla::Search::Condition')
-                && !$child->translated;
+    if (!$self->{sql}) {
+        my @strings;
+        foreach my $child (@{ $self->children }) {
+            next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
+            next if $child->isa('Bugzilla::Search::Condition')
+                    && !$child->translated;
 
-        my $string = $child->as_string;
-        if ($self->joiner eq 'AND') {
-            $string = "( $string )" if $string =~ /OR/;
-        }
-        else {
-            $string = "( $string )" if $string =~ /AND/;
+            my $string = $child->as_string;
+            next unless $string;
+            if ($self->joiner eq 'AND') {
+                $string = "( $string )" if $string =~ /OR/;
+            }
+            else {
+                $string = "( $string )" if $string =~ /AND/;
+            }
+            push(@strings, $string);
         }
-        push(@strings, $string);
+
+        my $sql = join(' ' . $self->joiner . ' ', @strings);
+        $sql = "NOT( $sql )" if $sql && $self->negate;
+        $self->{sql} = $sql;
     }
-    
-    my $sql = join(' ' . $self->joiner . ' ', @strings);
-    $sql = "NOT( $sql )" if $sql && $self->negate;
-    return $sql;
+    return $self->{sql};
 }
 
 # Search.pm converts URL parameters to Clause objects. This helps do the
index 2268da1..167b4f0 100644 (file)
@@ -32,9 +32,16 @@ sub new {
 }
 
 sub field    { return $_[0]->{field}    }
-sub operator { return $_[0]->{operator} }
 sub value    { return $_[0]->{value}    }
 
+sub operator {
+    my ($self, $value) = @_;
+    if (@_ == 2) {
+        $self->{operator} = $value;
+    }
+    return $self->{operator};
+}
+
 sub fov {
     my ($self) = @_;
     return ($self->field, $self->operator, $self->value);
index 7424f83..fd9d796 100644 (file)
@@ -377,9 +377,14 @@ sub _handle_field_names {
 
     # Flag and requestee shortcut
     if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
-        addChart('flagtypes.name', 'substring', $1, $negate);
-        $chart++; $and = $or = 0; # Next chart for boolean AND
-        addChart('requestees.login_name', 'substring', $2, $negate);
+        my ($flagtype, $requestee) = ($1, $2);
+        addChart('flagtypes.name', 'substring', $flagtype, $negate);
+        if ($requestee) {
+            # AND
+            $chart++;
+            $and = $or = 0;
+            addChart('requestees.login_name', 'substring', $requestee, $negate);
+        }
         return 1;
     }
 
index fc773fc..9919411 100644 (file)
@@ -109,7 +109,7 @@ sub check {
     if (!$search->shared_with_group
         or !$user->in_group($search->shared_with_group)) 
     {
-        ThrowUserError('missing_query', { queryname => $search->name, 
+        ThrowUserError('missing_query', { name => $search->name,
                                           sharer_id => $search->user->id });
     }
 
index 48b871b..cd75079 100644 (file)
@@ -69,7 +69,7 @@ use constant FORMAT_2_SIZE => [19,55];
 # Pseudo-constant.
 sub SAFE_URL_REGEXP {
     my $safe_protocols = join('|', SAFE_PROTOCOLS);
-    return qr/($safe_protocols):[^\s<>\"]+[\w\/]/i;
+    return qr/($safe_protocols):[^:\s<>\"][^\s<>\"]+[\w\/]/i;
 }
 
 # Convert the constants in the Bugzilla::Constants module into a hash we can
@@ -154,8 +154,9 @@ sub get_format {
 # If you want to modify this routine, read the comments carefully
 
 sub quoteUrls {
-    my ($text, $bug, $comment) = (@_);
+    my ($text, $bug, $comment, $user) = @_;
     return $text unless $text;
+    $user ||= Bugzilla->user;
 
     # We use /g for speed, but uris can have other things inside them
     # (http://foo/bug#3 for example). Filtering that out filters valid
@@ -185,7 +186,7 @@ sub quoteUrls {
     my @hook_regexes;
     Bugzilla::Hook::process('bug_format_comment',
         { text => \$text, bug => $bug, regexes => \@hook_regexes,
-          comment => $comment });
+          comment => $comment, user => $user });
 
     foreach my $re (@hook_regexes) {
         my ($match, $replace) = @$re{qw(match replace)};
@@ -207,7 +208,7 @@ sub quoteUrls {
         map { qr/$_/ } grep($_, Bugzilla->params->{'urlbase'}, 
                             Bugzilla->params->{'sslbase'})) . ')';
     $text =~ s~\b(${urlbase_re}\Qshow_bug.cgi?id=\E([0-9]+)(\#c([0-9]+))?)\b
-              ~($things[$count++] = get_bug_link($3, $1, { comment_num => $5 })) &&
+              ~($things[$count++] = get_bug_link($3, $1, { comment_num => $5, user => $user })) &&
                ("\0\0" . ($count-1) . "\0\0")
               ~egox;
 
@@ -236,7 +237,7 @@ sub quoteUrls {
 
     # attachment links
     $text =~ s~\b(attachment\s*\#?\s*(\d+)(?:\s+\[details\])?)
-              ~($things[$count++] = get_attachment_link($2, $1)) &&
+              ~($things[$count++] = get_attachment_link($2, $1, $user)) &&
                ("\0\0" . ($count-1) . "\0\0")
               ~egmxi;
 
@@ -253,7 +254,7 @@ sub quoteUrls {
     $text =~ s~\b($bug_re(?:\s*,?\s*$comment_re)?|$comment_re)
               ~ # We have several choices. $1 here is the link, and $2-4 are set
                 # depending on which part matched
-               (defined($2) ? get_bug_link($2, $1, { comment_num => $3 }) :
+               (defined($2) ? get_bug_link($2, $1, { comment_num => $3, user => $user }) :
                               "<a href=\"$current_bugurl#c$4\">$1</a>")
               ~egox;
 
@@ -262,7 +263,7 @@ sub quoteUrls {
     $text =~ s~(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )
                (\d+)
                (?=\ \*\*\*\Z)
-              ~get_bug_link($1, $1)
+              ~get_bug_link($1, $1, { user => $user })
               ~egmx;
 
     # Now remove the encoding hacks in reverse order
@@ -276,15 +277,18 @@ sub quoteUrls {
 
 # Creates a link to an attachment, including its title.
 sub get_attachment_link {
-    my ($attachid, $link_text) = @_;
+    my ($attachid, $link_text, $user) = @_;
     my $dbh = Bugzilla->dbh;
+    $user ||= Bugzilla->user;
 
     my $attachment = new Bugzilla::Attachment($attachid);
 
     if ($attachment) {
         my $title = "";
         my $className = "";
-        if (Bugzilla->user->can_see_bug($attachment->bug_id)) {
+        if ($user->can_see_bug($attachment->bug_id)
+            && (!$attachment->isprivate || $user->is_insider))
+        {
             $title = $attachment->description;
         }
         if ($attachment->isobsolete) {
@@ -324,6 +328,7 @@ sub get_attachment_link {
 sub get_bug_link {
     my ($bug, $link_text, $options) = @_;
     $options ||= {};
+    $options->{user} ||= Bugzilla->user;
     my $dbh = Bugzilla->dbh;
 
     if (defined $bug) {
@@ -700,10 +705,10 @@ sub create {
             clean_text => \&Bugzilla::Util::clean_text ,
 
             quoteUrls => [ sub {
-                               my ($context, $bug, $comment) = @_;
+                               my ($context, $bug, $comment, $user) = @_;
                                return sub {
                                    my $text = shift;
-                                   return quoteUrls($text, $bug, $comment);
+                                   return quoteUrls($text, $bug, $comment, $user);
                                };
                            },
                            1
@@ -719,10 +724,9 @@ sub create {
                           1
                         ],
 
-            bug_list_link => sub
-            {
-                my $buglist = shift;
-                return join(", ", map(get_bug_link($_, $_), split(/ *, */, $buglist)));
+            bug_list_link => sub {
+                my ($buglist, $options) = @_;
+                return join(", ", map(get_bug_link($_, $_, $options), split(/ *, */, $buglist)));
             },
 
             # In CSV, quotes are doubled, and any value containing a quote or a
@@ -968,7 +972,7 @@ sub create {
                 }
                 return \@optional;
             },
-            'default_authorizer' => new Bugzilla::Auth(),
+            'default_authorizer' => sub { return Bugzilla::Auth->new() },
         },
     };
 
index 2bb68e7..9c2242f 100644 (file)
@@ -275,13 +275,18 @@ sub Cancel {
 
     # Get information about the token being canceled.
     trick_taint($token);
-    my ($issuedate, $tokentype, $eventdata, $userid) =
-        $dbh->selectrow_array('SELECT ' . $dbh->sql_date_format('issuedate') . ',
+    my ($db_token, $issuedate, $tokentype, $eventdata, $userid) =
+        $dbh->selectrow_array('SELECT token, ' . $dbh->sql_date_format('issuedate') . ',
                                       tokentype, eventdata, userid
                                  FROM tokens
                                 WHERE token = ?',
                                 undef, $token);
 
+    # Some DBs such as MySQL are case-insensitive by default so we do
+    # a quick comparison to make sure the tokens are indeed the same.
+    (defined $db_token && $db_token eq $token)
+        || ThrowCodeError("cancel_token_does_not_exist");
+
     # If we are canceling the creation of a new user account, then there
     # is no entry in the 'profiles' table.
     my $user = new Bugzilla::User($userid);
@@ -346,10 +351,17 @@ sub GetTokenData {
     $token = clean_text($token);
     trick_taint($token);
 
-    return $dbh->selectrow_array(
-        "SELECT userid, " . $dbh->sql_date_format('issuedate') . ", eventdata 
-         FROM   tokens 
+    my @token_data = $dbh->selectrow_array(
+        "SELECT token, userid, " . $dbh->sql_date_format('issuedate') . ", eventdata
+         FROM   tokens
          WHERE  token = ?", undef, $token);
+
+    # Some DBs such as MySQL are case-insensitive by default so we do
+    # a quick comparison to make sure the tokens are indeed the same.
+    my $db_token = shift @token_data;
+    return undef if (!defined $db_token || $db_token ne $token);
+
+    return @token_data;
 }
 
 # Deletes specified token
index 391e416..0bc49d9 100644 (file)
@@ -1069,7 +1069,7 @@ sub get_accessible_products {
                        @{$self->get_selectable_products},
                        @{$self->get_enterable_products};
     
-    return [ values %products ];
+    return [ sort { $a->name cmp $b->name } values %products ];
 }
 
 sub check_can_admin_product {
@@ -1528,6 +1528,8 @@ sub match_field {
         my @logins;
         for my $query (@queries) {
             $query = trim($query);
+            next if $query eq '';
+
             my $users = match(
                 $query,   # match string
                 $limit,   # match limit
@@ -2068,7 +2070,7 @@ sub validate_password {
     my $complexity_level = Bugzilla->params->{password_complexity};
     if ($complexity_level eq 'letters_numbers_specialchars') {
         ThrowUserError('password_not_complex')
-          if ($password !~ /\w/ || $password !~ /\d/ || $password !~ /[[:punct:]]/);
+          if ($password !~ /[[:alpha:]]/ || $password !~ /\d/ || $password !~ /[[:punct:]]/);
     } elsif ($complexity_level eq 'letters_numbers') {
         ThrowUserError('password_not_complex')
           if ($password !~ /[[:lower:]]/ || $password !~ /[[:upper:]]/ || $password !~ /\d/);
@@ -2199,6 +2201,35 @@ Returns a hashref with tag IDs as key, and a hashref with tag 'id',
 
 =back
 
+=head2 Saved Recent Bug Lists
+
+=over
+
+=item C<recent_searches>
+
+Returns an arrayref of L<Bugzilla::Search::Recent> objects
+containing the user's recent searches.
+
+=item C<recent_search_containing(bug_id)>
+
+Returns a L<Bugzilla::Search::Recent> object that contains the most recent
+search by the user for the specified bug id. Retuns undef if no match is found.
+
+=item C<recent_search_for(bug)>
+
+Returns a L<Bugzilla::Search::Recent> object that contains a search by the
+user. Uses the list_id of the current loaded page, or the referrer page, and
+the bug id if that fails. Finally it will check the BUGLIST cookie, and create
+an object based on that, or undef if it does not exist.
+
+=item C<save_last_search>
+
+Saves the users most recent search in the database if logged in, or in the
+BUGLIST cookie if not logged in. Parameters are bug_ids, order, vars and
+list_id.
+
+=back
+
 =head2 Account Lockout
 
 =over
@@ -2397,7 +2428,8 @@ the database again. Used mostly by L<Bugzilla::Product>.
 
 =item C<can_enter_product($product_name, $warn)>
 
- Description: Returns 1 if the user can enter bugs into the specified product.
+ Description: Returns a product object if the user can enter bugs into the
+              specified product.
               If the user cannot enter bugs into the product, the behavior of
               this method depends on the value of $warn:
               - if $warn is false (or not given), a 'false' value is returned;
@@ -2408,7 +2440,7 @@ the database again. Used mostly by L<Bugzilla::Product>.
                               must be thrown if the user cannot enter bugs
                               into the specified product.
 
- Returns:     1 if the user can enter bugs into the product,
+ Returns:     A product object if the user can enter bugs into the product,
               0 if the user cannot enter bugs into the product and if $warn
               is false (an error is thrown if $warn is true).
 
index 78e64c9..958a955 100644 (file)
@@ -391,10 +391,10 @@ Description: Determines if a given setting exists in the database.
 Params:      C<$setting_name> - string - the setting name
 Returns:     boolean - true if the setting already exists in the DB.
 
-=back
-
 =end private
 
+=back
+
 =head1 METHODS
 
 =over 4
index 781e8b9..9ccd84c 100644 (file)
@@ -399,12 +399,23 @@ sub history {
 
 sub search {
     my ($self, $params) = @_;
-    
+
     if ( defined($params->{offset}) and !defined($params->{limit}) ) {
         ThrowCodeError('param_required', 
                        { param => 'limit', function => 'Bug.search()' });
     }
-    
+
+    my $max_results = Bugzilla->params->{max_search_results};
+    unless (defined $params->{limit} && $params->{limit} == 0) {
+        if (!defined $params->{limit} || $params->{limit} > $max_results) {
+            $params->{limit} = $max_results;
+        }
+    }
+    else {
+        delete $params->{limit};
+        delete $params->{offset};
+    }
+
     $params = Bugzilla::Bug::map_fields($params);
     delete $params->{WHERE};
 
@@ -431,7 +442,17 @@ sub search {
         my $clause = join(' OR ', @likes);
         $params->{WHERE}->{"($clause)"} = [map { "\%$_\%" } @strings];
     }
-   
+
+    # If no other parameters have been passed other than limit and offset
+    # and a WHERE parameter was not created earlier, then we throw error
+    # if system is configured to do so.
+    if (!$params->{WHERE}
+        && !grep(!/(limit|offset)/i, keys %$params)
+        && !Bugzilla->params->{search_allow_no_criteria})
+    {
+        ThrowUserError('buglist_parameters_required');
+    }
+
     # We want include_fields and exclude_fields to be passed to
     # _bug_to_hash but not to Bugzilla::Bug->match so we copy the 
     # params and delete those before passing to Bugzilla::Bug->match.
@@ -1997,6 +2018,59 @@ The same as L</get>.
 
 =back
 
+=head2 possible_duplicates
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Allows a user to find possible duplicate bugs based on a set of keywords
+such as a user may use as a bug summary. Optionally the search can be
+narrowed down to specific products.
+
+=item B<Params>
+
+=over
+
+=item C<summary> (string) B<Required> - A string of keywords defining
+the type of bug you are trying to report.
+
+=item C<products> (array) - One or more product names to narrow the
+duplicate search to. If omitted, all bugs are searched.
+
+=back
+
+=item B<Returns>
+
+The same as L</get>.
+
+Note that you will only be returned information about bugs that you
+can see. Bugs that you can't see will be entirely excluded from the
+results. So, if you want to see private bugs, you will have to first 
+log in and I<then> call this method.
+
+=item B<Errors>
+
+=over
+
+=item 50 (Param Required)
+
+You must specify a value for C<summary> containing a string of keywords to 
+search for duplicates.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<4.0>.
+
+=back
+
+=back
 
 =head2 search
 
@@ -2074,13 +2148,16 @@ May not be an array.
 
 =item C<limit>
 
-C<int> Limit the number of results returned to C<int> records.
+C<int> Limit the number of results returned to C<int> records. If the limit
+is more than zero and higher than the maximum limit set by the administrator,
+then the maximum limit will be used instead. If you set the limit equal to zero,
+then all matching results will be returned instead.
 
 =item C<offset>
 
-C<int> Used in conjunction with the C<limit> argument, C<offset> defines 
-the starting position for the search. For example, given a search that 
-would return 100 bugs, setting C<limit> to 10 and C<offset> to 10 would return 
+C<int> Used in conjunction with the C<limit> argument, C<offset> defines
+the starting position for the search. For example, given a search that
+would return 100 bugs, setting C<limit> to 10 and C<offset> to 10 would return
 bugs 11 through 20 from the set of 100.
 
 =item C<op_sys>
@@ -2166,10 +2243,16 @@ log in and I<then> call this method.
 
 =item B<Errors>
 
-Currently, this function doesn't throw any special errors (other than
-the ones that all webservice functions can throw). If you specify
-an invalid value for a particular field, you just won't get any results
-for that value.
+If you specify an invalid value for a particular field, you just won't
+get any results for that value.
+
+=over
+
+=item 1000 (Parameters Required)
+
+You may not search without any search terms.
+
+=back
 
 =item B<History>
 
@@ -2182,6 +2265,10 @@ for that value.
 =item The C<reporter> input parameter was renamed to C<creator>
 in Bugzilla B<4.0>.
 
+=item In B<4.2.6> and newer, added the ability to return all results if
+C<limit> is set equal to zero. Otherwise maximum results returned are limited
+by system configuration.
+
 =back
 
 =back
@@ -2198,8 +2285,9 @@ B<STABLE>
 =item B<Description>
 
 This allows you to create a new bug in Bugzilla. If you specify any
-invalid fields, they will be ignored. If you specify any fields you
-are not allowed to set, they will just be set to their defaults or ignored.
+invalid fields, an error will be thrown stating which field is invalid.
+If you specify any fields you are not allowed to set, they will just be
+set to their defaults or ignored.
 
 You cannot currently set all the items here that you can set on enter_bug.cgi.
 
@@ -2391,7 +2479,9 @@ these bugs.
 
 =item C<data>
 
-B<Required> C<base64> The content of the attachment.
+B<Required> C<base64> or C<string> The content of the attachment.
+If the content of the attachment is not ASCII text, you must encode
+it in base64 and declare it as the C<base64> type.
 
 =item C<file_name>
 
index 59aab9b..3207356 100644 (file)
@@ -166,6 +166,10 @@ use constant WS_ERROR_CODE => {
     group_exists => 801,
     empty_group_description => 802,
     invalid_regexp => 803,
+    invalid_group_name => 804,
+
+    # Search errors are 1000-1100
+    buglist_parameters_required => 1000,
 
     # Errors thrown by the WebService itself. The ones that are negative 
     # conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
index 4e03152..206f0c6 100644 (file)
@@ -25,6 +25,9 @@ use Scalar::Util qw(blessed);
 
 sub handle_login {
     my ($self, $class, $method, $full_method) = @_;
+    # Throw error if the supplied class does not exist or the method is private
+    ThrowCodeError('unknown_method', {method => $full_method}) if (!$class or $method =~ /^_/);
+
     eval "require $class";
     ThrowCodeError('unknown_method', {method => $full_method}) if $@;
     return if ($class->login_exempt($method) 
index 025fb8f..fc29742 100644 (file)
@@ -61,8 +61,16 @@ sub make_response {
 
     # XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
     # its cookies in Bugzilla::CGI, so we need to copy them over.
-    foreach (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
-        $self->response->headers->push_header('Set-Cookie', $_);
+    foreach my $cookie (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
+        $self->response->headers->push_header('Set-Cookie', $cookie);
+    }
+
+    # Copy across security related headers from Bugzilla::CGI
+    foreach my $header (split(/[\r\n]+/, Bugzilla->cgi->header)) {
+        my ($name, $value) = $header =~ /^([^:]+): (.*)/;
+        if (!$self->response->headers->header($name)) {
+           $self->response->headers->header($name => $value);
+        }
     }
 }
 
index f8704a9..deb7518 100644 (file)
@@ -233,12 +233,18 @@ sub _filter_users_by_group {
     # If no groups are specified, we return all users.
     return $users if (!$group_ids and !$group_names);
 
+    my $user = Bugzilla->user;
+
     my @groups = map { Bugzilla::Group->check({ id => $_ }) } 
                      @{ $group_ids || [] };
-    my @name_groups = map { Bugzilla::Group->check($_) } 
-                          @{ $group_names || [] };
-    push(@groups, @name_groups);
-    
+
+    if ($group_names) {
+        foreach my $name (@$group_names) {
+            my $group = Bugzilla::Group->check({ name => $name, _error => 'invalid_group_name' });
+            $user->in_group($group) || ThrowUserError('invalid_group_name', { name => $name });
+            push(@groups, $group);
+        }
+    }
 
     my @in_group = grep { $self->_user_in_any_group($_, \@groups) }
                         @$users;
@@ -586,10 +592,10 @@ C<real_name>, C<email>, and C<can_login> items.
 
 =over
 
-=item 51 (Bad Login Name or Group Name)
+=item 51 (Bad Login Name or Group ID)
 
 You passed an invalid login name in the "names" array or a bad
-group name/id in the C<groups>/C<group_ids> arguments.
+group ID in the C<group_ids> argument.
 
 =item 304 (Authorization Required)
 
@@ -601,6 +607,11 @@ wanted to get information about by user id.
 Logged-out users cannot use the "ids" or "match" arguments to this 
 function.
 
+=item 804 (Invalid Group Name)
+
+You passed a group name in the C<groups> argument which either does not
+exist or you do not belong to it.
+
 =back
 
 =item B<History>
@@ -614,6 +625,9 @@ function.
 =item C<include_disabled> added in Bugzilla B<4.0>. Default behavior 
 for C<match> has changed to only returning enabled accounts.
 
+=item Error 804 has been added in Bugzilla 4.0.9 and 4.2.4. It's now
+illegal to pass a group name you don't belong to.
+
 =back
 
 =back
index adb7fb4..fe4105c 100644 (file)
@@ -143,7 +143,7 @@ a hash to L</filter>, C<0> otherwise.
 
 =head2 validate
 
-This helps in the validation of parameters passed into the WebSerice
+This helps in the validation of parameters passed into the WebService
 methods. Currently it converts listed parameters into an array reference
 if the client only passed a single scalar value. It modifies the parameters
 hash in place so other parameters should be unaltered.
index 466f096..c111cf8 100755 (executable)
@@ -432,8 +432,7 @@ sub view {
     }
     print $cgi->header(-type=>"$contenttype; name=\"$filename\"",
                        -content_disposition=> "$disposition; filename=\"$filename\"",
-                       -content_length => $attachment->datasize,
-                       -x_content_type_options => "nosniff");
+                       -content_length => $attachment->datasize);
     disable_utf8();
     print $attachment->data;
 }
@@ -733,20 +732,23 @@ sub update {
         $attachment->set_filename(scalar $cgi->param('filename'));
 
         # Now make sure the attachment has not been edited since we loaded the page.
-        if (defined $cgi->param('delta_ts')
-            && $cgi->param('delta_ts') ne $attachment->modification_time)
-        {
-            ($vars->{'operations'}) =
-                Bugzilla::Bug::GetBugActivity($bug->id, $attachment->id, $cgi->param('delta_ts'));
+        my $delta_ts = $cgi->param('delta_ts');
+        my $modification_time = $attachment->modification_time;
 
-            # The token contains the old modification_time. We need a new one.
-            $cgi->param('token', issue_hash_token([$attachment->id, $attachment->modification_time]));
+        if ($delta_ts && $delta_ts ne $modification_time) {
+            datetime_from($delta_ts)
+              or ThrowCodeError('invalid_timestamp', { timestamp => $delta_ts });
+            ($vars->{'operations'}) =
+              Bugzilla::Bug::GetBugActivity($bug->id, $attachment->id, $delta_ts);
 
             # If the modification date changed but there is no entry in
             # the activity table, this means someone commented only.
             # In this case, there is no reason to midair.
             if (scalar(@{$vars->{'operations'}})) {
-                $cgi->param('delta_ts', $attachment->modification_time);
+                $cgi->param('delta_ts', $modification_time);
+                # The token contains the old modification_time. We need a new one.
+                $cgi->param('token', issue_hash_token([$attachment->id, $modification_time]));
+
                 $vars->{'attachment'} = $attachment;
 
                 print $cgi->header();
index 0cf4215..eeb9b88 100755 (executable)
@@ -64,7 +64,6 @@ my $buffer = $cgi->query_string();
 my $user = Bugzilla->login();
 
 if (length($buffer) == 0) {
-    print $cgi->header(-refresh=> '10; URL=query.cgi');
     ThrowUserError("buglist_parameters_required");
 }
 
@@ -141,7 +140,7 @@ my $serverpush =
     && exists $ENV{'HTTP_USER_AGENT'} 
       && $ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/ 
         && (($ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/) || ($ENV{'HTTP_USER_AGENT'} =~ /MSIE 5.*Mac_PowerPC/))
-          && $ENV{'HTTP_USER_AGENT'} !~ /WebKit/
+          && $ENV{'HTTP_USER_AGENT'} !~ /(?:WebKit|Trident|KHTML)/
             && !$agent
               && !defined($cgi->param('serverpush'))
                 || $cgi->param('serverpush');
@@ -213,7 +212,7 @@ sub LookupNamedQuery {
     Bugzilla->login(LOGIN_REQUIRED);
 
     my $query = Bugzilla::Search::Saved->check(
-        { user => $sharer_id, name => $name });
+        { user => $sharer_id, name => $name, _error => 'missing_query' });
 
     $query->url
        || ThrowUserError("buglist_parameters_required");
@@ -452,7 +451,9 @@ if ($cmdtype eq "dorem") {
         # Generate and return the UI (HTML page) from the appropriate template.
         $vars->{'message'} = "buglist_query_gone";
         $vars->{'namedcmd'} = $qname;
-        $vars->{'url'} = "buglist.cgi?newquery=" . url_quote($buffer) . "&cmdtype=doit&remtype=asnamed&newqueryname=" . url_quote($qname);
+        $vars->{'url'} = "buglist.cgi?newquery=" . url_quote($buffer)
+                         . "&cmdtype=doit&remtype=asnamed&newqueryname=" . url_quote($qname)
+                         . "&token=" . url_quote(issue_hash_token(['savedsearch']));
         $template->process("global/message.html.tmpl", $vars)
           || ThrowTemplateError($template->error());
         exit;
@@ -461,6 +462,10 @@ if ($cmdtype eq "dorem") {
 elsif (($cmdtype eq "doit") && defined $cgi->param('remtype')) {
     if ($cgi->param('remtype') eq "asdefault") {
         $user = Bugzilla->login(LOGIN_REQUIRED);
+        my $token = $cgi->param('token');
+        check_hash_token($token, ['searchknob']);
+        $buffer = $params->canonicalise_query('cmdtype', 'remtype',
+                                              'query_based_on', 'token');
         InsertNamedQuery(DEFAULT_QUERY_NAME, $buffer);
         $vars->{'message'} = "buglist_new_default_query";
     }
@@ -783,7 +788,7 @@ $params->delete('limit') if $vars->{'default_limited'};
 
 if ($cgi->param('debug')
     && Bugzilla->params->{debug_group}
-    && Bugzilla->user->in_group(Bugzilla->params->{debug_group})
+    && $user->in_group(Bugzilla->params->{debug_group})
 ) {
     $vars->{'debug'} = 1;
     $vars->{'query'} = $query;
@@ -1115,7 +1120,8 @@ else {
 
 # Set 'urlquerypart' once the buglist ID is known.
 $vars->{'urlquerypart'} = $params->canonicalise_query('order', 'cmdtype',
-                                                      'query_based_on');
+                                                      'query_based_on',
+                                                      'token');
 
 if ($format->{'extension'} eq "csv") {
     # We set CSV files to be downloaded, as they are designed for importing
index ee32c77..fddba3f 100755 (executable)
@@ -287,24 +287,32 @@ if ($bug_id) {
 
 =head2 Retrieving Product Information
 
-Call C<Product.get_product> with the name of the product you want to know more
-of.
+Call C<Product.get> with the name of the product you want to know more of.
 The call will return a C<Bugzilla::Product> object.
 
 =cut
 
 if ($product_name) {
-    $soapresult = $proxy->call('Product.get_product', $product_name);
+    $soapresult = $proxy->call('Product.get', {'names' => [$product_name]});
     _die_on_fault($soapresult);
-    $result = $soapresult->result;
-
-    if (ref($result) eq 'HASH') {
-        foreach (keys(%$result)) {
-            print "$_: $$result{$_}\n";
+    $result = $soapresult->result()->{'products'}->[0];
+
+    # Iterate all entries, the values may be scalars or array refs with hash refs.
+    foreach my $key (sort(keys %$result)) {
+      my $value = $result->{$key};
+
+      if (ref($value)) {
+        my $counter = 0;
+        foreach my $hash (@$value) {
+          while (my ($innerKey, $innerValue) = each %$hash) {
+            print "$key.$counter.$innerKey: $innerValue\n";
+          }
+          ++$counter;
         }
-    }
-    else {
-        print "$result\n";
+      }
+      else {
+        print "$key: $value\n"
+      }
     }
 }
 
index bd19285..d04397e 100755 (executable)
@@ -20,7 +20,6 @@
 #   Max Kanat-Alexander <mkanat@bugzilla.org>
 
 use strict;
-use warnings;
 use lib qw(. lib);
 
 use Bugzilla;
@@ -82,6 +81,8 @@ $dbh->bz_start_transaction();
 foreach my $pair (@translation) {
     my ($from, $to) = @$pair;
     print "Converting $from to $to...\n";
+    # There is no FK on bugs.bug_status pointing to bug_status.value,
+    # so it's fine to update the bugs table first.
     $dbh->do('UPDATE bugs SET bug_status = ? WHERE bug_status = ?',
              undef, $to, $from);
 
@@ -103,11 +104,53 @@ foreach my $pair (@translation) {
 
     # If the new status already exists, just delete the old one, but retain
     # the workflow items from it.
-    if (my $existing = new Bugzilla::Status({ name => $to })) {
+    my $new_status = new Bugzilla::Status({ name => $to });
+    my $old_status = new Bugzilla::Status({ name => $from });
+
+    if ($new_status && $old_status) {
+        my $to_id = $new_status->id;
+        my $from_id = $old_status->id;
+        # The subselect collects existing transitions from the target bug status.
+        # The main select collects existing transitions from the renamed bug status.
+        # The diff tells us which transitions are missing from the target bug status.
+        my $missing_transitions =
+          $dbh->selectcol_arrayref('SELECT sw1.new_status
+                                      FROM status_workflow sw1
+                                     WHERE sw1.old_status = ?
+                                       AND sw1.new_status NOT IN (SELECT sw2.new_status
+                                                                    FROM status_workflow sw2
+                                                                   WHERE sw2.old_status = ?)',
+                                     undef, ($from_id, $to_id));
+
+        $dbh->do('UPDATE status_workflow SET old_status = ? WHERE old_status = ? AND '
+                 . $dbh->sql_in('new_status', $missing_transitions),
+                 undef, ($to_id, $from_id)) if @$missing_transitions;
+
+        # The subselect collects existing transitions to the target bug status.
+        # The main select collects existing transitions to the renamed bug status.
+        # The diff tells us which transitions are missing to the target bug status.
+        # We have to explicitly exclude NULL from the subselect, because NOT IN
+        # doesn't know what to do with it (neither true nor false) and no data is returned.
+        $missing_transitions =
+          $dbh->selectcol_arrayref('SELECT sw1.old_status
+                                      FROM status_workflow sw1
+                                     WHERE sw1.new_status = ?
+                                       AND sw1.old_status NOT IN (SELECT sw2.old_status
+                                                                    FROM status_workflow sw2
+                                                                   WHERE sw2.new_status = ?
+                                                                     AND sw2.old_status IS NOT NULL)',
+                                     undef, ($from_id, $to_id));
+
+        $dbh->do('UPDATE status_workflow SET new_status = ? WHERE new_status = ? AND '
+                 . $dbh->sql_in('old_status', $missing_transitions),
+                 undef, ($to_id, $from_id)) if @$missing_transitions;
+
+        # Delete rows where old_status = new_status, and then the old status itself.
+        $dbh->do('DELETE FROM status_workflow WHERE old_status = new_status');
         $dbh->do('DELETE FROM bug_status WHERE value = ?', undef, $from);
     }
     # Otherwise, rename the old status to the new one.
-    else {
+    elsif ($old_status) {
         $dbh->do('UPDATE bug_status SET value = ? WHERE value = ?',
                  undef, $to, $from);
     }
index bfb1146..d207963 100644 (file)
      For a devel release, simple bump bz-ver and bz-date
 -->
 
-<!ENTITY bz-ver "4.2.1">
+<!ENTITY bz-ver "4.2.7">
 <!ENTITY bz-nextver "4.4">
-<!ENTITY bz-date "2012-04-18">
-<!ENTITY current-year "2012">
+<!ENTITY bz-date "2013-10-16">
+<!ENTITY current-year "2013">
 
 <!ENTITY landfillbase "http://landfill.bugzilla.org/bugzilla-4.2-branch/">
 <!ENTITY bz "http://www.bugzilla.org/">
index 111fc8b..8ba95da 100644 (file)
             <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
+            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>
@@ -2425,37 +2425,72 @@ ReadOnly: ENTRY, NA/NA, CANEDIT
               <emphasis>Type:</emphasis>
               The type of field to create. There are
               several types available:
-               <simplelist>
-                 <member>
-                   Bug ID: A field where you can enter the ID of another bug from
-                   the same Bugzilla installation. To point to a bug in a remote
-                   installation, use the See Also field instead.
-                 </member>
-                 <member>
-                   Large Text Box: A multiple line box for entering free text.
-                 </member>
-                 <member>
-                   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>
+              <variablelist>
+                <varlistentry>
+                  <term>Bug ID:</term>
+                  <listitem>
+                    <para>
+                      A field where you can enter the ID of another bug from
+                      the same Bugzilla installation. To point to a bug in a remote
+                      installation, use the See Also field instead.
+                    </para>
+                  </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                  <term>Large Text Box:</term>
+                  <listitem>
+                    <para>
+                      A multiple line box for entering free text.
+                    </para>
+                  </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                  <term>Free Text:</term>
+                  <listitem>
+                    <para>
+                      A single line box for entering free text.
+                    </para>
+                  </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                  <term>Multiple-Selection Box:</term>
+                  <listitem>
+                    <para>
+                      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.
+                    </para>
+                  </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                  <term>Drop Down:</term>
+                  <listitem>
+                    <para>
+                      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.
+                    </para>
+                  </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                  <term>Date/Time:</term>
+                  <listitem>
+                    <para>
+                      A date field. This field appears with a
+                      calendar widget for choosing the date.
+                    </para>
+                  </listitem>
+                </varlistentry>
+              </variablelist>
             </para>
           </listitem>
 
index 9b62b1d..c1524e0 100644 (file)
         The first method of making customizations is to directly edit the
         templates found in <filename>template/en/default</filename>.
         This is probably the best way to go about it if you are going to
-        be upgrading Bugzilla through CVS, because if you then execute
-        a <command>cvs update</command>, any changes you have made will
+        be upgrading Bugzilla through Bzr, because if you then execute
+        a <command>bzr update</command>, any changes you have made will
         be merged automagically with the updated versions.
       </para>
 
       <note>
         <para>
-          If you use this method, and CVS conflicts occur during an
+          If you use this method, and Bzr conflicts occur during an
           update, the conflicted templates (and possibly other parts
           of your installation) will not work until they are resolved.
         </para>
         The second method of customization should be used if you 
         use the overwriting method of upgrade, because otherwise 
         your changes will be lost.  This method may also be better if
-        you are using the CVS method of upgrading and are going to make major
+        you are using the Bzr method of upgrading and are going to make major
         changes, because it is guaranteed that the contents of this directory
         will not be touched during an upgrade, and you can then decide whether
         to continue using your own templates, or make the effort to merge your
index e9830e2..d504622 100644 (file)
 
       <para>
         <ulink url="http://www.bugzilla.org/download/">Download a Bugzilla tarball</ulink>
-        (or check it out from CVS) and place
-        it in a suitable directory, accessible by the default web server user 
+        (or <ulink url="https://wiki.mozilla.org/Bugzilla:Bzr">check it out from Bzr</ulink>)
+        and place it in a suitable directory, accessible by the default web server user
         (probably <quote>apache</quote> or <quote>www</quote>). 
         Good locations are either directly in the web server's document directories or
         in <filename>/usr/local</filename> with a symbolic link to the web server's 
@@ -1582,33 +1582,6 @@ AddType application/rdf+xml .rdf</screen>
 C:\perl&gt; <command>ppm install &lt;module name&gt;</command>
         </programlisting>
 
-        <para>
-          The best source for the Windows PPM modules needed for Bugzilla
-          is probably the theory58S website, which you can add to your list
-          of repositories as follows (for Perl 5.8.x):
-        </para>
-
-        <programlisting>
-<command>ppm repo add theory58S http://theoryx5.uwinnipeg.ca/ppms/</command>
-        </programlisting>
-
-        <para>
-          If you are using Perl 5.10.x, you cannot use the same PPM modules as Perl
-          5.8.x as they are incompatible. In this case, you should add the following
-          repository:
-        </para>
-        <programlisting>
-<command>ppm repo add theory58S http://cpan.uwinnipeg.ca/PPMPackages/10xx/</command>
-        </programlisting>
-
-        <note>
-          <para>
-            In versions prior to 5.8.8 build 819 of PPM the command is 
-            <programlisting>
-<command>ppm repository add theory58S http://theoryx5.uwinnipeg.ca/ppms/</command>
-            </programlisting>
-          </para>
-        </note>
         <note>
           <para>
             The PPM repository stores modules in 'packages' that may have
index 933c9de..2907dad 100644 (file)
       <para>
         Running Bugzilla on Windows requires the use of ActiveState
         Perl 5.8.1 or higher. Many modules already exist in the core
-        distribution of ActiveState Perl. Additional modules can be downloaded
-        from <ulink url="http://theoryx5.uwinnipeg.ca/ppms/" /> if you use
-        Perl 5.8.x or from <ulink url="http://cpan.uwinnipeg.ca/PPMPackages/10xx/" />
-        if you use Perl 5.10.x.
+        distribution of ActiveState Perl. If some modules are missing, upgrade
+        ActiveState Perl to at least 5.12; it has all the required modules.
       </para>
     </note>
 
index 3bf0558..53766ef 100644 (file)
     <orderedlist>
       <listitem>
         <para>
-        <emphasis>Product and Component</emphasis>: 
-        Bugs are divided up by Product and Component, with a Product
-        having one or more Components in it. For example,
-        bugzilla.mozilla.org's "Bugzilla" Product is composed of several
-        Components: 
-        <simplelist>
-        <member>
-        <emphasis>Administration:</emphasis>
-        Administration of a Bugzilla installation.</member>
-
-        <member>
-        <emphasis>Bugzilla-General:</emphasis>
-        Anything that doesn't fit in the other components, or spans
-        multiple components.</member>
-
-        <member>
-        <emphasis>Creating/Changing Bugs:</emphasis>
-        Creating, changing, and viewing bugs.</member>
-
-        <member>
-        <emphasis>Documentation:</emphasis>
-        The Bugzilla documentation, including The Bugzilla Guide.</member>
-
-        <member>
-        <emphasis>Email:</emphasis>
-        Anything to do with email sent by Bugzilla.</member>
-
-        <member>
-        <emphasis>Installation:</emphasis>
-        The installation process of Bugzilla.</member>
-
-        <member>
-        <emphasis>Query/Buglist:</emphasis>
-        Anything to do with searching for bugs and viewing the
-        buglists.</member>
-
-        <member>
-        <emphasis>Reporting/Charting:</emphasis>
-        Getting reports from Bugzilla.</member>
-
-        <member>
-        <emphasis>User Accounts:</emphasis>
-        Anything about managing a user account from the user's perspective.
-        Saved queries, creating accounts, changing passwords, logging in,
-        etc.</member>
-
-        <member>
-        <emphasis>User Interface:</emphasis>
-        General issues having to do with the user interface cosmetics (not
-        functionality) including cosmetic issues, HTML templates,
-        etc.</member>
-        </simplelist>
+          <emphasis>Product and Component</emphasis>: 
+          Bugs are divided up by Product and Component, with a Product
+          having one or more Components in it. For example,
+          bugzilla.mozilla.org's "Bugzilla" Product is composed of several
+          Components:
+          <variablelist>
+            <varlistentry>
+              <term>Administration:</term>
+              <listitem>
+                <para>
+                  Administration of a Bugzilla installation.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term>Bugzilla-General:</term>
+              <listitem>
+                <para>
+                  Anything that doesn't fit in the other components, or spans
+                  multiple components.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term>Creating/Changing Bugs:</term>
+              <listitem>
+                <para>
+                  Creating, changing, and viewing bugs.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term>Documentation:</term>
+              <listitem>
+                <para>
+                  The Bugzilla documentation, including The Bugzilla Guide.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term>Email:</term>
+              <listitem>
+                <para>
+                  Anything to do with email sent by Bugzilla.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term>Installation:</term>
+              <listitem>
+                <para>
+                  The installation process of Bugzilla.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term>Query/Buglist:</term>
+              <listitem>
+                <para>
+                  Anything to do with searching for bugs and viewing the
+                  buglists.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term>Reporting/Charting:</term>
+              <listitem>
+                <para>
+                  Getting reports from Bugzilla.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term>User Accounts:</term>
+              <listitem>
+                <para>
+                  Anything about managing a user account from the user's perspective.
+                  Saved queries, creating accounts, changing passwords, logging in,
+                  etc.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term>User Interface:</term>
+              <listitem>
+                <para>
+                  General issues having to do with the user interface cosmetics (not
+                  functionality) including cosmetic issues, HTML templates,
+                  etc.
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
         </para>
       </listitem>
 
         This form can be used for time tracking.
         To use this feature, you have to be blessed group membership
         specified by the <quote>timetrackinggroup</quote> parameter.
-        <simplelist>
-        <member>
-        <emphasis>Orig. Est.:</emphasis>
-        This field shows the original estimated time.</member>
-
-        <member>
-        <emphasis>Current Est.:</emphasis>
-        This field shows the current estimated time.
-        This number is calculated from <quote>Hours Worked</quote>
-        and <quote>Hours Left</quote>.</member>
-
-        <member>
-        <emphasis>Hours Worked:</emphasis>
-        This field shows the number of hours worked.</member>
-
-        <member>
-        <emphasis>Hours Left:</emphasis>
-        This field shows the <quote>Current Est.</quote> -
-        <quote>Hours Worked</quote>.
-        This value + <quote>Hours Worked</quote> will become the
-        new Current Est.</member>
-
-        <member>
-        <emphasis>%Complete:</emphasis>
-        This field shows what percentage of the task is complete.</member>
-
-        <member>
-        <emphasis>Gain:</emphasis>
-        This field shows the number of hours that the bug is ahead of the
-        <quote>Orig. Est.</quote>.</member>
-
-        <member>
-        <emphasis>Deadline:</emphasis>
-        This field shows the deadline for this bug.</member>
-        </simplelist>
+        <variablelist>
+          <varlistentry>
+            <term>Orig. Est.:</term>
+            <listitem>
+              <para>
+                This field shows the original estimated time.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Current Est.:</term>
+            <listitem>
+              <para>
+                This field shows the current estimated time.
+                This number is calculated from <quote>Hours Worked</quote>
+                and <quote>Hours Left</quote>.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Hours Worked:</term>
+            <listitem>
+              <para>
+                This field shows the number of hours worked.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Hours Left:</term>
+            <listitem>
+              <para>
+                This field shows the <quote>Current Est.</quote> -
+                <quote>Hours Worked</quote>.
+                This value + <quote>Hours Worked</quote> will become the
+                new Current Est.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>%Complete:</term>
+            <listitem>
+              <para>
+                This field shows what percentage of the task is complete.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Gain:</term>
+            <listitem>
+              <para>
+                This field shows the number of hours that the bug is ahead of the
+              <quote>Orig. Est.</quote>.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Deadline:</term>
+            <listitem>
+              <para>
+                This field shows the deadline for this bug.
+              </para>
+            </listitem>
+          </varlistentry>
+        </variablelist>
         </para>
       </listitem>
 
       <para>The format of the list is configurable. For example, it can be
       sorted by clicking the column headings. Other useful features can be
       accessed using the links at the bottom of the list:
-      <simplelist>
-        <member>
-        <emphasis>Long Format:</emphasis>
-
-        this gives you a large page with a non-editable summary of the fields
-        of each bug.</member>
-
-        <member>
-        <emphasis>XML:</emphasis>
-
-        get the buglist in the XML format.</member>
-
-        <member>
-        <emphasis>CSV:</emphasis>
-
-        get the buglist as comma-separated values, for import into e.g.
-        a spreadsheet.</member>
-
-        <member>
-        <emphasis>Feed:</emphasis>
-
-        get the buglist as an Atom feed.  Copy this link into your
-        favorite feed reader.  If you are using Firefox, you can also
-        save the list as a live bookmark by clicking the live bookmark
-        icon in the status bar.  To limit the number of bugs in the feed,
-        add a limit=n parameter to the URL.</member>
-
-        <member>
-        <emphasis>iCalendar:</emphasis>
-
-        Get the buglist as an iCalendar file. Each bug is represented as a
-        to-do item in the imported calendar.</member>
-
-        <member>
-        <emphasis>Change Columns:</emphasis>
-
-        change the bug attributes which appear in the list.</member>
-
-        <member>
-        <emphasis>Change several bugs at once:</emphasis>
-
-        If your account is sufficiently empowered, and more than one bug
-        appear in the bug list, this link is displayed which lets you make
-        the same change to all the bugs in the list - for example, changing
-        their assignee.</member>
-
-        <member>
-        <emphasis>Send mail to bug assignees:</emphasis>
-
-        If more than one bug appear in the bug list and there are at least
-        two distinct bug assignees, this links is displayed which lets you
-        easily send a mail to the assignees of all bugs on the list.</member>
-
-        <member>
-        <emphasis>Edit Search:</emphasis>
-
-        If you didn't get exactly the results you were looking for, you can
-        return to the Query page through this link and make small revisions
-        to the query you just made so you get more accurate results.</member>
-
-        <member>
-        <emphasis>Remember Search As:</emphasis>
-
-        You can give a search a name and remember it; a link will appear
-        in your page footer giving you quick access to run it again later.
-        </member>
-      </simplelist>
+        <variablelist>
+          <varlistentry>
+            <term>Long Format:</term>
+            <listitem>
+              <para>
+                this gives you a large page with a non-editable summary of the fields
+                of each bug.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>XML:</term>
+            <listitem>
+              <para>
+                get the buglist in the XML format.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>CSV:</term>
+            <listitem>
+              <para>
+                get the buglist as comma-separated values, for import into e.g.
+                a spreadsheet.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Feed:</term>
+            <listitem>
+              <para>
+                get the buglist as an Atom feed.  Copy this link into your
+                favorite feed reader.  If you are using Firefox, you can also
+                save the list as a live bookmark by clicking the live bookmark
+                icon in the status bar.  To limit the number of bugs in the feed,
+                add a limit=n parameter to the URL.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>iCalendar:</term>
+            <listitem>
+              <para>
+                Get the buglist as an iCalendar file. Each bug is represented as a
+                to-do item in the imported calendar.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Change Columns:</term>
+            <listitem>
+              <para>
+                change the bug attributes which appear in the list.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Change several bugs at once:</term>
+            <listitem>
+              <para>
+                If your account is sufficiently empowered, and more than one bug
+                appear in the bug list, this link is displayed which lets you make
+                the same change to all the bugs in the list - for example, changing
+                their assignee.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Send mail to bug assignees:</term>
+            <listitem>
+              <para>
+                If more than one bug appear in the bug list and there are at least
+                two distinct bug assignees, this links is displayed which lets you
+                easily send a mail to the assignees of all bugs on the list.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Edit Search:</term>
+            <listitem>
+              <para>
+                If you didn't get exactly the results you were looking for, you can
+                return to the Query page through this link and make small revisions
+                to the query you just made so you get more accurate results.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>Remember Search As:</term>
+            <listitem>
+              <para>
+                You can give a search a name and remember it; a link will appear
+                in your page footer giving you quick access to run it again later.
+              </para>
+            </listitem>
+          </varlistentry>
+        </variablelist>
       </para>
     </section>
 
       <title>Adding/removing tags to/from bugs</title>
       <para>
         You can add and remove tags from individual bugs, which let you find and
-        manage them more easily. Creating a new tag automatically generates a saved
-        search - whose name is the name of the tag - which lists bugs with this tag.
-        This saved search will be displayed in the footer of pages by default, as
-        all other saved searches. The main difference between tags and normal saved
-        searches is that saved searches, as described in the previous section, are
-        stored in the form of a list of matching criteria, while the saved search
-        generated by tags is a list of bug numbers. Consequently, you can easily
-        edit this list by either adding or removing tags from bugs. To enable this
+        manage bugs more easily. Tags are per-user and so are only visible and editable
+        by the user who created them. You can then run queries using tags as a criteria,
+        either by using the Advanced Search form, or simply by typing "tag:my_tag_name"
+        in the QuickSearch box at the top (or bottom) of the page. To enable this
         feature, you have to turn on the <quote>Enable tags for bugs</quote> user
         preference, see <xref linkend="userpreferences" />. This feature is disabled
         by default.
         these bugs and mixing all these reasons, you can now store these bugs in
         separate lists, e.g. <quote>Keep in mind</quote>, <quote>Interesting bugs</quote>,
         or <quote>Triage</quote>. One big advantage of this way to manage bugs
-        is that you can easily add or remove bugs one by one, which is not
-        possible to do with saved searches without having to edit the search
-        criteria again.
+        is that you can easily add or remove tags from bugs one by one.
       </para>
     </section>
   </section>
index 356835d..d433ca9 100755 (executable)
@@ -156,6 +156,9 @@ if ($action eq 'list') {
     my $component_id = $component ? $component->id : 0;
     my $show_flag_counts = $cgi->param('show_flag_counts') ? 1 : 0;
     my $group_id = $cgi->param('group');
+    if ($group_id) {
+        detaint_natural($group_id) || ThrowUserError('invalid_group_ID');
+    }
 
     my $bug_flagtypes;
     my $attach_flagtypes;
index 3b4f883..b28658b 100755 (executable)
@@ -235,7 +235,8 @@ sub process_bug {
 
     my $added_comment;
     if (trim($fields{'comment'})) {
-        $added_comment = $bug->comments->[-1];
+        # The "old" bug object doesn't contain the comment we just added.
+        $added_comment = Bugzilla::Bug->check($bug_id)->comments->[-1];
     }
     return ($bug, $added_comment);
 }
index 2fa5aa4..5336534 100755 (executable)
@@ -310,6 +310,7 @@ sub pickos {
               /\(.*Windows.*NT.*\)/ && do {push @os, "Windows NT";};
             };
             /\(.*Mac OS X.*\)/ && do {
+              /\(.*Mac OS X (?:|Mach-O |\()10.8.*\)/ && do {push @os, "Mac OS X 10.8";};
               /\(.*Mac OS X (?:|Mach-O |\()10.7.*\)/ && do {push @os, "Mac OS X 10.7";};
               /\(.*Mac OS X (?:|Mach-O |\()10.6.*\)/ && do {push @os, "Mac OS X 10.6";};
               /\(.*Mac OS X (?:|Mach-O |\()10.5.*\)/ && do {push @os, "Mac OS X 10.5";};
index 885a8e8..c0b3c62 100644 (file)
@@ -371,7 +371,13 @@ sub error_catch {
     my $new_error_msg = "Ah ah, you tried to access $page_id? Good try!";
     $new_error_msg = html_quote($new_error_msg);
     # There are better tools to parse an HTML page, but it's just an example.
-    $$page =~ s/(?<=<td id="error_msg" class="throw_error">).*(?=<\/td>)/$new_error_msg/si;
+    # Since Perl 5.16, we can no longer write "class" inside look-behind
+    # assertions, because "ss" is also seen as the german ß character, which
+    # makes Perl 5.16 complain. The right fix is to use the /aa modifier,
+    # but it's only understood since Perl 5.14. So the workaround is to write
+    # "clas[s]" instead of "class". Stupid and ugly hack, but it works with
+    # all Perl versions.
+    $$page =~ s/(?<=<td id="error_msg" clas[s]="throw_error">).*(?=<\/td>)/$new_error_msg/si;
 }
 
 sub flag_end_of_update {
index b12d36a..d1b9537 100644 (file)
@@ -132,7 +132,9 @@ sub _check_bug_resolution {
     my $original_validator = shift;
     my ($invocant, $resolution) = @_;
 
-    if ($resolution eq 'MOVED' and !Bugzilla->input_params->{'oldbugmove'}) {
+    if ($resolution eq 'MOVED' && $invocant->resolution ne 'MOVED'
+        && !Bugzilla->input_params->{'oldbugmove'})
+    {
         # MOVED has a special meaning and can only be used when
         # really moving bugs to another installation.
         ThrowUserError('oldbugmove_no_manual_move');
diff --git a/Websites/bugs.webkit.org/extensions/Voting/Config.pm b/Websites/bugs.webkit.org/extensions/Voting/Config.pm
new file mode 100644 (file)
index 0000000..438f1e0
--- /dev/null
@@ -0,0 +1,33 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Frédéric Buclin.
+# Portions created by the Initial Developer is Copyright (C) 2012 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Frédéric Buclin <LpSolit@gmail.com>
+
+package Bugzilla::Extension::Voting;
+use strict;
+
+use constant NAME => 'Voting';
+
+use constant REQUIRED_MODULES => [
+];
+
+use constant OPTIONAL_MODULES => [
+];
+
+__PACKAGE__->NAME;
index ead8126..a5a3bc1 100644 (file)
@@ -40,7 +40,6 @@ use Bugzilla::Token;
 
 use List::Util qw(min);
 
-use constant NAME => 'Voting';
 use constant VERSION => BUGZILLA_VERSION;
 use constant DEFAULT_VOTES_PER_BUG => 1;
 # These came from Bugzilla itself, so they maintain the old numbers
index c457f69..f242b64 100755 (executable)
@@ -498,7 +498,7 @@ sub process_bug {
     foreach my $comment ( $bug->children('long_desc') ) {
         Debug( "Parsing Long Description", DEBUG_LEVEL );
         my %long_desc = ( who       => $comment->field('who'),
-                          bug_when  => $comment->field('bug_when'),
+                          bug_when  => format_time($comment->field('bug_when'), '%Y-%m-%d %T'),
                           isprivate => $comment->{'att'}->{'isprivate'} || 0 );
 
         # If the exporter is not in the insidergroup, keep the comment public.
index d5080da..3a2291e 100755 (executable)
 #   Max Kanat-Alexander <mkanat@bugzilla.org>
 
 use strict;
+
+use Cwd qw(abs_path);
 use File::Basename;
-BEGIN { chdir dirname($0); }
+BEGIN {
+    # Untaint the abs_path.
+    my ($a) = abs_path($0) =~ /^(.*)$/;
+    chdir dirname($a);
+}
 
 use lib qw(. lib);
 use Bugzilla;
index 0ee7d24..7389703 100644 (file)
@@ -146,7 +146,15 @@ function fix_query_string(form_member) {
         return;
 
     var form = YAHOO.util.Dom.getAncestorByTagName(form_member, 'form');
+    // Disable the token field so setForm doesn't include it
+    var reenable_token = false;
+    if (form['token'] && !form['token'].disabled) {
+      form['token'].disabled = true;
+      reenable_token = true;
+    }
     var query = YAHOO.util.Connect.setForm(form);
+    if (reenable_token)
+      form['token'].disabled = false;
     window.History.replaceState(null, document.title, '?' + query);
 }
 
index 744f193..07433b2 100644 (file)
@@ -770,6 +770,7 @@ YAHOO.bugzilla.keywordAutocomplete = {
         }
         var keywordAutoComp = new YAHOO.widget.AutoComplete(field, container, this.dataSource);
         keywordAutoComp.maxResultsDisplayed = YAHOO.bugzilla.keyword_array.length;
+        keywordAutoComp.formatResult = keywordAutoComp.formatEscapedResult;
         keywordAutoComp.minQueryLength = 0;
         keywordAutoComp.useIFrame = true;
         keywordAutoComp.delimChar = [","," "];
@@ -786,5 +787,8 @@ YAHOO.bugzilla.keywordAutocomplete = {
                 this.expandContainer();
             }
         });
+        keywordAutoComp.dataRequestEvent.subscribe( function(type, args) {
+            args[0].autoHighlight = args[1] != '';
+        });
     }
 };
index b2f5cd0..9c26ed1 100644 (file)
Binary files a/Websites/bugs.webkit.org/js/yui/swfstore/swfstore.swf and b/Websites/bugs.webkit.org/js/yui/swfstore/swfstore.swf differ
index fe3425e..75b12b9 100755 (executable)
@@ -277,7 +277,7 @@ foreach my $dep_field (qw(dependson blocked)) {
     if (should_set($dep_field)) {
         if (my $dep_action = $cgi->param("${dep_field}_action")) {
             $set_all_fields{$dep_field}->{$dep_action} =
-                [split(/\s,/, $cgi->param($dep_field))];
+                [split(/[\s,]+/, $cgi->param($dep_field))];
         }
         else {
             $set_all_fields{$dep_field}->{set} = $cgi->param($dep_field);
index 1aebb8d..47c7508 100755 (executable)
@@ -39,6 +39,7 @@ use Bugzilla::Product;
 use Bugzilla::Keyword;
 use Bugzilla::Field;
 use Bugzilla::Install::Util qw(vers_cmp);
+use Bugzilla::Token;
 
 my $cgi = Bugzilla->cgi;
 my $dbh = Bugzilla->dbh;
@@ -51,6 +52,8 @@ my $userid = $user->id;
 
 if ($cgi->param('nukedefaultquery')) {
     if ($userid) {
+        my $token = $cgi->param('token');
+        check_hash_token($token, ['nukedefaultquery']);
         $dbh->do("DELETE FROM namedqueries" .
                  " WHERE userid = ? AND name = ?", 
                  undef, ($userid, DEFAULT_QUERY_NAME));
@@ -245,13 +248,6 @@ if (($cgi->param('query_format') || $cgi->param('format') || "")
     $vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
 }
 
-if ($cgi->param('format') && $cgi->param('format') =~ /^report-(table|graph)$/) {
-    # Get legal custom fields for tabular and graphical reports.
-    my @custom_fields_for_reports =
-      grep { $_->type == FIELD_TYPE_SINGLE_SELECT } Bugzilla->active_custom_fields;
-    $vars->{'custom_fields'} = \@custom_fields_for_reports;
-}
-
 $vars->{'known_name'} = $cgi->param('known_name');
 $vars->{'columnlist'} = $cgi->param('columnlist');
 
index 5a4e3db..4841409 100755 (executable)
@@ -84,10 +84,12 @@ if (defined($height)) {
    $height <= 2000 || ThrowUserError("chart_too_large");
 }
 
+my $formatparam = $cgi->param('format') || '';
+
 # These shenanigans are necessary to make sure that both vertical and 
 # horizontal 1D tables convert to the correct dimension when you ask to
 # display them as some sort of chart.
-if (defined $cgi->param('format') && $cgi->param('format') eq "table") {
+if ($formatparam eq "table") {
     if ($col_field && !$row_field) {    
         # 1D *tables* should be displayed vertically (with a row_field only)
         $row_field = $col_field;
@@ -210,7 +212,7 @@ $vars->{'row_names'} = \@row_names;
 $vars->{'tbl_names'} = \@tbl_names;
 
 # Below a certain width, we don't see any bars, so there needs to be a minimum.
-if ($width && $cgi->param('format') eq "bar") {
+if ($width && $formatparam eq "bar") {
     my $min_width = (scalar(@col_names) || 1) * 20;
 
     if (!$cgi->param('cumulate')) {
@@ -232,8 +234,6 @@ if ($cgi->param('debug')
     $vars->{'debug'} = 1;
 }
 
-my $formatparam = $cgi->param('format');
-
 if ($action eq "wrap") {
     # So which template are we using? If action is "wrap", we will be using
     # no format (it gets passed through to be the format of the actual data),
@@ -242,7 +242,6 @@ if ($action eq "wrap") {
     # data, or images generated by calling report.cgi again with action as
     # "plot".
     $formatparam =~ s/[^a-zA-Z\-]//g;
-    trick_taint($formatparam);
     $vars->{'format'} = $formatparam;
     $formatparam = '';
 
@@ -311,6 +310,10 @@ $template->process("$format->{'template'}", $vars)
 sub get_names {
     my ($names, $isnumeric, $field_name) = @_;
     my ($field, @sorted);
+    # XXX - This is a hack to handle the actual_time/work_time field,
+    # because it's named 'actual_time' in Search.pm but 'work_time' in Field.pm.
+    $_[2] = $field_name = 'work_time' if $field_name eq 'actual_time';
+
     # _realname fields aren't real Bugzilla::Field objects, but they are a
     # valid axis, so we don't vailidate them as Bugzilla::Field objects.
     $field = Bugzilla::Field->check($field_name) 
@@ -320,7 +323,7 @@ sub get_names {
         foreach my $value (@{$field->legal_values}) {
             push(@sorted, $value->name) if $names->{$value->name};
         }
-        unshift(@sorted, ' ') if $field_name eq 'resolution';
+        unshift(@sorted, '---') if $field_name eq 'resolution';
         @sorted = uniq @sorted;
     }  
     elsif ($isnumeric) {
@@ -349,6 +352,7 @@ sub check_value {
     else {
         $value = shift @$result;
         $value = ' ' if (!defined $value || $value eq '');
+        $value = '---' if ($field eq 'resolution' && $value eq ' ');
     }
     return $value;
 }
@@ -357,5 +361,5 @@ sub get_field_restrictions {
     my $field = shift;
     my $cgi = Bugzilla->cgi;
 
-    return join('&', map {"$field=$_"} $cgi->param($field));
+    return join('&amp;', map {url_quote($field) . '=' . url_quote($_)} $cgi->param($field));
 }
index 5f2dbc5..2f2b14e 100755 (executable)
@@ -51,9 +51,6 @@ if (!$cgi->param('id') && $single) {
     exit;
 }
 
-my $format = $template->get_format("bug/show", scalar $cgi->param('format'), 
-                                   scalar $cgi->param('ctype'));
-
 my @bugs;
 my %marks;
 
index a54785e..46a482f 100755 (executable)
@@ -120,7 +120,7 @@ chmod Bugzilla::Install::Filesystem::CGI_WRITE, $filename
     or warn install_string('chmod_failed', { path => $filename,
                                              error => $! });
 
-my $urlbase = Bugzilla->params->{'urlbase'};
+my $urlbase = correct_urlbase();
 
 print $fh "digraph G {";
 print $fh qq{
index 3a18e40..6337567 100644 (file)
@@ -30,8 +30,8 @@ body {
 /* page title */
 
 #titles {
-    -moz-border-radius-topleft: 5px;
-    -moz-border-radius-topright: 5px;
+    border-top-left-radius: 5px;
+    border-top-right-radius: 5px;
 }
 
 #header .links, #footer {
@@ -40,8 +40,8 @@ body {
 }
 
 #header {
-    -moz-border-radius-bottomleft: 5px;
-    -moz-border-radius-bottomright: 5px;
+    border-bottom-left-radius: 5px;
+    border-bottom-right-radius: 5px;
     border: none;
 }
 
@@ -61,7 +61,7 @@ body {
     border: 1px solid #747e93;
     padding: 10px;
     font-size: 10pt;
-    -moz-border-radius: 5px;
+    border-radius: 5px;
 }
 
 a {
@@ -174,7 +174,7 @@ hr {
 #footer {
     border: 1px solid #747e93;
     width: 100%;
-    -moz-border-radius: 5px;
+    border-radius: 5px;
 }
 
 #footer #links-actions,
index 0b28ff3..4d4b021 100644 (file)
@@ -55,8 +55,8 @@
         border-left: 1px solid #747E93;
         border-right: 1px solid #747E93;
         border-bottom: 1px solid #747E93;
-        -moz-border-radius-bottomleft: 5px;
-        -moz-border-radius-bottomright: 5px;
+        border-bottom-left-radius: 5px;
+        border-bottom-right-radius: 5px;
         padding: 0.5em;
     }
 
         width: 100%;
         background-color: #404D6C;
         color: #fff;
-        -moz-border-radius-topleft: 5px;
-        -moz-border-radius-topright: 5px;
+        border-top-left-radius: 5px;
+        border-top-right-radius: 5px;
         font-size: 110%;
         margin: 0;
         padding: 0.5em;
index 99c0b40..8214ce5 100644 (file)
@@ -2,7 +2,7 @@
     margin: 8px 0; 
     padding: 0.3em; 
     background-color: rgb(208, 208, 208); 
-    -moz-border-radius: 0.5em; 
+    border-radius: 0.5em;
     font-size: 125%; 
     font-weight: bold;
 }
index fbe40fb..a9d8603 100644 (file)
@@ -36,8 +36,8 @@
   [% IF cgi.request_method == "GET" AND cgi.query_string %]
     [% connector = "&" %]
   [% END %]
-  [% script_name = login_target _ connector _ "GoAheadAndLogIn=1" %]
-  <a id="login_link[% qs_suffix %]" href="[% script_name FILTER html %]"
+  [% script_url = login_target _ connector _ "GoAheadAndLogIn=1" %]
+  <a id="login_link[% qs_suffix %]" href="[% script_url FILTER html %]"
      onclick="return show_mini_login_form('[% qs_suffix %]')">Log In</a>
 
   [% Hook.process('additional_methods') %]
 </li>
 <li id="forgot_container[% qs_suffix %]">
   <span class="separator">| </span>
-  <a id="forgot_link[% qs_suffix %]" href="[% script_name FILTER html %]#forgot"
+  <a id="forgot_link[% qs_suffix %]" href="[% script_url FILTER html %]#forgot"
      onclick="return show_forgot_form('[% qs_suffix %]')">Forgot Password</a>
   <form action="token.cgi" method="post" id="forgot_form[% qs_suffix %]"
         class="mini_forgot bz_default_hidden">
     <input id="forgot_button[% qs_suffix %]" value="Reset Password" 
            type="submit">
     <input type="hidden" name="a" value="reqpw">
+    <input type="hidden" id="token[% qs_suffix FILTER html %]" name="token" value="[% issue_hash_token(['reqpw']) FILTER html %]">
     <a href="#" onclick="return hide_forgot_form('[% qs_suffix %]')">[x]</a>
   </form>
 </li>
index 122ef6f..3de52b6 100644 (file)
       enter your login name below and submit a request
       to change your password.<br>
       <input size="35" name="loginname">
+      <input type="hidden" id="token" name="token" value="[% issue_hash_token(['reqpw']) FILTER html %]">
       <input type="submit" id="request" value="Reset Password">
     </form>
   [% END %]
index 2cb985a..de0476e 100644 (file)
@@ -52,7 +52,7 @@
 <form id="flagtype_properties" method="post" action="editflagtypes.cgi">
   <input type="hidden" name="action" value="[% action FILTER html %]">
   <input type="hidden" name="can_fully_edit" value="[% can_fully_edit FILTER html %]">
-  <input type="hidden" name="id" value="[% type.id %]">
+  <input type="hidden" name="id" value="[% type.id FILTER html %]">
   <input type="hidden" name="token" value="[% token FILTER html %]">
   <input type="hidden" name="target_type" value="[% type.target_type FILTER html %]">
   <input type="hidden" name="check_clusions" value="[% check_clusions FILTER none %]">
         this type will be sorted when displayed to users in a list; ignore if you
         don't care what order the types appear in or if you want them to appear
         in alphabetical order.<br>
-        <input type="text" name="sortkey" value="[% type.sortkey || 1 %]" size="5" maxlength="5"
-               [%- ' disabled="disabled"' UNLESS can_fully_edit %]>
+        <input type="text" name="sortkey" value="[% type.sortkey || 1 FILTER html %]" size="5"
+               maxlength="5" [% ' disabled="disabled"' UNLESS can_fully_edit %]>
       </td>
     </tr>
 
index fd38d65..14fba58 100644 (file)
       [% ELSE %]
 
         <div class="contribute"><strong>Note:</strong>
-          [%+ terms.Bugzilla %] is developed entirely by volunteers. The
-          best way to give back to the [% terms.Bugzilla %] project is
-          to <a href="http://www.bugzilla.org/contribute/">contribute</a>
-          yourself! You don't have to be a programmer to contribute, there are
-          lots of things that we need.
+          B[% %]ugzilla is developed entirely by volunteers.
+          The best way to give back to the B[% %]ugzilla project is to
+          <a href="http://www.bugzilla.org/contribute/">contribute</a>
+          yourself!
+          You don't have to be a programmer to contribute, there are lots of
+          things that we need.
         </div>
 
         <p>
index 170c693..e3099d9 100644 (file)
     [% sort_order = "oldest_to_newest" %]
 [% END %]
 
-
-[%# Set up the variables as needed, depending on the sort order %]
-[% IF sort_order == "oldest_to_newest" %]
-    [% count = 0 %]
-    [% description = 0 %]
-    [% increment = 1 %]
-[% ELSE %]
-    [% increment = -1 %]
-    [% IF sort_order == "newest_to_oldest" %]
-        [% count = comments.size - 1 %]
-        [% description = 0 %]
-    [% ELSIF sort_order == "newest_to_oldest_desc_first" %]
-        [% count = comments.size %]
-        [% description = comments.size %]
-    [% END %]
-[% END %]
-
 <!-- This auto-sizes the comments and positions the collapse/expand links 
      to the right. -->
 <table class="bz_comment_table" cellpadding="0" cellspacing="0"><tr>
 <td>
 
 [% FOREACH comment = comments %]
-  [% IF count >= start_at %]
+  [% IF comment.count >= start_at %]
     [% PROCESS a_comment %]
   [% END %]
-  
-  [% count = count + increment %]
 [% END %]
 
 [% IF user.settings.comment_box_position.value == "before_comments" && user.id %]
   [% comment_text = comment.body_full %]
   [% RETURN IF comment_text == '' AND (comment.work_time - 0) != 0 AND !user.is_timetracker %]
 
-    <div id="c[% count %]" class="bz_comment[% " bz_private" IF comment.is_private %]
-                [% " bz_comment_hilite" IF marks.$count %]
-                [% " bz_first_comment" IF count == description %]">
-      [% IF count == description %]
+    <div id="c[% comment.count %]" class="bz_comment[% " bz_private" IF comment.is_private %]
+                [% " bz_comment_hilite" IF marks.${comment.count} %]
+                [% " bz_first_comment" IF comment.count == 0 %]">
+      [% IF comment.count == 0 %]
         [% class_name = "bz_first_comment_head" %]
         [% comment_label = "Description" %]
       [% ELSE %]
         [% class_name = "bz_comment_head" %]
-        [% comment_label = "Comment " _ count %]
+        [% comment_label = "Comment " _ comment.count %]
       [% END %]
 
       <div class="[% class_name FILTER html %]">
         [% IF mode == "edit" %]
           <span class="bz_comment_actions">
             <script type="text/javascript"><!--
-              addReplyLink([% count %], [% comment.id %]);
-              addCollapseLink([% count %], 'Toggle comment display'); // -->
+              addReplyLink([% comment.count %], [% comment.id %]);
+              addCollapseLink([% comment.count %], 'Toggle comment display'); // -->
             </script>
           </span>
         [% END %]
             <input type="checkbox"
                    name="isprivate_[% comment.id %]" value="1"
                    id="isprivate_[% comment.id %]"
-                   onClick="updateCommentPrivacy(this, [% count %])"
+                   onClick="updateCommentPrivacy(this, [% comment.count %])"
                    [% " checked=\"checked\"" IF comment.is_private %]>
             <label for="isprivate_[% comment.id %]">Private</label>
           </div>
 
         <span class="bz_comment_number">
           <a 
-             href="show_bug.cgi?id=[% bug.bug_id %]#c[% count %]">
+             href="show_bug.cgi?id=[% bug.bug_id %]#c[% comment.count %]">
             [%- comment_label FILTER html %]</a>
         </span>
 
   # generated HTML
   #%]
 <pre class="bz_comment_text" 
-     [% ' id="comment_text_' _ count _ '"' IF mode == "edit" %]>
+     [% ' id="comment_text_' _ comment.count _ '"' IF mode == "edit" %]>
   [%- comment_text FILTER quoteUrls(bug, comment) -%]
 </pre>
     </div>
index f3dd680..634bcf3 100644 (file)
@@ -303,7 +303,7 @@ TUI_hide_default('attachment_text_field');
        bug = default, field = bug_fields.op_sys, editable = 1, 
        value = default.op_sys %]
   </tr>
-  [% IF !Param('defaultplatform') || !Param('defaultopsys') %]
+  [% IF (!Param('defaultplatform') || !Param('defaultopsys')) && !cloned_bug_id %]
     <tr>
       <th colspan="3">&nbsp;</th>
       <td id="os_guess_note" class="comment">
index 10279f9..17f0080 100644 (file)
     [% END %] 
   </h3>
   [% IF ids.size %]
-    ([% IF maxdepth -%]Up to [% maxdepth %] level[% "s" IF maxdepth > 1 %] deep | [% END -%]
-    <a href="buglist.cgi?bug_id=[% ids.join(",") %]">view as [% terms.bug %] list</a>
-    [% IF user.in_group('editbugs') && ids.size > 1 %]
-      | <a href="buglist.cgi?bug_id=[% ids.join(",") %]&amp;tweak=1">change several</a>
-    [% END %])
+    [%# 27 chars is the length of buglist.cgi?tweak=&bug_id=" %]
+    [% use_post = (ids.join(",").length > constants.CGI_URI_LIMIT - 27 ) ? 1 : 0 %]
+    [% IF use_post %]
+      <form action="buglist.cgi" method="post">
+      <input type="hidden" name="bug_id" value="[% ids.join(",") %]">
+    [% END %]
+
+    [% IF maxdepth -%]Up to [% maxdepth %] level[% "s" IF maxdepth > 1 %] deep | [% END -%]
+    [% IF use_post %]
+      <button>view as [% terms.bug %] list</button>
+      [% IF user.in_group('editbugs') && ids.size > 1 %]
+        | <button type="submit" name="tweak" value="1">change several</button>
+      [% END %]
+      </form>
+    [% ELSE %]
+      <a href="buglist.cgi?bug_id=[% ids.join(",") %]">view as [% terms.bug %] list</a>
+      [% IF user.in_group('editbugs') && ids.size > 1 %]
+        | <a href="buglist.cgi?bug_id=[% ids.join(",") %]&amp;tweak=1">change several</a>
+      [% END %]
+    [% END %]
+
     <ul class="tree">
       [% INCLUDE display_tree tree=$tree_name %]
     </ul>
index bdee838..fbc6e4a 100644 (file)
@@ -30,9 +30,8 @@
 
 [% PROCESS bug/time.html.tmpl %]
 
-  <script type="text/javascript">
-  <!--
-  
+<script type="text/javascript">
+<!--
   /* Outputs a link to call replyToComment(); used to reduce HTML output */
   function addReplyLink(id, real_id) {
       /* XXX this should really be updated to use the DOM Core's
 
 [% END %]
 
+[% IF user.id %]
   /* Index all classifications so we can keep track of the classification
    * for the selected product, which could control field visibility.
    */
       all_classifications['[% product.name FILTER js %]'] = '
           [%- product.classification.name FILTER js %]';
   [%- END %]
-
-  //-->
-  </script>
+[% END %]
+//-->
+</script>
 
 <form name="changeform" id="changeform" method="post" action="process_bug.cgi">
 
index 13ec18d..3133c15 100644 (file)
   #%]
 
 [% FOREACH controlled_field = field.controls_visibility_of %]
+  [% vis_names = [] %]
+  [% FOREACH visibility_value = controlled_field.visibility_values %]
+    [%# Exclude non-enterable products and components outside the current product. %]
+    [% NEXT IF field.name == "product"
+               && visibility_value.id != product.id
+               && !user.can_enter_product(visibility_value) %]
+    [% NEXT IF field.name == "component" && visibility_value.product_id != product.id %]
+    [% vis_names.push(visibility_value.name) %]
+  [% END %]
+
+  [% NEXT UNLESS vis_names.size %]
+
   showFieldWhen('[% controlled_field.name FILTER js %]',
                 '[% field.name FILTER js %]', [
-  [%- FOREACH visibility_value = controlled_field.visibility_values -%]
-    '[%- visibility_value.name FILTER js -%]'[% "," UNLESS loop.last %]
-  [%- END %]
+                [%~ FOREACH vis_name = vis_names ~%]
+                  '[% vis_name FILTER js %]'[% "," UNLESS loop.last %]
+                [%~ END ~%]
   ]);
 [% END %]
 
@@ -43,6 +55,7 @@
   [% FOREACH controlled_field = legal_value.controlled_values.keys %]
     [% SET cont_ids = [] %]
     [% FOREACH val = legal_value.controlled_values.$controlled_field %]
+      [% NEXT IF !val.is_active %]
       [% cont_ids.push(val.id) %]
     [% END %]
     [% NEXT IF !cont_ids.size %]
index 7ae9991..a9449fb 100644 (file)
@@ -91,7 +91,7 @@ estimated_time =>
 
 keywords =>
    "You can add keywords from a defined list to $terms.bugs, in order"
-   _ " to tag and group them.",
+   _ " to easily identify and group them.",
 
 longdesc =>
   "$terms.Bugs have comments added to them by $terms.Bugzilla users."
index b138668..dc09848 100644 (file)
@@ -36,7 +36,9 @@
 [%# We use "FILTER none" here because link_title is filtered down below. %]
 [% link_title = BLOCK %]
   [% display_value('bug_status', bug.bug_status) FILTER none %]
-  [%+ display_value('resolution', bug.resolution) FILTER none %]
+  [% IF bug.resolution %]
+    [%+ display_value('resolution', bug.resolution) FILTER none %]
+  [% END %]
 [% END %]
 
 [% IF user.can_see_bug(bug) %]
index e42b556..d52fe63 100644 (file)
       [% FOREACH comment = new_comments.reverse %]
         <div>
           [% IF comment.count %]
-            <b>[% "Comment # ${comment.count}" FILTER bug_link( bug, 
-              {comment_num => comment.count, full_url => 1}) FILTER none %] 
+            <b>[% "Comment # ${comment.count}" FILTER bug_link(bug,
+              {comment_num => comment.count, full_url => 1, user => to_user}) FILTER none %]
+              on [% "$terms.bug $bug.id" FILTER bug_link(bug, { full_url => 1, user => to_user }) FILTER none %]
               from [% INCLUDE global/user.html.tmpl who = comment.author %]</b>
           [% END %]
-        <pre>[% comment.body_full({ wrap => 1 }) FILTER quoteUrls(bug, comment) %]</pre>
+        <pre>[% comment.body_full({ wrap => 1 }) FILTER quoteUrls(bug, comment, to_user) %]</pre>
         </div>
       [% END %]
       </p>
           [% SET in_table = 0 %]
         [% END %]
         [% IF change.blocker %]
-              [% "${terms.Bug} ${bug.id}" FILTER bug_link(bug, full_url => 1) FILTER none  %] depends 
-              on [% "${terms.bug} ${change.blocker.id}" 
-                  FILTER bug_link(change.blocker, full_url => 1) FILTER none %],
+              [% "${terms.Bug} ${bug.id}" FILTER bug_link(bug, {full_url => 1, user => to_user}) FILTER none %]
+              depends on
+              [%+ "${terms.bug} ${change.blocker.id}"
+                  FILTER bug_link(change.blocker, {full_url => 1, user => to_user}) FILTER none %],
               which changed state.
         [% ELSE %]
-              [% INCLUDE global/user.html.tmpl who = change.who %]
-              changed [% "${terms.Bug} ${bug.id}" FILTER bug_link(bug, full_url => 1) FILTER none %]
+              [% INCLUDE global/user.html.tmpl who = change.who %] changed
+              [%+ "${terms.bug} ${bug.id}" FILTER bug_link(bug, {full_url => 1, user => to_user}) FILTER none %]
         [% END %]
         <br>
           [% IF in_table == 0 %]
           <th>[% field_label FILTER html %]</th>
           <td>
             [% IF change.field_name == "bug_id" %]
-              [% new_value FILTER bug_link(bug, full_url => 1) FILTER none %]
+              [% new_value FILTER bug_link(bug, {full_url => 1, user => to_user}) FILTER none %]
             [% ELSE %]
               [% new_value FILTER html %]
             [% END %]
index 8680573..897ab14 100644 (file)
   'other_format.name', 
   'sizeurl', 
   'switchbase',
-  'format',
   'cumulate',
 ],
 
 'list/table.html.tmpl' => [
   'tableheader',
   'bug.bug_id', 
-  'abbrev.$id.title || field_descs.$id || column.title',
 ],
 
 'list/list.csv.tmpl' => [
 
 'global/confirm-user-match.html.tmpl' => [
   'script',
-  'fields.${field_name}.flag_type.name',
 ],
 
 'global/site-navigation.html.tmpl' => [
 
 'bug/comments.html.tmpl' => [
   'comment.id',
+  'comment.count',
   'bug.bug_id',
 ],
 
 ],
 
 'admin/flag-type/edit.html.tmpl' => [
-  'type.id', 
-  'type.sortkey || 1',
   'selname',
 ],
 
index f09415c..877fe8d 100644 (file)
     setting in [% constants.bz_locations.localconfig FILTER html %].
 
   [% ELSIF error == "mismatched_bug_ids_on_obsolete" %]
-    Attachment [% attach_id FILTER html %] ([% description FILTER html %]) 
-    is attached to [% terms.bug %] [%+ attach_bug_id FILTER html %], 
+    Attachment [% attach_id FILTER html %] is attached to another [% terms.bug %],
     but you tried to flag it as obsolete while creating a new attachment to 
     [%+ terms.bug %] [%+ my_bug_id FILTER html %].
 
   [% ELSIF error == "token_generation_error" %]
     Something is seriously wrong with the token generation system.
 
+  [% ELSIF error == "cancel_token_does_not_exist" %]
+    The token to be cancelled does not exist.
+
   [% ELSIF error == "template_error" %]
     [% template_error_msg FILTER html %]
 
   [% ELSIF error == "invalid_post_bug_submit_action" %]
     Invalid setting for post_bug_submit_action
 
+  [% ELSIF error == "search_field_operator_unsupported" %]
+    [% terms.Bugzilla %] does not support the search type
+    "[% operator FILTER html %]".
+
   [% ELSE %]
     [%# Try to find hooked error messages %]
     [% error_message = Hook.process("errors") %]
index 5549b51..971bf2f 100644 (file)
                 [% ELSE %]
                   matched
                   <b>[% query.value.users.0.identity FILTER html %]</b>
-                  <input type="hidden" name="[% field.key FILTER html %]"
-                         value="[% query.value.users.0.login FILTER html %]">
                 [% END %]
             [% ELSE %]
                 [% IF (query.key.length < 3) && !Param('emailsuffix') %]
 
 [% IF matchsuccess == 1 %]
 
-  [% SET exclude_these = 
-           matches.keys.merge(['Bugzilla_login', 'Bugzilla_password']) %]
+  [% SET exclude_these = ['Bugzilla_login', 'Bugzilla_password'] %]
+  [% FOREACH key IN matches.keys %]
+    [% exclude_these.push(key) IF cgi.param(key) == '' %]
+  [% END %]
   [% SET exclude = '^' _ exclude_these.join('|') _ '$' %]
   [% PROCESS "global/hidden-fields.html.tmpl" exclude = exclude %]
 
   [% ELSIF field_labels.$field_name %]
     [% field_labels.$field_name FILTER html %]
   [% ELSIF field_name.match("^requestee") %]
-    [% fields.${field_name}.flag_type.name %] requestee
+    [% fields.${field_name}.flag_type.name FILTER html %] requestee
   [% ELSE %]
     [% field_name FILTER html %]
   [% END %]
index 21f41c8..3e86e9b 100644 (file)
      "setting"                 => "Setting",
      "settings"                => "Settings",
      "short_desc"              => "Summary",
+     "short_short_desc"        => "Summary",
      "status_whiteboard"       => "Whiteboard",
      "tag.name"                => "Tags",
      "target_milestone"        => "Target Milestone",
index a744988..0dffcb5 100644 (file)
   #%]
 
   <body onload="[% onload %]"
-        class="[% urlbase.replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') %]
+        class="[% urlbase.replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') FILTER css_class_quote %]
                [% FOREACH class = bodyclasses %]
                  [% ' ' %][% class FILTER css_class_quote %]
                [% END %] yui-skin-sam">
index 3d1ac5c..8de4124 100644 (file)
     [% title = "Missing Search" %]
     [% docslinks = {'query.html' => "Searching for $terms.bugs",
                     'query.html#list' => "$terms.Bug lists"} %]
-    The search named <em>[% queryname FILTER html %]</em>
+    The search named <em>[% name FILTER html %]</em>
     [% IF sharer_id && sharer_id != user.id %]
       has not been made visible to you.
     [% ELSE %]
     [%+ constants.USER_PASSWORD_MIN_LENGTH FILTER html %] characters long.
 
   [% ELSIF error == "password_not_complex" %]
-     [% title = "Password Fails Requirements" %]
-     [% passregex = Param('password_complexity') %]
-     The password must contain at least one:
-     <ul>
-       [% IF passregex.search('letters') %]
-         <li>UPPERCASE letter</li>
-         <li>lowercase letter</li>
-       [% END %]
-       [% IF passregex.search('numbers') %]
-         <li>digit</li>
-       [% END %]
-       [% IF passregex.search('specialchars') %]
-         <li>special character</li>
-       [% END %]
-     </ul>
+    [% title = "Password Fails Requirements" %]
+    [% passregex = Param('password_complexity') %]
+    The password must contain at least one:
+    <ul>
+      [% IF passregex == 'letters_numbers_specialchars' %]
+        <li>letter</li>
+        <li>special character</li>
+      [% ELSIF passregex.search('letters') %]
+        <li>UPPERCASE letter</li>
+        <li>lowercase letter</li>
+      [% END %]
+      [% IF passregex.search('numbers') %]
+        <li>digit</li>
+      [% END %]
+    </ul>
 
   [% ELSIF error == "product_access_denied" %]
     [% title = "Product Access Denied" %]
     [% title = "Unknown Tab" %]
     <code>[% current_tab_name FILTER html %]</code> is not a legal tab name.
 
+  [% ELSIF error == "value_inactive" %]
+    [% title = "Value is Not Active" %]
+    [% type = BLOCK %][% INCLUDE object_name class = class %][% END %]
+    The [% type FILTER html %] value '[% value FILTER html %]' is not active.
+
   [% ELSIF error == "version_already_exists" %]
     [% title = "Version Already Exists" %]
     [% admindocslinks = {'versions.html' => 'Administering versions'} %]
                   
     [% FOREACH q = Bugzilla.user.queries %]
       [% IF q.name == namedcmd %]
-        or <a href="query.cgi?[% q.url FILTER uri %]">edit</a>
+        or <a href="query.cgi?[% q.url FILTER html %]">edit</a>
       [% END %]
     [% END %]
     
index d1c157f..914d827 100644 (file)
     <h1 style="margin-top: 20%; text-align: center;">Please stand by ...</h1>
 
     [% IF debug %]
-      <p>
-        [% FOREACH debugline = debugdata %]
-          <code>[% debugline FILTER html %]</code><br>
-        [% END %]
-      </p>
-      <p>
-        <code>[% query FILTER html %]</code>
-      </p>
+      <p>[% query FILTER html %]</p>
+      [% IF query_explain.defined %]
+        <pre>[% query_explain FILTER html %]</pre>
+      [% END %]
     [% END %]
-
   </body>
 </html>
index 2b266d4..a074fcb 100644 (file)
       [% PROCESS new_order %]
       [%-#%]&amp;query_based_on=
       [% defaultsavename OR searchname FILTER uri %]">
-        [%- abbrev.$id.title || field_descs.$id || column.title -%]
+        [%- abbrev.$id.title || field_descs.$id || column.title FILTER html -%]
         [% PROCESS order_arrow ~%]
     </a>
   </th>
index 3cba644..ebc08af 100644 (file)
 
 <h2 id="v42_point">Updates in this 4.2.x Release</h2>
 
+<h3>4.2.7</h3>
+
+<p>This release fixes several security issues. See the
+  <a href="http://www.bugzilla.org/security/4.0.10/">Security Advisory</a>
+  for details.</p>
+
+<p>In addition, the following [% terms.bugs %] have been fixed in this release:</p>
+
+<ul>
+  <li>Internet Explorer 11 and KHTML-based browsers such as Konqueror can now
+    display buglists correctly.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=902515">[% terms.Bug %] 902515</a> and
+    <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=914262">[% terms.bug %] 914262</a>)</li>
+  <li>When the <kbd>password_complexity</kbd> parameter was set to
+    'letters_numbers_specialchars', passwords containing numbers and special
+    characters only were accepted. Now it makes sure that a letter is also present.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=897264">[% terms.Bug %] 897264</a>)</li>
+  <li>With DB servers doing case-insensitive comparisons, such as MySQL, tokens
+    and login cookies were not correctly validated as the case was ignored.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=906745">[% terms.Bug %] 906745</a> and
+    <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=907438">[% terms.bug %] 907438</a>)</li>
+  <li>All security headers (such as X-Frame-Options) are now returned when using XML-RPC.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=787328">[% terms.Bug %] 787328</a>)</li>
+  <li>Oracle crashed when reporting a new [% terms.bug %] if a custom free-text field
+    was non-mandatory and left empty.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=919475">[% terms.Bug %] 919475</a>)</li>
+  <li>It was not possible to import [% terms.bugs %] using <kbd>importxml.pl</kbd> with Oracle.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=848063">[% terms.Bug %] 848063</a>)</li>
+</ul>
+
+<h3>4.2.6</h3>
+
+<p>The following important fixes/changes have been made in this release:</p>
+
+<ul>
+  <li>MySQL 5.6 is now supported.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=852560">[% terms.Bug %] 852560</a>)</li>
+  <li>A regression introduced in [% terms.Bugzilla %] 4.2.4 made Oracle crash
+    when installing [% terms.Bugzilla %] for the first time.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=858911">[% terms.Bug %] 858911</a>)</li>
+  <li>If a custom field depends on a product, component or classification,
+    the "mandatory" bit was ignored on [% terms.bug %] creation.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=782210">[% terms.Bug %] 782210</a>)</li>
+  <li>Queries involving flags were broken in several ways. These queries
+    have been fixed.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=828344">[% terms.Bug %] 828344</a>)</li>
+  <li>Tabular reports involving the empty resolution did not link [% terms.bug %]
+    counts correctly.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=212471">[% terms.Bug %] 212471</a>)</li>
+  <li>The <kbd>B[%%]ug.search</kbd> WebService method was returning all visible
+    [%+ terms.bugs %] when called with no arguments, ignoring the
+    <kbd>max_search_results</kbd> and <kbd>search_allow_no_criteria</kbd> parameters.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=859118">[% terms.Bug %] 859118</a>)</li>
+</ul>
+
+<h3>4.2.5</h3>
+
+<p>This release fixes one security issue. See the
+  <a href="http://www.bugzilla.org/security/3.6.12/">Security Advisory</a>
+  for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in this
+  release:</p>
+
+<ul>
+  <li>Queries involving commenters were slow to return results. These queries
+    have been optimized for better performance.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=818007">[% terms.Bug %] 818007</a>)</li>
+  <li>It is no longer possible to create a new [% terms.bug %] using a disabled
+    component, target milestone or version. These inactive values are also no
+    longer accessible when moving [% terms.abug %] into another product.
+    ([% terms.Bugs %] <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=752946">752946</a>
+    and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=840824">840824</a>)</li>
+  <li>It was possible to create a new [% terms.bug %] with no description
+    despite the status workflow required one for new [% terms.bugs %].
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=818890">[% terms.Bug %] 818890</a>)</li>
+  <li>Custom multi-select fields are now available in the "Search By Change
+    History" section of the "Advanced Search" page.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=839950">[% terms.Bug %] 839950</a>)</li>
+  <li>A custom select field could have its list of values truncated if one
+    or more of its values were disabled and the visibility of the values were
+    controlled by another field.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=806809">[% terms.Bug %] 806809</a>)</li>
+  <li>Warnings thrown by Return::Value 1.666002 about this deprecated module
+    and which are polluting the web server error log are now disabled.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=826678">[% terms.Bug %] 826678</a>)</li>
+</ul>
+
+<h3>4.2.4</h3>
+
+<p>This release fixes several security issues. See the
+  <a href="http://www.bugzilla.org/security/3.6.11/">Security Advisory</a>
+  for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in this
+  release:</p>
+
+<ul>
+  <li>Queries involving group substitution were crashing when the "usevisibilitygroups"
+    parameter was enabled. Also, CVE-2011-2979 was not fully fixed in
+    [%+ terms.Bugzilla %] 4.1.3.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=788098">[% terms.Bug %] 788098</a>)</li>
+  <li>Flag names were not properly escaped when displayed on the "confirm user
+    match" page. An admin could unintentionally break the display of this page
+    if a flag name contains a &lt; or &gt; character, because these characters
+    were not filtered.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=790215">[% terms.Bug %] 790215</a>)</li>
+  <li>We now prevent private WebServices methods from being called by external
+    applications.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=793826">[% terms.Bug %] 793826</a>)</li>
+  <li>PostgreSQL 9.2 requires DBD::Pg 2.19.3.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=799721">[% terms.Bug %] 799721</a>)</li>
+  <li>Oracle was crashing when listing keywords or flags in buglists.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780053">[% terms.Bug %] 780053</a>)</li>
+  <li>Oracle was crashing when typing several bare words in the QuickSearch field.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=804505">[% terms.Bug %] 804505</a>)</li>
+  <li>[% terms.Bugs %] with the resolution MOVED couldn't be edited anymore.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=757935">[% terms.Bug %] 757935</a>)</li>
+  <li>Editing dependencies from the "Change Several [% terms.Bugs %] at Once"
+    page didn't work as expected. [% terms.Bug %] IDs were incorrectly parsed.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=790909">[% terms.Bug %] 790909</a>)</li>
+  <li>The "Actual Hours" axis now works correctly in tabular and graphical reports.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=794389">[% terms.Bug %] 794389</a>)</li>
+  <li><kbd>checksetup.pl</kbd> was failing to run if the Voting extension was
+    enabled on a fresh installation and some mandatory modules were missing.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=652047">[% terms.Bug %] 652047</a>)</li>
+  <li>[% terms.Bugzilla %] no longer crashes when viewing [% terms.abug %] while
+    a custom field is being added.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=531243">[% terms.Bug %] 531243</a>)</li>
+  <li>For improved security, we now send the "X-Content-Type-Options:&nbsp;nosniff"
+    and "X-XSS-Protection:&nbsp;block" headers with every response.
+    ([% terms.Bugs %] <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=671612">671612</a>
+    and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=680771">680771</a>)</li>
+</ul>
+
+<h3>4.2.3</h3>
+
+<p>This release fixes two security issues. See the
+  <a href="http://www.bugzilla.org/security/3.6.10/">Security Advisory</a>
+  for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in this
+  release:</p>
+
+<ul>
+  <li>Attaching a file to [% terms.abug %] was broken due to a change in
+    Perl 5.16.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=771100">[% terms.Bug %] 771100</a>)</li>
+  <li>A regression in [% terms.Bugzilla %] 4.2.2 made Oracle crash when
+    displaying a buglist.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780028">[% terms.Bug %] 780028</a>)</li>
+  <li>It was possible to search on history for comments and attachments you
+    cannot see (though these private comments and attachments are never disclosed).
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=779709">[% terms.Bug %] 779709</a>)</li>
+  <li>PostgreSQL databases could be created with the wrong encoding despite
+    the utf8 parameter being enabled.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=783786">[% terms.Bug %] 783786</a>)</li>
+  <li>Scheduled whines could be sent at the wrong time on Oracle.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=559539">[% terms.Bug %] 559539</a>)</li>
+  <li>Tokens are no longer included in saved queries.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=772953">[% terms.Bug %] 772953</a>)</li>
+  <li>An admin could unintentionally break the display of buglists if a custom
+    field description contains a &lt; or &gt; character, because these characters
+    were not filtered.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=785917">[% terms.Bug %] 785917</a>)</li>
+  <li>Adding or removing a DB column in Oracle didn't handle SERIAL columns
+    correctly.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=731156">[% terms.Bug %] 731156</a>)</li>
+  <li>A minor CSRF vulnerability in token.cgi allowed possible unauthorized
+    password reset e-mail requests.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=706271">[% terms.Bug %] 706271</a>)</li>
+</ul>
+
+<h3>4.2.2</h3>
+
+<p>This release fixes two security issues. See the
+  <a href="http://www.bugzilla.org/security/3.6.9/">Security Advisory</a>
+  for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in this
+  release:</p>
+
+<ul>
+  <li>A regression introduced in [% terms.Bugzilla %] 4.0 caused some login
+    names to be ignored when entered in the CC list of [% terms.bugs %].
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=756314">[% terms.Bug %] 756314</a>)</li>
+  <li>Some queries could trigger an invalid SQL query if strings entered by
+    the user contained leading or trailing whitespaces.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=760075">[% terms.Bug %] 760075</a>)</li>
+  <li>The auto-completion form for keywords no longer automatically selects
+    the first keyword in the list when the field is empty.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=764517">[% terms.Bug %] 764517</a>)</li>
+  <li>A regression in [% terms.Bugzilla %] 4.2 prevented classifications
+    from being used in graphical and tabular reports in the "Multiple Tables"
+    field.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=753688">[% terms.Bug %] 753688</a>)</li>
+  <li>Attachments created by the <kbd>email_in.pl</kbd> script were associated
+    to the wrong comment.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=762785">[% terms.Bug %] 762785</a>)</li>
+  <li>Very long dependency lists can now be viewed correctly.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=762783">[% terms.Bug %] 762783</a>)</li>
+  <li>Keywords are now correctly escaped in the auto-completion form to prevent
+    any XSS abuse.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=754561">[% terms.Bug %] 754561</a>)</li>
+  <li>A regression introduced in [% terms.Bugzilla %] 4.0rc2 when fixing
+    CVE-2011-0046 caused the "Un-forget the search" link to not work correctly
+    anymore when restoring a deleted saved search, because this link was
+    lacking a valid token.
+    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=768870">[% terms.Bug %] 768870</a>)</li>
+  <li>Two minor CSRF vulnerabilities have been fixed which could let an attacker
+    alter your default search criteria in the Advanced Search page.
+    ([% terms.Bugs %] <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=754672">754672</a>
+    and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=754673">754673</a>)</li>
+</ul>
+
 <h3>4.2.1</h3>
 
 <p>This release fixes two security issues. See the
     [%- terms.Bug %] 584742</a>: When viewing [% terms.abug %], WebKit-based
     browsers can automatically reset a field's selected value when the field
     has disabled values.</li>
+  <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780053">
+    [%- terms.Bug %] 780053</a>: Oracle crashes when listing keywords, tags
+    or flags in buglists.</li>
 </ul>
 
 
     (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=640719">[% terms.Bug %] 640719</a>)</li>
   <li>Email notifications about dependencies and flags had the wrong
     timestamp.
-    (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=643910">[% terms.Bug %] 643910</a>
-    and  (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=652165">[% terms.Bug %] 652165</a>)</li>
+    ([% terms.Bugs %] <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=643910">643910</a>
+    and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=652165">652165</a>)</li>
   <li>You can now select "UTC" as a valid timezone in General Preferences.
     (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=646209">[% terms.Bug %] 646209</a>)</li>
   <li>Automatic duplicate detection now works on PostgreSQL (although
index 8a3ab95..cef47c2 100644 (file)
 [% END %]
 
 <script type="text/javascript">
+function bz_encode (str, decode) {
+  // First decode HTML entities, if requested.
+  if (decode)
+    str = str.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"')
+             .replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/\s+$/,"");
+
+  // encodeURIComponent() doesn't escape single quotes.
+  return encodeURIComponent(str).replace(/'/g, escape);
+};
+
 YAHOO.util.Event.addListener(window, "load", function() {
   this.Linkify = function(elLiner, oRecord, oColumn, oData) {
     if (oData == 0)
       elLiner.innerHTML = ".";
     else if (oRecord.getData("row_title") == "Total")
-      elLiner.innerHTML = "<a href='[% urlbase %]&amp;[% col_field FILTER js %]="
-                          + oColumn.field + "[% '&amp;' _ row_vals IF row_vals %]'>"
-                          + oData + "</a>";
+      elLiner.innerHTML = '<a href="[% urlbase FILTER js %]&amp;[% col_field FILTER uri FILTER js %]='
+                          + bz_encode(oColumn.field)
+                          + '[% "&amp;" _ row_vals IF row_vals %]">' + oData + '</a>';
     else
-      elLiner.innerHTML = "<a href='[% urlbase %]&amp;[% row_field FILTER js %]="
-                          + oRecord.getData("row_title").replace(/\s+$/,"")
-                          + "&amp;[% col_field FILTER js %]=" + oColumn.field
-                          + "'>" + oData + "</a>";
+      elLiner.innerHTML = '<a href="[% urlbase FILTER js %]&amp;[% row_field FILTER uri FILTER js %]='
+                          + bz_encode(oRecord.getData("row_title"), 1)
+                          + '&amp;[% col_field FILTER uri FILTER js %]='
+                          + bz_encode(oColumn.field) + '">' + oData + '</a>';
   };
 
   this.LinkifyTotal = function(elLiner, oRecord, oColumn, oData) {
     if (oData == 0)
       elLiner.innerHTML = ".";
     else if (oRecord.getData("row_title") == "Total")
-      elLiner.innerHTML = "<a href='[% urlbase %][% '&amp;' _ row_vals IF row_vals %]
-                          [%~ '&amp;' _ col_vals IF col_vals %]'>"
-                          + oData + "</a>";
+      elLiner.innerHTML = '<a href="[% urlbase FILTER js %][% "&amp;" _ row_vals IF row_vals %]
+                          [%~ "&amp;" _ col_vals IF col_vals %]">'
+                          + oData + '</a>';
     else
-      elLiner.innerHTML = "<a href='[% urlbase %]&amp;[% row_field FILTER js %]="
-                          + oRecord.getData("row_title").replace(/\s+$/,"")
-                          + "[% '&amp;' _ col_vals IF col_vals %]'>" + oData + "</a>";
+      elLiner.innerHTML = '<a href="[% urlbase FILTER js %]&amp;[% row_field FILTER uri FILTER js %]='
+                          + bz_encode(oRecord.getData("row_title"), 1)
+                          + '[% "&amp;" _ col_vals IF col_vals %]">' + oData + '</a>';
 
     YAHOO.util.Dom.addClass(elLiner.parentNode, "ttotal");
   };
@@ -102,7 +112,7 @@ YAHOO.util.Event.addListener(window, "load", function() {
   var myColumnDefs = [
         {key:"row_title", label:"", sortable:true, sortOptions: { sortFunction:totalNumberSorter }},
         [% FOREACH col = col_names %]
-          {key:"[% col FILTER js %]", label:"[% display_value(col_field, col) FILTER js %]", sortable:true,
+          {key:"[% col FILTER js %]", label:"[% display_value(col_field, col) FILTER html FILTER js %]", sortable:true,
            formatter:this.Linkify, sortOptions: { defaultDir: YAHOO.widget.DataTable.CLASS_DESC, sortFunction:totalNumberSorter }},
         [% END %]
         {key:"total", label:"Total", sortable:true, formatter:this.LinkifyTotal,
@@ -164,7 +174,7 @@ YAHOO.util.Event.addListener(window, "load", function() {
 [% col_idx = 0 %]
 [% row_idx = 0 %]
 [% grand_total = 0 %]
-<div id="tabular_report_container_[% tbl FILTER js %]">
+<div id="tabular_report_container_[% tbl FILTER html %]">
 <table id="tabular_report" border="1">
   [% IF col_field %]
     <thead>
index d4c9d40..94725ae 100644 (file)
       this report</a>
     [% ELSE %]
       <a href="query.cgi?[% switchbase %]&amp;chart_format=
-        [% format %]&amp;format=report-graph&amp;cumulate=[% cumulate %]">
+        [% format FILTER uri %]&amp;format=report-graph&amp;cumulate=[% cumulate %]">
         Edit this report
       </a>
     [% END %]
index 41e1165..241ade0 100644 (file)
@@ -341,12 +341,12 @@ TUI_hide_default('information_query');
           [% " selected" IF default.emailtype.$n == qv.name %]>[% qv.description %]</option>
       [% END %]
       </select>
-      [% IF feature_enabled('jsonrpc') %]
+      [% IF feature_enabled('jsonrpc') && Param('ajax_user_autocompletion') %]
         <div id="email[% n %]_autocomplete">
       [% END %]
       <input name="email[% n %]" class="email" id="email[% n %]" 
              value="[% default.email.$n FILTER html %]">
-      [% IF feature_enabled('jsonrpc') %]
+      [% IF feature_enabled('jsonrpc') && Param('ajax_user_autocompletion') %]
         <div id="email[% n %]_autocomplete_container"></div>
         </div>
         <script type="text/javascript">
index 17ff63a..e20822b 100644 (file)
@@ -40,6 +40,9 @@
    "Last Changed" => "Last Changed" } %]
 
 <input type="hidden" name="cmdtype" value="doit">
+[% IF user.id %]
+  <input type="hidden" name="token" value="[% issue_hash_token(['searchknob']) FILTER html %]">
+[% END %]
 
 <p>
   <label for="order">Sort results by</label>:
@@ -56,7 +59,7 @@
   <input type="submit" id="[% button_name FILTER html %]"
          value="[% button_name FILTER html %]">
   [% IF known_name %]
-    [%# We store known_name in case the user add a boolean chart. %]
+    [%# We store known_name in case the user adds a boolean chart. %]
     <input type="hidden" name="known_name" value="[% known_name FILTER html %]">
 
     [%# The name of the existing query will be passed to buglist.cgi. %]
   [% END %]
 </p>
 
-<p>
-  &nbsp;&nbsp;&nbsp;
-  <input type="checkbox" id="remasdefault"
-         name="remtype" value="asdefault">
-  <label for="remasdefault">
-    and remember these as my default search options
-  </label>
-</p>
+[% IF user.id %]
+  <p>
+    &nbsp;&nbsp;&nbsp;
+    <input type="checkbox" id="remasdefault"
+           name="remtype" value="asdefault">
+    <label for="remasdefault">
+      and remember these as my default search options
+    </label>
+  </p>
+[% END %]
         
 [% IF userdefaultquery %]
   <p>
-    <a href="query.cgi?nukedefaultquery=1">
+    <a href="query.cgi?nukedefaultquery=1&amp;token=
+       [%- issue_hash_token(['nukedefaultquery']) FILTER uri %]">
       Set my default search back to the system default</a>.
   </p>
 [% END %]
index ef7fa76..780d54e 100644 (file)
 
 
 [% js_data = BLOCK %]
-var queryform = "queryform"
+var queryform = "queryform";
+function remove_token() {
+  if (queryform.token) {
+    var asDefault = document.getElementById('remasdefault');
+    queryform.token.disabled = !asDefault.checked;
+  }
+}
 [% END %]
 
 [% PROCESS global/header.html.tmpl
@@ -53,7 +59,8 @@ var queryform = "queryform"
 
 <p id="search_help">Hover your mouse over each field label to get help for that field.</p>
 
-<form method="post" action="buglist.cgi" name="queryform" id="queryform">
+<form method="post" action="buglist.cgi" name="queryform" id="queryform"
+      onsubmit="remove_token()">
 
 [% PROCESS search/form.html.tmpl %]
 
index 5e5db06..4442589 100644 (file)
         [% " selected" IF default.$name.0 == field %]>
         [% field_descs.$field || field FILTER html %]</option>
     [% END %]
-
-    [%# Single-select fields are also valid column names. %]
-    [% FOREACH field = custom_fields %]
-      <option value="[% field.name FILTER html %]"
-        [% " selected" IF default.$name.0 == field.name %]>
-        [% field.description FILTER html %]</option>
-    [% END %]
   </select>
 [% END %]
index 36c3fe2..c96fc01 100644 (file)
@@ -382,24 +382,6 @@ as well), you should install patchutils from:
 
     http://cyberelk.net/tim/patchutils/
 END
-    ppm_repo_add => <<EOT,
-***********************************************************************
-* Note For Windows Users                                              *
-***********************************************************************
-* In order to install the modules listed below, you first have to run * 
-* the following command as an Administrator:                          *
-*                                                                     *
-*   ppm repo add theory58S ##theory_url##
-EOT
-    ppm_repo_up => <<EOT,
-*                                                                     *
-* Then you have to do (also as an Administrator):                     *
-*                                                                     *
-*   ppm repo up theory58S                                             *
-*                                                                     *
-* Do that last command over and over until you see "theory58S" at the *
-* top of the displayed list.                                          *
-EOT
     template_precompile   => "Precompiling templates...",
     template_removal_failed => <<END,
 WARNING: The directory '##template_cache##' could not be removed.
index b10830b..5c2c88c 100755 (executable)
@@ -67,9 +67,10 @@ if ($token) {
   trick_taint($token);
 
   # Make sure the token exists in the database.
-  my ($tokentype) = $dbh->selectrow_array('SELECT tokentype FROM tokens
-                                           WHERE token = ?', undef, $token);
-  $tokentype || ThrowUserError("token_does_not_exist");
+  my ($db_token, $tokentype) = $dbh->selectrow_array('SELECT token, tokentype FROM tokens
+                                                       WHERE token = ?', undef, $token);
+  (defined $db_token && $db_token eq $token)
+    || 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' ) {
@@ -108,6 +109,11 @@ if ( $action eq 'reqpw' ) {
         ThrowUserError("password_change_requests_not_allowed");
     }
 
+    # Check the hash token to make sure this user actually submitted
+    # the forgotten password form.
+    my $token = $cgi->param('token');
+    check_hash_token($token, ['reqpw']);
+
     validate_email_syntax($login_name)
         || ThrowUserError('illegal_email_address', {addr => $login_name});
 
index 512d180..85547df 100644 (file)
@@ -197,11 +197,14 @@ use constant GREATERTHAN_BROKEN => (
 );
 
 # allwords and allwordssubstr have these broken tests in common.
-#
-# allwordssubstr on cc fields matches against a single cc,
-# instead of matching against all ccs on a bug.
 use constant ALLWORDS_BROKEN => (
+    # allwordssubstr on cc fields matches against a single cc,
+    # instead of matching against all ccs on a bug.
     cc        => { contains => [1] },
+    # bug 828344 changed how these searches operate to revert back to the 4.0
+    # behavour, so these tests need to be updated (bug 849117).
+    'flagtypes.name' => { contains => [1] },
+    longdesc         => { contains => [1] },
 );
 
 # Fields that don't generally work at all with changed* searches, but
@@ -260,6 +263,15 @@ use constant KNOWN_BROKEN => {
     'allwords-<1>' => {
         ALLWORDS_BROKEN,
     },
+    'anywords-<1>' => {
+        'flagtypes.name' => { contains => [1,2,3,4,5] },
+    },
+    'anywords-<1> <2>' => {
+        'flagtypes.name' => { contains => [3,4,5] },
+    },
+    'anywordssubstr-<1> <2>' => {
+        'flagtypes.name' => { contains => [3,4,5] },
+    },
 
     # setters.login_name and requestees.login name aren't tracked individually
     # in bugs_activity, so can't be searched using this method.
@@ -330,6 +342,24 @@ use constant KNOWN_BROKEN => {
         # This should probably search the reporter.
         creation_ts => { contains => [1] },
     },
+    notequals => {
+        'flagtypes.name' => { contains => [1, 5] },
+        longdesc         => { contains => [1] },
+    },
+    notregexp => {
+        'flagtypes.name' => { contains => [1, 5] },
+        longdesc         => { contains => [1] },
+    },
+    notsubstring => {
+        'flagtypes.name' => { contains => [5] },
+        longdesc         => { contains => [1] },
+    },
+    nowords => {
+        'flagtypes.name' => { contains => [1, 5] },
+    },
+    nowordssubstr => {
+        'flagtypes.name' => { contains => [5] },
+    },
 };
 
 ###################
@@ -360,17 +390,40 @@ use constant CHANGED_FROM_TO_BROKEN_NOT => (
 
 # These are field/operator combinations that are broken when run under NOT().
 use constant BROKEN_NOT => {
-    allwords       => {
-        cc => { contains => [1] },
+    allwords => {
+        cc               => { contains => [1] },
+        'flagtypes.name' => { contains => [1, 5] },
+        longdesc         => { contains => [1] },
     },
     'allwords-<1> <2>' => {
         cc => { },
     },
     allwordssubstr => {
-        cc => { contains => [1] },
+        cc               => { contains => [1] },
+        'flagtypes.name' => { contains => [5, 6] },
+        longdesc         => { contains => [1] },
     },
     'allwordssubstr-<1>,<2>' => {
-        cc => { },
+        cc               => { },
+        longdesc         => { contains => [1] },
+    },
+    anyexact => {
+        'flagtypes.name' => { contains => [1, 2, 5] },
+    },
+    'anywords-<1>' => {
+        'flagtypes.name' => { contains => [1, 2, 3, 4, 5] },
+    },
+    'anywords-<1> <2>' => {
+        'flagtypes.name' => { contains => [3, 4, 5] },
+    },
+    anywordssubstr => {
+        'flagtypes.name' => { contains => [5] },
+    },
+    'anywordssubstr-<1> <2>' => {
+        'flagtypes.name' => { contains => [3,4,5] },
+    },
+    casesubstring => {
+        'flagtypes.name' => { contains => [5] },
     },
     changedafter => {
         "attach_data.thedata"   => { contains => [2, 3, 4] },
@@ -397,7 +450,6 @@ use constant BROKEN_NOT => {
         dependson       => { contains => [1, 3] },
         work_time       => { contains => [1] },
         FIELD_TYPE_BUG_ID, { contains => [1 .. 4] },
-        
     },
     changedto => {
         CHANGED_BROKEN_NOT,
@@ -406,10 +458,44 @@ use constant BROKEN_NOT => {
         "remaining_time" => { contains => [1] },
     },
     greaterthan => {
-        cc        => { contains => [1] },
+        cc               => { contains => [1] },
+        'flagtypes.name' => { contains => [5] },
     },
     greaterthaneq => {
         cc               => { contains => [1] },
+        'flagtypes.name' => { contains => [2, 5] },
+    },
+    equals => {
+        'flagtypes.name' => { contains => [1, 5] },
+    },
+    notequals => {
+        longdesc         => { contains => [1] },
+    },
+    notregexp => {
+        longdesc         => { contains => [1] },
+    },
+    notsubstring => {
+        longdesc         => { contains => [1] },
+    },
+    'nowords-<1>' => {
+        'flagtypes.name' => { contains => [5] },
+    },
+    'nowordssubstr-<1>' => {
+        'flagtypes.name' => { contains => [5] },
+    },
+    lessthan => {
+        'flagtypes.name' => { contains => [5] },
+    },
+    lessthaneq => {
+        'flagtypes.name' => { contains => [1, 5] },
+    },
+    regexp => {
+        'flagtypes.name' => { contains => [1, 5] },
+        longdesc         => { contains => [1] },
+    },
+    substring => {
+        'flagtypes.name' => { contains => [5] },
+        longdesc         => { contains => [1] },
     },
 };
 
index 283a90d..ee25f2d 100644 (file)
@@ -28,6 +28,7 @@ use strict;
 use warnings;
 use Bugzilla::Search;
 use Bugzilla::Test::Search::Constants;
+use Bugzilla::Util qw(trim);
 
 use Data::Dumper;
 use Scalar::Util qw(blessed);
@@ -72,6 +73,13 @@ sub bug {
     my $self = shift;
     return $self->search_test->bug(@_);
 }
+sub number {
+    my ($self, $id) = @_;
+    foreach my $number (1..NUM_BUGS) {
+        return $number if $self->search_test->bug($number)->id == $id;
+    }
+    return 0;
+}
 
 # The name displayed for this test by Test::More. Used in test descriptions.
 sub name {
@@ -147,9 +155,18 @@ sub translated_value {
     return $self->{translated_value};
 }
 # Used in failure diagnostic messages.
-sub debug_value {
-    my ($self) = @_;
-    return "Value: '" . $self->translated_value . "'";
+sub debug_fail {
+    my ($self, $number, $results, $sql) = @_;
+    my @expected = @{ $self->test->{contains} };
+    my @results = sort
+                  map { $self->number($_) }
+                  map { $_->[0] }
+                  @$results;
+    return
+        "   Value: '" . $self->translated_value . "'\n" .
+        "Expected: [" . join(',', @expected) . "]\n" .
+        " Results: [" . join(',', @results) . "]\n" .
+        trim($sql) . "\n";
 }
 
 # True for a bug if we ran the "transform" function on it and the
@@ -184,6 +201,7 @@ sub bug_is_contained {
 # The tests we know are broken for this operator/field combination.
 sub _known_broken {
     my ($self, $constant, $skip_pg_check) = @_;
+
     $constant ||= KNOWN_BROKEN;
     my $field = $self->field;
     my $type = $self->field_object->type;
@@ -192,8 +210,8 @@ sub _known_broken {
     my $value_name = "$operator-$value";
     if (my $extra_name = $self->test->{extra_name}) {
         $value_name .= "-$extra_name";
-    }    
-    
+    }
+
     my $value_broken = $constant->{$value_name}->{$field};
     $value_broken ||= $constant->{$value_name}->{$type};
     return $value_broken if $value_broken;
@@ -601,12 +619,12 @@ sub _test_content_for_bug {
         if ($self->bug_is_contained($number)) {
             ok($result_ids{$bug_id},
                "$name: contains bug $number ($bug_id)")
-                or diag Dumper($results) . $self->debug_value . "\n\nSQL: $sql";
+                or diag $self->debug_fail($number, $results, $sql);
         }
         else {
             ok(!$result_ids{$bug_id},
                "$name: does not contain bug $number ($bug_id)")
-                or diag Dumper($results) . $self->debug_value . "\n\nSQL: $sql";
+                or diag $self->debug_fail($number, $results, $sql);
         }
     }
 }