diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 471e940c7..123f42758 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -11,6 +11,9 @@ - fixed: Thingy list type form elements do not support key/value pairs (SDH Consulting Group) - rfe: enable/disable pagination in asset manager (#756) + - Added Comments asset aspect, which allows comments to be added to any asset + easily. + - Added comments aspect to wiki. - Removed cart icon from ViewCart macro. - Updated WebGUI::Shop::PayDriver::processTransaction() to accept a transaction as a param. diff --git a/docs/upgrades/packages-7.6.1/default-wiki-page.wgpkg b/docs/upgrades/packages-7.6.1/default-wiki-page.wgpkg new file mode 100644 index 000000000..d6f6ed5f1 Binary files /dev/null and b/docs/upgrades/packages-7.6.1/default-wiki-page.wgpkg differ diff --git a/docs/upgrades/upgrade_7.6.0-7.6.1.pl b/docs/upgrades/upgrade_7.6.0-7.6.1.pl index beeffbfa0..4acd1d045 100644 --- a/docs/upgrades/upgrade_7.6.0-7.6.1.pl +++ b/docs/upgrades/upgrade_7.6.0-7.6.1.pl @@ -32,16 +32,44 @@ addExportExtensionsToConfigFile($session); fixShortAssetIds( $session ); addDataFormDataIndexes($session); addThingyColumns( $session ); +addCommentsAspect( $session ); +addCommentsAspectToWiki( $session ); finish($session); # this line required +#---------------------------------------------------------------------------- +sub addCommentsAspectToWiki { + my $session = shift; + print "\tAdding comments aspect to wiki..." unless $quiet; + my $db = $session->db; + my $pages = $db->read("select assetId,revisionDate from WikiPage"); + while (my ($id, $rev) = $pages->array) { + $db->write("insert into assetAspectComments (assetId, revisionDate, comments, averageCommentRating) values (?,?,'[]',0)",[$id,$rev]); + } + print "Done.\n" unless $quiet; +} + +#---------------------------------------------------------------------------- +sub addCommentsAspect { + my $session = shift; + print "\tAdding comments asset aspect..." unless $quiet; + $session->db->write("create table assetAspectComments ( + assetId char(22) binary not null, + revisionDate bigint not null, + comments mediumtext, + averageCommentRating int, + primary key (assetId, revisionDate) + )"); + print "Done.\n" unless $quiet; +} + #---------------------------------------------------------------------------- # make sure each config file has the extensions to export as-is. however, if # this system received a backport, leave the field as is. sub addExportExtensionsToConfigFile { my $session = shift; - print "Adding binary export extensions to config file... " unless $quiet; + print "\tAdding binary export extensions to config file... " unless $quiet; # skip if the field has been defined already by backporting return if defined $session->config->get('exportBinaryExtensions'); @@ -53,6 +81,7 @@ sub addExportExtensionsToConfigFile { print "Done.\n" unless $quiet; } +#---------------------------------------------------------------------------- sub fixShortAssetIds { print "Fixing assets with short ids... " unless $quiet; my %assetIds = ( diff --git a/lib/WebGUI/Asset/WikiPage.pm b/lib/WebGUI/Asset/WikiPage.pm index 3bcce40cd..906dd7a49 100644 --- a/lib/WebGUI/Asset/WikiPage.pm +++ b/lib/WebGUI/Asset/WikiPage.pm @@ -10,8 +10,9 @@ package WebGUI::Asset::WikiPage; # http://www.plainblack.com info@plainblack.com # ------------------------------------------------------------------- -use base 'WebGUI::Asset'; use strict; +use Class::C3; +use base qw(WebGUI::AssetAspect::Comments WebGUI::Asset); use Tie::IxHash; use WebGUI::International; use WebGUI::Utility; @@ -40,7 +41,7 @@ Override the default method in order to deal with attachments. sub addRevision { my $self = shift; - my $newSelf = $self->SUPER::addRevision(@_); + my $newSelf = $self->next::method(@_); my $now = time(); $newSelf->update({ isHidden => 1, @@ -52,7 +53,7 @@ sub addRevision { sub canAdd { my $class = shift; my $session = shift; - $class->SUPER::canAdd($session, undef, '7'); + $class->next::method($session, undef, '7'); } #------------------------------------------------------------------- @@ -109,16 +110,7 @@ sub definition { properties => \%properties, }; - return $class->SUPER::definition($session, $definition); -} - - -#------------------------------------------------------------------- -# BUGGO: how to handle this? -sub duplicate { - my $self = shift; - my $newAsset = $self->SUPER::duplicate(@_); - return $newAsset; + return $class->next::method($session, $definition); } @@ -192,7 +184,7 @@ sub getWiki { #------------------------------------------------------------------- sub indexContent { my $self = shift; - my $indexer = $self->SUPER::indexContent; + my $indexer = $self->next::method; $indexer->addKeywords($self->get('content')); return $indexer; } @@ -216,7 +208,7 @@ sub preparePageTemplate { #------------------------------------------------------------------- sub prepareView { my $self = shift; - $self->SUPER::prepareView; + $self->next::method; $self->preparePageTemplate; } @@ -224,24 +216,27 @@ sub prepareView { #------------------------------------------------------------------- sub processPropertiesFromFormPost { my $self = shift; - $self->SUPER::processPropertiesFromFormPost(@_); + $self->next::method(@_); my $actionTaken = ($self->session->form->process("assetId") eq "new") ? "Created" : "Edited"; my $wiki = $self->getWiki; - $self->update({ groupIdView => $wiki->get('groupIdView'), - groupIdEdit => $wiki->get('groupToAdminister'), - actionTakenBy => $self->session->user->userId, - actionTaken => $actionTaken, - }); + my $properties = { + groupIdView => $wiki->get('groupIdView'), + groupIdEdit => $wiki->get('groupToAdminister'), + actionTakenBy => $self->session->user->userId, + actionTaken => $actionTaken, + }; if ($wiki->canAdminister) { - $self->update({isProtected => $self->session->form("isProtected")}); + $properties->{isProtected} = $self->session->form->get("isProtected"); } + $self->update($properties); + + # deal with attachments from the attachments form control my $options = { maxImageSize => $wiki->get('maxImageSize'), thumbnailSize => $wiki->get('thumbnailSize'), }; - # deal with attachments from the attachments form control my @attachments = $self->session->form->param("attachments"); my @tags = (); foreach my $assetId (@attachments) { @@ -311,7 +306,8 @@ Wrap update to force isHidden to be on, all the time. sub update { my $self = shift; my $properties = shift; - return $self->SUPER::update({%$properties, isHidden => 1}); + $properties->{isHidden} = 1; + return $self->next::method($properties); } #------------------------------------------------------------------- @@ -346,6 +342,7 @@ sub view { historyUrl => $self->getUrl("func=getHistory"), editContent => $self->getEditForm, allowsAttachments => $self->getWiki->get("allowAttachments"), + comments => $self->getFormattedComments(), content => $self->getWiki->autolinkHtml( $self->scrubContent, {skipTitles => [$self->get('title')]}, diff --git a/lib/WebGUI/AssetAspect/Comments.pm b/lib/WebGUI/AssetAspect/Comments.pm new file mode 100644 index 000000000..9e5cb532a --- /dev/null +++ b/lib/WebGUI/AssetAspect/Comments.pm @@ -0,0 +1,363 @@ +package WebGUI::AssetAspect::Comments; + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2008 Plain Black Corporation. + ------------------------------------------------------------------- + Please read the legal notices (docs/legal.txt) and the license + (docs/license.txt) that came with this distribution before using + this software. + ------------------------------------------------------------------- + http://www.plainblack.com info@plainblack.com + ------------------------------------------------------------------- + +=cut + +use strict; +use base 'Class::C3'; +use JSON; +use Tie::IxHash; +use WebGUI::Exception; +use WebGUI::Form; +use WebGUI::HTML; +use WebGUI::Utility; + +=head1 NAME + +Package WebGUI::AssetAspect::Comments + +=head1 DESCRIPTION + +This is an aspect which makes adding comments to existing assets trivial. + +=head1 SYNOPSIS + + use Class::C3; + use base qw(WebGUI::AssetAspect::Comments WebGUI::Asset); + +And then where-ever you would call $self->SUPER::someMethodName call $self->next::method instead. + +=head1 METHODS + +These methods are available from this class: + +=cut + +#------------------------------------------------------------------- + +=head2 addComment ( comment [, rating, user ] ) + +Posts a comment. + +=head3 comment + +A string that acts as a comment from a user. + +=head3 rating + +Defaults to 0. An integer between 0 and 5 inclusive. 0 represents N/A, 1 represents a negative rating, 3 represents a neutral rating, and 5 represents a positive rating. + +=head3 user + +Defaults to the current user. A WebGUI::User object. + +=cut + +sub addComment { + my ($self, $comment, $rating, $user) = @_; + my $session = $self->session; + $user ||= $session->user; + $rating ||= 0; + + # add the new comment to the list of comments + my $comments = $self->get('comments'); + push @$comments, { + id => $session->id->generate, + alias => $user->profileField('alias'), + userId => $user->userId, + comment => $comment, + rating => $rating, + date => time(), + ip => $session->var->get('lastIP'), + }; + + # calculate average + my $sum = 0; + my $count = 0; + foreach my $comment (@$comments) { + next unless $comment->{rating} > 0; # skip n/a ratings + $count++; + $sum += $comment->{rating}; + } + my $average = 0; + if ($count > 0) { + $average = $sum/$count; + } + + # update the database + $self->update({comments=>$comments, averageCommentRating=>$average}); + + # add karma + if ($session->setting->get('useKarma')) { + unless ($user->isVisitor) { + $user->karma($self->getKarmaAmountPerComment, $self->getId, 'Left comment for '.$self->getName.' '.$self->getTitle); + } + } +} + +#------------------------------------------------------------------- + +=head2 canComment () + +Returns a boolean indicating whether the current user can post a comment. + +=cut + +sub canComment { + my $self = shift; + return $self->session->user->isInGroup($self->getGroupToComment) || $self->canEdit; +} + + +#------------------------------------------------------------------- + +=head2 definition + +Extends the definition to add the comments and averageCommentRating fields. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my %properties; + tie %properties, 'Tie::IxHash'; + %properties = ( + comments => { + noFormPost => 1, + fieldType => "hidden", + defaultValue => [], + }, + averageCommentRating => { + noFormPost => 1, + fieldType => "hidden", + defaultValue => 0, + }, + ); + push(@{$definition}, { + autoGenerateForms => 1, + tableName => 'assetAspectComments', + className => 'WebGUI::Asset::Sku::BazaarItem', + properties => \%properties + }); + return $class->next::method($session, $definition); +} + +#------------------------------------------------------------------- + +=head2 deleteComment ( id ) + +Deletes a comment. + +=head3 id + +The GUID for the comment to delete. + +=cut + +sub deleteComment { + my ($self, $id) = @_; + my $session = $self->session; + + # remove the comment from the list of comments and calculate the average + my $comments = $self->get('comments'); + my @updatedComments; + my $sum = 0; + my $count = 0; + my $userId; + foreach my $comment (@$comments) { + if ($comment->{id} eq $id) { + $userId = $comment->{userId}; + next; + } + push @updatedComments, $comment; + next unless $comment->{rating} > 0; # skip n/a ratings + $count++; + $sum += $comment->{rating}; + } + + # update the database + my $average = 0; + if ($count > 0) { + $average = $sum/$count; + } + $self->update({comments=>\@updatedComments, averageCommentRating=>$average}); + + # remove karma + if ($session->setting->get('useKarma')) { + if (defined $userId) { + my $user = WebGUI::User->new($session, $userId); + unless ($user->isVisitor) { + $user->karma(($self->getKarmaAmountPerComment * -1), $self->getId, 'Deleted comment for '.$self->getName.' '.$self->getTitle); + } + } + } +} + +#------------------------------------------------------------------- + +=head2 get () + +See SUPER::get(). Extends the get() method to automatically decode the comments field into a Perl hash structure. + +=cut + +sub get { + my $self = shift; + my $param = shift; + if ($param eq 'comments') { + return JSON->new->decode($self->next::method('comments')||'[]'); + } + return $self->next::method($param, @_); +} + +#------------------------------------------------------------------- + +=head2 getAverageCommentRatingIcon () + +Returns the HTML needed to render the average rating icon. + +=cut + +sub getAverageCommentRatingIcon { + my $self = shift; + return q{}.$self->get('averageCommentRating').q{}; + +} + +#------------------------------------------------------------------- + +=head2 getFormattedComments () + +Returns an HTML string listing the comments so far and the leave a comment form if the user canComment(). + +=cut + +sub getFormattedComments { + my $self = shift; + my $session = $self->session; + my $url = $session->url; + my $out = '
'; + my $canEdit = $self->canEdit; + my $comments = $self->get('comments'); + foreach my $comment (@$comments) { + $out .= q{
}.$comment->{rating}.q{}; + if ($canEdit) { + $out .= q{ {id}).q{">[X] }; + } + $out .= q{}.$comment->{alias}.q{: "}.WebGUI::HTML::format($comment->{comment},'text').q{"
}; + } + if ($self->canComment) { + $out .= '
'; + $out .= WebGUI::Form::formHeader($session, {action=>$self->getUrl}); + $out .= WebGUI::Form::hidden($session, {name=>"func",value=>"addComment"}); + $out .= WebGUI::Form::textarea($session, {name=>"comment"}); + $out .= WebGUI::Form::commentRating($session, {name=>"rating"}); + $out .= WebGUI::Form::submit($session); + $out .= WebGUI::Form::formFooter($session); + $out .= '
'; + } + $out .= '
'; + return $out; +} + +#------------------------------------------------------------------- + +=head2 getGroupToComment () + +Returns '2' aka Registered Users. However, should be overridden by subclasses that wish to make this a settable property. + +=cut + +sub getGroupToComment { + return '2'; +} + +#------------------------------------------------------------------- + +=head2 getKarmaAmountPerComment () + +Returns 3. However, should be overridden by subclasses that wish to make this a settable property. + +=cut + +sub getKarmaAmountPerComment { + return 3; +} + + +#------------------------------------------------------------------- + +=head2 update () + +See SUPER::update(). Extends the update() method to encode the comments field into something storable in the database. + +=cut + +sub update { + my $self = shift; + my $properties = shift; + if (exists $properties->{comments}) { + my $comments = $properties->{comments}; + if (ref $comments ne 'ARRAY') { + $comments = eval{JSON->new->decode($comments)}; + if (WebGUI::Error->caught || ref $comments ne 'ARRAY') { + $comments = []; + } + } + $properties->{comments} = JSON->new->encode($comments); + } + $self->next::method($properties, @_); +} + +#------------------------------------------------------------------- + +=head2 www_addComment () + +Posts a comment after verifying the user's privileges. + +=cut + +sub www_addComment { + my $self = shift; + my $session = $self->session; + return $session->privilege->insufficient() unless ($self->canComment); + my $form = $session->form; + my $comment = $form->get('comment','textarea'); + WebGUI::Macro::negate(\$comment); + if ($comment ne '') { + $self->addComment($comment, $form->get('rating','commentRating')); + } + $self->www_view; +} + +#------------------------------------------------------------------- + +=head2 www_deleteComment () + +Removes a comment. + +=cut + +sub www_deleteComment { + my $self = shift; + my $session = $self->session; + return $session->privilege->insufficient() unless ($self->canEdit); + $self->deleteComment($session->form->get('commentId')); + $self->www_view; +} + +1; + diff --git a/lib/WebGUI/Form/CommentRating.pm b/lib/WebGUI/Form/CommentRating.pm new file mode 100644 index 000000000..c5f56bfdb --- /dev/null +++ b/lib/WebGUI/Form/CommentRating.pm @@ -0,0 +1,144 @@ +package WebGUI::Form::CommentRating; + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2008 Plain Black Corporation. + ------------------------------------------------------------------- + Please read the legal notices (docs/legal.txt) and the license + (docs/license.txt) that came with this distribution before using + this software. + ------------------------------------------------------------------- + http://www.plainblack.com info@plainblack.com + ------------------------------------------------------------------- + +=cut + +use strict; +use base 'WebGUI::Form::RadioList'; + +=head1 NAME + +Package WebGUI::Form::CommentRating + +=head1 DESCRIPTION + +Displays a comment rating field (unhappy to happy). + +=head1 SEE ALSO + +This is a subclass of WebGUI::Form::Control::RadioList. + +=head1 METHODS + +The following methods are specifically available from this class. Check the superclass for additional methods. + +=cut + +#------------------------------------------------------------------- + +=head2 definition ( [ additionalTerms ] ) + +See the super class for additional details. + +=head3 additionalTerms + +The following additional parameters have been added via this sub class. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift || []; + push(@{$definition}, { + }); + return $class->SUPER::definition($session, $definition); +} + +#------------------------------------------------------------------- + +=head2 getDefaultValue ( ) + +Returns 0 + +=cut + +sub getDefaultValue { + return 0; +} + +#------------------------------------------------------------------- + +=head2 getName ( session ) + +Returns the name of the form control. + +=cut + +sub getName { + my ($class, $session) = @_; + return 'Comment Rating'; +} + +#------------------------------------------------------------------- + +=head2 getOptions ( ) + +Options are passed in for many list types. Those options can come in as a hash ref, or a \n separated string, or a key|value\n separated string. This method returns a hash ref regardless of what's passed in. + +=cut + +sub getOptions { + my ($self) = @_; + my %options = (); + tie %options, 'Tie::IxHash'; + my $url = $self->session->url; + %options = ( + 0 => q{0}, + 1 => q{1}, + 2 => q{2}, + 3 => q{3}, + 4 => q{4}, + 5 => q{5}, + ); + return \%options; +} + +#------------------------------------------------------------------- + +=head2 getValue ( [ value ] ) + +Does some special processing. + +=cut + +sub getValue { + my $self = shift; + my $value = $self->SUPER::getValue(@_); + + if ($value !~ m/^\d+$/ || $value < 1 || $value > 5) { + $value = $self->getDefaultValue; + } + + return $value; +} + +#------------------------------------------------------------------- + +=head2 getValueAsHtml ( ) + +Formats as an icon. + +=cut + +sub getValueAsHtml { + my $self = shift; + my $value = $self->getValue; + my $url = $self->session->url; + return q{}.$value.q{}; +} + + +1; + diff --git a/t/Asset/WikiPage.t b/t/Asset/WikiPage.t index 1dd4d727d..2f445b2d9 100644 --- a/t/Asset/WikiPage.t +++ b/t/Asset/WikiPage.t @@ -16,7 +16,7 @@ use lib "$FindBin::Bin/../lib"; use WebGUI::Test; use WebGUI::Session; -use Test::More tests => 5; # increment this value for each test you create +use Test::More tests => 14; # increment this value for each test you create use WebGUI::Asset::Wobject::WikiMaster; use WebGUI::Asset::WikiPage; @@ -46,6 +46,34 @@ my $wikiPageCopy = $wikipage->duplicate(); isa_ok($wikiPageCopy, 'WebGUI::Asset::WikiPage'); my $thirdVersionTag = WebGUI::VersionTag->new($session,$wikiPageCopy->get("tagId")); + +################## +# This section tests the Comments aspect +################## + +is(ref $wikipage->get('comments'), "ARRAY", "Comments Aspect property returns an array ref"); + +my $firstComment = 'what say you fuzzy britches'; +$wikipage->addComment($firstComment,5); +my $secondComment = "i don't have her stuffed down my pants right now, sorry to say"; +$wikipage->addComment($secondComment, 1); + +my $comments = $wikipage->get('comments'); +is(scalar(@{$comments}), 2, "2 comments have been added"); +is($wikipage->get('averageCommentRating'), 3, 'average rating works'); +is($comments->[0]{comment}, $firstComment, "adding initial comment checks out"); +is($comments->[0]{rating}, 5, "adding initial comment rating checks out"); +is($comments->[1]{comment}, $secondComment, "adding additional comments checks out"); +is($comments->[1]{rating}, 1, "adding additional comment rating checks out"); + +$wikipage->deleteComment($comments->[0]{id}); +$comments = $wikipage->get('comments'); +is($comments->[0]{comment}, $secondComment, "you can delete a comment"); +is($wikipage->get('averageCommentRating'), 1, 'average rating is adjusted after deleting a comment'); + + +################## + TODO: { local $TODO = "Tests to make later"; ok(0, 'Lots and lots to do'); diff --git a/www/extras/form/CommentRating/0.png b/www/extras/form/CommentRating/0.png new file mode 100644 index 000000000..270e92ff1 Binary files /dev/null and b/www/extras/form/CommentRating/0.png differ diff --git a/www/extras/form/CommentRating/1.png b/www/extras/form/CommentRating/1.png new file mode 100644 index 000000000..c478d707f Binary files /dev/null and b/www/extras/form/CommentRating/1.png differ diff --git a/www/extras/form/CommentRating/2.png b/www/extras/form/CommentRating/2.png new file mode 100644 index 000000000..b8d4f0746 Binary files /dev/null and b/www/extras/form/CommentRating/2.png differ diff --git a/www/extras/form/CommentRating/3.png b/www/extras/form/CommentRating/3.png new file mode 100644 index 000000000..f18954025 Binary files /dev/null and b/www/extras/form/CommentRating/3.png differ diff --git a/www/extras/form/CommentRating/4.png b/www/extras/form/CommentRating/4.png new file mode 100644 index 000000000..9ae1a0ac5 Binary files /dev/null and b/www/extras/form/CommentRating/4.png differ diff --git a/www/extras/form/CommentRating/5.png b/www/extras/form/CommentRating/5.png new file mode 100644 index 000000000..36b530e10 Binary files /dev/null and b/www/extras/form/CommentRating/5.png differ