]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 452919: Allow the "created an attachment" message in comments to be localized
authormkanat%bugzilla.org <>
Fri, 4 Dec 2009 14:28:47 +0000 (14:28 +0000)
committermkanat%bugzilla.org <>
Fri, 4 Dec 2009 14:28:47 +0000 (14:28 +0000)
Patch by Max Kanat-Alexander <mkanat@bugzilla.org> r=LpSolit, a=LpSolit

14 files changed:
Bugzilla/Bug.pm
Bugzilla/Comment.pm
Bugzilla/Constants.pm
Bugzilla/Install/DB.pm
Bugzilla/Template.pm
Bugzilla/User.pm
Bugzilla/WebService/Bug.pm
attachment.cgi
importxml.pl
post_bug.cgi
template/en/default/bug/format_comment.txt.tmpl
template/en/default/bug/show.xml.tmpl
template/en/default/global/code-error.html.tmpl
template/en/default/global/user-error.html.tmpl

index d8134e3cbc7ff03c89deda68a924fdee590c6b1f..d55549cd66b4d5ad190deca379928976d264b422 100644 (file)
@@ -2678,6 +2678,7 @@ sub comments {
         my $count = 0;
         foreach my $comment (@{ $self->{'comments'} }) {
             $comment->{count} = $count++;
+            $comment->{bug} = $self;
         }
     }
     my @comments = @{ $self->{'comments'} };
@@ -2973,37 +2974,6 @@ sub bug_alias_to_id {
 # Subroutines
 #####################################################################
 
-sub update_comment {
-    my ($self, $comment_id, $new_comment) = @_;
-
-    # Some validation checks.
-    if ($self->{'error'}) {
-        ThrowCodeError("bug_error", { bug => $self });
-    }
-    detaint_natural($comment_id)
-      || ThrowCodeError('bad_arg', {argument => 'comment_id', function => 'update_comment'});
-
-    # The comment ID must belong to this bug.
-    my @current_comment_obj = grep {$_->id == $comment_id} @{$self->comments};
-    scalar(@current_comment_obj)
-      || ThrowCodeError('bad_arg', {argument => 'comment_id', function => 'update_comment'});
-
-    # If the new comment is undefined, then there is nothing to update.
-    # To delete a comment, an empty string should be passed.
-    return unless defined $new_comment;
-    $new_comment =~ s/\s*$//s;    # Remove trailing whitespaces.
-    $new_comment =~ s/\r\n?/\n/g; # Handle Windows and Mac-style line endings.
-    trick_taint($new_comment);
-
-    # We assume _check_comment() has already been called earlier.
-    Bugzilla->dbh->do('UPDATE longdescs SET thetext = ? WHERE comment_id = ?',
-                       undef, ($new_comment, $comment_id));
-    $self->_sync_fulltext();
-
-    # Update the comment object with this new text.
-    $current_comment_obj[0]->{'thetext'} = $new_comment;
-}
-
 # Represents which fields from the bugs table are handled by process_bug.cgi.
 sub editable_bug_fields {
     my @fields = Bugzilla->dbh->bz_table_columns('bugs');
index 7072dbbee79240f810697c39c0733882146ba310..f0998542624bb6121e6605f250f3df54a228681b 100644 (file)
@@ -24,6 +24,7 @@ package Bugzilla::Comment;
 
 use base qw(Bugzilla::Object);
 
+use Bugzilla::Attachment;
 use Bugzilla::Constants;
 use Bugzilla::Error;
 use Bugzilla::Util;
@@ -45,20 +46,66 @@ use constant DB_COLUMNS => qw(
     extra_data
 );
 
+use constant UPDATE_COLUMNS => qw(
+    type
+    extra_data
+);
+
 use constant DB_TABLE => 'longdescs';
 use constant ID_FIELD => 'comment_id';
 use constant LIST_ORDER => 'bug_when';
 
+use constant VALIDATORS => {
+    type => \&_check_type,
+};
+
+use constant UPDATE_VALIDATORS => {
+    extra_data => \&_check_extra_data,
+};
+
+#########################
+# Database Manipulation #
+#########################
+
+sub update {
+    my $self = shift;
+    my $changes = $self->SUPER::update(@_);
+    $self->bug->_sync_fulltext();
+    return $changes;
+}
+
 ###############################
 ####      Accessors      ######
 ###############################
 
 sub already_wrapped { return $_[0]->{'already_wrapped'}; }
-sub body        { return $_[0]->{'thetext'}; }
-sub bug_id      { return $_[0]->{'bug_id'}; }
-sub creation_ts { return $_[0]->{'bug_when'}; }
+sub body        { return $_[0]->{'thetext'};   }
+sub bug_id      { return $_[0]->{'bug_id'};    }
+sub creation_ts { return $_[0]->{'bug_when'};  }
 sub is_private  { return $_[0]->{'isprivate'}; }
 sub work_time   { return $_[0]->{'work_time'}; }
+sub type        { return $_[0]->{'type'};      }
+sub extra_data  { return $_[0]->{'extra_data'} }
+
+sub bug {
+    my $self = shift;
+    require Bugzilla::Bug;
+    $self->{bug} ||= new Bugzilla::Bug($self->bug_id);
+    return $self->{bug};
+}
+
+sub is_about_attachment {
+    my ($self) = @_;
+    return 1 if $self->type == CMT_ATTACHMENT_CREATED;
+    return 0;
+}
+
+sub attachment {
+    my ($self) = @_;
+    return undef if not $self->is_about_attachment;
+    $self->{attachment} ||= new Bugzilla::Attachment($self->extra_data);
+    return $self->{attachment};
+}
 
 sub author { 
     my $self = shift;
@@ -81,6 +128,63 @@ sub body_full {
     return $body;
 }
 
+############
+# Mutators #
+############
+
+sub set_extra_data { $_[0]->set('extra_data', $_[1]); }
+
+sub set_type {
+    my ($self, $type, $extra_data) = @_;
+    $self->set('type', $type);
+    $self->set_extra_data($extra_data);
+}
+
+##############
+# Validators #
+##############
+
+sub _check_extra_data {
+    my ($invocant, $extra_data, $type) = @_;
+    $type = $invocant->type if ref $invocant;
+    if ($type == CMT_NORMAL or $type == CMT_POPULAR_VOTES) {
+        if (defined $extra_data) {
+            ThrowCodeError('comment_extra_data_not_allowed',
+                           { type => $type, extra_data => $extra_data });
+        }
+    }
+    else {
+        if (!defined $extra_data) {
+            ThrowCodeError('comment_extra_data_required', { type => $type });
+        }
+        if ($type == CMT_MOVED_TO) {
+            $extra_data = Bugzilla::User->check($extra_data)->login;
+        }
+        elsif ($type == CMT_ATTACHMENT_CREATED) {
+             my $attachment = Bugzilla::Attachment->check({ 
+                 id => $extra_data });
+             $extra_data = $attachment->id;
+        }
+        else {
+            my $original = $extra_data;
+            detaint_natural($extra_data) 
+              or ThrowCodeError('comment_extra_data_not_numeric',
+                                { type => $type, extra_data => $original });
+        }
+    }
+
+    return $extra_data;
+}
+
+sub _check_type {
+    my ($invocant, $type) = @_;
+    $type ||= CMT_NORMAL;
+    my $original = $type;
+    detaint_natural($type)
+        or ThrowCodeError('comment_type_invalid', { type => $original });
+    return $type;
+}
+
 1;
 
 __END__
index 0bce3ed2f995692a3e3c625f85503a71c4470c09..0be6d1efa45aee9fd927e8e4cee14f6ef3bd626f 100644 (file)
@@ -91,6 +91,7 @@ use File::Basename;
     CMT_HAS_DUPE
     CMT_POPULAR_VOTES
     CMT_MOVED_TO
+    CMT_ATTACHMENT_CREATED
 
     THROW_ERROR
     
@@ -276,6 +277,7 @@ use constant CMT_DUPE_OF => 1;
 use constant CMT_HAS_DUPE => 2;
 use constant CMT_POPULAR_VOTES => 3;
 use constant CMT_MOVED_TO => 4;
+use constant CMT_ATTACHMENT_CREATED => 5;
 
 # Determine whether a validation routine should return 0 or throw
 # an error when the validation fails.
index d7eb14c249448e8dc2efdf9c509041782c964c98..0ae909ecde11e3afebec601941fdc46d113b52b2 100644 (file)
@@ -586,6 +586,8 @@ sub update_table_definitions {
     # 2009-11-01 LpSolit@gmail.com - Bug 525025
     _fix_invalid_custom_field_names();
 
+    _move_attachment_creation_comments_into_comment_type();
+
     ################################################################
     # New --TABLE-- changes should go *** A B O V E *** this point #
     ################################################################
@@ -3136,18 +3138,33 @@ sub _add_foreign_keys_to_multiselects {
     }
 }
 
+# This subroutine is used in multiple places (for times when we update
+# the text of comments), so it takes an argument, $bug_ids, which causes
+# it to update bugs_fulltext for those bug_ids instead of populating the
+# whole table.
 sub _populate_bugs_fulltext {
+    my $bug_ids = shift;
     my $dbh = Bugzilla->dbh;
     my $fulltext = $dbh->selectrow_array('SELECT 1 FROM bugs_fulltext '
                                          . $dbh->sql_limit(1));
-    # We only populate the table if it's empty...
-    if (!$fulltext) {
-        # ... and if there are bugs in the bugs table.
-        my $bug_ids = $dbh->selectcol_arrayref('SELECT bug_id FROM bugs');
+    # We only populate the table if it's empty or if we've been given a
+    # set of bug ids.
+    if ($bug_ids or !$fulltext) {
+        $bug_ids ||= $dbh->selectcol_arrayref('SELECT bug_id FROM bugs');
+        # If there are no bugs in the bugs table, there's nothing to populate.
         return if !@$bug_ids;
 
-        print "Populating bugs_fulltext...";
-        print " (this can take a long time.)\n";
+        my $where = "";
+        if ($fulltext) {
+            print "Updating bugs_fulltext...\n";
+            $where = "WHERE " . $dbh->sql_in('bugs.bug_id', $bug_ids);
+            $dbh->do("DELETE FROM bugs_fulltext WHERE " 
+                     . $dbh->sql_in('bug_id', $bug_ids));
+        }
+        else {
+            print "Populating bugs_fulltext...";
+            print " (this can take a long time.)\n";
+        }
         my $newline = $dbh->quote("\n");
         $dbh->do(
             q{INSERT INTO bugs_fulltext (bug_id, short_desc, comments, 
@@ -3155,12 +3172,13 @@ sub _populate_bugs_fulltext {
                    SELECT bugs.bug_id, bugs.short_desc, }
                  . $dbh->sql_group_concat('longdescs.thetext', $newline)
           . ', ' . $dbh->sql_group_concat('nopriv.thetext',    $newline) .
-                  q{ FROM bugs 
+                 qq{ FROM bugs 
                           LEFT JOIN longdescs
                                  ON bugs.bug_id = longdescs.bug_id
                           LEFT JOIN longdescs AS nopriv
                                  ON longdescs.comment_id = nopriv.comment_id
-                                    AND nopriv.isprivate = 0 }
+                                    AND nopriv.isprivate = 0 
+                     $where }
                  . $dbh->sql_group_by('bugs.bug_id', 'bugs.short_desc'));
     }
 }
@@ -3235,6 +3253,55 @@ sub _fix_invalid_custom_field_names {
     }
 }
 
+sub _move_attachment_creation_comments_into_comment_type {
+    my $dbh = Bugzilla->dbh;
+    # We check if there are any CMT_ATTACHMENT_CREATED comments already,
+    # first, because this is faster than a full LIKE search on the comments,
+    # and currently this will run every time we run checksetup.
+    my $test = $dbh->selectrow_array(
+        'SELECT 1 FROM longdescs WHERE type = ' . CMT_ATTACHMENT_CREATED
+        . ' ' . $dbh->sql_limit(1));
+    return if $test;
+    my %comments = @{ $dbh->selectcol_arrayref(
+        "SELECT comment_id, thetext FROM longdescs
+          WHERE thetext LIKE 'Created an attachment (id=%'", 
+        {Columns=>[1,2]}) };
+    my @comment_ids = keys %comments;
+    return if !scalar @comment_ids;
+    print "Setting the type field on attachment creation comments...\n";
+    my $sth = $dbh->prepare(
+        'UPDATE longdescs SET thetext = ?, type = ?, extra_data = ?
+          WHERE comment_id = ?');
+    my $count = 0;
+    my $total = scalar @comment_ids;
+    $dbh->bz_start_transaction();
+    foreach my $id (@comment_ids) {
+        $count++;
+        my $text = $comments{$id};
+        next if $text !~ /attachment \(id=(\d+)/;
+        my $attachment_id = $1;
+        # Now we have to remove the text up until we find a line that's
+        # just a single newline, because the old "Created an attachment"
+        # text included the attachment description underneath it, and in
+        # Bugzillas before 2.20, that could be wrapped into multiple lines,
+        # in the database.
+        my @lines = split("\n", $text);
+        while (1) {
+            my $line = shift @lines;
+            last if (!defined $line or trim($line) eq '');
+        }
+        $text = join("\n", @lines);
+        $sth->execute($text, CMT_ATTACHMENT_CREATED, $attachment_id, $id);
+        indicate_progress({ total => $total, current => $count, 
+                            every => 25 });
+    }
+    my $bug_ids = $dbh->selectcol_arrayref(
+        'SELECT DISTINCT bug_id FROM longdescs WHERE '
+        . $dbh->sql_in('comment_id', \@comment_ids));
+    _populate_bugs_fulltext($bug_ids);
+    $dbh->bz_commit_transaction();
+}
+
 1;
 
 __END__
index f714f04d1580da49949d4c1c48374d913ffc2b3b..0289141646bba8e1b4c78b99ae55f9ec88d7cb41 100644 (file)
@@ -242,12 +242,7 @@ sub quoteUrls {
     $text =~ s~\b(mailto:|)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b
               ~<a href=\"mailto:$2\">$1$2</a>~igx;
 
-    # attachment links - handle both cases separately for simplicity
-    $text =~ s~((?:^Created\ an\ |\b)attachment\s*\(id=(\d+)\)(\s\[edit\])?)
-              ~($things[$count++] = get_attachment_link($2, $1)) &&
-               ("\0\0" . ($count-1) . "\0\0")
-              ~egmx;
-
+    # attachment links
     $text =~ s~\b(attachment$s*\#?$s*(\d+))
               ~($things[$count++] = get_attachment_link($2, $1)) &&
                ("\0\0" . ($count-1) . "\0\0")
index 0fc2db33648e9942ad559cd5ebeaf75278b4bdbd..6a318c93f3c7669d6074d80bdf2add31e8214c4b 100644 (file)
@@ -1390,7 +1390,6 @@ sub wants_bug_mail {
     my $self = shift;
     my ($bug_id, $relationship, $fieldDiffs, $comments, $dependencyText,
         $changer, $bug_is_new) = @_;
-    my $comments_concatenated = join("\n", map { $_->body } @$comments);
 
     # Make a list of the events which have happened during this bug change,
     # from the point of view of this user.    
@@ -1439,7 +1438,7 @@ sub wants_bug_mail {
         }
     }
 
-    if ($comments_concatenated =~ /Created an attachment \(/) {
+    if (grep { $_->type == CMT_ATTACHMENT_CREATED } @$comments) {
         $events{+EVT_ATTACHMENT} = 1;
     }
     elsif (defined($$comments[0])) {
index 006fa0fee9fcc08f1162838579385508bea99b85..5c38ebc49d77440ba24c510ffbf821d0006262bb 100644 (file)
@@ -139,6 +139,8 @@ sub comments {
 # Helper for Bug.comments
 sub _translate_comment {
     my ($self, $comment, $filters) = @_;
+    my $attach_id = $comment->is_about_attachment ? $comment->extra_data
+                                                  : undef;
     return filter $filters, {
         id         => $self->type('int', $comment->id),
         bug_id     => $self->type('int', $comment->bug_id),
@@ -146,6 +148,7 @@ sub _translate_comment {
         time       => $self->type('dateTime', $comment->creation_ts),
         is_private => $self->type('boolean', $comment->is_private),
         text       => $self->type('string', $comment->body_full),
+        attachment_id => $self->type('int', $attach_id),
     };
 }
 
@@ -786,6 +789,11 @@ C<int> The globally unique ID for the comment.
 
 C<int> The ID of the bug that this comment is on.
 
+=item attachment_id
+
+C<int> If the comment was made on an attachment, this will be the
+ID of that attachment. Otherwise it will be null.
+
 =item text
 
 C<string> The actual text of the comment.
@@ -826,6 +834,16 @@ that id.
 
 =back
 
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<3.4>.
+
+=item C<attachment_id> was added to the return value in Bugzilla B<3.6>.
+
+=back
+
 =back
 
 
index a10d9f9704110d030eecc7f4ef4036898deef6c9..a89a46b991b1a51695ec46d2de19cb41a9d2c520 100755 (executable)
@@ -483,11 +483,10 @@ sub insert {
     $attachment->update($timestamp);
 
     # Insert a comment about the new attachment into the database.
-    my $comment = "Created an attachment (id=" . $attachment->id . ")\n" .
-                  $attachment->description . "\n";
-    $comment .= ("\n" . $cgi->param('comment')) if defined $cgi->param('comment');
-
-    $bug->add_comment($comment, { isprivate => $attachment->isprivate });
+    my $comment = $cgi->param('comment');
+    $bug->add_comment($comment, { isprivate => $attachment->isprivate,
+                                  type => CMT_ATTACHMENT_CREATED,
+                                  extra_data => $attachment->id });
 
   # Assign the bug to the user, if they are allowed to take it
   my $owner = "";
index c08f5c8b2dae89f5e8cd1832f4a4f863b3d48cf5..05f95d646235c34a74678c3b79bb814f0de3d7d9 100755 (executable)
@@ -523,6 +523,8 @@ sub process_bug {
             $data = decode_base64($data);
         }
 
+        # For backwards-compatibility with Bugzillas before 3.6:
+        #
         # If we leave the attachment ID in the comment it will be made a link
         # to the wrong attachment. Since the new attachment ID is unknown yet
         # let's strip it out for now. We will make a comment with the right ID
index c9de83bb781a1bf7f3994527d4a0c52b4586eb18..ef0f40d4d106b8681503e03382ef965a15370c62 100755 (executable)
@@ -223,19 +223,9 @@ if (defined($cgi->upload('data')) || $cgi->param('attachurl')) {
                                       $bug, $attachment, $vars, SKIP_REQUESTEE_ON_ERROR);
         $attachment->set_flags($flags, $new_flags);
         $attachment->update($timestamp);
-
-        # Update the comment to include the new attachment ID.
-        # This string is hardcoded here because Template::quoteUrls()
-        # expects to find this exact string.
-        my $new_comment = "Created an attachment (id=" . $attachment->id . ")\n" .
-                          $attachment->description . "\n";
-        # We can use $bug->comments here because we are sure that the bug
-        # description is of type CMT_NORMAL. No need to include it if it's
-        # empty, though.
-        if ($bug->comments->[0]->body !~ /^\s+$/) {
-            $new_comment .= "\n" . $bug->comments->[0]->body;
-        }
-        $bug->update_comment($bug->comments->[0]->id, $new_comment);
+        my $comment = $bug->comments->[0];
+        $comment->set_type(CMT_ATTACHMENT_CREATED, $attachment->id);
+        $comment->update();
     }
     else {
         $vars->{'message'} = 'attachment_creation_failed';
index 8e97d4d081b012afdd06fe6922571c2817014d52..49ab95ad10990f0aedebade2560eee2663854e04 100644 (file)
 [% PROCESS 'global/variables.none.tmpl' %]
 
 [% SET comment_body = comment.body %]
-[% IF is_bugmail %]
-  [% comment_body = comment_body.replace( '(Created an attachment \(id=([0-9]+)\))',
-                                          '$1' _ "\n" _ ' --> (' _ urlbase 
-                                          _ 'attachment.cgi?id=$2)' ) %]
-[% END %]
 
 [% IF comment.type == constants.CMT_DUPE_OF %]
 X[% comment_body %]
@@ -54,6 +49,14 @@ If the move succeeded, [% comment.extra_data %] will receive a mail containing
 the number of the new [% terms.bug %] in the other database.
 If all went well, please paste in a link to the new [% terms.bug %].
 Otherwise, reopen this [% terms.bug %].
+[% ELSIF comment.type == constants.CMT_ATTACHMENT_CREATED %]
+Created attachment [% comment.extra_data %]
+[% IF is_bugmail %]
+  --> [% urlbase _ "attachment.cgi?id=" _ comment.extra_data %]
+[% END %]
+[%+ comment.attachment.description %]
+
+[%+ comment.body %]
 [% ELSE %]
 X[% comment_body %]
 [% END %]
index 31e56d299dd4369cec7b15496f14983e161099b1..2349602dc892b42888b336480913d35ac83bb649 100644 (file)
@@ -69,6 +69,9 @@
           [% NEXT IF c.is_private && !user.is_insider %]
           <long_desc isprivate="[% c.is_private FILTER xml %]">
             <commentid>[% c.id FILTER xml %]</commentid>
+            [% IF c.is_about_attachment %]
+              <attachid>[% c.extra_data FILTER xml %]</attachid>
+            [% END %]
             <who name="[% c.author.name FILTER xml %]">[% c.author.email FILTER email FILTER xml %]</who>
             <bug_when>[% c.creation_ts FILTER time("%Y-%m-%d %T %z") FILTER xml %]</bug_when>
             [% IF user.is_timetracker && (c.work_time - 0 != 0) %]
index 09a1c4cf608bfb6c7a6a18af38b52dc41a642f0a..e851a00d9c8f86e693577d11f3c36cd62230b625 100644 (file)
     without specifying a default or something for $set_nulls_to, because
     there are NULL values currently in it.
 
+  [% ELSIF error == "comment_extra_data_not_allowed" %]
+    You tried to set the <code>extra_data</code> field to 
+    '[% extra_data FILTER html %]' but comments of type [% type FILTER html %]
+    do not accept an <code>extra_data</code> argument.
+
+  [% ELSIF error == "comment_extra_data_required" %]
+    Comments of type [% type FILTER html %] require an <code>extra_data</code>
+    argument to be set.
+
+  [% ELSIF error == "comment_extra_data_not_numeric" %]
+    You tried to set the <code>extra_data</code> field to
+    '[% extra_data FILTER html %]' but comments of type [% type FILTER html %]
+    require a numeric <code>extra_data</code> argument.
+
+  [% ELSIF error == "comment_type_invalid" %]
+    '[% type FILTER html %]' is not a valid comment type.
+
   [% ELSIF error == "db_rename_conflict" %]
     Name conflict: Cannot rename [% old FILTER html %] to
     [% new FILTER html %] because [% new FILTER html %] already exists.
index 144e2e7eabfe27f2b2ffb86ed8301d4f4c824215..c3e84c0ab32972b382c290ac88fa5d62213be7df 100644 (file)
 [% PROCESS global/footer.html.tmpl %]
 
 [% BLOCK object_name %]
-  [% IF class == "Bugzilla::User" %]
+  [% IF class == "Bugzilla::Attachment" %]
+    attachment
+  [% ELSIF class == "Bugzilla::User" %]
     user
   [% ELSIF class == "Bugzilla::Component" %]
     component