From ba0765bf4dc468815e4fa45509010c0cd675e5b2 Mon Sep 17 00:00:00 2001 From: Byron Jones Date: Mon, 25 Nov 2013 16:21:03 +0800 Subject: Bug 793963: add the ability to tag comments with arbitrary tags r=dkl, a=glob --- Bugzilla/Comment.pm | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 189 insertions(+), 6 deletions(-) (limited to 'Bugzilla/Comment.pm') diff --git a/Bugzilla/Comment.pm b/Bugzilla/Comment.pm index d4e3383ea..d1e1e2530 100644 --- a/Bugzilla/Comment.pm +++ b/Bugzilla/Comment.pm @@ -13,11 +13,13 @@ use strict; use parent qw(Bugzilla::Object); use Bugzilla::Attachment; +use Bugzilla::Comment::TagWeights; use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::User; use Bugzilla::Util; +use List::Util qw(first); use Scalar::Util qw(blessed); ############################### @@ -79,21 +81,90 @@ use constant VALIDATOR_DEPENDENCIES => { sub update { my $self = shift; - my $changes = $self->SUPER::update(@_); - $self->bug->_sync_fulltext( update_comments => 1); + my ($changes, $old_comment) = $self->SUPER::update(@_); + + if (exists $changes->{'thetext'} || exists $changes->{'isprivate'}) { + $self->bug->_sync_fulltext( update_comments => 1); + } + + my @old_tags = @{ $old_comment->tags }; + my @new_tags = @{ $self->tags }; + my ($removed_tags, $added_tags) = diff_arrays(\@old_tags, \@new_tags); + + if (@$removed_tags || @$added_tags) { + my $dbh = Bugzilla->dbh; + my $when = $dbh->selectrow_array("SELECT LOCALTIMESTAMP(0)"); + my $sth_delete = $dbh->prepare( + "DELETE FROM longdescs_tags WHERE comment_id = ? AND tag = ?" + ); + my $sth_insert = $dbh->prepare( + "INSERT INTO longdescs_tags(comment_id, tag) VALUES (?, ?)" + ); + my $sth_activity = $dbh->prepare( + "INSERT INTO longdescs_tags_activity + (bug_id, comment_id, who, bug_when, added, removed) + VALUES (?, ?, ?, ?, ?, ?)" + ); + + foreach my $tag (@$removed_tags) { + my $weighted = Bugzilla::Comment::TagWeights->new({ name => $tag }); + if ($weighted) { + if ($weighted->weight == 1) { + $weighted->remove_from_db(); + } else { + $weighted->set_weight($weighted->weight - 1); + $weighted->update(); + } + } + trick_taint($tag); + $sth_delete->execute($self->id, $tag); + $sth_activity->execute( + $self->bug_id, $self->id, Bugzilla->user->id, $when, '', $tag); + } + + foreach my $tag (@$added_tags) { + my $weighted = Bugzilla::Comment::TagWeights->new({ name => $tag }); + if ($weighted) { + $weighted->set_weight($weighted->weight + 1); + $weighted->update(); + } else { + Bugzilla::Comment::TagWeights->create({ tag => $tag, weight => 1 }); + } + trick_taint($tag); + $sth_insert->execute($self->id, $tag); + $sth_activity->execute( + $self->bug_id, $self->id, Bugzilla->user->id, $when, $tag, ''); + } + } + return $changes; } -# Speeds up displays of comment lists by loading all ->author objects -# at once for a whole list. +# Speeds up displays of comment lists by loading all author objects and tags at +# once for a whole list. sub preload { my ($class, $comments) = @_; + # Author my %user_ids = map { $_->{who} => 1 } @$comments; my $users = Bugzilla::User->new_from_list([keys %user_ids]); my %user_map = map { $_->id => $_ } @$users; foreach my $comment (@$comments) { $comment->{author} = $user_map{$comment->{who}}; } + # Tags + if (Bugzilla->params->{'comment_taggers_group'}) { + my $dbh = Bugzilla->dbh; + my @comment_ids = map { $_->id } @$comments; + my %comment_map = map { $_->id => $_ } @$comments; + my $rows = $dbh->selectall_arrayref( + "SELECT comment_id, " . $dbh->sql_group_concat('tag', "','") . " + FROM longdescs_tags + WHERE " . $dbh->sql_in('comment_id', \@comment_ids) . " + GROUP BY comment_id"); + foreach my $row (@$rows) { + $comment_map{$row->[0]}->{tags} = [ split(/,/, $row->[1]) ]; + } + } } ############################### @@ -113,6 +184,39 @@ sub work_time { sub type { return $_[0]->{'type'}; } sub extra_data { return $_[0]->{'extra_data'} } +sub tags { + my ($self) = @_; + return [] unless Bugzilla->params->{'comment_taggers_group'}; + $self->{'tags'} ||= Bugzilla->dbh->selectcol_arrayref( + "SELECT tag + FROM longdescs_tags + WHERE comment_id = ? + ORDER BY tag", + undef, $self->id); + return $self->{'tags'}; +} + +sub collapsed { + my ($self) = @_; + return 0 unless Bugzilla->params->{'comment_taggers_group'}; + return $self->{collapsed} if exists $self->{collapsed}; + $self->{collapsed} = 0; + Bugzilla->request_cache->{comment_tags_collapsed} + ||= [ split(/\s*,\s*/, Bugzilla->params->{'collapsed_comment_tags'}) ]; + my @collapsed_tags = @{ Bugzilla->request_cache->{comment_tags_collapsed} }; + foreach my $my_tag (@{ $self->tags }) { + $my_tag = lc($my_tag); + foreach my $collapsed_tag (@collapsed_tags) { + if ($my_tag eq lc($collapsed_tag)) { + $self->{collapsed} = 1; + last; + } + } + last if $self->{collapsed}; + } + return $self->{collapsed}; +} + sub bug { my $self = shift; require Bugzilla::Bug; @@ -170,6 +274,26 @@ sub set_is_private { $_[0]->set('isprivate', $_[1]); } sub set_type { $_[0]->set('type', $_[1]); } sub set_extra_data { $_[0]->set('extra_data', $_[1]); } +sub add_tag { + my ($self, $tag) = @_; + $tag = $self->_check_tag($tag); + + my $tags = $self->tags; + return if grep { lc($tag) eq lc($_) } @$tags; + push @$tags, $tag; + $self->{'tags'} = [ sort @$tags ]; +} + +sub remove_tag { + my ($self, $tag) = @_; + $tag = $self->_check_tag($tag); + + my $tags = $self->tags; + my $index = first { lc($tags->[$_]) eq lc($tag) } 0..scalar(@$tags) - 1; + return unless defined $index; + splice(@$tags, $index, 1); +} + ############## # Validators # ############## @@ -312,6 +436,17 @@ sub _check_isprivate { return $isprivate ? 1 : 0; } +sub _check_tag { + my ($invocant, $tag) = @_; + length($tag) < MIN_COMMENT_TAG_LENGTH + and ThrowUserError('comment_tag_too_short', { tag => $tag }); + length($tag) > MAX_COMMENT_TAG_LENGTH + and ThrowUserError('comment_tag_too_long', { tag => $tag }); + $tag =~ /^[\w\d\._-]+$/ + or ThrowUserError('comment_tag_invalid', { tag => $tag }); + return $tag; +} + sub count { my ($self) = @_; @@ -326,7 +461,7 @@ sub count { undef, $self->bug_id, $self->creation_ts); return --$self->{'count'}; -} +} 1; @@ -372,7 +507,7 @@ C Time spent as related to this comment. =item C -C Comment is marked as private +C Comment is marked as private. =item C @@ -387,6 +522,54 @@ L who created the comment. C The position this comment is located in the full list of comments for a bug starting from 0. +=item C + +C Comment should be displayed as collapsed by default. + +=item C + +C The tags attached to the comment. + +=item C + +=over + +=item B + +Attaches the specified tag to the comment. + +=item B + +=over + +=item C + +C The tag to attach. + +=back + +=back + +=item C + +=over + +=item B + +Detaches the specified tag from the comment. + +=item B + +=over + +=item C + +C The tag to detach. + +=back + +=back + =item C =over -- cgit v1.2.3-24-g4f1b